locale.inc 92.7 KB
Newer Older
Dries's avatar
 
Dries committed
1 2
<?php
// $Id$
Dries's avatar
 
Dries committed
3

Dries's avatar
 
Dries committed
4 5
/**
 * @file
6
 * Administration functions for locale.module.
Dries's avatar
 
Dries committed
7 8
 */

9 10 11
/**
 * Regular expression pattern used to localize JavaScript strings.
 */
12 13
define('LOCALE_JS_STRING', '(?:(?:\'(?:\\\\\'|[^\'])*\'|"(?:\\\\"|[^"])*")(?:\s*\+\s*)?)+');

14 15 16 17 18 19 20 21 22 23 24 25
/**
 * Translation import mode overwriting all existing translations
 * if new translated version available.
 */
define('LOCALE_IMPORT_OVERWRITE', 0);

/**
 * Translation import mode keeping existing translations and only
 * inserting new strings.
 */
define('LOCALE_IMPORT_KEEP', 1);

Dries's avatar
 
Dries committed
26
/**
27 28
 * @defgroup locale-language-overview Language overview functionality
 * @{
Dries's avatar
 
Dries committed
29 30 31
 */

/**
32
 * User interface for the language overview screen.
Dries's avatar
 
Dries committed
33
 */
34
function locale_languages_overview_form() {
35
  $languages = language_list('language', TRUE);
Dries's avatar
 
Dries committed
36

37
  $options = array();
38 39
  $form['weight'] = array('#tree' => TRUE);
  foreach ($languages as $langcode => $language) {
Dries's avatar
 
Dries committed
40

41 42 43
    $options[$langcode] = '';
    if ($language->enabled) {
      $enabled[] = $langcode;
Dries's avatar
 
Dries committed
44
    }
45 46
    $form['weight'][$langcode] = array(
      '#type' => 'weight',
47 48
      '#default_value' => $language->weight,
      '#attributes' => array('class' => 'language-order-weight'),
49
    );
50 51 52
    $form['name'][$langcode] = array('#markup' => check_plain($language->name));
    $form['native'][$langcode] = array('#markup' => check_plain($language->native));
    $form['direction'][$langcode] = array('#markup' => ($language->direction == LANGUAGE_RTL ? t('Right to left') : t('Left to right')));
Dries's avatar
 
Dries committed
53
  }
54 55 56 57 58 59
  $form['enabled'] = array('#type' => 'checkboxes',
    '#options' => $options,
    '#default_value' => $enabled,
  );
  $form['site_default'] = array('#type' => 'radios',
    '#options' => $options,
60
    '#default_value' => language_default('language'),
61
  );
62
  $form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration'));
63
  $form['#theme'] = 'locale_languages_overview_form';
64

65
  return $form;
66
}
Dries's avatar
 
Dries committed
67

68
/**
Dries's avatar
Dries committed
69
 * Theme the language overview form.
70 71
 *
 * @ingroup themeable
72
 */
73
function theme_locale_languages_overview_form($form) {
74
  $default = language_default();
75
  foreach ($form['name'] as $key => $element) {
76
    // Do not take form control structures.
77
    if (is_array($element) && element_child($key)) {
78 79
      // Disable checkbox for the default language, because it cannot be disabled.
      if ($key == $default->language) {
80
        $form['enabled'][$key]['#attributes']['disabled'] = 'disabled';
81 82
      }
      $rows[] = array(
83 84 85 86 87 88 89 90 91 92 93
        'data' => array(
          '<strong>' . drupal_render($form['name'][$key]) . '</strong>',
          drupal_render($form['native'][$key]),
          check_plain($key),
          drupal_render($form['direction'][$key]),
          array('data' => drupal_render($form['enabled'][$key]), 'align' => 'center'),
          drupal_render($form['site_default'][$key]),
          drupal_render($form['weight'][$key]),
          l(t('edit'), 'admin/settings/language/edit/' . $key) . (($key != 'en' && $key != $default->language) ? ' ' . l(t('delete'), 'admin/settings/language/delete/' . $key) : '')
        ),
        'class' => 'draggable'
94
      );
95 96
    }
  }
97 98
  $header = array(array('data' => t('English name')), array('data' => t('Native name')), array('data' => t('Code')), array('data' => t('Direction')), array('data' => t('Enabled')), array('data' => t('Default')), array('data' => t('Weight')), array('data' => t('Operations')));
  $output = theme('table', $header, $rows, array('id' => 'language-order'));
99
  $output .= drupal_render($form);
100 101
  
  drupal_add_tabledrag('language-order', 'order', 'sibling', 'language-order-weight');
Dries's avatar
Dries committed
102

103
  return $output;
Dries's avatar
 
Dries committed
104 105 106
}

