From eb0caa354ea6fec8d63f930ee249121c09eec1b7 Mon Sep 17 00:00:00 2001 From: Dries Buytaert Date: Mon, 26 Mar 2007 01:32:22 +0000 Subject: [PATCH] - Patch #128866 by Gabor, Steven, chx, Jose et al: new language subsystem. --- includes/bootstrap.inc | 105 +++- includes/common.inc | 39 +- includes/language.inc | 136 +++++ includes/locale.inc | 538 ++++++++++++------ includes/path.inc | 51 +- modules/locale/locale.install | 97 +++- modules/locale/locale.module | 186 +++--- modules/node/node.module | 4 +- modules/path/path.module | 63 +- modules/system/system.install | 35 +- sites/default/settings.php | 5 +- themes/engines/phptemplate/phptemplate.engine | 2 +- 12 files changed, 917 insertions(+), 344 deletions(-) create mode 100644 includes/language.inc diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc index 13e58445202..26fa25ec257 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -85,15 +85,20 @@ define('DRUPAL_BOOTSTRAP_SESSION', 4); define('DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE', 5); /** - * Seventh bootstrap phase: set $_GET['q'] to Drupal path of request. + * Seventh bootstrap phase: find out language of the page. */ -define('DRUPAL_BOOTSTRAP_PATH', 6); +define('DRUPAL_BOOTSTRAP_LANGUAGE', 6); + +/** + * Eighth bootstrap phase: set $_GET['q'] to Drupal path of request. + */ +define('DRUPAL_BOOTSTRAP_PATH', 7); /** * Final bootstrap phase: Drupal is fully loaded; validate and fix * input data. */ -define('DRUPAL_BOOTSTRAP_FULL', 7); +define('DRUPAL_BOOTSTRAP_FULL', 8); /** * Role ID for anonymous users; should match what's in the "role" table. @@ -105,6 +110,30 @@ define('DRUPAL_ANONYMOUS_RID', 1); */ define('DRUPAL_AUTHENTICATED_RID', 2); +/** + * No language negotiation. The default language is used. + */ +define('LANGUAGE_NEGOTIATION_NONE', 0); + +/** + * Path based negotiation with fallback to default language + * if no defined path prefix identified. + */ +define('LANGUAGE_NEGOTIATION_PATH_DEFAULT', 1); + +/** + * Path based negotiation with fallback to user preferences + * and browser language detection if no defined path prefix + * identified. + */ +define('LANGUAGE_NEGOTIATION_PATH', 2); + +/** + * Domain based negotiation with fallback to default language + * if no language identified by domain. + */ +define('LANGUAGE_NEGOTIATION_DOMAIN', 3); + /** * Start the timer with the specified name. If you start and stop * the same timer multiple times, the measured intervals will be @@ -409,6 +438,7 @@ function variable_del($name) { unset($conf[$name]); } + /** * Retrieve the current page from the cache. * @@ -762,11 +792,12 @@ function drupal_anonymous_user($session = '') { * DRUPAL_BOOTSTRAP_SESSION: initialize session handling. * DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE: load bootstrap.inc and module.inc, start * the variable system and try to serve a page from the cache. + * DRUPAL_BOOTSTRAP_LANGUAGE: identify the language used on the page. * DRUPAL_BOOTSTRAP_PATH: set $_GET['q'] to Drupal path of request. * DRUPAL_BOOTSTRAP_FULL: Drupal is fully loaded, validate and fix input data. */ function drupal_bootstrap($phase) { - static $phases = array(DRUPAL_BOOTSTRAP_CONFIGURATION, DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE, DRUPAL_BOOTSTRAP_DATABASE, DRUPAL_BOOTSTRAP_ACCESS, DRUPAL_BOOTSTRAP_SESSION, DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE, DRUPAL_BOOTSTRAP_PATH, DRUPAL_BOOTSTRAP_FULL); + static $phases = array(DRUPAL_BOOTSTRAP_CONFIGURATION, DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE, DRUPAL_BOOTSTRAP_DATABASE, DRUPAL_BOOTSTRAP_ACCESS, DRUPAL_BOOTSTRAP_SESSION, DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE, DRUPAL_BOOTSTRAP_LANGUAGE, DRUPAL_BOOTSTRAP_PATH, DRUPAL_BOOTSTRAP_FULL); while (!is_null($current_phase = array_shift($phases))) { _drupal_bootstrap($current_phase); @@ -824,6 +855,10 @@ function _drupal_bootstrap($phase) { drupal_page_header(); break; + case DRUPAL_BOOTSTRAP_LANGUAGE: + drupal_init_language(); + break; + case DRUPAL_BOOTSTRAP_PATH: require_once './includes/path.inc'; // Initialize $_GET['q'] prior to loading modules and invoking hook_init(). @@ -898,3 +933,65 @@ function get_t() { } return $t; } + +/** + * Choose a language for the current page, based on site and user preferences. + */ +function drupal_init_language() { + global $language, $user; + + // Ensure the language is correctly returned, even without multilanguage support. + // Useful for eg. XML/HTML 'lang' attributes. + if (variable_get('language_count', 1) == 1) { + $language = language_default(); + } + else { + include_once './includes/language.inc'; + $language = language_initialize(); + } +} + +/** + * Get a list of languages set up indexed by the specified key + * + * @param $field The field to index the list with. + * @param $reset Boolean to request a reset of the list. + */ +function language_list($field = 'language', $reset = FALSE) { + static $languages = NULL; + + // Reset language list + if ($reset) { + $languages = NULL; + } + + // Init language list + if (!isset($languages)) { + $result = db_query('SELECT * FROM {languages} ORDER BY weight ASC, name ASC'); + while ($row = db_fetch_object($result)) { + $languages['language'][$row->language] = $row; + } + } + + // Return the array indexed by the right field + if (!isset($languages[$field])) { + $languages[$field] = array(); + foreach($languages['language'] as $lang) { + // Some values should be collected into an array + if (in_array($field, array('enabled', 'weight'))) { + $languages[$field][$lang->$field][$lang->language] = $lang; + } + else { + $languages[$field][$lang->$field] = $lang; + } + } + } + return $languages[$field]; +} + +/** + * Default language used on the site + */ +function language_default() { + return variable_get('language_default', (object) array('language' => 'en', 'name' => 'English', 'direction' => 0, 'native' => 'English')); +} diff --git a/includes/common.inc b/includes/common.inc index 9515c7623be..a51891cafa9 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -599,33 +599,6 @@ function fix_gpc_magic() { } } -/** - * Initialize the localization system. - */ -function locale_initialize() { - global $user; - - if (function_exists('i18n_get_lang')) { - return i18n_get_lang(); - } - - if (function_exists('locale')) { - $languages = locale_supported_languages(); - $languages = $languages['name']; - } - else { - // Ensure the locale/language is correctly returned, even without locale.module. - // Useful for e.g. XML/HTML 'lang' attributes. - $languages = array('en' => 'English'); - } - if ($user->uid && isset($languages[$user->language])) { - return $user->language; - } - else { - return key($languages); - } -} - /** * Translate strings to the current locale. * @@ -722,8 +695,8 @@ function locale_initialize() { * The translated string. */ function t($string, $args = 0) { - global $locale; - if (function_exists('locale') && $locale != 'en') { + global $language; + if (function_exists('locale') && $language->language != 'en') { $string = locale($string); } if (!$args) { @@ -1177,6 +1150,11 @@ function url($path = NULL, $options = array()) { 'absolute' => FALSE, 'alias' => FALSE, ); + + // May need language dependant rewriting if language.inc is present + if (function_exists('language_url_rewrite')) { + language_url_rewrite($path, $options); + } if ($options['fragment']) { $options['fragment'] = '#'. $options['fragment']; } @@ -1884,7 +1862,6 @@ function xmlrpc($url) { function _drupal_bootstrap_full() { static $called; - global $locale; if ($called) { return; @@ -1908,8 +1885,6 @@ function _drupal_bootstrap_full() { fix_gpc_magic(); // Load all enabled modules module_load_all(); - // Initialize the localization system. Depends on i18n.module being loaded already. - $locale = locale_initialize(); // Let all modules take action before menu system handles the reqest module_invoke_all('init'); diff --git a/includes/language.inc b/includes/language.inc new file mode 100644 index 00000000000..d145fd7f74b --- /dev/null +++ b/includes/language.inc @@ -0,0 +1,136 @@ +domain); + if ($_SERVER['SERVER_NAME'] == $parts['host']) { + return $language; + } + } + return language_default(); + + case LANGUAGE_NEGOTIATION_PATH_DEFAULT: + case LANGUAGE_NEGOTIATION_PATH: + $languages = language_list('prefix'); + $args = explode('/', $_GET['q']); + $language = array_shift($args); + if (isset($languages[$language])) { + $_GET['q'] = implode('/', $args); + return $languages[$language]; + } + elseif ($mode == LANGUAGE_NEGOTIATION_PATH_DEFAULT) { + return language_default(); + } + break; + } + + // User language. + $languages = language_list(); + if ($user->uid && isset($languages[$user->language])) { + return $languages[$user->language]; + } + + // Browser accept-language parsing. + if ($language = language_from_browser()) { + return $language; + } + + // Fall back on the default if everything else fails. + return language_default(); +} + +/** + * Indetify language from the Accept-language HTTP header we got. + */ +function language_from_browser() { + // Specified by the user via the browser's Accept Language setting + // Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5" + $browser_langs = array(); + + if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { + $browser_accept = explode(",", $_SERVER['HTTP_ACCEPT_LANGUAGE']); + for ($i = 0; $i < count($browser_accept); $i++) { + // The language part is either a code or a code with a quality. + // We cannot do anything with a * code, so it is skipped. + // If the quality is missing, it is assumed to be 1 according to the RFC. + if (preg_match("!([a-z-]+)(;q=([0-9\\.]+))?!", trim($browser_accept[$i]), $found)) { + $browser_langs[$found[1]] = (isset($found[3]) ? (float) $found[3] : 1.0); + } + } + } + + // Order the codes by quality + arsort($browser_langs); + + // Try to find the first preferred language we have + $languages = language_list('enabled'); + foreach ($browser_langs as $langcode => $q) { + if (isset($languages['1'][$langcode])) { + return $languages['1'][$langcode]; + } + } +} + +/** + * Rewrite URL's with language based prefix. Parameters are the same + * as those of the url() function. + */ +function language_url_rewrite(&$path, &$options) { + global $language; + + // Only modify relative (insite) URLs. + if (!$options['absolute']) { + + // Language can be passed as an option, or we go for current language. + $path_language = isset($options['language']) ? $options['language'] : $language; + switch(variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE)) { + + case LANGUAGE_NEGOTIATION_NONE: + break; + + case LANGUAGE_NEGOTIATION_DOMAIN: + if ($rewritelang->domain) { + $options['absolute'] = TRUE; + $path = $path_language->domain .'/'. $path; + } + break; + + case LANGUAGE_NEGOTIATION_PATH_DEFAULT: + $default = language_default(); + if ($path_language->language == $default->language) { + break; + } + // Intentionally no break here. + + case LANGUAGE_NEGOTIATION_PATH: + if (isset($path_language->prefix) && $path_language->prefix) { + // Get alias if not already aliased. + if (!$options['alias']) { + $path = drupal_get_path_alias($path, $path_language->language); + $options['alias'] = TRUE; + } + $path = empty($path) ? $path_language->prefix : $path_language->prefix .'/'. $path; + } + break; + + } + } +} diff --git a/includes/locale.inc b/includes/locale.inc index 9e830d4f05c..d526a33de34 100644 --- a/includes/locale.inc +++ b/includes/locale.inc @@ -7,92 +7,101 @@ */ // --------------------------------------------------------------------------------- -// Language addition functionality (administration only) +// Language management functionality (administration only) /** * Helper function to add a language + * + * @param $code + * Language code. + * @param $name + * English name of the language + * @param $native + * Native name of the language + * @param $direction + * 0 for left to right, 1 for right to left direction + * @param $domain + * Optional custom domain name with protocol, without + * trailing slash (eg. http://de.example.com). + * @param $prefix + * Optional path prefix for the language. Defaults to the + * language code if omitted. + * @param $verbose + * Switch to omit the verbose message to the user when used + * only as a utility function. */ -function _locale_add_language($code, $name, $onlylanguage = TRUE) { - db_query("INSERT INTO {locales_meta} (locale, name) VALUES ('%s','%s')", $code, $name); +function _locale_add_language($code, $name, $native, $direction = 0, $domain = '', $prefix = '', $verbose = TRUE) { + if (empty($prefix)) { + $prefix = $code; + } + db_query("INSERT INTO {languages} (language, name, native, direction, domain, prefix) VALUES ('%s', '%s', '%s', %d, '%s', '%s')", $code, $name, $native, $direction, $domain, $prefix); $result = db_query("SELECT lid FROM {locales_source}"); while ($string = db_fetch_object($result)) { - db_query("INSERT INTO {locales_target} (lid, locale, translation) VALUES (%d,'%s', '')", $string->lid, $code); + db_query("INSERT INTO {locales_target} (lid, language, translation) VALUES (%d,'%s', '')", $string->lid, $code); } // If only the language was added, and not a PO file import triggered // the language addition, we need to inform the user on how to start - // a translation - if ($onlylanguage) { - drupal_set_message(t('The language %locale has been created and can now be used to import a translation. More information is available in the help screen.', array('%locale' => t($name), '@locale-help' => url('admin/help/locale')))); + // working with the language. + if ($verbose) { + drupal_set_message(t('The language %language has been created and can now be used. More information is available on the help screen.', array('%language' => t($name), '@locale-help' => url('admin/help/locale')))); } else { - drupal_set_message(t('The language %locale has been created.', array('%locale' => t($name)))); + drupal_set_message(t('The language %language has been created.', array('%language' => t($name)))); } - watchdog('locale', t('The %language language (%locale) has been created.', array('%language' => $name, '%locale' => $code))); + watchdog('locale', t('The %language language (%code) has been created.', array('%language' => t($name), '%code' => $code))); } /** * User interface for the language management screen. */ function _locale_admin_manage_screen() { - $languages = locale_supported_languages(TRUE, TRUE); + $languages = language_list('language', TRUE); $options = array(); - $form['name'] = array('#tree' => TRUE); - foreach ($languages['name'] as $key => $lang) { - $options[$key] = ''; - $status = db_fetch_object(db_query("SELECT isdefault, enabled FROM {locales_meta} WHERE locale = '%s'", $key)); - if ($status->enabled) { - $enabled[] = $key; - } - if ($status->isdefault) { - $isdefault = $key; - } - if ($key == 'en') { - $form['name']['en'] = array('#value' => check_plain($lang)); - } - else { - $original = db_fetch_object(db_query("SELECT COUNT(*) AS strings FROM {locales_source}")); - $translation = db_fetch_object(db_query("SELECT COUNT(*) AS translation FROM {locales_target} WHERE locale = '%s' AND translation != ''", $key)); + $form['weight'] = array('#tree' => TRUE); + foreach ($languages as $langcode => $language) { - $ratio = ($original->strings > 0 && $translation->translation > 0) ? round(($translation->translation/$original->strings)*100., 2) : 0; - - $form['name'][$key] = array('#type' => 'textfield', - '#default_value' => $lang, - '#size' => 15, - '#maxlength' => 64, - ); - $form['translation'][$key] = array('#value' => "$translation->translation/$original->strings ($ratio%)"); + $options[$langcode] = ''; + if ($language->enabled) { + $enabled[] = $langcode; } + $form['weight'][$langcode] = array( + '#type' => 'weight', + '#default_value' => $language->weight + ); + $form['name'][$langcode] = array('#value' => check_plain($language->name)); + $form['native'][$langcode] = array('#value' => check_plain($language->native)); + $form['direction'][$langcode] = array('#value' => ($language->direction ? 'Right to left' : 'Left to right')); } $form['enabled'] = array('#type' => 'checkboxes', '#options' => $options, '#default_value' => $enabled, ); + $default = language_default(); $form['site_default'] = array('#type' => 'radios', '#options' => $options, - '#default_value' => $isdefault, + '#default_value' => $default->language, ); $form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration')); - $form['#submit']['locale_admin_manage_screen_submit'] = array(); - $form['#validate']['locale_admin_manage_screen_validate'] = array(); + $form['#submit']['_locale_admin_manage_screen_submit'] = array(); $form['#theme'] = 'locale_admin_manage_screen'; return $form; } /** - * Theme the locale admin manager form. + * Theme the admin langauge manager form. */ function theme_locale_admin_manage_screen($form) { foreach ($form['name'] as $key => $element) { // Do not take form control structures. if (is_array($element) && element_child($key)) { - $rows[] = array(check_plain($key), drupal_render($form['name'][$key]), drupal_render($form['enabled'][$key]), drupal_render($form['site_default'][$key]), ($key != 'en' ? drupal_render($form['translation'][$key]) : t('n/a')), ($key != 'en' ? l(t('delete'), 'admin/build/locale/language/delete/'. $key) : '')); + $rows[] = array(array('data' => drupal_render($form['enabled'][$key]), 'align' => 'center'), check_plain($key), ''. drupal_render($form['name'][$key]) .'', drupal_render($form['native'][$key]), drupal_render($form['direction'][$key]), drupal_render($form['site_default'][$key]), drupal_render($form['weight'][$key]), l(t('edit'), 'admin/build/locale/language/edit/'. $key). ($key != 'en' ? ' ' .l(t('delete'), 'admin/build/locale/language/delete/'. $key) : '')); } } - $header = array(array('data' => t('Code')), array('data' => t('English name')), array('data' => t('Enabled')), array('data' => t('Default')), array('data' => t('Translated')), array('data' => t('Operations'))); + $header = array(array('data' => t('Enabled')), array('data' => t('Code')), array('data' => t('English name')), array('data' => t('Native name')), array('data' => t('Direction')), array('data' => t('Default')), array('data' => t('Weight')), array('data' => t('Operations'))); $output = theme('table', $header, $rows); $output .= drupal_render($form); @@ -104,65 +113,67 @@ function theme_locale_admin_manage_screen($form) { */ function _locale_admin_manage_screen_submit($form_id, $form_values) { // Save changes to existing languages. - $languages = locale_supported_languages(FALSE, TRUE); - foreach ($languages['name'] as $key => $value) { - if ($form_values['site_default'] == $key) { - $form_values['enabled'][$key] = 1; // autoenable the default language + $languages = language_list(); + $enabled_count = 0; + foreach ($languages as $langcode => $language) { + if ($form_values['site_default'] == $langcode) { + $form_values['enabled'][$langcode] = 1; // autoenable the default language } - $enabled = $form_values['enabled'][$key] ? 1 : 0; - if ($key == 'en') { - // Disallow name change for English locale. - db_query("UPDATE {locales_meta} SET isdefault = %d, enabled = %d WHERE locale = 'en'", ($form_values['site_default'] == $key), $enabled); + if ($form_values['enabled'][$langcode]) { + $enabled_count++; + $language->enabled = 1; } else { - db_query("UPDATE {locales_meta} SET name = '%s', isdefault = %d, enabled = %d WHERE locale = '%s'", $form_values['name'][$key], ($form_values['site_default'] == $key), $enabled, $key); + $language->enabled = 0; } + $language->weight = $form_values['weight'][$langcode]; + db_query("UPDATE {languages} SET enabled = %d, weight = %d WHERE language = '%s'", $language->enabled, $language->weight, $langcode); + $languages[$langcode] = $language; } drupal_set_message(t('Configuration saved.')); + variable_set('language_default', $languages[$form_values['site_default']]); + variable_set('language_count', $enabled_count); - // Changing the locale settings impacts the interface: + // Changing the language settings impacts the interface. cache_clear_all('*', 'cache_page', TRUE); return 'admin/build/locale/language/overview'; } +/** + * Predefined language setup form. + */ function locale_add_language_form() { - $isocodes = _locale_prepare_iso_list(); + $predefined = _locale_prepare_predefined_list(); $form = array(); $form['language list'] = array('#type' => 'fieldset', - '#title' => t('Language list'), + '#title' => t('Predefined language'), '#collapsible' => TRUE, ); $form['language list']['langcode'] = array('#type' => 'select', '#title' => t('Language name'), - '#default_value' => key($isocodes), - '#options' => $isocodes, - '#description' => t('Select your language here, or add it below, if you are unable to find it.'), + '#default_value' => key($predefined), + '#options' => $predefined, + '#description' => t('Select the desired language here, or add it below, if you are unable to find it in the list.'), ); $form['language list']['submit'] = array('#type' => 'submit', '#value' => t('Add language')); return $form; } +/** + * Custom language addition form. + */ function locale_custom_language_form() { $form = array(); $form['custom language'] = array('#type' => 'fieldset', '#title' => t('Custom language'), '#collapsible' => TRUE, ); - $form['custom language']['langcode'] = array('#type' => 'textfield', - '#title' => t('Language code'), - '#size' => 12, - '#maxlength' => 60, - '#required' => TRUE, - '#description' => t('Commonly this is an ISO 639 language code with an optional country code for regional variants. Examples include "en", "en-US" and "zh-cn".', array('@iso-codes' => 'http://www.w3.org/WAI/ER/IG/ert/iso639.htm')), + _locale_language_form($form['custom language']); + $form['custom language']['submit'] = array( + '#type' => 'submit', + '#value' => t('Add custom language') ); - $form['custom language']['langname'] = array('#type' => 'textfield', - '#title' => t('Language name in English'), - '#maxlength' => 64, - '#required' => TRUE, - '#description' => t('Name of the language. Will be available for translation in all languages.'), - ); - $form['custom language']['submit'] = array('#type' => 'submit', '#value' => t('Add custom language')); // Use the validation and submit functions of the add language form. $form['#submit']['locale_add_language_form_submit'] = array(); $form['#validate']['locale_add_language_form_validate'] = array(); @@ -170,6 +181,100 @@ function locale_custom_language_form() { return $form; } +/** + * Editing screen for a particular language. + * + * @param $langcode + * Languauge code of the language to edit. + */ +function _locale_admin_manage_edit_screen($langcode) { + if ($language = db_fetch_object(db_query("SELECT * FROM {languages} WHERE language = '%s'", $langcode))) { + $form = array(); + _locale_language_form($form, $language); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save language') + ); + $form['#submit']['locale_edit_language_form_submit'] = array(); + $form['#validate']['locale_edit_language_form_validate'] = array(); + $form['#theme'] = 'locale_edit_language_form'; + return $form; + } + else { + drupal_not_found(); + } +} + +/** + * Common pieces of the language addition and editing form. + * + * @param $form + * A parent form item (or empty array) to add items below. + * @param $language + * Language object to edit. + */ +function _locale_language_form(&$form, $language = NULL) { + if (!is_object($language)) { + $language = new stdClass(); + } + if (isset($language->language)) { + $form['langcode_view'] = array( + '#type' => 'item', + '#title' => t('Language code'), + '#value' => $language->language + ); + $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)), + '#description' => t('This should be an RFC 4646 compliant language identifier. Basic tags use a country code with an optional script or regional variant name, like "en", "en-US" and "zh-Hant".', array('@rfc4646' => 'http://www.ietf.org/rfc/rfc4646.txt')), + ); + } + $form['langname'] = array('#type' => 'textfield', + '#title' => t('Language name in English'), + '#maxlength' => 64, + '#default_value' => @$language->name, + '#required' => TRUE, + '#description' => t('Name of the language. Will be available for translation in all languages.'), + ); + $form['langnative'] = array('#type' => 'textfield', + '#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, + '#description' => t('Optional path prefix, for example "deutsch" for the German version. This value is not used in the "None" and "Domain" negotiation schemes. You can leave this empty if you use "Path only" negotiation and this is the default language. Changing this will break existing URLs.') + ); + $form['domain'] = array('#type' => 'textfield', + '#title' => t('Language domain'), + '#maxlength' => 64, + '#default_value' => @$language->domain, + '#description' => t('Optional custom domain with protocol (eg. "http://example.de" or "http://de.example.com" for the German version). This value is only used in the "Domain" negotiation mode. If left empty and in "Domain" mode, this language will not be accessible.'), + ); + $form['direction'] = array('#type' => 'radios', + '#title' => t('Direction'), + '#required' => TRUE, + '#description' => t('Direction of the text being written in this language.'), + '#default_value' => @$language->direction, + '#options' => array(0 => t('Left to right'), 1 => t('Right to left')) + ); + return $form; +} + /** * User interface for the language addition screen. */ @@ -183,16 +288,21 @@ function _locale_admin_manage_add_screen() { * Validate the language addition form. */ function locale_add_language_form_validate($form_id, $form_values) { - if ($duplicate = db_num_rows(db_query("SELECT locale FROM {locales_meta} WHERE locale = '%s'", $form_values['langcode'])) != 0) { - form_set_error(t('The language %language (%code) already exists.', array('%language' => $form_values['langname'], '%code' => $form_values['langcode']))); + if ($duplicate = db_num_rows(db_query("SELECT language FROM {languages} WHERE language = '%s'", $form_values['langcode'])) != 0) { + form_set_error('langcode', t('The language %language (%code) already exists.', array('%language' => $form_values['langname'], '%code' => $form_values['langcode']))); } if (!isset($form_values['langname'])) { - $isocodes = _locale_get_iso639_list(); - if (!isset($isocodes[$form_values['langcode']])) { + // Predefined language selection. + $predefined = _locale_get_predefined_list(); + if (!isset($predefined[$form_values['langcode']])) { form_set_error('langcode', t('Invalid language code.')); } } + else { + // Reuse the editing form validation routine if we add a custom language + locale_edit_language_form_validate($form_id, $form_values); + } } /** @@ -201,31 +311,108 @@ function locale_add_language_form_validate($form_id, $form_values) { function locale_add_language_form_submit($form_id, $form_values) { if (isset($form_values['langname'])) { // Custom language form. - _locale_add_language($form_values['langcode'], $form_values['langname']); + _locale_add_language($form_values['langcode'], $form_values['langname'], $form_values['langnative'], $form_values['direction'], $form_values['domain'], $form_values['prefix']); } else { - $isocodes = _locale_get_iso639_list(); - _locale_add_language($form_values['langcode'], $isocodes[$form_values['langcode']][0]); + // Predefined language selection. + $predefined = _locale_get_predefined_list(); + $lang = &$predefined[$form_values['langcode']]; + _locale_add_language($form_values['langcode'], $lang[0], isset($lang[1]) ? $lang[1] : $lang[0], isset($lang[2]) ? $lang[2] : 0, '', $form_values['langcode']); } return 'admin/build/locale'; } +/** + * Validate the language editing form. Reused for custom language addition too. + */ +function locale_edit_language_form_validate($form_id, $form_values) { + if (!empty($form_values['domain']) && !empty($form_values['prefix'])) { + form_set_error('prefix', t('Domain and path prefix values should not be set at the same time.')); + } + if (!empty($form_values['domain']) && $duplicate = db_fetch_object(db_query("SELECT language FROM {languages} WHERE domain = '%s' AND language != '%s'", $form_values['domain'], $form_values['langcode']))) { + form_set_error('domain', t('The domain (%domain) is already tied to a language (%language).', array('%domain' => $form_values['domain'], '%language' => $duplicate->language))); + } + $default = language_default(); + if (empty($form_values['prefix']) && $default->language != $form_values['langcode'] && empty($form_values['domain'])) { + form_set_error('prefix', t('Only the default language can have both the domain and prefix empty.')); + } + if (!empty($form_values['prefix']) && $duplicate = db_fetch_object(db_query("SELECT language FROM {languages} WHERE prefix = '%s' AND language != '%s'", $form_values['prefix'], $form_values['langcode']))) { + form_set_error('prefix', t('The prefix (%prefix) is already tied to a language (%language).', array('%prefix' => $form_values['prefix'], '%language' => $duplicate->language))); + } +} + +/** + * Process the language editing form submission. + */ +function locale_edit_language_form_submit($form_id, $form_values) { + db_query("UPDATE {languages} SET name = '%s', native = '%s', domain = '%s', prefix = '%s', direction = %d WHERE language = '%s'", $form_values['langname'], $form_values['langnative'], $form_values['domain'], $form_values['prefix'], $form_values['direction'], $form_values['langcode']); + $default = language_default(); + if ($default->language == $form_values['langcode']) { + $default->name = $form_values['langname']; + $default->native = $form_values['langnative']; + $default->domain = $form_values['domain']; + $default->prefix = $form_values['prefix']; + $default->direction = $form_values['direction']; + variable_set('language_default', $default); + } + return 'admin/build/locale'; +} + +/** + * Setting for language negotiation options + */ +function locale_configure_language_form() { + $form['language_negotiation'] = array( + '#title' => t('Language negotiation'), + '#type' => 'radios', + '#options' => array( + LANGUAGE_NEGOTIATION_NONE => t('None. Language will be independent of visitor preferences and language prefixes or domains.'), + LANGUAGE_NEGOTIATION_PATH_DEFAULT => t('Path prefix only. If a suitable path prefix is not identified, the default language is used.'), + LANGUAGE_NEGOTIATION_PATH => t('Path prefix with language fallback. If a suitable path prefix is not identified, language is based on user preferences and browser language settings.'), + LANGUAGE_NEGOTIATION_DOMAIN => t('Domain name only. If a suitable domain name is not identified, the default language is used.')), + '#default_value' => variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE), + '#description' => t('How should languages get detected? Changing this also changes how paths are constructed, so setting a different value breaks all URLs. Do not change on a live site without thinking twice!') + ); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save settings') + ); + return $form; +} + +/** + * Submit function for language negotiation settings. + */ +function locale_configure_language_form_submit($form_id, $form_values) { + variable_set('language_negotiation', $form_values['language_negotiation']); + drupal_set_message(t('Language negotiation configuration saved.')); + return 'admin/build/locale'; +} + +// --------------------------------------------------------------------------------- +// Translation import/export screens (administration only) + /** * User interface for the translation import screen. */ function _locale_admin_import() { - $languages = locale_supported_languages(FALSE, TRUE); - $languages = array_map('t', $languages['name']); + $languages = language_list(); + $names = array(); + foreach ($languages as $langcode => $language) { + $names[$langcode] = t($language->name); + } + // English means the factory default strings, + // we should not import into it. unset($languages['en']); if (!count($languages)) { - $languages = _locale_prepare_iso_list(); + $languages = _locale_prepare_predefined_list(); } else { $languages = array( t('Already added languages') => $languages, - t('Languages not yet added') => _locale_prepare_iso_list() + t('Languages not yet added') => _locale_prepare_predefined_list() ); } @@ -240,7 +427,7 @@ function _locale_admin_import() { ); $form['import']['langcode'] = array('#type' => 'select', '#title' => t('Import into'), - '#options' => $languages, + '#options' => $names, '#description' => t('Choose the language you want to add strings into. If you choose a language which is not yet set up, then it will be added.'), ); $form['import']['mode'] = array('#type' => 'radios', @@ -259,10 +446,11 @@ function _locale_admin_import() { */ function _locale_admin_import_submit($form_id, $form_values) { // Add language, if not yet supported - $languages = locale_supported_languages(TRUE, TRUE); - if (!isset($languages['name'][$form_values['langcode']])) { - $isocodes = _locale_get_iso639_list(); - _locale_add_language($form_values['langcode'], $isocodes[$form_values['langcode']][0], FALSE); + $languages = language_list('language', TRUE); + if (!isset($languages[$form_values['langcode']])) { + $predefined = _locale_get_predefined_list(); + $lang = &$predefined[$form_values['langcode']]; + _locale_add_language($form_values['langcode'], $lang[0], isset($lang[1]) ? $lang[1] : '', isset($lang[2]) ? $lang[2] : 0, '', '', FALSE); } // Now import strings into the language @@ -283,7 +471,7 @@ function _locale_export_po_form($languages) { ); $form['export']['langcode'] = array('#type' => 'select', '#title' => t('Language name'), - '#options' => $languages, + '#options' => array_keys($languages), '#description' => t('Select the language you would like to export in gettext Portable Object (.po) format.'), ); $form['export']['submit'] = array('#type' => 'submit', '#value' => t('Export')); @@ -308,8 +496,11 @@ function _locale_export_pot_form() { * User interface for the translation export screen */ function _locale_admin_export_screen() { - $languages = locale_supported_languages(FALSE, TRUE); - $languages = array_map('t', $languages['name']); + $languages = language_list(); + $names = array(); + foreach ($languages as $langcode => $language) { + $names[$langcode] = t($language->name); + } unset($languages['en']); $output = ''; @@ -330,14 +521,20 @@ function _locale_export_po_form_submit($form_id, $form_values) { _locale_export_po($form_values['langcode']); } +// --------------------------------------------------------------------------------- +// String search and editing (administration only) + /** * User interface for the string search screen */ function _locale_string_seek_form() { // Get *all* languages set up - $languages = locale_supported_languages(FALSE, TRUE); - asort($languages['name']); unset($languages['name']['en']); - $languages['name'] = array_map('check_plain', $languages['name']); + $languages = language_list(); + unset($languages['en']); + $names = array(); + foreach ($languages as $language) { + $names[$language->language] = check_plain(t($language->name)); + } // Present edit form preserving previous user settings $query = _locale_string_seek_query(); @@ -347,19 +544,19 @@ function _locale_string_seek_form() { ); $form['search']['string'] = array('#type' => 'textfield', '#title' => t('Strings to search for'), - '#default_value' => $query->string, + '#default_value' => @$query['string'], '#size' => 30, '#maxlength' => 30, '#description' => t('Leave blank to show all strings. The search is case sensitive.'), ); $form['search']['language'] = array('#type' => 'radios', '#title' => t('Language'), - '#default_value' => ($query->language ? $query->language : 'all'), - '#options' => array_merge(array('all' => t('All languages'), 'en' => t('English (provided by Drupal)')), $languages['name']), + '#default_value' => (!empty($query['language']) ? $query['language'] : 'all'), + '#options' => array_merge(array('all' => t('All languages'), 'en' => t('English (provided by Drupal)')), $names), ); $form['search']['searchin'] = array('#type' => 'radios', '#title' => t('Search in'), - '#default_value' => ($query->searchin ? $query->searchin : 'all'), + '#default_value' => (!empty($query['searchin']) ? $query['searchin'] : 'all'), '#options' => array('all' => t('All strings in that language'), 'translated' => t('Only translated strings'), 'untranslated' => t('Only untranslated strings')), ); $form['search']['submit'] = array('#type' => 'submit', '#value' => t('Search')); @@ -372,10 +569,10 @@ function _locale_string_seek_form() { * User interface for string editing. */ function _locale_string_edit($lid) { - $languages = locale_supported_languages(FALSE, TRUE); - unset($languages['name']['en']); + $languages = language_list(); + unset($languages['en']); - $result = db_query('SELECT DISTINCT s.source, t.translation, t.locale FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid WHERE s.lid = %d', $lid); + $result = db_query('SELECT DISTINCT s.source, t.translation, t.language FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid WHERE s.lid = %d', $lid); $form = array(); $form['translations'] = array('#tree' => TRUE); while ($translation = db_fetch_object($result)) { @@ -384,13 +581,13 @@ function _locale_string_edit($lid) { // Approximate the number of rows in a textfield with a maximum of 10. $rows = min(ceil(str_word_count($orig) / 12), 10); - $form['translations'][$translation->locale] = array( + $form['translations'][$translation->language] = array( '#type' => 'textarea', - '#title' => $languages['name'][$translation->locale], + '#title' => $languages[$translation->language]->name, '#default_value' => $translation->translation, '#rows' => $rows, ); - unset($languages['name'][$translation->locale]); + unset($languages[$translation->language]); } // Handle erroneous lid. @@ -406,10 +603,10 @@ function _locale_string_edit($lid) { '#weight' => -1, ); - foreach ($languages['name'] as $key => $lang) { - $form['translations'][$key] = array( + foreach ($languages as $langcode => $language) { + $form['translations'][$langcode] = array( '#type' => 'textarea', - '#title' => $lang, + '#title' => t($language->name), '#rows' => $rows, ); } @@ -427,12 +624,12 @@ function _locale_string_edit($lid) { function _locale_string_edit_submit($form_id, $form_values) { $lid = $form_values['lid']; foreach ($form_values['translations'] as $key => $value) { - $trans = db_fetch_object(db_query("SELECT translation FROM {locales_target} WHERE lid = %d AND locale = '%s'", $lid, $key)); + $trans = db_fetch_object(db_query("SELECT translation FROM {locales_target} WHERE lid = %d AND languauge = '%s'", $lid, $key)); if (isset($trans->translation)) { - db_query("UPDATE {locales_target} SET translation = '%s' WHERE lid = %d AND locale = '%s'", $value, $lid, $key); + 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, locale) VALUES (%d, '%s', '%s')", $lid, $value, $key); + db_query("INSERT INTO {locales_target} (lid, translation, language) VALUES (%d, '%s', '%s')", $lid, $value, $key); } } drupal_set_message(t('The string has been saved.')); @@ -457,6 +654,9 @@ function _locale_string_delete($lid) { drupal_goto('admin/build/locale/string/search'); } +// --------------------------------------------------------------------------------- +// Utility functions + /** * Parses Gettext Portable Object file information and inserts into database * @@ -474,7 +674,7 @@ function _locale_import_po($file, $lang, $mode) { } // Check if we have the language already in the database - if (!db_fetch_object(db_query("SELECT locale FROM {locales_meta} WHERE locale = '%s'", $lang))) { + if (!db_fetch_object(db_query("SELECT language FROM {languages} WHERE language = '%s'", $lang))) { drupal_set_message(t('The language selected for import is not supported.'), 'error'); return FALSE; } @@ -708,10 +908,10 @@ function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NUL // Get the plural formula if ($hdr["Plural-Forms"] && $p = _locale_import_parse_plural_forms($hdr["Plural-Forms"], $file->filename)) { list($nplurals, $plural) = $p; - db_query("UPDATE {locales_meta} SET plurals = %d, formula = '%s' WHERE locale = '%s'", $nplurals, $plural, $lang); + db_query("UPDATE {languages} SET plurals = %d, formula = '%s' WHERE language = '%s'", $nplurals, $plural, $lang); } else { - db_query("UPDATE {locales_meta} SET plurals = %d, formula = '%s' WHERE locale = '%s'", 0, '', $lang); + db_query("UPDATE {languages} SET plurals = %d, formula = '%s' WHERE language = '%s'", 0, '', $lang); } $headerdone = TRUE; } @@ -738,13 +938,13 @@ function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NUL $lid = $loc->lid; // update location field db_query("UPDATE {locales_source} SET location = '%s' WHERE lid = %d", $comments, $lid); - $trans2 = db_fetch_object(db_query("SELECT lid, translation, plid, plural FROM {locales_target} WHERE lid = %d AND locale = '%s'", $lid, $lang)); + $trans2 = db_fetch_object(db_query("SELECT lid, translation, plid, plural FROM {locales_target} WHERE lid = %d AND language = '%s'", $lid, $lang)); if (!$trans2->lid) { // no translation in current language - db_query("INSERT INTO {locales_target} (lid, locale, translation, plid, plural) VALUES (%d, '%s', '%s', %d, %d)", $lid, $lang, $trans, $plid, $key); + db_query("INSERT INTO {locales_target} (lid, language, translation, plid, plural) VALUES (%d, '%s', '%s', %d, %d)", $lid, $lang, $trans, $plid, $key); $additions++; } // translation exists else if ($mode == 'overwrite' || $trans2->translation == '') { - db_query("UPDATE {locales_target} SET translation = '%s', plid = %d, plural = %d WHERE locale = '%s' AND lid = %d", $trans, $plid, $key, $lang, $lid); + db_query("UPDATE {locales_target} SET translation = '%s', plid = %d, plural = %d WHERE language = '%s' AND lid = %d", $trans, $plid, $key, $lang, $lid); if ($trans2->translation == '') { $additions++; } @@ -757,7 +957,7 @@ function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NUL db_query("INSERT INTO {locales_source} (location, source) VALUES ('%s', '%s')", $comments, $english[$key]); $loc = db_fetch_object(db_query("SELECT lid FROM {locales_source} WHERE source = '%s'", $english[$key])); $lid = $loc->lid; - db_query("INSERT INTO {locales_target} (lid, locale, translation, plid, plural) VALUES (%d, '%s', '%s', %d, %d)", $lid, $lang, $trans, $plid, $key); + db_query("INSERT INTO {locales_target} (lid, language, translation, plid, plural) VALUES (%d, '%s', '%s', %d, %d)", $lid, $lang, $trans, $plid, $key); if ($trans != '') { $additions++; } @@ -775,13 +975,13 @@ function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NUL $lid = $loc->lid; // update location field db_query("UPDATE {locales_source} SET location = '%s' WHERE source = '%s'", $comments, $english); - $trans = db_fetch_object(db_query("SELECT lid, translation FROM {locales_target} WHERE lid = %d AND locale = '%s'", $lid, $lang)); + $trans = db_fetch_object(db_query("SELECT lid, translation FROM {locales_target} WHERE lid = %d AND language = '%s'", $lid, $lang)); if (!$trans->lid) { // no translation in current language - db_query("INSERT INTO {locales_target} (lid, locale, translation) VALUES (%d, '%s', '%s')", $lid, $lang, $translation); + db_query("INSERT INTO {locales_target} (lid, language, translation) VALUES (%d, '%s', '%s')", $lid, $lang, $translation); $additions++; } // translation exists else if ($mode == 'overwrite') { //overwrite in any case - db_query("UPDATE {locales_target} SET translation = '%s' WHERE locale = '%s' AND lid = %d", $translation, $lang, $lid); + db_query("UPDATE {locales_target} SET translation = '%s' WHERE language = '%s' AND lid = %d", $translation, $lang, $lid); if ($trans->translation == '') { $additions++; } @@ -790,7 +990,7 @@ function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NUL } } // overwrite if empty string else if ($trans->translation == '') { - db_query("UPDATE {locales_target} SET translation = '%s' WHERE locale = '%s' AND lid = %d", $translation, $lang, $lid); + db_query("UPDATE {locales_target} SET translation = '%s' WHERE language = '%s' AND lid = %d", $translation, $lang, $lid); $additions++; } } @@ -798,7 +998,7 @@ function _locale_import_one_string($op, $value = NULL, $mode = NULL, $lang = NUL db_query("INSERT INTO {locales_source} (location, source) VALUES ('%s', '%s')", $comments, $english); $loc = db_fetch_object(db_query("SELECT lid FROM {locales_source} WHERE source = '%s'", $english)); $lid = $loc->lid; - db_query("INSERT INTO {locales_target} (lid, locale, translation) VALUES (%d, '%s', '%s')", $lid, $lang, $translation); + db_query("INSERT INTO {locales_target} (lid, language, translation) VALUES (%d, '%s', '%s')", $lid, $lang, $translation); if ($translation != '') { $additions++; } @@ -1104,8 +1304,8 @@ function _locale_export_po($language) { // Get language specific strings, or all strings if ($language) { - $meta = db_fetch_object(db_query("SELECT * FROM {locales_meta} WHERE locale = '%s'", $language)); - $result = db_query("SELECT s.lid, s.source, s.location, t.translation, t.plid, t.plural FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid WHERE t.locale = '%s' ORDER BY t.plid, t.plural", $language); + $meta = db_fetch_object(db_query("SELECT * FROM {languages} WHERE language = '%s'", $language)); + $result = db_query("SELECT s.lid, s.source, s.location, t.translation, t.plid, t.plural FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid WHERE t.language = '%s' ORDER BY t.plid, t.plural", $language); } else { $result = db_query("SELECT s.lid, s.source, s.location, t.plid, t.plural FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid ORDER BY t.plid, t.plural"); @@ -1290,40 +1490,27 @@ function _locale_string_language_list($translation) { // Add CSS drupal_add_css(drupal_get_path('module', 'locale') .'/locale.css', 'module', 'all', FALSE); - $languages = locale_supported_languages(FALSE, TRUE); - unset($languages['name']['en']); + $languages = language_list(); + unset($languages['en']); $output = ''; - foreach ($languages['name'] as $key => $value) { - if (isset($translation[$key])) { - $output .= ($translation[$key] != '') ? $key .' ' : "$key "; - } + foreach ($languages as $langcode => $language) { + $output .= (!empty($translation[$langcode])) ? $langcode .' ' : "$langcode "; } return $output; } /** - * Build object out of search criteria specified in request variables + * Build array out of search criteria specified in request variables */ function _locale_string_seek_query() { - static $query; + static $query = NULL; if (!isset($query)) { + $query = array(); $fields = array('string', 'language', 'searchin'); - $query = new stdClass(); - if (is_array($_REQUEST['edit'])) { - foreach ($_REQUEST['edit'] as $key => $value) { - if (!empty($value) && in_array($key, $fields)) { - $query->$key = $value; - } - } - } - else { - foreach ($_REQUEST as $key => $value) { - if (!empty($value) && in_array($key, $fields)) { - $query->$key = strpos(',', $value) ? explode(',', $value) : $value; - } - } + foreach ($fields as $field) { + $query[$field] = !empty($_REQUEST[$field]) ? $_REQUEST[$field] : ''; } } return $query; @@ -1333,37 +1520,39 @@ function _locale_string_seek_query() { * Perform a string search and display results in a table */ function _locale_string_seek() { + $output = ''; + // We have at least one criterion to match if ($query = _locale_string_seek_query()) { - $join = "SELECT s.source, s.location, s.lid, t.translation, t.locale FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid "; + $join = "SELECT s.source, s.location, s.lid, t.translation, t.language FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid "; $arguments = array(); // Compute LIKE section - switch ($query->searchin) { + switch ($query['searchin']) { case 'translated': $where = "WHERE (t.translation LIKE '%%%s%%' AND t.translation != '')"; $orderby = "ORDER BY t.translation"; - $arguments[] = $query->string; + $arguments[] = $query['string']; break; case 'untranslated': $where = "WHERE (s.source LIKE '%%%s%%' AND t.translation = '')"; $orderby = "ORDER BY s.source"; - $arguments[] = $query->string; + $arguments[] = $query['string']; break; case 'all' : default: $where = "WHERE (s.source LIKE '%%%s%%' OR t.translation LIKE '%%%s%%')"; $orderby = ''; - $arguments[] = $query->string; - $arguments[] = $query->string; + $arguments[] = $query['string']; + $arguments[] = $query['string']; break; } - switch ($query->language) { + switch ($query['language']) { // Force search in source strings case "en": $sql = $join ." WHERE s.source LIKE '%%%s%%' ORDER BY s.source"; - $arguments = array($query->string); // $where is not used, discard its arguments + $arguments = array($query['string']); // $where is not used, discard its arguments break; // Search in all languages case "all": @@ -1371,8 +1560,8 @@ function _locale_string_seek() { break; // Some different language default: - $sql = "$join $where AND t.locale = '%s' $orderby"; - $arguments[] = $query->language; + $sql = "$join $where AND t.language = '%s' $orderby"; + $arguments[] = $query['language']; } $result = pager_query($sql, 50, 0, NULL, $arguments); @@ -1380,25 +1569,19 @@ function _locale_string_seek() { $header = array(t('String'), t('Locales'), array('data' => t('Operations'), 'colspan' => '2')); $arr = array(); while ($locale = db_fetch_object($result)) { - $arr[$locale->lid]['locales'][$locale->locale] = $locale->translation; + $arr[$locale->lid]['locales'][$locale->language] = $locale->translation; $arr[$locale->lid]['location'] = $locale->location; $arr[$locale->lid]['source'] = $locale->source; } + $rows = array(); foreach ($arr as $lid => $value) { $rows[] = array(array('data' => check_plain(truncate_utf8($value['source'], 150, FALSE, TRUE)) .'
'. $value['location'] .''), array('data' => _locale_string_language_list($value['locales']), 'align' => 'center'), array('data' => l(t('edit'), "admin/build/locale/string/edit/$lid"), 'class' => 'nowrap'), array('data' => l(t('delete'), "admin/build/locale/string/delete/$lid"), 'class' => 'nowrap')); } - $request = array(); - if (count($query)) { - foreach ($query as $key => $value) { - $request[$key] = (is_array($value)) ? implode(',', $value) : $value; - } - } - if (count($rows)) { $output .= theme('table', $header, $rows); } - if ($pager = theme('pager', NULL, 50, 0, $request)) { + if ($pager = theme('pager', NULL, 50)) { $output .= $pager; } } @@ -1412,24 +1595,25 @@ function _locale_string_seek() { /** * Prepares the language code list for a select form item with only the unsupported ones */ -function _locale_prepare_iso_list() { - $languages = locale_supported_languages(FALSE, TRUE); - $isocodes = _locale_get_iso639_list(); - foreach ($isocodes as $key => $value) { - if (isset($languages['name'][$key])) { - unset($isocodes[$key]); +function _locale_prepare_predefined_list() { + $languages = language_list(); + $predefined = _locale_get_predefined_list(); + foreach ($predefined as $key => $value) { + if (isset($languages[$key])) { + unset($predefined[$key]); continue; } + // Include native name in output, if possible if (count($value) == 2) { $tname = t($value[0]); - $isocodes[$key] = ($tname == $value[1]) ? $tname : "$tname ($value[1])"; + $predefined[$key] = ($tname == $value[1]) ? $tname : "$tname ($value[1])"; } else { - $isocodes[$key] = t($value[0]); + $predefined[$key] = t($value[0]); } } - asort($isocodes); - return $isocodes; + asort($predefined); + return $predefined; } /** @@ -1437,7 +1621,7 @@ function _locale_prepare_iso_list() { * * Based on ISO 639 and http://people.w3.org/rishida/names/languages.html */ -function _locale_get_iso639_list() { +function _locale_get_predefined_list() { return array( "aa" => array("Afar"), "ab" => array("Abkhazian", "аҧсуа бызшәа"), @@ -1445,7 +1629,7 @@ function _locale_get_iso639_list() { "af" => array("Afrikaans"), "ak" => array("Akan"), "am" => array("Amharic", "አማርኛ"), - "ar" => array("Arabic", "العربية"), + "ar" => array("Arabic", /* Left-to-right marker "‭" */ "العربية", 1), "as" => array("Assamese"), "av" => array("Avar"), "ay" => array("Aymara"), @@ -1480,7 +1664,7 @@ function _locale_get_iso639_list() { "es" => array("Spanish", "Español"), "et" => array("Estonian", "Eesti"), "eu" => array("Basque", "Euskera"), - "fa" => array("Persian", "فارسی"), + "fa" => array("Persian", /* Left-to-right marker "‭" */ "فارسی", 1), "ff" => array("Fulah", "Fulfulde"), "fi" => array("Finnish", "Suomi"), "fj" => array("Fiji"), @@ -1494,7 +1678,7 @@ function _locale_get_iso639_list() { "gu" => array("Gujarati"), "gv" => array("Manx"), "ha" => array("Hausa"), - "he" => array("Hebrew", "עברית"), + "he" => array("Hebrew", /* Left-to-right marker "‭" */ "עברית", 1), "hi" => array("Hindi", "हिन्दी"), "ho" => array("Hiri Motu"), "hr" => array("Croatian", "Hrvatski"), @@ -1561,7 +1745,7 @@ function _locale_get_iso639_list() { "pa" => array("Punjabi"), "pi" => array("Pali"), "pl" => array("Polish", "Polski"), - "ps" => array("Pashto", "پښتو"), + "ps" => array("Pashto", /* Left-to-right marker "‭" */ "پښتو", 1), "pt" => array("Portuguese, Portugal", "Português"), "pt-br" => array("Portuguese, Brazil", "Português"), "qu" => array("Quechua"), @@ -1605,7 +1789,7 @@ function _locale_get_iso639_list() { "ty" => array("Tahitian"), "ug" => array("Uighur"), "uk" => array("Ukrainian", "Українська"), - "ur" => array("Urdu", "اردو"), + "ur" => array("Urdu", /* Left-to-right marker "‭" */ "اردو", 1), "uz" => array("Uzbek", "o'zbek"), "ve" => array("Venda"), "vi" => array("Vietnamese", "Tiếng Việt"), diff --git a/includes/path.inc b/includes/path.inc index 4801458dbe7..9efee799ac1 100644 --- a/includes/path.inc +++ b/includes/path.inc @@ -34,15 +34,21 @@ function drupal_init_path() { * - source: return the Drupal system URL for a path alias (if one exists). * @param $path * The path to investigate for corresponding aliases or system URLs. + * @param $path_language + * Optional language code to search the path with. Defaults to the page language. + * If there's no path defined for that language it will search paths without + * language. * * @return * Either a Drupal system path, an aliased path, or FALSE if no path was * found. */ -function drupal_lookup_path($action, $path = '') { - // $map keys are Drupal paths and the values are the corresponding aliases - static $map = array(), $no_src = array(); - static $count; +function drupal_lookup_path($action, $path = '', $path_language = '') { + global $language; + // $map is an array with language keys, holding arrays of Drupal paths to alias relations + static $map = array(), $no_src = array(), $count; + + $path_language = $path_language ? $path_language : $language->language; // Use $count to avoid looking up paths in subsequent calls if there simply are no aliases if (!isset($count)) { @@ -55,26 +61,29 @@ function drupal_lookup_path($action, $path = '') { } elseif ($count > 0 && $path != '') { if ($action == 'alias') { - if (isset($map[$path])) { - return $map[$path]; + if (isset($map[$path_language][$path])) { + return $map[$path_language][$path]; } - $alias = db_result(db_query("SELECT dst FROM {url_alias} WHERE src = '%s'", $path)); - $map[$path] = $alias; + // Get the most fitting result falling back with alias without language + $alias = db_result(db_query("SELECT dst FROM {url_alias} WHERE src = '%s' AND language IN('%s', '') ORDER BY language DESC", $path, $path_language)); + $map[$path_language][$path] = $alias; return $alias; } // Check $no_src for this $path in case we've already determined that there // isn't a path that has this alias - elseif ($action == 'source' && !isset($no_src[$path])) { + elseif ($action == 'source' && !isset($no_src[$path_language][$path])) { // Look for the value $path within the cached $map - if (!$src = array_search($path, $map)) { - if ($src = db_result(db_query("SELECT src FROM {url_alias} WHERE dst = '%s'", $path))) { - $map[$src] = $path; + $src = ''; + if (!isset($map[$path_language]) || !($src = array_search($path, $map[$path_language]))) { + // Get the most fitting result falling back with alias without language + if ($src = db_result(db_query("SELECT src FROM {url_alias} WHERE dst = '%s' AND language IN('%s', '') ORDER BY language DESC", $path, $path_language))) { + $map[$path_language][$src] = $path; } else { // We can't record anything into $map because we do not have a valid // index and there is no need because we have not learned anything // about any Drupal path. Thus cache to $no_src. - $no_src[$path] = TRUE; + $no_src[$path_language][$path] = TRUE; } } return $src; @@ -89,18 +98,20 @@ function drupal_lookup_path($action, $path = '') { * * @param $path * An internal Drupal path. + * @param $path_language + * An optional language code to look up the path in. * * @return * An aliased path if one was found, or the original path if no alias was * found. */ -function drupal_get_path_alias($path) { +function drupal_get_path_alias($path, $path_language = '') { $result = $path; - if ($alias = drupal_lookup_path('alias', $path)) { + if ($alias = drupal_lookup_path('alias', $path, $path_language)) { $result = $alias; } if (function_exists('custom_url_rewrite')) { - $result = custom_url_rewrite('alias', $result, $path); + $result = custom_url_rewrite('alias', $result, $path, $path_language); } return $result; } @@ -110,18 +121,20 @@ function drupal_get_path_alias($path) { * * @param $path * A Drupal path alias. + * @param $path_language + * An optional language code to look up the path in. * * @return * The internal path represented by the alias, or the original alias if no * internal path was found. */ -function drupal_get_normal_path($path) { +function drupal_get_normal_path($path, $path_language = '') { $result = $path; - if ($src = drupal_lookup_path('source', $path)) { + if ($src = drupal_lookup_path('source', $path, $path_language)) { $result = $src; } if (function_exists('custom_url_rewrite')) { - $result = custom_url_rewrite('source', $result, $path); + $result = custom_url_rewrite('source', $result, $path, $path_language); } return $result; } diff --git a/modules/locale/locale.install b/modules/locale/locale.install index ecba859167c..da8b26c6bed 100644 --- a/modules/locale/locale.install +++ b/modules/locale/locale.install @@ -11,14 +11,18 @@ function locale_install() { switch ($GLOBALS['db_type']) { case 'mysql': case 'mysqli': - db_query("CREATE TABLE {locales_meta} ( - locale varchar(12) NOT NULL default '', + db_query("CREATE TABLE {languages} ( + language varchar(12) NOT NULL default '', name varchar(64) NOT NULL default '', + native varchar(64) NOT NULL default '', + direction int NOT NULL default '0', enabled int NOT NULL default '0', - isdefault int NOT NULL default '0', plurals int NOT NULL default '0', formula varchar(128) NOT NULL default '', - PRIMARY KEY (locale) + domain varchar(128) NOT NULL default '', + prefix varchar(128) NOT NULL default '', + weight int NOT NULL default '0', + PRIMARY KEY (language) ) /*!40100 DEFAULT CHARACTER SET UTF8 */ "); db_query("CREATE TABLE {locales_source} ( @@ -32,25 +36,29 @@ function locale_install() { db_query("CREATE TABLE {locales_target} ( lid int NOT NULL default '0', translation blob NOT NULL, - locale varchar(12) NOT NULL default '', + language varchar(12) NOT NULL default '', plid int NOT NULL default '0', plural int NOT NULL default '0', KEY lid (lid), - KEY lang (locale), + KEY lang (language), KEY plid (plid), KEY plural (plural) ) /*!40100 DEFAULT CHARACTER SET UTF8 */ "); break; case 'pgsql': - db_query("CREATE TABLE {locales_meta} ( - locale varchar(12) NOT NULL default '', + db_query("CREATE TABLE {languages} ( + language varchar(12) NOT NULL default '', name varchar(64) NOT NULL default '', + native varchar(64) NOT NULL default '', + direction int NOT NULL default '0', enabled int NOT NULL default '0', - isdefault int NOT NULL default '0', plurals int NOT NULL default '0', formula varchar(128) NOT NULL default '', - PRIMARY KEY (locale) + domain varchar(128) NOT NULL default '', + prefix varchar(128) NOT NULL default '', + weight int NOT NULL default '0', + PRIMARY KEY (language) )"); db_query("CREATE TABLE {locales_source} ( @@ -63,25 +71,86 @@ function locale_install() { db_query("CREATE TABLE {locales_target} ( lid int NOT NULL default '0', translation text NOT NULL, - locale varchar(12) NOT NULL default '', + language varchar(12) NOT NULL default '', plid int NOT NULL default '0', plural int NOT NULL default '0' )"); db_query("CREATE INDEX {locales_target}_lid_idx ON {locales_target} (lid)"); - db_query("CREATE INDEX {locales_target}_locale_idx ON {locales_target} (locale)"); + db_query("CREATE INDEX {locales_target}_language_idx ON {locales_target} (language)"); db_query("CREATE INDEX {locales_target}_plid_idx ON {locales_target} (plid)"); db_query("CREATE INDEX {locales_target}_plural_idx ON {locales_target} (plural)"); db_query("CREATE INDEX {locales_source}_source_idx ON {locales_source} (source)"); break; } - db_query("INSERT INTO {locales_meta} (locale, name, enabled, isdefault) VALUES ('en', 'English', '1', '1')"); + db_query("INSERT INTO {languages} (language, name, native, direction, enabled, weight) VALUES ('en', 'English', 'English', '0', '1', '0')"); } +/** + * @defgroup updates-5.0-to-x.x Locale updates from 5.0 to x.x + * @{ + */ + +function locale_update_2001() { + $ret = array(); + switch ($GLOBALS['db_type']) { + case 'mysql': + case 'mysqli': + $ret[] = update_sql("CREATE TABLE {languages} ( + language varchar(12) NOT NULL default '', + name varchar(64) NOT NULL default '', + native varchar(64) NOT NULL default '', + direction int NOT NULL default '0', + enabled int NOT NULL default '0', + plurals int NOT NULL default '0', + formula varchar(128) NOT NULL default '', + domain varchar(128) NOT NULL default '', + prefix varchar(128) NOT NULL default '', + weight int NOT NULL default '0', + PRIMARY KEY (language) + ) /*!40100 DEFAULT CHARACTER SET UTF8 */ "); + break; + case 'pgsql': + $ret[] = update_sql("CREATE TABLE {languages} ( + language varchar(12) NOT NULL default '', + name varchar(64) NOT NULL default '', + native varchar(64) NOT NULL default '', + direction int NOT NULL default '0', + enabled int NOT NULL default '0', + plurals int NOT NULL default '0', + formula varchar(128) NOT NULL default '', + domain varchar(128) NOT NULL default '', + prefix varchar(128) NOT NULL default '', + weight int NOT NULL default '0', + PRIMARY KEY (language) + )"); + break; + } + + // Save the languages + $ret[] = update_sql("INSERT INTO {languages} (language, name, native, direction, enabled, plurals, formula, domain, prefix, weight) SELECT locale, name, '', 0, enabled, plurals, formula, '', locale, 0 FROM {locales_meta}"); + + // Save the language count in the variable table + $count = db_result(db_query('SELECT COUNT(*) FROM {languages} WHERE enabled = 1')); + variable_set('language_count', $count); + + // Save the default language in the variable table + $default = db_fetch_object(db_query('SELECT * FROM {locales_meta} WHERE isdefault = 1')); + variable_set('language_default', (object) array('language' => $default->locale, 'name' => $default->name, 'native' => '', 'direction' => 0, 'enabled' => 1, 'plurals' => $default->plurals, 'formula' => $default->formula, 'domain' => '', 'prefix' => $default->locale, 'weight' => 0)); + + $ret[] = update_sql("DROP TABLE {locales_meta}"); + return $ret; +} + +/** + * @} End of "defgroup updates-5.0-to-x.x" + * The next series of updates should start at 3000. + */ + /** * Implementation of hook_uninstall(). */ function locale_uninstall() { - db_query('DROP TABLE {locales_meta}'); + db_query('DROP TABLE {languages}'); db_query('DROP TABLE {locales_source}'); db_query('DROP TABLE {locales_target}'); } diff --git a/modules/locale/locale.module b/modules/locale/locale.module index df8103cc193..9c63936b205 100644 --- a/modules/locale/locale.module +++ b/modules/locale/locale.module @@ -25,6 +25,7 @@ function locale_help($section) { $output .= '

'. t('For more information please read the configuration and customization handbook Locale page.', array('@locale' => 'http://drupal.org/handbook/modules/locale/')) .'

'; return $output; case 'admin/build/locale': + case 'admin/build/locale/language': case 'admin/build/locale/language/overview': return 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 add language page, or directly by importing a translation. 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.

Drupal interface translations may be added or extended by several courses: by importing an existing translation, by translating everything from scratch, or by a combination of these approaches.

", array("@search" => url("admin/build/locale/string/search"), "@import" => url("admin/build/locale/language/import"), "@add-language" => url("admin/build/locale/language/add"))); case 'admin/build/locale/language/add': @@ -35,6 +36,8 @@ function locale_help($section) { return '

'. 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.") .'

'; case 'admin/build/locale/string/search': return '

'. t("It is often convenient to get the strings from your setup on the export page, 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/locale/language/export"))) .'

'; + case 'admin/build/locale/language/configure': + return '

'. 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 language editing pages.', array('@languages' => url('admin/build/locale/language/overview'))) .'

'; } } @@ -44,8 +47,8 @@ function locale_help($section) { function locale_menu() { // Main admin menu item $items['admin/build/locale'] = array( - 'title' => t('Localization'), - 'description' => t('Configure site localization and user interface translation.'), + 'title' => t('Languages'), + 'description' => t('Configure languages and user interface translation.'), 'page callback' => 'locale_admin_manage', 'access arguments' => array('administer locales'), ); @@ -56,8 +59,8 @@ function locale_menu() { 'weight' => -10, 'type' => MENU_DEFAULT_LOCAL_TASK, ); - $items['admin/build/locale/string/search'] = array( - 'title' => t('Manage strings'), + $items['admin/build/locale/string'] = array( + 'title' => t('Manage interface strings'), 'page callback' => 'locale_string_search', 'weight' => 10, 'type' => MENU_LOCAL_TASK, @@ -76,13 +79,32 @@ function locale_menu() { 'weight' => 5, 'type' => MENU_LOCAL_TASK, ); - $items['admin/build/locale/language/import'] = array( + $items['admin/build/locale/language/configure'] = array( + 'title' => t('Configure'), + 'page callback' => 'locale_admin_manage_configure', + 'weight' => 10, + 'type' => MENU_LOCAL_TASK, + ); + $items['admin/build/locale/language/edit/%'] = array( + 'title' => t('Edit language'), + 'page callback' => 'drupal_get_form', + 'page arguments' => array('locale_admin_manage_edit', 5), + 'type' => MENU_CALLBACK, + ); + + // Manage interface translations subtabs + $items['admin/build/locale/string/search'] = array( + 'title' => t('Search'), + 'weight' => 0, + 'type' => MENU_DEFAULT_LOCAL_TASK, + ); + $items['admin/build/locale/string/import'] = array( 'title' => t('Import'), 'page callback' => 'locale_admin_import', 'weight' => 10, 'type' => MENU_LOCAL_TASK, ); - $items['admin/build/locale/language/export'] = array( + $items['admin/build/locale/string/export'] = array( 'title' => t('Export'), 'page callback' => 'locale_admin_export', 'weight' => 20, @@ -124,12 +146,17 @@ function locale_perm() { * Implementation of hook_user(). */ function locale_user($type, $edit, &$user, $category = NULL) { - $languages = locale_supported_languages(); - if ($type == 'form' && $category == 'account' && count($languages['name']) > 1) { + if ($type == 'form' && $category == 'account' && variable_get('language_count', 1) > 1 && variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE) == LANGUAGE_NEGOTIATION_PATH) { + $languages = language_list('enabled'); + $languages = $languages['1']; if ($user->language == '') { - $user->language = key($languages['name']); + $default = language_default(); + $user->language = $default->language; + } + $names = array(); + foreach($languages as $langcode => $language) { + $names[$langcode] = t($language->name) .' ('. $language->native .')'; } - $languages['name'] = array_map('check_plain', array_map('t', $languages['name'])); $form['locale'] = array('#type' => 'fieldset', '#title' => t('Interface language settings'), '#weight' => 1, @@ -137,13 +164,31 @@ function locale_user($type, $edit, &$user, $category = NULL) { $form['locale']['language'] = array('#type' => 'radios', '#title' => t('Language'), '#default_value' => $user->language, - '#options' => $languages['name'], + '#options' => $names, '#description' => t('Selecting a different locale will change the interface language of the site.'), ); return $form; } } +/** + * Implementation of hook_form_alter(). Adds language fields to forms. + */ +function locale_form_alter($form_id, &$form) { + switch ($form_id) { + case 'path_admin_edit': + $form['language'] = array( + '#type' => 'radios', + '#title' => t('Language'), + '#options' => array('' => t('All languages')) + locale_language_list('name'), + '#default_value' => $form['#alias'] ? $form['#alias']['language'] : '', + '#weight' => -10 + ); + break; + } +} + + // --------------------------------------------------------------------------------- // Locale core functionality (needed on all page loads) @@ -153,16 +198,14 @@ function locale_user($type, $edit, &$user, $category = NULL) { * This function is called from t() to translate a string if needed. */ function locale($string) { - global $locale; + global $language; static $locale_t; // Store database cached translations in a static var. if (!isset($locale_t)) { - $cache = cache_get("locale:$locale", 'cache'); - - if (!$cache) { + if (!($cache = cache_get('locale:'. $language->language, 'cache'))) { locale_refresh_cache(); - $cache = cache_get("locale:$locale", 'cache'); + $cache = cache_get('locale:'. $language->language, 'cache'); } $locale_t = unserialize($cache->data); } @@ -175,7 +218,7 @@ function locale($string) { // We do not have this translation cached, so get it from the DB. else { - $result = db_query("SELECT s.lid, t.translation FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid WHERE s.source = '%s' AND t.locale = '%s'", $string, $locale); + $result = db_query("SELECT s.lid, t.translation FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid WHERE s.source = '%s' AND t.language = '%s'", $string, $language->language); // Translation found if ($trans = db_fetch_object($result)) { if (!empty($trans->translation)) { @@ -189,20 +232,20 @@ function locale($string) { $result = db_query("SELECT lid, source FROM {locales_source} WHERE source = '%s'", $string); // We have no such translation if ($obj = db_fetch_object($result)) { - if ($locale) { - db_query("INSERT INTO {locales_target} (lid, locale, translation) VALUES (%d, '%s', '')", $obj->lid, $locale); + if ($language) { + db_query("INSERT INTO {locales_target} (lid, language, translation) VALUES (%d, '%s', '')", $obj->lid, $language->language); } } // We have no such source string else { db_query("INSERT INTO {locales_source} (location, source) VALUES ('%s', '%s')", request_uri(), $string); - if ($locale) { + if ($language) { $lid = db_fetch_object(db_query("SELECT lid FROM {locales_source} WHERE source = '%s'", $string)); - db_query("INSERT INTO {locales_target} (lid, locale, translation) VALUES (%d, '%s', '')", $lid->lid, $locale); + db_query("INSERT INTO {locales_target} (lid, language, translation) VALUES (%d, '%s', '')", $lid->lid, $language->language); } } // Clear locale cache in DB - cache_clear_all("locale:$locale", 'cache'); + cache_clear_all('locale:' . $language->language, 'cache'); } } @@ -215,61 +258,31 @@ function locale($string) { * We only store short strings to improve performance and consume less memory. */ function locale_refresh_cache() { - $languages = locale_supported_languages(); + $languages = language_list('enabled'); + $languages = $languages['1']; - foreach (array_keys($languages['name']) as $locale) { - $result = db_query("SELECT s.source, t.translation, t.locale FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid WHERE t.locale = '%s' AND LENGTH(s.source) < 75", $locale); + foreach ($languages as $language) { + $result = db_query("SELECT s.source, t.translation, t.language FROM {locales_source} s INNER JOIN {locales_target} t ON s.lid = t.lid WHERE t.language = '%s' AND LENGTH(s.source) < 75", $language->language); $t = array(); while ($data = db_fetch_object($result)) { $t[$data->source] = (empty($data->translation) ? TRUE : $data->translation); } - cache_set("locale:$locale", 'cache', serialize($t)); + cache_set('locale:' . $language->language, 'cache', serialize($t)); } } -/** - * Returns list of languages supported on this site. - * - * @param $reset Refresh cached language list. - * @param $getall Return all languages (even disabled ones) - */ -function locale_supported_languages($reset = FALSE, $getall = FALSE) { - static $enabled = NULL; - static $all = NULL; - - if ($reset) { - unset($enabled); unset($all); - } - - if (!isset($enabled)) { - $enabled = $all = array(); - $all['name'] = $all['formula'] = $enabled['name'] = $enabled['formula'] = array(); - $result = db_query('SELECT locale, name, formula, enabled FROM {locales_meta} ORDER BY isdefault DESC, enabled DESC, name ASC'); - while ($row = db_fetch_object($result)) { - $all['name'][$row->locale] = $row->name; - $all['formula'][$row->locale] = $row->formula; - if ($row->enabled) { - $enabled['name'][$row->locale] = $row->name; - $enabled['formula'][$row->locale] = $row->formula; - } - } - } - return $getall ? $all : $enabled; -} - /** * Returns plural form index for a specific number. * * The index is computed from the formula of this language. */ function locale_get_plural($count) { - global $locale; + global $language; static $locale_formula, $plurals = array(); if (!isset($plurals[$count])) { if (!isset($locale_formula)) { - $languages = locale_supported_languages(); - $locale_formula = $languages['formula'][$locale]; + $locale_formula = $language->formula; } if ($locale_formula) { $n = $count; @@ -284,6 +297,34 @@ function locale_get_plural($count) { return $plurals[$count]; } + +/** + * 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 + */ +function locale_language_list($field = 'name') { + $languages = language_list('enabled'); + $list = array(); + foreach($languages[1] as $language) { + $list[$language->language] = ($field == 'name') ? t($language->name) : $language->$field; + } + return $list; +} + // --------------------------------------------------------------------------------- // Language management functionality (administration only) @@ -308,14 +349,15 @@ function locale_admin_manage_delete_form($langcode) { } // For other locales, warn user that data loss is ahead. - $languages = locale_supported_languages(FALSE, TRUE); + $languages = language_list(); - if (!isset($languages['name'][$langcode])) { + if (!isset($languages[$langcode])) { drupal_not_found(); } else { $form['langcode'] = array('#type' => 'value', '#value' => $langcode); - return confirm_form($form, t('Are you sure you want to delete the language %name?', array('%name' => t($languages['name'][$langcode]))), 'admin/build/locale/language/overview', t('Deleting a language will remove all data associated with it. This action cannot be undone.'), t('Delete'), t('Cancel')); + $form['#submit']['locale_admin_manage_delete_form_submit'] = array(); + return confirm_form($form, t('Are you sure you want to delete the language %name?', array('%name' => t($languages[$langcode]->name))), 'admin/build/locale/language/overview', t('Deleting a language will remove all data associated with it. This action cannot be undone.'), t('Delete'), t('Cancel')); } } @@ -323,11 +365,11 @@ function locale_admin_manage_delete_form($langcode) { * Process language deletion submissions. */ function locale_admin_manage_delete_form_submit($form_id, $form_values) { - $languages = locale_supported_languages(FALSE, TRUE); - if (isset($languages['name'][$form_values['langcode']])) { - db_query("DELETE FROM {locales_meta} WHERE locale = '%s'", $form_values['langcode']); - db_query("DELETE FROM {locales_target} WHERE locale = '%s'", $form_values['langcode']); - $message = t('The language %locale has been removed.', array('%locale' => t($languages['name'][$form_values['langcode']]))); + $languages = language_list(); + if (isset($languages[$form_values['langcode']])) { + db_query("DELETE FROM {languages} WHERE language = '%s'", $form_values['langcode']); + db_query("DELETE FROM {locales_target} WHERE language = '%s'", $form_values['langcode']); + $message = t('The language %locale has been removed.', array('%locale' => t($languages[$form_values['langcode']]->name))); drupal_set_message($message); watchdog('locale', $message); } @@ -346,6 +388,16 @@ function locale_admin_manage_add() { return _locale_admin_manage_add_screen(); } +function locale_admin_manage_edit($langcode) { + include_once './includes/locale.inc'; + return _locale_admin_manage_edit_screen($langcode); +} + +function locale_admin_manage_configure() { + include_once './includes/locale.inc'; + return drupal_get_form("locale_configure_language_form"); +} + // --------------------------------------------------------------------------------- // Gettext Portable Object import functionality (administration only) diff --git a/modules/node/node.module b/modules/node/node.module index 5e9bce1a6e7..3ccec64ab45 100644 --- a/modules/node/node.module +++ b/modules/node/node.module @@ -1747,7 +1747,7 @@ function node_block($op = 'list', $delta = 0) { * The link should be an absolute URL. */ function node_feed($nodes = 0, $channel = array()) { - global $base_url, $locale; + global $base_url, $language; if (!$nodes) { $nodes = db_query_range(db_rewrite_sql('SELECT n.nid, n.created FROM {node} n WHERE n.promote = 1 AND n.status = 1 ORDER BY n.created DESC'), 0, variable_get('feed_default_items', 10)); @@ -1809,7 +1809,7 @@ function node_feed($nodes = 0, $channel = array()) { 'title' => variable_get('site_name', 'Drupal') .' - '. variable_get('site_slogan', ''), 'link' => $base_url, 'description' => variable_get('site_mission', ''), - 'language' => $locale + 'language' => $language->language ); $channel = array_merge($channel_defaults, $channel); diff --git a/modules/path/path.module b/modules/path/path.module index c245fb63b16..f78300f1c1a 100644 --- a/modules/path/path.module +++ b/modules/path/path.module @@ -26,6 +26,7 @@ function path_help($section) { $output .= '

'. t('For more information please read the configuration and customization handbook Path page.', array('@path' => 'http://drupal.org/handbook/modules/path/')) .'

'; return $output; case 'admin/build/path': + case 'admin/build/path/list': return '

'. t("Drupal provides users complete control over URLs through aliasing. This feature is typically used to make URLs human-readable or easy to remember. For example, one could map the relative URL 'node/1' onto 'about'. Each system path can have multiple aliases.") .'

'; case 'admin/build/path/add': return '

'. t('Enter the path you wish to create the alias for, followed by the name of the new alias.') .'

'; @@ -105,7 +106,6 @@ function path_admin_delete_confirm($pid) { $_GET['destination'] ? $_GET['destination'] : 'admin/build/path', t('This action cannot be undone.'), t('Delete'), t('Cancel') ); } - return $output; } @@ -127,49 +127,51 @@ function path_admin_delete($pid = 0) { drupal_set_message(t('The alias has been deleted.')); } - - /** * Set an aliased path for a given Drupal path, preventing duplicates. */ -function path_set_alias($path = NULL, $alias = NULL, $pid = NULL) { +function path_set_alias($path = NULL, $alias = NULL, $pid = NULL, $language = '') { if ($path && !$alias) { - db_query("DELETE FROM {url_alias} WHERE src = '%s'", $path); + // Delete based on path + db_query("DELETE FROM {url_alias} WHERE src = '%s' AND language = '%s'", $path, $language); db_query("UPDATE {menu} SET link_path = path WHERE path = '%s'", $path); drupal_clear_path_cache(); } else if (!$path && $alias) { - db_query("DELETE FROM {url_alias} WHERE dst = '%s'", $alias); + // Delete based on alias + db_query("DELETE FROM {url_alias} WHERE dst = '%s' AND language = '%s'", $alias, $language); db_query("UPDATE {menu} SET link_path = path WHERE link_path = '%s'", $alias); drupal_clear_path_cache(); } else if ($path && $alias) { $path = urldecode($path); - $path_count = db_result(db_query("SELECT COUNT(src) FROM {url_alias} WHERE src = '%s'", $path)); + $path_count = db_result(db_query("SELECT COUNT(src) FROM {url_alias} WHERE src = '%s' AND language = '%s'", $path, $language)); $alias = urldecode($alias); // Alias count can only be 0 or 1. - $alias_count = db_result(db_query("SELECT COUNT(dst) FROM {url_alias} WHERE dst = '%s'", $alias)); + $alias_count = db_result(db_query("SELECT COUNT(dst) FROM {url_alias} WHERE dst = '%s' AND language = '%s'", $alias, $language)); if ($alias_count == 0) { if ($pid) { - db_query("UPDATE {url_alias} SET dst = '%s', src = '%s' WHERE pid = %d", $alias, $path, $pid); + // Existing path changed data + db_query("UPDATE {url_alias} SET src = '%s', dst = '%s', language = '%s' WHERE pid = %d", $path, $alias, $language, $pid); } else { - db_query("INSERT INTO {url_alias} (src, dst) VALUES ('%s', '%s')", $path, $alias); + // No such alias yet in this language + db_query("INSERT INTO {url_alias} (src, dst, language) VALUES ('%s', '%s', '%s')", $path, $alias, $language); } } // The alias exists. else { // This path has no alias yet, so we redirect the alias here. if ($path_count == 0) { - db_query("UPDATE {url_alias} SET src = '%s' WHERE dst = '%s'", $path, $alias); + db_query("UPDATE {url_alias} SET src = '%s' WHERE dst = '%s' AND language = '%s'", $path, $alias, $language); } else { // This will delete the path that alias was originally pointing to. - path_set_alias(NULL, $alias); + path_set_alias(NULL, $alias, NULL, $language); // This will remove the current aliases of the path. - path_set_alias($path); - path_set_alias($path, $alias); + path_set_alias($path, NULL, NULL, $language); + path_set_alias($path, $alias, NULL, $language); } } if ($alias_count == 0 || $path_count == 0) { @@ -186,6 +188,7 @@ function path_form($edit = '') { $form['#submit']['path_form_submit'] = array(); $form['#validate']['path_form_validate'] = array(); $form['#theme'] = 'path_form'; + $form['#alias'] = $edit; $form['src'] = array( '#type' => 'textfield', @@ -198,13 +201,13 @@ function path_form($edit = '') { ); $form['dst'] = array( '#type' => 'textfield', + '#title' => t('Path alias'), '#default_value' => $edit['dst'], '#maxlength' => 64, '#size' => 45, '#description' => t('Specify an alternative path by which this data can be accessed. For example, type "about" when writing an about page. Use a relative path and don\'t add a trailing slash or the URL alias won\'t work.'), '#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q=') ); - if ($edit['pid']) { $form['pid'] = array('#type' => 'hidden', '#value' => $edit['pid']); $form['submit'] = array('#type' => 'submit', '#value' => t('Update alias')); @@ -227,7 +230,8 @@ function path_nodeapi(&$node, $op, $arg) { switch ($op) { case 'validate': $node->path = trim($node->path); - if (db_result(db_query("SELECT COUNT(dst) FROM {url_alias} WHERE dst = '%s' AND src != '%s'", $node->path, "node/$node->nid"))) { + $language = isset($node->language) ? $node->language : ''; + if (db_result(db_query("SELECT COUNT(dst) FROM {url_alias} WHERE dst = '%s' AND src != '%s' AND language = '%s'", $node->path, "node/$node->nid", $language))) { form_set_error('path', t('The path is already in use.')); } break; @@ -307,23 +311,37 @@ function path_perm() { * Return a listing of all defined URL aliases. */ function path_overview() { + // Enable language column if locale is enabled or if we have any alias with language + $count = db_result(db_query("SELECT COUNT(*) FROM {url_alias} WHERE language != ''")); + $multilanguage = (module_exists('locale') || $count); + $sql = 'SELECT * FROM {url_alias}'; $header = array( array('data' => t('Alias'), 'field' => 'dst', 'sort' => 'asc'), array('data' => t('System'), 'field' => 'src'), array('data' => t('Operations'), 'colspan' => '2') ); + if ($multilanguage) { + $header[3] = $header[2]; + $header[2] = array('data' => t('Language'), 'field' => 'language'); + } $sql .= tablesort_sql($header); $result = pager_query($sql, 50); $rows = array(); $destination = drupal_get_destination(); while ($data = db_fetch_object($result)) { - $rows[] = array(check_plain($data->dst), check_plain($data->src), l(t('edit'), "admin/build/path/edit/$data->pid", array('query' => $destination)), l(t('delete'), "admin/build/path/delete/$data->pid", array('query' => $destination))); + $row = array(check_plain($data->dst), check_plain($data->src), l(t('edit'), "admin/build/path/edit/$data->pid", array('query' => $destination)), l(t('delete'), "admin/build/path/delete/$data->pid", array('query' => $destination))); + if ($multilanguage) { + $row[4] = $row[3]; + $row[3] = $row[2]; + $row[2] = module_invoke('locale', 'language_name', $data->language); + } + $rows[] = $row; } if (empty($rows)) { - $rows[] = array(array('data' => t('No URL aliases available.'), 'colspan' => '4')); + $rows[] = array(array('data' => t('No URL aliases available.'), 'colspan' => ($multilanguage ? 5 : 4))); } $output = theme('table', $header, $rows); @@ -345,9 +363,11 @@ function path_form_validate($form_id, $form_values) { $src = $form_values['src']; $dst = $form_values['dst']; $pid = $form_values['pid']; + // Language is only set if locale module is enabled, otherwise save for all languages. + $language = isset($form_values['language']) ? $form_values['language'] : ''; - if (db_result(db_query("SELECT COUNT(dst) FROM {url_alias} WHERE pid != %d AND dst = '%s'", $pid, $dst))) { - form_set_error('dst', t('The alias %alias is already in use.', array('%alias' => $dst))); + if (db_result(db_query("SELECT COUNT(dst) FROM {url_alias} WHERE pid != %d AND dst = '%s' AND language = '%s'", $pid, $dst, $language))) { + form_set_error('dst', t('The alias %alias is already in use in this language.', array('%alias' => $dst))); } } @@ -355,7 +375,8 @@ function path_form_validate($form_id, $form_values) { * Save a new URL alias to the database. */ function path_form_submit($form_id, $form_values) { - path_set_alias($form_values['src'], $form_values['dst'], $form_values['pid']); + // Language is only set if locale module is enabled + path_set_alias($form_values['src'], $form_values['dst'], $form_values['pid'], isset($form_values['language']) ? $form_values['language'] : ''); drupal_set_message(t('The alias has been saved.')); return 'admin/build/path'; diff --git a/modules/system/system.install b/modules/system/system.install index d0ddeb92941..d942b71d07e 100644 --- a/modules/system/system.install +++ b/modules/system/system.install @@ -440,8 +440,9 @@ function system_install() { pid int unsigned NOT NULL auto_increment, src varchar(128) NOT NULL default '', dst varchar(128) NOT NULL default '', + language varchar(12) NOT NULL default '', PRIMARY KEY (pid), - UNIQUE KEY dst (dst), + UNIQUE KEY dst_language (dst, language), KEY src (src) ) /*!40100 DEFAULT CHARACTER SET UTF8 */ "); @@ -585,7 +586,8 @@ function system_install() { db_query("CREATE TABLE {variable} ( name varchar(128) NOT NULL default '', value longtext NOT NULL, - PRIMARY KEY (name) + language varchar(12) NOT NULL default '', + PRIMARY KEY (name, language) ) /*!40100 DEFAULT CHARACTER SET UTF8 */ "); db_query("CREATE TABLE {vocabulary} ( @@ -926,10 +928,11 @@ function system_install() { pid serial CHECK (pid >= 0), src varchar(128) NOT NULL default '', dst varchar(128) NOT NULL default '', - PRIMARY KEY (pid), - UNIQUE (dst) + language varchar(12) NOT NULL default '', + PRIMARY KEY (pid) )"); db_query("CREATE INDEX {url_alias}_src_idx ON {url_alias} (src)"); + db_query("CREATE UNIQUE INDEX {url_alias}_dst_language_idx ON {url_alias} (dst, language)"); db_query("CREATE TABLE {permission} ( rid int_unsigned NOT NULL default '0', @@ -1072,7 +1075,8 @@ function system_install() { db_query("CREATE TABLE {variable} ( name varchar(128) NOT NULL default '', value text NOT NULL, - PRIMARY KEY (name) + language varchar(12) NOT NULL default '', + PRIMARY KEY (name, language) )"); db_query("CREATE TABLE {vocabulary} ( @@ -3688,6 +3692,27 @@ function system_update_2004() { return $ret; } +/** + * Add language to url_alias table and modify indexes. + */ +function system_update_2005() { + $ret = array(); + switch ($GLOBALS['db_type']) { + case 'pgsql': + db_add_column($ret, 'url_alias', 'language', 'varchar(12)', array('default' => "''", 'not null' => TRUE)); + $ret[] = update_sql('DROP INDEX {url_alias}_dst_idx'); + $ret[] = update_sql('CREATE UNIQUE INDEX {url_alias}_dst_language_idx ON {url_alias}(dst, language)'); + break; + case 'mysql': + case 'mysqli': + $ret[] = update_sql("ALTER TABLE {url_alias} ADD language varchar(12) NOT NULL default ''"); + $ret[] = update_sql("ALTER TABLE {url_alias} DROP INDEX dst"); + $ret[] = update_sql("ALTER TABLE {url_alias} ADD UNIQUE dst_language (dst, language)"); + break; + } + return $ret; +} + /** * @} End of "defgroup updates-5.0-to-x.x" * The next series of updates should start at 3000. diff --git a/sites/default/settings.php b/sites/default/settings.php index 42df5e25d21..3da74f0775a 100644 --- a/sites/default/settings.php +++ b/sites/default/settings.php @@ -90,7 +90,7 @@ * $db_url = 'mysqli://username:password@localhost/databasename'; * $db_url = 'pgsql://username:password@localhost/databasename'; */ -$db_url = 'mysql://username:password@localhost/databasename'; +$db_url = 'mysql://doug:civicactions@localhost/doug_drupal6_lang'; $db_prefix = ''; /** @@ -112,7 +112,7 @@ $db_prefix = ''; * It is not allowed to have a trailing slash; Drupal will add it * for you. */ -# $base_url = 'http://www.example.com'; // NO trailing slash! +$base_url = 'http://localhost/doug6lang'; // NO trailing slash! /** * PHP settings: @@ -135,6 +135,7 @@ ini_set('session.save_handler', 'user'); ini_set('session.use_only_cookies', 1); ini_set('session.use_trans_sid', 0); ini_set('url_rewriter.tags', ''); +ini_set('safe_mode', 1); // because my scripts actually take longer! /** * We try to set the correct cookie domain. diff --git a/themes/engines/phptemplate/phptemplate.engine b/themes/engines/phptemplate/phptemplate.engine index 38f6dfdca2b..7d7438e8fd9 100644 --- a/themes/engines/phptemplate/phptemplate.engine +++ b/themes/engines/phptemplate/phptemplate.engine @@ -201,7 +201,7 @@ function phptemplate_page($content, $show_blocks = TRUE) { 'head' => drupal_get_html_head(), 'head_title' => implode(' | ', $head_title), 'help' => theme('help'), - 'language' => $GLOBALS['locale'], + 'language' => $GLOBALS['language'], 'layout' => $layout, 'logo' => theme_get_setting('logo'), 'messages' => theme('status_messages'),