Change translation service from Bing to Yandex

The Bing API has been replaced with Azure, which, from a quick look,
seems to require a bunch of personal information at sign-up.

So Yandex it is until we can find something better.
master
Chimo 6 years ago
parent b32c3482c6
commit 1c9850daec
  1. 50
      TranslateNoticePlugin.php
  2. 29
      actions/translatenotice.php
  3. 2
      actions/translatenoticesettings.php
  4. 21
      forms/translatenoticesettings.php
  5. 61
      js/gs-translate.js
  6. 171
      lib/MicrosoftTranslator.php
  7. 43
      lib/YandexTranslator.php

@ -4,20 +4,14 @@ if (!defined('GNUSOCIAL')) {
exit(1);
}
require_once INSTALLDIR . '/plugins/TranslateNotice/lib/MicrosoftTranslator.php';
require_once INSTALLDIR . '/local/plugins/TranslateNotice/lib/YandexTranslator.php';
class TranslateNoticePlugin extends Plugin
{
const VERSION = '0.0.1';
const VERSION = '0.1.0';
private $translator;
function initialize() {
$this->translator = new MicrosoftTranslator($this->client_id, $this->client_secret);
return true;
}
function onRouterInitialized($m) {
$m->connect(
'settings/translatenotice', array(
@ -26,8 +20,8 @@ class TranslateNoticePlugin extends Plugin
);
$m->connect(
'main/translatenotice/renewtoken', array(
'action' => 'renewtoken'
'main/translatenotice/translatenotice', array(
'action' => 'translatenotice'
)
);
@ -39,7 +33,6 @@ class TranslateNoticePlugin extends Plugin
return;
}
// TODO: server-side fallback
$item->out->element('a', array('href' => '#', 'class' => 'gs-translate'), 'Translate this notice');
return true;
@ -54,42 +47,17 @@ class TranslateNoticePlugin extends Plugin
}
function onEndShowScripts($action) {
if (!common_logged_in()) {
return;
}
// Tell the JS which language we should translate to
$targetLanguage = Translate_notice::getKV('user_id', common_current_user()->id);
if ($targetLanguage === false) {
$targetLanguage = 'en';
} else {
$targetLanguage = $targetLanguage->target_lang;
}
try {
// Pass the access token to the JS
// TODO: Set this as cookie?
$accessToken = $this->translator->getAccessToken();
$action->inlineScript(
'var gsTranslate = {};' .
'gsTranslate.accessToken = {' .
'token: "' . $accessToken->token . '",' .
'expires: "' . $accessToken->expires .
'"};' .
'gsTranslate.targetLanguage = "' . $targetLanguage . '";'
);
if (common_logged_in()) {
$action->script($this->path('js/gs-translate.js'));
} catch (Exception $e) {
$action->inlineScript('// ' . $e->getMessage());
}
return true;
}
function onEndShowStyles($action) {
$action->cssLink($this->path('css/gs-translate.css'));
if (common_logged_in()) {
$action->cssLink($this->path('css/gs-translate.css'));
}
return true;
}
@ -105,7 +73,7 @@ class TranslateNoticePlugin extends Plugin
{
$versions[] = array('name' => 'TranslateNotice',
'version' => self::VERSION,
'author' => 'Stephane Berube',
'author' => 'chimo',
'homepage' => 'https://github.com/chimo/gs-translate',
'description' =>
// TRANS: Plugin description.

@ -4,40 +4,45 @@ if (!defined('GNUSOCIAL')) {
exit(1);
}
require_once INSTALLDIR . '/plugins/TranslateNotice/lib/MicrosoftTranslator.php';
require_once INSTALLDIR . '/local/plugins/TranslateNotice/lib/YandexTranslator.php';
class RenewtokenAction extends Action
class TranslatenoticeAction extends Action
{
function handle($args)
function handle()
{
GNUsocial::setApi(true);
$user = common_current_user();
if (!common_logged_in()) { // Make sure we're logged in
$this->clientError(_('Not logged in.'));
return;
} else if (!common_is_real_login()) { // Make _really_ sure we're logged in...
common_set_returnto($this->selfUrl());
$user = common_current_user();
if (Event::handle('RedirectToLogin', array($this, $user))) {
common_redirect(common_local_url('login'), 303);
}
} else { // k, I think by now we're logged in. For realz.
$this->user = common_current_user();
}
// Get plugin settings
$plugins = GNUsocial::getActivePlugins();
$transl_attrs = $plugins['TranslateNotice'];
// Get auth info
$client_id = $transl_attrs['client_id'];
$client_secret = $transl_attrs['client_secret'];
// Get API key
$api_key = $transl_attrs['api_key'];
$translator = new MicrosoftTranslator($client_id, $client_secret);
$accessToken = $translator->getAccessToken();
// Params
$text = $this->trimmed('text');
$target_language = Translate_notice::getTargetLanguage($user);
$translator = new YandexTranslator($api_key);
$results = $translator->translate($text, $target_language);
header('Content-Type: application/json; charset=utf-8');
print json_encode($accessToken);
print $results;
}
function isReadOnly($args) {

@ -4,7 +4,7 @@ if (!defined('GNUSOCIAL')) {
exit(1);
}
require_once INSTALLDIR . '/plugins/TranslateNotice/lib/MicrosoftTranslator.php';
require_once INSTALLDIR . '/local/plugins/TranslateNotice/lib/YandexTranslator.php';
class TranslateNoticeSettingsAction extends SettingsAction
{

@ -4,7 +4,7 @@ if (!defined('GNUSOCIAL')) {
exit(1);
}
require_once INSTALLDIR . '/plugins/TranslateNotice/lib/MicrosoftTranslator.php';
require_once INSTALLDIR . '/local/plugins/TranslateNotice/lib/YandexTranslator.php';
class TranslateNoticeSettingsForm extends Form
{
@ -33,24 +33,15 @@ class TranslateNoticeSettingsForm extends Form
{
$user = common_current_user();
$targetLanguage = Translate_notice::getTargetLanguage($user); // Get language from db. Will be null if never set.
// Get language from db. Will be null if never set.
$targetLanguage = Translate_notice::getTargetLanguage($user);
// Get auth info
$client_id = common_config('translatenotice', 'client_id');
$client_secret = common_config('translatenotice', 'client_secret');
$api_key= common_config('translatenotice', 'api_key');
// Get supported languages
$translator = new MicrosoftTranslator($client_id, $client_secret);
$languageCodes = $translator->getLanguagesForTranslate();
$languageNames = $translator->getLanguageNames($languageCodes);
$languages = array();
// Build list of <options>s
for ($i = 0; $i < count($languageNames); $i += 1) {
$languageCode = $languageCodes[$i]->__toString();
$languages[$languageCode] = $languageNames[$i];
}
$translator = new YandexTranslator($api_key);
$languages = $translator->getSupportedLanguages();
// Start outputing HTML
$this->out->elementStart('fieldset');

@ -3,58 +3,25 @@
"use strict";
var gsTranslate = window.gsTranslate,
encode = encodeURIComponent,
translate,
installdir = _peopletagAC.replace( "/main/peopletagautocomplete", "" ),
$dialog = $( "<div class='gs-translate-dialog'>" ).dialog( { autoOpen: false } )/*.appendTo( "body" )*/;
translate = function( text ) {
var appId = "Bearer ", /* FIXME legacy: http://msdn.microsoft.com/en-us/library/hh454950.aspx */
to = gsTranslate.targetLanguage || "en",
callbackName = "gsTranslate.callback",
script;
script = document.createElement( "script" );
script.src = "//api.microsofttranslator.com/V2/Ajax.svc/Translate" +
"?appId=" + encode( appId ) + encode( gsTranslate.accessToken.token ) +
"&to=" + encode( to ) +
"&text=" + encode( text ) +
"&oncomplete=" + encode( callbackName );
document.body.appendChild( script );
};
$dialog = $( "<div class='gs-translate-dialog'>" ).dialog( { autoOpen: false } );
$( document ).on( "click", ".gs-translate", function( e ) {
e.preventDefault();
var $this = $( this ),
text = $this.closest( ".notice" ).find( ".e-content" ).first().text();
if ( gsTranslate.accessToken.expires < Date.now() ) {
/* TODO: Error handling */
$.getJSON( installdir + "/main/translatenotice/renewtoken", function( data ) {
gsTranslate.accessToken.token = data.token;
gsTranslate.accessToken.expires = data.expires;
translate( text );
} );
} else {
translate( text );
}
endpoint = installdir + "/main/translatenotice/translatenotice",
text = $this.closest( ".notice" ).find( ".e-content" ).first().text(),
params = {
text: text
};
$.getJSON( endpoint, params, function( data ) {
$dialog
.text( data.text[ 0 ] )
.append( "<p><a href='http://translate.yandex.com/'>Powered by Yandex.Translate</a></p>" )
.dialog( "open" );
} );
} );
gsTranslate.callback = function( response ) {
$dialog
.closest( ".ui-dialog" )
.find( ".ui-dialog-title" ).html(
"<a style='text-decoration: none;' href='http://aka.ms/MicrosoftTranslatorAttribution'>" +
"Translated by " +
"<img style='vertical-align: middle;' src='" + installdir +
"/plugins/TranslateNotice/extlib/MicrosoftTranslator/MSFT_logo_rgb_C-Gray_D.png' alt='Microsoft Translator'>" +
"</a>"
);
$dialog.text( response ).dialog( "open" );
};
}( window, jQuery ) );

@ -1,171 +0,0 @@
<?php
// Logic specific to Microsoft Translator
class AccessToken {
public $token;
public $expires;
function AccessToken($token) {
$this->token = $token;
if (preg_match('/&ExpiresOn=(\d+)&/', $token, $matches)) {
$this->expires = $matches[1] . "000"; // Seconds to milliseconds
}
}
}
class MicrosoftTranslator {
private $client_id;
private $client_secret;
private $scope;
private $grant_type;
private $auth_url;
private $access_token;
// Constructor
function MicrosoftTranslator($client_id, $client_secret) {
$this->client_id = $client_id;
$this->client_secret = $client_secret;
// Populate our access token
$this->getAccessToken();
}
// Checks if access token is expired
// and requests a new one if so
function renewAccessTokenIfExpired() {
$now = new DateTime();
// Access token expired, get another one
if ($now->getTimestamp() > $this->access_token->expires) {
$this->getAccessToken();
}
}
// Get human-readable names from ISO 639-1 language codes
function getLanguageNames($codes) {
$this->renewAccessTokenIfExpired();
// XML representation of the codes we're interested in
$params = '<ArrayOfstring xmlns="http://schemas.microsoft.com/2003/10/Serialization/Arrays" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">';
if (sizeof($codes) > 0) {
foreach ($codes as $code) {
$params .= '<string>' . $code . '</string>';
}
} else {
return array();
}
$params .= '</ArrayOfstring>';
$url = 'http://api.microsofttranslator.com/V2/Http.svc/GetLanguageNames?locale=en'; // TODO: configurable locale
$header = 'Authorization: Bearer ' . $this->access_token->token;
$response = $this->http($url, $header, $params);
$xmlObj = simplexml_load_string($response);
$languages = array();
foreach ($xmlObj->string as $language) {
$languages[] = $language;
}
return $languages;
}
// Get list of supported languages
function getLanguagesForTranslate() {
$this->renewAccessTokenIfExpired();
$url = 'http://api.microsofttranslator.com/V2/Http.svc/GetLanguagesForTranslate';
$header = 'Authorization: Bearer ' . $this->access_token->token;
$response = $this->http($url, $header);
$xmlObj = simplexml_load_string($response);
if ($xmlObj === false) {
// TODO: Exception
return array();
}
$languageCodes = array();
foreach ($xmlObj->string as $language) {
$languageCodes[] = $language;
}
return $languageCodes;
}
// Identify the language of a selected piece of text
function detect($text) {
$this->renewAccessTokenIfExpired();
$url = 'http://api.microsofttranslator.com/V2/Http.svc/Detect?text=';
$header = 'Authorization: Bearer ' . $this->access_token->token;
$response = $this->http($url, $header);
$xmlObj = simplexml_load_string($response);
foreach((array)$xmlObj[0] as $val){
$languageCode = $val;
}
return $languageCode;
}
// Simple curl wrapper
private function http($url, $headers = null, $postParams = null) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
if ($postParams !== null) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postParams);
}
if ($headers !== null) {
curl_setopt($ch, CURLOPT_HTTPHEADER, array($headers, 'Content-Type: text/xml'));
}
$response = curl_exec($ch);
$errno = curl_errno($ch);
if ($errno) {
$err = curl_error($ch);
throw new Exception($err);
}
curl_close($ch);
return $response;
}
// Get an access token
function getAccessToken() {
$scope = 'http://api.microsofttranslator.com/';
$grant_type = 'client_credentials';
$url = 'https://datamarket.accesscontrol.windows.net/v2/OAuth2-13';
$params = http_build_query(
array(
'client_id' => $this->client_id,
'client_secret' => $this->client_secret,
'scope' => $scope,
'grant_type' => $grant_type
)
);
$response = $this->http($url, null, $params);
$json = json_decode($response);
if (isset($json->error)) {
throw new Exception($json->error_description);
}
return $this->access_token = new AccessToken($json->access_token);
}
}

@ -0,0 +1,43 @@
<?php
class YandexTranslator {
private $api_key;
private $api_root;
// Constructor
function YandexTranslator($api_key) {
$this->api_key = $api_key;
$this->api_root = 'https://translate.yandex.net/api/v1.5/tr.json';
}
public function getSupportedLanguages() {
$endpoint = $this->api_root . '/getLangs';
$params = array(
'key' => $this->api_key,
'ui' => 'en' // TODO: configurable?
);
$response = HTTPClient::quickGet($endpoint, null, $params);
$json = json_decode($response, true);
asort($json['langs']);
return $json['langs'];
}
public function translate($text, $target_language) {
$endpoint = $this->api_root . '/translate';
$lang = isset($target_language) ? $target_language : 'en';
$params = array(
'key' => $this->api_key,
'text' => $text,
'lang' => $lang
);
return HTTPClient::quickGet($endpoint, null, $params);
}
}