/**
107
 * Process language overview form submissions, updating existing languages.
Dries's avatar
 
Dries committed
108
 */
109
function locale_languages_overview_form_submit($form, &$form_state) {
110
  $languages = language_list();
111
  $default = language_default();
112 113
  $enabled_count = 0;
  foreach ($languages as $langcode => $language) {
114 115 116 117
    if ($form_state['values']['site_default'] == $langcode || $default->language == $langcode) {
      // Automatically enable the default language and the language
      // which was default previously (because we will not get the
      // value from that disabled checkox).
118
      $form_state['values']['enabled'][$langcode] = 1;
119
    }
120
    if ($form_state['values']['enabled'][$langcode]) {
121 122
      $enabled_count++;
      $language->enabled = 1;
123 124
    }
    else {
125
      $language->enabled = 0;
126
    }
127
    $language->weight = $form_state['values']['weight'][$langcode];
128 129
    db_query("UPDATE {languages} SET enabled = %d, weight = %d WHERE language = '%s'", $language->enabled, $language->weight, $langcode);
    $languages[$langcode] = $language;
130 131
  }
  drupal_set_message(t('Configuration saved.'));
132
  variable_set('language_default', $languages[$form_state['values']['site_default']]);
133
  variable_set('language_count', $enabled_count);
Dries's avatar
Dries committed
134

135
  // Changing the language settings impacts the interface.
136
  cache_clear_all('*', 'cache_page', TRUE);
Dries's avatar
 
Dries committed
137

138 139
  $form_state['redirect'] = 'admin/settings/language';
  return;
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
}
/**
 * @} End of "locale-language-overview"
 */

/**
 * @defgroup locale-language-add-edit Language addition and editing functionality
 * @{
 */

/**
 * User interface for the language addition screen.
 */
function locale_languages_add_screen() {
  $output = drupal_get_form('locale_languages_predefined_form');
  $output .= drupal_get_form('locale_languages_custom_form');
  return $output;
157 158
}

159 160 161
/**
 * Predefined language setup form.
 */
162
function locale_languages_predefined_form() {
163
  $predefined = _locale_prepare_predefined_list();
164
  $form = array();
165
  $form['language list'] = array('#type' => 'fieldset',
166
    '#title' => t('Predefined language'),
167 168 169 170
    '#collapsible' => TRUE,
  );
  $form['language list']['langcode'] = array('#type' => 'select',
    '#title' => t('Language name'),
171 172
    '#default_value' => key($predefined),
    '#options' => $predefined,
173
    '#description' => t('Select the desired language and click the <em>Add language</em> button. (Use the <em>Custom language</em> options if your desired language does not appear in this list.)'),
174 175
  );
  $form['language list']['submit'] = array('#type' => 'submit', '#value' => t('Add language'));
176 177
  return $form;
}
Dries's avatar
Dries committed
178

179 180 181
/**
 * Custom language addition form.
 */
182
function locale_languages_custom_form() {
183
  $form = array();
184 185 186
  $form['custom language'] = array('#type' => 'fieldset',
    '#title' => t('Custom language'),
    '#collapsible' => TRUE,
187
    '#collapsed' => TRUE,
188
  );
189
  _locale_languages_common_controls($form['custom language']);
190 191 192
  $form['custom language']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Add custom language')
193
  );
194
  // Reuse the validation and submit functions of the predefined language setup form.
195 196
  $form['#submit'][] = 'locale_languages_predefined_form_submit';
  $form['#validate'][] = 'locale_languages_predefined_form_validate';
197 198 199 200 201 202 203
  return $form;
}

/**
 * Editing screen for a particular language.
 *
 * @param $langcode
204
 *   Language code of the language to edit.
205
 */
206
function locale_languages_edit_form(&$form_state, $langcode) {
207 208
  if ($language = db_fetch_object(db_query("SELECT * FROM {languages} WHERE language = '%s'", $langcode))) {
    $form = array();
209
    _locale_languages_common_controls($form, $language);
210 211 212 213
    $form['submit'] = array(
      '#type' => 'submit',
      '#value' => t('Save language')
    );
214 215
    $form['#submit'][] = 'locale_languages_edit_form_submit';
    $form['#validate'][] = 'locale_languages_edit_form_validate';
216 217 218 219 220 221 222 223
    return $form;
  }
  else {
    drupal_not_found();
  }
}

/**
224
 * Common elements of the language addition and editing form.
225 226 227 228 229 230
 *
 * @param $form
 *   A parent form item (or empty array) to add items below.
 * @param $language
 *   Language object to edit.
 */
