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

Dries's avatar
 
Dries committed
4 5
/**
 * @file
6 7
 *   Add language handling functionality and enables the translation of the
 *   user interface to languages other than English.
Dries's avatar
 
Dries committed
8
 *
9 10 11 12
 *   When enabled, multiple languages can be set up. The site interface
 *   can be displayed in different languages, as well as nodes can have languages
 *   assigned. The setup of languages and translations is completely web based.
 *   Gettext portable object files are supported.
Dries's avatar
 
Dries committed
13 14
 */

15 16 17 18 19 20 21 22 23 24 25
/**
 * Language written left to right. Possible value of $language->direction.
 */
define('LANGUAGE_LTR', 0);

/**
 * Language written right to left. Possible value of $language->direction.
 */
define('LANGUAGE_RTL', 1);


Dries's avatar
 
Dries committed
26
// ---------------------------------------------------------------------------------
27
// Hook implementations
Dries's avatar
 
Dries committed
28

Dries's avatar
 
Dries committed
29 30 31
/**
 * Implementation of hook_help().
 */
32 33
function locale_help($path, $arg) {
  switch ($path) {
34
    case 'admin/help#locale':
35
      $output = '<p>'. t('The locale module allows you to present your Drupal site in a language other than the default English. You can use it to set up a multi-lingual website or replace given <em>built-in</em> text with text which has been customized for your site. Whenever the locale module encounters text which needs to be displayed, it tries to translate it into the currently selected language. If a translation is not available, then the string is remembered, so you can look up untranslated strings easily.') .'</p>';
36
      $output .= '<p>'. t('The locale module provides two options for providing translations. The first is the integrated web interface, via which you can search for untranslated strings, and specify their translations. An easier and less time-consuming method is to import existing translations for your language. These translations are available as <em>GNU gettext Portable Object files</em> (<em>.po</em> files for short). Translations for many languages are available for download from the translation page.') .'</p>';
37
      $output .= '<p>'. t("If an existing translation does not meet your needs, the <em>.po</em> files are easily edited with special editing tools. The locale module's import feature allows you to add strings from such files into your site's database. The export functionality enables you to share your translations with others, generating Portable Object files from your site strings.") .'</p>';
38
      $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@locale">Locale module</a>.', array('@locale' => 'http://drupal.org/handbook/modules/locale/')) .'</p>';
39
      return $output;
40
    case 'admin/settings/language':
41 42 43 44
      $output = '<p>'. t('Drupal provides support for the translation of its interface text into different languages. This page provides an overview of the installed languages. You can add a language on the <a href="@add-language">add language page</a>, or directly by <a href="@import">importing a translation</a>. If multiple languages are enabled, registered users will be able to set their preferred language. The site default will be used for anonymous visitors and for users without their own settings.', array('@add-language' => url('admin/settings/language/add'), '@import' => url('admin/build/translate/import'))) .'</p>';
      $output .= '<p>'. t('Drupal interface translations may be added or extended by <a href="@import">importing</a> an existing translation, by <a href="@search">translating everything</a> from scratch, or by a combination of these approaches.', array('@import' => url('admin/build/translate/import'), '@search' => url('admin/build/translate/search'))) .'</p>';
      $output .= '<p>'. t('To quickly add additional languages to your site, a number of <a href="@translations">contributed translations</a> are available.', array('@translations' => 'http://drupal.org/project/translations')) .'</p>';
      return $output;
45
    case 'admin/settings/language/add':
46
      return '<p>'. t("You need to add all languages in which you would like to display the site interface. If you can't find the desired language in the quick-add dropdown, then you will need to provide the proper language code yourself. The language code may be used to negotiate with browsers and to present flags, etc., so it is important to pick a code that is standardized for the desired language. You can also add a language by <a href=\"@import\">importing a translation</a>.", array("@import" => url("admin/build/translate/import"))) .'</p>';
47 48 49
    case 'admin/settings/language/configure':
      return '<p>'. t('The language used to display a web page is determined with a negotiation algorithm. You can choose how this algorithm should work. By default, there is no negotiation and the default language is used. You can use path prefixes (like "de" and "it" for German and Italian content) with different fallback options, so you can have web addresses like /de/contact and /it/contact. Alternatively you can use custom domains like de.example.com and it.example.com. Customize path prefixes and set domain names on the <a href="@languages">language editing pages</a>.', array('@languages' => url('admin/settings/language'))) .'</p>';
    case 'admin/build/translate':
50 51 52
      $output = '<p>'. t("This page provides an overview of interface translation on the site. Drupal groups all translatable strings in so called 'text groups'. Modules may provide additional to the 'built-in interface' default, to separate strings used for other purposes, allowing you to focus your translation efforts on the groups of text you care most about. For example, a translation team could choose not to fully translate a text group that includes less important data, being still able to ensure that built-in interface is always fully translated.") .'</p>';
      $output .= '<p>'. t('To quickly add additional languages to your site, a number of <a href="@translations">contributed translations</a> are available.', array('@translations' => 'http://drupal.org/project/translations')) .'</p>';
      return $output;
53
    case 'admin/build/translate/import':
54
      return '<p>'. t('This page allows you to import a translation provided in the gettext Portable Object (.po) format. The easiest way to get your site translated is to obtain an existing Drupal translation and to import it. You can find existing translations on the <a href="@translations">Drupal translation page</a>. Note that importing a translation file might take a while.', array('@translations' => 'http://drupal.org/project/translations')) .'</p>';
55
    case 'admin/build/translate/export':
56
      return '<p>'. t('This page allows you to export Drupal strings. The first option is to export a translation so it can be shared. The second option generates a translation template, which contains all Drupal strings, but without their translations. You can use this template to start a new translation using various software packages designed for this task.') .'</p>';
57
    case 'admin/build/translate/search':
58
      return '<p>'. t('It is often convenient to get the strings from your setup on the <a href="@export">export page</a>, and use a desktop Gettext translation editor to edit the translations. On this page you can search in the translated and untranslated strings, and the default English texts provided by Drupal.', array('@export' => url('admin/build/translate/export'))) .'</p>';
59 60
    case 'admin/build/block/configure':
      if ($arg[4] == 'locale' && $arg[5] == 0) {
61
        return '<p>'. t("This block is only shown if you have <a href=\"@languages\">at least two languages enabled</a> and you have a <a href=\"@configuration\">language negotiation setting</a> other than 'none', so you have different web addresses for different language versions.", array('@languages' => url('admin/settings/language'), '@configuration' => url('admin/settings/language/configure'))) .'</p>';
62 63
      }
      break;
Dries's avatar
 
Dries committed
64
  }
Dries's avatar
 
Dries committed
65 66
}

Dries's avatar
 
Dries committed
67
/**
Dries's avatar
 
Dries committed
68
 * Implementation of hook_menu().
69 70 71
 *
 * Locale module only provides administrative menu items, so all
 * menu items are invoked through locale_inc_callback().
Dries's avatar
 
Dries committed
72
 */
73
function locale_menu() {
74 75
  // Manage languages
  $items['admin/settings/language'] = array(
76
    'title' => 'Languages',
77 78 79 80
    'description' => 'Configure languages for content and the user interface.',
    'page callback' => 'locale_inc_callback',
    'page arguments' => array('drupal_get_form', 'locale_languages_overview_form'),
    'access arguments' => array('administer languages'),
81
  );
82
  $items['admin/settings/language/overview'] = array(
83
    'title' => 'List',
84 85 86
    'weight' => 0,
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
87
  $items['admin/settings/language/add'] = array(
88
    'title' => 'Add language',
89 90
    'page callback' => 'locale_inc_callback',
    'page arguments' => array('locale_languages_add_screen'), // two forms concatenated
91 92 93
    'weight' => 5,
    'type' => MENU_LOCAL_TASK,
  );
94
  $items['admin/settings/language/configure'] = array(
95
    'title' => 'Configure',
96 97
    'page callback' => 'locale_inc_callback',
    'page arguments' => array('drupal_get_form', 'locale_languages_configure_form'),
98 99 100
    'weight' => 10,
    'type' => MENU_LOCAL_TASK,
  );
101
  $items['admin/settings/language/edit/%'] = array(
102
    'title' => 'Edit language',
103 104 105 106 107 108 109 110
    'page callback' => 'locale_inc_callback',
    'page arguments' => array('drupal_get_form', 'locale_languages_edit_form', 4),
    'type' => MENU_CALLBACK,
  );
  $items['admin/settings/language/delete/%'] = array(
    'title' => 'Confirm',
    'page callback' => 'locale_inc_callback',
    'page arguments' => array('drupal_get_form', 'locale_languages_delete_form', 4),
111 112 113
    'type' => MENU_CALLBACK,
  );

114 115 116 117 118 119 120 121 122 123
  // Translation functionality
  $items['admin/build/translate'] = array(
    'title' => 'Translate interface',
    'description' => 'Translate the built in interface as well as menu items and taxonomies.',
    'page callback' => 'locale_inc_callback',
    'page arguments' => array('locale_translate_overview_screen'), // not a form, just a table
    'access arguments' => array('translate interface'),
  );
  $items['admin/build/translate/overview'] = array(
    'title' => 'Overview',
124 125 126
    'weight' => 0,
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
127 128
  $items['admin/build/translate/search'] = array(
    'title' => 'Search',
129 130
    'weight' => 10,
    'type' => MENU_LOCAL_TASK,
131 132
    'page callback' => 'locale_inc_callback',
    'page arguments' => array('locale_translate_seek_screen'), // search results and form concatenated
133
  );
134 135 136 137
  $items['admin/build/translate/import'] = array(
    'title' => 'Import',
    'page callback' => 'locale_inc_callback',
    'page arguments' => array('drupal_get_form', 'locale_translate_import_form'),
138 139 140
    'weight' => 20,
    'type' => MENU_LOCAL_TASK,
  );
141 142 143 144 145 146
  $items['admin/build/translate/export'] = array(
    'title' => 'Export',
    'page callback' => 'locale_inc_callback',
    'page arguments' => array('locale_translate_export_screen'), // possibly multiple forms concatenated
    'weight' => 30,
    'type' => MENU_LOCAL_TASK,
147
  );
148
  $items['admin/build/translate/edit/%'] = array(
149
    'title' => 'Edit string',
150 151
    'page callback' => 'locale_inc_callback',
    'page arguments' => array('drupal_get_form', 'locale_translate_edit_form', 4),
152 153
    'type' => MENU_CALLBACK,
  );
154
  $items['admin/build/translate/delete/%'] = array(
155
    'title' => 'Delete string',
156 157
    'page callback' => 'locale_inc_callback',
    'page arguments' => array('locale_translate_delete', 4),  // directly deletes, no confirmation
158 159
    'type' => MENU_CALLBACK,
  );
Dries's avatar
 
Dries committed
160

Dries's avatar
 
Dries committed
161
  return $items;
Dries's avatar
 
Dries committed
162 163
}

164 165 166 167 168 169 170 171 172 173
/**
 * Wrapper function to be able to set callbacks in locale.inc
 */
function locale_inc_callback() {
  $args = func_get_args();
  $function = array_shift($args);
  include_once './includes/locale.inc';
  return call_user_func_array($function, $args);
}

Dries's avatar
 
Dries committed
174
/**
Dries's avatar
 
Dries committed
175
 * Implementation of hook_perm().
Dries's avatar
 
Dries committed
176
 */
Dries's avatar
 
Dries committed
177
function locale_perm() {
178 179 180 181 182 183 184 185 186 187 188
  return array('administer languages', 'translate interface');
}

/**
 * Implementation of hook_locale().
 */
function locale_locale($op = 'groups') {
  switch ($op) {
    case 'groups':
      return array('default' => t('Built-in interface'));
  }
Dries's avatar
 
Dries committed
189 190 191 192 193 194
}

/**
 * Implementation of hook_user().
 */
function locale_user($type, $edit, &$user, $category = NULL) {
195 196 197 198 199
  global $language;

  // If we have more then one language and either creating a user on the
  // admin interface or edit the user, show the language selector.
  if (variable_get('language_count', 1) > 1 && ($type == 'register' && user_access('administer users') || $type == 'form' && $category == 'account' )) {
200 201
    $languages = language_list('enabled');
    $languages = $languages['1'];
202 203

    // If the user is being created, we set the user language to the page language.
204
    $user_preferred_language = $user ? user_preferred_language($user) : $language;
205

206
    $names = array();
207 208 209
    foreach ($languages as $langcode => $item) {
      $name = t($item->name);
      $names[$langcode] = $name . ($item->native != $name ? ' ('. $item->native .')' : '');
Dries's avatar
 
Dries committed
210
    }
211 212 213
    $form['locale'] = array(
      '#type' => 'fieldset',
      '#title' => t('Language settings'),
214 215
      '#weight' => 1,
    );
Dries's avatar
Dries committed
216

217 218
    $form['locale']['language'] = array(
      '#type' => 'radios',
219
      '#title' => t('Language'),
220
      '#default_value' => $user_preferred_language->language,
221
      '#options' => $names,
222
      '#description' => t('Sets the default site interface and e-mail language for this account.'),
223
    );
224
    return $form;
Dries's avatar
 
Dries committed
225 226 227
  }
}

228 229 230
/**
 * Implementation of hook_form_alter(). Adds language fields to forms.
 */
231
function locale_form_alter(&$form, $form_state, $form_id) {
232
  switch ($form_id) {
233 234

    // Language field for paths
235
    case 'path_admin_form':
236
      $form['language'] = array(
237
        '#type' => 'select',
238 239
        '#title' => t('Language'),
        '#options' => array('' => t('All languages')) + locale_language_list('name'),
240 241 242
        '#default_value' => $form['language']['#value'],
        '#weight' => -10,
        '#description' => t('Path aliases added for languages take precedence over path aliases added for all languages for the same Drupal path.'),
243 244
      );
      break;
245 246 247 248 249 250 251 252 253

    // Language setting for content types
    case 'node_type_form':
      if (isset($form['identity']['type'])) {
        $form['workflow']['language'] = array(
          '#type' => 'radios',
          '#title' => t('Multilingual support'),
          '#default_value' => variable_get('language_'. $form['#node_type']->type, 0),
          '#options' => array(t('Disabled'), t('Enabled')),
254
          '#description' => t('Enable multilingual support for this content type. If enabled, a language selection field will be added to the editing form, allowing you to select from one of the <a href="!languages">enabled languages</a>. If disabled, new posts are saved with the default language. Existing content will not be affected by changing this option.', array('!languages' => url('admin/settings/language'))),
255 256 257 258 259 260
        );
      }
      break;

    // Language field for nodes
    default:
261
      if (isset($form['#id']) && $form['#id'] == 'node-form') {
262
        if (isset($form['#node']->type) && variable_get('language_'. $form['#node']->type, 0)) {
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
          $form['language'] = array(
            '#type' => 'select',
            '#title' => t('Language'),
            '#default_value' => (isset($form['#node']->language) ? $form['#node']->language : ''),
            '#options' => array('' => t('Language neutral')) + locale_language_list('name'),
          );
        }
        // Node type without language selector: assign the default for new nodes
        elseif (!isset($form['#node']->nid)) {
          $default = language_default();
          $form['language'] = array(
            '#type' => 'value',
            '#value' => $default->language
          );
        }
      }
279 280 281
  }
}

282 283 284 285 286 287 288 289 290 291
/**
 * Implementation of hook_theme()
 */
function locale_theme() {
  return array(
    'locale_languages_overview_form' => array(
      'arguments' => array('form' => array()),
    ),
  );
}
292

Dries's avatar
 
Dries committed
293
// ---------------------------------------------------------------------------------
294
// Locale core functionality
Dries's avatar
 
Dries committed
295

Dries's avatar
 
Dries committed
296
/**
297
 * Provides interface translation services.
Dries's avatar
 
Dries committed
298 299
 *
 * This function is called from t() to translate a string if needed.
300 301 302
 *
 * @param $string
 *   A string to look up translation for. If omitted, all the
303
 *   cached strings will be returned in all languages already
304 305 306
 *   used on the page.
 * @param $langcode
 *   Language code to use for the lookup.
Dries's avatar
 
Dries committed
307
 */
308
function locale($string = NULL, $langcode = NULL) {
309
  global $language;
Dries's avatar
 
Dries committed
310
  static $locale_t;
Dries's avatar
 
Dries committed
311

312 313 314 315 316
  // Return all cached strings if no string was specified
  if (!isset($string)) {
    return $locale_t;
  }

317 318
  $langcode = isset($langcode) ? $langcode : $language->language;

319
  // Store database cached translations in a static var.
320 321
  if (!isset($locale_t[$langcode])) {
    $locale_t[$langcode] = array();
322 323 324
    // Disabling the usage of string caching allows a module to watch for
    // the exact list of strings used on a page. From a performance
    // perspective that is a really bad idea, so we have no user
325 326
    // interface for this. Be careful when turning this option off!
    if (variable_get('locale_cache_strings', 1) == 1) {
327
      if ($cache = cache_get('locale:'. $langcode, 'cache')) {
328 329
        $locale_t[$langcode] = $cache->data;
      }
330 331 332 333 334 335 336 337 338 339
      else {
        // Refresh database stored cache of translations for given language.
        // We only store short strings used in current version, to improve
        // performance and consume less memory.
        $result = db_query("SELECT s.source, t.translation, t.language FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = '%s' WHERE s.textgroup = 'default' AND s.version = '%s' AND LENGTH(s.source) < 75", $langcode, VERSION);
        while ($data = db_fetch_object($result)) {
          $locale_t[$langcode][$data->source] = (empty($data->translation) ? TRUE : $data->translation);
        }
        cache_set('locale:'. $langcode, $locale_t[$langcode]);
      }
340
    }
Dries's avatar
 
Dries committed
341 342
  }

343 344
  // If we have the translation cached, skip checking the database
  if (!isset($locale_t[$langcode][$string])) {
Dries's avatar
 
Dries committed
345

346
    // We do not have this translation cached, so get it from the DB.
347
    $translation = db_fetch_object(db_query("SELECT s.lid, t.translation, s.version FROM {locales_source} s LEFT JOIN {locales_target} t ON s.lid = t.lid AND t.language = '%s' WHERE s.source = '%s' AND s.textgroup = 'default'", $langcode, $string));
348 349
    if ($translation) {
      // We have the source string at least.
350 351
      // Cache translation string or TRUE if no translation exists.
      $locale_t[$langcode][$string] = (empty($translation->translation) ? TRUE : $translation->translation);
352 353 354 355 356

      if ($translation->version != VERSION) {
        // This is the first use of this string under current Drupal version. Save version
        // and clear cache, to include the string into caching next time. Saved version is
        // also a string-history information for later pruning of the tables.
357
        db_query("UPDATE {locales_source} SET version = '%s' WHERE lid = %d", VERSION, $translation->lid);
358
        cache_clear_all('locale:', 'cache', TRUE);
359
      }
Dries's avatar
 
Dries committed
360 361
    }
    else {
362
      // We don't have the source string, cache this as untranslated.
363
      db_query("INSERT INTO {locales_source} (location, source, textgroup, version) VALUES ('%s', '%s', 'default', '%s')", request_uri(), $string, VERSION);
364 365
      $locale_t[$langcode][$string] = TRUE;
      // Clear locale cache so this string can be added in a later request.
366
      cache_clear_all('locale:', 'cache', TRUE);
Dries's avatar
 
Dries committed
367
    }
Dries's avatar
 
Dries committed
368 369
  }

370
  return ($locale_t[$langcode][$string] === TRUE ? $string : $locale_t[$langcode][$string]);
Dries's avatar
 
Dries committed
371
}
Dries's avatar
 
Dries committed
372

Dries's avatar
 
Dries committed
373
/**
374
 * Returns plural form index for a specific number.
Dries's avatar
 
Dries committed
375
 *
376
 * The index is computed from the formula of this language.
377
 *
378 379 380 381 382
 * @param $count
 *   Number to return plural for.
 * @param $langcode
 *   Optional language code to translate to a language other than
 *   what is used to display the page.
Dries's avatar
 
Dries committed
383
 */
384
function locale_get_plural($count, $langcode = NULL) {
385
  global $language;
Dries's avatar
 
Dries committed
386 387
  static $locale_formula, $plurals = array();

388
  $langcode = $langcode ? $langcode : $language->language;
389

390
  if (!isset($plurals[$langcode][$count])) {
Dries's avatar
 
Dries committed
391
    if (!isset($locale_formula)) {
392 393
      $language_list = language_list();
      $locale_formula[$langcode] = $language_list[$langcode]->formula;
Dries's avatar
 
Dries committed
394
    }
395
    if ($locale_formula[$langcode]) {
Dries's avatar
 
Dries committed
396
      $n = $count;
397 398
      $plurals[$langcode][$count] = @eval('return intval('. $locale_formula[$langcode] .');');
      return $plurals[$langcode][$count];
Dries's avatar
 
Dries committed
399 400
    }
    else {
401
      $plurals[$langcode][$count] = -1;
Dries's avatar
 
Dries committed
402
      return -1;
403
    }
Dries's avatar
 
Dries committed
404
  }
405
  return $plurals[$langcode][$count];
406
}
Dries's avatar
 
Dries committed
407

408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425

/**
 * Returns a language name
 */
function locale_language_name($lang) {
  static $list = NULL;
  if (!isset($list)) {
    $list = locale_language_list();
  }
  return ($lang && isset($list[$lang])) ? $list[$lang] : t('All');
}

/**
 * Returns array of language names
 *
 * @param $field
 *   'name' => names in current language, localized
 *   'native' => native names
426 427
 * @param $all
 *   Boolean to return all languages or only enabled ones
428
 */
429 430 431 432 433 434 435 436
function locale_language_list($field = 'name', $all = FALSE) {
  if ($all) {
    $languages = language_list();
  }
  else {
    $languages = language_list('enabled');
    $languages = $languages[1];
  }
437
  $list = array();
438
  foreach ($languages as $language) {
439 440 441 442
    $list[$language->language] = ($field == 'name') ? t($language->name) : $language->$field;
  }
  return $list;
}
443 444 445 446 447 448 449 450 451 452 453 454 455

/**
 * Imports translations when new modules or themes are installed or enabled.
 *
 * This function will either import translation for the component change
 * right away, or start a batch if more files need to be imported.
 *
 * @param $components
 *   An array of component (theme and/or module) names to import
 *   translations for.
 */
function locale_system_update($components) {
  include_once 'includes/locale.inc';
456
  if ($batch = locale_batch_by_component($components)) {
457 458 459 460
    batch_set($batch);
  }
}

461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 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 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528
/**
 * Update JavaScript translation file, if required, and add it to the page.
 *
 * This function checks all JavaScript files currently added via drupal_add_js()
 * and invokes parsing if they have not yet been parsed for Drupal.t()
 * and Drupal.formatPlural() calls. Also refreshes the JavaScript translation
 * file if necessary, and adds it to the page.
 */
function locale_update_js_files() {
  global $language;

  $dir = file_create_path(variable_get('locale_js_directory', 'languages'));
  $parsed = variable_get('javascript_parsed', array());

  // The first three parameters are NULL in order to get an array with all
  // scopes. This is necessary to prevent recreation of JS translation files
  // when new files are added for example in the footer.
  $javascript = drupal_add_js(NULL, NULL, NULL);
  $files = $new_files = FALSE;

  foreach ($javascript as $scope) {
    foreach ($scope as $type => $data) {
      if ($type != 'setting' && $type != 'inline') {
        foreach ($data as $filepath => $info) {
          $files = TRUE;
          if (!in_array($filepath, $parsed)) {
            // Don't parse our own translations files.
            if (substr($filepath, 0, strlen($dir)) != $dir) {
              locale_inc_callback('_locale_parse_js_file', $filepath);
              watchdog('locale', 'Parsed JavaScript file %file.', array('%file' => $filepath));
              $parsed[] = $filepath;
              $new_files = TRUE;
            }
          }
        }
      }
    }
  }

  // If there are any new source files we parsed, invalidate existing
  // JavaScript translation files for all languages, adding the refresh
  // flags into the existing array.
  if ($new_files) {
    $parsed += locale_inc_callback('_locale_invalidate_js');
  }

  // If necessary, rebuild the translation file for the current language.
  if (!empty($parsed['refresh:'. $language->language])) {
    // Don't clear the refresh flag on failure, so that another try will
    // be performed later.
    if (locale_inc_callback('_locale_rebuild_js')) {
      unset($parsed['refresh:'. $language->language]);
    }
    // Store any changes after refresh was attempted.
    variable_set('javascript_parsed', $parsed);
  }
  // If no refresh was attempted, but we have new source files, we need
  // to store them too. This occurs if current page is in English.
  else if ($new_files) {
    variable_set('javascript_parsed', $parsed);
  }

  // Add the translation JavaScript file to the page.
  if ($files && !empty($language->javascript)) {
    drupal_add_js($dir .'/'. $language->language .'_'. $language->javascript .'.js', 'core');
  }
}

529 530 531 532 533 534 535 536 537 538
// ---------------------------------------------------------------------------------
// Language switcher block

/**
 * Implementation of hook_block().
 * Displays a language switcher. Translation links may be provided by other modules.
 */
function locale_block($op = 'list', $delta = 0) {
  if ($op == 'list') {
    $block[0]['info'] = t('Language switcher');
539 540
    // Not worth caching.
    $block[0]['cache'] = BLOCK_NO_CACHE;
541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556
    return $block;
  }

  // Only show if we have at least two languages and language dependent
  // web addresses, so we can actually link to other language versions.
  elseif ($op == 'view' && variable_get('language_count', 1) > 1 && variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE) != LANGUAGE_NEGOTIATION_NONE) {
    $languages = language_list('enabled');
    $links = array();
    foreach ($languages[1] as $language) {
      $links[$language->language] = array(
        'href'       => $_GET['q'],
        'title'      => $language->native,
        'language'   => $language,
        'attributes' => array('class' => 'language-link'),
      );
    }
557

558 559 560 561 562
    // Allow modules to provide translations for specific links.
    // A translation link may need to point to a different path or use
    // a translated link text before going through l(), which will just
    // handle the path aliases.
    drupal_alter('translation_link', $links, $_GET['q']);
563

564 565 566 567 568
    $block['subject'] = t('Languages');
    $block['content'] = theme('links', $links, array());
    return $block;
  }
}