View source
<?php
define('L10N_CLIENT_STRINGS', 100);
function l10n_client_menu() {
$items = array();
$items['l10n_client/save'] = array(
'title' => 'Save string',
'page callback' => 'l10n_client_save_string',
'access callback' => 'l10n_client_access',
'type' => MENU_CALLBACK,
);
$items['locale'] = array(
'title' => 'Translate strings',
'page callback' => 'l10n_client_translate_page',
'access callback' => 'l10n_client_access',
);
$items['locale/untranslated'] = array(
'title' => 'Untranslated',
'page arguments' => array(
'untranslated',
),
'access callback' => 'l10n_client_access',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
);
$items['locale/translated'] = array(
'title' => 'Translated',
'page arguments' => array(
'translated',
),
'access callback' => 'l10n_client_access',
'type' => MENU_LOCAL_TASK,
'weight' => 10,
);
$items['admin/config/regional/translate/client'] = array(
'title' => 'Client',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'l10n_client_settings_form',
),
'access arguments' => array(
'administer languages',
),
'type' => MENU_LOCAL_TASK,
'file' => 'l10n_client.admin.inc',
'weight' => 40,
);
return $items;
}
function l10n_client_permission() {
return array(
'use on-page translation' => array(
'title' => t('Use on-page translation'),
'description' => t('Makes it possible to translate the Drupal interface on-page.'),
),
'submit translations to localization server' => array(
'title' => t('Submit translations to localization server'),
'description' => t('Allows users to submit translations to a remote localization server.'),
),
);
}
function l10n_client_theme($existing, $type, $theme, $path) {
return array(
'l10n_client_message' => array(
'variables' => array(
'message' => '',
'level' => WATCHDOG_ERROR,
),
),
);
}
function l10n_client_init() {
global $conf, $language;
if (l10n_client_access()) {
$conf['locale_cache_strings'] = 0;
drupal_static_reset('locale');
drupal_add_css(drupal_get_path('module', 'l10n_client') . '/l10n_client.css');
drupal_add_js(drupal_get_path('module', 'l10n_client') . '/jquery.hotkeys.js');
drupal_add_js('misc/jquery.cookie.js');
drupal_add_js(drupal_get_path('module', 'l10n_client') . '/l10n_client.js');
drupal_add_js('misc/textarea.js');
}
}
function l10n_client_access($account = NULL) {
if (!isset($account)) {
global $user;
$account = $user;
}
$user_enabled = empty($account->data['l10n_client_disabled']);
$user_access = user_access('use on-page translation', $account);
$paths = variable_get('l10n_client_disabled_paths', '');
$paths_disabled = !empty($paths) && drupal_match_path(current_path(), $paths);
return $user_access && $user_enabled && !$paths_disabled;
}
function l10n_client_translate_page($display_translated = FALSE, $textgroup = 'default', $allow_translation = TRUE) {
global $language;
$table = array();
$query = db_select('locales_source', 's')
->extend('PagerDefault');
$query
->leftJoin('locales_target', 't', "s.lid = t.lid AND t.language = :lang", array(
':lang' => $language->language,
));
$query
->fields('s', array(
'source',
))
->fields('t', array(
'translation',
'language',
))
->orderBy('s.source');
if ($display_translated) {
$header = array(
t('Source string'),
t('Translation'),
);
$query
->condition('t.translation', '', '!=');
}
else {
$header = array(
t('Source string'),
);
$query
->isNull('t.lid');
}
if (!empty($textgroup)) {
$query
->condition('s.textgroup', $textgroup);
}
$allow_translation = $textgroup == 'default' && $language->language == 'en' ? FALSE : $allow_translation;
$result = $query
->limit(L10N_CLIENT_STRINGS)
->execute();
foreach ($result as $data) {
if ($display_translated) {
$table[] = array(
check_plain($data->source),
check_plain($data->translation),
);
if ($allow_translation) {
l10_client_add_string_to_page($data->source, $data->translation, $textgroup);
}
}
else {
$table[] = array(
check_plain($data->source),
);
if ($allow_translation) {
l10_client_add_string_to_page($data->source, TRUE, $textgroup);
}
}
}
$pager = theme('pager');
return $pager . theme('table', array(
'header' => $header,
'rows' => $table,
'empty' => t('No strings found to translate.'),
)) . $pager;
}
function l10n_client_page_alter(&$page) {
global $conf, $language;
if (l10n_client_access() && ($page_strings = _l10n_client_page_strings())) {
$l10n_strings = array();
foreach ($page_strings as $textgroup => $group_strings) {
foreach ($group_strings as $context => $context_strings) {
foreach ($context_strings as $string => $translation) {
$l10n_strings[] = array(
$string,
$translation,
$textgroup,
$context,
);
}
}
}
array_multisort($l10n_strings);
$string_list = _l10n_client_string_list($l10n_strings);
$form = drupal_get_form('l10n_client_form', $l10n_strings);
$l10n_form = drupal_render($form);
$form = drupal_get_form('l10n_client_search_form');
$l10n_search = drupal_render($form);
$l10n_dom = _l10n_client_dom_strings($l10n_strings);
$string_label = '<h2>' . t('Page Text') . '</h2>';
$source_label = '<h2>' . t('Source') . '</h2>';
$translation_label = '<h2>' . t('Translation to %language', array(
'%language' => $language->native,
)) . '</h2>';
$toggle_label = t('Translate Text');
$output = "\n <div id='l10n-client' class='l10n-client-minimized'>\n <div class='labels'>\n <span class='toggle'>{$toggle_label}</span>\n <div class='label strings'>{$string_label}</div>\n <div class='label source'>{$source_label}</div>\n <div class='label translation'>{$translation_label}</div>\n </div>\n <div id='l10n-client-string-select'>\n {$string_list}\n {$l10n_search}\n </div>\n <div id='l10n-client-string-editor'>\n <div class='source'>\n <div class='source-text'></div>\n <div class='context'></div>\n </div>\n <div class='translation'>\n {$l10n_form}\n </div>\n </div>\n </div>\n {$l10n_dom}\n ";
$page['page_bottom']['l10n_client'] = array(
'#type' => 'markup',
'#markup' => $output,
);
}
}
function l10_client_add_string_to_page($source = NULL, $translation = NULL, $textgroup = 'default', $context = '') {
static $strings = array();
if (isset($source)) {
$strings[$textgroup][$context][$source] = $translation;
}
else {
return $strings;
}
}
function _l10n_client_page_strings() {
global $language;
$strings = l10_client_add_string_to_page();
if (arg(0) != 'locale' && is_array($locale = locale()) && isset($locale[$language->language])) {
$strings += array(
'default' => array(),
);
foreach ($locale[$language->language] as $context => $context_strings) {
$strings['default'] += array(
$context => array(),
);
$strings['default'][$context] = array_merge($strings['default'][$context], $context_strings);
}
$result = db_query("SELECT s.source, t.translation, s.textgroup, s.context FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = :language WHERE s.location = :location", array(
':language' => $language->language,
':location' => request_uri(),
));
foreach ($result as $data) {
if (!array_key_exists($data->source, $strings[$data->textgroup])) {
$strings[$data->textgroup][$data->context][$data->source] = empty($data->translation) ? TRUE : $data->translation;
}
}
}
return $strings;
}
function _l10n_client_dom_strings($strings) {
$output = '';
foreach ($strings as $values) {
list($source, $target, $textgroup, $context) = $values;
$source = $source === TRUE ? '' : htmlspecialchars($source, ENT_NOQUOTES, 'UTF-8');
$target = $target === TRUE ? '' : htmlspecialchars($target, ENT_NOQUOTES, 'UTF-8');
$context = htmlspecialchars($context, ENT_NOQUOTES, 'UTF-8');
$output .= "<div><span class='source'>{$source}</span><span class='target'>{$target}</span><span class='textgroup'>{$textgroup}</span><span class='context'>{$context}</span></div>";
}
return "<div id='l10n-client-data'>{$output}</div>";
}
function _l10n_client_string_list($strings) {
foreach ($strings as $values) {
if ($values[1] === TRUE) {
$str_class = 'untranslated';
}
else {
$str_class = 'translated';
}
$original = $values[1] === TRUE ? $values[0] : $values[1];
$string = strip_tags($original);
if (empty($string)) {
$string = htmlspecialchars(truncate_utf8($original, 78, TRUE, TRUE), ENT_NOQUOTES, 'UTF-8');
}
else {
$string = truncate_utf8($string, 78, TRUE, TRUE);
}
$select_list[] = "<li class='{$str_class}'>{$string}</li>";
}
$output = implode("\n", $select_list);
return "<ul class='string-list'>{$output}</ul>";
}
function l10n_client_form($form_id, $strings) {
global $language;
$form = array();
$form['#action'] = url('l10n_client/save');
$form['target'] = array(
'#title' => t('Translation to %language', array(
'%language' => $language->native,
)),
'#title_display' => 'invisible',
'#type' => 'textarea',
'#resizable' => false,
'#rows' => 6,
'#attributes' => array(
'class' => array(
'translation-target',
),
),
);
$form['save'] = array(
'#value' => t('Save translation'),
'#type' => 'submit',
'#attributes' => array(
'class' => array(
'edit-save',
),
),
);
$form['textgroup'] = array(
'#type' => 'hidden',
'#value' => 'default',
'#attributes' => array(
'class' => array(
'source-textgroup',
),
),
);
$form['context'] = array(
'#type' => 'hidden',
'#value' => 'default',
'#attributes' => array(
'class' => array(
'source-context',
),
),
);
$form['copy'] = array(
'#type' => 'button',
'#id' => 'l10n-client-edit-copy',
'#attributes' => array(
'class' => array(
'edit-copy',
),
),
'#value' => t('Copy source'),
);
$form['clear'] = array(
'#type' => 'button',
'#id' => 'l10n-client-edit-clear',
'#attributes' => array(
'class' => array(
'edit-clear',
),
),
'#value' => t('Clear'),
);
return $form;
}
function l10n_client_search_form() {
global $language;
$form = array();
$form['search'] = array(
'#title' => t('Search'),
'#title_display' => 'invisible',
'#type' => 'textfield',
'#attributes' => array(
'class' => array(
'string-search',
),
),
);
$form['clear-button'] = array(
'#type' => 'button',
'#id' => 'l10n-client-search-filter-clear',
'#value' => t('X'),
);
return $form;
}
function l10n_client_save_string() {
global $user, $language;
if (l10n_client_access()) {
if (isset($_POST['source']) && isset($_POST['target']) && !empty($_POST['textgroup']) && !empty($_POST['form_token']) && drupal_valid_token($_POST['form_token'], 'l10n_client_form')) {
$lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = :context AND textgroup = :textgroup", array(
':source' => $_POST['source'],
':context' => $_POST['context'],
':textgroup' => $_POST['textgroup'],
))
->fetchField();
if (!empty($lid)) {
include_once 'includes/locale.inc';
$report = array(
'skips' => 0,
'additions' => 0,
'updates' => 0,
'deletes' => 0,
);
_locale_import_one_string_db($report, $language->language, $_POST['context'], $_POST['source'], $_POST['target'], $_POST['textgroup'], NULL, LOCALE_IMPORT_OVERWRITE);
cache_clear_all('locale:', 'cache', TRUE);
_locale_invalidate_js($language->language);
if (!empty($report['skips'])) {
$message = theme('l10n_client_message', array(
'message' => t('Not saved locally due to invalid HTML content.'),
));
}
elseif (!empty($report['additions']) || !empty($report['updates'])) {
$message = theme('l10n_client_message', array(
'message' => t('Translation saved locally.'),
'level' => WATCHDOG_INFO,
));
}
elseif (!empty($report['deletes'])) {
$message = theme('l10n_client_message', array(
'message' => t('Translation successfuly removed locally.'),
'level' => WATCHDOG_INFO,
));
}
else {
$message = theme('l10n_client_message', array(
'message' => t('Unknown error while saving translation locally.'),
));
}
if (empty($report['skips']) && variable_get('l10n_client_use_server', FALSE) && user_access('submit translations to localization server') && $_POST['textgroup'] == 'default') {
if (!empty($user->data['l10n_client_key'])) {
$remote_result = l10n_client_submit_translation($language->language, $_POST['source'], $_POST['target'], $user->data['l10n_client_key'], l10n_client_user_token($user));
$message .= theme('l10n_client_message', array(
'message' => $remote_result[1],
'level' => $remote_result[0] ? WATCHDOG_INFO : WATCHDOG_ERROR,
));
}
else {
$server_url = variable_get('l10n_client_server', 'https://localize.drupal.org/');
$user_edit_url = url('user/' . $user->uid . '/edit', array(
'absolute' => TRUE,
));
$message .= theme('l10n_client_message', array(
'message' => t('You could share your work with !l10n_server if you set your API key at !user_link.', array(
'!l10n_server' => l($server_url, $server_url),
'!user_link' => l($user_edit_url, 'user/' . $user->uid . '/edit'),
)),
'level' => WATCHDOG_WARNING,
));
}
}
}
else {
$message = theme('l10n_client_message', array(
'message' => t('Not saved due to source string missing.'),
));
}
}
else {
$message = theme('l10n_client_message', array(
'message' => t('Not saved due to missing form values.'),
));
}
}
else {
$message = theme('l10n_client_message', array(
'message' => t('Not saved due to insufficient permissions.'),
));
}
drupal_json_output($message);
exit;
}
function theme_l10n_client_message($vars) {
switch ($vars['level']) {
case WATCHDOG_INFO:
return '<div class="l10n-client-feedback message-info">' . $vars['message'] . '</div>';
break;
case WATCHDOG_WARNING:
return '<div class="l10n-client-feedback message-warning">' . $vars['message'] . '</div>';
break;
case WATCHDOG_ERROR:
return '<div class="l10n-client-feedback message-error">' . $vars['message'] . '</div>';
break;
}
}
function l10n_client_form_user_profile_form_alter(&$form, &$form_state) {
if ($form['#user_category'] == 'account') {
$items = array();
$account = $form['#user'];
$use_server = variable_get('l10n_client_use_server', FALSE);
$server_root = variable_get('l10n_client_server', 'https://localize.drupal.org/');
if ($use_server && user_access('submit translations to localization server', $account)) {
$server_link = $server_root . '?q=translate/remote/userkey/' . l10n_client_user_token($account);
$items['l10n_client_key'] = array(
'#type' => 'textfield',
'#title' => t('Your API key for @server', array(
'@server' => $server_root,
)),
'#default_value' => !empty($account->data['l10n_client_key']) ? $account->data['l10n_client_key'] : '',
'#description' => t('This is a unique key that will allow you to send translations to the remote server. To get your API key go to !server-link.', array(
'!server-link' => l($server_link, $server_link),
)),
);
}
if (user_access('use on-page translation', $account)) {
$items['l10n_client_disabled'] = array(
'#type' => 'checkbox',
'#title' => t('Hide on-page translation from you'),
'#default_value' => !empty($account->data['l10n_client_disabled']),
);
}
if (!empty($items)) {
$form['l10n_client'] = array(
'#type' => 'fieldset',
'#title' => t('Localization client'),
'#weight' => 1,
'items' => $items,
);
}
}
}
function l10n_client_user_presave(&$edit, $account, $category) {
if (isset($edit['l10n_client_key'])) {
$edit['data']['l10n_client_key'] = trim($edit['l10n_client_key']);
}
$edit['data']['l10n_client_disabled'] = !empty($edit['l10n_client_disabled']);
}
function l10n_client_user_token($account = NULL) {
global $user;
$account = isset($account) ? $account : $user;
return md5('l10n_client' . $account->uid . drupal_get_private_key());
}
function l10n_client_submit_translation($langcode, $source, $translation, $user_key, $user_token) {
$server_uid = current(explode(':', $user_key));
$signature = md5($user_key . $langcode . $source . $translation . $user_token);
$server_url = variable_get('l10n_client_server', 'https://localize.drupal.org/');
$response = xmlrpc($server_url . '/xmlrpc.php', array(
'l10n.submit.translation' => array(
$langcode,
$source,
$translation,
(int) $server_uid,
$user_token,
$signature,
),
));
if (!empty($response) && isset($response['status'])) {
if ($response['status']) {
$message = t('Translation sent and accepted by @server.', array(
'@server' => $server_url,
));
watchdog('l10n_client', 'Translation sent and accepted by @server.', array(
'@server' => $server_url,
));
}
else {
$message = t('Translation rejected by @server. Reason: %reason', array(
'%reason' => $response['reason'],
'@server' => $server_url,
));
watchdog('l10n_client', 'Translation rejected by @server. Reason: %reason', array(
'%reason' => $response['reason'],
'@server' => $server_url,
), WATCHDOG_WARNING);
}
return array(
$response['status'],
$message,
);
}
else {
$message = t('The connection with @server failed with the following error: %error_code: %error_message.', array(
'%error_code' => xmlrpc_errno(),
'%error_message' => xmlrpc_error_msg(),
'@server' => $server_url,
));
watchdog('l10n_client', 'The connection with @server failed with the following error: %error_code: %error_message.', array(
'%error_code' => xmlrpc_errno(),
'%error_message' => xmlrpc_error_msg(),
'@server' => $server_url,
), WATCHDOG_ERROR);
return array(
FALSE,
$message,
);
}
}
function l10n_client_field_extra_fields() {
$extra['user']['user']['form']['l10nclient'] = array(
'label' => 'Localization client',
'description' => t('On-page localization form element.'),
'weight' => 10,
);
return $extra;
}