Code coverage for /20081101/includes/locale.inc

Line #Times calledCode
1
<?php
2
// $Id: locale.inc,v 1.190 2008/10/26 18:06:38 dries Exp $
3
4
/**
5
 * @file
6
 * Administration functions for locale.module.
7
 */
8
9
/**
10
 * Regular expression pattern used to localize JavaScript strings.
11
 */
1235
define('LOCALE_JS_STRING',
'(?:(?:\'(?:\\\\\'|[^\'])*\'|"(?:\\\\"|[^"])*")(?:\s*\+\s*)?)+');
13
14
/**
15
 * Translation import mode overwriting all existing translations
16
 * if new translated version available.
17
 */
1835
define('LOCALE_IMPORT_OVERWRITE', 0);
19
20
/**
21
 * Translation import mode keeping existing translations and only
22
 * inserting new strings.
23
 */
2435
define('LOCALE_IMPORT_KEEP', 1);
25
26
/**
27
 * @defgroup locale-language-overview Language overview functionality
28
 * @{
29
 */
30
31
/**
32
 * User interface for the language overview screen.
33
 */
3435
function locale_languages_overview_form() {
358
  $languages = language_list('language', TRUE);
36
378
  $options = array();
388
  $form['weight'] = array('#tree' => TRUE);
398
  foreach ($languages as $langcode => $language) {
40
418
    $options[$langcode] = '';
428
    if ($language->enabled) {
438
      $enabled[] = $langcode;
448
    }
458
    $form['weight'][$langcode] = array(
468
      '#type' => 'weight',
478
      '#default_value' => $language->weight,
488
      '#attributes' => array('class' => 'language-order-weight'),
49
    );
508
    $form['name'][$langcode] = array('#markup' =>
check_plain($language->name));
518
    $form['native'][$langcode] = array('#markup' =>
check_plain($language->native));
528
    $form['direction'][$langcode] = array('#markup' =>
($language->direction == LANGUAGE_RTL ? t('Right to left') : t('Left to
right')));
538
  }
548
  $form['enabled'] = array('#type' => 'checkboxes',
558
    '#options' => $options,
568
    '#default_value' => $enabled,
57
  );
588
  $form['site_default'] = array('#type' => 'radios',
598
    '#options' => $options,
608
    '#default_value' => language_default('language'),
61
  );
628
  $form['submit'] = array('#type' => 'submit', '#value' => t('Save
configuration'));
638
  $form['#theme'] = 'locale_languages_overview_form';
64
658
  return $form;
660
}
67
68
/**
69
 * Theme the language overview form.
70
 *
71
 * @ingroup themeable
72
 */
7335
function theme_locale_languages_overview_form($form) {
747
  $default = language_default();
757
  foreach ($form['name'] as $key => $element) {
76
    // Do not take form control structures.
777
    if (is_array($element) && element_child($key)) {
78
      // Disable checkbox for the default language, because it cannot be
disabled.
797
      if ($key == $default->language) {
807
        $form['enabled'][$key]['#attributes']['disabled'] = 'disabled';
817
      }
827
      $rows[] = array(
83
        'data' => array(
847
          '<strong>' . drupal_render($form['name'][$key]) . '</strong>',
857
          drupal_render($form['native'][$key]),
867
          check_plain($key),
877
          drupal_render($form['direction'][$key]),
887
          array('data' => drupal_render($form['enabled'][$key]), 'align' =>
'center'),
897
          drupal_render($form['site_default'][$key]),
907
          drupal_render($form['weight'][$key]),
917
          l(t('edit'), 'admin/settings/language/edit/' . $key) . (($key !=
'en' && $key != $default->language) ? ' ' . l(t('delete'),
'admin/settings/language/delete/' . $key) : '')
927
        ),
93
        'class' => 'draggable'
947
      );
957
    }
967
  }
977
  $header = array(array('data' => t('English name')), array('data' =>
t('Native name')), array('data' => t('Code')), array('data' =>
t('Direction')), array('data' => t('Enabled')), array('data' =>
t('Default')), array('data' => t('Weight')), array('data' =>
t('Operations')));
987
  $output = theme('table', $header, $rows, array('id' =>
'language-order'));
997
  $output .= drupal_render($form);
100
  
1017
  drupal_add_tabledrag('language-order', 'order', 'sibling',
'language-order-weight');
102
1037
  return $output;
1040
}
105
106
/**
107
 * Process language overview form submissions, updating existing languages.
108
 */
10935
function locale_languages_overview_form_submit($form, &$form_state) {
1101
  $languages = language_list();
1111
  $default = language_default();
1121
  $enabled_count = 0;
1131
  foreach ($languages as $langcode => $language) {
1141
    if ($form_state['values']['site_default'] == $langcode ||
$default->language == $langcode) {
115
      // Automatically enable the default language and the language
116
      // which was default previously (because we will not get the
117
      // value from that disabled checkox).
1181
      $form_state['values']['enabled'][$langcode] = 1;
1191
    }
1201
    if ($form_state['values']['enabled'][$langcode]) {
1211
      $enabled_count++;
1221
      $language->enabled = 1;
1231
    }
124
    else {
1250
      $language->enabled = 0;
126
    }
1271
    $language->weight = $form_state['values']['weight'][$langcode];
1281
    db_query("UPDATE {languages} SET enabled = %d, weight = %d WHERE
language = '%s'", $language->enabled, $language->weight, $langcode);
1291
    $languages[$langcode] = $language;
1301
  }
1311
  drupal_set_message(t('Configuration saved.'));
1321
  variable_set('language_default',
$languages[$form_state['values']['site_default']]);
1331
  variable_set('language_count', $enabled_count);
134
135
  // Changing the language settings impacts the interface.
1361
  cache_clear_all('*', 'cache_page', TRUE);
137
1381
  $form_state['redirect'] = 'admin/settings/language';
1391
  return;
1400
}
141
/**
142
 * @} End of "locale-language-overview"
143
 */
144
145
/**
146
 * @defgroup locale-language-add-edit Language addition and editing
functionality
147
 * @{
148
 */
149
150
/**
151
 * User interface for the language addition screen.
152
 */
15335
function locale_languages_add_screen() {
1546
  $output = drupal_get_form('locale_languages_predefined_form');
1554
  $output .= drupal_get_form('locale_languages_custom_form');
1563
  return $output;
1570
}
158
159
/**
160
 * Predefined language setup form.
161
 */
16235
function locale_languages_predefined_form() {
1636
  $predefined = _locale_prepare_predefined_list();
1646
  $form = array();
1656
  $form['language list'] = array('#type' => 'fieldset',
1666
    '#title' => t('Predefined language'),
1676
    '#collapsible' => TRUE,
168
  );
1696
  $form['language list']['langcode'] = array('#type' => 'select',
1706
    '#title' => t('Language name'),
1716
    '#default_value' => key($predefined),
1726
    '#options' => $predefined,
1736
    '#description' => t('Select the desired language and click the <em>Add
language</em> button. (Use the <em>Custom language</em> options if your
desired language does not appear in this list.)'),
174
  );
1756
  $form['language list']['submit'] = array('#type' => 'submit', '#value' =>
t('Add language'));
1766
  return $form;
1770
}
178
179
/**
180
 * Custom language addition form.
181
 */
18235
function locale_languages_custom_form() {
1834
  $form = array();
1844
  $form['custom language'] = array('#type' => 'fieldset',
1854
    '#title' => t('Custom language'),
1864
    '#collapsible' => TRUE,
1874
    '#collapsed' => TRUE,
188
  );
1894
  _locale_languages_common_controls($form['custom language']);
1904
  $form['custom language']['submit'] = array(
1914
    '#type' => 'submit',
1924
    '#value' => t('Add custom language')
1934
  );
194
  // Reuse the validation and submit functions of the predefined language
setup form.
1954
  $form['#submit'][] = 'locale_languages_predefined_form_submit';
1964
  $form['#validate'][] = 'locale_languages_predefined_form_validate';
1974
  return $form;
1980
}
199
200
/**
201
 * Editing screen for a particular language.
202
 *
203
 * @param $langcode
204
 *   Language code of the language to edit.
205
 */
20635
function locale_languages_edit_form(&$form_state, $langcode) {
2070
  if ($language = db_fetch_object(db_query("SELECT * FROM {languages} WHERE
language = '%s'", $langcode))) {
2080
    $form = array();
2090
    _locale_languages_common_controls($form, $language);
2100
    $form['submit'] = array(
2110
      '#type' => 'submit',
2120
      '#value' => t('Save language')
2130
    );
2140
    $form['#submit'][] = 'locale_languages_edit_form_submit';
2150
    $form['#validate'][] = 'locale_languages_edit_form_validate';
2160
    return $form;
2170
  }
218
  else {
2190
    drupal_not_found();
220
  }
2210
}
222
223
/**
224
 * Common elements of the language addition and editing form.
225
 *
226
 * @param $form
227
 *   A parent form item (or empty array) to add items below.
228
 * @param $language
229
 *   Language object to edit.
230
 */
23135
function _locale_languages_common_controls(&$form, $language = NULL) {
2324
  if (!is_object($language)) {
2334
    $language = new stdClass();
2344
  }
2354
  if (isset($language->language)) {
2360
    $form['langcode_view'] = array(
2370
      '#type' => 'item',
2380
      '#title' => t('Language code'),
2390
      '#markup' => $language->language
2400
    );
2410
    $form['langcode'] = array(
2420
      '#type' => 'value',
2430
      '#value' => $language->language
2440
    );
2450
  }
246
  else {
2474
    $form['langcode'] = array('#type' => 'textfield',
2484
      '#title' => t('Language code'),
2494
      '#size' => 12,
2504
      '#maxlength' => 60,
2514
      '#required' => TRUE,
2524
      '#default_value' => @$language->language,
2534
      '#disabled' => (isset($language->language)),
2544
      '#description' => t('<a href="@rfc4646">RFC 4646</a> compliant
language identifier. Language codes typically use a country code, and
optionally, a script or regional variant name. <em>Examples: "en", "en-US"
and "zh-Hant".</em>', array('@rfc4646' =>
'http://www.ietf.org/rfc/rfc4646.txt')),
255
    );
256
  }
2574
  $form['name'] = array('#type' => 'textfield',
2584
    '#title' => t('Language name in English'),
2594
    '#maxlength' => 64,
2604
    '#default_value' => @$language->name,
2614
    '#required' => TRUE,
2624
    '#description' => t('Name of the language in English. Will be available
for translation in all languages.'),
263
  );
2644
  $form['native'] = array('#type' => 'textfield',
2654
    '#title' => t('Native language name'),
2664
    '#maxlength' => 64,
2674
    '#default_value' => @$language->native,
2684
    '#required' => TRUE,
2694
    '#description' => t('Name of the language in the language being
added.'),
270
  );
2714
  $form['prefix'] = array('#type' => 'textfield',
2724
    '#title' => t('Path prefix'),
2734
    '#maxlength' => 64,
2744
    '#default_value' => @$language->prefix,
2754
    '#description' => t('Language code or other custom string for pattern
matching within the path. With language negotiation set to <em>Path prefix
only</em> or <em>Path prefix with language fallback</em>, this site is
presented in this language when the Path prefix value matches an element in
the path. For the default language, this value may be left blank.
<strong>Modifying this value will break existing URLs and should be used
with caution in a production environment.</strong> <em>Example: Specifying
"deutsch" as the path prefix for German results in URLs in the form
"www.example.com/deutsch/node".</em>')
2764
  );
2774
  $form['domain'] = array('#type' => 'textfield',
2784
    '#title' => t('Language domain'),
2794
    '#maxlength' => 128,
2804
    '#default_value' => @$language->domain,
2814
    '#description' => t('Language-specific URL, with protocol. With
language negotiation set to <em>Domain name only</em>, the site is
presented in this language when the URL accessing the site references this
domain. For the default language, this value may be left blank.
<strong>This value must include a protocol as part of the string.</strong>
<em>Example: Specifying "http://example.de" or "http://de.example.com" as
language domains for German results in URLs in the forms
"http://example.de/node" and "http://de.example.com/node",
respectively.</em>'),
282
  );
2834
  $form['direction'] = array('#type' => 'radios',
2844
    '#title' => t('Direction'),
2854
    '#required' => TRUE,
2864
    '#description' => t('Direction that text in this language is
presented.'),
2874
    '#default_value' => @$language->direction,
2884
    '#options' => array(LANGUAGE_LTR => t('Left to right'), LANGUAGE_RTL =>
t('Right to left'))
2894
  );
2904
  return $form;
2910
}
292
293
/**
294
 * Validate the language addition form.
295
 */
29635
function locale_languages_predefined_form_validate($form, &$form_state) {
2973
  $langcode = $form_state['values']['langcode'];
298
2993
  if ($duplicate = db_result(db_query("SELECT COUNT(*) FROM {languages}
WHERE language = '%s'", $langcode)) != 0) {
3000
    form_set_error('langcode', t('The language %language (%code) already
exists.', array('%language' => $form_state['values']['name'], '%code' =>
$langcode)));
3010
  }
302
3033
  if (!isset($form_state['values']['name'])) {
304
    // Predefined language selection.
3052
    $predefined = _locale_get_predefined_list();
3062
    if (!isset($predefined[$langcode])) {
3070
      form_set_error('langcode', t('Invalid language code.'));
3080
    }
3092
  }
310
  else {
311
    // Reuse the editing form validation routine if we add a custom
language.
3121
    locale_languages_edit_form_validate($form, $form_state);
313
  }
3143
}
315
316
/**
317
 * Process the language addition form submission.
318
 */
31935
function locale_languages_predefined_form_submit($form, &$form_state) {
3203
  $langcode = $form_state['values']['langcode'];
3213
  if (isset($form_state['values']['name'])) {
322
    // Custom language form.
3231
    locale_add_language($langcode, $form_state['values']['name'],
$form_state['values']['native'], $form_state['values']['direction'],
$form_state['values']['domain'], $form_state['values']['prefix']);
3241
    drupal_set_message(t('The language %language has been created and can
now be used. More information is available on the <a
href="@locale-help">help screen</a>.', array('%language' =>
t($form_state['values']['name']), '@locale-help' =>
url('admin/help/locale'))));
3251
  }
326
  else {
327
    // Predefined language selection.
3282
    $predefined = _locale_get_predefined_list();
3292
    locale_add_language($langcode);
3302
    drupal_set_message(t('The language %language has been created and can
now be used. More information is available on the <a
href="@locale-help">help screen</a>.', array('%language' =>
t($predefined[$langcode][0]), '@locale-help' =>
url('admin/help/locale'))));
331
  }
332
333
  // See if we have language files to import for the newly added
334
  // language, collect and import them.
3353
  if ($batch = locale_batch_by_language($langcode,
'_locale_batch_language_finished')) {
3360
    batch_set($batch);
3370
  }
338
3393
  $form_state['redirect'] = 'admin/settings/language';
3403
  return;
3410
}
342
343
/**
344
 * Validate the language editing form. Reused for custom language addition
too.
345
 */
34635
function locale_languages_edit_form_validate($form, &$form_state) {
3471
  if (!empty($form_state['values']['domain']) &&
!empty($form_state['values']['prefix'])) {
3480
    form_set_error('prefix', t('Domain and path prefix values should not be
set at the same time.'));
3490
  }
3501
  if (!empty($form_state['values']['domain']) && $duplicate =
db_fetch_object(db_query("SELECT language FROM {languages} WHERE domain =
'%s' AND language <> '%s'", $form_state['values']['domain'],
$form_state['values']['langcode']))) {
3510
    form_set_error('domain', t('The domain (%domain) is already tied to a
language (%language).', array('%domain' => $form_state['values']['domain'],
'%language' => $duplicate->language)));
3520
  }
3531
  if (empty($form_state['values']['prefix']) &&
language_default('language') != $form_state['values']['langcode'] &&
empty($form_state['values']['domain'])) {
3540
    form_set_error('prefix', t('Only the default language can have both the
domain and prefix empty.'));
3550
  }
3561
  if (!empty($form_state['values']['prefix']) && $duplicate =
db_fetch_object(db_query("SELECT language FROM {languages} WHERE prefix =
'%s' AND language <> '%s'", $form_state['values']['prefix'],
$form_state['values']['langcode']))) {
3570
    form_set_error('prefix', t('The prefix (%prefix) is already tied to a
language (%language).', array('%prefix' => $form_state['values']['prefix'],
'%language' => $duplicate->language)));
3580
  }
3591
}
360
361
/**
362
 * Process the language editing form submission.
363
 */
36435
function locale_languages_edit_form_submit($form, &$form_state) {
3650
  db_query("UPDATE {languages} SET name = '%s', native = '%s', domain =
'%s', prefix = '%s', direction = %d WHERE language = '%s'",
$form_state['values']['name'], $form_state['values']['native'],
$form_state['values']['domain'], $form_state['values']['prefix'],
$form_state['values']['direction'], $form_state['values']['langcode']);
3660
  $default = language_default();
3670
  if ($default->language == $form_state['values']['langcode']) {
3680
    $properties = array('name', 'native', 'direction', 'enabled',
'plurals', 'formula', 'domain', 'prefix', 'weight');
3690
    foreach ($properties as $keyname) {
3700
      if (isset($form_state['values'][$keyname])) {
3710
        $default->$keyname = $form_state['values'][$keyname];
3720
      }
3730
    }
3740
    variable_set('language_default', $default);
3750
  }
3760
  $form_state['redirect'] = 'admin/settings/language';
3770
  return;
3780
}
379
/**
380
 * @} End of "locale-language-add-edit"
381
 */
382
383
/**
384
 * @defgroup locale-language-delete Language deletion functionality
385
 * @{
386
 */
387
388
/**
389
 * User interface for the language deletion confirmation screen.
390
 */
39135
function locale_languages_delete_form(&$form_state, $langcode) {
392
393
  // Do not allow deletion of English locale.
3943
  if ($langcode == 'en') {
3950
    drupal_set_message(t('The English language cannot be deleted.'));
3960
    drupal_goto('admin/settings/language');
3970
  }
398
3993
  if (language_default('language') == $langcode) {
4000
    drupal_set_message(t('The default language cannot be deleted.'));
4010
    drupal_goto('admin/settings/language');
4020
  }
403
404
  // For other languages, warn user that data loss is ahead.
4053
  $languages = language_list();
406
4073
  if (!isset($languages[$langcode])) {
4081
    drupal_not_found();
4091
  }
410
  else {
4112
    $form['langcode'] = array('#type' => 'value', '#value' => $langcode);
4122
    return confirm_form($form, t('Are you sure you want to delete the
language %name?', array('%name' => t($languages[$langcode]->name))),
'admin/settings/language', t('Deleting a language will remove all interface
translations associated with it, and posts in this language will be set to
be language neutral. This action cannot be undone.'), t('Delete'),
t('Cancel'));
413
  }
4141
}
415
416
/**
417
 * Process language deletion submissions.
418
 */
41935
function locale_languages_delete_form_submit($form, &$form_state) {
4201
  $languages = language_list();
4211
  if (isset($languages[$form_state['values']['langcode']])) {
422
    // Remove translations first.
4231
    db_query("DELETE FROM {locales_target} WHERE language = '%s'",
$form_state['values']['langcode']);
4241
    cache_clear_all('locale:' . $form_state['values']['langcode'],
'cache');
425
    // With no translations, this removes existing JavaScript translations
file.
4261
    _locale_rebuild_js($form_state['values']['langcode']);
427
    // Remove the language.
4281
    db_query("DELETE FROM {languages} WHERE language = '%s'",
$form_state['values']['langcode']);
4291
    db_query("UPDATE {node} SET language = '' WHERE language = '%s'",
$form_state['values']['langcode']);
4301
    $variables = array('%locale' =>
$languages[$form_state['values']['langcode']]->name);
4311
    drupal_set_message(t('The language %locale has been removed.',
$variables));
4321
    watchdog('locale', 'The language %locale has been removed.',
$variables);
4331
  }
434
435
  // Changing the language settings impacts the interface:
4361
  cache_clear_all('*', 'cache_page', TRUE);
437
4381
  $form_state['redirect'] = 'admin/settings/language';
4391
  return;
4400
}
441
/**
442
 * @} End of "locale-language-add-edit"
443
 */
444
445
/**
446
 * @defgroup locale-languages-negotiation Language negotiation options
screen
447
 * @{
448
 */
449
450
/**
451
 * Setting for language negotiation options
452
 */
45335
function locale_languages_configure_form() {
4540
  $form['language_negotiation'] = array(
4550
    '#title' => t('Language negotiation'),
4560
    '#type' => 'radios',
457
    '#options' => array(
4580
      LANGUAGE_NEGOTIATION_NONE => t('None.'),
4590
      LANGUAGE_NEGOTIATION_PATH_DEFAULT => t('Path prefix only.'),
4600
      LANGUAGE_NEGOTIATION_PATH => t('Path prefix with language
fallback.'),
4610
      LANGUAGE_NEGOTIATION_DOMAIN => t('Domain name only.')),
4620
    '#default_value' => variable_get('language_negotiation',
LANGUAGE_NEGOTIATION_NONE),
4630
    '#description' => t("Select the mechanism used to determine your site's
presentation language. <strong>Modifying this setting may break all
incoming URLs and should be used with caution in a production
environment.</strong>")
4640
  );
4650
  $form['submit'] = array(
4660
    '#type' => 'submit',
4670
    '#value' => t('Save settings')
4680
  );
4690
  return $form;
4700
}
471
472
/**
473
 * Submit function for language negotiation settings.
474
 */
47535
function locale_languages_configure_form_submit($form, &$form_state) {
4760
  variable_set('language_negotiation',
$form_state['values']['language_negotiation']);
4770
  drupal_set_message(t('Language negotiation configuration saved.'));
4780
  $form_state['redirect'] = 'admin/settings/language';
4790
  return;
4800
}
481
/**
482
 * @} End of "locale-languages-negotiation"
483
 */
484
485
/**
486
 * @defgroup locale-translate-overview Translation overview screen.
487
 * @{
488
 */
489
490
/**
491
 * Overview screen for translations.
492
 */
49335
function locale_translate_overview_screen() {
4940
  $languages = language_list('language', TRUE);
4950
  $groups = module_invoke_all('locale', 'groups');
496
497
  // Build headers with all groups in order.
4980
  $headers = array_merge(array(t('Language')), array_values($groups));
499
500
  // Collect summaries of all source strings in all groups.
5010
  $sums = db_query("SELECT COUNT(*) AS strings, textgroup FROM
{locales_source} GROUP BY textgroup");
5020
  $groupsums = array();
5030
  while ($group = db_fetch_object($sums)) {
5040
    $groupsums[$group->textgroup] = $group->strings;
5050
  }
506
507
  // Set up overview table with default values, ensuring common order for
values.
5080
  $rows = array();
5090
  foreach ($languages as $langcode => $language) {
5100
    $rows[$langcode] = array('name' => ($langcode == 'en' ? t('English
(built-in)') : t($language->name)));
5110
    foreach ($groups as $group => $name) {
5120
      $rows[$langcode][$group] = ($langcode == 'en' ? t('n/a') : '0/' .
(isset($groupsums[$group]) ? $groupsums[$group] : 0) . ' (0%)');
5130
    }
5140
  }
515
516
  // Languages with at least one record in the locale table.
5170
  $translations = db_query("SELECT COUNT(*) AS translation, t.language,
s.textgroup FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid
= t.lid GROUP BY textgroup, language");
5180
  while ($data = db_fetch_object($translations)) {
5190
    $ratio = (!empty($groupsums[$data->textgroup]) && $data->translation >
0) ? round(($data->translation/$groupsums[$data->textgroup])*100., 2) : 0;
5200
    $rows[$data->language][$data->textgroup] = $data->translation . '/' .
$groupsums[$data->textgroup] . " ($ratio%)";
5210
  }
522
5230
  return theme('table', $headers, $rows);
5240
}
525
/**
526
 * @} End of "locale-translate-overview"
527
 */
528
529
/**
530
 * @defgroup locale-translate-seek Translation search screen.
531
 * @{
532
 */
533
534
/**
535
 * String search screen.
536
 */
53735
function locale_translate_seek_screen() {
5388
  $output = _locale_translate_seek();
5398
  $output .= drupal_get_form('locale_translate_seek_form');
5408
  return $output;
5410
}
542
543
/**
544
 * User interface for the string search screen.
545
 */
54635
function locale_translate_seek_form() {
547
  // Get all languages, except English
5488
  $languages = locale_language_list('name', TRUE);
5498
  unset($languages['en']);
550
551
  // Present edit form preserving previous user settings
5528
  $query = _locale_translate_seek_query();
5538
  $form = array();
5548
  $form['search'] = array('#type' => 'fieldset',
5558
    '#title' => t('Search'),
556
  );
5578
  $form['search']['string'] = array('#type' => 'textfield',
5588
    '#title' => t('String contains'),
5598
    '#default_value' => @$query['string'],
5608
    '#description' => t('Leave blank to show all strings. The search is
case sensitive.'),
561
  );
5628
  $form['search']['language'] = array(
563
    // Change type of form widget if more the 5 options will
564
    // be present (2 of the options are added below).
5658
    '#type' => (count($languages) <= 3 ? 'radios' : 'select'),
5668
    '#title' => t('Language'),
5678
    '#default_value' => (!empty($query['language']) ? $query['language'] :
'all'),
5688
    '#options' => array_merge(array('all' => t('All languages'), 'en' =>
t('English (provided by Drupal)')), $languages),
569
  );
5708
  $form['search']['translation'] = array('#type' => 'radios',
5718
    '#title' => t('Search in'),
5728
    '#default_value' => (!empty($query['translation']) ?
$query['translation'] : 'all'),
5738
    '#options' => array('all' => t('Both translated and untranslated
strings'), 'translated' => t('Only translated strings'), 'untranslated' =>
t('Only untranslated strings')),
574
  );
5758
  $groups = module_invoke_all('locale', 'groups');
5768
  $form['search']['group'] = array('#type' => 'radios',
5778
    '#title' => t('Limit search to'),
5788
    '#default_value' => (!empty($query['group']) ? $query['group'] :
'all'),
5798
    '#options' => array_merge(array('all' => t('All text groups')),
$groups),
580
  );
581
5828
  $form['search']['submit'] = array('#type' => 'submit', '#value' =>
t('Search'));
5838
  $form['#redirect'] = FALSE;
584
5858
  return $form;
5860
}
587
/**
588
 * @} End of "locale-translate-seek"
589
 */
590
591
/**
592
 * @defgroup locale-translate-import Translation import screen.
593
 * @{
594
 */
595
596
/**
597
 * User interface for the translation import screen.
598
 */
59935
function locale_translate_import_form() {
600
  // Get all languages, except English
6010
  $names = locale_language_list('name', TRUE);
6020
  unset($names['en']);
603
6040
  if (!count($names)) {
6050
    $languages = _locale_prepare_predefined_list();
6060
    $default = array_shift(array_keys($languages));
6070
  }
608
  else {
609
    $languages = array(
6100
      t('Already added languages') => $names,
6110
      t('Languages not yet added') => _locale_prepare_predefined_list()
6120
    );
6130
    $default = array_shift(array_keys($names));
614
  }
615
6160
  $form = array();
6170
  $form['import'] = array('#type' => 'fieldset',
6180
    '#title' => t('Import translation'),
619
  );
6200
  $form['import']['file'] = array('#type' => 'file',
6210
    '#title' => t('Language file'),
6220
    '#size' => 50,
6230
    '#description' => t('A Gettext Portable Object (<em>.po</em>) file.'),
624
  );
6250
  $form['import']['langcode'] = array('#type' => 'select',
6260
    '#title' => t('Import into'),
6270
    '#options' => $languages,
6280
    '#default_value' => $default,
6290
    '#description' => t('Choose the language you want to add strings into.
If you choose a language which is not yet set up, it will be added.'),
630
  );
6310
  $form['import']['group'] = array('#type' => 'radios',
6320
    '#title' => t('Text group'),
6330
    '#default_value' => 'default',
6340
    '#options' => module_invoke_all('locale', 'groups'),
6350
    '#description' => t('Imported translations will be added to this text
group.'),
636
  );
6370
  $form['import']['mode'] = array('#type' => 'radios',
6380
    '#title' => t('Mode'),
6390
    '#default_value' => LOCALE_IMPORT_KEEP,
640
    '#options' => array(
6410
      LOCALE_IMPORT_OVERWRITE => t('Strings in the uploaded file replace
existing ones, new ones are added'),
6420
      LOCALE_IMPORT_KEEP => t('Existing strings are kept, only new strings
are added')
6430
    ),
644
  );
6450
  $form['import']['submit'] = array('#type' => 'submit', '#value' =>
t('Import'));
6460
  $form['#attributes']['enctype'] = 'multipart/form-data';
647
6480
  return $form;
6490
}
650
651
/**
652
 * Process the locale import form submission.
653
 */
65435
function locale_translate_import_form_submit($form, &$form_state) {
655
  // Ensure we have the file uploaded
6560
  if ($file = file_save_upload('file')) {
657
658
    // Add language, if not yet supported
6590
    $languages = language_list('language', TRUE);
6600
    $langcode = $form_state['values']['langcode'];
6610
    if (!isset($languages[$langcode])) {
6620
      $predefined = _locale_get_predefined_list();
6630
      locale_add_language($langcode);
6640
      drupal_set_message(t('The language %language has been created.',
array('%language' => t($predefined[$langcode][0]))));
6650
    }
666
667
    // Now import strings into the language
6680
    if ($ret = _locale_import_po($file, $langcode,
$form_state['values']['mode'], $form_state['values']['group']) == FALSE) {
6690
      $variables = array('%filename' => $file->filename);
6700
      drupal_set_message(t('The translation import of %filename failed.',
$variables), 'error');
6710
      watchdog('locale', 'The translation import of %filename failed.',
$variables, WATCHDOG_ERROR);
6720
    }
6730
  }
674
  else {
6750
    drupal_set_message(t('File to import not found.'), 'error');
6760
    return 'admin/build/translate/import';
677
  }
678
6790
  $form_state['redirect'] = 'admin/build/translate';
6800
  return;
6810
}
682
/**
683
 * @} End of "locale-translate-import"
684
 */
685
686
/**
687
 * @defgroup locale-translate-export Translation export screen.
688
 * @{
689
 */
690
691
/**
692
 * User interface for the translation export screen.
693
 */
69435
function locale_translate_export_screen() {
695
  // Get all languages, except English
6960
  $names = locale_language_list('name', TRUE);
6970
  unset($names['en']);
6980
  $output = '';
699
  // Offer translation export if any language is set up.
7000
  if (count($names)) {
7010
    $output = drupal_get_form('locale_translate_export_po_form', $names);
7020
  }
7030
  $output .= drupal_get_form('locale_translate_export_pot_form');
7040
  return $output;
7050
}
706
707
/**
708
 * Form to export PO files for the languages provided.
709
 *
710
 * @param $names
711
 *   An associate array with localized language names
712
 */
71335
function locale_translate_export_po_form(&$form_state, $names) {
7140
  $form['export'] = array('#type' => 'fieldset',
7150
    '#title' => t('Export translation'),
7160
    '#collapsible' => TRUE,
717
  );
7180
  $form['export']['langcode'] = array('#type' => 'select',
7190
    '#title' => t('Language name'),
7200
    '#options' => $names,
7210
    '#description' => t('Select the language to export in Gettext Portable
Object (<em>.po</em>) format.'),
722
  );
7230
  $form['export']['group'] = array('#type' => 'radios',
7240
    '#title' => t('Text group'),
7250
    '#default_value' => 'default',
7260
    '#options' => module_invoke_all('locale', 'groups'),
727
  );
7280
  $form['export']['submit'] = array('#type' => 'submit', '#value' =>
t('Export'));
7290
  return $form;
7300
}
731
732
/**
733
 * Translation template export form.
734
 */
73535
function locale_translate_export_pot_form() {
736
  // Complete template export of the strings
7370
  $form['export'] = array('#type' => 'fieldset',
7380
    '#title' => t('Export template'),
7390
    '#collapsible' => TRUE,
7400
    '#description' => t('Generate a Gettext Portable Object Template
(<em>.pot</em>) file with all strings from the Drupal locale database.'),
741
  );
7420
  $form['export']['group'] = array('#type' => 'radios',
7430
    '#title' => t('Text group'),
7440
    '#default_value' => 'default',
7450
    '#options' => module_invoke_all('locale', 'groups'),
746
  );
7470
  $form['export']['submit'] = array('#type' => 'submit', '#value' =>
t('Export'));
748
  // Reuse PO export submission callback.
7490
  $form['#submit'][] = 'locale_translate_export_po_form_submit';
7500
  $form['#validate'][] = 'locale_translate_export_po_form_validate';
7510
  return $form;
7520
}
753
754
/**
755
 * Process a translation (or template) export form submission.
756
 */
75735
function locale_translate_export_po_form_submit($form, &$form_state) {
758
  // If template is required, language code is not given.
7590
  $language = NULL;
7600
  if (isset($form_state['values']['langcode'])) {
7610
    $languages = language_list();
7620
    $language = $languages[$form_state['values']['langcode']];
7630
  }
7640
  _locale_export_po($language, _locale_export_po_generate($language,
_locale_export_get_strings($language, $form_state['values']['group'])));
7650
}
766
/**
767
 * @} End of "locale-translate-export"
768
 */
769
770
/**
771
 * @defgroup locale-translate-edit Translation text editing
772
 * @{
773
 */
774
775
/**
776
 * User interface for string editing.
777
 */
77835
function locale_translate_edit_form(&$form_state, $lid) {
779
  // Fetch source string, if possible.
7802
  $source = db_fetch_object(db_query('SELECT source, textgroup, location
FROM {locales_source} WHERE lid = %d', $lid));
7812
  if (!$source) {
7820
    drupal_set_message(t('String not found.'), 'error');
7830
    drupal_goto('admin/build/translate/search');
7840
  }
785
786
  // Add original text to the top and some values for form altering.
787
  $form = array(
788
    'original' => array(
7892
      '#type'  => 'item',
7902
      '#title' => t('Original text'),
7912
      '#markup' => check_plain(wordwrap($source->source, 0)),
7922
    ),
793
    'lid' => array(
7942
      '#type'  => 'value',
795
      '#value' => $lid
7962
    ),
797
    'textgroup' => array(
7982
      '#type'  => 'value',
7992
      '#value' => $source->textgroup,
8002
    ),
801
    'location' => array(
8022
      '#type'  => 'value',
8032
      '#value' => $source->location
8042
    ),
8052
  );
806
807
  // Include default form controls with empty values for all languages.
808
  // This ensures that the languages are always in the same order in forms.
8092
  $languages = language_list();
8102
  $default = language_default();
811
  // We don't need the default language value, that value is in $source.
8122
  $omit = $source->textgroup == 'default' ? 'en' : $default->language;
8132
  unset($languages[($omit)]);
8142
  $form['translations'] = array('#tree' => TRUE);
815
  // Approximate the number of rows to use in the default textarea.
8162
  $rows = min(ceil(str_word_count($source->source) / 12), 10);
8172
  foreach ($languages as $langcode => $language) {
8182
    $form['translations'][$langcode] = array(
8192
      '#type' => 'textarea',
8202
      '#title' => t($language->name),
8212
      '#rows' => $rows,
8222
      '#default_value' => '',
823
    );
8242
  }
825
826
  // Fetch translations and fill in default values in the form.
8272
  $result = db_query("SELECT DISTINCT translation, language FROM
{locales_target} WHERE lid = %d AND language <> '%s'", $lid, $omit);
8282
  while ($translation = db_fetch_object($result)) {
8290
    $form['translations'][$translation->language]['#default_value'] =
$translation->translation;
8300
  }
831
8322
  $form['submit'] = array('#type' => 'submit', '#value' => t('Save
translations'));
8332
  return $form;
8340
}
835
836
/**
837
 * Process string editing form submissions.
838
 * Saves all translations of one string submitted from a form.
839
 */
84035
function locale_translate_edit_form_submit($form, &$form_state) {
8411
  $lid = $form_state['values']['lid'];
8421
  foreach ($form_state['values']['translations'] as $key => $value) {
8431
    $translation = db_result(db_query("SELECT translation FROM
{locales_target} WHERE lid = %d AND language = '%s'", $lid, $key));
8441
    if (!empty($value)) {
845
      // Only update or insert if we have a value to use.
8461
      if (!empty($translation)) {
8470
        db_query("UPDATE {locales_target} SET translation = '%s' WHERE lid
= %d AND language = '%s'", $value, $lid, $key);
8480
      }
849
      else {
8501
        db_query("INSERT INTO {locales_target} (lid, translation, language)
VALUES (%d, '%s', '%s')", $lid, $value, $key);
851
      }
8521
    }
8530
    elseif (!empty($translation)) {
854
      // Empty translation entered: remove existing entry from database.
8550
      db_query("DELETE FROM {locales_target} WHERE lid = %d AND language =
'%s'", $lid, $key);
8560
    }
857
858
    // Force JavaScript translation file recreation for this language.
8591
    _locale_invalidate_js($key);
8601
  }
861
8621
  drupal_set_message(t('The string has been saved.'));
863
864
  // Clear locale cache.
8651
  _locale_invalidate_js();
8661
  cache_clear_all('locale:', 'cache', TRUE);
867
8681
  $form_state['redirect'] = 'admin/build/translate/search';
8691
  return;
8700
}
871
/**
872
 * @} End of "locale-translate-edit"
873
 */
874
875
/**
876
 * @defgroup locale-translate-delete Translation delete interface.
877
 * @{
878
 */
879
880
/**
881
 * String deletion confirmation page.
882
 */
88335
function locale_translate_delete_page($lid) {
8842
  if ($source = db_fetch_object(db_query('SELECT * FROM {locales_source}
WHERE lid = %d', $lid))) {
8852
    return drupal_get_form('locale_translate_delete_form', $source);
8860
  }
887
  else {
8880
    return drupal_not_found();
889
  }
8900
}
891
892
/**
893
 * User interface for the string deletion confirmation screen.
894
 */
89535
function locale_translate_delete_form(&$form_state, $source) {
8962
  $form['lid'] = array('#type' => 'value', '#value' => $source->lid);
8972
  return confirm_form($form, t('Are you sure you want to delete the string
"%source"?', array('%source' => $source->source)),
'admin/build/translate/search', t('Deleting the string will remove all
translations of this string in all languages. This action cannot be
undone.'), t('Delete'), t('Cancel'));
8980
}
899
900
/**
901
 * Process string deletion submissions.
902
 */
90335
function locale_translate_delete_form_submit($form, &$form_state) {
9041
  db_query('DELETE FROM {locales_source} WHERE lid = %d',
$form_state['values']['lid']);
9051
  db_query('DELETE FROM {locales_target} WHERE lid = %d',
$form_state['values']['lid']);
906
  // Force JavaScript translation file recreation for all languages.
9071
  _locale_invalidate_js();
9081
  cache_clear_all('locale:', 'cache', TRUE);
9091
  drupal_set_message(t('The string has been removed.'));
9101
  $form_state['redirect'] = 'admin/build/translate/search';
9111
}
912
/**
913
 * @} End of "locale-translate-delete"
914
 */
915
916
/**
917
 * @defgroup locale-api-add Language addition API.
918
 * @{
919
 */
920
921
/**
922
 * API function to add a language.
923
 *
924
 * @param $langcode
925
 *   Language code.
926
 * @param $name
927
 *   English name of the language
928
 * @param $native
929
 *   Native name of the language
930
 * @param $direction
931
 *   LANGUAGE_LTR or LANGUAGE_RTL
932
 * @param $domain
933
 *   Optional custom domain name with protocol, without
934
 *   trailing slash (eg. http://de.example.com).
935
 * @param $prefix
936
 *   Optional path prefix for the language. Defaults to the
937
 *   language code if omitted.
938
 * @param $enabled
939
 *   Optionally TRUE to enable the language when created or FALSE to
disable.
940
 * @param $default
941
 *   Optionally set this language to be the default.
942
 */
94335
function locale_add_language($langcode, $name = NULL, $native = NULL,
$direction = LANGUAGE_LTR, $domain = '', $prefix = '', $enabled = TRUE,
$default = FALSE) {
944
  // Default prefix on language code.
9453
  if (empty($prefix)) {
9462
    $prefix = $langcode;
9472
  }
948
949
  // If name was not set, we add a predefined language.
9503
  if (!isset($name)) {
9512
    $predefined = _locale_get_predefined_list();
9522
    $name = $predefined[$langcode][0];
9532
    $native = isset($predefined[$langcode][1]) ? $predefined[$langcode][1]
: $predefined[$langcode][0];
9542
    $direction = isset($predefined[$langcode][2]) ?
$predefined[$langcode][2] : LANGUAGE_LTR;
9552
  }
956
9573
  db_query("INSERT INTO {languages} (language, name, native, direction,
domain, prefix, enabled) VALUES ('%s', '%s', '%s', %d, '%s', '%s', %d)",
$langcode, $name, $native, $direction, $domain, $prefix, $enabled);
958
959
  // Only set it as default if enabled.
9603
  if ($enabled && $default) {
9610
    variable_set('language_default', (object) array('language' =>
$langcode, 'name' => $name, 'native' => $native, 'direction' => $direction,
'enabled' => (int) $enabled, 'plurals' => 0, 'formula' => '', 'domain' =>
'', 'prefix' => $prefix, 'weight' => 0, 'javascript' => ''));
9620
  }
963
9643
  if ($enabled) {
965
    // Increment enabled language count if we are adding an enabled
language.
9663
    variable_set('language_count', variable_get('language_count', 1) + 1);
9673
  }
968
969
  // Force JavaScript translation file creation for the newly added
language.
9703
  _locale_invalidate_js($langcode);
971
9723
  watchdog('locale', 'The %language language (%code) has been created.',
array('%language' => $name, '%code' => $langcode));
9733
}
974
/**
975
 * @} End of "locale-api-add"
976
 */
977
978
/**
979
 * @defgroup locale-api-import Translation import API.
980
 * @{
981
 */
982
983
/**
984
 * Parses Gettext Portable Object file information and inserts into
database
985
 *
986
 * @param $file
987
 *   Drupal file object corresponding to the PO file to import
988
 * @param $langcode
989
 *   Language code
990
 * @param $mode
991
 *   Should existing translations be replaced LOCALE_IMPORT_KEEP or
LOCALE_IMPORT_OVERWRITE
992
 * @param $group
993
 *   Text group to import PO file into (eg. 'default' for interface
translations)
994
 */
99535
function _locale_import_po($file, $langcode, $mode, $group = NULL) {
996
  // If not in 'safe mode', increase the maximum execution time.
9970
  if (!ini_get('safe_mode')) {
9980
    set_time_limit(240);
9990
  }
1000
1001
  // Check if we have the language already in the database.
10020
  if (!db_fetch_object(db_query("SELECT language FROM {languages} WHERE
language = '%s'", $langcode))) {
10030
    drupal_set_message(t('The language selected for import is not
supported.'), 'error');
10040
    return FALSE;
10050
  }
1006
1007
  // Get strings from file (returns on failure after a partial import, or
on success)
10080
  $status = _locale_import_read_po('db-store', $file, $mode, $langcode,
$group);
10090
  if ($status === FALSE) {
1010
    // Error messages are set in _locale_import_read_po().
10110
    return FALSE;
10120
  }
1013
1014
  // Get status information on import process.
10150
  list($headerdone, $additions, $updates, $deletes) =
_locale_import_one_string('db-report');
1016
10170
  if (!$headerdone) {
10180
    drupal_set_message(t('The translation file %filename appears to have a
missing or malformed header.', array('%filename' => $file->filename)),
'error');
10190
  }
1020
1021
  // Clear cache and force refresh of JavaScript translations.
10220
  _locale_invalidate_js($langcode);
10230
  cache_clear_all('locale:', 'cache', TRUE);
1024
1025
  // Rebuild the menu, strings may have changed.
10260
  menu_rebuild();
1027
10280
  drupal_set_message(t('The translation was successfully imported. There
are %number newly created translated strings, %update strings were updated
and %delete strings were removed.', array('%number' => $additions,
'%update' => $updates, '%delete' => $deletes)));
10290
  watchdog('locale', 'Imported %file into %locale: %number new strings
added, %update updated and %delete removed.', array('%file' =>
$file->filename, '%locale' => $langcode, '%number' => $additions, '%update'
=> $updates, '%delete' => $deletes));
10300
  return TRUE;
10310
}
1032
1033
/**
1034
 * Parses Gettext Portable Object file into an array
1035
 *
1036
 * @param $op
1037
 *   Storage operation type: db-store or mem-store
1038
 * @param $file
1039
 *   Drupal file object corresponding to the PO file to import
1040
 * @param $mode
1041
 *   Should existing translations be replaced LOCALE_IMPORT_KEEP or
LOCALE_IMPORT_OVERWRITE
1042
 * @param $lang
1043
 *   Language code
1044
 * @param $group
1045
 *   Text group to import PO file into (eg. 'default' for interface
translations)
1046
 */
104735
function _locale_import_read_po($op, $file, $mode = NULL, $lang = NULL,
$group = 'default') {
1048
10490
  $fd = fopen(DRUPAL_ROOT . '/' . $file->filepath, "rb"); // File will get
closed by PHP on return
10500
  if (!$fd) {
10510
    _locale_import_message('The translation import failed, because the file
%filename could not be read.', $file);
10520
    return FALSE;
10530
  }
1054
10550
  $context = "COMMENT"; // Parser context: COMMENT, MSGID, MSGID_PLURAL,
MSGSTR and MSGSTR_ARR
10560
  $current = array();   // Current entry being read
10570
  $plural = 0;          // Current plural form
10580
  $lineno = 0;          // Current line
1059
10600
  while (!feof($fd)) {
10610
    $line = fgets($fd, 10*1024); // A line should not be this long
10620
    if ($lineno == 0) {
1063
      // The first line might come with a UTF-8 BOM, which should be
removed.
10640
      $line = str_replace("\xEF\xBB\xBF", '', $line);
10650
    }
10660
    $lineno++;
10670
    $line = trim(strtr($line, array("\\\n" => "")));
1068
10690
    if (!strncmp("#", $line, 1)) { // A comment
10700
      if ($context == "COMMENT") { // Already in comment context: add
10710
        $current["#"][] = substr($line, 1);
10720
      }
10730
      elseif (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) { //
End current entry, start a new one
10740
        _locale_import_one_string($op, $current, $mode, $lang, $file,
$group);
10750
        $current = array();
10760
        $current["#"][] = substr($line, 1);
10770
        $context = "COMMENT";
10780
      }
1079
      else { // Parse error
10800
        _locale_import_message('The translation file %filename contains an
error: "msgstr" was expected but not found on line %line.', $file,
$lineno);
10810
        return FALSE;
1082
      }
10830
    }
10840
    elseif (!strncmp("msgid_plural", $line, 12)) {
10850
      if ($context != "MSGID") { // Must be plural form for current entry
10860
        _locale_import_message('The translation file %filename contains an
error: "msgid_plural" was expected but not found on line %line.', $file,
$lineno);
10870
        return FALSE;
10880
      }
10890
      $line = trim(substr($line, 12));
10900
      $quoted = _locale_import_parse_quoted($line);
10910
      if ($quoted === FALSE) {
10920
        _locale_import_message('The translation file %filename contains a
syntax error on line %line.', $file, $lineno);
10930
        return FALSE;
10940
      }
10950
      $current["msgid"] = $current["msgid"] . "\0" . $quoted;
10960
      $context = "MSGID_PLURAL";
10970
    }
10980
    elseif (!strncmp("msgid", $line, 5)) {
10990
      if ($context == "MSGSTR") {   // End current entry, start a new one
11000
        _locale_import_one_string($op, $current, $mode, $lang, $file,
$group);
11010
        $current = array();
11020
      }
11030
      elseif ($context == "MSGID") { // Already in this context? Parse
error
11040
        _locale_import_message('The translation file %filename contains an
error: "msgid" is unexpected on line %line.', $file, $lineno);
11050
        return FALSE;
11060
      }
11070
      $line = trim(substr($line, 5));
11080
      $quoted = _locale_import_parse_quoted($line);
11090
      if ($quoted === FALSE) {
11100
        _locale_import_message('The translation file %filename contains a
syntax error on line %line.', $file, $lineno);
11110
        return FALSE;
11120
      }
11130
      $current["msgid"] = $quoted;
11140
      $context = "MSGID";
11150
    }
11160
    elseif (!strncmp("msgstr[", $line, 7)) {
11170
      if (($context != "MSGID") && ($context != "MSGID_PLURAL") &&
($context != "MSGSTR_ARR")) { // Must come after msgid, msgid_plural, or
msgstr[]
11180
        _locale_import_message('The translation file %filename contains an
error: "msgstr[]" is unexpected on line %line.', $file, $lineno);
11190
        return FALSE;
11200
      }
11210
      if (strpos($line, "]") === FALSE) {
11220
        _locale_import_message('The translation file %filename contains a
syntax error on line %line.', $file, $lineno);
11230
        return FALSE;
11240
      }
11250
      $frombracket = strstr($line, "[");
11260
      $plural = substr($frombracket, 1, strpos($frombracket, "]") - 1);
11270
      $line = trim(strstr($line, " "));
11280
      $quoted = _locale_import_parse_quoted($line);
11290
      if ($quoted === FALSE) {
11300
        _locale_import_message('The translation file %filename contains a
syntax error on line %line.', $file, $lineno);
11310
        return FALSE;
11320
      }
11330
      $current["msgstr"][$plural] = $quoted;
11340
      $context = "MSGSTR_ARR";
11350
    }
11360
    elseif (!strncmp("msgstr", $line, 6)) {
11370
      if ($context != "MSGID") {   // Should come just after a msgid block
11380
        _locale_import_message('The translation file %filename contains an
error: "msgstr" is unexpected on line %line.', $file, $lineno);
11390
        return FALSE;
11400
      }
11410
      $line = trim(substr($line, 6));
11420
      $quoted = _locale_import_parse_quoted($line);
11430
      if ($quoted === FALSE) {
11440
        _locale_import_message('The translation file %filename contains a
syntax error on line %line.', $file, $lineno);
11450
        return FALSE;
11460
      }
11470
      $current["msgstr"] = $quoted;
11480
      $context = "MSGSTR";
11490
    }
11500
    elseif ($line != "") {
11510
      $quoted = _locale_import_parse_quoted($line);
11520
      if ($quoted === FALSE) {
11530
        _locale_import_message('The translation file %filename contains a
syntax error on line %line.', $file, $lineno);
11540
        return FALSE;
11550
      }
11560
      if (($context == "MSGID") || ($context == "MSGID_PLURAL")) {
11570
        $current["msgid"] .= $quoted;
11580
      }
11590
      elseif ($context == "MSGSTR") {
11600
        $current["msgstr"] .= $quoted;
11610
      }
11620
      elseif ($context == "MSGSTR_ARR") {
11630
        $current["msgstr"][$plural] .= $quoted;
11640
      }
1165
      else {
11660
        _locale_import_message('The translation file %filename contains an
error: there is an unexpected string on line %line.', $file, $lineno);
11670
        return FALSE;
1168
      }
11690
    }
11700
  }
1171
1172
  // End of PO file, flush last entry
11730
  if (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) {
11740
    _locale_import_one_string($op, $current, $mode, $lang, $file, $group);
11750
  }
11760
  elseif ($context != "COMMENT") {
11770
    _locale_import_message('The translation file %filename ended
unexpectedly at line %line.', $file, $lineno);
11780
    return FALSE;
11790
  }
1180
11810
}
1182
1183
/**
1184
 * Sets an error message occurred during locale file parsing.
1185
 *
1186
 * @param $message
1187
 *   The message to be translated
1188
 * @param $file
1189
 *   Drupal file object corresponding to the PO file to import
1190
 * @param $lineno
1191
 *   An optional line number argument
1192
 */
119335
function _locale_import_message($message, $file, $lineno = NULL) {
11940
  $vars = array('%filename' => $file->filename);
11950
  if (isset($lineno)) {
11960
    $vars['%line'] = $lineno;
11970
  }
11980
  $t = get_t();
11990
  drupal_set_message($t($message, $vars), 'error');
12000
}
1201
1202
/**
1203
 * Imports a string into the database
1204
 *
1205
 * @param $op
1206
 *   Operation to perform: 'db-store', 'db-report', 'mem-store' or
'mem-report'
1207
 * @param $value
1208
 *   Details of the string stored
1209
 * @param $mode
1210
 *   Should existing translations be replaced LOCALE_IMPORT_KEEP or
LOCALE_IMPORT_OVERWRITE
1211
 * @param $lang
1212
 *   Language to store the string in
1213
 * @param $file
1214
 *   Object representation of file being imported, only required when op is
'db-store'
1215
 * @param $group
1216
 *   Text group to import PO file into (eg. 'default' for interface
translations)
1217
 */
121835
function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang
= NULL, $file = NULL, $group = 'default') {
12190
  static $report = array(0, 0, 0);
12200
  static $headerdone = FALSE;
12210
  static $strings = array();
1222
1223
  switch ($op) {
1224
    // Return stored strings
12250
    case 'mem-report':
12260
      return $strings;
1227
1228
    // Store string in memory (only supports single strings)
12290
    case 'mem-store':
12300
      $strings[$value['msgid']] = $value['msgstr'];
12310
      return;
1232
1233
    // Called at end of import to inform the user
12340
    case 'db-report':
12350
      return array($headerdone, $report[0], $report[1], $report[2]);
1236
1237
    // Store the string we got in the database.
12380
    case 'db-store':
1239
      // We got header information.
12400
      if ($value['msgid'] == '') {
12410
        $header = _locale_import_parse_header($value['msgstr']);
1242
1243
        // Get the plural formula and update in database.
12440
        if (isset($header["Plural-Forms"]) && $p =
_locale_import_parse_plural_forms($header["Plural-Forms"],
$file->filename)) {
12450
          list($nplurals, $plural) = $p;
12460
          db_query("UPDATE {languages} SET plurals = %d, formula = '%s'
WHERE language = '%s'", $nplurals, $plural, $lang);
12470
        }
1248
        else {
12490
          db_query("UPDATE {languages} SET plurals = %d, formula = '%s'
WHERE language = '%s'", 0, '', $lang);
1250
        }
12510
        $headerdone = TRUE;
12520
      }
1253
1254
      else {
1255
        // Some real string to import.
12560
        $comments = _locale_import_shorten_comments(empty($value['#']) ?
array() : $value['#']);
1257
12580
        if (strpos($value['msgid'], "\0")) {
1259
          // This string has plural versions.
12600
          $english = explode("\0", $value['msgid'], 2);
12610
          $entries = array_keys($value['msgstr']);
12620
          for ($i = 3; $i <= count($entries); $i++) {
12630
            $english[] = $english[1];
12640
          }
12650
          $translation = array_map('_locale_import_append_plural',
$value['msgstr'], $entries);
12660
          $english = array_map('_locale_import_append_plural', $english,
$entries);
12670
          foreach ($translation as $key => $trans) {
12680
            if ($key == 0) {
12690
              $plid = 0;
12700
            }
12710
            $plid = _locale_import_one_string_db($report, $lang,
$english[$key], $trans, $group, $comments, $mode, $plid, $key);
12720
          }
12730
        }
1274
1275
        else {
1276
          // A simple string to import.
12770
          $english = $value['msgid'];
12780
          $translation = $value['msgstr'];
12790
          _locale_import_one_string_db($report, $lang, $english,
$translation, $group, $comments, $mode);
1280
        }
1281
      }
12820
  } // end of db-store operation
12830
}
1284
1285
/**
1286
 * Import one string into the database.
1287
 *
1288
 * @param $report
1289
 *   Report array summarizing the number of changes done in the form:
1290
 *   array(inserts, updates, deletes).
1291
 * @param $langcode
1292
 *   Language code to import string into.
1293
 * @param $source
1294
 *   Source string.
1295
 * @param $translation
1296
 *   Translation to language specified in $langcode.
1297
 * @param $textgroup
1298
 *   Name of textgroup to store translation in.
1299
 * @param $location
1300
 *   Location value to save with source string.
1301
 * @param $mode
1302
 *   Import mode to use, LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE.
1303
 * @param $plid
1304
 *   Optional plural ID to use.
1305
 * @param $plural
1306
 *   Optional plural value to use.
1307
 * @return
1308
 *   The string ID of the existing string modified or the new string added.
1309
 */
131035
function _locale_import_one_string_db(&$report, $langcode, $source,
$translation, $textgroup, $location, $mode, $plid = NULL, $plural = NULL) {
13110
  $lid = db_result(db_query("SELECT lid FROM {locales_source} WHERE source
= '%s' AND textgroup = '%s'", $source, $textgroup));
1312
13130
  if (!empty($translation)) {
13140
    if ($lid) {
1315
      // We have this source string saved already.
13160
      db_query("UPDATE {locales_source} SET location = '%s' WHERE lid =
%d", $location, $lid);
13170
      $exists = (bool) db_result(db_query("SELECT lid FROM {locales_target}
WHERE lid = %d AND language = '%s'", $lid, $langcode));
13180
      if (!$exists) {
1319
        // No translation in this language.
13200
        db_query("INSERT INTO {locales_target} (lid, language, translation,
plid, plural) VALUES (%d, '%s', '%s', %d, %d)", $lid, $langcode,
$translation, $plid, $plural);
13210
        $report[0]++;
13220
      }
13230
      elseif ($mode == LOCALE_IMPORT_OVERWRITE) {
1324
        // Translation exists, only overwrite if instructed.
13250
        db_query("UPDATE {locales_target} SET translation = '%s', plid =
%d, plural = %d WHERE language = '%s' AND lid = %d", $translation, $plid,
$plural, $langcode, $lid);
13260
        $report[1]++;
13270
      }
13280
    }
1329
    else {
1330
      // No such source string in the database yet.
13310
      db_query("INSERT INTO {locales_source} (location, source, textgroup)
VALUES ('%s', '%s', '%s')", $location, $source, $textgroup);
13320
      $lid = db_result(db_query("SELECT lid FROM {locales_source} WHERE
source = '%s' AND textgroup = '%s'", $source, $textgroup));
13330
      db_query("INSERT INTO {locales_target} (lid, language, translation,
plid, plural) VALUES (%d, '%s', '%s', %d, %d)", $lid, $langcode,
$translation, $plid, $plural);
13340
      $report[0]++;
1335
    }
13360
  }
13370
  elseif ($mode == LOCALE_IMPORT_OVERWRITE) {
1338
    // Empty translation, remove existing if instructed.
13390
    db_query("DELETE FROM {locales_target} WHERE language = '%s' AND lid =
%d AND plid = %d AND plural = %d", $translation, $langcode, $lid, $plid,
$plural);
13400
    $report[2]++;
13410
  }
1342
13430
  return $lid;
13440
}
1345
1346
/**
1347
 * Parses a Gettext Portable Object file header
1348
 *
1349
 * @param $header
1350
 *   A string containing the complete header
1351
 * @return
1352
 *   An associative array of key-value pairs
1353
 */
135435
function _locale_import_parse_header($header) {
13550
  $header_parsed = array();
13560
  $lines = array_map('trim', explode("\n", $header));
13570
  foreach ($lines as $line) {
13580
    if ($line) {
13590
      list($tag, $contents) = explode(":", $line, 2);
13600
      $header_parsed[trim($tag)] = trim($contents);
13610
    }
13620
  }
13630
  return $header_parsed;
13640
}
1365
1366
/**
1367
 * Parses a Plural-Forms entry from a Gettext Portable Object file header
1368
 *
1369
 * @param $pluralforms
1370
 *   A string containing the Plural-Forms entry
1371
 * @param $filename
1372
 *   A string containing the filename
1373
 * @return
1374
 *   An array containing the number of plurals and a
1375
 *   formula in PHP for computing the plural form
1376
 */
137735
function _locale_import_parse_plural_forms($pluralforms, $filename) {
1378
  // First, delete all whitespace
13790
  $pluralforms = strtr($pluralforms, array(" " => "", "\t" => ""));
1380
1381
  // Select the parts that define nplurals and plural
13820
  $nplurals = strstr($pluralforms, "nplurals=");
13830
  if (strpos($nplurals, ";")) {
13840
    $nplurals = substr($nplurals, 9, strpos($nplurals, ";") - 9);
13850
  }
1386
  else {
13870
    return FALSE;
1388
  }
13890
  $plural = strstr($pluralforms, "plural=");
13900
  if (strpos($plural, ";")) {
13910
    $plural = substr($plural, 7, strpos($plural, ";") - 7);
13920
  }
1393
  else {
13940
    return FALSE;
1395
  }
1396
1397
  // Get PHP version of the plural formula
13980
  $plural = _locale_import_parse_arithmetic($plural);
1399
14000
  if ($plural !== FALSE) {
14010
    return array($nplurals, $plural);
14020
  }
1403
  else {
14040
    drupal_set_message(t('The translation file %filename contains an error:
the plural formula could not be parsed.', array('%filename' => $filename)),
'error');
14050
    return FALSE;
1406
  }
14070
}
1408
1409
/**
1410
 * Parses and sanitizes an arithmetic formula into a PHP expression
1411
 *
1412
 * While parsing, we ensure, that the operators have the right
1413
 * precedence and associativity.
1414
 *
1415
 * @param $string
1416
 *   A string containing the arithmetic formula
1417
 * @return
1418
 *   The PHP version of the formula
1419
 */
142035
function _locale_import_parse_arithmetic($string) {
1421
  // Operator precedence table
14220
  $prec = array("(" => -1, ")" => -1, "?" => 1, ":" => 1, "||" => 3, "&&"
=> 4, "==" => 5, "!=" => 5, "<" => 6, ">" => 6, "<=" => 6, ">=" => 6, "+"
=> 7, "-" => 7, "*" => 8, "/" => 8, "%" => 8);
1423
  // Right associativity
14240
  $rasc = array("?" => 1, ":" => 1);
1425
14260
  $tokens = _locale_import_tokenize_formula($string);
1427
1428
  // Parse by converting into infix notation then back into postfix
14290
  $opstk = array();
14300
  $elstk = array();
1431
14320
  foreach ($tokens as $token) {
14330
    $ctok = $token;
1434
1435
    // Numbers and the $n variable are simply pushed into $elarr
14360
    if (is_numeric($token)) {
14370
      $elstk[] = $ctok;
14380
    }
14390
    elseif ($ctok == "n") {
14400
      $elstk[] = '$n';
14410
    }
14420
    elseif ($ctok == "(") {
14430
      $opstk[] = $ctok;
14440
    }
14450
    elseif ($ctok == ")") {
14460
      $topop = array_pop($opstk);
14470
      while (isset($topop) && ($topop != "(")) {
14480
        $elstk[] = $topop;
14490
        $topop = array_pop($opstk);
14500
      }
14510
    }
14520
    elseif (!empty($prec[$ctok])) {
1453
      // If it's an operator, then pop from $oparr into $elarr until the
1454
      // precedence in $oparr is less than current, then push into $oparr
14550
      $topop = array_pop($opstk);
14560
      while (isset($topop) && ($prec[$topop] >= $prec[$ctok]) &&
!(($prec[$topop] == $prec[$ctok]) && !empty($rasc[$topop]) &&
!empty($rasc[$ctok]))) {
14570
        $elstk[] = $topop;
14580
        $topop = array_pop($opstk);
14590
      }
14600
      if ($topop) {
14610
        $opstk[] = $topop;   // Return element to top
14620
      }
14630
      $opstk[] = $ctok;      // Parentheses are not needed
14640
    }
1465
    else {
14660
      return FALSE;
1467
    }
14680
  }
1469
1470
  // Flush operator stack
14710
  $topop = array_pop($opstk);
14720
  while ($topop != NULL) {
14730
    $elstk[] = $topop;
14740
    $topop = array_pop($opstk);
14750
  }
1476
1477
  // Now extract formula from stack
14780
  $prevsize = count($elstk) + 1;
14790
  while (count($elstk) < $prevsize) {
14800
    $prevsize = count($elstk);
14810
    for ($i = 2; $i < count($elstk); $i++) {
14820
      $op = $elstk[$i];
14830
      if (!empty($prec[$op])) {
14840
        $f = "";
14850
        if ($op == ":") {
14860
          $f = $elstk[$i - 2] . "):" . $elstk[$i - 1] . ")";
14870
        }
14880
        elseif ($op == "?") {
14890
          $f = "(" . $elstk[$i - 2] . "?(" . $elstk[$i - 1];
14900
        }
1491
        else {
14920
          $f = "(" . $elstk[$i - 2] . $op . $elstk[$i - 1] . ")";
1493
        }
14940
        array_splice($elstk, $i - 2, 3, $f);
14950
        break;
14960
      }
14970
    }
14980
  }
1499
1500
  // If only one element is left, the number of operators is appropriate
15010
  if (count($elstk) == 1) {
15020
    return $elstk[0];
15030
  }
1504
  else {
15050
    return FALSE;
1506
  }
15070
}
1508
1509
/**
1510
 * Backward compatible implementation of token_get_all() for formula
parsing
1511
 *
1512
 * @param $string
1513
 *   A string containing the arithmetic formula
1514
 * @return
1515
 *   The PHP version of the formula
1516
 */
151735
function _locale_import_tokenize_formula($formula) {
15180
  $formula = str_replace(" ", "", $formula);
15190
  $tokens = array();
15200
  for ($i = 0; $i < strlen($formula); $i++) {
15210
    if (is_numeric($formula[$i])) {
15220
      $num = $formula[$i];
15230
      $j = $i + 1;
15240
      while ($j < strlen($formula) && is_numeric($formula[$j])) {
15250
        $num .= $formula[$j];
15260
        $j++;
15270
      }
15280
      $i = $j - 1;
15290
      $tokens[] = $num;
15300
    }
15310
    elseif ($pos = strpos(" =<>!&|", $formula[$i])) { // We won't have a
space
15320
      $next = $formula[$i + 1];
1533
      switch ($pos) {
15340
        case 1:
15350
        case 2:
15360
        case 3:
15370
        case 4:
15380
          if ($next == '=') {
15390
            $tokens[] = $formula[$i] . '=';
15400
            $i++;
15410
          }
1542
          else {
15430
            $tokens[] = $formula[$i];
1544
          }
15450
          break;
15460
        case 5:
15470
          if ($next == '&') {
15480
            $tokens[] = '&&';
15490
            $i++;
15500
          }
1551
          else {
15520
            $tokens[] = $formula[$i];
1553
          }
15540
          break;
15550
        case 6:
15560
          if ($next == '|') {
15570
            $tokens[] = '||';
15580
            $i++;
15590
          }
1560
          else {
15610
            $tokens[] = $formula[$i];
1562
          }
15630
          break;
15640
      }
15650
    }
1566
    else {
15670
      $tokens[] = $formula[$i];
1568
    }
15690
  }
15700
  return $tokens;
15710
}
1572
1573
/**
1574
 * Modify a string to contain proper count indices
1575
 *
1576
 * This is a callback function used via array_map()
1577
 *
1578
 * @param $entry
1579
 *   An array element
1580
 * @param $key
1581
 *   Index of the array element
1582
 */
158335
function _locale_import_append_plural($entry, $key) {
1584
  // No modifications for 0, 1
15850
  if ($key == 0 || $key == 1) {
15860
    return $entry;
15870
  }
1588
1589
  // First remove any possibly false indices, then add new ones
15900
  $entry = preg_replace('/(@count)\[[0-9]\]/', '\\1', $entry);
15910
  return preg_replace('/(@count)/', "\\1[$key]", $entry);
15920
}
1593
1594
/**
1595
 * Generate a short, one string version of the passed comment array
1596
 *
1597
 * @param $comment
1598
 *   An array of strings containing a comment
1599
 * @return
1600
 *   Short one string version of the comment
1601
 */
160235
function _locale_import_shorten_comments($comment) {
16030
  $comm = '';
16040
  while (count($comment)) {
16050
    $test = $comm . substr(array_shift($comment), 1) . ', ';
16060
    if (strlen($comm) < 130) {
16070
      $comm = $test;
16080
    }
1609
    else {
16100
      break;
1611
    }
16120
  }
16130
  return substr($comm, 0, -2);
16140
}
1615
1616
/**
1617
 * Parses a string in quotes
1618
 *
1619
 * @param $string
1620
 *   A string specified with enclosing quotes
1621
 * @return
1622
 *   The string parsed from inside the quotes
1623
 */
162435
function _locale_import_parse_quoted($string) {
16250
  if (substr($string, 0, 1) != substr($string, -1, 1)) {
16260
    return FALSE;   // Start and end quotes must be the same
16270
  }
16280
  $quote = substr($string, 0, 1);
16290
  $string = substr($string, 1, -1);
16300
  if ($quote == '"') {        // Double quotes: strip slashes
16310
    return stripcslashes($string);
16320
  }
16330
  elseif ($quote == "'") {  // Simple quote: return as-is
16340
    return $string;
16350
  }
1636
  else {
16370
    return FALSE;             // Unrecognized quote
1638
  }
16390
}
1640
/**
1641
 * @} End of "locale-api-import"
1642
 */
1643
1644
/**
1645
 * Parses a JavaScript file, extracts strings wrapped in Drupal.t() and
1646
 * Drupal.formatPlural() and inserts them into the database.
1647
 */
164835
function _locale_parse_js_file($filepath) {
164911
  global $language;
1650
1651
  // Load the JavaScript file.
165211
  $file = file_get_contents($filepath);
1653
1654
  // Match all calls to Drupal.t() in an array.
1655
  // Note: \s also matches newlines with the 's' modifier.
165611
  preg_match_all('~[^\w]Drupal\s*\.\s*t\s*\(\s*(' . LOCALE_JS_STRING .
')\s*[,\)]~s', $file, $t_matches);
1657
1658
  // Match all Drupal.formatPlural() calls in another array.
165911
  preg_match_all('~[^\w]Drupal\s*\.\s*formatPlural\s*\(\s*.+?\s*,\s*(' .
LOCALE_JS_STRING .
')\s*,\s*((?:(?:\'(?:\\\\\'|[^\'])*@count(?:\\\\\'|[^\'])*\'|"(?:\\\\"|[^"])*@count(?:\\\\"|[^"])*")(?:\s*\+\s*)?)+)\s*[,\)]~s',
$file, $plural_matches);
1660
1661
  // Loop through all matches and process them.
166211
  $all_matches = array_merge($plural_matches[1], $t_matches[1]);
166311
  foreach ($all_matches as $key => $string) {
16648
    $strings = array($string);
1665
1666
    // If there is also a plural version of this string, add it to the
strings array.
16678
    if (isset($plural_matches[2][$key])) {
16680
      $strings[] = $plural_matches[2][$key];
16690
    }
1670
16718
    foreach ($strings as $key => $string) {
1672
      // Remove the quotes and string concatenations from the string.
16738
      $string = implode('', preg_split('~(?<!\\\\)[\'"]\s*\+\s*[\'"]~s',
substr($string, 1, -1)));
1674
16758
      $result = db_query("SELECT lid, location FROM {locales_source} WHERE
source = '%s' AND textgroup = 'default'", $string);
16768
      if ($source = db_fetch_object($result)) {
1677
        // We already have this source string and now have to add the
location
1678
        // to the location column, if this file is not yet present in
there.
16792
        $locations = preg_split('~\s*;\s*~', $source->location);
1680
16812
        if (!in_array($filepath, $locations)) {
16820
          $locations[] = $filepath;
16830
          $locations = implode('; ', $locations);
1684
1685
          // Save the new locations string to the database.
16860
          db_query("UPDATE {locales_source} SET location = '%s' WHERE lid =
%d", $locations, $source->lid);
16870
        }
16882
      }
1689
      else {
1690
        // We don't have the source string yet, thus we insert it into the
database.
16918
        db_query("INSERT INTO {locales_source} (location, source,
textgroup) VALUES ('%s', '%s', 'default')", $filepath, $string);
1692
      }
16938
    }
16948
  }
169511
}
1696
1697
/**
1698
 * @defgroup locale-api-export Translation (template) export API.
1699
 * @{
1700
 */
1701
1702
/**
1703
 * Generates a structured array of all strings with translations in
1704
 * $language, if given. This array can be used to generate an export
1705
 * of the string in the database.
1706
 *
1707
 * @param $language
1708
 *   Language object to generate the output for, or NULL if generating
1709
 *   translation template.
1710
 * @param $group
1711
 *   Text group to export PO file from (eg. 'default' for interface
translations)
1712
 */
171335
function _locale_export_get_strings($language = NULL, $group = 'default') {
17140
  if (isset($language)) {
17150
    $result = db_query("SELECT s.lid, s.source, s.location, t.translation,
t.plid, t.plural FROM {locales_source} s LEFT JOIN {locales_target} t ON
s.lid = t.lid AND t.language = '%s' WHERE s.textgroup = '%s' ORDER BY
t.plid, t.plural", $language->language, $group);
17160
  }
1717
  else {
17180
    $result = db_query("SELECT s.lid, s.source, s.location, t.plid,
t.plural FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid =
t.lid WHERE s.textgroup = '%s' ORDER BY t.plid, t.plural", $group);
1719
  }
17200
  $strings = array();
17210
  while ($child = db_fetch_object($result)) {
1722
    $string = array(
17230
      'comment'     => $child->location,
17240
      'source'      => $child->source,
17250
      'translation' => isset($child->translation) ? $child->translation :
''
17260
    );
17270
    if ($child->plid) {
1728
      // Has a parent lid. Since we process in the order of plids,
1729
      // we already have the parent in the array, so we can add the
1730
      // lid to the next plural version to it. This builds a linked
1731
      // list of plurals.
17320
      $string['child'] = TRUE;
17330
      $strings[$child->plid]['plural'] = $child->lid;
17340
    }
17350
    $strings[$child->lid] = $string;
17360
  }
17370
  return $strings;
17380
}
1739
1740
/**
1741
 * Generates the PO(T) file contents for given strings.
1742
 *
1743
 * @param $language
1744
 *   Language object to generate the output for, or NULL if generating
1745
 *   translation template.
1746
 * @param $strings
1747
 *   Array of strings to export. See _locale_export_get_strings()
1748
 *   on how it should be formatted.
1749
 * @param $header
1750
 *   The header portion to use for the output file. Defaults
1751
 *   are provided for PO and POT files.
1752
 */
175335
function _locale_export_po_generate($language = NULL, $strings = array(),
$header = NULL) {
17540
  global $user;
1755
17560
  if (!isset($header)) {
17570
    if (isset($language)) {
17580
      $header = '# ' . $language->name . ' translation of ' .
variable_get('site_name', 'Drupal') . "\n";
17590
      $header .= '# Generated by ' . $user->name . ' <' . $user->mail .
">\n";
17600
      $header .= "#\n";
17610
      $header .= "msgid \"\"\n";
17620
      $header .= "msgstr \"\"\n";
17630
      $header .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
17640
      $header .= "\"POT-Creation-Date: " . date("Y-m-d H:iO") . "\\n\"\n";
17650
      $header .= "\"PO-Revision-Date: " . date("Y-m-d H:iO") . "\\n\"\n";
17660
      $header .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n";
17670
      $header .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n";
17680
      $header .= "\"MIME-Version: 1.0\\n\"\n";
17690
      $header .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
17700
      $header .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
17710
      if ($language->formula && $language->plurals) {
17720
        $header .= "\"Plural-Forms: nplurals=" . $language->plurals . ";
plural=" . strtr($language->formula, array('$' => '')) . ";\\n\"\n";
17730
      }
17740
    }
1775
    else {
17760
      $header = "# LANGUAGE translation of PROJECT\n";
17770
      $header .= "# Copyright (c) YEAR NAME <EMAIL@ADDRESS>\n";
17780
      $header .= "#\n";
17790
      $header .= "msgid \"\"\n";
17800
      $header .= "msgstr \"\"\n";
17810
      $header .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
17820
      $header .= "\"POT-Creation-Date: " . date("Y-m-d H:iO") . "\\n\"\n";
17830
      $header .= "\"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\\n\"\n";
17840
      $header .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n";
17850
      $header .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n";
17860
      $header .= "\"MIME-Version: 1.0\\n\"\n";
17870
      $header .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
17880
      $header .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
17890
      $header .= "\"Plural-Forms: nplurals=INTEGER;
plural=EXPRESSION;\\n\"\n";
1790
    }
17910
  }
1792
17930
  $output = $header . "\n";
1794
17950
  foreach ($strings as $lid => $string) {
1796
    // Only process non-children, children are output below their parent.
17970
    if (!isset($string['child'])) {
17980
      if ($string['comment']) {
17990
        $output .= '#: ' . $string['comment'] . "\n";
18000
      }
18010
      $output .= 'msgid ' . _locale_export_string($string['source']);
18020
      if (!empty($string['plural'])) {
18030
        $plural = $string['plural'];
18040
        $output .= 'msgid_plural ' .
_locale_export_string($strings[$plural]['source']);
18050
        if (isset($language)) {
18060
          $translation = $string['translation'];
18070
          for ($i = 0; $i < $language->plurals; $i++) {
18080
            $output .= 'msgstr[' . $i . '] ' .
_locale_export_string($translation);
18090
            if ($plural) {
18100
              $translation =
_locale_export_remove_plural($strings[$plural]['translation']);
18110
              $plural = isset($strings[$plural]['plural']) ?
$strings[$plural]['plural'] : 0;
18120
            }
1813
            else {
18140
              $translation = '';
1815
            }
18160
          }
18170
        }
1818
        else {
18190
          $output .= 'msgstr[0] ""' . "\n";
18200
          $output .= 'msgstr[1] ""' . "\n";
1821
        }
18220
      }
1823
      else {
18240
        $output .= 'msgstr ' .
_locale_export_string($string['translation']);
1825
      }
18260
      $output .= "\n";
18270
    }
18280
  }
18290
  return $output;
18300
}
1831
1832
/**
1833
 * Write a generated PO or POT file to the output.
1834
 *
1835
 * @param $language
1836
 *   Language object to generate the output for, or NULL if generating
1837
 *   translation template.
1838
 * @param $output
1839
 *   The PO(T) file to output as a string. See _locale_export_generate_po()
1840
 *   on how it can be generated.
1841
 */
184235
function _locale_export_po($language = NULL, $output = NULL) {
1843
  // Log the export event.
18440
  if (isset($language)) {
18450
    $filename = $language->language . '.po';
18460
    watchdog('locale', 'Exported %locale translation file: %filename.',
array('%locale' => $language->name, '%filename' => $filename));
18470
  }
1848
  else {
18490
    $filename = 'drupal.pot';
18500
    watchdog('locale', 'Exported translation file: %filename.',
array('%filename' => $filename));
1851
  }
1852
  // Download the file fo the client.
18530
  header("Content-Disposition: attachment; filename=$filename");
18540
  header("Content-Type: text/plain; charset=utf-8");
18550
  print $output;
18560
  die();
18570
}
1858
1859
/**
1860
 * Print out a string on multiple lines
1861
 */
186235
function _locale_export_string($str) {
18630
  $stri = addcslashes($str, "\0..\37\\\"");
18640
  $parts = array();
1865
1866
  // Cut text into several lines
18670
  while ($stri != "") {
18680
    $i = strpos($stri, "\\n");
18690
    if ($i === FALSE) {
18700
      $curstr = $stri;
18710
      $stri = "";
18720
    }
1873
    else {
18740
      $curstr = substr($stri, 0, $i + 2);
18750
      $stri = substr($stri, $i + 2);
1876
    }
18770
    $curparts = explode("\n", _locale_export_wrap($curstr, 70));
18780
    $parts = array_merge($parts, $curparts);
18790
  }
1880
1881
  // Multiline string
18820
  if (count($parts) > 1) {
18830
    return "\"\"\n\"" . implode("\"\n\"", $parts) . "\"\n";
18840
  }
1885
  // Single line string
18860
  elseif (count($parts) == 1) {
18870
    return "\"$parts[0]\"\n";
18880
  }
1889
  // No translation
1890
  else {
18910
    return "\"\"\n";
1892
  }
18930
}
1894
1895
/**
1896
 * Custom word wrapping for Portable Object (Template) files.
1897
 */
189835
function _locale_export_wrap($str, $len) {
18990
  $words = explode(' ', $str);
19000
  $ret = array();
1901
19020
  $cur = "";
19030
  $nstr = 1;
19040
  while (count($words)) {
19050
    $word = array_shift($words);
19060
    if ($nstr) {
19070
      $cur = $word;
19080
      $nstr = 0;
19090
    }
19100
    elseif (strlen("$cur $word") > $len) {
19110
      $ret[] = $cur . " ";
19120
      $cur = $word;
19130
    }
1914
    else {
19150
      $cur = "$cur $word";
1916
    }
19170
  }
19180
  $ret[] = $cur;
1919
19200
  return implode("\n", $ret);
19210
}
1922
1923
/**
1924
 * Removes plural index information from a string
1925
 */
192635
function _locale_export_remove_plural($entry) {
19270
  return preg_replace('/(@count)\[[0-9]\]/', '\\1', $entry);
19280
}
1929
/**
1930
 * @} End of "locale-api-export"
1931
 */
1932
1933
/**
1934
 * @defgroup locale-api-seek String search functions.
1935
 * @{
1936
 */
1937
1938
/**
1939
 * Perform a string search and display results in a table
1940
 */
194135
function _locale_translate_seek() {
19428
  $output = '';
1943
1944
  // We have at least one criterion to match
19458
  if ($query = _locale_translate_seek_query()) {
19463
    $join = "SELECT s.source, s.location, s.lid, s.textgroup,
t.translation, t.language FROM {locales_source} s LEFT JOIN
{locales_target} t ON s.lid = t.lid ";
1947
19483
    $arguments = array();
19493
    $limit_language = FALSE;
1950
    // Compute LIKE section
19513
    switch ($query['translation']) {
19523
      case 'translated':
19530
        $where = "WHERE (t.translation LIKE ?)";
19540
        $orderby = "ORDER BY t.translation";
19550
        $arguments[] = '%'. $query['string'] .'%';
19560
        break;
19573
      case 'untranslated':
19580
        $where = "WHERE (s.source LIKE ? AND t.translation IS NULL)";
19590
        $orderby = "ORDER BY s.source";
19600
        $arguments[] = '%'. $query['string'] .'%';
19610
        break;
19623
      case 'all' :
19633
      default:
19643
        $where = "WHERE (s.source LIKE ? OR t.translation LIKE ?)";
19653
        $orderby = '';
19663
        $arguments[] = '%'. $query['string'] .'%';
19673
        $arguments[] = '%'. $query['string'] .'%';
19683
        break;
19690
    }
19703
    $grouplimit = '';
19713
    if (!empty($query['group']) && $query['group'] != 'all') {
19720
      $grouplimit = " AND s.textgroup = ?";
19730
      $arguments[] = $query['group'];
19740
    }
1975
19763
    switch ($query['language']) {
1977
      // Force search in source strings
19783
      case "en":
19790
        $sql = $join . " WHERE s.source LIKE ? $grouplimit ORDER BY
s.source";
19800
        $arguments = array('%' . $query['string'] . '%'); // $where is not
used, discard its arguments
19810
        if (!empty($grouplimit)) {
19820
          $arguments[] = $query['group'];
19830
        }
19840
        break;
1985
      // Search in all languages
19863
      case "all":
19873
        $sql = "$join $where $grouplimit $orderby";
19883
        break;
1989
      // Some different language
19900
      default:
19910
        $sql = "$join AND t.language = ? $where $grouplimit $orderby";
19920
        array_unshift($arguments, $query['language']);
1993
        // Don't show translation flags for other languages, we can't see
them with this search.
19940
        $limit_language = $query['language'];
19950
    }
1996
19973
    $result = pager_query($sql, 50, 0, NULL, $arguments);
1998
19993
    $groups = module_invoke_all('locale', 'groups');
20003
    $header = array(t('Text group'), t('String'), ($limit_language) ?
t('Language') : t('Languages'), array('data' => t('Operations'), 'colspan'
=> '2'));
20013
    $arr = array();
20023
    while ($locale = db_fetch_object($result)) {
20032
      $arr[$locale->lid]['group'] = $groups[$locale->textgroup];
20042
      $arr[$locale->lid]['languages'][$locale->language] =
$locale->translation;
20052
      $arr[$locale->lid]['location'] = $locale->location;
20062
      $arr[$locale->lid]['source'] = $locale->source;
20072
    }
20083
    $rows = array();
20093
    foreach ($arr as $lid => $value) {
20102
      $rows[] = array(
20112
        $value['group'],
20122
        array('data' => check_plain(truncate_utf8($value['source'], 150,
FALSE, TRUE)) . '<br /><small>' . $value['location'] . '</small>'),
20132
        array('data' =>
_locale_translate_language_list($value['languages'], $limit_language),
'align' => 'center'),
20142
        array('data' => l(t('edit'), "admin/build/translate/edit/$lid"),
'class' => 'nowrap'),
20152
        array('data' => l(t('delete'),
"admin/build/translate/delete/$lid"), 'class' => 'nowrap'),
2016
      );
20172
    }
2018
20193
    if (count($rows)) {
20202
      $output .= theme('table', $header, $rows);
20212
      if ($pager = theme('pager', NULL, 50)) {
20220
        $output .= $pager;
20230
      }
20242
    }
2025
    else {
20261
      $output .= t('No strings found for your search.');
2027
    }
20283
  }
2029
20308
  return $output;
20310
}
2032
2033
/**
2034
 * Build array out of search criteria specified in request variables
2035
 */
203635
function _locale_translate_seek_query() {
20378
  static $query = NULL;
20388
  if (!isset($query)) {
20398
    $query = array();
20408
    $fields = array('string', 'language', 'translation', 'group');
20418
    foreach ($fields as $field) {
20428
      if (isset($_REQUEST[$field])) {
20433
        $query[$field] = $_REQUEST[$field];
20443
      }
20458
    }
20468
  }
20478
  return $query;
20480
}
2049
2050
/**
2051
 * Force the JavaScript translation file(s) to be refreshed.
2052
 *
2053
 * This function sets a refresh flag for a specified language, or all
2054
 * languages except English, if none specified. JavaScript translation
2055
 * files are rebuilt (with locale_update_js_files()) the next time a
2056
 * request is served in that language.
2057
 *
2058
 * @param $langcode
2059
 *   The language code for which the file needs to be refreshed.
2060
 * @return
2061
 *   New content of the 'javascript_parsed' variable.
2062
 */
206335
function _locale_invalidate_js($langcode = NULL) {
206416
  $parsed = variable_get('javascript_parsed', array());
2065
206616
  if (empty($langcode)) {
2067
    // Invalidate all languages.
206813
    $languages = language_list();
206913
    unset($languages['en']);
207013
    foreach ($languages as $lcode => $data) {
20717
      $parsed['refresh:' . $lcode] = 'waiting';
20727
    }
207313
  }
2074
  else {
2075
    // Invalidate single language.
20764
    $parsed['refresh:' . $langcode] = 'waiting';
2077
  }
2078
207916
  variable_set('javascript_parsed', $parsed);
208016
  return $parsed;
20810
}
2082
2083
/**
2084
 * (Re-)Creates the JavaScript translation file for a language.
2085
 *
2086
 * @param $language
2087
 *   The language, the translation file should be (re)created for.
2088
 */
208935
function _locale_rebuild_js($langcode = NULL) {
20902
  if (!isset($langcode)) {
20911
    global $language;
20921
  }
2093
  else {
2094
    // Get information about the locale.
20951
    $languages = language_list();
20961
    $language = $languages[$langcode];
2097
  }
2098
2099
  // Construct the array for JavaScript translations.
2100
  // We sort on plural so that we have all plural forms before singular
forms.
21012
  $result = db_query("SELECT s.lid, s.source, t.plid, t.plural,
t.translation
2102
    FROM {locales_source} s
2103
      LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language =
:language
2104
    WHERE s.location LIKE '%.js%'
2105
      AND s.textgroup = 'default'
21062
    ORDER BY t.plural DESC", array(':language' => $language->language));
2107
21082
  $translations = $plurals = array();
21092
  while ($data = db_fetch_object($result)) {
2110
    // Only add this to the translations array when there is actually a
translation.
21112
    if (!empty($data->translation)) {
21120
      if ($data->plural) {
2113
        // When the translation is a plural form, first add it to another
array and
2114
        // wait for the singular (parent) translation.
21150
        if (!isset($plurals[$data->plid])) {
21160
          $plurals[$data->plid] = array($data->plural =>
$data->translation);
21170
        }
2118
        else {
21190
          $plurals[$data->plid] += array($data->plural =>
$data->translation);
2120
        }
21210
      }
21220
      elseif (isset($plurals[$data->lid])) {
2123
        // There are plural translations for this translation, so get them
from
2124
        // the plurals array and add them to the final translations array.
21250
        $translations[$data->source] = array($data->plural =>
$data->translation) + $plurals[$data->lid];
21260
        unset($plurals[$data->lid]);
21270
      }
2128
      else {
2129
        // There are no plural forms for this translation, so just add it
to
2130
        // the translations array.
21310
        $translations[$data->source] = $data->translation;
2132
      }
21330
    }
21342
  }
2135
2136
  // Construct the JavaScript file, if there are translations.
21372
  $data = $status = '';
21382
  if (!empty($translations)) {
2139
21400
    $data = "Drupal.locale = { ";
2141
21420
    if (!empty($language->formula)) {
21430
      $data .= "'pluralFormula': function(\$n) { return
Number({$language->formula}); }, ";
21440
    }
2145
21460
    $data .= "'strings': " . drupal_to_js($translations) . " };";
21470
    $data_hash = md5($data);
21480
  }
2149
2150
  // Construct the filepath where JS translation files are stored.
2151
  // There is (on purpose) no front end to edit that variable.
21522
  $dir = file_create_path(variable_get('locale_js_directory',
'languages'));
2153
2154
  // Delete old file, if we have no translations anymore, or a different
file to be saved.
21552
  if (!empty($language->javascript) && (!$data || $language->javascript !=
$data_hash)) {
21560
    file_delete(file_create_path($dir . '/' . $language->language . '_' .
$language->javascript . '.js'));
21570
    $language->javascript = '';
21580
    $status = 'deleted';
21590
  }
2160
2161
  // Only create a new file if the content has changed.
21622
  if ($data && $language->javascript != $data_hash) {
2163
    // Ensure that the directory exists and is writable, if possible.
21640
    file_check_directory($dir, TRUE);
2165
2166
    // Save the file.
21670
    $dest = $dir . '/' . $language->language . '_' . $data_hash . '.js';
21680
    if (file_unmanaged_save_data($data, $dest)) {
21690
      $language->javascript = $data_hash;
21700
      $status = ($status == 'deleted') ? 'updated' : 'created';
21710
    }
2172
    else {
21730
      $language->javascript = '';
21740
      $status = 'error';
2175
    }
21760
  }
2177
2178
  // Save the new JavaScript hash (or an empty value if the file
2179
  // just got deleted). Act only if some operation was executed.
21802
  if ($status) {
21810
    db_query("UPDATE {languages} SET javascript = '%s' WHERE language =
'%s'", $language->javascript, $language->language);
2182
2183
    // Update the default language variable if the default language has
been altered.
2184
    // This is necessary to keep the variable consistent with the database
2185
    // version of the language and to prevent checking against an outdated
hash.
21860
    $default_langcode = language_default('language');
21870
    if ($default_langcode == $language->language) {
21880
      $default = db_fetch_object(db_query("SELECT * FROM {languages} WHERE
language = '%s'", $default_langcode));
21890
      variable_set('language_default', $default);
21900
    }
21910
  }
2192
2193
  // Log the operation and return success flag.
2194
  switch ($status) {
21952
    case 'updated':
21960
      watchdog('locale', 'Updated JavaScript translation file for the
language %language.', array('%language' => t($language->name)));
21970
      return TRUE;
21982
    case 'created':
21990
      watchdog('locale', 'Created JavaScript translation file for the
language %language.', array('%language' => t($language->name)));
22000
      return TRUE;
22012
    case 'deleted':
22020
      watchdog('locale', 'Removed JavaScript translation file for the
language %language, because no translations currently exist for that
language.', array('%language' => t($language->name)));
22030
      return TRUE;
22042
    case 'error':
22050
      watchdog('locale', 'An error occurred during creation of the
JavaScript translation file for the language %language.', array('%language'
=> t($language->name)), WATCHDOG_ERROR);
22060
      return FALSE;
22072
    default:
2208
      // No operation needed.
22092
      return TRUE;
22102
  }
22110
}
2212
2213
/**
2214
 * List languages in search result table
2215
 */
221635
function _locale_translate_language_list($translation, $limit_language) {
2217
  // Add CSS
22182
  drupal_add_css(drupal_get_path('module', 'locale') . '/locale.css',
array('preprocess' => FALSE));
2219
22202
  $languages = language_list();
22212
  unset($languages['en']);
22222
  $output = '';
22232
  foreach ($languages as $langcode => $language) {
22242
    if (!$limit_language || $limit_language == $langcode) {
22252
      $output .= (!empty($translation[$langcode])) ? $langcode . ' ' : "<em
class=\"locale-untranslated\">$langcode</em> ";
22262
    }
22272
  }
2228
22292
  return $output;
22300
}
2231
/**
2232
 * @} End of "locale-api-seek"
2233
 */
2234
2235
/**
2236
 * @defgroup locale-api-predefined List of predefined languages
2237
 * @{
2238
 */
2239
2240
/**
2241
 * Prepares the language code list for a select form item with only the
unsupported ones
2242
 */
224335
function _locale_prepare_predefined_list() {
22446
  $languages = language_list();
22456
  $predefined = _locale_get_predefined_list();
22466
  foreach ($predefined as $key => $value) {
22476
    if (isset($languages[$key])) {
22486
      unset($predefined[$key]);
22496
      continue;
22500
    }
2251
    // Include native name in output, if possible
22526
    if (count($value) > 1) {
22536
      $tname = t($value[0]);
22546
      $predefined[$key] = ($tname == $value[1]) ? $tname : "$tname
($value[1])";
22556
    }
2256
    else {
22576
      $predefined[$key] = t($value[0]);
2258
    }
22596
  }
22606
  asort($predefined);
22616
  return $predefined;
22620
}
2263
2264
/**
2265
 * Some of the common languages with their English and native names
2266
 *
2267
 * Based on ISO 639 and http://people.w3.org/rishida/names/languages.html
2268
 */
226935
function _locale_get_predefined_list() {
2270
  return array(
22716
    "aa" => array("Afar"),
22726
    "ab" => array("Abkhazian", "аҧсуа бызшәа"),
22736
    "ae" => array("Avestan"),
22746
    "af" => array("Afrikaans"),
22756
    "ak" => array("Akan"),
22766
    "am" => array("Amharic", "አማርኛ"),
22776
    "ar" => array("Arabic", /* Left-to-right marker "‭" */
"العربية", LANGUAGE_RTL),
22786
    "as" => array("Assamese"),
22796
    "av" => array("Avar"),
22806
    "ay" => array("Aymara"),
22816
    "az" => array("Azerbaijani", "azərbaycan"),
22826
    "ba" => array("Bashkir"),
22836
    "be" => array("Belarusian", "Беларуская"),
22846
    "bg" => array("Bulgarian", "Български"),
22856
    "bh" => array("Bihari"),
22866
    "bi" => array("Bislama"),
22876
    "bm" => array("Bambara", "Bamanankan"),
22886
    "bn" => array("Bengali"),
22896
    "bo" => array("Tibetan"),
22906
    "br" => array("Breton"),
22916
    "bs" => array("Bosnian", "Bosanski"),
22926
    "ca" => array("Catalan", "Català"),
22936
    "ce" => array("Chechen"),
22946
    "ch" => array("Chamorro"),
22956
    "co" => array("Corsican"),
22966
    "cr" => array("Cree"),
22976
    "cs" => array("Czech", "Čeština"),
22986
    "cu" => array("Old Slavonic"),
22996
    "cv" => array("Chuvash"),
23006
    "cy" => array("Welsh", "Cymraeg"),
23016
    "da" => array("Danish", "Dansk"),
23026
    "de" => array("German", "Deutsch"),
23036
    "dv" => array("Maldivian"),
23046
    "dz" => array("Bhutani"),
23056
    "ee" => array("Ewe", "Ɛʋɛ"),
23066
    "el" => array("Greek", "Ελληνικά"),
23076
    "en" => array("English"),
23086
    "eo" => array("Esperanto"),
23096
    "es" => array("Spanish", "Español"),
23106
    "et" => array("Estonian", "Eesti"),
23116
    "eu" => array("Basque", "Euskera"),
23126
    "fa" => array("Persian", /* Left-to-right marker "‭" */ "فارسی",
LANGUAGE_RTL),
23136
    "ff" => array("Fulah", "Fulfulde"),
23146
    "fi" => array("Finnish", "Suomi"),
23156
    "fj" => array("Fiji"),
23166
    "fo" => array("Faeroese"),
23176
    "fr" => array("French", "Français"),
23186
    "fy" => array("Frisian", "Frysk"),
23196
    "ga" => array("Irish", "Gaeilge"),
23206
    "gd" => array("Scots Gaelic"),
23216
    "gl" => array("Galician", "Galego"),
23226
    "gn" => array("Guarani"),
23236
    "gu" => array("Gujarati"),
23246
    "gv" => array("Manx"),
23256
    "ha" => array("Hausa"),
23266
    "he" => array("Hebrew", /* Left-to-right marker "‭" */ "עברית",
LANGUAGE_RTL),
23276
    "hi" => array("Hindi", "हिन्दी"),
23286
    "ho" => array("Hiri Motu"),
23296
    "hr" => array("Croatian", "Hrvatski"),
23306
    "hu" => array("Hungarian", "Magyar"),
23316
    "hy" => array("Armenian", "Հայերեն"),
23326
    "hz" => array("Herero"),
23336
    "ia" => array("Interlingua"),
23346
    "id" => array("Indonesian", "Bahasa Indonesia"),
23356
    "ie" => array("Interlingue"),
23366
    "ig" => array("Igbo"),
23376
    "ik" => array("Inupiak"),
23386
    "is" => array("Icelandic", "Íslenska"),
23396
    "it" => array("Italian", "Italiano"),
23406
    "iu" => array("Inuktitut"),
23416
    "ja" => array("Japanese", "日本語"),
23426
    "jv" => array("Javanese"),
23436
    "ka" => array("Georgian"),
23446
    "kg" => array("Kongo"),
23456
    "ki" => array("Kikuyu"),
23466
    "kj" => array("Kwanyama"),
23476
    "kk" => array("Kazakh", "Қазақ"),
23486
    "kl" => array("Greenlandic"),
23496
    "km" => array("Cambodian"),
23506
    "kn" => array("Kannada", "ಕನ್ನಡ"),
23516
    "ko" => array("Korean", "한국어"),
23526
    "kr" => array("Kanuri"),
23536
    "ks" => array("Kashmiri"),
23546
    "ku" => array("Kurdish", "Kurdî"),
23556
    "kv" => array("Komi"),
23566
    "kw" => array("Cornish"),
23576
    "ky" => array("Kirghiz", "Кыргыз"),
23586
    "la" => array("Latin", "Latina"),
23596
    "lb" => array("Luxembourgish"),
23606
    "lg" => array("Luganda"),
23616
    "ln" => array("Lingala"),
23626
    "lo" => array("Laothian"),
23636
    "lt" => array("Lithuanian", "Lietuvių"),
23646
    "lv" => array("Latvian", "Latviešu"),
23656
    "mg" => array("Malagasy"),
23666
    "mh" => array("Marshallese"),
23676
    "mi" => array("Maori"),
23686
    "mk" => array("Macedonian", "Македонски"),
23696
    "ml" => array("Malayalam", "മലയാളം"),
23706
    "mn" => array("Mongolian"),
23716
    "mo" => array("Moldavian"),
23726
    "mr" => array("Marathi"),
23736
    "ms" => array("Malay", "Bahasa Melayu"),
23746
    "mt" => array("Maltese", "Malti"),
23756
    "my" => array("Burmese"),
23766
    "na" => array("Nauru"),
23776
    "nd" => array("North Ndebele"),
23786
    "ne" => array("Nepali"),
23796
    "ng" => array("Ndonga"),
23806
    "nl" => array("Dutch", "Nederlands"),
23816
    "nb" => array("Norwegian Bokmål", "Bokmål"),
23826
    "nn" => array("Norwegian Nynorsk", "Nynorsk"),
23836
    "nr" => array("South Ndebele"),
23846
    "nv" => array("Navajo"),
23856
    "ny" => array("Chichewa"),
23866
    "oc" => array("Occitan"),
23876
    "om" => array("Oromo"),
23886
    "or" => array("Oriya"),
23896
    "os" => array("Ossetian"),
23906
    "pa" => array("Punjabi"),
23916
    "pi" => array("Pali"),
23926
    "pl" => array("Polish", "Polski"),
23936
    "ps" => array("Pashto", /* Left-to-right marker "‭" */ "پښتو",
LANGUAGE_RTL),
23946
    "pt-pt" => array("Portuguese, Portugal", "Português"),
23956
    "pt-br" => array("Portuguese, Brazil", "Português"),
23966
    "qu" => array("Quechua"),
23976
    "rm" => array("Rhaeto-Romance"),
23986
    "rn" => array("Kirundi"),
23996
    "ro" => array("Romanian", "Română"),
24006
    "ru" => array("Russian", "Русский"),
24016
    "rw" => array("Kinyarwanda"),
24026
    "sa" => array("Sanskrit"),
24036
    "sc" => array("Sardinian"),
24046
    "sd" => array("Sindhi"),
24056
    "se" => array("Northern Sami"),
24066
    "sg" => array("Sango"),
24076
    "sh" => array("Serbo-Croatian"),
24086
    "si" => array("Singhalese"),
24096
    "sk" => array("Slovak", "Slovenčina"),
24106
    "sl" => array("Slovenian", "Slovenščina"),
24116
    "sm" => array("Samoan"),
24126
    "sn" => array("Shona"),
24136
    "so" => array("Somali"),
24146
    "sq" => array("Albanian", "Shqip"),
24156
    "sr" => array("Serbian", "Српски"),
24166
    "ss" => array("Siswati"),
24176
    "st" => array("Sesotho"),
24186
    "su" => array("Sudanese"),
24196
    "sv" => array("Swedish", "Svenska"),
24206
    "sw" => array("Swahili", "Kiswahili"),
24216
    "ta" => array("Tamil", "தமிழ்"),
24226
    "te" => array("Telugu", "తెలుగు"),
24236
    "tg" => array("Tajik"),
24246
    "th" => array("Thai", "ภาษาไทย"),
24256
    "ti" => array("Tigrinya"),
24266
    "tk" => array("Turkmen"),
24276
    "tl" => array("Tagalog"),
24286
    "tn" => array("Setswana"),
24296
    "to" => array("Tonga"),
24306
    "tr" => array("Turkish", "Türkçe"),
24316
    "ts" => array("Tsonga"),
24326
    "tt" => array("Tatar", "Tatarça"),
24336
    "tw" => array("Twi"),
24346
    "ty" => array("Tahitian"),
24356
    "ug" => array("Uighur"),
24366
    "uk" => array("Ukrainian", "Українська"),
24376
    "ur" => array("Urdu", /* Left-to-right marker "‭" */ "اردو",
LANGUAGE_RTL),
24386
    "uz" => array("Uzbek", "o'zbek"),
24396
    "ve" => array("Venda"),
24406
    "vi" => array("Vietnamese", "Tiếng Việt"),
24416
    "wo" => array("Wolof"),
24426
    "xh" => array("Xhosa", "isiXhosa"),
24436
    "yi" => array("Yiddish"),
24446
    "yo" => array("Yoruba", "Yorùbá"),
24456
    "za" => array("Zhuang"),
24466
    "zh-hans" => array("Chinese, Simplified", "简体中文"),
24476
    "zh-hant" => array("Chinese, Traditional", "繁體中文"),
24486
    "zu" => array("Zulu", "isiZulu"),
24496
  );
24500
}
2451
/**
2452
 * @} End of "locale-api-languages-predefined"
2453
 */
2454
2455
/**
2456
 * @defgroup locale-autoimport Automatic interface translation import
2457
 * @{
2458
 */
2459
2460
/**
2461
 * Prepare a batch to import translations for all enabled
2462
 * modules in a given language.
2463
 *
2464
 * @param $langcode
2465
 *   Language code to import translations for.
2466
 * @param $finished
2467
 *   Optional finished callback for the batch.
2468
 * @param $skip
2469
 *   Array of component names to skip. Used in the installer for the
2470
 *   second pass import, when most components are already imported.
2471
 * @return
2472
 *   A batch structure or FALSE if no files found.
2473
 */
247435
function locale_batch_by_language($langcode, $finished = NULL, $skip =
array()) {
2475
  // Collect all files to import for all enabled modules and themes.
24763
  $files = array();
24773
  $components = array();
24783
  $query = "SELECT name, filename FROM {system} WHERE status = 1";
24793
  if (count($skip)) {
24800
    $query .= " AND name NOT IN (" . db_placeholders($skip, 'varchar') .
")";
24810
  }
24823
  $result = db_query($query, $skip);
24833
  while ($component = db_fetch_object($result)) {
2484
    // Collect all files for all components, names as $langcode.po or
2485
    // with names ending with $langcode.po. This allows for filenames
2486
    // like node-module.de.po to let translators use small files and
2487
    // be able to import in smaller chunks.
24883
    $files = array_merge($files,
file_scan_directory(dirname($component->filename) . '/translations',
'/(^|\.)' . $langcode . '\.po$/', array('.', '..', 'CVS'), 0, FALSE));
24893
    $components[] = $component->name;
24903
  }
2491
24923
  return _locale_batch_build($files, $finished, $components);
24930
}
2494
2495
/**
2496
 * Prepare a batch to run when installing modules or enabling themes.
2497
 * This batch will import translations for the newly added components
2498
 * in all the languages already set up on the site.
2499
 *
2500
 * @param $components
2501
 *   An array of component (theme and/or module) names to import
2502
 *   translations for.
2503
 * @param $finished
2504
 *   Optional finished callback for the batch.
2505
 */
250635
function locale_batch_by_component($components, $finished =
'_locale_batch_system_finished') {
25071
  $files = array();
25081
  $languages = language_list('enabled');
25091
  unset($languages[1]['en']);
25101
  if (count($languages[1])) {
25110
    $language_list = join('|', array_keys($languages[1]));
2512
    // Collect all files to import for all $components.
25130
    $result = db_query("SELECT name, filename FROM {system} WHERE status =
1");
25140
    while ($component = db_fetch_object($result)) {
25150
      if (in_array($component->name, $components)) {
2516
        // Collect all files for this component in all enabled languages,
named
2517
        // as $langcode.po or with names ending with $langcode.po. This
allows
2518
        // for filenames like node-module.de.po to let translators use
small
2519
        // files and be able to import in smaller chunks.
25200
        $files = array_merge($files,
file_scan_directory(dirname($component->filename) . '/translations',
'/(^|\.)(' . $language_list . ')\.po$/', array('.', '..', 'CVS'), 0,
FALSE));
25210
      }
25220
    }
25230
    return _locale_batch_build($files, $finished);
25240
  }
25251
  return FALSE;
25260
}
2527
2528
/**
2529
 * Build a locale batch from an array of files.
2530
 *
2531
 * @param $files
2532
 *   Array of files to import
2533
 * @param $finished
2534
 *   Optional finished callback for the batch.
2535
 * @param $components
2536
 *   Optional list of component names the batch covers. Used in the
installer.
2537
 * @return
2538
 *   A batch structure
2539
 */
254035
function _locale_batch_build($files, $finished = NULL, $components =
array()) {
25413
  $t = get_t();
25423
  if (count($files)) {
25430
    $operations = array();
25440
    foreach ($files as $file) {
2545
      // We call _locale_batch_import for every batch operation.
25460
      $operations[] = array('_locale_batch_import',
array($file->filename));    }
2547
      $batch = array(
25480
        'operations'    => $operations,
25490
        'title'         => $t('Importing interface translations'),
25500
        'init_message'  => $t('Starting import'),
25510
        'error_message' => $t('Error importing interface translations'),
25520
        'file'          => 'includes/locale.inc',
2553
        // This is not a batch API construct, but data passed along to the
2554
        // installer, so we know what did we import already.
25550
        '#components'   => $components,
25560
      );
25570
      if (isset($finished)) {
25580
        $batch['finished'] = $finished;
25590
      }
25600
    return $batch;
25610
  }
25623
  return FALSE;
25630
}
2564
2565
/**
2566
 * Perform interface translation import as a batch step.
2567
 *
2568
 * @param $filepath
2569
 *   Path to a file to import.
2570
 * @param $results
2571
 *   Contains a list of files imported.
2572
 */
257335
function _locale_batch_import($filepath, &$context) {
2574
  // The filename is either {langcode}.po or {prefix}.{langcode}.po, so
2575
  // we can extract the language code to use for the import from the end.
25760
  if (preg_match('!(/|\.)([^\./]+)\.po$!', $filepath, $langcode)) {
25770
    $file = (object) array('filename' => basename($filepath), 'filepath' =>
$filepath);
25780
    _locale_import_read_po('db-store', $file, LOCALE_IMPORT_KEEP,
$langcode[2]);
25790
    $context['results'][] = $filepath;
25800
  }
25810
}
2582
2583
/**
2584
 * Finished callback of system page locale import batch.
2585
 * Inform the user of translation files imported.
2586
 */
258735
function _locale_batch_system_finished($success, $results) {
25880
  if ($success) {
25890
    drupal_set_message(format_plural(count($results), 'One translation file
imported for the newly installed modules.', '@count translation files
imported for the newly installed modules.'));
25900
  }
25910
}
2592
2593
/**
2594
 * Finished callback of language addition locale import batch.
2595
 * Inform the user of translation files imported.
2596
 */
259735
function _locale_batch_language_finished($success, $results) {
25980
  if ($success) {
25990
    drupal_set_message(format_plural(count($results), 'One translation file
imported for the enabled modules.', '@count translation files imported for
the enabled modules.'));
26000
  }
26010
}
2602
2603
/**
2604
 * @} End of "locale-autoimport"
2605
 */
260635