231
function _locale_languages_common_controls(&$form, $language = NULL) {
232 233 234 235 236 237 238
  if (!is_object($language)) {
    $language = new stdClass();
  }
  if (isset($language->language)) {
    $form['langcode_view'] = array(
      '#type' => 'item',
      '#title' => t('Language code'),
239
      '#markup' => $language->language
240 241 242 243 244 245 246 247 248 249 250 251 252 253
    );
    $form['langcode'] = array(
      '#type' => 'value',
      '#value' => $language->language
    );
  }
  else {
    $form['langcode'] = array('#type' => 'textfield',
      '#title' => t('Language code'),
      '#size' => 12,
      '#maxlength' => 60,
      '#required' => TRUE,
      '#default_value' => @$language->language,
      '#disabled' => (isset($language->language)),
254
      '#description' => t('<a href="@rfc4646">RFC 4646</a> compliant language identifier. Language codes typically use a country code, and optionally, a script or regional variant name. <em>Examples: "en", "en-US" and "zh-Hant".</em>', array('@rfc4646' => 'http://www.ietf.org/rfc/rfc4646.txt')),
255 256
    );
  }
257
  $form['name'] = array('#type' => 'textfield',
258 259
    '#title' => t('Language name in English'),
    '#maxlength' => 64,
260
    '#default_value' => @$language->name,
261
    '#required' => TRUE,
262
    '#description' => t('Name of the language in English. Will be available for translation in all languages.'),
263
  );
264
  $form['native'] = array('#type' => 'textfield',
265 266 267 268 269 270 271 272 273 274
    '#title' => t('Native language name'),
    '#maxlength' => 64,
    '#default_value' => @$language->native,
    '#required' => TRUE,
    '#description' => t('Name of the language in the language being added.'),
  );
  $form['prefix'] = array('#type' => 'textfield',
    '#title' => t('Path prefix'),
    '#maxlength' => 64,
    '#default_value' => @$language->prefix,
275
    '#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>')
276 277 278
  );
  $form['domain'] = array('#type' => 'textfield',
    '#title' => t('Language domain'),
279
    '#maxlength' => 128,
280
    '#default_value' => @$language->domain,
281
    '#description' => t('Language-specific URL, with protocol. With language negotiation set to <em>Domain name only</em>, the site is presented in this language when the URL accessing the site references this domain. For the default language, this value may be left blank. <strong>This value must include a protocol as part of the string.</strong> <em>Example: Specifying "http://example.de" or "http://de.example.com" as language domains for German results in URLs in the forms "http://example.de/node" and "http://de.example.com/node", respectively.</em>'),
282 283 284 285
  );
  $form['direction'] = array('#type' => 'radios',
    '#title' => t('Direction'),
    '#required' => TRUE,
286
    '#description' => t('Direction that text in this language is presented.'),
287
    '#default_value' => @$language->direction,
288
    '#options' => array(LANGUAGE_LTR => t('Left to right'), LANGUAGE_RTL => t('Right to left'))
289
  );
290 291
  return $form;
}
Dries's avatar
 
Dries committed
292 293

/**
294 295
 * Validate the language addition form.
 */
296 297
function locale_languages_predefined_form_validate($form, &$form_state) {
  $langcode = $form_state['values']['langcode'];
298

299
  if ($duplicate = db_result(db_query("SELECT COUNT(*) FROM {languages} WHERE language = '%s'", $langcode)) != 0) {
300
    form_set_error('langcode', t('The language %language (%code) already exists.', array('%language' => $form_state['values']['name'], '%code' => $langcode)));
301 302
  }

303
  if (!isset($form_state['values']['name'])) {
304 305
    // Predefined language selection.
    $predefined = _locale_get_predefined_list();
306
    if (!isset($predefined[$langcode])) {
307 308 309
      form_set_error('langcode', t('Invalid language code.'));
    }
  }
310
  else {
311
    // Reuse the editing form validation routine if we add a custom language.
312
    locale_languages_edit_form_validate($form, $form_state);
313
  }
314 315 316 317 318
}

/**
 * Process the language addition form submission.
 */
