Newer
Older
Dries Buytaert
committed
<?php
/**
Dries Buytaert
committed
* @file
* Interface translation summary, editing and deletion user interfaces.
Dries Buytaert
committed
*/
/**
* String search screen.
*/
function locale_translate_seek_screen() {
// Add CSS.
drupal_add_css(drupal_get_path('module', 'locale') . '/locale.css');
$elements = drupal_get_form('locale_translation_filter_form');
$output = drupal_render($elements);
$output .= _locale_translate_seek();
return $output;
}
/**
* Perform a string search and display results in a table
*/
function _locale_translate_seek() {
$output = '';
// We have at least one criterion to match
if (!($query = _locale_translate_seek_query())) {
$query = array(
'translation' => 'all',
'language' => 'all',
Dries Buytaert
committed
'customized' => 'all',
Dries Buytaert
committed
'string' => '',
);
}
$sql_query = db_select('locales_source', 's');
$sql_query->leftJoin('locales_target', 't', 't.lid = s.lid');
Dries Buytaert
committed
$sql_query->fields('s', array('source', 'location', 'context', 'lid'));
Dries Buytaert
committed
$sql_query->fields('t', array('translation', 'language', 'customized'));
Dries Buytaert
committed
// Compute LIKE section.
switch ($query['translation']) {
case 'translated':
$sql_query->condition('t.translation', '%' . db_like($query['string']) . '%', 'LIKE');
$sql_query->orderBy('t.translation', 'DESC');
Dries Buytaert
committed
if ($query['customized'] != 'all') {
$sql_query->condition('t.customized', $query['customized']);
}
Dries Buytaert
committed
break;
case 'untranslated':
$sql_query->condition(db_and()
->condition('s.source', '%' . db_like($query['string']) . '%', 'LIKE')
->isNull('t.translation')
);
$sql_query->orderBy('s.source');
break;
case 'all' :
default:
$condition = db_or()
->condition('s.source', '%' . db_like($query['string']) . '%', 'LIKE');
catch
committed
if ($query['language'] != LANGUAGE_SYSTEM) {
// Only search in translations if the language is not forced to system language.
Dries Buytaert
committed
$condition->condition('t.translation', '%' . db_like($query['string']) . '%', 'LIKE');
}
$sql_query->condition($condition);
break;
}
$limit_language = NULL;
catch
committed
if ($query['language'] != LANGUAGE_SYSTEM && $query['language'] != 'all') {
Dries Buytaert
committed
$sql_query->condition('language', $query['language']);
$limit_language = $query['language'];
}
$sql_query = $sql_query->extend('PagerDefault')->limit(50);
$locales = $sql_query->execute();
Dries Buytaert
committed
$header = array(t('String'), t('Context'), ($limit_language) ? t('Language') : t('Languages'), array('data' => t('Operations'), 'colspan' => '2'));
Dries Buytaert
committed
$strings = array();
foreach ($locales as $locale) {
if (!isset($strings[$locale->lid])) {
$strings[$locale->lid] = array(
'languages' => array(),
'location' => $locale->location,
'source' => $locale->source,
'context' => $locale->context,
);
}
if (isset($locale->language)) {
$strings[$locale->lid]['languages'][$locale->language] = $locale->translation;
}
}
$rows = array();
foreach ($strings as $lid => $string) {
$rows[] = array(
Dries Buytaert
committed
array('data' => check_plain(truncate_utf8(str_replace(LOCALE_PLURAL_DELIMITER, ', ', $string['source']), 150, FALSE, TRUE)) . '<br /><small>' . $string['location'] . '</small>'),
Dries Buytaert
committed
$string['context'],
array('data' => _locale_translate_language_list($string['languages'], $limit_language), 'align' => 'center'),
array('data' => l(t('edit'), "admin/config/regional/translate/edit/$lid", array('query' => drupal_get_destination())), 'class' => array('nowrap')),
array('data' => l(t('delete'), "admin/config/regional/translate/delete/$lid", array('query' => drupal_get_destination())), 'class' => array('nowrap')),
);
}
$output .= theme('table', array('header' => $header, 'rows' => $rows, 'empty' => t('No strings available.')));
$output .= theme('pager');
return $output;
}
/**
* List languages in search result table
*/
function _locale_translate_language_list($translation, $limit_language) {
// Add CSS.
drupal_add_css(drupal_get_path('module', 'locale') . '/locale.css');
$languages = language_list();
catch
committed
if (!locale_translate_english()) {
unset($languages['en']);
}
Dries Buytaert
committed
$output = '';
foreach ($languages as $langcode => $language) {
if (!$limit_language || $limit_language == $langcode) {
$output .= (!empty($translation[$langcode])) ? $langcode . ' ' : "<em class=\"locale-untranslated\">$langcode</em> ";
}
}
return $output;
}
/**
* Build array out of search criteria specified in request variables
*/
function _locale_translate_seek_query() {
$query = &drupal_static(__FUNCTION__);
if (!isset($query)) {
$query = array();
Dries Buytaert
committed
$fields = array('string', 'language', 'translation', 'customized');
Dries Buytaert
committed
foreach ($fields as $field) {
if (isset($_SESSION['locale_translation_filter'][$field])) {
$query[$field] = $_SESSION['locale_translation_filter'][$field];
}
}
}
return $query;
}
/**
* List locale translation filters that can be applied.
*/
function locale_translation_filters() {
$filters = array();
// Get all languages, except English
drupal_static_reset('language_list');
Dries Buytaert
committed
$languages = language_list();
Dries Buytaert
committed
$language_options = array();
foreach ($languages as $langcode => $language) {
if ($langcode != 'en' || locale_translate_english()) {
$language_options[$langcode] = $language->name;
}
catch
committed
}
Dries Buytaert
committed
$filters['string'] = array(
'title' => t('String contains'),
'description' => t('Leave blank to show all strings. The search is case sensitive.'),
);
$filters['language'] = array(
'title' => t('Language'),
Dries Buytaert
committed
'options' => array_merge(array('all' => t('All languages'), LANGUAGE_SYSTEM => t('System (English)')), $language_options),
Dries Buytaert
committed
);
$filters['translation'] = array(
'title' => t('Search in'),
Dries Buytaert
committed
'options' => array(
'all' => t('Both translated and untranslated strings'),
'translated' => t('Only translated strings'),
'untranslated' => t('Untranslated strings')
),
);
$filters['customized'] = array(
'title' => t('Translation type'),
'options' => array(
'all' => t('All'),
LOCALE_NOT_CUSTOMIZED => t('Non-customized translation'),
LOCALE_CUSTOMIZED => t('Customized translation'),
),
'states' => array(
'visible' => array(
':input[name=translation]' => array('value' => 'translated'),
)
),
Dries Buytaert
committed
);
return $filters;
}
/**
* Return form for locale translation filters.
*
* @ingroup forms
*/
function locale_translation_filter_form() {
$filters = locale_translation_filters();
$form['filters'] = array(
'#type' => 'fieldset',
'#title' => t('Filter translatable strings'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
foreach ($filters as $key => $filter) {
// Special case for 'string' filter.
if ($key == 'string') {
$form['filters']['status']['string'] = array(
catch
committed
'#type' => 'search',
Dries Buytaert
committed
'#title' => $filter['title'],
'#description' => $filter['description'],
);
}
else {
$form['filters']['status'][$key] = array(
'#title' => $filter['title'],
'#type' => 'select',
'#empty_value' => 'all',
'#empty_option' => $filter['options']['all'],
'#size' => 0,
'#options' => $filter['options'],
);
Dries Buytaert
committed
if (isset($filter['states'])) {
$form['filters']['status'][$key]['#states'] = $filter['states'];
}
Dries Buytaert
committed
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
}
if (!empty($_SESSION['locale_translation_filter'][$key])) {
$form['filters']['status'][$key]['#default_value'] = $_SESSION['locale_translation_filter'][$key];
}
}
$form['filters']['actions'] = array(
'#type' => 'actions',
'#attributes' => array('class' => array('container-inline')),
);
$form['filters']['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Filter'),
);
if (!empty($_SESSION['locale_translation_filter'])) {
$form['filters']['actions']['reset'] = array(
'#type' => 'submit',
'#value' => t('Reset')
);
}
return $form;
}
/**
* Validate result from locale translation filter form.
*/
function locale_translation_filter_form_validate($form, &$form_state) {
Dries Buytaert
committed
if ($form_state['values']['op'] == t('Filter') && empty($form_state['values']['language'])) {
Dries Buytaert
committed
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
form_set_error('type', t('You must select something to filter by.'));
}
}
/**
* Process result from locale translation filter form.
*/
function locale_translation_filter_form_submit($form, &$form_state) {
$op = $form_state['values']['op'];
$filters = locale_translation_filters();
switch ($op) {
case t('Filter'):
foreach ($filters as $name => $filter) {
if (isset($form_state['values'][$name])) {
$_SESSION['locale_translation_filter'][$name] = $form_state['values'][$name];
}
}
break;
case t('Reset'):
$_SESSION['locale_translation_filter'] = array();
break;
}
$form_state['redirect'] = 'admin/config/regional/translate/translate';
}
/**
* User interface for string editing.
Dries Buytaert
committed
*
* @ingroup forms
Dries Buytaert
committed
*/
function locale_translate_edit_form($form, &$form_state, $lid) {
// Fetch source string, if possible.
Dries Buytaert
committed
$source = db_query('SELECT source, context, location FROM {locales_source} WHERE lid = :lid', array(':lid' => $lid))->fetchObject();
Dries Buytaert
committed
if (!$source) {
drupal_set_message(t('String not found.'), 'error');
drupal_goto('admin/config/regional/translate/translate');
}
Dries Buytaert
committed
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
// Split source to work with plural values.
$source_array = explode(LOCALE_PLURAL_DELIMITER, $source->source);
if (count($source_array) == 1) {
// Add original text value and mark as non-plural.
$form['plural'] = array(
'#type' => 'value',
'#value' => 0
);
$form['original'] = array(
'#type' => 'item',
'#title' => t('Original text'),
'#markup' => check_plain($source_array[0]),
);
}
else {
// Add original text value and mark as plural.
$form['plural'] = array(
'#type' => 'value',
'#value' => 1
);
$form['original_singular'] = array(
'#type' => 'item',
'#title' => t('Original singular form'),
'#markup' => check_plain($source_array[0]),
);
$form['original_plural'] = array(
'#type' => 'item',
'#title' => t('Original plural form'),
'#markup' => check_plain($source_array[1]),
);
}
Dries Buytaert
committed
if (!empty($source->context)) {
$form['context'] = array(
'#type' => 'item',
'#title' => t('Context'),
'#markup' => check_plain($source->context),
);
}
$form['lid'] = array(
'#type' => 'value',
'#value' => $lid
);
$form['location'] = array(
'#type' => 'value',
'#value' => $source->location
);
// Include default form controls with empty values for all languages.
// This ensures that the languages are always in the same order in forms.
$languages = language_list();
catch
committed
if (!locale_translate_english()) {
unset($languages['en']);
}
Dries Buytaert
committed
// Store languages to iterate for validation and submission of the form.
$form_state['langcodes'] = array_keys($languages);
$plural_formulas = variable_get('locale_translation_plurals', array());
$form['translations'] = array(
'#type' => 'vertical_tabs',
'#tree' => TRUE
);
Dries Buytaert
committed
// Approximate the number of rows to use in the default textarea.
Dries Buytaert
committed
$rows = min(ceil(str_word_count($source_array[0]) / 12), 10);
Dries Buytaert
committed
foreach ($languages as $langcode => $language) {
$form['translations'][$langcode] = array(
Dries Buytaert
committed
'#type' => 'fieldset',
catch
committed
'#title' => $language->name,
Dries Buytaert
committed
);
Dries Buytaert
committed
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
if (empty($form['plural']['#value'])) {
$form['translations'][$langcode][0] = array(
'#type' => 'textarea',
'#title' => $language->name,
'#rows' => $rows,
'#default_value' => '',
);
}
else {
// Dealing with plural strings.
if (isset($plural_formulas[$langcode]['plurals']) && $plural_formulas[$langcode]['plurals'] > 1) {
// Add a textarea for each plural variant.
for ($i = 0; $i < $plural_formulas[$langcode]['plurals']; $i++) {
$form['translations'][$langcode][$i] = array(
'#type' => 'textarea',
'#title' => ($i == 0 ? t('Singular form') : format_plural($i, 'First plural form', '@count. plural form')),
'#rows' => $rows,
'#default_value' => '',
);
}
}
else {
// Fallback for unknown number of plurals.
$form['translations'][$langcode][0] = array(
'#type' => 'textarea',
'#title' => t('Sigular form'),
'#rows' => $rows,
'#default_value' => '',
);
$form['translations'][$langcode][1] = array(
'#type' => 'textarea',
'#title' => t('Plural form'),
'#rows' => $rows,
'#default_value' => '',
);
}
}
Dries Buytaert
committed
}
// Fetch translations and fill in default values in the form.
Dries Buytaert
committed
$result = db_query("SELECT DISTINCT translation, language FROM {locales_target} WHERE lid = :lid", array(':lid' => $lid));
Dries Buytaert
committed
foreach ($result as $translation) {
Dries Buytaert
committed
$translation_array = explode(LOCALE_PLURAL_DELIMITER, $translation->translation);
for ($i = 0; $i < count($translation_array); $i++) {
$form['translations'][$translation->language][$i]['#default_value'] = $translation_array[$i];
}
Dries Buytaert
committed
}
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Save translations'));
return $form;
}
/**
* Validate string editing form submissions.
*/
function locale_translate_edit_form_validate($form, &$form_state) {
Dries Buytaert
committed
foreach ($form_state['langcodes'] as $langcode) {
foreach ($form_state['values']['translations'][$langcode] as $key => $value) {
if (!locale_string_is_safe($value)) {
form_set_error("translations][$langcode][$key", t('The submitted string contains disallowed HTML: %string', array('%string' => $value)));
watchdog('locale', 'Attempted submission of a translation string with disallowed HTML: %string', array('%string' => $value), WATCHDOG_WARNING);
}
Dries Buytaert
committed
}
}
}
/**
* Process string editing form submissions.
*
* Saves all translations of one string submitted from a form.
*/
function locale_translate_edit_form_submit($form, &$form_state) {
$lid = $form_state['values']['lid'];
Dries Buytaert
committed
foreach ($form_state['langcodes'] as $langcode) {
// Serialize plural variants in one string by LOCALE_PLURAL_DELIMITER.
$value = implode(LOCALE_PLURAL_DELIMITER, $form_state['values']['translations'][$langcode]);
$translation = db_query("SELECT translation FROM {locales_target} WHERE lid = :lid AND language = :language", array(':lid' => $lid, ':language' => $langcode))->fetchField();
// No translation when all strings are empty.
$has_translation = FALSE;
foreach ($form_state['values']['translations'][$langcode] as $string) {
if (!empty($string)) {
$has_translation = TRUE;
break;
}
}
if ($has_translation) {
Dries Buytaert
committed
// Only update or insert if we have a value to use.
Dries Buytaert
committed
if (!empty($translation) && $translation != $value) {
Dries Buytaert
committed
db_update('locales_target')
->fields(array(
'translation' => $value,
Dries Buytaert
committed
'customized' => LOCALE_CUSTOMIZED,
Dries Buytaert
committed
))
->condition('lid', $lid)
Dries Buytaert
committed
->condition('language', $langcode)
Dries Buytaert
committed
->execute();
}
Dries Buytaert
committed
if (empty($translation)) {
Dries Buytaert
committed
db_insert('locales_target')
->fields(array(
'lid' => $lid,
'translation' => $value,
Dries Buytaert
committed
'language' => $langcode,
Dries Buytaert
committed
'customized' => LOCALE_CUSTOMIZED,
Dries Buytaert
committed
))
->execute();
}
}
elseif (!empty($translation)) {
// Empty translation entered: remove existing entry from database.
db_delete('locales_target')
->condition('lid', $lid)
Dries Buytaert
committed
->condition('language', $langcode)
Dries Buytaert
committed
->execute();
}
// Force JavaScript translation file recreation for this language.
Dries Buytaert
committed
_locale_invalidate_js($langcode);
Dries Buytaert
committed
}
drupal_set_message(t('The string has been saved.'));
// Clear locale cache.
_locale_invalidate_js();
Dries Buytaert
committed
cache()->deletePrefix('locale:');
Dries Buytaert
committed
$form_state['redirect'] = 'admin/config/regional/translate/translate';
return;
}
/**
* String deletion confirmation page.
*/
function locale_translate_delete_page($lid) {
if ($source = db_query('SELECT lid, source FROM {locales_source} WHERE lid = :lid', array(':lid' => $lid))->fetchObject()) {
return drupal_get_form('locale_translate_delete_form', $source);
}
else {
return drupal_not_found();
}
}
/**
* User interface for the string deletion confirmation screen.
Dries Buytaert
committed
*
* @ingroup forms
Dries Buytaert
committed
*/
function locale_translate_delete_form($form, &$form_state, $source) {
$form['lid'] = array('#type' => 'value', '#value' => $source->lid);
return confirm_form($form, t('Are you sure you want to delete the string "%source"?', array('%source' => $source->source)), 'admin/config/regional/translate/translate', t('Deleting the string will remove all translations of this string in all languages. This action cannot be undone.'), t('Delete'), t('Cancel'));
}
/**
* Process string deletion submissions.
*/
function locale_translate_delete_form_submit($form, &$form_state) {
db_delete('locales_source')
->condition('lid', $form_state['values']['lid'])
->execute();
db_delete('locales_target')
->condition('lid', $form_state['values']['lid'])
->execute();
// Force JavaScript translation file recreation for all languages.
_locale_invalidate_js();
Dries Buytaert
committed
cache()->deletePrefix('locale:');
Dries Buytaert
committed
drupal_set_message(t('The string has been removed.'));
$form_state['redirect'] = 'admin/config/regional/translate/translate';
}