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 @@ ...@@ -85,15 +85,20 @@
define('DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE', 5); 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 * Final bootstrap phase: Drupal is fully loaded; validate and fix
* input data. * 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. * Role ID for anonymous users; should match what's in the "role" table.
...@@ -105,6 +110,30 @@ ...@@ -105,6 +110,30 @@
*/ */
define('DRUPAL_AUTHENTICATED_RID', 2); 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 * Start the timer with the specified name. If you start and stop
* the same timer multiple times, the measured intervals will be * the same timer multiple times, the measured intervals will be
...@@ -409,6 +438,7 @@ function variable_del($name) { ...@@ -409,6 +438,7 @@ function variable_del($name) {
unset($conf[$name]); unset($conf[$name]);
} }
/** /**
* Retrieve the current page from the cache. * Retrieve the current page from the cache.
* *
...@@ -762,11 +792,12 @@ function drupal_anonymous_user($session = '') { ...@@ -762,11 +792,12 @@ function drupal_anonymous_user($session = '') {
* DRUPAL_BOOTSTRAP_SESSION: initialize session handling. * DRUPAL_BOOTSTRAP_SESSION: initialize session handling.
* DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE: load bootstrap.inc and module.inc, start * DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE: load bootstrap.inc and module.inc, start
* the variable system and try to serve a page from the cache. * 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_PATH: set $_GET['q'] to Drupal path of request.
* DRUPAL_BOOTSTRAP_FULL: Drupal is fully loaded, validate and fix input data. * DRUPAL_BOOTSTRAP_FULL: Drupal is fully loaded, validate and fix input data.
*/ */
function drupal_bootstrap($phase) { 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))) { while (!is_null($current_phase = array_shift($phases))) {
_drupal_bootstrap($current_phase); _drupal_bootstrap($current_phase);
...@@ -824,6 +855,10 @@ function _drupal_bootstrap($phase) { ...@@ -824,6 +855,10 @@ function _drupal_bootstrap($phase) {
drupal_page_header(); drupal_page_header();
break; break;
case DRUPAL_BOOTSTRAP_LANGUAGE:
drupal_init_language();
break;
case DRUPAL_BOOTSTRAP_PATH: case DRUPAL_BOOTSTRAP_PATH:
require_once './includes/path.inc'; require_once './includes/path.inc';
// Initialize $_GET['q'] prior to loading modules and invoking hook_init(). // Initialize $_GET['q'] prior to loading modules and invoking hook_init().
...@@ -898,3 +933,65 @@ function get_t() { ...@@ -898,3 +933,65 @@ function get_t() {
} }
return $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() { ...@@ -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. * Translate strings to the current locale.
* *
...@@ -722,8 +695,8 @@ function locale_initialize() { ...@@ -722,8 +695,8 @@ function locale_initialize() {
* The translated string. * The translated string.
*/ */
function t($string, $args = 0) { function t($string, $args = 0) {
global $locale; global $language;
if (function_exists('locale') && $locale != 'en') { if (function_exists('locale') && $language->language != 'en') {
$string = locale($string); $string = locale($string);
} }
if (!$args) { if (!$args) {
...@@ -1177,6 +1150,11 @@ function url($path = NULL, $options = array()) { ...@@ -1177,6 +1150,11 @@ function url($path = NULL, $options = array()) {
'absolute' => FALSE, 'absolute' => FALSE,
'alias' => 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']) { if ($options['fragment']) {
$options['fragment'] = '#'. $options['fragment']; $options['fragment'] = '#'. $options['fragment'];
} }
...@@ -1884,7 +1862,6 @@ function xmlrpc($url) { ...@@ -1884,7 +1862,6 @@ function xmlrpc($url) {
function _drupal_bootstrap_full() { function _drupal_bootstrap_full() {
static $called; static $called;
global $locale;
if ($called) { if ($called) {
return; return;
...@@ -1908,8 +1885,6 @@ function _drupal_bootstrap_full() { ...@@ -1908,8 +1885,6 @@ function _drupal_bootstrap_full() {
fix_gpc_magic(); fix_gpc_magic();
// Load all enabled modules // Load all enabled modules
module_load_all(); 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 // Let all modules take action before menu system handles the reqest
module_invoke_all('init'); 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;
}
}
}
This diff is collapsed.
...@@ -34,15 +34,21 @@ function drupal_init_path() { ...@@ -34,15 +34,21 @@ function drupal_init_path() {
* - source: return the Drupal system URL for a path alias (if one exists). * - source: return the Drupal system URL for a path alias (if one exists).
* @param $path * @param $path
* The path to investigate for corresponding aliases or system URLs. * 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 * @return
* Either a Drupal system path, an aliased path, or FALSE if no path was * Either a Drupal system path, an aliased path, or FALSE if no path was
* found. * found.
*/ */
function drupal_lookup_path($action, $path = '') { function drupal_lookup_path($action, $path = '', $path_language = '') {
// $map keys are Drupal paths and the values are the corresponding aliases global $language;
static $map = array(), $no_src = array(); // $map is an array with language keys, holding arrays of Drupal paths to alias relations
static $count; 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 // Use $count to avoid looking up paths in subsequent calls if there simply are no aliases
if (!isset($count)) { if (!isset($count)) {
...@@ -55,26 +61,29 @@ function drupal_lookup_path($action, $path = '') { ...@@ -55,26 +61,29 @@ function drupal_lookup_path($action, $path = '') {
} }
elseif ($count > 0 && $path != '') { elseif ($count > 0 && $path != '') {
if ($action == 'alias') { if ($action == 'alias') {
if (isset($map[$path])) { if (isset($map[$path_language][$path])) {
return $map[$path]; return $map[$path_language][$path];
} }
$alias = db_result(db_query("SELECT dst FROM {url_alias} WHERE src = '%s'", $path)); // Get the most fitting result falling back with alias without language
$map[$path] = $alias; $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; return $alias;
} }
// Check $no_src for this $path in case we've already determined that there // Check $no_src for this $path in case we've already determined that there
// isn't a path that has this alias // 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 // Look for the value $path within the cached $map
if (!$src = array_search($path, $map)) { $src = '';
if ($src = db_result(db_query("SELECT src FROM {url_alias} WHERE dst = '%s'", $path))) { if (!isset($map[$path_language]) || !($src = array_search($path, $map[$path_language]))) {
$map[$src] = $path; // 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 { else {
// We can't record anything into $map because we do not have a valid // 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 // index and there is no need because we have not learned anything
// about any Drupal path. Thus cache to $no_src. // about any Drupal path. Thus cache to $no_src.
$no_src[$path] = TRUE; $no_src[$path_language][$path] = TRUE;
} }
} }
return $src; return $src;
...@@ -89,18 +98,20 @@ function drupal_lookup_path($action, $path = '') { ...@@ -89,18 +98,20 @@ function drupal_lookup_path($action, $path = '') {
* *
* @param $path * @param $path
* An internal Drupal path. * An internal Drupal path.
* @param $path_language
* An optional language code to look up the path in.
* *
* @return * @return
* An aliased path if one was found, or the original path if no alias was * An aliased path if one was found, or the original path if no alias was
* found. * found.
*/ */
function drupal_get_path_alias($path) { function drupal_get_path_alias($path, $path_language = '') {
$result = $path; $result = $path;
if ($alias = drupal_lookup_path('alias', $path)) { if ($alias = drupal_lookup_path('alias', $path, $path_language)) {
$result = $alias; $result = $alias;
} }
if (function_exists('custom_url_rewrite')) { if (function_exists('custom_url_rewrite')) {
$result = custom_url_rewrite('alias', $result, $path); $result = custom_url_rewrite('alias', $result, $path, $path_language);
} }
return $result; return $result;
} }
...@@ -110,18 +121,20 @@ function drupal_get_path_alias($path) { ...@@ -110,18 +121,20 @@ function drupal_get_path_alias($path) {
* *
* @param $path * @param $path
* A Drupal path alias. * A Drupal path alias.
* @param $path_language
* An optional language code to look up the path in.
* *
* @return * @return
* The internal path represented by the alias, or the original alias if no * The internal path represented by the alias, or the original alias if no
* internal path was found. * internal path was found.
*/ */
function drupal_get_normal_path($path) { function drupal_get_normal_path($path, $path_language = '') {
$result = $path; $result = $path;
if ($src = drupal_lookup_path('source', $path)) { if ($src = drupal_lookup_path('source', $path, $path_language)) {
$result = $src; $result = $src;
} }
if (function_exists('custom_url_rewrite')) { if (function_exists('custom_url_rewrite')) {
$result = custom_url_rewrite('source', $result, $path); $result = custom_url_rewrite('source', $result, $path, $path_language);
} }
return $result; return $result;
} }
......
...@@ -11,14 +11,18 @@ function locale_install() { ...@@ -11,14 +11,18 @@ function locale_install() {
switch ($GLOBALS['db_type']) { switch ($GLOBALS['db_type']) {
case 'mysql': case 'mysql':
case 'mysqli': case 'mysqli':
db_query("CREATE TABLE {locales_meta} ( db_query("CREATE TABLE {languages} (
locale varchar(12) NOT NULL default '', language varchar(12) NOT NULL default '',
name varchar(64) 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', enabled int NOT NULL default '0',
isdefault int NOT NULL default '0',
plurals int NOT NULL default '0', plurals int NOT NULL default '0',
formula varchar(128) NOT NULL default '', 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 */ "); ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
db_query("CREATE TABLE {locales_source} ( db_query("CREATE TABLE {locales_source} (
...@@ -32,25 +36,29 @@ function locale_install() { ...@@ -32,25 +36,29 @@ function locale_install() {
db_query("CREATE TABLE {locales_target} ( db_query("CREATE TABLE {locales_target} (
lid int NOT NULL default '0', lid int NOT NULL default '0',
translation blob NOT NULL, translation blob NOT NULL,
locale varchar(12) NOT NULL default '', language varchar(12) NOT NULL default '',
plid int NOT NULL default '0', plid int NOT NULL default '0',
plural int NOT NULL default '0', plural int NOT NULL default '0',
KEY lid (lid), KEY lid (lid),
KEY lang (locale), KEY lang (language),
KEY plid (plid), KEY plid (plid),
KEY plural (plural) KEY plural (plural)
) /*!40100 DEFAULT CHARACTER SET UTF8 */ "); ) /*!40100 DEFAULT CHARACTER SET UTF8 */ ");
break; break;
case 'pgsql': case 'pgsql':
db_query("CREATE TABLE {locales_meta} ( db_query("CREATE TABLE {languages} (