Calling all Drupal developers!
Help us get this on the first page of Digg. DIGG NOW!
Help us get this on the first page of Digg. DIGG NOW!
<?php
// $Id: l10n_client.module,v 1.11 2008/03/25 18:02:10 goba Exp $
/**
* @file
* Localization client. Provides on-page translation editing.
*/
/**
* Implementation of hook_menu().
*/
function l10n_client_menu() {
$items = array();
// AJAX callback path for strings.
$items['l10n_client/save'] = array(
'title' => 'Save string',
'page callback' => 'l10n_client_save_string',
'access arguments' => array('use on-page translation'),
'type' => MENU_CALLBACK,
);
// Direct copy of the import tab from locale module to
// make space for the "Reimport package" tab below.
$items['admin/build/translate/import/file'] = array(
'title' => 'Import file',
'page callback' => 'locale_inc_callback',
'page arguments' => array('drupal_get_form', 'locale_translate_import_form'),
'weight' => -5,
'type' => MENU_DEFAULT_LOCAL_TASK,
);
$items['admin/build/translate/import/package'] = array(
'title' => 'Reimport package',
'page callback' => 'drupal_get_form',
'page arguments' => array('l10n_client_import_package_form'),
'weight' => 5,
'type' => MENU_LOCAL_TASK,
);
// Direct copy of the Configure tab from locale module to
// make space for the "Localization sharing" tab below.
$items['admin/settings/language/configure/language'] = array(
'title' => 'Language negotiation',
'page callback' => 'locale_inc_callback',
'page arguments' => array('drupal_get_form', 'locale_languages_configure_form'),
'weight' => -10,
'type' => MENU_DEFAULT_LOCAL_TASK,
);
$items['admin/settings/language/configure/l10n_client'] = array(
'title' => 'Localization sharing',
'page callback' => 'drupal_get_form',
'page arguments' => array('l10n_client_settings_form'),
'weight' => 5,
'type' => MENU_LOCAL_TASK,
);
return $items;
}
/**
* Implementation of hook_perm().
*/
function l10n_client_perm() {
return array('use on-page translation');
}
/**
* Implementation of hook_init().
*/
function l10n_client_init() {
global $conf, $language;
if (user_access('use on-page translation')) {
// Turn off the short string cache *in this request*, so we will
// have an accurate picture of strings used to assemble the page.
$conf['locale_cache_strings'] = 0;
drupal_add_css(drupal_get_path('module', 'l10n_client') .'/l10n_client.css', 'module');
// Add jquery cookie plugin -- this should actually belong in
// jstools (but hasn't been updated for HEAD)
drupal_add_js(drupal_get_path('module', 'l10n_client') .'/jquery.hotkeys.js', 'module');
drupal_add_js(drupal_get_path('module', 'l10n_client') .'/jquery.cookie.js', 'module');
drupal_add_js(drupal_get_path('module', 'l10n_client') .'/l10n_client.js', 'module');
// Add server address if we have a server set up. Otherwise add empty string.
$server = variable_get('l10n_client_server', '');
drupal_add_js(array('l10n_client_server' => $server ? $server .'/translate/remote/1.0/'. $language->language : ''), 'setting');
// We use textareas to be able to edit long text, which need resizing.
drupal_add_js('misc/textarea.js', 'module');
}
}
/**
* Implementation of hook_footer().
*
* Output a form to the page and a list of strings used to build
* the page in JSON form.
*/
function l10n_client_footer() {
global $conf, $language;
if (user_access('use on-page translation')) {
// Get all strings used on the page.
$strings = locale();
if (is_array($strings) && isset($strings[$language->language])) {
// If we have strings for the page language, restructure the data.
$l10n_strings = array();
foreach ($strings[$language->language] as $string => $translation) {
$l10n_strings[] = array($string, $translation);
}
array_multisort($l10n_strings);
// Include string selector on page.
$string_list = _l10n_client_string_list($l10n_strings);
// Include editing form on page.
$l10n_form = drupal_get_form('l10n_client_form', $l10n_strings);
// Include search form on page.
$l10n_search = drupal_get_form('l10n_client_search_form');
// We need this hack as JS addition does not work this late on the page.
//$l10n_json = '<script type="text/javascript">jQuery.extend(Drupal, { l10nStrings: '. drupal_to_js($l10n_strings) .' });</script>';
$l10n_dom = _l10n_client_dom_strings($l10n_strings);
// UI Labels
$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 = "
<div id='l10n-client' class='hidden'>
<div class='labels'>
<span class='toggle'>$toggle_label</span>
<div class='label strings'>$string_label</div>
<div class='label source'>$source_label</div>
<div class='label translation'>$translation_label</div>
</div>
<div id='l10n-client-string-select'>
$string_list
$l10n_search
</div>
<div id='l10n-client-string-editor'>
<div class='source'>
<div class='source-text'></div>
</div>
<div class='translation'>
$l10n_form
</div>
</div>
</div>
$l10n_dom
";
return $output;
}
}
}
/**
* Helper function for the string list DOM tree
*/
function _l10n_client_dom_strings($strings) {
$output = '';
foreach ($strings as $values) {
$source = $values[0] === TRUE ? '' : $values[0];
$target = $values[1] === TRUE ? '' : $values[1];
$output .= "<div><span class='source'>$source</span><span class='target'>$target</span></div>";
}
return "<div id='l10n-client-data'>$output</div>";
}
/**
* String selection has been moved to a jquery-based list.
* Todo: make this a themeable function.
*/
function _l10n_client_string_list($strings) {
// Build a list of short string excerpts for a selectable list.
foreach ($strings as $values) {
// Add a class to help identify translated strings
if ($values[1] === TRUE) {
$str_class = 'untranslated';
}
else {
$str_class = 'translated';
}
// TRUE means we don't have translation, so we use the original string,
// so we always have the string displayed on the page in the dropdown.
$original = $values[1] === TRUE ? $values[0] : $values[1];
// Remove html tags, at least for display
$original = strip_tags($original);
// Truncate and add ellipsis if too long.
$string = truncate_utf8($original, 78, TRUE);
$select_list[] = "<li class='$str_class'>". $string . ($original == $string ? '' : '...') .'</li>';
}
$output = implode("\n", $select_list);
return "<ul class='string-list'>$output</ul>";
}
/**
* String editing form. Source & selection moved to UI components outside the form.
* Backed with jquery magic on the client.
*
* @todo
* This form has nothing to do with different plural versions yet.
*/
function l10n_client_form($form_id, $strings) {
global $language;
// Selector and editing form.
$form = array();
$form['#action'] = url('l10n_client/save');
$form['target'] = array(
'#type' => 'textarea',
'#resizable' => false,
'#rows' => 6,
);
$form['save'] = array(
'#value' => t('Save translation'),
'#type' => 'submit',
);
$form['copy'] = array(
'#value' => "<input id='edit-copy' class='form-submit' type='button' value='". t('Copy Source') ."'/>",
);
$form['clear'] = array(
'#value' => "<input id='edit-clear' class='form-submit' type='button' value='". t('Clear') ."'/>",
);
return $form;
}
/**
* Search form for string list
*/
function l10n_client_search_form() {
global $language;
// Selector and editing form.
$form = array();
$form['search'] = array(
'#type' => 'textfield',
);
$form['clear-button'] = array(
'#value' => "<input id='search-filter-clear' class='form-submit' type='button' value='". t('X') ."'/>",
);
return $form;
}
/**
* Menu callback. Saves a string translation coming as POST data.
*/
function l10n_client_save_string() {
global $language;
if (user_access('use on-page translation')) {
if (isset($_POST['source']) && isset($_POST['target'])) {
include_once 'includes/locale.inc';
$report = array(0, 0, 0);
_locale_import_one_string_db($report, $language->language, $_POST['source'], $_POST['target'], 'default', NULL, LOCALE_IMPORT_OVERWRITE);
cache_clear_all('locale:', 'cache', TRUE);
}
}
}
// -----------------------------------------------------------------------------
/**
* Page callback function to present a form to reimport a translation package.
*
* @ingroup forms
* @see l10n_client_import_package_form_submit()
*/
function l10n_client_import_package_form(&$form_state) {
// Get all languages, except English
$names = locale_language_list('name', TRUE);
unset($names['en']);
if (!count($names)) {
// This only works if there is any foreign language set up.
drupal_set_message(t('No languages set up to reimport packages into.', 'warning'));
return array();
}
$form = array();
$form['reimport'] = array(
'#type' => 'fieldset',
'#title' => t('Reimport translation package'),
);
$form['reimport']['langcode'] = array(
'#type' => 'radios',
'#title' => t('Language package'),
'#options' => $names,
'#default_value' => array_shift(array_keys($names)),
'#description' => t('Choose a language package to reimport translations from. All files of the package should be already uncompressed to the Drupal directories. All translation files will be imported for enabled modules and themes and will be imported to the built in interface textgroup.'),
);
$form['reimport']['cleanup'] = array(
'#type' => 'checkbox',
'#title' => t('Clean up database before reimport'),
'#description' => t('If checked, all translations for the given language will be deleted from the database first, and you will loose all your customized translations and those not available in the files being imported. Use with extreme caution.')
);
$form['reimport']['submit'] = array('#type' => 'submit', '#value' => t('Reimport package'));
return $form;
}
/**
* Submission handler for package reimport form.
*
* @see l10n_client_import_package_form()
*/
function l10n_client_import_package_form_submit($form, &$form_state) {
if (!empty($form_state['values']['cleanup'])) {
// Clean out all translations first if user asked to do that.
db_query("DELETE FROM {locales_target} WHERE language = '%s'", $form_state['values']['langcode']);
// Also remove all source strings without translations.
db_query("DELETE FROM {locales_source} WHERE lid NOT IN (SELECT lid FROM {locales_target})");
}
// Set up and start batch for new imports.
include_once 'includes/locale.inc';
if ($batch = locale_batch_by_language($form_state['values']['langcode'], '_locale_batch_language_finished')) {
batch_set($batch);
}
$form_state['redirect'] = 'admin/build/translate';
}
// -----------------------------------------------------------------------------
/**
* Settings form for l10n_client.
*
* Enable users to set up a central server to share translations with.
*/
function l10n_client_settings_form() {
$form = array();
$form['l10n_client_server'] = array(
'#title' => t('Share translations with server'),
'#type' => 'textfield',
'#description' => t('Address of localization server to use. The server will be used to share translations submitted through the localization client interface. Each local submission will result in a call to this server as well. To be able to submit a translation there, you should be logged in there, but from then on, everything is automated. A list of servers you can use is available from the <a href="@project">Localization server project page</a>.', array('@project' => 'http://drupal.org/project/l10n_server')),
'#default_value' => variable_get('l10n_client_server', ''),
);
return system_settings_form($form);
}
/**
* Validation to make sure the provided server can handle our submissions.
*
* Make sure it supports the exact version of the API we will try to use.
*/
function l10n_client_settings_form_validate($form, &$form_state) {
// Try to invoke the remote string submission with a test request.
$response = drupal_http_request($form_state['values']['l10n_client_server'] .'/translate/remote/1.0/test');
if (!preg_match('!^<\!-- v1\.0 Localization community!', $response->data)) {
form_set_error('l10n_client_server', t('Invalid localization server address specified, or the given server could not handle the v1.0 remote submission API. Make sure you specified the right server address.'));
}
else {
drupal_set_message(t('Verified that the specified server can handle remote string submissions.'));
}
}