Index: admin_templates/languages/phrase_edit.tpl
===================================================================
--- admin_templates/languages/phrase_edit.tpl (revision 14184)
+++ admin_templates/languages/phrase_edit.tpl (working copy)
@@ -81,8 +81,53 @@
-
-
+
+
+
+
+
+
+ :
+ |
+ |
+
+ " name="" id="">
+
+
+
+
+ |
+
+
+
+
Index: admin_templates/regional/languages_edit.tpl
===================================================================
--- admin_templates/regional/languages_edit.tpl (revision 14184)
+++ admin_templates/regional/languages_edit.tpl (working copy)
@@ -93,7 +93,7 @@
|
" name="" value="">
- " type="checkbox" id="_cb_CopyLabels" name="_cb_CopyLabels" onclick="update_checkbox(this, document.getElementById(''))">
+ " type="checkbox" id="_cb_CopyLabels" name="_cb_CopyLabels" onclick="update_checkbox(this, document.getElementById('')); reflectAutoTranslateLabels();">
" name="" id="">
@@ -102,6 +102,23 @@
|
+
+
+
+
+
+
+
Index: install/english.lang
===================================================================
--- install/english.lang (revision 14184)
+++ install/english.lang (working copy)
@@ -46,6 +46,7 @@
U2VsZWN0IEFsbA==
U2V0IFZhbHVl
U2hvdyBTdHJ1Y3R1cmU=
+ VHJhbnNsYXRl
VW5zZWxlY3Q=
VXA=
VXNl
@@ -254,6 +255,8 @@
UmVkaXJlY3QgdG8gSFRUUCB3aGVuIFNTTCBpcyBub3QgcmVxdWlyZWQ=
RnVsbCBpbWFnZSBIZWlnaHQ=
RnVsbCBpbWFnZSBXaWR0aA==
+ R29vZ2xlIFRyYW5zbGF0ZSBBUEkgS2V5
+ R29vZ2xlIFRyYW5zbGF0ZSBBUEkgVXJs
QnlwYXNzIEhUVFAgQXV0aGVudGljYXRpb24gZnJvbSBJUHMgKHNlcGFyYXRlZCBieSBzZW1pY29sb25zKQ==
UGFzc3dvcmQgZm9yIEhUVFAgQXV0aGVudGljYXRpb24=
VXNlcm5hbWUgZm9yIEhUVFAgQXV0aGVudGljYXRpb24=
@@ -328,6 +331,7 @@
RXJyb3IgY29weWluZyBzdWJzZWN0aW9ucw==
Q3VzdG9tIGZpZWxkIHdpdGggaWRlbnRpY2FsIG5hbWUgYWxyZWFkeSBleGlzdHM=
RmlsZSBpcyB0b28gbGFyZ2U=
+ WW91IGFyZSBtaXNzaW5nIHlvdXIgR29vZ2xlIFRyYW5zbGF0ZSBBUEkgS2V5LiBQbGVhc2UgZW50ZXIgaXQgaW4gb3JkZXIgdG8gdXNlIEdvb2dsZSBUcmFuc2xhdGlvbiBzZXJ2aWNlIQ==
Z3JvdXAgbm90IGZvdW5k
SW52YWxpZCBGaWxlIEZvcm1hdA==
aW52YWxpZCBvcHRpb24=
@@ -336,6 +340,7 @@
RXJyb3IgbW92aW5nIHN1YnNlY3Rpb24=
Q2FuJ3QgaW5oZXJpdCB0ZW1wbGF0ZSBmcm9tIHRvcCBjYXRlZ29yeQ==
Tm8gbWF0Y2hpbmcgY29sdW1ucyBhcmUgZm91bmQ=
+ U29ycnksIG5vIHRyYW5zbGF0aW9uIGlzIGF2YWlsYWJsZSE=
VGhpcyBvcGVyYXRpb24gaXMgbm90IGFsbG93ZWQh
VmFsaWRhdGlvbiBlcnJvciwgcGxlYXNlIGRvdWJsZS1jaGVjayBJbi1Qb3J0YWwgdGFncw==
UGFzc3dvcmRzIGRvIG5vdCBtYXRjaCE=
@@ -389,6 +394,7 @@
QXR0YWNobWVudA==
QXV0byBDcmVhdGUgRmlsZSBOYW1l
QXV0b21hdGljIEZpbGVuYW1l
+ VHJhbnNsYXRlIGFmdGVyIENvcHlpbmc=
QXZhaWxhYmxlIENvbHVtbnM=
QmFja2dyb3VuZA==
QmFja2dyb3VuZCBBdHRhY2htZW50
@@ -491,6 +497,7 @@
RnJvbSBFbWFpbA==
RnJvbnQtRW5kIE9ubHk=
QWxsb3cgUmVnaXN0cmF0aW9uIG9uIEZyb250LWVuZA==
+ R29vZ2xlIFRyYW5zbGF0aW9u
VXNlciBHcm91cA==
SUQ=
R3JvdXAgTmFtZQ==
@@ -697,6 +704,7 @@
SGVhZCBGcmFtZQ==
SGlkZQ==
QWxsIEZpbGVz
+ QWxsIExhYmVscyB3aWxsIGJlIGF1dG8tdHJhbnNsYXRlZCB1c2luZyBHb29nbGUgVHJhbnNsYXRlIFNlcnZpY2Ugb25jZSBuZXcgbGFuZ3VhZ2UgaXMgY3JlYXRlZCBhbmQgbGFiZWxzIGNvcGllZC4gQXV0by10cmFuc2xhdGUgd2lsbCB3b3JrIG9ubHkgaWYgIkNvcHkgTGFiZWxzIGZyb20gdGhpcyBMYW5ndWFnZSIgb3B0aW9uIGlzIGNoZWNrZWQu
Q1NWIEZpbGVz
U2luZ2xlIElQIG9yIHJhbmdlIHJlY29yZCBwZXIgbGluZSAoZm9ybWF0czogMS4yLjMuNCBvciAxLjIuMyBvciAxLjIuMy4zMi0xLjIuMy41NCBvciAxLjIuMy4zMi8yNyBvciAxLjIuMy4zMi8yNTUuMjU1LjI1NS4yMjQp
U2luZ2xlIEVtYWlsIEV2ZW50IHBlciBsaW5lIChmb3JtYXRzOiBVU0VSLkFERCwgT1JERVIuU1VCTUlUKQ==
@@ -1075,6 +1083,7 @@
UmVwbGFjZW1lbnQgVGFncw==
U2VuZGVyIEluZm9ybWF0aW9u
U2V0dGluZ3M=
+ M3JkIFBhcnR5IEFQSSBTZXR0aW5ncw==
QWRtaW4gQ29uc29sZSBTZXR0aW5ncw==
Q2FjaGluZyBTZXR0aW5ncw==
Q1NWIEV4cG9ydCBTZXR0aW5ncw==
Index: install/install_data.sql
===================================================================
--- install/install_data.sql (revision 14184)
+++ install/install_data.sql (working copy)
@@ -94,6 +94,8 @@
INSERT INTO ConfigurationValues VALUES(DEFAULT, 'CSVExportEncoding', '0', 'In-Portal', 'in-portal:configure_advanced', 'la_section_SettingsCSVExport', 'la_config_CSVExportEncoding', 'radio', NULL, '0=la_Unicode||1=la_Regular', 70.04, 0, 1, NULL);
INSERT INTO ConfigurationValues VALUES(DEFAULT, 'CacheHandler', 'Fake', 'In-Portal', 'in-portal:configure_advanced', 'la_section_SettingsCaching', 'la_config_CacheHandler', 'select', NULL, 'Fake=la_None||Memcache=+Memcached||Apc=+Alternative PHP Cache||XCache=+XCache', 80.01, 0, 0, NULL);
INSERT INTO ConfigurationValues VALUES(DEFAULT, 'MemcacheServers', 'localhost:11211', 'In-Portal', 'in-portal:configure_advanced', 'la_section_SettingsCaching', 'la_config_MemcacheServers', 'text', NULL, '', 80.02, 0, 0, 'la_hint_MemcacheServers');
+INSERT INTO ConfigurationValues VALUES(DEFAULT, 'GoogleTranslateApiUrl', 'https://www.googleapis.com/language/translate/v2', 'In-Portal', 'in-portal:configure_advanced', 'la_section_Settings3rdPartyApi', 'la_config_GoogleTranslateApiUrl', 'text', NULL, '', 90.01, 0, 0, NULL);
+INSERT INTO ConfigurationValues VALUES(DEFAULT, 'GoogleTranslateApiKey', '', 'In-Portal', 'in-portal:configure_advanced', 'la_section_Settings3rdPartyApi', 'la_config_GoogleTranslateApiKey', 'text', NULL, '', 90.02, 0, 0, NULL);
# Section "in-portal:configure_users":
INSERT INTO ConfigurationValues VALUES(DEFAULT, 'User_Allow_New', '3', 'In-Portal:Users', 'in-portal:configure_users', 'la_title_General', 'la_users_allow_new', 'radio', '', '1=la_opt_UserInstantRegistration||2=la_opt_UserNotAllowedRegistration||3=la_opt_UserUponApprovalRegistration||4=la_opt_UserEmailActivation', 10.01, 0, 1, NULL);
Index: units/languages/languages_config.php
===================================================================
--- units/languages/languages_config.php (revision 14184)
+++ units/languages/languages_config.php (working copy)
@@ -216,6 +216,7 @@
'formatter' => 'kOptionsFormatter', 'options_sql' => 'SELECT %s FROM ' . TABLE_PREFIX . 'Language ORDER BY PackName', 'option_title_field' => 'PackName', 'option_key_field' => 'LanguageId',
'default' => '',
),
+ 'AutoTranslateLabels' => Array ('type' => 'int', 'default' => 0),
),
'Grids' => Array(
Index: units/languages/languages_event_handler.php
===================================================================
--- units/languages/languages_event_handler.php (revision 14184)
+++ units/languages/languages_event_handler.php (working copy)
@@ -249,10 +249,10 @@
$object =& $event->getObject();
/* @var $object kDBItem */
- $src_language = $object->GetDBField('CopyFromLanguage');
+ $src_language_id = $object->GetDBField('CopyFromLanguage');
- if ($object->GetDBField('CopyLabels') && $src_language) {
- $dst_language = $object->GetID();
+ if ($object->GetDBField('CopyLabels') && $src_language_id) {
+ $dst_language_id = $object->GetID();
// 1. schedule data copy after OnSave event is executed
$var_name = $event->getPrefixSpecial() . '_copy_data' . $this->Application->GetVar('m_wid');
@@ -261,10 +261,25 @@
if ($pending_actions) {
$pending_actions = unserialize($pending_actions);
}
-
- $pending_actions[$src_language] = $dst_language;
+
+ $pending_actions[$src_language_id] = Array( 'target_id' => $dst_language_id );
+
+ if ( $object->GetDBField('AutoTranslateLabels') ) {
+ $live_table = $this->Application->getUnitOption($event->Prefix, 'TableName');
+ $sql = 'SELECT Locale FROM ' . $live_table . '
+ WHERE ' . $object->IDField. ' = ' . $src_language_id;
+ $src_lang_locale = $this->Conn->GetOne($sql);
+
+ if ( $src_lang_locale ) {
+ $pending_actions[$src_language_id]['source_locale'] = strstr($src_lang_locale, '-', true); // save the locale for later
+ $pending_actions[$src_language_id]['target_locale'] = strstr($object->GetDBField('Locale'), '-', true); // save the locale for later
+ }
+ }
+
$this->Application->StoreVar($var_name, serialize($pending_actions));
+
$object->SetDBField('CopyLabels', 0);
+ $object->SetDBField('AutoTranslateLabels', 0);
}
}
@@ -280,6 +295,9 @@
if ($event->status != erSUCCESS) {
return ;
}
+
+ $object =& $event->getObject();
+ /* @var $object kDBItem */
$var_name = $event->getPrefixSpecial() . '_copy_data' . $this->Application->GetVar('m_wid');
$pending_actions = $this->Application->RecallVar($var_name, Array ());
@@ -293,7 +311,9 @@
$ml_helper->createFields('phrases');
$ml_helper->createFields('emailevents');
- foreach ($pending_actions as $src_language => $dst_language) {
+ foreach ($pending_actions as $src_language => $lang_data) {
+ $dst_language = $lang_data['target_id'];
+
// phrases import
$sql = 'UPDATE ' . $this->Application->getUnitOption('phrases', 'TableName') . '
SET l' . $dst_language . '_Translation = l' . $src_language . '_Translation';
@@ -305,6 +325,14 @@
l' . $dst_language . '_Subject = l' . $src_language . '_Subject,
l' . $dst_language . '_Body = l' . $src_language . '_Body';
$this->Conn->Query($sql);
+
+ if ( isset($lang_data['source_locale']) ) {
+ // perform the translations
+ $event->setEventParam('source_locale', $lang_data['source_locale']);
+ $event->setEventParam('target_locale', $lang_data['target_locale']);
+ $event->setEventParam('target_lang_id', $dst_language);
+ $event->CallSubEvent('OnTranslateLanguagePack');
+ }
}
$this->Application->RemoveVar($var_name);
@@ -314,6 +342,137 @@
}
/**
+ * Performs actual translation and data update
+ *
+ * @param string $prefix
+ * @param array $data
+ * @param int $lang_id
+ * @param string $source_locale
+ * @param string $target_locale
+ */
+ function _performTranslation($prefix, $data, $lang_id, $source_locale, $target_locale)
+ {
+ $post_data = '';
+ $counter = 0;
+ $posting_limits = ($prefix == 'phrases')? 100 : 50; // limits are: 100 phrases or 50 email events (subject + body)
+ $table = $this->Application->getUnitOption($prefix, 'TableName');
+
+ $url = $this->Application->ConfigValue('GoogleTranslateApiUrl') . '?key=' . $this->Application->ConfigValue('GoogleTranslateApiKey') . '&prettyprint=true&source=' . $source_locale . '&target=' . $target_locale;
+ $curl_helper =& $this->Application->recallObject('CurlHelper');
+ /* @var $curl_helper kCurlHelper */
+
+ foreach ($data as $data_id => $source) {
+ if ($prefix == 'phrases') {
+ $post_data .= 'q=' . rawurlencode($source) . '&';
+ }
+ else {
+ $post_data .= 'q=' . rawurlencode($source['l'.$lang_id.'_Subject']) . '&q=' . rawurlencode($source['l'.$lang_id.'_Body']) . '&'; // email subject and boby
+ }
+
+ $counter++;
+ $ids[] = $data_id; // save ids we are going to translate in this round for later
+
+ if ( count($ids) == $posting_limits || count($data) == $counter || strlen($post_data) > 4500) {
+ // with single Curl request process 100 or phrases or 4.5K (API POST data limits)
+ $curl_helper->followLocation = true;
+ $curl_helper->SetHeader('X-HTTP-Method-Override', 'GET'); // see - http://code.google.com/apis/language/translate/v2/using_rest.html#WorkingResults
+ $curl_helper->SetPostData($post_data);
+
+ $response = $curl_helper->Send( $url );
+
+ if ( $response ) {
+ $parsed_response = json_decode($response, true);
+ $translations = $parsed_response['data']['translations']; // array of translations
+
+ if ( isset($translations) && is_array($translations) ) {
+ $where_case = '';
+ $where_case1 = '';
+
+ foreach ($translations as $index => $translation) {
+ // build multiple translation updates into one SQL
+ if ( $translation ) {
+ if ($prefix == 'phrases') {
+ $where_case .= ' WHEN ' . $ids[ $index ] . ' THEN ' . $this->Conn->qstr($translation['translatedText']);
+ }
+ elseif ($prefix == 'emailevents') {
+ // do this go through all odd array records to be case for body
+ // since we are processing both subject and body that belong to one event record
+ $even = $index % 2? FALSE : TRUE;
+ if ( $even ) {
+ $event_id = $index / 2;
+ $where_case .= ' WHEN ' . $ids[ $event_id ] . ' THEN ' . $this->Conn->qstr($translation['translatedText']);
+ }
+ else {
+ $where_case1 .= ' WHEN ' . $ids[ $event_id ] . ' THEN ' . $this->Conn->qstr($translation['translatedText']);
+ }
+ }
+ }
+ else {
+ // translation is missing
+ unset($ids[ $index ]);
+ }
+ }
+
+ if ($prefix == 'phrases' && $where_case) {
+ $sql = 'UPDATE ' . $table . '
+ SET l' . $lang_id . '_Translation = CASE PhraseId ' . $where_case . ' END
+ WHERE PhraseId IN (' . implode(',', $ids) . ')';
+ $this->Conn->Query($sql);
+ }
+ elseif ($prefix == 'emailevents' && ($where_case || $where_case1)) {
+ $sql = 'UPDATE ' . $table . '
+ SET l' . $lang_id . '_Subject = CASE EventId ' . $where_case . ' END, l' . $lang_id . '_Body = CASE EventId ' . $where_case1 . ' END
+ WHERE EventId IN (' . implode(',', $ids) . ')';
+ $this->Conn->Query($sql);
+ }
+ }
+ // reset variables so we can prepare a new request
+ $post_data = '';
+ unset($ids);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Auto-translate language labels using Google Translate API
+ *
+ * @param kEvent $event
+ */
+ function OnTranslateLanguagePack(&$event)
+ {
+ $lang_id = $event->getEventParam('target_lang_id');
+ $source_locale = $event->getEventParam('source_locale');
+ $target_locale = $event->getEventParam('target_locale');
+
+ // 1. translate language phrases
+ $table = $this->Application->getUnitOption('phrases', 'TableName');
+ $sql = 'SELECT l' . $lang_id . '_Translation, PhraseId
+ FROM ' . $table . '
+ WHERE l' . $lang_id . '_Translation != \'\' AND l' . $lang_id . '_Translation IS NOT NULL
+ ORDER BY PhraseId';
+
+ $phrases_to_translate = $this->Conn->GetCol($sql, 'PhraseId');
+ if ( $phrases_to_translate ) {
+ $this->_performTranslation('phrases', $phrases_to_translate, $lang_id, $source_locale, $target_locale);
+ }
+
+ // 2. translate language events
+ $table = $this->Application->getUnitOption('emailevents', 'TableName');
+ $sql = 'SELECT l' . $lang_id . '_Subject, l' . $lang_id . '_Body, EventId
+ FROM ' . $table . '
+ WHERE (l' . $lang_id . '_Subject != \'\' AND l' . $lang_id . '_Subject IS NOT NULL) OR
+ (l' . $lang_id . '_Body != \'\' AND l' . $lang_id . '_Body IS NOT NULL)
+ ORDER BY EventId';
+
+ $events_to_translate = $this->Conn->Query($sql, 'EventId');
+ if ( $events_to_translate ) {
+ $this->_performTranslation('emailevents', $events_to_translate, $lang_id, $source_locale, $target_locale);
+ }
+ }
+
+ /**
* Prepare temp tables for creating new item
* but does not create it. Actual create is
* done in OnPreSaveCreated
Index: units/phrases/phrases_config.php
===================================================================
--- units/phrases/phrases_config.php (revision 14184)
+++ units/phrases/phrases_config.php (working copy)
@@ -159,6 +159,12 @@
),
'ExportPhrases' => Array ('type' => 'string', 'default' => ''),
'ExportEmailEvents' => Array ('type' => 'string', 'default' => ''),
+
+ 'GoogleTranslation' => Array ('type' => 'string', 'default' => ''),
+ 'GoogleTranslationTargetLanguage' => Array (
+ 'type' => 'string',
+ 'formatter' => 'kOptionsFormatter', 'options_sql' => 'SELECT PackName, SUBSTR(Locale, 1, 2) AS Target FROM ' . TABLE_PREFIX . 'Language WHERE (Locale != "") ORDER BY PrimaryLang DESC, Priority DESC', 'option_key_field' => 'Target', 'option_title_field' => 'PackName',
+ ),
),
'Grids' => Array (
Index: units/phrases/phrases_event_handler.php
===================================================================
--- units/phrases/phrases_event_handler.php (revision 14184)
+++ units/phrases/phrases_event_handler.php (working copy)
@@ -55,6 +55,11 @@
return true;
}
}
+
+ if ($this->Application->isAdmin && $event->Name == 'OnGetGoogleTranslation') {
+ // request to google translation api
+ return true;
+ }
return parent::CheckPermission($event);
}
@@ -329,4 +334,31 @@
// use language from grid, instead of primary language used by default
$event->SetRedirectParam('m_lang', $this->Application->GetVar('m_lang'));
}
+
+ /**
+ * Send request to Google Translate API to translate text
+ *
+ * @param kEvent $event
+ */
+ function OnGetGoogleTranslation(&$event)
+ {
+ $curl_helper =& $this->Application->recallObject('CurlHelper');
+ /* @var $curl_helper kCurlHelper */
+
+ $target_locale = $this->Application->GetVar('target_locale');
+ $to_translate = rawurlencode( $this->Application->GetVar('q') );
+
+ $request_url = $this->Application->ConfigValue('GoogleTranslateApiUrl') . '?key=' . $this->Application->ConfigValue('GoogleTranslateApiKey') . '&target=' . $target_locale . '&q=' . $to_translate;
+
+ $response = $curl_helper->Send($request_url);
+
+ if ( $response ) {
+ $parsed_response = json_decode($response, true);
+ if ( isset($parsed_response['data']['translations'][0]['translatedText']) ) {
+ echo $parsed_response['data']['translations'][0]['translatedText'];
+ }
+ }
+
+ $event->status = erSTOP;
+ }
}
\ No newline at end of file