319 320 321
function locale_languages_predefined_form_submit($form, &$form_state) {
  $langcode = $form_state['values']['langcode'];
  if (isset($form_state['values']['name'])) {
322
    // Custom language form.
323 324
    locale_add_language($langcode, $form_state['values']['name'], $form_state['values']['native'], $form_state['values']['direction'], $form_state['values']['domain'], $form_state['values']['prefix']);
    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'))));
325 326
  }
  else {
327 328
    // Predefined language selection.
    $predefined = _locale_get_predefined_list();
329 330
    locale_add_language($langcode);
    drupal_set_message(t('The language %language has been created and can now be used. More information is available on the <a href="@locale-help">help screen</a>.', array('%language' => t($predefined[$langcode][0]), '@locale-help' => url('admin/help/locale'))));
331 332
  }

333 334 335 336 337 338
  // See if we have language files to import for the newly added
  // language, collect and import them.
  if ($batch = locale_batch_by_language($langcode, '_locale_batch_language_finished')) {
    batch_set($batch);
  }

339 340
  $form_state['redirect'] = 'admin/settings/language';
  return;
341 342 343 344 345
}

/**
 * Validate the language editing form. Reused for custom language addition too.
 */
346 347
function locale_languages_edit_form_validate($form, &$form_state) {
  if (!empty($form_state['values']['domain']) && !empty($form_state['values']['prefix'])) {
348 349
    form_set_error('prefix', t('Domain and path prefix values should not be set at the same time.'));
  }
350
  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']))) {
351
    form_set_error('domain', t('The domain (%domain) is already tied to a language (%language).', array('%domain' => $form_state['values']['domain'], '%language' => $duplicate->language)));
352
  }
353
  if (empty($form_state['values']['prefix']) && language_default('language') != $form_state['values']['langcode'] && empty($form_state['values']['domain'])) {
354 355
    form_set_error('prefix', t('Only the default language can have both the domain and prefix empty.'));
  }
356
  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']))) {
357
    form_set_error('prefix', t('The prefix (%prefix) is already tied to a language (%language).', array('%prefix' => $form_state['values']['prefix'], '%language' => $duplicate->language)));
358 359 360 361 362 363
  }
}

/**
 * Process the language editing form submission.
 */
364 365
function locale_languages_edit_form_submit($form, &$form_state) {
  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']);
366
  $default = language_default();
367
  if ($default->language == $form_state['values']['langcode']) {
368
    $properties = array('name', 'native', 'direction', 'enabled', 'plurals', 'formula', 'domain', 'prefix', 'weight');
369
    foreach ($properties as $keyname) {
370 371 372
      if (isset($form_state['values'][$keyname])) {
        $default->$keyname = $form_state['values'][$keyname];
      }
373
    }
374
    variable_set('language_default', $default);
375
  }
376 377
  $form_state['redirect'] = 'admin/settings/language';
  return;
378
}
379 380 381 382 383 384 385 386 387 388 389 390
/**
 * @} End of "locale-language-add-edit"
 */

/**
 * @defgroup locale-language-delete Language deletion functionality
 * @{
 */

/**
 * User interface for the language deletion confirmation screen.
 */
391
function locale_languages_delete_form(&$form_state, $langcode) {
392 393 394 395 396 397 398

  // Do not allow deletion of English locale.
  if ($langcode == 'en') {
    drupal_set_message(t('The English language cannot be deleted.'));
    drupal_goto('admin/settings/language');
  }

399
  if (language_default('language') == $langcode) {
400 401 402 403 404 405 406 407 408 409 410 411
    drupal_set_message(t('The default language cannot be deleted.'));
    drupal_goto('admin/settings/language');
  }

  // For other languages, warn user that data loss is ahead.
  $languages = language_list();

  if (!isset($languages[$langcode])) {
    drupal_not_found();
  }
  else {
    $form['langcode'] = array('#type' => 'value', '#value' => $langcode);
412
    return confirm_form($form, t('Are you sure you want to delete the language %name?', array('%name' => t($languages[$langcode]->name))), 'admin/settings/language', t('Deleting a language will remove all interface translations associated with it, and posts in this language will be set to be language neutral. This action cannot be undone.'), t('Delete'), t('Cancel'));
413 414 415 416 417 418
  }
}

/**
 * Process language deletion submissions.
 */
419
function locale_languages_delete_form_submit($form, &$form_state) {
420
  $languages = language_list();
421
  if (isset($languages[$form_state['values']['langcode']])) {
422
    // Remove translations first.
423
    db_query("DELETE FROM {locales_target} WHERE language = '%s'", $form_state['values']['langcode']);
424
    cache_clear_all('locale:' . $form_state['values']['langcode'], 'cache');
425 426 427 428
    // With no translations, this removes existing JavaScript translations file.
    _locale_rebuild_js($form_state['values']['langcode']);
    // Remove the language.
    db_query("DELETE FROM {languages} WHERE language = '%s'", $form_state['values']['langcode']);
429 430
    db_query("UPDATE {node} SET language = '' WHERE language = '%s'", $form_state['values']['langcode']);
    $variables = array('%locale' => $languages[$form_state['values']['langcode']]->name);
431 432 433 434 435 436 437
    drupal_set_message(t('The language %locale has been removed.', $variables));
    watchdog('locale', 'The language %locale has been removed.', $variables);
  }

  // Changing the language settings impacts the interface:
  cache_clear_all('*', 'cache_page', TRUE);

438 439
  $form_state['redirect'] = 'admin/settings/language';
  return;
440 441 442 443 444 445 446 447 448
}
/**
 * @} End of "locale-language-add-edit"
 */

