Commit eb0caa35 authored by Dries's avatar Dries

- Patch #128866 by Gabor, Steven, chx, Jose et al: new language subsystem.

parent 5739c24c
......@@ -85,15 +85,20 @@
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_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'));
}
......@@ -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');
......
<?php
// $Id$
/**
* @file
* Multiple language handling functionality.
*/
/**
* Choose a language for the page, based on language negotiation settings.
*/
function language_initialize() {
global $user;
$mode = variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE);
switch($mode) {
case LANGUAGE_NEGOTIATION_NONE:
return language_default();
case LANGUAGE_NEGOTIATION_DOMAIN:
$languages = language_list();
foreach($languages as $language) {
$parts = parse_url($language->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;
}
}
}
......@@ -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 <a href="@locale-help">help screen</a>.', 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 <a href="@locale-help">help screen</a>.', 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));
$ratio = ($original->strings > 0 && $translation->translation > 0) ? round(($translation->translation/$original->strings)*100., 2) : 0;
$form['weight'] = array('#tree' => TRUE);
foreach ($languages as $langcode => $language) {
$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), '<strong>'. drupal_render($form['name'][$key]) .'</strong>', 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,69 +113,165 @@ 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 <a href="@iso-codes">ISO 639 language code</a> 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',
// 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();
$form['#theme'] = 'locale_add_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;