Code coverage for /20080809/includes/locale.inc

Line #Times calledCode
1
<?php
2
// $Id: locale.inc,v 1.179 2008/08/05 18:11:44 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
 */
1230
define('LOCALE_JS_STRING',
'(?:(?:\'(?:\\\\\'|[^\'])*\'|"(?:\\\\"|[^"])*")(?:\s*\+\s*)?)+');
13
14
/**
15
 * Translation import mode overwriting all existing translations
16
 * if new translated version available.
17
 */
1830
define('LOCALE_IMPORT_OVERWRITE', 0);
19
20
/**
21
 * Translation import mode keeping existing translations and only
22
 * inserting new strings.
23
 */
2430
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
 */
3430
function locale_languages_overview_form() {
357
  $languages = language_list('language', TRUE);
36
377
  $options = array();
387
  $form['weight'] = array('#tree' => TRUE);
397
  foreach ($languages as $langcode => $language) {
40
417
    $options[$langcode] = '';
427
    if ($language->enabled) {
437
      $enabled[] = $langcode;
447
    }
457
    $form['weight'][$langcode] = array(
467
      '#type' => 'weight',
477
      '#default_value' => $language->weight
487
    );
497
    $form['name'][$langcode] = array('#markup' =>
check_plain($language->name));
507
    $form['native'][$langcode] = array('#markup' =>
check_plain($language->native));
517
    $form['direction'][$langcode] = array('#markup' =>
($language->direction == LANGUAGE_RTL ? t('Right to left') : t('Left to
right')));
527
  }
537
  $form['enabled'] = array('#type' => 'checkboxes',
547
    '#options' => $options,
557
    '#default_value' => $enabled,
56
  );
577
  $form['site_default'] = array('#type' => 'radios',
587
    '#options' => $options,
597
    '#default_value' => language_default('language'),
60
  );
617
  $form['submit'] = array('#type' => 'submit', '#value' => t('Save
configuration'));
627
  $form['#theme'] = 'locale_languages_overview_form';
63
647
  return $form;
650
}
66
67
/**
68
 * Theme the language overview form.
69
 *
70
 * @ingroup themeable
71
 */
7230
function theme_locale_languages_overview_form($form) {
736
  $default = language_default();
746
  foreach ($form['name'] as $key => $element) {
75
    // Do not take form control structures.
766
    if (is_array($element) && element_child($key)) {
77
      // Disable checkbox for the default language, because it cannot be
disabled.
786
      if ($key == $default->language) {
796
        $form['enabled'][$key]['#attributes']['disabled'] = 'disabled';
806
      }
816
      $rows[] = array(
826
        array('data' => drupal_render($form['enabled'][$key]), 'align' =>
'center'),
836
        check_plain($key),
846
        '<strong>' . drupal_render($form['name'][$key]) . '</strong>',
856
        drupal_render($form['native'][$key]),
866
        drupal_render($form['direction'][$key]),
876
        drupal_render($form['site_default'][$key]),
886
        drupal_render($form['weight'][$key]),
896
        l(t('edit'), 'admin/settings/language/edit/' . $key) . (($key !=
'en' && $key != $default->language) ? ' ' . l(t('delete'),
'admin/settings/language/delete/' . $key) : '')
906
      );
916
    }
926
  }
936
  $header = array(array('data' => t('Enabled')), array('data' =>
t('Code')), array('data' => t('English name')), array('data' => t('Native
name')), array('data' => t('Direction')), array('data' => t('Default')),
array('data' => t('Weight')), array('data' => t('Operations')));
946
  $output = theme('table', $header, $rows);
956
  $output .= drupal_render($form);
96
976
  return $output;
980
}
99
100
/**
101
 * Process language overview form submissions, updating existing
languages.
102
 */
10330
function locale_languages_overview_form_submit($form, &$form_state) {
1041
  $languages = language_list();
1051
  $default = language_default();
1061
  $enabled_count = 0;
1071
  foreach ($languages as $langcode => $language) {
1081
    if ($form_state['values']['site_default'] == $langcode ||
$default->language == $langcode) {
109
      // Automatically enable the default language and the language
110
      // which was default previously (because we will not get the
111
      // value from that disabled checkox).
1121
      $form_state['values']['enabled'][$langcode] = 1;
1131
    }
1141
    if ($form_state['values']['enabled'][$langcode]) {
1151
      $enabled_count++;
1161
      $language->enabled = 1;
1171
    }
118
    else {
1190
      $language->enabled = 0;
120
    }
1211
    $language->weight = $form_state['values']['weight'][$langcode];
1221
    db_query("UPDATE {languages} SET enabled = %d, weight = %d WHERE
language = '%s'", $language->enabled, $language->weight, $langcode);
1231
    $languages[$langcode] = $language;
1241
  }
1251
  drupal_set_message(t('Configuration saved.'));
1261
  variable_set('language_default',
$languages[$form_state['values']['site_default']]);
1271
  variable_set('language_count', $enabled_count);
128
129
  // Changing the language settings impacts the interface.
1301
  cache_clear_all('*', 'cache_page', TRUE);
131
1321
  $form_state['redirect'] = 'admin/settings/language';
1331
  return;
1340
}
135
/**
136
 * @} End of "locale-language-overview"
137
 */
138
139
/**
140
 * @defgroup locale-language-add-edit Language addition and editing
functionality
141
 * @{
142
 */
143
144
/**
145
 * User interface for the language addition screen.
146
 */
14730
function locale_languages_add_screen() {
1484
  $output = drupal_get_form('locale_languages_predefined_form');
1493
  $output .= drupal_get_form('locale_languages_custom_form');
1502
  return $output;
1510
}
152
153
/**
154
 * Predefined language setup form.
155
 */
15630
function locale_languages_predefined_form() {
1574
  $predefined = _locale_prepare_predefined_list();
1584
  $form = array();
1594
  $form['language list'] = array('#type' => 'fieldset',
1604
    '#title' => t('Predefined language'),
1614
    '#collapsible' => TRUE,
162
  );
1634
  $form['language list']['langcode'] = array('#type' => 'select',
1644
    '#title' => t('Language name'),
1654
    '#default_value' => key($predefined),
1664
    '#options' => $predefined,
1674
    '#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.)'),
168
  );
1694
  $form['language list']['submit'] = array('#type' => 'submit', '#value' =>
t('Add language'));
1704
  return $form;
1710
}
172
173
/**
174
 * Custom language addition form.
175
 */
17630
function locale_languages_custom_form() {
1773
  $form = array();
1783
  $form['custom language'] = array('#type' => 'fieldset',
1793
    '#title' => t('Custom language'),
1803
    '#collapsible' => TRUE,
1813
    '#collapsed' => TRUE,
182
  );
1833
  _locale_languages_common_controls($form['custom language']);
1843
  $form['custom language']['submit'] = array(
1853
    '#type' => 'submit',
1863
    '#value' => t('Add custom language')
1873
  );
188
  // Reuse the validation and submit functions of the predefined language
setup form.
1893
  $form['#submit'][] = 'locale_languages_predefined_form_submit';
1903
  $form['#validate'][] = 'locale_languages_predefined_form_validate';
1913
  return $form;
1920
}
193
194
/**
195
 * Editing screen for a particular language.
196
 *
197
 * @param $langcode
198
 *   Language code of the language to edit.
199
 */
20030
function locale_languages_edit_form(&$form_state, $langcode) {
2010
  if ($language = db_fetch_object(db_query("SELECT * FROM {languages} WHERE
language = '%s'", $langcode))) {
2020
    $form = array();
2030
    _locale_languages_common_controls($form, $language);
2040
    $form['submit'] = array(
2050
      '#type' => 'submit',
2060
      '#value' => t('Save language')
2070
    );
2080
    $form['#submit'][] = 'locale_languages_edit_form_submit';
2090
    $form['#validate'][] = 'locale_languages_edit_form_validate';
2100
    return $form;
2110
  }
212
  else {
2130
    drupal_not_found();
214
  }
2150
}
216
217
/**
218
 * Common elements of the language addition and editing form.
219
 *
220
 * @param $form
221
 *   A parent form item (or empty array) to add items below.
222
 * @param $language
223
 *   Language object to edit.
224
 */
22530
function _locale_languages_common_controls(&$form, $language = NULL) {
2263
  if (!is_object($language)) {
2273
    $language = new stdClass();
2283
  }
2293
  if (isset($language->language)) {
2300
    $form['langcode_view'] = array(
2310
      '#type' => 'item',
2320
      '#title' => t('Language code'),
2330
      '#markup' => $language->language
2340
    );
2350
    $form['langcode'] = array(
2360
      '#type' => 'value',
2370
      '#value' => $language->language
2380
    );
2390
  }
240
  else {
2413
    $form['langcode'] = array('#type' => 'textfield',
2423
      '#title' => t('Language code'),
2433
      '#size' => 12,
2443
      '#maxlength' => 60,
2453
      '#required' => TRUE,
2463
      '#default_value' => @$language->language,
2473
      '#disabled' => (isset($language->language)),
2483
      '#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')),
249
    );
250
  }
2513
  $form['name'] = array('#type' => 'textfield',
2523
    '#title' => t('Language name in English'),
2533
    '#maxlength' => 64,
2543
    '#default_value' => @$language->name,
2553
    '#required' => TRUE,
2563
    '#description' => t('Name of the language in English. Will be available
for translation in all languages.'),
257
  );
2583
  $form['native'] = array('#type' => 'textfield',
2593
    '#title' => t('Native language name'),
2603
    '#maxlength' => 64,
2613
    '#default_value' => @$language->native,
2623
    '#required' => TRUE,
2633
    '#description' => t('Name of the language in the language being
added.'),
264
  );
2653
  $form['prefix'] = array('#type' => 'textfield',
2663
    '#title' => t('Path prefix'),
2673
    '#maxlength' => 64,
2683
    '#default_value' => @$language->prefix,
2693
    '#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>')
2703
  );
2713
  $form['domain'] = array('#type' => 'textfield',
2723
    '#title' => t('Language domain'),
2733
    '#maxlength' => 64,
2743
    '#default_value' => @$language->domain,
2753
    '#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>'),
276
  );
2773
  $form['direction'] = array('#type' => 'radios',
2783
    '#title' => t('Direction'),
2793
    '#required' => TRUE,
2803
    '#description' => t('Direction that text in this language is
presented.'),
2813
    '#default_value' => @$language->direction,
2823
    '#options' => array(LANGUAGE_LTR => t('Left to right'), LANGUAGE_RTL =>
t('Right to left'))
2833
  );
2843
  return $form;
2850
}
286
287
/**
288
 * Validate the language addition form.
289
 */
29030
function locale_languages_predefined_form_validate($form, &$form_state) {
2912
  $langcode = $form_state['values']['langcode'];
292
2932
  if ($duplicate = db_result(db_query("SELECT COUNT(*) FROM {languages}
WHERE language = '%s'", $langcode)) != 0) {
2940
    form_set_error('langcode', t('The language %language (%code) already
exists.', array('%language' => $form_state['values']['name'], '%code' =>
$langcode)));
2950
  }
296
2972
  if (!isset($form_state['values']['name'])) {
298
    // Predefined language selection.
2991
    $predefined = _locale_get_predefined_list();
3001
    if (!isset($predefined[$langcode])) {
3010
      form_set_error('langcode', t('Invalid language code.'));
3020
    }
3031
  }
304
  else {
305
    // Reuse the editing form validation routine if we add a custom
language.
3061
    locale_languages_edit_form_validate($form, $form_state);
307
  }
3082
}
309
310
/**
311
 * Process the language addition form submission.
312
 */
31330
function locale_languages_predefined_form_submit($form, &$form_state) {
3142
  $langcode = $form_state['values']['langcode'];
3152
  if (isset($form_state['values']['name'])) {
316
    // Custom language form.
3171
    locale_add_language($langcode, $form_state['values']['name'],
$form_state['values']['native'], $form_state['values']['direction'],
$form_state['values']['domain'], $form_state['values']['prefix']);
3181
    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'))));
3191
  }
320
  else {
321
    // Predefined language selection.
3221
    $predefined = _locale_get_predefined_list();
3231
    locale_add_language($langcode);
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($predefined[$langcode][0]), '@locale-help' =>
url('admin/help/locale'))));
325
  }
326
327
  // See if we have language files to import for the newly added
328
  // language, collect and import them.
3292
  if ($batch = locale_batch_by_language($langcode,
'_locale_batch_language_finished')) {
3300
    batch_set($batch);
3310
  }
332
3332
  $form_state['redirect'] = 'admin/settings/language';
3342
  return;
3350
}
336
337
/**
338
 * Validate the language editing form. Reused for custom language addition
too.
339
 */
34030
function locale_languages_edit_form_validate($form, &$form_state) {
3411
  if (!empty($form_state['values']['domain']) &&
!empty($form_state['values']['prefix'])) {
3420
    form_set_error('prefix', t('Domain and path prefix values should not be
set at the same time.'));
3430
  }
3441
  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']))) {
3450
    form_set_error('domain', t('The domain (%domain) is already tied to a
language (%language).', array('%domain' => $form_state['values']['domain'],
'%language' => $duplicate->language)));
3460
  }
3471
  if (empty($form_state['values']['prefix']) &&
language_default('language') != $form_state['values']['langcode'] &&
empty($form_state['values']['domain'])) {
3480
    form_set_error('prefix', t('Only the default language can have both the
domain and prefix empty.'));
3490
  }
3501
  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']))) {
3510
    form_set_error('prefix', t('The prefix (%prefix) is already tied to a
language (%language).', array('%prefix' => $form_state['values']['prefix'],
'%language' => $duplicate->language)));
3520
  }
3531
}
354
355
/**
356
 * Process the language editing form submission.
357
 */
35830
function locale_languages_edit_form_submit($form, &$form_state) {
3590
  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']);
3600
  $default = language_default();
3610
  if ($default->language == $form_state['values']['langcode']) {
3620
    $properties = array('name', 'native', 'direction', 'enabled',
'plurals', 'formula', 'domain', 'prefix', 'weight');
3630
    foreach ($properties as $keyname) {
3640
      if (isset($form_state['values'][$keyname])) {
3650
        $default->$keyname = $form_state['values'][$keyname];
3660
      }
3670
    }
3680
    variable_set('language_default', $default);
3690
  }
3700
  $form_state['redirect'] = 'admin/settings/language';
3710
  return;
3720
}
373
/**
374
 * @} End of "locale-language-add-edit"
375
 */
376
377
/**
378
 * @defgroup locale-language-delete Language deletion functionality
379
 * @{
380
 */
381
382
/**
383
 * User interface for the language deletion confirmation screen.
384
 */
38530
function locale_languages_delete_form(&$form_state, $langcode) {
386
387
  // Do not allow deletion of English locale.
3883
  if ($langcode == 'en') {
3890
    drupal_set_message(t('The English language cannot be deleted.'));
3900
    drupal_goto('admin/settings/language');
3910
  }
392
3933
  if (language_default('language') == $langcode) {
3940
    drupal_set_message(t('The default language cannot be deleted.'));
3950
    drupal_goto('admin/settings/language');
3960
  }
397
398
  // For other languages, warn user that data loss is ahead.
3993
  $languages = language_list();
400
4013
  if (!isset($languages[$langcode])) {
4021
    drupal_not_found();
4031
  }
404
  else {
4052
    $form['langcode'] = array('#type' => 'value', '#value' => $langcode);
4062
    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'));
407
  }
4081
}
409
410
/**
411
 * Process language deletion submissions.
412
 */
41330
function locale_languages_delete_form_submit($form, &$form_state) {
4141
  $languages = language_list();
4151
  if (isset($languages[$form_state['values']['langcode']])) {
416
    // Remove translations first.
4171
    db_query("DELETE FROM {locales_target} WHERE language = '%s'",
$form_state['values']['langcode']);
4181
    cache_clear_all('locale:' . $form_state['values']['langcode'],
'cache');
419
    // With no translations, this removes existing JavaScript translations
file.
4201
    _locale_rebuild_js($form_state['values']['langcode']);
421
    // Remove the language.
4221
    db_query("DELETE FROM {languages} WHERE language = '%s'",
$form_state['values']['langcode']);
4231
    db_query("UPDATE {node} SET language = '' WHERE language = '%s'",
$form_state['values']['langcode']);
4241
    $variables = array('%locale' =>
$languages[$form_state['values']['langcode']]->name);
4251
    drupal_set_message(t('The language %locale has been removed.',
$variables));
4261
    watchdog('locale', 'The language %locale has been removed.',
$variables);
4271
  }
428
429
  // Changing the language settings impacts the interface:
4301
  cache_clear_all('*', 'cache_page', TRUE);
431
4321
  $form_state['redirect'] = 'admin/settings/language';
4331
  return;
4340
}
435
/**
436
 * @} End of "locale-language-add-edit"
437
 */
438
439
/**
440
 * @defgroup locale-languages-negotiation Language negotiation options
screen
441
 * @{
442
 */
443
444
/**
445
 * Setting for language negotiation options
446
 */
44730
function locale_languages_configure_form() {
4480
  $form['language_negotiation'] = array(
4490
    '#title' => t('Language negotiation'),
4500
    '#type' => 'radios',
451
    '#options' => array(
4520
      LANGUAGE_NEGOTIATION_NONE => t('None.'),
4530
      LANGUAGE_NEGOTIATION_PATH_DEFAULT => t('Path prefix only.'),
4540
      LANGUAGE_NEGOTIATION_PATH => t('Path prefix with language
fallback.'),
4550
      LANGUAGE_NEGOTIATION_DOMAIN => t('Domain name only.')),
4560
    '#default_value' => variable_get('language_negotiation',
LANGUAGE_NEGOTIATION_NONE),
4570
    '#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>")
4580
  );
4590
  $form['submit'] = array(
4600
    '#type' => 'submit',
4610
    '#value' => t('Save settings')
4620
  );
4630
  return $form;
4640
}
465
466
/**
467
 * Submit function for language negotiation settings.
468
 */
46930
function locale_languages_configure_form_submit($form, &$form_state) {
4700
  variable_set('language_negotiation',
$form_state['values']['language_negotiation']);
4710
  drupal_set_message(t('Language negotiation configuration saved.'));
4720
  $form_state['redirect'] = 'admin/settings/language';
4730
  return;
4740
}
475
/**
476
 * @} End of "locale-languages-negotiation"
477
 */
478
479
/**
480
 * @defgroup locale-translate-overview Translation overview screen.
481
 * @{
482
 */
483
484
/**
485
 * Overview screen for translations.
486
 */
48730
function locale_translate_overview_screen() {
4880
  $languages = language_list('language', TRUE);
4890
  $groups = module_invoke_all('locale', 'groups');
490
491
  // Build headers with all groups in order.
4920
  $headers = array_merge(array(t('Language')), array_values($groups));
493
494
  // Collect summaries of all source strings in all groups.
4950
  $sums = db_query("SELECT COUNT(*) AS strings, textgroup FROM
{locales_source} GROUP BY textgroup");
4960
  $groupsums = array();
4970
  while ($group = db_fetch_object($sums)) {
4980
    $groupsums[$group->textgroup] = $group->strings;
4990
  }
500
501
  // Set up overview table with default values, ensuring common order for
values.
5020
  $rows = array();
5030
  foreach ($languages as $langcode => $language) {
5040
    $rows[$langcode] = array('name' => ($langcode == 'en' ? t('English
(built-in)') : t($language->name)));
5050
    foreach ($groups as $group => $name) {
5060
      $rows[$langcode][$group] = ($langcode == 'en' ? t('n/a') : '0/' .
(isset($groupsums[$group]) ? $groupsums[$group] : 0) . ' (0%)');
5070
    }
5080
  }
509
510
  // Languages with at least one record in the locale table.
5110
  $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");
5120
  while ($data = db_fetch_object($translations)) {
5130
    $ratio = (!empty($groupsums[$data->textgroup]) && $data->translation >
0) ? round(($data->translation/$groupsums[$data->textgroup])*100., 2) : 0;
5140
    $rows[$data->language][$data->textgroup] = $data->translation . '/' .
$groupsums[$data->textgroup] . " ($ratio%)";
5150
  }
516
5170
  return theme('table', $headers, $rows);
5180
}
519
/**
520
 * @} End of "locale-translate-overview"
521
 */
522
523
/**
524
 * @defgroup locale-translate-seek Translation search screen.
525
 * @{
526
 */
527
528
/**
529
 * String search screen.
530
 */
53130
function locale_translate_seek_screen() {
5328
  $output = _locale_translate_seek();
5338
  $output .= drupal_get_form('locale_translate_seek_form');
5348
  return $output;
5350
}
536
537
/**
538
 * User interface for the string search screen.
539
 */
54030
function locale_translate_seek_form() {
541
  // Get all languages, except English
5428
  $languages = locale_language_list('name', TRUE);
5438
  unset($languages['en']);
544
545
  // Present edit form preserving previous user settings
5468
  $query = _locale_translate_seek_query();
5478
  $form = array();
5488
  $form['search'] = array('#type' => 'fieldset',
5498
    '#title' => t('Search'),
550
  );
5518
  $form['search']['string'] = array('#type' => 'textfield',
5528
    '#title' => t('String contains'),
5538
    '#default_value' => @$query['string'],
5548
    '#description' => t('Leave blank to show all strings. The search is
case sensitive.'),
555
  );
5568
  $form['search']['language'] = array(
557
    // Change type of form widget if more the 5 options will
558
    // be present (2 of the options are added below).
5598
    '#type' => (count($languages) <= 3 ? 'radios' : 'select'),
5608
    '#title' => t('Language'),
5618
    '#default_value' => (!empty($query['language']) ? $query['language'] :
'all'),
5628
    '#options' => array_merge(array('all' => t('All languages'), 'en' =>
t('English (provided by Drupal)')), $languages),
563
  );
5648
  $form['search']['translation'] = array('#type' => 'radios',
5658
    '#title' => t('Search in'),
5668
    '#default_value' => (!empty($query['translation']) ?
$query['translation'] : 'all'),
5678
    '#options' => array('all' => t('Both translated and untranslated
strings'), 'translated' => t('Only translated strings'), 'untranslated' =>
t('Only untranslated strings')),
568
  );
5698
  $groups = module_invoke_all('locale', 'groups');
5708
  $form['search']['group'] = array('#type' => 'radios',
5718
    '#title' => t('Limit search to'),
5728
    '#default_value' => (!empty($query['group']) ? $query['group'] :
'all'),
5738
    '#options' => array_merge(array('all' => t('All text groups')),
$groups),
574
  );
575
5768
  $form['search']['submit'] = array('#type' => 'submit', '#value' =>
t('Search'));
5778
  $form['#redirect'] = FALSE;
578
5798
  return $form;
5800
}
581
/**
582
 * @} End of "locale-translate-seek"
583
 */
584
585
/**
586
 * @defgroup locale-translate-import Translation import screen.
587
 * @{
588
 */
589
590
/**
591
 * User interface for the translation import screen.
592
 */
59330
function locale_translate_import_form() {
594
  // Get all languages, except English
5950
  $names = locale_language_list('name', TRUE);
5960
  unset($names['en']);
597
5980
  if (!count($names)) {
5990
    $languages = _locale_prepare_predefined_list();
6000
    $default = array_shift(array_keys($languages));
6010
  }
602
  else {
603
    $languages = array(
6040
      t('Already added languages') => $names,
6050
      t('Languages not yet added') => _locale_prepare_predefined_list()
6060
    );
6070
    $default = array_shift(array_keys($names));
608
  }
609
6100
  $form = array();
6110
  $form['import'] = array('#type' => 'fieldset',
6120
    '#title' => t('Import translation'),
613
  );
6140
  $form['import']['file'] = array('#type' => 'file',
6150
    '#title' => t('Language file'),
6160
    '#size' => 50,
6170
    '#description' => t('A Gettext Portable Object (<em>.po</em>) file.'),
618
  );
6190
  $form['import']['langcode'] = array('#type' => 'select',
6200
    '#title' => t('Import into'),
6210
    '#options' => $languages,
6220
    '#default_value' => $default,
6230
    '#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.'),
624
  );
6250
  $form['import']['group'] = array('#type' => 'radios',
6260
    '#title' => t('Text group'),
6270
    '#default_value' => 'default',
6280
    '#options' => module_invoke_all('locale', 'groups'),
6290
    '#description' => t('Imported translations will be added to this text
group.'),
630
  );
6310
  $form['import']['mode'] = array('#type' => 'radios',
6320
    '#title' => t('Mode'),
6330
    '#default_value' => LOCALE_IMPORT_KEEP,
634
    '#options' => array(
6350
      LOCALE_IMPORT_OVERWRITE => t('Strings in the uploaded file replace
existing ones, new ones are added'),
6360
      LOCALE_IMPORT_KEEP => t('Existing strings are kept, only new strings
are added')
6370
    ),
638
  );
6390
  $form['import']['submit'] = array('#type' => 'submit', '#value' =>
t('Import'));
6400
  $form['#attributes']['enctype'] = 'multipart/form-data';
641
6420
  return $form;
6430
}
644
645
/**
646
 * Process the locale import form submission.
647
 */
64830
function locale_translate_import_form_submit($form, &$form_state) {
649
  // Ensure we have the file uploaded
6500
  if ($file = file_save_upload('file')) {
651
652
    // Add language, if not yet supported
6530
    $languages = language_list('language', TRUE);
6540
    $langcode = $form_state['values']['langcode'];
6550
    if (!isset($languages[$langcode])) {
6560
      $predefined = _locale_get_predefined_list();
6570
      locale_add_language($langcode);
6580
      drupal_set_message(t('The language %language has been created.',
array('%language' => t($predefined[$langcode][0]))));
6590
    }
660
661
    // Now import strings into the language
6620
    if ($ret = _locale_import_po($file, $langcode,
$form_state['values']['mode'], $form_state['values']['group']) == FALSE) {
6630
      $variables = array('%filename' => $file->filename);
6640
      drupal_set_message(t('The translation import of %filename failed.',
$variables), 'error');
6650
      watchdog('locale', 'The translation import of %filename failed.',
$variables, WATCHDOG_ERROR);
6660
    }
6670
  }
668
  else {
6690
    drupal_set_message(t('File to import not found.'), 'error');
6700
    return 'admin/build/translate/import';
671
  }
672
6730
  $form_state['redirect'] = 'admin/build/translate';
6740
  return;
6750
}
676
/**
677
 * @} End of "locale-translate-import"
678
 */
679
680
/**
681
 * @defgroup locale-translate-export Translation export screen.
682
 * @{
683
 */
684
685
/**
686
 * User interface for the translation export screen.
687
 */
68830
function locale_translate_export_screen() {
689
  // Get all languages, except English
6900
  $names = locale_language_list('name', TRUE);
6910
  unset($names['en']);
6920
  $output = '';
693
  // Offer translation export if any language is set up.
6940
  if (count($names)) {
6950
    $output = drupal_get_form('locale_translate_export_po_form', $names);
6960
  }
6970
  $output .= drupal_get_form('locale_translate_export_pot_form');
6980
  return $output;
6990
}
700
701
/**
702
 * Form to export PO files for the languages provided.
703
 *
704
 * @param $names
705
 *   An associate array with localized language names
706
 */
70730
function locale_translate_export_po_form(&$form_state, $names) {
7080
  $form['export'] = array('#type' => 'fieldset',
7090
    '#title' => t('Export translation'),
7100
    '#collapsible' => TRUE,
711
  );
7120
  $form['export']['langcode'] = array('#type' => 'select',
7130
    '#title' => t('Language name'),
7140
    '#options' => $names,
7150
    '#description' => t('Select the language to export in Gettext Portable
Object (<em>.po</em>) format.'),
716
  );
7170
  $form['export']['group'] = array('#type' => 'radios',
7180
    '#title' => t('Text group'),
7190
    '#default_value' => 'default',
7200
    '#options' => module_invoke_all('locale', 'groups'),
721
  );
7220
  $form['export']['submit'] = array('#type' => 'submit', '#value' =>
t('Export'));
7230
  return $form;
7240
}
725
726
/**
727
 * Translation template export form.
728
 */
72930
function locale_translate_export_pot_form() {
730
  // Complete template export of the strings
7310
  $form['export'] = array('#type' => 'fieldset',
7320
    '#title' => t('Export template'),
7330
    '#collapsible' => TRUE,
7340
    '#description' => t('Generate a Gettext Portable Object Template
(<em>.pot</em>) file with all strings from the Drupal locale database.'),
735
  );
7360
  $form['export']['group'] = array('#type' => 'radios',
7370
    '#title' => t('Text group'),
7380
    '#default_value' => 'default',
7390
    '#options' => module_invoke_all('locale', 'groups'),
740
  );
7410
  $form['export']['submit'] = array('#type' => 'submit', '#value' =>
t('Export'));
742
  // Reuse PO export submission callback.
7430
  $form['#submit'][] = 'locale_translate_export_po_form_submit';
7440
  $form['#validate'][] = 'locale_translate_export_po_form_validate';
7450
  return $form;
7460
}
747
748
/**
749
 * Process a translation (or template) export form submission.
750
 */
75130
function locale_translate_export_po_form_submit($form, &$form_state) {
752
  // If template is required, language code is not given.
7530
  $language = NULL;
7540
  if (isset($form_state['values']['langcode'])) {
7550
    $languages = language_list();
7560
    $language = $languages[$form_state['values']['langcode']];
7570
  }
7580
  _locale_export_po($language, _locale_export_po_generate($language,
_locale_export_get_strings($language, $form_state['values']['group'])));
7590
}
760
/**
761
 * @} End of "locale-translate-export"
762
 */
763
764
/**
765
 * @defgroup locale-translate-edit Translation text editing
766
 * @{
767
 */
768
769
/**
770
 * User interface for string editing.
771
 */
77230
function locale_translate_edit_form(&$form_state, $lid) {
773
  // Fetch source string, if possible.
7742
  $source = db_fetch_object(db_query('SELECT source, textgroup, location
FROM {locales_source} WHERE lid = %d', $lid));
7752
  if (!$source) {
7760
    drupal_set_message(t('String not found.'), 'error');
7770
    drupal_goto('admin/build/translate/search');
7780
  }
779
780
  // Add original text to the top and some values for form altering.
781
  $form = array(
782
    'original' => array(
7832
      '#type'  => 'item',
7842
      '#title' => t('Original text'),
7852
      '#markup' => check_plain(wordwrap($source->source, 0)),
7862
    ),
787
    'lid' => array(
7882
      '#type'  => 'value',
789
      '#value' => $lid
7902
    ),
791
    'textgroup' => array(
7922
      '#type'  => 'value',
7932
      '#value' => $source->textgroup,
7942
    ),
795
    'location' => array(
7962
      '#type'  => 'value',
7972
      '#value' => $source->location
7982
    ),
7992
  );
800
801
  // Include default form controls with empty values for all languages.
802
  // This ensures that the languages are always in the same order in
forms.
8032
  $languages = language_list();
8042
  $default = language_default();
805
  // We don't need the default language value, that value is in $source.
8062
  $omit = $source->textgroup == 'default' ? 'en' : $default->language;
8072
  unset($languages[($omit)]);
8082
  $form['translations'] = array('#tree' => TRUE);
809
  // Approximate the number of rows to use in the default textarea.
8102
  $rows = min(ceil(str_word_count($source->source) / 12), 10);
8112
  foreach ($languages as $langcode => $language) {
8122
    $form['translations'][$langcode] = array(
8132
      '#type' => 'textarea',
8142
      '#title' => t($language->name),
8152
      '#rows' => $rows,
8162
      '#default_value' => '',
817
    );
8182
  }
819
820
  // Fetch translations and fill in default values in the form.
8212
  $result = db_query("SELECT DISTINCT translation, language FROM
{locales_target} WHERE lid = %d AND language <> '%s'", $lid, $omit);
8222
  while ($translation = db_fetch_object($result)) {
8230
    $form['translations'][$translation->language]['#default_value'] =
$translation->translation;
8240
  }
825
8262
  $form['submit'] = array('#type' => 'submit', '#value' => t('Save
translations'));
8272
  return $form;
8280
}
829
830
/**
831
 * Process string editing form submissions.
832
 * Saves all translations of one string submitted from a form.
833
 */
83430
function locale_translate_edit_form_submit($form, &$form_state) {
8351
  $lid = $form_state['values']['lid'];
8361
  foreach ($form_state['values']['translations'] as $key => $value) {
8371
    $translation = db_result(db_query("SELECT translation FROM
{locales_target} WHERE lid = %d AND language = '%s'", $lid, $key));
8381
    if (!empty($value)) {
839
      // Only update or insert if we have a value to use.
8401
      if (!empty($translation)) {
8410
        db_query("UPDATE {locales_target} SET translation = '%s' WHERE lid
= %d AND language = '%s'", $value, $lid, $key);
8420
      }
843
      else {
8441
        db_query("INSERT INTO {locales_target} (lid, translation, language)
VALUES (%d, '%s', '%s')", $lid, $value, $key);
845
      }
8461
    }
8470
    elseif (!empty($translation)) {
848
      // Empty translation entered: remove existing entry from database.
8490
      db_query("DELETE FROM {locales_target} WHERE lid = %d AND language =
'%s'", $lid, $key);
8500
    }
851
852
    // Force JavaScript translation file recreation for this language.
8531
    _locale_invalidate_js($key);
8541
  }
855
8561
  drupal_set_message(t('The string has been saved.'));
857
858
  // Clear locale cache.
8591
  cache_clear_all('locale:', 'cache', TRUE);
860
8611
  $form_state['redirect'] = 'admin/build/translate/search';
8621
  return;
8630
}
864
/**
865
 * @} End of "locale-translate-edit"
866
 */
867
868
/**
869
 * @defgroup locale-translate-delete Translation delete interface.
870
 * @{
871
 */
872
873
/**
874
 * String deletion confirmation page.
875
 */
87630
function locale_translate_delete_page($lid) {
8772
  if ($source = db_fetch_object(db_query('SELECT * FROM {locales_source}
WHERE lid = %d', $lid))) {
8782
    return drupal_get_form('locale_translate_delete_form', $source);
8790
  }
880
  else {
8810
    return drupal_not_found();
882
  }
8830
}
884
885
/**
886
 * User interface for the string deletion confirmation screen.
887
 */
88830
function locale_translate_delete_form(&$form_state, $source) {
8892
  $form['lid'] = array('#type' => 'value', '#value' => $source->lid);
8902
  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'));
8910
}
892
893
/**
894
 * Process string deletion submissions.
895
 */
89630
function locale_translate_delete_form_submit($form, &$form_state) {
8971
  db_query('DELETE FROM {locales_source} WHERE lid = %d',
$form_state['values']['lid']);
8981
  db_query('DELETE FROM {locales_target} WHERE lid = %d',
$form_state['values']['lid']);
899
  // Force JavaScript translation file recreation for all languages.
9001
  _locale_invalidate_js();
9011
  cache_clear_all('locale:', 'cache', TRUE);
9021
  drupal_set_message(t('The string has been removed.'));
9031
  $form_state['redirect'] = 'admin/build/translate/search';
9041
}
905
/**
906
 * @} End of "locale-translate-delete"
907
 */
908
909
/**
910
 * @defgroup locale-api-add Language addition API.
911
 * @{
912
 */
913
914
/**
915
 * API function to add a language.
916
 *
917
 * @param $langcode
918
 *   Language code.
919
 * @param $name
920
 *   English name of the language
921
 * @param $native
922
 *   Native name of the language
923
 * @param $direction
924
 *   LANGUAGE_LTR or LANGUAGE_RTL
925
 * @param $domain
926
 *   Optional custom domain name with protocol, without
927
 *   trailing slash (eg. http://de.example.com).
928
 * @param $prefix
929
 *   Optional path prefix for the language. Defaults to the
930
 *   language code if omitted.
931
 * @param $enabled
932
 *   Optionally TRUE to enable the language when created or FALSE to
disable.
933
 * @param $default
934
 *   Optionally set this language to be the default.
935
 */
93630
function locale_add_language($langcode, $name = NULL, $native = NULL,
$direction = LANGUAGE_LTR, $domain = '', $prefix = '', $enabled = TRUE,
$default = FALSE) {
937
  // Default prefix on language code.
9382
  if (empty($prefix)) {
9391
    $prefix = $langcode;
9401
  }
941
942
  // If name was not set, we add a predefined language.
9432
  if (!isset($name)) {
9441
    $predefined = _locale_get_predefined_list();
9451
    $name = $predefined[$langcode][0];
9461
    $native = isset($predefined[$langcode][1]) ? $predefined[$langcode][1]
: $predefined[$langcode][0];
9471
    $direction = isset($predefined[$langcode][2]) ?
$predefined[$langcode][2] : LANGUAGE_LTR;
9481
  }
949
9502
  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);
951
952
  // Only set it as default if enabled.
9532
  if ($enabled && $default) {
9540
    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' => ''));
9550
  }
956
9572
  if ($enabled) {
958
    // Increment enabled language count if we are adding an enabled
language.
9592
    variable_set('language_count', variable_get('language_count', 1) + 1);
9602
  }
961
962
  // Force JavaScript translation file creation for the newly added
language.
9632
  _locale_invalidate_js($langcode);
964
9652
  watchdog('locale', 'The %language language (%code) has been created.',
array('%language' => $name, '%code' => $langcode));
9662
}
967
/**
968
 * @} End of "locale-api-add"
969
 */
970
971
/**
972
 * @defgroup locale-api-import Translation import API.
973
 * @{
974
 */
975
976
/**
977
 * Parses Gettext Portable Object file information and inserts into
database
978
 *
979
 * @param $file
980
 *   Drupal file object corresponding to the PO file to import
981
 * @param $langcode
982
 *   Language code
983
 * @param $mode
984
 *   Should existing translations be replaced LOCALE_IMPORT_KEEP or
LOCALE_IMPORT_OVERWRITE
985
 * @param $group
986
 *   Text group to import PO file into (eg. 'default' for interface
translations)
987
 */
98830
function _locale_import_po($file, $langcode, $mode, $group = NULL) {
989
  // If not in 'safe mode', increase the maximum execution time.
9900
  if (!ini_get('safe_mode')) {
9910
    set_time_limit(240);
9920
  }
993
994
  // Check if we have the language already in the database.
9950
  if (!db_fetch_object(db_query("SELECT language FROM {languages} WHERE
language = '%s'", $langcode))) {
9960
    drupal_set_message(t('The language selected for import is not
supported.'), 'error');
9970
    return FALSE;
9980
  }
999
1000
  // Get strings from file (returns on failure after a partial import, or
on success)
10010
  $status = _locale_import_read_po('db-store', $file, $mode, $langcode,
$group);
10020
  if ($status === FALSE) {
1003
    // Error messages are set in _locale_import_read_po().
10040
    return FALSE;
10050
  }
1006
1007
  // Get status information on import process.
10080
  list($headerdone, $additions, $updates, $deletes) =
_locale_import_one_string('db-report');
1009
10100
  if (!$headerdone) {
10110
    drupal_set_message(t('The translation file %filename appears to have a
missing or malformed header.', array('%filename' => $file->filename)),
'error');
10120
  }
1013
1014
  // Clear cache and force refresh of JavaScript translations.
10150
  _locale_invalidate_js($langcode);
10160
  cache_clear_all('locale:', 'cache', TRUE);
1017
1018
  // Rebuild the menu, strings may have changed.
10190
  menu_rebuild();
1020
10210
  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)));
10220
  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));
10230
  return TRUE;
10240
}
1025
1026
/**
1027
 * Parses Gettext Portable Object file into an array
1028
 *
1029
 * @param $op
1030
 *   Storage operation type: db-store or mem-store
1031
 * @param $file
1032
 *   Drupal file object corresponding to the PO file to import
1033
 * @param $mode
1034
 *   Should existing translations be replaced LOCALE_IMPORT_KEEP or
LOCALE_IMPORT_OVERWRITE
1035
 * @param $lang
1036
 *   Language code
1037
 * @param $group
1038
 *   Text group to import PO file into (eg. 'default' for interface
translations)
1039
 */
104030
function _locale_import_read_po($op, $file, $mode = NULL, $lang = NULL,
$group = 'default') {
1041
10420
  $fd = fopen($file->filepath, "rb"); // File will get closed by PHP on
return
10430
  if (!$fd) {
10440
    _locale_import_message('The translation import failed, because the file
%filename could not be read.', $file);
10450
    return FALSE;
10460
  }
1047
10480
  $context = "COMMENT"; // Parser context: COMMENT, MSGID, MSGID_PLURAL,
MSGSTR and MSGSTR_ARR
10490
  $current = array();   // Current entry being read
10500
  $plural = 0;          // Current plural form
10510
  $lineno = 0;          // Current line
1052
10530
  while (!feof($fd)) {
10540
    $line = fgets($fd, 10*1024); // A line should not be this long
10550
    if ($lineno == 0) {
1056
      // The first line might come with a UTF-8 BOM, which should be
removed.
10570
      $line = str_replace("\xEF\xBB\xBF", '', $line);
10580
    }
10590
    $lineno++;
10600
    $line = trim(strtr($line, array("\\\n" => "")));
1061
10620
    if (!strncmp("#", $line, 1)) { // A comment
10630
      if ($context == "COMMENT") { // Already in comment context: add
10640
        $current["#"][] = substr($line, 1);
10650
      }
10660
      elseif (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) { //
End current entry, start a new one
10670
        _locale_import_one_string($op, $current, $mode, $lang, $file,
$group);
10680
        $current = array();
10690
        $current["#"][] = substr($line, 1);
10700
        $context = "COMMENT";
10710
      }
1072
      else { // Parse error
10730
        _locale_import_message('The translation file %filename contains an
error: "msgstr" was expected but not found on line %line.', $file,
$lineno);
10740
        return FALSE;
1075
      }
10760
    }
10770
    elseif (!strncmp("msgid_plural", $line, 12)) {
10780
      if ($context != "MSGID") { // Must be plural form for current entry
10790
        _locale_import_message('The translation file %filename contains an
error: "msgid_plural" was expected but not found on line %line.', $file,
$lineno);
10800
        return FALSE;
10810
      }
10820
      $line = trim(substr($line, 12));
10830
      $quoted = _locale_import_parse_quoted($line);
10840
      if ($quoted === FALSE) {
10850
        _locale_import_message('The translation file %filename contains a
syntax error on line %line.', $file, $lineno);
10860
        return FALSE;
10870
      }
10880
      $current["msgid"] = $current["msgid"] . "\0" . $quoted;
10890
      $context = "MSGID_PLURAL";
10900
    }
10910
    elseif (!strncmp("msgid", $line, 5)) {
10920
      if ($context == "MSGSTR") {   // End current entry, start a new one
10930
        _locale_import_one_string($op, $current, $mode, $lang, $file,
$group);
10940
        $current = array();
10950
      }
10960
      elseif ($context == "MSGID") { // Already in this context? Parse
error
10970
        _locale_import_message('The translation file %filename contains an
error: "msgid" is unexpected on line %line.', $file, $lineno);
10980
        return FALSE;
10990
      }
11000
      $line = trim(substr($line, 5));
11010
      $quoted = _locale_import_parse_quoted($line);
11020
      if ($quoted === FALSE) {
11030
        _locale_import_message('The translation file %filename contains a
syntax error on line %line.', $file, $lineno);
11040
        return FALSE;
11050
      }
11060
      $current["msgid"] = $quoted;
11070
      $context = "MSGID";
11080
    }
11090
    elseif (!strncmp("msgstr[", $line, 7)) {
11100
      if (($context != "MSGID") && ($context != "MSGID_PLURAL") &&
($context != "MSGSTR_ARR")) { // Must come after msgid, msgid_plural, or
msgstr[]
11110
        _locale_import_message('The translation file %filename contains an
error: "msgstr[]" is unexpected on line %line.', $file, $lineno);
11120
        return FALSE;
11130
      }
11140
      if (strpos($line, "]") === FALSE) {
11150
        _locale_import_message('The translation file %filename contains a
syntax error on line %line.', $file, $lineno);
11160
        return FALSE;
11170
      }
11180
      $frombracket = strstr($line, "[");
11190
      $plural = substr($frombracket, 1, strpos($frombracket, "]") - 1);
11200
      $line = trim(strstr($line, " "));
11210
      $quoted = _locale_import_parse_quoted($line);
11220
      if ($quoted === FALSE) {
11230
        _locale_import_message('The translation file %filename contains a
syntax error on line %line.', $file, $lineno);
11240
        return FALSE;
11250
      }
11260
      $current["msgstr"][$plural] = $quoted;
11270
      $context = "MSGSTR_ARR";
11280
    }
11290
    elseif (!strncmp("msgstr", $line, 6)) {
11300
      if ($context != "MSGID") {   // Should come just after a msgid block
11310
        _locale_import_message('The translation file %filename contains an
error: "msgstr" is unexpected on line %line.', $file, $lineno);
11320
        return FALSE;
11330
      }
11340
      $line = trim(substr($line, 6));
11350
      $quoted = _locale_import_parse_quoted($line);
11360
      if ($quoted === FALSE) {
11370
        _locale_import_message('The translation file %filename contains a
syntax error on line %line.', $file, $lineno);
11380
        return FALSE;
11390
      }
11400
      $current["msgstr"] = $quoted;
11410
      $context = "MSGSTR";
11420
    }
11430
    elseif ($line != "") {
11440
      $quoted = _locale_import_parse_quoted($line);
11450
      if ($quoted === FALSE) {
11460
        _locale_import_message('The translation file %filename contains a
syntax error on line %line.', $file, $lineno);
11470
        return FALSE;
11480
      }
11490
      if (($context == "MSGID") || ($context == "MSGID_PLURAL")) {
11500
        $current["msgid"] .= $quoted;
11510
      }
11520
      elseif ($context == "MSGSTR") {
11530
        $current["msgstr"] .= $quoted;
11540
      }
11550
      elseif ($context == "MSGSTR_ARR") {
11560
        $current["msgstr"][$plural] .= $quoted;
11570
      }
1158
      else {
11590
        _locale_import_message('The translation file %filename contains an
error: there is an unexpected string on line %line.', $file, $lineno);
11600
        return FALSE;
1161
      }
11620
    }
11630
  }
1164
1165
  // End of PO file, flush last entry
11660
  if (($context == "MSGSTR") || ($context == "MSGSTR_ARR")) {
11670
    _locale_import_one_string($op, $current, $mode, $lang, $file, $group);
11680
  }
11690
  elseif ($context != "COMMENT") {
11700
    _locale_import_message('The translation file %filename ended
unexpectedly at line %line.', $file, $lineno);
11710
    return FALSE;
11720
  }
1173
11740
}
1175
1176
/**
1177
 * Sets an error message occurred during locale file parsing.
1178
 *
1179
 * @param $message
1180
 *   The message to be translated
1181
 * @param $file
1182
 *   Drupal file object corresponding to the PO file to import
1183
 * @param $lineno
1184
 *   An optional line number argument
1185
 */
118630
function _locale_import_message($message, $file, $lineno = NULL) {
11870
  $vars = array('%filename' => $file->filename);
11880
  if (isset($lineno)) {
11890
    $vars['%line'] = $lineno;
11900
  }
11910
  $t = get_t();
11920
  drupal_set_message($t($message, $vars), 'error');
11930
}
1194
1195
/**
1196
 * Imports a string into the database
1197
 *
1198
 * @param $op
1199
 *   Operation to perform: 'db-store', 'db-report', 'mem-store' or
'mem-report'
1200
 * @param $value
1201
 *   Details of the string stored
1202
 * @param $mode
1203
 *   Should existing translations be replaced LOCALE_IMPORT_KEEP or
LOCALE_IMPORT_OVERWRITE
1204
 * @param $lang
1205
 *   Language to store the string in
1206
 * @param $file
1207
 *   Object representation of file being imported, only required when op is
'db-store'
1208
 * @param $group
1209
 *   Text group to import PO file into (eg. 'default' for interface
translations)
1210
 */
121130
function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang
= NULL, $file = NULL, $group = 'default') {
12120
  static $report = array(0, 0, 0);
12130
  static $headerdone = FALSE;
12140
  static $strings = array();
1215
1216
  switch ($op) {
1217
    // Return stored strings
12180
    case 'mem-report':
12190
      return $strings;
1220
1221
    // Store string in memory (only supports single strings)
12220
    case 'mem-store':
12230
      $strings[$value['msgid']] = $value['msgstr'];
12240
      return;
1225
1226
    // Called at end of import to inform the user
12270
    case 'db-report':
12280
      return array($headerdone, $report[0], $report[1], $report[2]);
1229
1230
    // Store the string we got in the database.
12310
    case 'db-store':
1232
      // We got header information.
12330
      if ($value['msgid'] == '') {
12340
        $header = _locale_import_parse_header($value['msgstr']);
1235
1236
        // Get the plural formula and update in database.
12370
        if (isset($header["Plural-Forms"]) && $p =
_locale_import_parse_plural_forms($header["Plural-Forms"],
$file->filename)) {
12380
          list($nplurals, $plural) = $p;
12390
          db_query("UPDATE {languages} SET plurals = %d, formula = '%s'
WHERE language = '%s'", $nplurals, $plural, $lang);
12400
        }
1241
        else {
12420
          db_query("UPDATE {languages} SET plurals = %d, formula = '%s'
WHERE language = '%s'", 0, '', $lang);
1243
        }
12440
        $headerdone = TRUE;
12450
      }
1246
1247
      else {
1248
        // Some real string to import.
12490
        $comments = _locale_import_shorten_comments(empty($value['#']) ?
array() : $value['#']);
1250
12510
        if (strpos($value['msgid'], "\0")) {
1252
          // This string has plural versions.
12530
          $english = explode("\0", $value['msgid'], 2);
12540
          $entries = array_keys($value['msgstr']);
12550
          for ($i = 3; $i <= count($entries); $i++) {
12560
            $english[] = $english[1];
12570
          }
12580
          $translation = array_map('_locale_import_append_plural',
$value['msgstr'], $entries);
12590
          $english = array_map('_locale_import_append_plural', $english,
$entries);
12600
          foreach ($translation as $key => $trans) {
12610
            if ($key == 0) {
12620
              $plid = 0;
12630
            }
12640
            $plid = _locale_import_one_string_db($report, $lang,
$english[$key], $trans, $group, $comments, $mode, $plid, $key);
12650
          }
12660
        }
1267
1268
        else {
1269
          // A simple string to import.
12700
          $english = $value['msgid'];
12710
          $translation = $value['msgstr'];
12720
          _locale_import_one_string_db($report, $lang, $english,
$translation, $group, $comments, $mode);
1273
        }
1274
      }
12750
  } // end of db-store operation
12760
}
1277
1278
/**
1279
 * Import one string into the database.
1280
 *
1281
 * @param $report
1282
 *   Report array summarizing the number of changes done in the form:
1283
 *   array(inserts, updates, deletes).
1284
 * @param $langcode
1285
 *   Language code to import string into.
1286
 * @param $source
1287
 *   Source string.
1288
 * @param $translation
1289
 *   Translation to language specified in $langcode.
1290
 * @param $textgroup
1291
 *   Name of textgroup to store translation in.
1292
 * @param $location
1293
 *   Location value to save with source string.
1294
 * @param $mode
1295
 *   Import mode to use, LOCALE_IMPORT_KEEP or LOCALE_IMPORT_OVERWRITE.
1296
 * @param $plid
1297
 *   Optional plural ID to use.
1298
 * @param $plural
1299
 *   Optional plural value to use.
1300
 * @return
1301
 *   The string ID of the existing string modified or the new string
added.
1302
 */
130330
function _locale_import_one_string_db(&$report, $langcode, $source,
$translation, $textgroup, $location, $mode, $plid = NULL, $plural = NULL)
{
13040
  $lid = db_result(db_query("SELECT lid FROM {locales_source} WHERE source
= '%s' AND textgroup = '%s'", $source, $textgroup));
1305
13060
  if (!empty($translation)) {
13070
    if ($lid) {
1308
      // We have this source string saved already.
13090
      db_query("UPDATE {locales_source} SET location = '%s' WHERE lid =
%d", $location, $lid);
13100
      $exists = (bool) db_result(db_query("SELECT lid FROM {locales_target}
WHERE lid = %d AND language = '%s'", $lid, $langcode));
13110
      if (!$exists) {
1312
        // No translation in this language.
13130
        db_query("INSERT INTO {locales_target} (lid, language, translation,
plid, plural) VALUES (%d, '%s', '%s', %d, %d)", $lid, $langcode,
$translation, $plid, $plural);
13140
        $report[0]++;
13150
      }
13160
      else if ($mode == LOCALE_IMPORT_OVERWRITE) {
1317
        // Translation exists, only overwrite if instructed.
13180
        db_query("UPDATE {locales_target} SET translation = '%s', plid =
%d, plural = %d WHERE language = '%s' AND lid = %d", $translation, $plid,
$plural, $langcode, $lid);
13190
        $report[1]++;
13200
      }
13210
    }
1322
    else {
1323
      // No such source string in the database yet.
13240
      db_query("INSERT INTO {locales_source} (location, source, textgroup)
VALUES ('%s', '%s', '%s')", $location, $source, $textgroup);
13250
      $lid = db_result(db_query("SELECT lid FROM {locales_source} WHERE
source = '%s' AND textgroup = '%s'", $source, $textgroup));
13260
      db_query("INSERT INTO {locales_target} (lid, language, translation,
plid, plural) VALUES (%d, '%s', '%s', %d, %d)", $lid, $langcode,
$translation, $plid, $plural);
13270
      $report[0]++;
1328
    }
13290
  }
13300
  elseif ($mode == LOCALE_IMPORT_OVERWRITE) {
1331
    // Empty translation, remove existing if instructed.
13320
    db_query("DELETE FROM {locales_target} WHERE language = '%s' AND lid =
%d AND plid = %d AND plural = %d", $translation, $langcode, $lid, $plid,
$plural);
13330
    $report[2]++;
13340
  }
1335
13360
  return $lid;
13370
}
1338
1339
/**
1340
 * Parses a Gettext Portable Object file header
1341
 *
1342
 * @param $header
1343
 *   A string containing the complete header
1344
 * @return
1345
 *   An associative array of key-value pairs
1346
 */
134730
function _locale_import_parse_header($header) {
13480
  $header_parsed = array();
13490
  $lines = array_map('trim', explode("\n", $header));
13500
  foreach ($lines as $line) {
13510
    if ($line) {
13520
      list($tag, $contents) = explode(":", $line, 2);
13530
      $header_parsed[trim($tag)] = trim($contents);
13540
    }
13550
  }
13560
  return $header_parsed;
13570
}
1358
1359
/**
1360
 * Parses a Plural-Forms entry from a Gettext Portable Object file header
1361
 *
1362
 * @param $pluralforms
1363
 *   A string containing the Plural-Forms entry
1364
 * @param $filename
1365
 *   A string containing the filename
1366
 * @return
1367
 *   An array containing the number of plurals and a
1368
 *   formula in PHP for computing the plural form
1369
 */
137030
function _locale_import_parse_plural_forms($pluralforms, $filename) {
1371
  // First, delete all whitespace
13720
  $pluralforms = strtr($pluralforms, array(" " => "", "\t" => ""));
1373
1374
  // Select the parts that define nplurals and plural
13750
  $nplurals = strstr($pluralforms, "nplurals=");
13760
  if (strpos($nplurals, ";")) {
13770
    $nplurals = substr($nplurals, 9, strpos($nplurals, ";") - 9);
13780
  }
1379
  else {
13800
    return FALSE;
1381
  }
13820
  $plural = strstr($pluralforms, "plural=");
13830
  if (strpos($plural, ";")) {
13840
    $plural = substr($plural, 7, strpos($plural, ";") - 7);
13850
  }
1386
  else {
13870
    return FALSE;
1388
  }
1389
1390
  // Get PHP version of the plural formula
13910
  $plural = _locale_import_parse_arithmetic($plural);
1392
13930
  if ($plural !== FALSE) {
13940
    return array($nplurals, $plural);
13950
  }
1396
  else {
13970
    drupal_set_message(t('The translation file %filename contains an error:
the plural formula could not be parsed.', array('%filename' => $filename)),
'error');
13980
    return FALSE;
1399
  }
14000
}
1401
1402
/**
1403
 * Parses and sanitizes an arithmetic formula into a PHP expression
1404
 *
1405
 * While parsing, we ensure, that the operators have the right
1406
 * precedence and associativity.
1407
 *
1408
 * @param $string
1409
 *   A string containing the arithmetic formula
1410
 * @return
1411
 *   The PHP version of the formula
1412
 */
141330
function _locale_import_parse_arithmetic($string) {
1414
  // Operator precedence table
14150
  $prec = array("(" => -1, ")" => -1, "?" => 1, ":" => 1, "||" => 3, "&&"
=> 4, "==" => 5, "!=" => 5, "<" => 6, ">" => 6, "<=" => 6, ">=" => 6, "+"
=> 7, "-" => 7, "*" => 8, "/" => 8, "%" => 8);
1416
  // Right associativity
14170
  $rasc = array("?" => 1, ":" => 1);
1418
14190
  $tokens = _locale_import_tokenize_formula($string);
1420
1421
  // Parse by converting into infix notation then back into postfix
14220
  $opstk = array();
14230
  $elstk = array();
1424
14250
  foreach ($tokens as $token) {
14260
    $ctok = $token;
1427
1428
    // Numbers and the $n variable are simply pushed into $elarr
14290
    if (is_numeric($token)) {
14300
      $elstk[] = $ctok;
14310
    }
14320
    elseif ($ctok == "n") {
14330
      $elstk[] = '$n';
14340
    }
14350
    elseif ($ctok == "(") {
14360
      $opstk[] = $ctok;
14370
    }
14380
    elseif ($ctok == ")") {
14390
      $topop = array_pop($opstk);
14400
      while (isset($topop) && ($topop != "(")) {
14410
        $elstk[] = $topop;
14420
        $topop = array_pop($opstk);
14430
      }
14440
    }
14450
    elseif (!empty($prec[$ctok])) {
1446
      // If it's an operator, then pop from $oparr into $elarr until the
1447
      // precedence in $oparr is less than current, then push into $oparr
14480
      $topop = array_pop($opstk);
14490
      while (isset($topop) && ($prec[$topop] >= $prec[$ctok]) &&
!(($prec[$topop] == $prec[$ctok]) && !empty($rasc[$topop]) &&
!empty($rasc[$ctok]))) {
14500
        $elstk[] = $topop;
14510
        $topop = array_pop($opstk);
14520
      }
14530
      if ($topop) {
14540
        $opstk[] = $topop;   // Return element to top
14550
      }
14560
      $opstk[] = $ctok;      // Parentheses are not needed
14570
    }
1458
    else {
14590
      return FALSE;
1460
    }
14610
  }
1462
1463
  // Flush operator stack
14640
  $topop = array_pop($opstk);
14650
  while ($topop != NULL) {
14660
    $elstk[] = $topop;
14670
    $topop = array_pop($opstk);
14680
  }
1469
1470
  // Now extract formula from stack
14710
  $prevsize = count($elstk) + 1;
14720
  while (count($elstk) < $prevsize) {
14730
    $prevsize = count($elstk);
14740
    for ($i = 2; $i < count($elstk); $i++) {
14750
      $op = $elstk[$i];
14760
      if (!empty($prec[$op])) {
14770
        $f = "";
14780
        if ($op == ":") {
14790
          $f = $elstk[$i - 2] . "):" . $elstk[$i - 1] . ")";
14800
        }
14810
        elseif ($op == "?") {
14820
          $f = "(" . $elstk[$i - 2] . "?(" . $elstk[$i - 1];
14830
        }
1484
        else {
14850
          $f = "(" . $elstk[$i - 2] . $op . $elstk[$i - 1] . ")";
1486
        }
14870
        array_splice($elstk, $i - 2, 3, $f);
14880
        break;
14890
      }
14900
    }
14910
  }
1492
1493
  // If only one element is left, the number of operators is appropriate
14940
  if (count($elstk) == 1) {
14950
    return $elstk[0];
14960
  }
1497
  else {
14980
    return FALSE;
1499
  }
15000
}
1501
1502
/**
1503
 * Backward compatible implementation of token_get_all() for formula
parsing
1504
 *
1505
 * @param $string
1506
 *   A string containing the arithmetic formula
1507
 * @return
1508
 *   The PHP version of the formula
1509
 */
151030
function _locale_import_tokenize_formula($formula) {
15110
  $formula = str_replace(" ", "", $formula);
15120
  $tokens = array();
15130
  for ($i = 0; $i < strlen($formula); $i++) {
15140
    if (is_numeric($formula[$i])) {
15150
      $num = $formula[$i];
15160
      $j = $i + 1;
15170
      while ($j < strlen($formula) && is_numeric($formula[$j])) {
15180
        $num .= $formula[$j];
15190
        $j++;
15200
      }
15210
      $i = $j - 1;
15220
      $tokens[] = $num;
15230
    }
15240
    elseif ($pos = strpos(" =<>!&|", $formula[$i])) { // We won't have a
space
15250
      $next = $formula[$i + 1];
1526
      switch ($pos) {
15270
        case 1:
15280
        case 2:
15290
        case 3:
15300
        case 4:
15310
          if ($next == '=') {
15320
            $tokens[] = $formula[$i] . '=';
15330
            $i++;
15340
          }
1535
          else {
15360
            $tokens[] = $formula[$i];
1537
          }
15380
          break;
15390
        case 5:
15400
          if ($next == '&') {
15410
            $tokens[] = '&&';
15420
            $i++;
15430
          }
1544
          else {
15450
            $tokens[] = $formula[$i];
1546
          }
15470
          break;
15480
        case 6:
15490
          if ($next == '|') {
15500
            $tokens[] = '||';
15510
            $i++;
15520
          }
1553
          else {
15540
            $tokens[] = $formula[$i];
1555
          }
15560
          break;
15570
      }
15580
    }
1559
    else {
15600
      $tokens[] = $formula[$i];
1561
    }
15620
  }
15630
  return $tokens;
15640
}
1565
1566
/**
1567
 * Modify a string to contain proper count indices
1568
 *
1569
 * This is a callback function used via array_map()
1570
 *
1571
 * @param $entry
1572
 *   An array element
1573
 * @param $key
1574
 *   Index of the array element
1575
 */
157630
function _locale_import_append_plural($entry, $key) {
1577
  // No modifications for 0, 1
15780
  if ($key == 0 || $key == 1) {
15790
    return $entry;
15800
  }
1581
1582
  // First remove any possibly false indices, then add new ones
15830
  $entry = preg_replace('/(@count)\[[0-9]\]/', '\\1', $entry);
15840
  return preg_replace('/(@count)/', "\\1[$key]", $entry);
15850
}
1586
1587
/**
1588
 * Generate a short, one string version of the passed comment array
1589
 *
1590
 * @param $comment
1591
 *   An array of strings containing a comment
1592
 * @return
1593
 *   Short one string version of the comment
1594
 */
159530
function _locale_import_shorten_comments($comment) {
15960
  $comm = '';
15970
  while (count($comment)) {
15980
    $test = $comm . substr(array_shift($comment), 1) . ', ';
15990
    if (strlen($comm) < 130) {
16000
      $comm = $test;
16010
    }
1602
    else {
16030
      break;
1604
    }
16050
  }
16060
  return substr($comm, 0, -2);
16070
}
1608
1609
/**
1610
 * Parses a string in quotes
1611
 *
1612
 * @param $string
1613
 *   A string specified with enclosing quotes
1614
 * @return
1615
 *   The string parsed from inside the quotes
1616
 */
161730
function _locale_import_parse_quoted($string) {
16180
  if (substr($string, 0, 1) != substr($string, -1, 1)) {
16190
    return FALSE;   // Start and end quotes must be the same
16200
  }
16210
  $quote = substr($string, 0, 1);
16220
  $string = substr($string, 1, -1);
16230
  if ($quote == '"') {        // Double quotes: strip slashes
16240
    return stripcslashes($string);
16250
  }
16260
  elseif ($quote == "'") {  // Simple quote: return as-is
16270
    return $string;
16280
  }
1629
  else {
16300
    return FALSE;             // Unrecognized quote
1631
  }
16320
}
1633
/**
1634
 * @} End of "locale-api-import"
1635
 */
1636
1637
/**
1638
 * Parses a JavaScript file, extracts strings wrapped in Drupal.t() and
1639
 * Drupal.formatPlural() and inserts them into the database.
1640
 */
164130
function _locale_parse_js_file($filepath) {
16428
  global $language;
1643
1644
  // Load the JavaScript file.
16458
  $file = file_get_contents($filepath);
1646
1647
  // Match all calls to Drupal.t() in an array.
1648
  // Note: \s also matches newlines with the 's' modifier.
16498
  preg_match_all('~[^\w]Drupal\s*\.\s*t\s*\(\s*(' . LOCALE_JS_STRING .
')\s*[,\)]~s', $file, $t_matches);
1650
1651
  // Match all Drupal.formatPlural() calls in another array.
16528
  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);
1653
1654
  // Loop through all matches and process them.
16558
  $all_matches = array_merge($plural_matches[1], $t_matches[1]);
16568
  foreach ($all_matches as $key => $string) {
16574
    $strings = array($string);
1658
1659
    // If there is also a plural version of this string, add it to the
strings array.
16604
    if (isset($plural_matches[2][$key])) {
16610
      $strings[] = $plural_matches[2][$key];
16620
    }
1663
16644
    foreach ($strings as $key => $string) {
1665
      // Remove the quotes and string concatenations from the string.
16664
      $string = implode('', preg_split('~(?<!\\\\)[\'"]\s*\+\s*[\'"]~s',
substr($string, 1, -1)));
1667
16684
      $result = db_query("SELECT lid, location FROM {locales_source} WHERE
source = '%s' AND textgroup = 'default'", $string);
16694
      if ($source = db_fetch_object($result)) {
1670
        // We already have this source string and now have to add the
location
1671
        // to the location column, if this file is not yet present in
there.
16721
        $locations = preg_split('~\s*;\s*~', $source->location);
1673
16741
        if (!in_array($filepath, $locations)) {
16750
          $locations[] = $filepath;
16760
          $locations = implode('; ', $locations);
1677
1678
          // Save the new locations string to the database.
16790
          db_query("UPDATE {locales_source} SET location = '%s' WHERE lid =
%d", $locations, $source->lid);
16800
        }
16811
      }
1682
      else {
1683
        // We don't have the source string yet, thus we insert it into the
database.
16844
        db_query("INSERT INTO {locales_source} (location, source,
textgroup) VALUES ('%s', '%s', 'default')", $filepath, $string);
1685
      }
16864
    }
16874
  }
16888
}
1689
1690
/**
1691
 * @defgroup locale-api-export Translation (template) export API.
1692
 * @{
1693
 */
1694
1695
/**
1696
 * Generates a structured array of all strings with translations in
1697
 * $language, if given. This array can be used to generate an export
1698
 * of the string in the database.
1699
 *
1700
 * @param $language
1701
 *   Language object to generate the output for, or NULL if generating
1702
 *   translation template.
1703
 * @param $group
1704
 *   Text group to export PO file from (eg. 'default' for interface
translations)
1705
 */
170630
function _locale_export_get_strings($language = NULL, $group = 'default')
{
17070
  if (isset($language)) {
17080
    $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);
17090
  }
1710
  else {
17110
    $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);
1712
  }
17130
  $strings = array();
17140
  while ($child = db_fetch_object($result)) {
1715
    $string = array(
17160
      'comment'     => $child->location,
17170
      'source'      => $child->source,
17180
      'translation' => isset($child->translation) ? $child->translation :
''
17190
    );
17200
    if ($child->plid) {
1721
      // Has a parent lid. Since we process in the order of plids,
1722
      // we already have the parent in the array, so we can add the
1723
      // lid to the next plural version to it. This builds a linked
1724
      // list of plurals.
17250
      $string['child'] = TRUE;
17260
      $strings[$child->plid]['plural'] = $child->lid;
17270
    }
17280
    $strings[$child->lid] = $string;
17290
  }
17300
  return $strings;
17310
}
1732
1733
/**
1734
 * Generates the PO(T) file contents for given strings.
1735
 *
1736
 * @param $language
1737
 *   Language object to generate the output for, or NULL if generating
1738
 *   translation template.
1739
 * @param $strings
1740
 *   Array of strings to export. See _locale_export_get_strings()
1741
 *   on how it should be formatted.
1742
 * @param $header
1743
 *   The header portion to use for the output file. Defaults
1744
 *   are provided for PO and POT files.
1745
 */
174630
function _locale_export_po_generate($language = NULL, $strings = array(),
$header = NULL) {
17470
  global $user;
1748
17490
  if (!isset($header)) {
17500
    if (isset($language)) {
17510
      $header = '# ' . $language->name . ' translation of ' .
variable_get('site_name', 'Drupal') . "\n";
17520
      $header .= '# Generated by ' . $user->name . ' <' . $user->mail .
">\n";
17530
      $header .= "#\n";
17540
      $header .= "msgid \"\"\n";
17550
      $header .= "msgstr \"\"\n";
17560
      $header .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
17570
      $header .= "\"POT-Creation-Date: " . date("Y-m-d H:iO") . "\\n\"\n";
17580
      $header .= "\"PO-Revision-Date: " . date("Y-m-d H:iO") . "\\n\"\n";
17590
      $header .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n";
17600
      $header .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n";
17610
      $header .= "\"MIME-Version: 1.0\\n\"\n";
17620
      $header .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
17630
      $header .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
17640
      if ($language->formula && $language->plurals) {
17650
        $header .= "\"Plural-Forms: nplurals=" . $language->plurals . ";
plural=" . strtr($language->formula, array('$' => '')) . ";\\n\"\n";
17660
      }
17670
    }
1768
    else {
17690
      $header = "# LANGUAGE translation of PROJECT\n";
17700
      $header .= "# Copyright (c) YEAR NAME <EMAIL@ADDRESS>\n";
17710
      $header .= "#\n";
17720
      $header .= "msgid \"\"\n";
17730
      $header .= "msgstr \"\"\n";
17740
      $header .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
17750
      $header .= "\"POT-Creation-Date: " . date("Y-m-d H:iO") . "\\n\"\n";
17760
      $header .= "\"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\\n\"\n";
17770
      $header .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n";
17780
      $header .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n";
17790
      $header .= "\"MIME-Version: 1.0\\n\"\n";
17800
      $header .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
17810
      $header .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
17820
      $header .= "\"Plural-Forms: nplurals=INTEGER;
plural=EXPRESSION;\\n\"\n";
1783
    }
17840
  }
1785
17860
  $output = $header . "\n";
1787
17880
  foreach ($strings as $lid => $string) {
1789
    // Only process non-children, children are output below their parent.
17900
    if (!isset($string['child'])) {
17910
      if ($string['comment']) {
17920
        $output .= '#: ' . $string['comment'] . "\n";
17930
      }
17940
      $output .= 'msgid ' . _locale_export_string($string['source']);
17950
      if (!empty($string['plural'])) {
17960
        $plural = $string['plural'];
17970
        $output .= 'msgid_plural ' .
_locale_export_string($strings[$plural]['source']);
17980
        if (isset($language)) {
17990
          $translation = $string['translation'];
18000
          for ($i = 0; $i < $language->plurals; $i++) {
18010
            $output .= 'msgstr[' . $i . '] ' .
_locale_export_string($translation);
18020
            if ($plural) {
18030
              $translation =
_locale_export_remove_plural($strings[$plural]['translation']);
18040
              $plural = isset($strings[$plural]['plural']) ?
$strings[$plural]['plural'] : 0;
18050
            }
1806
            else {
18070
              $translation = '';
1808
            }
18090
          }
18100
        }
1811
        else {
18120
          $output .= 'msgstr[0] ""' . "\n";
18130
          $output .= 'msgstr[1] ""' . "\n";
1814
        }
18150
      }
1816
      else {
18170
        $output .= 'msgstr ' .
_locale_export_string($string['translation']);
1818
      }
18190
      $output .= "\n";
18200
    }
18210
  }
18220
  return $output;
18230
}
1824
1825
/**
1826
 * Write a generated PO or POT file to the output.
1827
 *
1828
 * @param $language
1829
 *   Language object to generate the output for, or NULL if generating
1830
 *   translation template.
1831
 * @param $output
1832
 *   The PO(T) file to output as a string. See
_locale_export_generate_po()
1833
 *   on how it can be generated.
1834
 */
183530
function _locale_export_po($language = NULL, $output = NULL) {
1836
  // Log the export event.
18370
  if (isset($language)) {
18380
    $filename = $language->language . '.po';
18390
    watchdog('locale', 'Exported %locale translation file: %filename.',
array('%locale' => $language->name, '%filename' => $filename));
18400
  }
1841
  else {
18420
    $filename = 'drupal.pot';
18430
    watchdog('locale', 'Exported translation file: %filename.',
array('%filename' => $filename));
1844
  }
1845
  // Download the file fo the client.
18460
  header("Content-Disposition: attachment; filename=$filename");
18470
  header("Content-Type: text/plain; charset=utf-8");
18480
  print $output;
18490
  die();
18500
}
1851
1852
/**
1853
 * Print out a string on multiple lines
1854
 */
185530
function _locale_export_string($str) {
18560
  $stri = addcslashes($str, "\0..\37\\\"");
18570
  $parts = array();
1858
1859
  // Cut text into several lines
18600
  while ($stri != "") {
18610
    $i = strpos($stri, "\\n");
18620
    if ($i === FALSE) {
18630
      $curstr = $stri;
18640
      $stri = "";
18650
    }
1866
    else {
18670
      $curstr = substr($stri, 0, $i + 2);
18680
      $stri = substr($stri, $i + 2);
1869
    }
18700
    $curparts = explode("\n", _locale_export_wrap($curstr, 70));
18710
    $parts = array_merge($parts, $curparts);
18720
  }
1873
1874
  // Multiline string
18750
  if (count($parts) > 1) {
18760
    return "\"\"\n\"" . implode("\"\n\"", $parts) . "\"\n";
18770
  }
1878
  // Single line string
18790
  elseif (count($parts) == 1) {
18800
    return "\"$parts[0]\"\n";
18810
  }
1882
  // No translation
1883
  else {
18840
    return "\"\"\n";
1885
  }
18860
}
1887
1888
/**
1889
 * Custom word wrapping for Portable Object (Template) files.
1890
 */
189130
function _locale_export_wrap($str, $len) {
18920
  $words = explode(' ', $str);
18930
  $ret = array();
1894
18950
  $cur = "";
18960
  $nstr = 1;
18970
  while (count($words)) {
18980
    $word = array_shift($words);
18990
    if ($nstr) {
19000
      $cur = $word;
19010
      $nstr = 0;
19020
    }
19030
    elseif (strlen("$cur $word") > $len) {
19040
      $ret[] = $cur . " ";
19050
      $cur = $word;
19060
    }
1907
    else {
19080
      $cur = "$cur $word";
1909
    }
19100
  }
19110
  $ret[] = $cur;
1912
19130
  return implode("\n", $ret);
19140
}
1915
1916
/**
1917
 * Removes plural index information from a string
1918
 */
191930
function _locale_export_remove_plural($entry) {
19200
  return preg_replace('/(@count)\[[0-9]\]/', '\\1', $entry);
19210
}
1922
/**
1923
 * @} End of "locale-api-export"
1924
 */
1925
1926
/**
1927
 * @defgroup locale-api-seek String search functions.
1928
 * @{
1929
 */
1930
1931
/**
1932
 * Perform a string search and display results in a table
1933
 */
193430
function _locale_translate_seek() {
19358
  $output = '';
1936
1937
  // We have at least one criterion to match
19388
  if ($query = _locale_translate_seek_query()) {
19393
    $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 ";
1940
19413
    $arguments = array();
19423
    $limit_language = FALSE;
1943
    // Compute LIKE section
19443
    switch ($query['translation']) {
19453
      case 'translated':
19460
        $where = "WHERE (t.translation LIKE '%%%s%%')";
19470
        $orderby = "ORDER BY t.translation";
19480
        $arguments[] = $query['string'];
19490
        break;
19503
      case 'untranslated':
19510
        $where = "WHERE (s.source LIKE '%%%s%%' AND t.translation IS
NULL)";
19520
        $orderby = "ORDER BY s.source";
19530
        $arguments[] = $query['string'];
19540
        break;
19553
      case 'all' :
19563
      default:
19573
        $where = "WHERE (s.source LIKE '%%%s%%' OR t.translation LIKE
'%%%s%%')";
19583
        $orderby = '';
19593
        $arguments[] = $query['string'];
19603
        $arguments[] = $query['string'];
19613
        break;
19620
    }
19633
    $grouplimit = '';
19643
    if (!empty($query['group']) && $query['group'] != 'all') {
19650
      $grouplimit = " AND s.textgroup = '%s'";
19660
      $arguments[] = $query['group'];
19670
    }
1968
19693
    switch ($query['language']) {
1970
      // Force search in source strings
19713
      case "en":
19720
        $sql = $join . " WHERE s.source LIKE '%%%s%%' $grouplimit ORDER BY
s.source";
19730
        $arguments = array($query['string']); // $where is not used,
discard its arguments
19740
        if (!empty($grouplimit)) {
19750
          $arguments[] = $query['group'];
19760
        }
19770
        break;
1978
      // Search in all languages
19793
      case "all":
19803
        $sql = "$join $where $grouplimit $orderby";
19813
        break;
1982
      // Some different language
19830
      default:
19840
        $sql = "$join AND t.language = '%s' $where $grouplimit $orderby";
19850
        array_unshift($arguments, $query['language']);
1986
        // Don't show translation flags for other languages, we can't see
them with this search.
19870
        $limit_language = $query['language'];
19880
    }
1989
19903
    $result = pager_query($sql, 50, 0, NULL, $arguments);
1991
19923
    $groups = module_invoke_all('locale', 'groups');
19933
    $header = array(t('Text group'), t('String'), ($limit_language) ?
t('Language') : t('Languages'), array('data' => t('Operations'), 'colspan'
=> '2'));
19943
    $arr = array();
19953
    while ($locale = db_fetch_object($result)) {
19962
      $arr[$locale->lid]['group'] = $groups[$locale->textgroup];
19972
      $arr[$locale->lid]['languages'][$locale->language] =
$locale->translation;
19982
      $arr[$locale->lid]['location'] = $locale->location;
19992
      $arr[$locale->lid]['source'] = $locale->source;
20002
    }
20013
    $rows = array();
20023
    foreach ($arr as $lid => $value) {
20032
      $rows[] = array(
20042
        $value['group'],
20052
        array('data' => check_plain(truncate_utf8($value['source'], 150,
FALSE, TRUE)) . '<br /><small>' . $value['location'] . '</small>'),
20062
        array('data' =>
_locale_translate_language_list($value['languages'], $limit_language),
'align' => 'center'),
20072
        array('data' => l(t('edit'), "admin/build/translate/edit/$lid"),
'class' => 'nowrap'),
20082
        array('data' => l(t('delete'),
"admin/build/translate/delete/$lid"), 'class' => 'nowrap'),
2009
      );
20102
    }
2011
20123
    if (count($rows)) {
20132
      $output .= theme('table', $header, $rows);
20142
      if ($pager = theme('pager', NULL, 50)) {
20150
        $output .= $pager;
20160
      }
20172
    }
2018
    else {
20191
      $output .= t('No strings found for your search.');
2020
    }
20213
  }
2022
20238
  return $output;
20240
}
2025
2026
/**
2027
 * Build array out of search criteria specified in request variables
2028
 */
202930
function _locale_translate_seek_query() {
20308
  static $query = NULL;
20318
  if (!isset($query)) {
20328
    $query = array();
20338
    $fields = array('string', 'language', 'translation', 'group');
20348
    foreach ($fields as $field) {
20358
      if (isset($_REQUEST[$field])) {
20363
        $query[$field] = $_REQUEST[$field];
20373
      }
20388
    }
20398
  }
20408
  return $query;
20410
}
2042
2043
/**
2044
 * Force the JavaScript translation file(s) to be refreshed.
2045
 *
2046
 * This function sets a refresh flag for a specified language, or all
2047
 * languages except English, if none specified. JavaScript translation
2048
 * files are rebuilt (with locale_update_js_files()) the next time a
2049
 * request is served in that language.
2050
 *
2051
 * @param $langcode
2052
 *   The language code for which the file needs to be refreshed.
2053
 * @return
2054
 *   New content of the 'javascript_parsed' variable.
2055
 */
205630
function _locale_invalidate_js($langcode = NULL) {
205712
  $parsed = variable_get('javascript_parsed', array());
2058
205912
  if (empty($langcode)) {
2060
    // Invalidate all languages.
20619
    $languages = language_list();
20629
    unset($languages['en']);
20639
    foreach ($languages as $lcode => $data) {
20644
      $parsed['refresh:' . $lcode] = 'waiting';
20654
    }
20669
  }
2067
  else {
2068
    // Invalidate single language.
20693
    $parsed['refresh:' . $langcode] = 'waiting';
2070
  }
2071
207212
  variable_set('javascript_parsed', $parsed);
207312
  return $parsed;
20740
}
2075
2076
/**
2077
 * (Re-)Creates the JavaScript translation file for a language.
2078
 *
2079
 * @param $language
2080
 *   The language, the translation file should be (re)created for.
2081
 */
208230
function _locale_rebuild_js($langcode = NULL) {
20831
  if (!isset($langcode)) {
20840
    global $language;
20850
  }
2086
  else {
2087
    // Get information about the locale.
20881
    $languages = language_list();
20891
    $language = $languages[$langcode];
2090
  }
2091
2092
  // Construct the array for JavaScript translations.
2093
  // We sort on plural so that we have all plural forms before singular
forms.
20941
  $result = db_query("SELECT s.lid, s.source, t.plid, t.plural,
t.translation FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid
= t.lid AND t.language = '%s' WHERE s.location LIKE '%%.js%%' AND
s.textgroup = 'default' ORDER BY t.plural DESC", $language->language);
2095
20961
  $translations = $plurals = array();
20971
  while ($data = db_fetch_object($result)) {
2098
    // Only add this to the translations array when there is actually a
translation.
20991
    if (!empty($data->translation)) {
21000
      if ($data->plural) {
2101
        // When the translation is a plural form, first add it to another
array and
2102
        // wait for the singular (parent) translation.
21030
        if (!isset($plurals[$data->plid])) {
21040
          $plurals[$data->plid] = array($data->plural =>
$data->translation);
21050
        }
2106
        else {
21070
          $plurals[$data->plid] += array($data->plural =>
$data->translation);
2108
        }
21090
      }
21100
      elseif (isset($plurals[$data->lid])) {
2111
        // There are plural translations for this translation, so get them
from
2112
        // the plurals array and add them to the final translations array.
21130
        $translations[$data->source] = array($data->plural =>
$data->translation) + $plurals[$data->lid];
21140
        unset($plurals[$data->lid]);
21150
      }
2116
      else {
2117
        // There are no plural forms for this translation, so just add it
to
2118
        // the translations array.
21190
        $translations[$data->source] = $data->translation;
2120
      }
21210
    }
21221
  }
2123
2124
  // Construct the JavaScript file, if there are translations.
21251
  $data = $status = '';
21261
  if (!empty($translations)) {
2127
21280
    $data = "Drupal.locale = { ";
2129
21300
    if (!empty($language->formula)) {
21310
      $data .= "'pluralFormula': function(\$n) { return
Number({$language->formula}); }, ";
21320
    }
2133
21340
    $data .= "'strings': " . drupal_to_js($translations) . " };";
21350
    $data_hash = md5($data);
21360
  }
2137
2138
  // Construct the filepath where JS translation files are stored.
2139
  // There is (on purpose) no front end to edit that variable.
21401
  $dir = file_create_path(variable_get('locale_js_directory',
'languages'));
2141
2142
  // Delete old file, if we have no translations anymore, or a different
file to be saved.
21431
  if (!empty($language->javascript) && (!$data || $language->javascript !=
$data_hash)) {
21440
    file_delete(file_create_path($dir . '/' . $language->language . '_' .
$language->javascript . '.js'));
21450
    $language->javascript = '';
21460
    $status = 'deleted';
21470
  }
2148
2149
  // Only create a new file if the content has changed.
21501
  if ($data && $language->javascript != $data_hash) {
2151
    // Ensure that the directory exists and is writable, if possible.
21520
    file_check_directory($dir, TRUE);
2153
2154
    // Save the file.
21550
    $dest = $dir . '/' . $language->language . '_' . $data_hash . '.js';
21560
    if (file_save_data($data, $dest)) {
21570
      $language->javascript = $data_hash;
21580
      $status = ($status == 'deleted') ? 'updated' : 'created';
21590
    }
2160
    else {
21610
      $language->javascript = '';
21620
      $status = 'error';
2163
    }
21640
  }
2165
2166
  // Save the new JavaScript hash (or an empty value if the file
2167
  // just got deleted). Act only if some operation was executed.
21681
  if ($status) {
21690
    db_query("UPDATE {languages} SET javascript = '%s' WHERE language =
'%s'", $language->javascript, $language->language);
2170
2171
    // Update the default language variable if the default language has
been altered.
2172
    // This is necessary to keep the variable consistent with the database
2173
    // version of the language and to prevent checking against an outdated
hash.
21740
    $default_langcode = language_default('language');
21750
    if ($default_langcode == $language->language) {
21760
      $default = db_fetch_object(db_query("SELECT * FROM {languages} WHERE
language = '%s'", $default_langcode));
21770
      variable_set('language_default', $default);
21780
    }
21790
  }
2180
2181
  // Log the operation and return success flag.
2182
  switch ($status) {
21831
    case 'updated':
21840
      watchdog('locale', 'Updated JavaScript translation file for the
language %language.', array('%language' => t($language->name)));
21850
      return TRUE;
21861
    case 'created':
21870
      watchdog('locale', 'Created JavaScript translation file for the
language %language.', array('%language' => t($language->name)));
21880
      return TRUE;
21891
    case 'deleted':
21900
      watchdog('locale', 'Removed JavaScript translation file for the
language %language, because no translations currently exist for that
language.', array('%language' => t($language->name)));
21910
      return TRUE;
21921
    case 'error':
21930
      watchdog('locale', 'An error occurred during creation of the
JavaScript translation file for the language %language.', array('%language'
=> t($language->name)), WATCHDOG_ERROR);
21940
      return FALSE;
21951
    default:
2196
      // No operation needed.
21971
      return TRUE;
21981
  }
21990
}
2200
2201
/**
2202
 * List languages in search result table
2203
 */
220430
function _locale_translate_language_list($translation, $limit_language) {
2205
  // Add CSS
22062
  drupal_add_css(drupal_get_path('module', 'locale') . '/locale.css',
'module', 'all', FALSE);
2207
22082
  $languages = language_list();
22092
  unset($languages['en']);
22102
  $output = '';
22112
  foreach ($languages as $langcode => $language) {
22122
    if (!$limit_language || $limit_language == $langcode) {
22132
      $output .= (!empty($translation[$langcode])) ? $langcode . ' ' : "<em
class=\"locale-untranslated\">$langcode</em> ";
22142
    }
22152
  }
2216
22172
  return $output;
22180
}
2219
/**
2220
 * @} End of "locale-api-seek"
2221
 */
2222
2223
/**
2224
 * @defgroup locale-api-predefined List of predefined languages
2225
 * @{
2226
 */
2227
2228
/**
2229
 * Prepares the language code list for a select form item with only the
unsupported ones
2230
 */
223130
function _locale_prepare_predefined_list() {
22324
  $languages = language_list();
22334
  $predefined = _locale_get_predefined_list();
22344
  foreach ($predefined as $key => $value) {
22354
    if (isset($languages[$key])) {
22364
      unset($predefined[$key]);
22374
      continue;
22380
    }
2239
    // Include native name in output, if possible
22404
    if (count($value) > 1) {
22414
      $tname = t($value[0]);
22424
      $predefined[$key] = ($tname == $value[1]) ? $tname : "$tname
($value[1])";
22434
    }
2244
    else {
22454
      $predefined[$key] = t($value[0]);
2246
    }
22474
  }
22484
  asort($predefined);
22494
  return $predefined;
22500
}
2251
2252
/**
2253
 * Some of the common languages with their English and native names
2254
 *
2255
 * Based on ISO 639 and http://people.w3.org/rishida/names/languages.html
2256
 */
225730
function _locale_get_predefined_list() {
2258
  return array(
22594
    "aa" => array("Afar"),
22604
    "ab" => array("Abkhazian", "аҧсуа бызшәа"),
22614
    "ae" => array("Avestan"),
22624
    "af" => array("Afrikaans"),
22634
    "ak" => array("Akan"),
22644
    "am" => array("Amharic", "አማርኛ"),
22654
    "ar" => array("Arabic", /* Left-to-right marker "‭" */
"العربية", LANGUAGE_RTL),
22664
    "as" => array("Assamese"),
22674
    "av" => array("Avar"),
22684
    "ay" => array("Aymara"),
22694
    "az" => array("Azerbaijani", "azərbaycan"),
22704
    "ba" => array("Bashkir"),
22714
    "be" => array("Belarusian", "Беларуская"),
22724
    "bg" => array("Bulgarian", "Български"),
22734
    "bh" => array("Bihari"),
22744
    "bi" => array("Bislama"),
22754
    "bm" => array("Bambara", "Bamanankan"),
22764
    "bn" => array("Bengali"),
22774
    "bo" => array("Tibetan"),
22784
    "br" => array("Breton"),
22794
    "bs" => array("Bosnian", "Bosanski"),
22804
    "ca" => array("Catalan", "Català"),
22814
    "ce" => array("Chechen"),
22824
    "ch" => array("Chamorro"),
22834
    "co" => array("Corsican"),
22844
    "cr" => array("Cree"),
22854
    "cs" => array("Czech", "Čeština"),
22864
    "cu" => array("Old Slavonic"),
22874
    "cv" => array("Chuvash"),
22884
    "cy" => array("Welsh", "Cymraeg"),
22894
    "da" => array("Danish", "Dansk"),
22904
    "de" => array("German", "Deutsch"),
22914
    "dv" => array("Maldivian"),
22924
    "dz" => array("Bhutani"),
22934
    "ee" => array("Ewe", "Ɛʋɛ"),
22944
    "el" => array("Greek", "Ελληνικά"),
22954
    "en" => array("English"),
22964
    "eo" => array("Esperanto"),
22974
    "es" => array("Spanish", "Español"),
22984
    "et" => array("Estonian", "Eesti"),
22994
    "eu" => array("Basque", "Euskera"),
23004
    "fa" => array("Persian", /* Left-to-right marker "‭" */ "فارسی",
LANGUAGE_RTL),
23014
    "ff" => array("Fulah", "Fulfulde"),
23024
    "fi" => array("Finnish", "Suomi"),
23034
    "fj" => array("Fiji"),
23044
    "fo" => array("Faeroese"),
23054
    "fr" => array("French", "Français"),
23064
    "fy" => array("Frisian", "Frysk"),
23074
    "ga" => array("Irish", "Gaeilge"),
23084
    "gd" => array("Scots Gaelic"),
23094
    "gl" => array("Galician", "Galego"),
23104
    "gn" => array("Guarani"),
23114
    "gu" => array("Gujarati"),
23124
    "gv" => array("Manx"),
23134
    "ha" => array("Hausa"),
23144
    "he" => array("Hebrew", /* Left-to-right marker "‭" */ "עברית",
LANGUAGE_RTL),
23154
    "hi" => array("Hindi", "हिन्दी"),
23164
    "ho" => array("Hiri Motu"),
23174
    "hr" => array("Croatian", "Hrvatski"),
23184
    "hu" => array("Hungarian", "Magyar"),
23194
    "hy" => array("Armenian", "Հայերեն"),
23204
    "hz" => array("Herero"),
23214
    "ia" => array("Interlingua"),
23224
    "id" => array("Indonesian", "Bahasa Indonesia"),
23234
    "ie" => array("Interlingue"),
23244
    "ig" => array("Igbo"),
23254
    "ik" => array("Inupiak"),
23264
    "is" => array("Icelandic", "Íslenska"),
23274
    "it" => array("Italian", "Italiano"),
23284
    "iu" => array("Inuktitut"),
23294
    "ja" => array("Japanese", "日本語"),
23304
    "jv" => array("Javanese"),
23314
    "ka" => array("Georgian"),
23324
    "kg" => array("Kongo"),
23334
    "ki" => array("Kikuyu"),
23344
    "kj" => array("Kwanyama"),
23354
    "kk" => array("Kazakh", "Қазақ"),
23364
    "kl" => array("Greenlandic"),
23374
    "km" => array("Cambodian"),
23384
    "kn" => array("Kannada", "ಕನ್ನಡ"),
23394
    "ko" => array("Korean", "한국어"),
23404
    "kr" => array("Kanuri"),
23414
    "ks" => array("Kashmiri"),
23424
    "ku" => array("Kurdish", "Kurdî"),
23434
    "kv" => array("Komi"),
23444
    "kw" => array("Cornish"),
23454
    "ky" => array("Kirghiz", "Кыргыз"),
23464
    "la" => array("Latin", "Latina"),
23474
    "lb" => array("Luxembourgish"),
23484
    "lg" => array("Luganda"),
23494
    "ln" => array("Lingala"),
23504
    "lo" => array("Laothian"),
23514
    "lt" => array("Lithuanian", "Lietuvių"),
23524
    "lv" => array("Latvian", "Latviešu"),
23534
    "mg" => array("Malagasy"),
23544
    "mh" => array("Marshallese"),
23554
    "mi" => array("Maori"),
23564
    "mk" => array("Macedonian", "Македонски"),
23574
    "ml" => array("Malayalam", "മലയാളം"),
23584
    "mn" => array("Mongolian"),
23594
    "mo" => array("Moldavian"),
23604
    "mr" => array("Marathi"),
23614
    "ms" => array("Malay", "Bahasa Melayu"),
23624
    "mt" => array("Maltese", "Malti"),
23634
    "my" => array("Burmese"),
23644
    "na" => array("Nauru"),
23654
    "nd" => array("North Ndebele"),
23664
    "ne" => array("Nepali"),
23674
    "ng" => array("Ndonga"),
23684
    "nl" => array("Dutch", "Nederlands"),
23694
    "nb" => array("Norwegian Bokmål", "Bokmål"),
23704
    "nn" => array("Norwegian Nynorsk", "Nynorsk"),
23714
    "nr" => array("South Ndebele"),
23724
    "nv" => array("Navajo"),
23734
    "ny" => array("Chichewa"),
23744
    "oc" => array("Occitan"),
23754
    "om" => array("Oromo"),
23764
    "or" => array("Oriya"),
23774
    "os" => array("Ossetian"),
23784
    "pa" => array("Punjabi"),
23794
    "pi" => array("Pali"),
23804
    "pl" => array("Polish", "Polski"),
23814
    "ps" => array("Pashto", /* Left-to-right marker "‭" */ "پښتو",
LANGUAGE_RTL),
23824
    "pt" => array("Portuguese, Portugal", "Português"),
23834
    "pt-br" => array("Portuguese, Brazil", "Português"),
23844
    "qu" => array("Quechua"),
23854
    "rm" => array("Rhaeto-Romance"),
23864
    "rn" => array("Kirundi"),
23874
    "ro" => array("Romanian", "Română"),
23884
    "ru" => array("Russian", "Русский"),
23894
    "rw" => array("Kinyarwanda"),
23904
    "sa" => array("Sanskrit"),
23914
    "sc" => array("Sardinian"),
23924
    "sd" => array("Sindhi"),
23934
    "se" => array("Northern Sami"),
23944
    "sg" => array("Sango"),
23954
    "sh" => array("Serbo-Croatian"),
23964
    "si" => array("Singhalese"),
23974
    "sk" => array("Slovak", "Slovenčina"),
23984
    "sl" => array("Slovenian", "Slovenščina"),
23994
    "sm" => array("Samoan"),
24004
    "sn" => array("Shona"),
24014
    "so" => array("Somali"),
24024
    "sq" => array("Albanian", "Shqip"),
24034
    "sr" => array("Serbian", "Српски"),
24044
    "ss" => array("Siswati"),
24054
    "st" => array("Sesotho"),
24064
    "su" => array("Sudanese"),
24074
    "sv" => array("Swedish", "Svenska"),
24084
    "sw" => array("Swahili", "Kiswahili"),
24094
    "ta" => array("Tamil", "தமிழ்"),
24104
    "te" => array("Telugu", "తెలుగు"),
24114
    "tg" => array("Tajik"),
24124
    "th" => array("Thai", "ภาษาไทย"),
24134
    "ti" => array("Tigrinya"),
24144
    "tk" => array("Turkmen"),
24154
    "tl" => array("Tagalog"),
24164
    "tn" => array("Setswana"),
24174
    "to" => array("Tonga"),
24184
    "tr" => array("Turkish", "Türkçe"),
24194
    "ts" => array("Tsonga"),
24204
    "tt" => array("Tatar", "Tatarça"),
24214
    "tw" => array("Twi"),
24224
    "ty" => array("Tahitian"),
24234
    "ug" => array("Uighur"),
24244
    "uk" => array("Ukrainian", "Українська"),
24254
    "ur" => array("Urdu", /* Left-to-right marker "‭" */ "اردو",
LANGUAGE_RTL),
24264
    "uz" => array("Uzbek", "o'zbek"),
24274
    "ve" => array("Venda"),
24284
    "vi" => array("Vietnamese", "Tiếng Việt"),
24294
    "wo" => array("Wolof"),
24304
    "xh" => array("Xhosa", "isiXhosa"),
24314
    "yi" => array("Yiddish"),
24324
    "yo" => array("Yoruba", "Yorùbá"),
24334
    "za" => array("Zhuang"),
24344
    "zh-hans" => array("Chinese, Simplified", "简体中文"),
24354
    "zh-hant" => array("Chinese, Traditional", "繁體中文"),
24364
    "zu" => array("Zulu", "isiZulu"),
24374
  );
24380
}
2439
/**
2440
 * @} End of "locale-api-languages-predefined"
2441
 */
2442
2443
/**
2444
 * @defgroup locale-autoimport Automatic interface translation import
2445
 * @{
2446
 */
2447
2448
/**
2449
 * Prepare a batch to import translations for all enabled
2450
 * modules in a given language.
2451
 *
2452
 * @param $langcode
2453
 *   Language code to import translations for.
2454
 * @param $finished
2455
 *   Optional finished callback for the batch.
2456
 * @param $skip
2457
 *   Array of component names to skip. Used in the installer for the
2458
 *   second pass import, when most components are already imported.
2459
 * @return
2460
 *   A batch structure or FALSE if no files found.
2461
 */
246230
function locale_batch_by_language($langcode, $finished = NULL, $skip =
array()) {
2463
  // Collect all files to import for all enabled modules and themes.
24642
  $files = array();
24652
  $components = array();
24662
  $query = "SELECT name, filename FROM {system} WHERE status = 1";
24672
  if (count($skip)) {
24680
    $query .= " AND name NOT IN (" . db_placeholders($skip, 'varchar') .
")";
24690
  }
24702
  $result = db_query($query, $skip);
24712
  while ($component = db_fetch_object($result)) {
2472
    // Collect all files for all components, names as $langcode.po or
2473
    // with names ending with $langcode.po. This allows for filenames
2474
    // like node-module.de.po to let translators use small files and
2475
    // be able to import in smaller chunks.
24762
    $files = array_merge($files,
file_scan_directory(dirname($component->filename) . '/translations',
'(^|\.)' . $langcode . '\.po$', array('.', '..', 'CVS'), 0, FALSE));
24772
    $components[] = $component->name;
24782
  }
2479
24802
  return _locale_batch_build($files, $finished, $components);
24810
}
2482
2483
/**
2484
 * Prepare a batch to run when installing modules or enabling themes.
2485
 * This batch will import translations for the newly added components
2486
 * in all the languages already set up on the site.
2487
 *
2488
 * @param $components
2489
 *   An array of component (theme and/or module) names to import
2490
 *   translations for.
2491
 * @param $finished
2492
 *   Optional finished callback for the batch.
2493
 */
249430
function locale_batch_by_component($components, $finished =
'_locale_batch_system_finished') {
24951
  $files = array();
24961
  $languages = language_list('enabled');
24971
  unset($languages[1]['en']);
24981
  if (count($languages[1])) {
24990
    $language_list = join('|', array_keys($languages[1]));
2500
    // Collect all files to import for all $components.
25010
    $result = db_query("SELECT name, filename FROM {system} WHERE status =
1");
25020
    while ($component = db_fetch_object($result)) {
25030
      if (in_array($component->name, $components)) {
2504
        // Collect all files for this component in all enabled languages,
named
2505
        // as $langcode.po or with names ending with $langcode.po. This
allows
2506
        // for filenames like node-module.de.po to let translators use
small
2507
        // files and be able to import in smaller chunks.
25080
        $files = array_merge($files,
file_scan_directory(dirname($component->filename) . '/translations',
'(^|\.)(' . $language_list . ')\.po$', array('.', '..', 'CVS'), 0,
FALSE));
25090
      }
25100
    }
25110
    return _locale_batch_build($files, $finished);
25120
  }
25131
  return FALSE;
25140
}
2515
2516
/**
2517
 * Build a locale batch from an array of files.
2518
 *
2519
 * @param $files
2520
 *   Array of files to import
2521
 * @param $finished
2522
 *   Optional finished callback for the batch.
2523
 * @param $components
2524
 *   Optional list of component names the batch covers. Used in the
installer.
2525
 * @return
2526
 *   A batch structure
2527
 */
252830
function _locale_batch_build($files, $finished = NULL, $components =
array()) {
25292
  $t = get_t();
25302
  if (count($files)) {
25310
    $operations = array();
25320
    foreach ($files as $file) {
2533
      // We call _locale_batch_import for every batch operation.
25340
      $operations[] = array('_locale_batch_import',
array($file->filename));    }
2535
      $batch = array(
25360
        'operations'    => $operations,
25370
        'title'         => $t('Importing interface translations'),
25380
        'init_message'  => $t('Starting import'),
25390
        'error_message' => $t('Error importing interface translations'),
25400
        'file'          => './includes/locale.inc',
2541
        // This is not a batch API construct, but data passed along to the
2542
        // installer, so we know what did we import already.
25430
        '#components'   => $components,
25440
      );
25450
      if (isset($finished)) {
25460
        $batch['finished'] = $finished;
25470
      }
25480
    return $batch;
25490
  }
25502
  return FALSE;
25510
}
2552
2553
/**
2554
 * Perform interface translation import as a batch step.
2555
 *
2556
 * @param $filepath
2557
 *   Path to a file to import.
2558
 * @param $results
2559
 *   Contains a list of files imported.
2560
 */
256130
function _locale_batch_import($filepath, &$context) {
2562
  // The filename is either {langcode}.po or {prefix}.{langcode}.po, so
2563
  // we can extract the language code to use for the import from the end.
25640
  if (preg_match('!(/|\.)([^\./]+)\.po$!', $filepath, $langcode)) {
25650
    $file = (object) array('filename' => basename($filepath), 'filepath' =>
$filepath);
25660
    _locale_import_read_po('db-store', $file, LOCALE_IMPORT_KEEP,
$langcode[2]);
25670
    $context['results'][] = $filepath;
25680
  }
25690
}
2570
2571
/**
2572
 * Finished callback of system page locale import batch.
2573
 * Inform the user of translation files imported.
2574
 */
257530
function _locale_batch_system_finished($success, $results) {
25760
  if ($success) {
25770
    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.'));
25780
  }
25790
}
2580
2581
/**
2582
 * Finished callback of language addition locale import batch.
2583
 * Inform the user of translation files imported.
2584
 */
258530
function _locale_batch_language_finished($success, $results) {
25860
  if ($success) {
25870
    drupal_set_message(format_plural(count($results), 'One translation file
imported for the enabled modules.', '@count translation files imported for
the enabled modules.'));
25880
  }
25890
}
2590
2591
/**
2592
 * @} End of "locale-autoimport"
2593
 */
259430