/**
 * @defgroup locale-languages-negotiation Language negotiation options screen
 * @{
 */
449 450 451 452

/**
 * Setting for language negotiation options
 */
453
function locale_languages_configure_form() {
454 455 456 457
  $form['language_negotiation'] = array(
    '#title' => t('Language negotiation'),
    '#type' => 'radios',
    '#options' => array(
458 459 460 461
      LANGUAGE_NEGOTIATION_NONE => t('None.'),
      LANGUAGE_NEGOTIATION_PATH_DEFAULT => t('Path prefix only.'),
      LANGUAGE_NEGOTIATION_PATH => t('Path prefix with language fallback.'),
      LANGUAGE_NEGOTIATION_DOMAIN => t('Domain name only.')),
462
    '#default_value' => variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE),
463
    '#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>")
464 465 466 467 468 469 470
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save settings')
  );
  return $form;
}
Dries's avatar
Dries committed
471

472 473 474
/**
 * Submit function for language negotiation settings.
 */
475 476
function locale_languages_configure_form_submit($form, &$form_state) {
  variable_set('language_negotiation', $form_state['values']['language_negotiation']);
477
  drupal_set_message(t('Language negotiation configuration saved.'));
478 479
  $form_state['redirect'] = 'admin/settings/language';
  return;
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506
}
/**
 * @} End of "locale-languages-negotiation"
 */

/**
 * @defgroup locale-translate-overview Translation overview screen.
 * @{
 */

/**
 * Overview screen for translations.
 */
function locale_translate_overview_screen() {
  $languages = language_list('language', TRUE);
  $groups = module_invoke_all('locale', 'groups');

  // Build headers with all groups in order.
  $headers = array_merge(array(t('Language')), array_values($groups));

  // Collect summaries of all source strings in all groups.
  $sums = db_query("SELECT COUNT(*) AS strings, textgroup FROM {locales_source} GROUP BY textgroup");
  $groupsums = array();
  while ($group = db_fetch_object($sums)) {
    $groupsums[$group->textgroup] = $group->strings;
  }

507
  // Set up overview table with default values, ensuring common order for values.
508 509 510 511
  $rows = array();
  foreach ($languages as $langcode => $language) {
    $rows[$langcode] = array('name' => ($langcode == 'en' ? t('English (built-in)') : t($language->name)));
    foreach ($groups as $group => $name) {
512
      $rows[$langcode][$group] = ($langcode == 'en' ? t('n/a') : '0/' . (isset($groupsums[$group]) ? $groupsums[$group] : 0) . ' (0%)');
513 514 515 516
    }
  }

  // Languages with at least one record in the locale table.
517
  $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");
518 519
  while ($data = db_fetch_object($translations)) {
    $ratio = (!empty($groupsums[$data->textgroup]) && $data->translation > 0) ? round(($data->translation/$groupsums[$data->textgroup])*100., 2) : 0;
520
    $rows[$data->language][$data->textgroup] = $data->translation . '/' . $groupsums[$data->textgroup] . " ($ratio%)";
521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561
  }

  return theme('table', $headers, $rows);
}
/**
 * @} End of "locale-translate-overview"
 */

/**
 * @defgroup locale-translate-seek Translation search screen.
 * @{
 */

/**
 * String search screen.
 */
function locale_translate_seek_screen() {
  $output = _locale_translate_seek();
  $output .= drupal_get_form('locale_translate_seek_form');
  return $output;
}

/**
 * User interface for the string search screen.
 */
function locale_translate_seek_form() {
  // Get all languages, except English
  $languages = locale_language_list('name', TRUE);
  unset($languages['en']);

  // Present edit form preserving previous user settings
  $query = _locale_translate_seek_query();
  $form = array();
  $form['search'] = array('#type' => 'fieldset',
    '#title' => t('Search'),
  );
  $form['search']['string'] = array('#type' => 'textfield',
    '#title' => t('String contains'),
    '#default_value' => @$query['string'],
    '#description' => t('Leave blank to show all strings. The search is case sensitive.'),
  );
562
  $form['search']['language'] = array(
563 564 565
    // Change type of form widget if more the 5 options will
    // be present (2 of the options are added below).
    '#type' => (count($languages) <= 3 ? 'radios' : 'select'),
566 567 568 569 570 571 572
    '#title' => t('Language'),
    '#default_value' => (!empty($query['language']) ? $query['language'] : 'all'),
    '#options' => array_merge(array('all' => t('All languages'), 'en' => t('English (provided by Drupal)')), $languages),
  );
  $form['search']['translation'] = array('#type' => 'radios',
    '#title' => t('Search in'),
    '#default_value' => (!empty($query['translation']) ? $query['translation'] : 'all'),
573
    '#options' => array('all' => t('Both translated and untranslated strings'), 'translated' => t('Only translated strings'), 'untranslated' => t('Only untranslated strings')),
574 575 576 577 578 579 580 581 582 583 584 585
  );
  $groups = module_invoke_all('locale', 'groups');
  $form['search']['group'] = array('#type' => 'radios',
    '#title' => t('Limit search to'),
    '#default_value' => (!empty($query['group']) ? $query['group'] : 'all'),
    '#options' => array_merge(array('all' => t('All text groups')), $groups),
  );

  $form['search']['submit'] = array('#type' => 'submit', '#value' => t('Search'));
  $form['#redirect'] = FALSE;

  return $form;
586
}
587 588 589
/**
 * @} End of "locale-translate-seek"
 */
590

591 592 593 594
/**
 * @defgroup locale-translate-import Translation import screen.
 * @{
 */
595

596 597
/**
 * User interface for the translation import screen.
Dries's avatar
 
Dries committed
598
 */
599 600
function locale_translate_import_form() {
  // Get all languages, except English
601 602
  $names = locale_language_list('name', TRUE);
  unset($names['en']);
Dries's avatar
 
Dries committed
603

604
  if (!count($names)) {
605
    $languages = _locale_prepare_predefined_list();
606
    $default = array_shift(array_keys($languages));
Dries's avatar
 
Dries committed
607 608 609
  }
  else {
    $languages = array(
610
      t('Already added languages') => $names,
611
      t('Languages not yet added') => _locale_prepare_predefined_list()
Dries's avatar
 
Dries committed
612
    );
613
    $default = array_shift(array_keys($names));
Dries's avatar
 
Dries committed
614
  }
Steven Wittens's avatar
Locale:  
Steven Wittens committed
615

616
  $form = array();
617
  $form['import'] = array('#type' => 'fieldset',
drumm's avatar
drumm committed
618
    '#title' => t('Import translation'),
619 620 621 622
  );
  $form['import']['file'] = array('#type' => 'file',
    '#title' => t('Language file'),
    '#size' => 50,
623
    '#description' => t('A Gettext Portable Object (<em>.po</em>) file.'),
624 625 626
  );
  $form['import']['langcode'] = array('#type' => 'select',
    '#title' => t('Import into'),
627 628
    '#options' => $languages,
    '#default_value' => $default,
629 630 631 632 633 634 635
    '#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.'),
  );
  $form['import']['group'] = array('#type' => 'radios',
    '#title' => t('Text group'),
    '#default_value' => 'default',
    '#options' => module_invoke_all('locale', 'groups'),
    '#description' => t('Imported translations will be added to this text group.'),
636 637 638
  );
  $form['import']['mode'] = array('#type' => 'radios',
    '#title' => t('Mode'),
639 640 641 642 643
    '#default_value' => LOCALE_IMPORT_KEEP,
    '#options' => array(
      LOCALE_IMPORT_OVERWRITE => t('Strings in the uploaded file replace existing ones, new ones are added'),
      LOCALE_IMPORT_KEEP => t('Existing strings are kept, only new strings are added')
    ),
644 645
  );
  $form['import']['submit'] = array('#type' => 'submit', '#value' => t('Import'));
646
  $form['#attributes']['enctype'] = 'multipart/form-data';
647

648
  return $form;
Dries's avatar
 
Dries committed
649 650
}

651 652 653
/**
 * Process the locale import form submission.
 */
654
function locale_translate_import_form_submit($form, &$form_state) {
655
  // Ensure we have the file uploaded
656
  if ($file = file_save_upload('file')) {
657 658 659

    // Add language, if not yet supported
    $languages = language_list('language', TRUE);
660
    $langcode = $form_state['values']['langcode'];
661
    if (!isset($languages[$langcode])) {
662
      $predefined = _locale_get_predefined_list();
663 664
      locale_add_language($langcode);
      drupal_set_message(t('The language %language has been created.', array('%language' => t($predefined[$langcode][0]))));
665
    }
666

667
    // Now import strings into the language
668
    if ($ret = _locale_import_po($file, $langcode, $form_state['values']['mode'], $form_state['values']['group']) == FALSE) {
669 670 671
      $variables = array('%filename' => $file->filename);
      drupal_set_message(t('The translation import of %filename failed.', $variables), 'error');
      watchdog('locale', 'The translation import of %filename failed.', $variables, WATCHDOG_ERROR);
672 673 674 675
    }
  }
  else {
    drupal_set_message(t('File to import not found.'), 'error');
676
    return 'admin/build/translate/import';
677 678
  }

679 680
  $form_state['redirect'] = 'admin/build/translate';
  return;
681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704
}
/**
 * @} End of "locale-translate-import"
 */

/**
 * @defgroup locale-translate-export Translation export screen.
 * @{
 */

/**
 * User interface for the translation export screen.
 */
function locale_translate_export_screen() {
  // Get all languages, except English
  $names = locale_language_list('name', TRUE);
  unset($names['en']);
  $output = '';
  // Offer translation export if any language is set up.
  if (count($names)) {
    $output = drupal_get_form('locale_translate_export_po_form', $names);
  }
  $output .= drupal_get_form('locale_translate_export_pot_form');
  return $output;
705 706
}

707 708 709 710 711 712
/**
 * Form to export PO files for the languages provided.
 *
 * @param $names
 *   An associate array with localized language names
 */
713
function locale_translate_export_po_form(&$form_state, $names) {
714 715 716 717 718 719
  $form['export'] = array('#type' => 'fieldset',
    '#title' => t('Export translation'),
    '#collapsible' => TRUE,
  );
  $form['export']['langcode'] = array('#type' => 'select',
    '#title' => t('Language name'),
720
    '#options' => $names,
721
    '#description' => t('Select the language to export in Gettext Portable Object (<em>.po</em>) format.'),
722
  );
723 724 725 726 727
  $form['export']['group'] = array('#type' => 'radios',
    '#title' => t('Text group'),
    '#default_value' => 'default',
    '#options' => module_invoke_all('locale', 'groups'),
  );
728 729 730 731
  $form['export']['submit'] = array('#type' => 'submit', '#value' => t('Export'));
  return $form;
}

732 733 734 735
/**
 * Translation template export form.
 */
function locale_translate_export_pot_form() {
736 737 738 739
  // Complete template export of the strings
  $form['export'] = array('#type' => 'fieldset',
    '#title' => t('Export template'),
    '#collapsible' => TRUE,
740
    '#description' => t('Generate a Gettext Portable Object Template (<em>.pot</em>) file with all strings from the Drupal locale database.'),
741 742 743 744 745
  );
  $form['export']['group'] = array('#type' => 'radios',
    '#title' => t('Text group'),
    '#default_value' => 'default',
    '#options' => module_invoke_all('locale', 'groups'),
746 747
  );
  $form['export']['submit'] = array('#type' => 'submit', '#value' => t('Export'));
748
  // Reuse PO export submission callback.
749 750
  $form['#submit'][] = 'locale_translate_export_po_form_submit';
  $form['#validate'][] = 'locale_translate_export_po_form_validate';
751 752 753
  return $form;
}

754
/**
755
 * Process a translation (or template) export form submission.
756
 */
757
function locale_translate_export_po_form_submit($form, &$form_state) {
758
  // If template is required, language code is not given.
759 760 761 762 763 764
  $language = NULL;
  if (isset($form_state['values']['langcode'])) {
    $languages = language_list();
    $language = $languages[$form_state['values']['langcode']];
  }
  _locale_export_po($language, _locale_export_po_generate($language, _locale_export_get_strings($language, $form_state['values']['group'])));
765 766
}
/**
767
 * @} End of "locale-translate-export"
768
 */
769

770
/**
771 772
 * @defgroup locale-translate-edit Translation text editing
 * @{
773 774 775 776 777
 */

/**
 * User interface for string editing.
 */
778
function locale_translate_edit_form(&$form_state, $lid) {
779 780 781
  // Fetch source string, if possible.
  $source = db_fetch_object(db_query('SELECT source, textgroup, location FROM {locales_source} WHERE lid = %d', $lid));
  if (!$source) {
782 783
    drupal_set_message(t('String not found.'), 'error');
    drupal_goto('admin/build/translate/search');
784 785
  }

786 787 788 789 790
  // Add original text to the top and some values for form altering.
  $form = array(
    'original' => array(
      '#type'  => 'item',
      '#title' => t('Original text'),
791
      '#markup' => check_plain(wordwrap($source->source, 0)),
792 793 794 795 796 797 798 799 800 801 802 803 804
    ),
    'lid' => array(
      '#type'  => 'value',
      '#value' => $lid
    ),
    'textgroup' => array(
      '#type'  => 'value',
      '#value' => $source->textgroup,
    ),
    'location' => array(
      '#type'  => 'value',
      '#value' => $source->location
    ),
805 806
  );

807 808 809 810 811 812 813 814 815 816
  // Include default form controls with empty values for all languages.
  // This ensures that the languages are always in the same order in forms.
  $languages = language_list();
  $default = language_default();
  // We don't need the default language value, that value is in $source.
  $omit = $source->textgroup == 'default' ? 'en' : $default->language;
  unset($languages[($omit)]);
  $form['translations'] = array('#tree' => TRUE);
  // Approximate the number of rows to use in the default textarea.
  $rows = min(ceil(str_word_count($source->source) / 12), 10);
817 818
  foreach ($languages as $langcode => $language) {
    $form['translations'][$langcode] = array(
819
      '#type' => 'textarea',
820
      '#title' => t($language->name),
821
      '#rows' => $rows,
822
      '#default_value' => '',
823 824
    );
  }
825

826
  // Fetch translations and fill in default values in the form.
827
  $result = db_query("SELECT DISTINCT translation, language FROM {locales_target} WHERE lid = %d AND language <> '%s'", $lid, $omit);
828 829 830
  while ($translation = db_fetch_object($result)) {
    $form['translations'][$translation->language]['#default_value'] = $translation->translation;
  }
831 832

  $form['submit'] = array('#type' => 'submit', '#value' => t('Save translations'));
833
  return $form;
834 835 836 837 838 839
}

/**
 * Process string editing form submissions.
 * Saves all translations of one string submitted from a form.
 */
840 841 842
function locale_translate_edit_form_submit($form, &$form_state) {
  $lid = $form_state['values']['lid'];
  foreach ($form_state['values']['translations'] as $key => $value) {
843 844 845 846 847 848 849 850 851
    $translation = db_result(db_query("SELECT translation FROM {locales_target} WHERE lid = %d AND language = '%s'", $lid, $key));
    if (!empty($value)) {
      // Only update or insert if we have a value to use.
      if (!empty($translation)) {
        db_query("UPDATE {locales_target} SET translation = '%s' WHERE lid = %d AND language = '%s'", $value, $lid, $key);
      }
      else {
        db_query("INSERT INTO {locales_target} (lid, translation, language) VALUES (%d, '%s', '%s')", $lid, $value, $key);
      }
852
    }
853 854 855
    elseif (!empty($translation)) {
      // Empty translation entered: remove existing entry from database.
      db_query("DELETE FROM {locales_target} WHERE lid = %d AND language = '%s'", $lid, $key);
856
    }
857

858 859
    // Force JavaScript translation file recreation for this language.
    _locale_invalidate_js($key);
860
  }
861

862 863
  drupal_set_message(t('The string has been saved.'));

864
  // Clear locale cache.
865
  _locale_invalidate_js();
866
  cache_clear_all('locale:', 'cache', TRUE);
867

868 869
  $form_state['redirect'] = 'admin/build/translate/search';
  return;
870
}
871 872 873 874 875 876 877 878
/**
 * @} End of "locale-translate-edit"
 */

/**
 * @defgroup locale-translate-delete Translation delete interface.
 * @{
 */
879 880

/**
881
 * String deletion confirmation page.
882
 */
883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905
function locale_translate_delete_page($lid) {
  if ($source = db_fetch_object(db_query('SELECT * FROM {locales_source} WHERE lid = %d', $lid))) {
    return drupal_get_form('locale_translate_delete_form', $source);
  }
  else {
    return drupal_not_found();
  }
}

/**
 * User interface for the string deletion confirmation screen.
 */
function locale_translate_delete_form(&$form_state, $source) {
  $form['lid'] = array('#type' => 'value', '#value' => $source->lid);
  return confirm_form($form, t('Are you sure you want to delete the string "%source"?', array('%source' => $source->source)), 'admin/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'));
}

/**
 * Process string deletion submissions.
 */
function locale_translate_delete_form_submit($form, &$form_state) {
  db_query('DELETE FROM {locales_source} WHERE lid = %d', $form_state['values']['lid']);
  db_query('DELETE FROM {locales_target} WHERE lid = %d', $form_state['values']['lid']);
906 907
  // Force JavaScript translation file recreation for all languages.
  _locale_invalidate_js();
908
  cache_clear_all('locale:', 'cache', TRUE);
909
  drupal_set_message(t('The string has been removed.'));
910
  $fo