6 * Internationalization (i18n) module
8 * This module extends multilingual support being the base module for the i18n package.
9 * - Multilingual variables
10 * - Extended languages for nodes
11 * - Extended language API
13 * @author Jose A. Reyero, 2004
17 // Some constants. Language support modes for content
18 define('LANGUAGE_SUPPORT_NONE', 0);
19 define('LANGUAGE_SUPPORT_NORMAL', 1);
20 define('LANGUAGE_SUPPORT_EXTENDED', 2);
23 * Implementation of hook_init()
25 * Will initialize language dependent variables
26 * Modify rewriting conditions when viewing specific nodes
29 // If not in bootstrap, variable init
30 if(!_i18n_is_bootstrap()){
31 //include drupal_get_path('module', 'i18n').'/i18n.inc';
34 if (arg(0) == 'node') {
35 if(isset($_POST['language']) && $_POST['language']) {
36 $pagelang = $_POST['language'];
37 } elseif( is_numeric(arg(1)) && $node = node_load(arg(1)) ) {
38 // Node language when loading specific nodes
39 $pagelang = $node->language
;
41 if(isset($pagelang)) i18n_selection_mode('node', db_escape_string($pagelang));
42 } elseif(arg(0) == 'admin' && arg(0) == 'content' && user_access('administer all languages')) {
43 // No restrictions for administration pages
44 i18n_selection_mode('off');
49 * Implementation of hook_help().
51 function i18n_help($section = 'admin/help#i18n' ) {
53 case
'admin/help#i18n' :
54 $output = '<p>'.
t('This module improves support for multilingual content in Drupal sites:').
'</p>';
56 $output .
= '<li>'.
t('Shows content depending on page language').
'</li>';
57 $output .
= '<li>'.
t('Handles multilingual variables').
'</li>';
58 $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>';
59 $output .
= '<li>'.
t('Provides a block for language selection and two theme functions: <i>i18n_flags</i> and <i>i18n_links</i>').
'</li>';
61 $output .
= '<p>'.
t('This is the base module for several others adding different features:').
'</p>';
63 $output .
= '<li>'.
t('Multilingual menu items').
'</li>';
64 $output .
= '<li>'.
t('Multilingual taxonomy Adds a language field for taxonomy vocabularies and terms').
'</li>';
66 $output .
= '<p>'.
t('For more information please read the <a href="@i18n">on-line help pages</a>.', array('@i18n' =>'http://drupal.org/node/31631')) .
'</p>';
68 case
'admin/settings/i18n':
70 $output .
= '<li>'.
t('To manage languages go to the !configure_languages', array('!configure_languages' => l(t('Languages configuration page'), 'admin/settings/language'))).
'</li>';
71 $output .
= '<li>'.
t('To enable multilingual support for specific content types go to !configure_content_types.', array('!configure_content_types' => l(t('configure content types'), 'admin/content/types'), )).
'</li>';
78 * Implementation of hook_menu().
80 function i18n_menu() {
81 $items['admin/settings/i18n'] = array(
82 'title' => 'Multilingual system',
83 'description' => 'Configure extended options for multilingual content and translations.',
84 'page callback' => 'drupal_get_form',
85 'page arguments' => array('i18n_admin_settings'),
86 'access arguments' => array('administer site configuration'),
87 'file' => 'i18n.admin.inc',
89 $items['admin/settings/i18n/main'] = array(
90 'title' => 'Internationalization',
91 'type' => MENU_DEFAULT_LOCAL_TASK
,
98 * Implementation of hook_menu_alter().
100 * Take over the node translation page
102 function i18n_menu_alter(&$items) {
103 // dsm($router_items);
105 $items['node/%node/translate']['page callback'] = 'i18n_translation_node_overview';
106 $items['node/%node/translate']['file'] = 'i18n.pages.inc';
107 $items['node/%node/translate']['module'] = 'i18n';
111 $items['node/%node/translate'] = array(
112 'title' => 'Translate',
113 'page callback' => 'translation_node_overview',
114 'page arguments' => array(1),
115 'access callback' => '_translation_tab_access',
116 'access arguments' => array(1),
117 'type' => MENU_LOCAL_TASK,
119 'file' => 'i18n.pages.inc',
126 * Implementation of hook_nodeapi().
129 function i18n_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
130 if (variable_get("i18n_node_$node->type", 0)) {
133 return db_fetch_array(db_query("SELECT trid, language, status AS i18n_status FROM {i18n_node} WHERE nid=%d", $node->nid));
136 db_query("DELETE FROM {i18n_node} WHERE nid=%d",$node->nid);
137 if ($node->language){
138 // Assign a trid from the beginning
139 db_query("INSERT INTO {i18n_node} (nid, trid, language, status) VALUES(%d, '%d', '%s', '%d')", $node->nid, $node->trid, $node->language, $node->i18n_status);
141 // Handle menu items. Fixes duplication issue and language for menu items which happens when editing nodes in languages other than current.
142 if (isset($node->menu) && !$node->menu['delete'] && $node->menu['title']) {
144 $item['path'] = ($item['path']) ? $item['path'] : "node/$node->nid";
145 $item['type'] = $item['type'] | MENU_MODIFIED_BY_ADMIN;
148 db_query("UPDATE {menu} SET pid = %d, path = '%s', title = '%s', description = '%s', weight = %d, type = %d, language = '%s' WHERE mid = %d", $item['pid'], $item['path'], $item['title'], $item['description'], $item['weight'], $item['type'], $node->language, $item['mid']);
149 drupal_set_message(t('The menu item %title has been updated with node language.', array('%title' => $item['title'])));
150 } elseif(SAVED_NEW == menu_save_item($item)) {
151 // Creating new menu item with node language
152 db_query("UPDATE {menu} SET language = '%s' WHERE mid = %d", $node->language, $item['mid']);
153 drupal_set_message(t('The menu item %title has been added with node language.', array('%title' => $item['title'])));
156 unset($node->menu); // Avoid further processing by menu module
158 // Pathauto integration. Dynamic replacement of variables to allow different patterns per language
159 if (module_exists('path') && module_exists('pathauto')) {
160 // Language for pathauto variables is either node language or default language
161 $language = $node->language ? $node->language : i18n_default_language();
162 if ($language != i18n_get_lang()) {
163 i18n_variable_init($language, 'pathauto_node');
168 db_query('DELETE FROM {i18n_node} WHERE nid=%d', $node->nid);
171 // Book pages, set the right language nodes and outlines
172 if (arg(3) == 'parent' && is_numeric(arg(4)) && ($parent = node_load(arg(4))) && $parent->language) {
173 $node->language = $parent->language;
174 i18n_selection_mode('node', $parent->language);
183 * Implementation of hook_alter_translation_link().
185 * Handles links for extended language. The links will have current language
187 function i18n_translation_link_alter(&$links, $path) {
190 // Check for a node related path, and for its translations.
191 if ((preg_match("!^node/([0-9]+)(/.+|)$!", $path, $matches)) && ($node = node_load((int)$matches[1])) && !empty($node->tnid
)) {
192 $languages = language_list();
194 foreach (translation_node_get_translations($node->tnid
) as
$langcode => $translation_node) {
195 if (!isset($links[$langcode]) && isset($languages[$langcode])) {
196 $extended[$langcode] = array(
197 'href' => 'node/'.
$translation_node->nid .
$matches[2],
198 'language' => $language,
199 'language_icon' => $languages[$langcode],
200 'title' => $languages[$langcode]->native
,
201 'attributes' => array('class' => 'language-link'),
205 // This will run after languageicon module, so we add icon in case that one is enabled
206 if ($extended && function_exists('languageicons_translation_link_alter')) {
207 languageicons_translation_link_alter($extended, $path);
209 $links = array_merge($links, $extended);
214 * Implementation of hook_link_alter().
216 * Handles links for extended languages. Sets current interface language;
218 function i18n_link_alter(&$links, $node) {
222 foreach (array_keys(i18n_language_list('extended')) as
$langcode) {
223 $index = 'node_translation_'.
$langcode;
224 if (!empty($links[$index])) {
225 $links[$index]['language'] = $language;
232 * Implementation of hook_user()
234 * Switch to user's language after login
236 function i18n_user($op, &$edit, &$account, $category = NULL
) {
237 if($op == 'login' && $account->language
) {
238 $_SESSION['language'] = $account->language
;
239 i18n_get_lang($account->language
);
248 * Get language properties
253 * It may be 'name', 'native', 'ltr'...
255 function i18n_language_property($code, $property) {
256 $languages = language_list();
257 return isset($languages[$code]->$property) ?
$languages[$code]->$property : NULL
;
263 function i18n_node_get_lang($nid, $default = '') {
264 $lang = db_result(db_query('SELECT language FROM {node} WHERE nid = %d',$nid));
265 return $lang ?
$lang : $default ;
269 * Get allowed languages for node
271 * This allows node types to define its own language list implementing hook 'language_list'
274 * Node to retrieve language list for
276 * Only languages available for translation. Filter out existing translations
278 function i18n_node_language_list($node, $translate = FALSE
) {
279 // Check if the node module manages its own language list
280 $languages = node_invoke($node, 'language_list', $translate);
282 if (variable_get('i18n_node_'.
$node->type
, 0) == LANGUAGE_SUPPORT_EXTENDED
) {
283 $languages = locale_language_list('name', TRUE
); // All defined languages
285 $languages = locale_language_list(); // All enabled languages
287 if ($translate && isset($node->tnid
) && $node->tnid
&& ($translations = translation_node_get_translations($node->tnid
))) {
288 unset($translations[$node->language
]);
289 foreach (array_keys($translations) as
$langcode) {
290 unset($languages[$langcode]);
293 $languages = array('' => t('Language neutral')) + $languages;
300 * Function i18n_get_links
302 * Returns an array of links for all languages, with or without names/flags
305 * Drupal internal path
309 * Names to use for the links. Defaults to native language names
311 function i18n_get_links($path = '', $query = NULL
, $names = NULL
) {
312 if($path == i18n_frontpage()) {
315 $names = $names ?
$names : locale_language_list('native');
316 foreach (array_keys(i18n_supported_languages()) as
$lang){
317 $links[$lang]= theme('i18n_link', $names[$lang], i18n_path($path, $lang), $lang, $query);
323 * i18n_selection_mode
325 * Allows several modes for query rewriting and to change them programatically
326 * off = No language conditions inserted
327 * simple = Only current language and no language
328 * mixed = Only current and default languages
329 * strict = Only current language
330 * default = Only default language
331 * user = User defined, in the module's settings page
332 * params = Gets the stored params
333 * reset = Returns to previous
334 * custom = add custom where clause, like "%alias.language = 'en'"
336 function i18n_selection_mode($mode= NULL
, $params= NULL
){
337 static
$current_mode = 'simple';
338 static
$current_value = '';
339 static
$store = array();
342 return $current_mode;
343 } elseif($mode == 'params'){
344 return $current_value;
345 } elseif($mode == 'reset'){
346 list($current_mode, $current_value) = array_pop($store);
347 //drupal_set_message("i18n mode reset mode=$current_mode value=$current_value");
349 array_push($store, array($current_mode, $current_value));
350 $current_mode = $mode;
351 $current_value = $params;
356 * Implementation of hook_db_rewrite_sql()
358 function i18n_db_rewrite_sql($query, $primary_table, $primary_key){
360 switch ($primary_table) {
364 // No rewrite for translation module queries
365 if (preg_match("/.*FROM {node} $primary_table WHERE.*$primary_table\.tnid/", $query)) return;
366 // When loading specific nodes, language conditions shouldn't apply
367 if (preg_match("/WHERE.*\s$primary_table.nid\s*=\s*(\d|%d)/", $query)) return;
368 // If language conditions already there, get out
369 if (preg_match("/i18n/", $query)) return;
371 //$result['join'] = "LEFT JOIN {i18n_node} i18n ON $primary_table.nid = i18n.nid";
372 $result['where'] = i18n_db_rewrite_where($primary_table, 'node');
378 * Rewrites queries depending on rewriting mode
380 function i18n_db_rewrite_where($alias, $type, $mode = NULL
){
382 // Some exceptions for query rewrites
383 $mode = i18n_selection_mode();
384 // drupal_set_message("i18n_db_rewrite mode=$mode query=$query");
386 // Special case. Selection mode is 'strict' but this should be only for node queries
387 if ($mode == 'strict' && $type != 'node') {
394 return "$alias.language ='".
i18n_get_lang().
"' OR $alias.language ='' OR $alias.language IS NULL" ;
396 return "$alias.language ='".
i18n_get_lang().
"' OR $alias.language ='".
i18n_default_language().
"' OR $alias.language ='' OR $alias.language IS NULL" ;
398 return "$alias.language ='".
i18n_get_lang().
"'" ;
401 return "$alias.language ='".
i18n_selection_mode('params').
"' OR $alias.language ='' OR $alias.language IS NULL" ;
403 return "$alias.language ='".
i18n_default_language().
"' OR $alias.language ='' OR $alias.language IS NULL" ;
405 return str_replace('%alias', $alias, i18n_selection_mode('params'));
410 * Implementation of hook_exit
412 function i18n_exit(){
413 _i18n_variable_exit();
417 * Implementation of hook_form_alter();
419 * This is the place to add language fields to all forms
421 function i18n_form_alter(&$form, $form_state, $form_id) {
423 case
'node_type_form':
424 // Add extended language support option to content type form.
425 $form['workflow']['i18n_node'] = array(
427 '#title' => t('Extended language support'),
428 '#default_value' => variable_get('i18n_node_'.
$form['#node_type']->type
, LANGUAGE_SUPPORT_NORMAL
),
429 '#options' => _i18n_content_language_options(),
430 '#description' => t('If enabled, all defined languages will be allowed for this content type in addition to only enabled ones. This is useful to have more languages for content than for the interface.')
436 // Extended language for node edit form
437 if (isset($form['#id']) && $form['#id'] == 'node-form') {
438 if (isset($form['#node']->type
) && variable_get('language_content_type_'.
$form['#node']->type
, 0)) {
439 $form['language']['#options'] = i18n_node_language_list($form['#node'], TRUE
);
443 if (isset($form['type']) && $form['type']['#value'] .'_node_form' == $form_id && $node = $form['#node']) {
445 if(variable_get('i18n_node_'.$form['type']['#value'], 0) && !isset($form['i18n']['language'])) {
447 $form['i18n'] = array('#type' => 'fieldset', '#title' => t('Multilingual settings'), '#collapsible' => TRUE, '#collapsed' => FALSE, '#weight' => -4);
448 // Language will default to current only when creating a node
449 $language = isset($form['#node']->language) ? $form['#node']->language : (arg(1)=='add' ? i18n_get_lang() : '');
450 $form['i18n']['language'] = _i18n_language_select($language, t('If you change the Language, you must click on <i>Preview</i> to get the right Categories & Terms for that language.'), -4, i18n_node_language_list($node));
451 $form['i18n']['trid'] = array('#type' => 'value', '#value' => $form['#node']->trid);
453 // Correction for lang/node/nid aliases generated by path module
454 // if($form['#node']->path && $form['#node']->path == i18n_get_lang().'/node/'.$form['#node']->nid){
456 $alias = drupal_lookup_path('alias', 'node/'.$node->nid);
457 if($alias && $alias != 'node/'.$node->nid){
458 $form['#node']->path = $alias;
460 unset($form['#node']->path);
465 // Multilingual variables in settings form
466 if (isset($form['#theme']) && $form['#theme'] == 'system_settings_form' && $variables = variable_get('i18n_variables', 0)) {
467 if (i18n_form_alter_settings($form, $variables)) {
468 $form['#submit'][] = 'i18n_variable_form_submit';
475 * Implementation of hook_perm().
477 * Permissions defined
478 * - administer all languages
479 * Disables language conditions for administration pages, so the user can view objects for all languages at the same time.
480 * This applies for: menu items, taxonomy
482 function i18n_perm() {
483 return array('administer all languages');
486 * Process menu and menu item add/edit form submissions.
488 function i18n_menu_edit_item_form_submit($form_id, $form_values) {
489 $mid = menu_edit_item_save($form_values);
490 db_query("UPDATE {menu} SET language = '%s' WHERE mid = %d", $form_values['language'], $mid);
491 return 'admin/build/menu';
495 * Check for multilingual variables in form
497 function i18n_form_alter_settings(&$form, &$variables) {
499 foreach (element_children($form) as
$field) {
500 if(isset($form[$field]['#type']) && $form[$field]['#type'] == 'fieldset') {
501 $result += i18n_form_alter_settings($form[$field], $variables);
503 elseif (in_array($field, $variables)) {
504 $form[$field]['#description'] .
= ' <strong>'.
t('This is a multilingual variable.').
'</strong>';
512 * Save multilingual variables and remove them from form
514 function i18n_variable_form_submit($form, &$form_state) {
515 $form_values = $form_state['values'];
516 $op = isset($form_values['op']) ?
$form_values['op'] : '';
517 $variables = variable_get('i18n_variables', array());
518 $language = i18n_get_lang();
519 foreach ($form_values as
$key => $value) {
520 if (in_array($key, $variables)) {
521 if ($op == t('Reset to defaults')) {
522 i18n_variable_del($key, $language);
525 if (is_array($value) && isset($form_values['array_filter'])) {
526 $value = array_keys(array_filter($value));
528 i18n_variable_set($key, $value, $language);
530 unset($form_values[$key]);
534 // system_settings_form_submit($form_id, $form_values);
538 * Initialization of multilingual variables
541 * Language to retrieve variables. Defaults to current language
543 * Variable name prefix to load just a selected group of variables
545 function i18n_variable_init($language = NULL
, $prefix = ''){
548 $language = $language ?
$language : i18n_get_lang();
549 if ($i18n_variables = variable_get('i18n_variables', '')){
551 $i18n_conf = array();
553 $variables = _i18n_variable_init($language, $prefix);
554 foreach($i18n_variables as
$name){
555 $i18n_conf[$name] = isset($variables[$name]) ?
$variables[$name] : (isset($conf[$name]) ?
$conf[$name] : '');
557 $conf = array_merge($conf, $i18n_conf);
564 * Helper function to create language selector
566 function _i18n_language_select($value ='', $description ='', $weight = -20, $languages = NULL
){
567 $languages = $languages ?
$languages : locale_language_list();
570 '#title' => t('Language'),
571 '#default_value' => $value,
572 '#options' => array_merge(array('' => ''), $languages),
573 '#description' => $description,
574 '#weight' => $weight,
579 * Load language variables into array
581 function _i18n_variable_init($language, $prefix = ''){
582 $variables = array();
583 $cacheid = 'variables:'.
$language.
($prefix ?
':'.
$prefix : '');
584 if ($cached = cache_get($cacheid)) {
585 $variables = $cached->data
;
588 $result = db_query("SELECT * FROM {i18n_variable} WHERE language='%s' AND name LIKE '%s%'", $language, $prefix);
589 while ($variable = db_fetch_object($result)) {
590 $variables[$variable->name
] = unserialize($variable->value
);
592 cache_set($cacheid, $variables);
598 * Save multilingual variables that may have been changed by other methods than settings pages
600 function _i18n_variable_exit(){
604 $lang = i18n_get_lang();
606 // Rewritten because array_diff_assoc may fail with array variables
607 foreach($i18n_conf as
$name => $value){
608 if($value != $conf[$name]) {
610 $i18n_conf[$name] = $conf[$name];
611 db_query("DELETE FROM {i18n_variable} WHERE name='%s' AND language='%s'", $name, $lang );
612 db_query("INSERT INTO {i18n_variable} (language, name, value) VALUES('%s', '%s', '%s')", $lang, $name, serialize($conf[$name]));
616 cache_set('variables:'.
$lang, 'cache', serialize($i18n_conf));
622 * Check whether we are in bootstrap mode
624 function _i18n_is_bootstrap(){
625 return !function_exists('drupal_get_headers');
629 * Sets db_prefix to given language
631 function _i18n_set_db_prefix($lang) {
632 global $db_prefix, $db_prefix_i18n;
633 if (is_array($db_prefix_i18n)) {
634 $db_prefix = array_merge($db_prefix, str_replace('**', $lang, $db_prefix_i18n));
639 * To get the original path.
640 * Cannot use $_GET["q"] cause it may have been already changed
642 function _i18n_get_original_path() {
643 return isset($_REQUEST["q"]) ?
trim($_REQUEST["q"],"/") : '';
647 * Drupal 6, backwards compatibility layer
648 * @ TO DO Fully upgrade all the modules and remove
652 * This one expects to be called first from common.inc
654 function i18n_get_lang() {
656 return $language->language
;
662 * Language dependent front page
663 * This function will search for aliases like 'en/home', 'es/home'...
665 function i18n_frontpage($lang = NULL
) {
666 $lang = $lang ?
$lang : i18n_get_lang();
667 return i18n_get_normal_path($lang.
'/'.
variable_get('site_frontpage','node'));
672 * @defgroup i18n_api Extended language API
674 * This is an extended language API to be used by modules in i18n package.
678 * Returns language lists
680 function i18n_language_list($type = 'enabled', $field = 'name') {
683 return locale_language_list($field);
685 $enabled = locale_language_list($field);
686 $defined = locale_language_list($field, TRUE
);
687 return array_diff_assoc($defined, $enabled);
691 * Returns default language code
693 function i18n_default_language(){
694 return language_default('language');
698 * Get list of supported languages, native name
701 * TRUE to get all defined languages
703 function i18n_supported_languages($all = FALSE
) {
704 return locale_language_list('native', $all);
708 * Set a persistent language dependent variable.
711 * The name of the variable to set.
713 * The value to set. This can be any PHP data type; these functions take care
714 * of serialization as necessary.
718 function i18n_variable_set($name, $value, $langcode) {
719 global $conf, $i18n_conf;
721 db_lock_table('i18n_variable');
722 db_query("DELETE FROM {i18n_variable} WHERE name = '%s' AND language='%s'", $name, $langcode);
723 db_query("INSERT INTO {i18n_variable} (name, language, value) VALUES ('%s', '%s', '%s')", $name, $langcode, serialize($value));
726 cache_clear_all('variables:'.
$langcode, 'cache');
728 $conf[$name] = $value;
729 $i18n_conf[$name] = $value;
733 * Unset a persistent multilingual variable.
736 * The name of the variable to undefine.
740 function i18n_variable_del($name, $langcode) {
741 global $conf, $i18n_conf;
743 db_query("DELETE FROM {i18n_variable} WHERE name = '%s' AND language='%s'", $name, $langcode);
744 cache_clear_all('variables:'.
$langcode, 'cache');
747 unset($i18n_conf[$name]);
751 * Utility. Get part of array variable
753 function i18n_array_variable_get($name, $element, $default = NULL
) {
754 if (($values = variable_get($name, array())) && isset($values[$element])) {
755 return $values[$element];
762 * Utility. Set part of array variable
764 function i18n_array_variable_set($name, $element, $value) {
765 $values = variable_get($name, array());
766 $values[$element] = $value;
767 variable_set($name, $values);
771 * @} End of "defgroup i18n_api".
774 // List of language support modes for content
775 function _i18n_content_language_options() {
777 LANGUAGE_SUPPORT_NORMAL
=> t('Normal - All enabled languages will be allowed.'),
778 LANGUAGE_SUPPORT_EXTENDED
=> t('Extended - All defined languages will be allowed.')