Commit 1b9cde9d authored by webchick's avatar webchick

#282191 by plach, nedjo, catch, et al: TF #1: Allow different interface language for the same path.

parent d72c5656
......@@ -154,27 +154,19 @@
define('DRUPAL_KILOBYTE', 1024);
/**
* No language negotiation. The default language is used.
* The type of language used to define the content language.
*/
define('LANGUAGE_NEGOTIATION_NONE', 0);
define('LANGUAGE_TYPE_CONTENT', 'language');
/**
* Path based negotiation with fallback to default language if no defined path
* prefix identified.
* The type of language used to select the user interface.
*/
define('LANGUAGE_NEGOTIATION_PATH_DEFAULT', 1);
define('LANGUAGE_TYPE_INTERFACE', 'language_interface');
/**
* Path based negotiation with fallback to user preferences and browser
* language detection if no defined path prefix identified.
* The type of language used for URLs.
*/
define('LANGUAGE_NEGOTIATION_PATH', 2);
/**
* Domain based negotiation with fallback to default language if no language
* identified by domain.
*/
define('LANGUAGE_NEGOTIATION_DOMAIN', 3);
define('LANGUAGE_TYPE_URL', 'language_url');
/**
* Language written left to right. Possible value of $language->direction.
......@@ -1628,22 +1620,49 @@ function get_t() {
}
/**
* Choose a language for the current page, based on site and user preferences.
* Initialize all the defined language types.
*/
function drupal_language_initialize() {
global $language, $user;
$types = language_types();
// 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();
$default = language_default();
foreach ($types as $type) {
$GLOBALS[$type] = $default;
}
}
else {
include_once DRUPAL_ROOT . '/includes/language.inc';
$language = language_initialize();
foreach ($types as $type) {
$GLOBALS[$type] = language_initialize($type);
}
}
}
/**
* The built-in language types.
*
* @return
* An array of key-values pairs where the key is the language type and the
* value is its configurability.
*/
function drupal_language_types() {
return array(
LANGUAGE_TYPE_CONTENT => TRUE,
LANGUAGE_TYPE_INTERFACE => TRUE,
LANGUAGE_TYPE_URL => FALSE,
);
}
/**
* Return an array of the available language types.
*/
function language_types() {
return array_keys(variable_get('language_types', drupal_language_types()));
}
/**
* Get a list of languages set up indexed by the specified key
*
......
......@@ -392,6 +392,26 @@ function drupal_get_query_parameters(array $query = NULL, array $exclude = array
return $params;
}
/**
* Split an URL-encoded query string into an array.
*
* @param $query
* The query string to split.
*
* @return
* An array of url decoded couples $param_name => $value.
*/
function drupal_get_query_array($query) {
$result = array();
if (!empty($query)) {
foreach (explode('&', $query) as $param) {
$param = explode('=', $param);
$result[$param[0]] = isset($param[1]) ? rawurldecode($param[1]) : '';
}
}
return $result;
}
/**
* Parse an array into a valid, rawurlencoded query string.
*
......@@ -1455,12 +1475,12 @@ function fix_gpc_magic() {
* The translated string.
*/
function t($string, array $args = array(), array $options = array()) {
global $language;
global $language_interface;
static $custom_strings;
// Merge in default.
if (empty($options['langcode'])) {
$options['langcode'] = isset($language->language) ? $language->language : 'en';
$options['langcode'] = isset($language_interface->language) ? $language_interface->language : 'en';
}
if (empty($options['context'])) {
$options['context'] = '';
......@@ -2438,7 +2458,8 @@ function url($path = NULL, array $options = array()) {
$path = '';
}
elseif (!empty($path) && !$options['alias']) {
$path = drupal_get_path_alias($path, isset($options['language']) ? $options['language']->language : '');
$language = isset($options['language']) && isset($options['language']->language) ? $options['language']->language : '';
$path = drupal_get_path_alias($path, $language);
}
if (function_exists('custom_url_rewrite_outbound')) {
......@@ -2546,7 +2567,7 @@ function drupal_attributes(array $attributes = array()) {
* an HTML string containing a link to the given path.
*/
function l($text, $path, array $options = array()) {
global $language;
global $language_url;
// Merge in defaults.
$options += array(
......@@ -2556,7 +2577,7 @@ function l($text, $path, array $options = array()) {
// Append active class.
if (($path == $_GET['q'] || ($path == '<front>' && drupal_is_front_page())) &&
(empty($options['language']) || $options['language']->language == $language->language)) {
(empty($options['language']) || $options['language']->language == $language_url->language)) {
$options['attributes']['class'][] = 'active';
}
......
This diff is collapsed.
This diff is collapsed.
......@@ -1397,7 +1397,7 @@ function theme_links($variables) {
$links = $variables['links'];
$attributes = $variables['attributes'];
$heading = $variables['heading'];
global $language;
global $language_url;
$output = '';
if (count($links) > 0) {
......@@ -1438,7 +1438,7 @@ function theme_links($variables) {
$class[] = 'last';
}
if (isset($link['href']) && ($link['href'] == $_GET['q'] || ($link['href'] == '<front>' && drupal_is_front_page()))
&& (empty($link['language']) || $link['language']->language == $language->language)) {
&& (empty($link['language']) || $link['language']->language == $language_url->language)) {
$class[] = 'active';
}
$output .= '<li' . drupal_attributes(array('class' => $class)) . '>';
......
......@@ -25,26 +25,117 @@ function hook_locale($op = 'groups') {
}
/**
* Perform alterations on translation links.
* Perform alterations on language switcher links.
*
* A translation link may need to point to a different path or use a translated
* link text before going through l(), which will just handle the path aliases.
* A language switcher link may need to point to a different path or use a
* translated link text before going through l(), which will just handle the
* path aliases.
*
* @param $links
* Nested array of links keyed by language code.
* @param $type
* The language type the links will switch.
* @param $path
* The current path.
*/
function hook_translation_link_alter(array &$links, $path) {
function hook_language_switch_link_alter(array &$links, $type, $path) {
global $language;
if (isset($links[$language])) {
if ($type == LANGUAGE_TYPE_CONTENT && isset($links[$language])) {
foreach ($links[$language] as $link) {
$link['attributes']['class'][] = 'active-language';
}
}
}
/**
* Allow modules to define their own language types.
*
* @return
* An array of language type definitions. Each language type has an identifier
* key. The language type definition is an associative array that may contain
* the following key-value pairs:
* - "name": The human-readable language type identifier.
* - "description": A description of the language type.
*/
function hook_language_types_info() {
return array(
'custom_language_type' => array(
'name' => t('Custom language'),
'description' => t('A custom language type.'),
),
);
}
/**
* Perform alterations on language types.
*
* @param $language_types
* Array of language type definitions.
*/
function hook_language_types_info_alter(array &$language_types) {
if (isset($language_types['custom_language_type'])) {
$language_types['custom_language_type_custom']['description'] = t('A far better description.');
}
}
/**
* Allow modules to define their own language providers.
*
* @return
* An array of language provider definitions. Each language provider has an
* identifier key. The language provider definition is an associative array
* that may contain the following key-value pairs:
* - "types": An array of allowed language types. If a language provider does
* not specify which language types it should be used with, it will be
* available for all the configurable language types.
* - "callbacks": An array of functions that will be called to perform various
* tasks. Possible key-value pairs are:
* - "language": Required. The callback that will determine the language
* value.
* - "switcher": The callback that will determine the language switch links
* associated to the current language provider.
* - "url_rewrite": The callback that will provide URL rewriting.
* - "file": A file that will be included before the callback is invoked; this
* allows callback functions to be in separate files.
* - "weight": The default weight the language provider has.
* - "name": A human-readable identifier.
* - "description": A description of the language provider.
* - "config": An internal path pointing to the language provider
* configuration page.
* - "cache": The value Drupal's page cache should be set to for the current
* language provider to be invoked.
*/
function hook_language_negotiation_info() {
return array(
'custom_language_provider' => array(
'callbacks' => array(
'language' => 'custom_language_provider_callback',
'switcher' => 'custom_language_switcher_callback',
'url_rewrite' => 'custom_language_url_rewrite_callback',
),
'file' => drupal_get_path('module', 'custom') . '/custom.module',
'weight' => -4,
'types' => array('custom_language_type'),
'name' => t('Custom language provider'),
'description' => t('This is a custom language provider.'),
'cache' => CACHE_DISABLED,
),
);
}
/**
* Perform alterations on language providers.
*
* @param $language_providers
* Array of language provider definitions.
*/
function hook_language_negotiation_info_alter(array &$language_providers) {
if (isset($language_providers['custom_language_provider'])) {
$language_providers['custom_language_provider']['config'] = 'admin/config/regional/language/configure/custom-language-provider';
}
}
/**
* @} End of "addtogroup hooks".
*/
......@@ -19,3 +19,11 @@
#locale-translation-filter-form .form-item select.form-select {
width: 100%;
}
.language-switcher-locale-session .active a.active {
color: #0062A0;
}
.language-switcher-locale-session .active a.session-active {
color: #000000;
}
......@@ -40,6 +40,47 @@ function locale_update_7000() {
db_add_index('locales_source', 'source_context', array(array('source', 30), 'context'));
}
/**
* Upgrade language negotiation settings.
*/
function locale_update_7001() {
require_once DRUPAL_ROOT . '/includes/language.inc';
switch (variable_get('language_negotiation', 0)) {
// LANGUAGE_NEGOTIATION_NONE.
case 0:
$negotiation = array();
break;
// LANGUAGE_NEGOTIATION_PATH_DEFAULT.
case 1:
$negotiation = array(LOCALE_LANGUAGE_NEGOTIATION_URL);
break;
// LANGUAGE_NEGOTIATION_PATH.
case 2:
$negotiation = array(LOCALE_LANGUAGE_NEGOTIATION_URL, LOCALE_LANGUAGE_NEGOTIATION_USER, LOCALE_LANGUAGE_NEGOTIATION_BROWSER);
break;
// LANGUAGE_NEGOTIATION_DOMAIN.
case 3:
variable_set('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN);
$negotiation = array(LOCALE_LANGUAGE_NEGOTIATION_URL);
break;
}
// Save new language negotiation options: UI language is tied to content
// language as this was Drupal 6 behavior.
language_negotiation_set(LANGUAGE_TYPE_CONTENT, array_flip($negotiation));
language_negotiation_set(LANGUAGE_TYPE_INTERFACE, array(LOCALE_LANGUAGE_NEGOTIATION_CONTENT => 0));
language_negotiation_set(LANGUAGE_TYPE_URL, array(LOCALE_LANGUAGE_NEGOTIATION_URL => 0));
// Unset the old language negotiation system variable.
variable_del('language_negotiation');
return array();
}
/**
* @} End of "defgroup updates-6.x-to-7.x"
*/
......@@ -62,15 +103,23 @@ function locale_uninstall() {
// Clear variables.
variable_del('language_default');
variable_del('language_count');
variable_del('language_negotiation');
variable_del('javascript_parsed');
variable_del('language_types');
variable_del('locale_language_negotiation_url_part');
variable_del('locale_language_negotiation_session_param');
variable_del('language_content_type_default');
variable_del('language_content_type_negotiation');
variable_del('locale_cache_strings');
variable_del('locale_js_directory');
variable_del('javascript_parsed');
foreach (language_types() as $type) {
variable_del("language_negotiation_$type");
variable_del("locale_language_providers_enabled_$type");
variable_del("locale_language_providers_weight_$type");
}
foreach (node_type_get_types() as $type => $content_type) {
$setting = variable_del('language_content_type_' . $type);
$setting = variable_del("language_content_type_$type");
}
// Switch back to English: with a $language->language value different from 'en'
......
This diff is collapsed.
This diff is collapsed.
......@@ -1137,6 +1137,52 @@ function node_build_content($node, $build_mode = 'full') {
drupal_alter('node_build', $node, $build_mode);
}
/**
* Implement hook_language_negotiation_info().
*/
function node_language_negotiation_info() {
$providers = array();
$providers['node-language'] = array(
'types' => array(LANGUAGE_TYPE_CONTENT),
'callbacks' => array('language' => 'node_language_provider'),
'file' => drupal_get_path('module', 'node') . '/node.module',
'name' => t('Node'),
'description' => t('The current node language is used.'),
);
return $providers;
}
/**
* Return the language of the current node.
*
* @param $languages
* An array of valid language objects.
*
* @return
* A valid language code on succes, FALSE otherwise.
*/
function node_language_provider($languages) {
require_once DRUPAL_ROOT . '/includes/path.inc';
$path = isset($_GET['q']) ? $_GET['q'] : '';
list($language, $path) = language_url_split_prefix($path, $languages);
$language = $language ? $language : language_default();
$path = drupal_get_normal_path($path, $language->language);
// We cannot use args now.
$path = explode('/', $path);
// Act only if we are in a node page.
if ($path[0] == 'node' && $nid = intval($path[1])) {
// We cannot perform a node load here.
$result = db_query('SELECT n.language FROM {node} n WHERE n.nid = :nid', array(':nid' => $nid))->fetchAssoc();
return $result['language'];
}
return FALSE;
}
/**
* Generate an array which displays a node detail page.
*
......
......@@ -183,7 +183,9 @@ class PathLanguageTestCase extends DrupalWebTestCase {
drupal_static_reset('language_list');
// Set language negotiation to "Path prefix with fallback".
variable_set('language_negotiation', LANGUAGE_NEGOTIATION_PATH);
include_once DRUPAL_ROOT . '/includes/locale.inc';
variable_set('language_negotiation_' . LANGUAGE_TYPE_CONTENT, locale_language_negotiation_info());
variable_set('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX);
// Force inclusion of language.inc.
drupal_language_initialize();
......
......@@ -170,17 +170,12 @@ function translation_form_alter(&$form, &$form_state, $form_id) {
*/
function translation_node_view($node, $build_mode) {
if (isset($node->tnid) && $translations = translation_node_get_translations($node->tnid)) {
$path = 'node/' . $node->nid;
$links = language_negotiation_get_switch_links(LANGUAGE_TYPE_CONTENT, $path);
if (is_object($links)) {
$links = $links->links;
// Do not show link to the same node.
unset($translations[$node->language]);
$languages = language_list();
foreach ($languages as $langcode => $language) {
if (isset($translations[$langcode])) {
$links["node_translation_$langcode"] = array(
'title' => $language->native,
'href' => 'node/' . $translations[$langcode]->nid,
'language' => $language,
'attributes' => array('title' => $translations[$langcode]->title, 'class' => array('translation-link')),
);
unset($links[$node->language]);
$node->content['links']['translation'] = array(
'#theme' => 'links',
'#links' => $links,
......@@ -188,7 +183,6 @@ function translation_node_view($node, $build_mode) {
);
}
}
}
}
/**
......@@ -407,12 +401,12 @@ function translation_path_get_translations($path) {
}
/**
* Implement hook_translation_link_alter().
* Implement hook_language_switch_link_alter().
*
* Replaces links with pointers to translated versions of the content.
*/
function translation_translation_link_alter(array &$links, $path) {
if ($paths = translation_path_get_translations($path)) {
function translation_language_switch_links_alter(array &$links, $type, $path) {
if ($type == LANGUAGE_TYPE_CONTENT && $paths = translation_path_get_translations($path)) {
foreach ($links as $langcode => $link) {
if (isset($paths[$langcode])) {
// Translation in a different node.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment