i18n.module 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. <?php
  2. /**
  3. * @file
  4. * Internationalization (i18n) module.
  5. *
  6. * This module extends multilingual support being the base module for the i18n package.
  7. * - Multilingual variables
  8. * - Extended languages for nodes
  9. * - Extended language API
  10. *
  11. * @author Jose A. Reyero, 2004
  12. */
  13. // All multilingual options disabled
  14. define('I18N_LANGUAGE_DISABLED', 0);
  15. // Language list will include all enabled languages
  16. define('I18N_LANGUAGE_ENABLED', 1);
  17. // Language list will include also disabled languages
  18. define('I18N_LANGUAGE_EXTENDED', 4);
  19. // Disabled languages will be hidden when possible
  20. define('I18N_LANGUAGE_HIDDEN', 8);
  21. // All defined languages will be allowed but hidden when possible
  22. define('I18N_LANGUAGE_EXTENDED_NOT_DISPLAYED', I18N_LANGUAGE_EXTENDED | I18N_LANGUAGE_HIDDEN);
  23. // No multilingual options
  24. define('I18N_MODE_NONE', 0);
  25. // Localizable object. Run through the localization system
  26. define('I18N_MODE_LOCALIZE', 1);
  27. // Predefined language for this object and all related ones.
  28. define('I18N_MODE_LANGUAGE', 2);
  29. // Multilingual objects, translatable but not localizable.
  30. define('I18N_MODE_TRANSLATE', 4);
  31. // Objects are translatable (if they have language or localizable if not)
  32. define('I18N_MODE_MULTIPLE', I18N_MODE_LOCALIZE | I18N_MODE_TRANSLATE);
  33. /**
  34. * Global variable for i18n context language.
  35. */
  36. define('I18N_LANGUAGE_TYPE_CONTEXT', 'i18n_language_context');
  37. /**
  38. * Implements hook_boot().
  39. */
  40. function i18n_boot() {
  41. // Just make sure the module is loaded for boot and the API is available.
  42. }
  43. /**
  44. * Implements hook_hook_info().
  45. */
  46. function i18n_hook_info() {
  47. $hooks['i18n_object_info'] = array(
  48. 'group' => 'i18n',
  49. );
  50. return $hooks;
  51. }
  52. /**
  53. * WARNING: Obsolete function, use other i18n_language_* instead.
  54. *
  55. * Get global language object, make sure it is initialized
  56. *
  57. * @param $language
  58. * Language code or language object to convert to valid language object
  59. */
  60. function i18n_language($language = NULL) {
  61. if ($language && ($language_object = i18n_language_object($language))) {
  62. return $language_object;
  63. }
  64. else {
  65. return i18n_language_interface();
  66. }
  67. }
  68. /**
  69. * Get language object from language code or object.
  70. *
  71. * @param $language
  72. * Language code or language object to convert to valid language object.
  73. * @return
  74. * Language object if this is an object or a valid language code.
  75. */
  76. function i18n_language_object($language) {
  77. if (is_object($language)) {
  78. return $language;
  79. }
  80. else {
  81. $list = language_list();
  82. return isset($list[$language]) ? $list[$language] : NULL;
  83. }
  84. }
  85. /**
  86. * Get interface language, make sure it is initialized.
  87. */
  88. function i18n_language_interface() {
  89. if (empty($GLOBALS[LANGUAGE_TYPE_INTERFACE])) {
  90. // We don't have language yet, initialize the language system and retry
  91. drupal_bootstrap(DRUPAL_BOOTSTRAP_LANGUAGE);
  92. }
  93. return $GLOBALS[LANGUAGE_TYPE_INTERFACE];
  94. }
  95. /**
  96. * Get content language, make sure it is initialized.
  97. */
  98. function i18n_language_content() {
  99. if (empty($GLOBALS[LANGUAGE_TYPE_CONTENT])) {
  100. // We don't have language yet, initialize the language system and retry
  101. drupal_bootstrap(DRUPAL_BOOTSTRAP_LANGUAGE);
  102. }
  103. return $GLOBALS[LANGUAGE_TYPE_CONTENT];
  104. }
  105. /**
  106. * Get / set language from current context / content.
  107. *
  108. * Depending on the page content we may need to use a different language for some operations.
  109. *
  110. * This should be the language of the specific page content. I.e. node language for node pages
  111. * or term language for taxonomy term page.
  112. *
  113. * @param $language
  114. * Optional language object to set for context.
  115. * @return
  116. * Context language object, which defaults to content language.
  117. *
  118. * @see hook_i18n_context_language().
  119. */
  120. function i18n_language_context($language = NULL) {
  121. if ($language) {
  122. $GLOBALS[I18N_LANGUAGE_TYPE_CONTEXT] = $language;
  123. }
  124. elseif (!isset($GLOBALS[I18N_LANGUAGE_TYPE_CONTEXT])) {
  125. // It will default to content language.
  126. $GLOBALS[I18N_LANGUAGE_TYPE_CONTEXT] = i18n_language_content();
  127. // Get language from the first module that provides it.
  128. foreach (module_implements('i18n_context_language') as $module) {
  129. if ($language = module_invoke($module, 'i18n_context_language')) {
  130. $GLOBALS[I18N_LANGUAGE_TYPE_CONTEXT] = $language;
  131. break;
  132. }
  133. }
  134. }
  135. return $GLOBALS[I18N_LANGUAGE_TYPE_CONTEXT];
  136. }
  137. /**
  138. * Menu object loader, language
  139. */
  140. function i18n_language_load($langcode) {
  141. $list = language_list();
  142. return isset($list[$langcode]) ? $list[$langcode] : FALSE;
  143. }
  144. /**
  145. * Get language selector form element
  146. */
  147. function i18n_element_language_select($default = LANGUAGE_NONE) {
  148. if (is_object($default) || is_array($default)) {
  149. $default = i18n_object_langcode($default, LANGUAGE_NONE);
  150. }
  151. return array(
  152. '#type' => 'select',
  153. '#title' => t('Language'),
  154. '#default_value' => $default,
  155. '#options' => array(LANGUAGE_NONE => t('Language neutral')) + i18n_language_list(),
  156. );
  157. }
  158. /**
  159. * Get language field for hook_field_extra_fields()
  160. */
  161. function i18n_language_field_extra() {
  162. return array(
  163. 'form' => array(
  164. 'language' => array(
  165. 'label' => t('Language'),
  166. 'description' => t('Language selection'),
  167. 'weight' => 0,
  168. ),
  169. ),
  170. 'display' => array(
  171. 'language' => array(
  172. 'label' => t('Language'),
  173. 'description' => t('Language'),
  174. 'weight' => 0,
  175. ),
  176. ),
  177. );
  178. }
  179. /**
  180. * Get full language list
  181. *
  182. * @todo See about creating a permission for seeing disabled languages
  183. */
  184. function i18n_language_list($field = 'name', $mode = NULL) {
  185. $mode = isset($mode) ? $mode : variable_get('i18n_language_list', I18N_LANGUAGE_ENABLED);
  186. return locale_language_list($field, I18N_LANGUAGE_EXTENDED & $mode);
  187. }
  188. /**
  189. * Get language name for any defined (enabled or not) language
  190. *
  191. * @see locale_language_list()
  192. */
  193. function i18n_language_name($lang) {
  194. $list = &drupal_static(__FUNCTION__);
  195. if (!isset($list)) {
  196. $list = locale_language_list('name', TRUE);
  197. }
  198. if (!$lang || $lang === LANGUAGE_NONE) {
  199. return t('Undefined');
  200. }
  201. elseif (isset($list[$lang])) {
  202. return check_plain($list[$lang]);
  203. }
  204. else {
  205. return t('Unknown');
  206. }
  207. }
  208. /**
  209. * Get valid language code for current page or check whether the code is a defined language
  210. */
  211. function i18n_langcode($langcode = NULL) {
  212. return $langcode && $langcode !== LANGUAGE_NONE ? $langcode : i18n_language()->language;
  213. }
  214. /**
  215. * Implements hook_help().
  216. */
  217. function i18n_help($path = 'admin/help#i18n', $arg) {
  218. switch ($path) {
  219. case 'admin/help#i18n' :
  220. $output = '<p>' . t('This module improves support for multilingual content in Drupal sites:') . '</p>';
  221. $output .= '<ul>';
  222. $output .= '<li>' . t('Shows content depending on page language.') . '</li>';
  223. $output .= '<li>' . t('Handles multilingual variables.') . '</li>';
  224. $output .= '<li>' . t('Extended language option for chosen content types. For these content types transations will be allowed for all defined languages, not only for enabled ones.') . '</li>';
  225. $output .= '<li>' . t('Provides a block for language selection and two theme functions: <em>i18n_flags</em> and <em>i18n_links</em>.') . '</li>';
  226. $output .= '</ul>';
  227. $output .= '<p>' . t('This is the base module for several others adding different features:') . '</p>';
  228. $output .= '<ul>';
  229. $output .= '<li>' . t('Multilingual menu items.') . '</li>';
  230. $output .= '<li>' . t('Multilingual taxonomy adds a language field for taxonomy vocabularies and terms.') . '</li>';
  231. $output .= '</ul>';
  232. $output .= '<p>' . t('For more information, see the online handbook entry for <a href="@i18n">Internationalization module</a>.', array('@i18n' => 'http://drupal.org/node/133977')) . '</p>';
  233. return $output;
  234. case 'admin/config/language/i18n':
  235. $output = '<ul>';
  236. $output .= '<li>' . t('To enable multilingual support for specific content types go to <a href="@configure_content_types">configure content types</a>.', array('@configure_content_types' => url('admin/structure/types'))) . '</li>';
  237. $output .= '</ul>';
  238. return $output;
  239. }
  240. }
  241. /**
  242. * Implements hook_menu().
  243. */
  244. function i18n_menu() {
  245. $items['admin/config/regional/i18n'] = array(
  246. 'title' => 'Multilingual settings',
  247. 'description' => 'Configure extended options for multilingual content and translations.',
  248. 'page callback' => 'drupal_get_form',
  249. 'page arguments' => array('variable_module_form', 'i18n'),
  250. 'access arguments' => array('administer site configuration'),
  251. 'weight' => 10,
  252. );
  253. $items['admin/config/regional/i18n/configure'] = array(
  254. 'title' => 'Multilingual system',
  255. 'description' => 'Configure extended options for multilingual content and translations.',
  256. 'type' => MENU_DEFAULT_LOCAL_TASK,
  257. );
  258. module_load_include('pages.inc', 'i18n');
  259. $items += i18n_page_menu_items();
  260. return $items;
  261. }
  262. /**
  263. * Simple i18n API
  264. */
  265. /**
  266. * Switch select Mode on off if enabled
  267. *
  268. * Usage for disabling content selection for a while then return to previous state
  269. *
  270. * // Disable selection, but store previous mode
  271. * $previous = i18n_select(FALSE);
  272. *
  273. * // Other code to be run without content selection here
  274. * ..........................
  275. *
  276. * // Return to previous mode
  277. * i18n_select($previous);
  278. *
  279. * @param $value
  280. * Optional, enable/disable selection: TRUE/FALSE
  281. * @return boolean
  282. * Previous selection mode (TRUE/FALSE)
  283. */
  284. function i18n_select($value = NULL) {
  285. static $mode = FALSE;
  286. if (isset($value)) {
  287. $previous = $mode;
  288. $mode = $value;
  289. return $previous;
  290. }
  291. else {
  292. return $mode;
  293. }
  294. }
  295. /**
  296. * Get language properties.
  297. *
  298. * @param $code
  299. * Language code.
  300. * @param $property
  301. * It may be 'name', 'native', 'ltr'...
  302. */
  303. function i18n_language_property($code, $property) {
  304. $languages = language_list();
  305. return isset($languages[$code]->$property) ? $languages[$code]->$property : NULL;
  306. }
  307. /**
  308. * Implements hook_preprocess_html().
  309. */
  310. function i18n_preprocess_html(&$variables) {
  311. global $language;
  312. $variables['classes_array'][] = 'i18n-' . $language->language;
  313. }
  314. /**
  315. * Translate or update user defined string. Entry point for i18n_string API if enabled.
  316. *
  317. * This function is from i18n_string sub module and is subject to be moved back.
  318. *
  319. * @param $name
  320. * Textgroup and context glued with ':'.
  321. * @param $default
  322. * String in default language. Default language may or may not be English.
  323. * @param $options
  324. * An associative array of additional options, with the following keys:
  325. * - 'langcode' (defaults to the current language) The language code to translate to a language other than what is used to display the page.
  326. * - 'filter' Filtering callback to apply to the translated string only
  327. * - 'format' Input format to apply to the translated string only
  328. * - 'callback' Callback to apply to the result (both to translated or untranslated string
  329. * - 'update' (defaults to FALSE) Whether to update source table
  330. * - 'translate' (defaults to TRUE) Whether to return a translation
  331. *
  332. * @return $string
  333. * Translated string, $string if not found
  334. */
  335. function i18n_string($name, $string, $options = array()) {
  336. $options += array('translate' => TRUE, 'update' => FALSE);
  337. if ($options['update']) {
  338. $result = function_exists('i18n_string_update') ? i18n_string_update($name, $string, $options) : FALSE;
  339. }
  340. if ($options['translate']) {
  341. $result = function_exists('i18n_string_translate') ? i18n_string_translate($name, $string, $options) : $string;
  342. }
  343. return $result;
  344. }
  345. /**
  346. * Get object wrapper.
  347. *
  348. * Create an object wrapper or retrieve it from the static cache if
  349. * a wrapper for the same object was created before.
  350. *
  351. * @see i18n_object_info()
  352. *
  353. * @param $type
  354. * The object type.
  355. */
  356. function i18n_object($type, $object) {
  357. $key = i18n_object_key($type, $object);
  358. return i18n_get_object($type, $key, $object);
  359. }
  360. /**
  361. * Get object wrapper by object key.
  362. *
  363. * @param $type
  364. * The object type to load e.g. node_type, menu, taxonomy_term.
  365. * @param $key
  366. * The object key, can be an scalar or an array.
  367. * @param $object
  368. * Optional Drupal object or array. It will be autoloaded using the key if not present.
  369. *
  370. * @return
  371. * A fully-populated object wrapper.
  372. */
  373. function i18n_get_object($type, $key, $object = NULL) {
  374. $cache = &drupal_static(__FUNCTION__);
  375. $index = is_array($key) ? implode(':', $key) : $key;
  376. if (!isset($cache[$type][$index])) {
  377. $class = i18n_object_info($type, 'class', 'i18n_object_wrapper');
  378. $cache[$type][$index] = new $class($type, $key, $object);
  379. }
  380. return $cache[$type][$index];
  381. }
  382. /**
  383. * Get object language code
  384. *
  385. * @param $object
  386. * Object or array having language field or plain language field
  387. * @param $default
  388. * What value to return if the object doesn't have a valid language
  389. */
  390. function i18n_object_langcode($object, $default = FALSE, $field = 'language') {
  391. $value = i18n_object_field($object, $field, $default);
  392. return $value && $value != LANGUAGE_NONE ? $value : $default;
  393. }
  394. /**
  395. * Get translation information for objects
  396. */
  397. function i18n_object_info($type = NULL, $property = NULL, $default = NULL) {
  398. $info = &drupal_static(__FUNCTION__);
  399. if (!$info) {
  400. $info = module_invoke_all('i18n_object_info');
  401. drupal_alter('i18n_object_info', $info);
  402. }
  403. if ($property) {
  404. return isset($info[$type][$property]) ? $info[$type][$property] : $default;
  405. }
  406. elseif ($type) {
  407. return isset($info[$type]) ? $info[$type] : array();
  408. }
  409. else {
  410. return $info;
  411. }
  412. }
  413. /**
  414. * Get field value from object/array
  415. */
  416. function i18n_object_field($object, $field, $default = NULL) {
  417. if (is_array($field)) {
  418. // We can handle a list of fields too. This is useful for multiple keys (like blocks)
  419. foreach ($field as $key => $name) {
  420. $values[$key] = i18n_object_field($object, $name);
  421. }
  422. return $values;
  423. }
  424. elseif (strpos($field, '.')) {
  425. // Access nested properties with the form 'name1.name2..', will map to $object->name1->name2...
  426. $names = explode('.', $field);
  427. $current = array_shift($names);
  428. if ($nested = i18n_object_field($object, $current)) {
  429. return i18n_object_field($nested, implode('.', $names), $default);
  430. }
  431. else {
  432. return $default;
  433. }
  434. }
  435. elseif (is_object($object)) {
  436. return isset($object->$field) ? $object->$field : $default;
  437. }
  438. elseif (is_array($object)) {
  439. return isset($object[$field]) ? $object[$field] : $default;
  440. }
  441. else {
  442. return $default;
  443. }
  444. }
  445. /**
  446. * Get key value from object/array
  447. */
  448. function i18n_object_key($type, $object, $default = NULL) {
  449. if ($field = i18n_object_info($type, 'key')) {
  450. return i18n_object_field($object, $field, $default);
  451. }
  452. else {
  453. return $default;
  454. }
  455. }
  456. /**
  457. * Menu access callback for mixed translation tab
  458. */
  459. function i18n_object_translate_access($type, $object) {
  460. return i18n_object($type, $object)->get_translate_access();
  461. }
  462. /**
  463. * Get translations for path.
  464. *
  465. * @param $path
  466. * Path to get translations for or '<front>' for front page.
  467. */
  468. function i18n_get_path_translations($path) {
  469. $translations = &drupal_static(__FUNCTION__);
  470. if (!isset($translations[$path])) {
  471. $translations[$path] = array();
  472. foreach (module_implements('i18n_translate_path') as $module) {
  473. $translated = call_user_func($module . '_i18n_translate_path', $path);
  474. // Add into the array, if two modules returning a translation first takes precedence.
  475. if ($translated) {
  476. $translations[$path] += $translated;
  477. }
  478. }
  479. // Chance for altering the results.
  480. drupal_alter('i18n_translate_path', $translations[$path], $path);
  481. }
  482. return $translations[$path];
  483. }
  484. /**
  485. * Implements hook_language_switch_links_alter().
  486. *
  487. * Replaces links with pointers to translated versions of the content.
  488. */
  489. function i18n_language_switch_links_alter(array &$links, $type, $path) {
  490. // For the front page we have nothing to add to Drupal core links.
  491. if ($path != '<front>' && ($translations = i18n_get_path_translations($path))) {
  492. foreach ($translations as $langcode => $translation) {
  493. if (isset($links[$langcode])) {
  494. $links[$langcode]['href'] = $translation['href'];
  495. if (!empty($translation['title'])) {
  496. $links[$langcode]['attributes']['title'] = $translation['title'];
  497. }
  498. }
  499. }
  500. }
  501. }
  502. /**
  503. * Build translation link
  504. */
  505. function i18n_translation_link($path, $langcode, $link = array()) {
  506. $language = i18n_language_object($langcode);
  507. $link += array(
  508. 'href' => $path,
  509. 'title' => $language->native,
  510. 'language' => $language,
  511. 'i18n_translation' => TRUE,
  512. );
  513. $link['attributes']['class'] = array('language-link');
  514. // @todo Fix languageicons weight, but until that
  515. if (function_exists('languageicons_link_add')) {
  516. languageicons_link_add($link);
  517. }
  518. return $link;
  519. }
  520. /**
  521. * Implements hook_form_FORM_ID_alter().
  522. */
  523. function i18n_form_block_admin_display_form_alter(&$form, &$form_state) {
  524. $form['#submit'][] = 'i18n_form_block_admin_display_form_submit';
  525. }
  526. /**
  527. * Display a help message when enabling the language switcher block.
  528. */
  529. function i18n_form_block_admin_display_form_submit($form, &$form_state) {
  530. foreach ($form_state['values']['blocks'] as $key => $block) {
  531. $previous = $form['blocks'][$key]['region']['#default_value'];
  532. if (empty($previous) && $block['region'] != -1 && $block['module'] == 'locale') {
  533. $message = t('The language switcher will appear only after configuring <a href="!url">language detection</a>. You need to enable at least one method that alters URLs like <em>URL</em> or <em>Session</em>.', array('!url' => url('admin/config/regional/language/configure')));
  534. drupal_set_message($message, 'warning', FALSE);
  535. break;
  536. }
  537. }
  538. }
  539. /**
  540. * Normal path should be checked with menu item's language to avoid
  541. * troubles when a node and it's translation has the same url alias.
  542. */
  543. function i18n_prepare_normal_path($link_path, $language) {
  544. $normal_path = drupal_get_normal_path($link_path, $language);
  545. if ($link_path != $normal_path) {
  546. drupal_set_message(t('The menu system stores system paths only, but will use the URL alias for display. %link_path has been stored as %normal_path', array('%link_path' => $link_path, '%normal_path' => $normal_path)));
  547. }
  548. return $normal_path;
  549. }
  550. /**
  551. * Checks if an entity translation is enabled for the given entity type.
  552. * @param $entity_type
  553. */
  554. function i18n_entity_translation_enabled($entity_type) {
  555. $cache = &drupal_static(__FUNCTION__);
  556. if (!isset($cache[$entity_type])) {
  557. // Check if the entity_translation module exists and if so if the given
  558. // entity type is handled.
  559. $cache[$entity_type] = module_exists('entity_translation') && entity_translation_enabled($entity_type);
  560. }
  561. return $cache[$entity_type];
  562. }
  563. /**
  564. * Implements hook_modules_enabled().
  565. */
  566. function i18n_modules_enabled($modules) {
  567. drupal_static_reset('i18n_object_info');
  568. }