all contrib module security updates done

This commit is contained in:
2020-09-24 22:47:32 +02:00
parent 7d87153100
commit 0fbb9c4610
1612 changed files with 116663 additions and 32269 deletions
@@ -20,7 +20,7 @@ For support, please create a support request for this module's project:
Support questions by email to the module maintainer will be simply ignored. Use the issue tracker.
Now if you want professional (paid) support the module maintainer may be available occassionally.
Now if you want professional (paid) support the module maintainer may be available occasionally.
Drop me a message to check availability and hourly rates, http://reyero.net/en/contact
====================================================================
@@ -4,15 +4,12 @@ dependencies[] = locale
dependencies[] = variable
package = Multilingual - Internationalization
core = 7.x
files[] = i18n.install
files[] = i18n_object.inc
files[] = i18n.test
configure = admin/config/regional/i18n
; Information added by drupal.org packaging script on 2013-01-13
version = "7.x-1.8"
; Information added by Drupal.org packaging script on 2020-06-17
version = "7.x-1.27"
core = "7.x"
project = "i18n"
datestamp = "1358075001"
datestamp = "1592373390"
@@ -22,12 +22,21 @@ function i18n_uninstall() {
}
/**
* Add fields to schema if they don't exist
* Add fields to the schema if they don't exist.
*
* @param string $table
* The name of the database table.
* @param array $fields
* The list of database fields to create.
* @param boolean $force_rebuild_schema
* Whether to force drupal_get_schema() to rebuild the schema. This may be
* necessary when additional implementations of hook_schema_alter() have
* become available since the schema was originally built.
*/
function i18n_install_create_fields($table, $fields) {
function i18n_install_create_fields($table, $fields, $force_rebuild_schema = FALSE) {
static $schema;
// Do not force schema refresh more than once per request.
$schema = drupal_get_schema($table, !isset($schema));
$rebuild_schema = !isset($schema) || $force_rebuild_schema;
$schema = drupal_get_schema($table, $rebuild_schema);
foreach ($fields as $field) {
if (!empty($schema['fields'][$field])) {
if (!db_field_exists($table, $field)) {
@@ -198,7 +198,8 @@ function i18n_language_field_extra() {
*/
function i18n_language_list($field = 'name', $mode = NULL) {
$mode = isset($mode) ? $mode : variable_get('i18n_language_list', I18N_LANGUAGE_ENABLED);
return locale_language_list($field, I18N_LANGUAGE_EXTENDED & $mode);
$all = I18N_LANGUAGE_EXTENDED & $mode;
return locale_language_list($field, $all);
}
/**
@@ -403,9 +404,17 @@ function i18n_get_object($type, $key, $object = NULL) {
$index = is_array($key) ? implode(':', $key) : $key;
if (!isset($cache[$type][$index])) {
$class = i18n_object_info($type, 'class', 'i18n_object_wrapper');
$cache[$type][$index] = new $class($type, $key, $object);
$object_wrapper = new $class($type, $key, $object);
// Do not cache object with empty index.
if (!empty($index)) {
$cache[$type][$index] = $object_wrapper;
}
}
return $cache[$type][$index];
else {
$object_wrapper = $cache[$type][$index];
}
return $object_wrapper;
}
/**
@@ -498,9 +507,12 @@ function i18n_object_translate_access($type, $object) {
*
* @param $path
* Path to get translations for or '<front>' for front page.
* @param $check_access
* Whether to check access to paths, defaults to TRUE
*/
function i18n_get_path_translations($path) {
function i18n_get_path_translations($path, $check_access = TRUE) {
$translations = &drupal_static(__FUNCTION__);
if (!isset($translations[$path])) {
$translations[$path] = array();
foreach (module_implements('i18n_translate_path') as $module) {
@@ -510,10 +522,31 @@ function i18n_get_path_translations($path) {
$translations[$path] += $translated;
}
}
// Add access information if not there.
foreach ($translations[$path] as $langcode => &$info) {
if (!isset($info['access'])) {
$item = menu_get_item($info['href']);
// If no menu item, it may be an external URL, we allow access.
$info['access'] = $item ? !empty($item['access']) : TRUE;
}
}
// Chance for altering the results.
drupal_alter('i18n_translate_path', $translations[$path], $path);
}
return $translations[$path];
if ($check_access) {
return array_filter($translations[$path], '_i18n_get_path_translations_access');
}
else {
return $translations[$path];
}
}
/**
* Helper function to check access to path translation.
*/
function _i18n_get_path_translations_access($path) {
return $path['access'];
}
/**
@@ -11,7 +11,7 @@ class Drupali18nTestCase extends DrupalWebTestCase {
function setUpLanguages($admin_permissions = array()) {
// Setup admin user.
$this->admin_user = $this->drupalCreateUser(array_merge(array('bypass node access', 'administer nodes', 'administer languages', 'administer content types', 'administer blocks', 'access administration pages', 'translate interface'), $admin_permissions));
$this->admin_user = $this->drupalCreateUser(array_merge(array('bypass node access', 'administer nodes', 'administer languages', 'administer content types', 'administer fields', 'administer blocks', 'access administration pages', 'translate interface'), $admin_permissions));
$this->drupalLogin($this->admin_user);
@@ -144,12 +144,14 @@ class Drupali18nTestCase extends DrupalWebTestCase {
}
/**
* Create a "Basic page" in the specified language.
*
* Create a node of the specified type in the specified language.
* @param $type
* The node type.
* @param $title
* Title of basic page in specified language.
* Title of node in specified language.
* @param $body
* Body of basic page in specified language.
* Body of node in specified language.
* @param $langcode
* Language code.
*/
@@ -159,7 +161,9 @@ class Drupali18nTestCase extends DrupalWebTestCase {
$edit["body[$lang][0][value]"] = $body;
$edit['language'] = $langcode;
$this->drupalPost('node/add/' . $type, $edit, t('Save'));
$this->assertRaw(t('Basic @type %title has been created.', array('@type' => $type, '%title' => $title)), t('Basic page created.'));
$info = node_type_load($type);
$message = t('@name %title has been created.', array('@name' => $info->name, '%title' => $title));
$this->assertRaw($message);
// Check to make sure the node was created.
$node = $this->drupalGetNodeByTitle($title);
@@ -169,15 +173,14 @@ class Drupali18nTestCase extends DrupalWebTestCase {
}
/**
* Create a translation for the specified basic page in the specified
* language.
* Create a translation for the specified node in the specified language.
*
* @param $node
* The basic page to create translation for.
* @param $title
* Title of basic page in specified language.
* Title of node in specified language.
* @param $body
* Body of basic page in specified language.
* Body of node in specified language.
* @param $language
* Language code.
*/
@@ -196,8 +199,9 @@ class Drupali18nTestCase extends DrupalWebTestCase {
$edit["title"] = $title;
$edit[$body_key] = $body;
$this->drupalPost(NULL, $edit, t('Save'));
$this->assertRaw(t('Basic page %title has been created.', array('%title' => $title)), t('Translation created.'));
$info = node_type_load($node->type);
$message = t('@name %title has been created.', array('@name' => $info->name, '%title' => $title));
$this->assertRaw($message);
// Check to make sure that translation was successful.
$translation = $this->drupalGetNodeByTitle($title);
$this->assertTrue($translation, t('Node found in database.'));
@@ -358,6 +362,7 @@ class Drupali18nTestCase extends DrupalWebTestCase {
function resetCaches() {
drupal_static_reset('locale_url_outbound_alter');
drupal_static_reset('language_list');
drupal_language_initialize();
}
/**
@@ -0,0 +1,92 @@
CONTENTS OF THIS FILE
---------------------
* Introduction
* Requirements
* Recommended modules
* Installation
* Configuration
* Troubleshooting
* Maintainers
INTRODUCTION
------------
The Block languages module, part of the Internationalization
(https://www.drupal.org/project/i18n) package, allows the user to configure
for which languages each block is visible.
* For a full description of the module,
visit https://www.drupal.org/node/1279698.
* To submit bug reports and feature suggestions, or to track changes visit
https://www.drupal.org/project/issues/i18n.
REQUIREMENTS
------------
This module requires the following module:
* Internationalization - https://www.drupal.org/project/i18n
RECOMMENDED MODULES
-------------------
* Internationalization Views - https://www.drupal.org/project/i18nviews
* Language Icons - https://www.drupal.org/project/languageicons
* Translation Overview - https://www.drupal.org/project/translation_overview
* Localization Client - https://www.drupal.org/project/l10n_client
* Internationalization contributions -
https://www.drupal.org/project/i18n_contrib
INSTALLATION
------------
This is a submodule of the Internationalization module. Install the
Internationalization module as you would normally install a contributed Drupal
module. Visit https://www.drupal.org/node/895232 for further information.
CONFIGURATION
-------------
The settings for visibility per language are provided under Visibility
Settings via the Languages tab when configuring a block.
The Languages tab also provides a setting for whether the block is translatable.
For custom blocks, the block title and the block content will be translatable.
For blocks defined by modules, only the block title will be translatable. If
"Make this block translatable" is selected, a Translate tab will appear for that
block. This tab provides a UI for adding translations of the block in each
available language.
TROUBLESHOOTING
---------------
Conflicts with Context
The Block languages module conflicts with the Context module, which alters how
blocks are rendered. This issue can be tracked in the Internationalization
issue queue: http://drupal.org/node/1343044
String Errors
The user must allow your used string format to be translated on
admin/config/regional/i18n/strings or you are going to have an error message
like "The string blocks:block:1:body for textgroup blocks is not allowed for
translation because of its text format."
MAINTAINERS
-----------
* Jose Reyero - https://www.drupal.org/u/jose-reyero
* Florian Weber (webflo) - https://www.drupal.org/u/webflo
* Peter Philipp - https://www.drupal.org/u/das-peter
* Joseph Olstad - https://www.drupal.org/u/joseph.olstad
* Nathaniel Catchpole - https://www.drupal.org/u/catch
@@ -8,9 +8,8 @@ files[] = i18n_block.inc
files[] = i18n_block.test
; Information added by drupal.org packaging script on 2013-01-13
version = "7.x-1.8"
; Information added by Drupal.org packaging script on 2020-06-17
version = "7.x-1.27"
core = "7.x"
project = "i18n"
datestamp = "1358075001"
datestamp = "1592373390"
@@ -37,6 +37,17 @@ function i18n_block_menu() {
return $items;
}
/**
* Implements hook_permission().
*/
function i18n_block_permission() {
return array(
'translate blocks' => array(
'title' => t('Translate Blocks'),
),
);
}
/**
* Implement hook_menu_alter().
*
@@ -59,7 +70,7 @@ function i18n_block_menu_alter(&$items) {
*/
function i18n_block_translate_tab_access($module, $delta) {
$block = block_load($module, $delta);
return user_access('translate interface') && isset($block) && ($block->i18n_mode == I18N_MODE_LOCALIZE);
return (user_access('translate interface') || user_access('translate blocks')) && $block && isset($block->i18n_mode) && ($block->i18n_mode == I18N_MODE_LOCALIZE);
}
/**
@@ -226,7 +237,7 @@ function i18n_block_form_block_admin_configure_alter(&$form, &$form_state, $form
'#options' => i18n_language_list(),
'#description' => t('If no language is selected, block will show regardless of language.'),
);
if (user_access('translate interface')) {
if (user_access('translate interface') || user_access('translate blocks')) {
$form['actions']['translate'] = array(
'#type' => 'submit',
'#name' => 'save_translate',
@@ -72,15 +72,16 @@ class i18nBlocksTestCase extends Drupali18nTestCase {
$this->clickLink(t('translate'));
// Title is a textarea, body is a text_format.
$this->assertFieldByName('strings[blocks:block:' . $box2['delta'] . ':title]', $translations['title']['es']);
$this->assertFieldByName('strings[blocks:block:' . $box2['delta'] . ':body]', $translations['body']['es']);
$this->assertFieldByName('strings[blocks:block:' . $box2['delta'] . ':body][value]', $translations['body']['es']);
// Update the translation.
$translations['title']['es'] = $this->randomName(10);
$translations['body']['es'] = $this->randomName(20);
$edit = array(
'strings[blocks:block:' . $box2['delta'] . ':title]' => $translations['title']['es'],
'strings[blocks:block:' . $box2['delta'] . ':body]' => $translations['body']['es'],
'strings[blocks:block:' . $box2['delta'] . ':body][value]' => $translations['body']['es'],
);
$this->drupalPost(NULL, $edit, t('Save translation'));
$this->i18nAssertTranslations($translations['title'], '', 'Updated block title translation displayed.');
@@ -0,0 +1,76 @@
CONTENTS OF THIS FILE
---------------------
* Introduction
* Requirements
* Recommended modules
* Installation
* Configuration
* Maintainers
INTRODUCTION
------------
The Contact translation module, part of the Internationalization
(https://www.drupal.org/project/i18n) package, helps with the multilingual
configuration of the site contact forms. This module makes contact categories
and replies available for translation.
* For a full description of the module, visit
https://www.drupal.org/node/1396984.
* To submit bug reports and feature suggestions, or to track changes,
visit https://www.drupal.org/project/issues/i18n.
REQUIREMENTS
------------
This module requires the following module:
* Internationalization - https://www.drupal.org/project/i18n
RECOMMENDED MODULES
-------------------
* Internationalization Views - https://www.drupal.org/project/i18nviews
* Language Icons - https://www.drupal.org/project/languageicons
* Translation Overview - https://www.drupal.org/project/translation_overview
* Localization Client - https://www.drupal.org/project/l10n_client
* Internationalization contributions -
https://www.drupal.org/project/i18n_contrib
INSTALLATION
------------
This is a submodule of the Internationalization module. Install the
Internationalization module as you would normally install a contributed Drupal
module. Visit https://www.drupal.org/node/895232 for further information.
CONFIGURATION
-------------
1. Enable the Contact translation module included with the Internationalization
module.
2. Go to Administration > Structure > Contact form.
3. Select edit for the form to configure.
4. Select the Translate tab.
5. Select the translate link for a language.
6. Translate the Category and Auto-reply text.
7. Select Save translation.
8. Repeat steps 5 to 7 for each language.
9. Repeat steps 2 to 8 for all forms.
MAINTAINERS
-----------
* Jose Reyero - https://www.drupal.org/u/jose-reyero
* Florian Weber (webflo) - https://www.drupal.org/u/webflo
* Peter Philipp - https://www.drupal.org/u/das-peter
* Joseph Olstad - https://www.drupal.org/u/joseph.olstad
* Nathaniel Catchpole - https://www.drupal.org/u/catch
@@ -5,9 +5,8 @@ dependencies[] = i18n_string
package = Multilingual - Internationalization
core = 7.x
; Information added by drupal.org packaging script on 2013-01-13
version = "7.x-1.8"
; Information added by Drupal.org packaging script on 2020-06-17
version = "7.x-1.27"
core = "7.x"
project = "i18n"
datestamp = "1358075001"
datestamp = "1592373390"
@@ -0,0 +1,78 @@
CONTENTS OF THIS FILE
---------------------
* Introduction
* Requirements
* Recommended modules
* Installation
* Configuration
* Maintainers
INTRODUCTION
------------
The Field translation module, part of the Internationalization
(https://www.drupal.org/project/i18n) package, allows for translation of text
associated with a field's settings including the label, help text, default
value, and list options.
* For a full description of the module, visit this page
https://www.drupal.org/node/1279346.
* To submit bug reports and feature suggestions, or to track changes visit
https://www.drupal.org/project/issues/i18n.
REQUIREMENTS
------------
This module requires the following module:
* Internationalization - https://www.drupal.org/project/i18n
RECOMMENDED MODULES
-------------------
* Internationalization Views - https://www.drupal.org/project/i18nviews
* Language Icons - https://www.drupal.org/project/languageicons
* Translation Overview - https://www.drupal.org/project/translation_overview
* Localization Client - https://www.drupal.org/project/l10n_client
* Internationalization contributions -
https://www.drupal.org/project/i18n_contrib
INSTALLATION
------------
This is a submodule of the Internationalization module. Install the
Internationalization module as you would normally install a contributed Drupal
module. Visit https://www.drupal.org/node/895232 for further information.
CONFIGURATION
-------------
1. Enable the Field translation module included with Internationalization.
2. Go to Administration > Configuration > Regional and language > Translate
interface.
3. Select the Filter Translatable Strings field set and limit search to fields.
4. Edit the desired field and Save.
For the translation to be displayed, the user needs to use some of the Field
Formatters provided by this module whose name usually ends up in 'translated'.
For most core fields it is Default translated.
Note: The Field Translation module does not provide content translation for
fields. This functionality is provided by the Entity Translation (ET) module.
MAINTAINERS
-----------
* Jose Reyero - https://www.drupal.org/u/jose-reyero
* Florian Weber (webflo) - https://www.drupal.org/u/webflo
* Peter Philipp - https://www.drupal.org/u/das-peter
* Joseph Olstad - https://www.drupal.org/u/joseph.olstad
* Nathaniel Catchpole - https://www.drupal.org/u/catch
@@ -0,0 +1,53 @@
<?php
/**
* @file
* API documentation file for Field translation module.
*
* This module takes care of translating common field elements like title and
* description for all fields, plus some field specific values (default, options)
* for field types defined by Drupal core.
*
* Before implementing any of these hooks, consider whether you would be better
* off implementing Drupal core's hook_field_widget_form_alter().
*
* @see i18n_field_field_widget_form_alter()
*/
/**
* Provide information about callbacks for translating specific field types.
*
* This information can be retrieved using i18n_field_type_info().
* @return
* Array of values indexed by field type. Valid keys are:
* - 'translate_default', Callback for translating the default value for this field type.
* - 'translate_options', Callback for translating options for this field type.
*
* @see i18n_field_type_info()
* @see i18n_field_i18n_field_info()
*
* For examples of both callback types:
*
* @see i18n_field_translate_allowed_values()
* @see i18n_field_translate_default()
*
*/
function hook_i18n_field_info() {
$info['text'] = $info['text_long'] = $info['text_with_summary'] = array(
'translate_default' => 'i18n_field_translate_default',
);
$info['list_text'] = $info['list_boolean'] = $info['list_integer'] = array(
'translate_options' => 'i18n_field_translate_allowed_values',
);
return $info;
}
/**
* Alter information provided by hook_i18n_field_info().
*
* @see i18n_field_type_info()
*/
function hook_i18n_field_info_alter(&$info) {
// Unset the default callback for text fields.
unset($info['text']['translate_default']);
}
@@ -72,6 +72,7 @@ function i18n_field_i18n_string_info() {
'description' => t('Configurable fields descriptions, defaults, options, etc.'),
'format' => FALSE, // This group doesn't have formatted strings
'list' => TRUE, // This group can list all strings
'class' => variable_get('i18n_string_textgroup_class_field', 'i18n_string_textgroup_cached'),
);
return $groups;
}
@@ -6,9 +6,8 @@ package = Multilingual - Internationalization
core = 7.x
files[] = i18n_field.inc
files[] = i18n_field.test
; Information added by drupal.org packaging script on 2013-01-13
version = "7.x-1.8"
; Information added by Drupal.org packaging script on 2020-06-17
version = "7.x-1.27"
core = "7.x"
project = "i18n"
datestamp = "1358075001"
datestamp = "1592373390"
@@ -163,7 +163,6 @@ function i18n_field_field_formatter_view($entity_type, $entity, $field, $instanc
return $element;
}
/**
* Implements hook_field_widget_form_alter().
*
@@ -176,8 +175,8 @@ function i18n_field_field_formatter_view($entity_type, $entity, $field, $instanc
function i18n_field_field_widget_form_alter(&$element, &$form_state, $context) {
global $language;
// Skip the node type edit fields by checking for existing entity
if (empty($element['#entity'])) {
// Don't translate if the widget is being shown on the field edit form.
if ($form_state['build_info']['form_id'] == 'field_ui_field_edit_form') {
return;
}
@@ -189,8 +188,45 @@ function i18n_field_field_widget_form_alter(&$element, &$form_state, $context) {
$instance = $context['instance'];
$langcode = $context['langcode'];
// Get the element to alter. Account for inconsistencies in how the element
// is built for different field types.
if (isset($element[0]) && count($element) == 1) {
// Single-value file fields and image fields.
$alter_element = &$element[0];
}
elseif ($field['type'] == 'url' && $field['module'] == 'url' && $field['cardinality'] == 1) {
$alter_element = &$element;
}
elseif (isset($element['value'])) {
// Number fields. Single-value text fields.
$alter_element = &$element['value'];
}
elseif ($field['type'] == 'entityreference' && isset($element['target_id'])) {
// Entityreference fields using the entityreference_autocomplete widget.
$alter_element = &$element['target_id'];
}
elseif ($field['type'] == 'node_reference' && isset($element['nid'])) {
// The node_reference fields using the entityreference_autocomplete widget.
$alter_element = &$element['nid'];
}
else {
// All other fields.
$alter_element = &$element;
}
// If a subelement has the same title as the parent, translate it instead.
// Allows fields such as email and commerce_price to be translated.
foreach (element_get_visible_children($element) as $key) {
$single_value = ($field['cardinality'] == 1);
$has_title = (isset($element['#title']) && isset($element[$key]['#title']));
if ($single_value && $has_title && $element[$key]['#title'] == $element['#title']) {
$alter_element = &$element[$key];
break;
}
}
// The field language may affect some variables (default) but not others (description will be in current page language)
$i18n_langcode = empty($element['#language']) || $element['#language'] == LANGUAGE_NONE ? $language->language : $element['#language'];
$i18n_langcode = empty($alter_element['#language']) || $alter_element['#language'] == LANGUAGE_NONE ? $language->language : $alter_element['#language'];
// Translate instance to current page language and set to form_state
// so it will be used for validation messages later.
@@ -200,37 +236,60 @@ function i18n_field_field_widget_form_alter(&$element, &$form_state, $context) {
}
// Translate field title if set and it is the default one.
// When cardinality is 1, $element['value'] is used instead.
if (!empty($instance_current['label']) && $instance_current['label'] != $instance['label']) {
if (!empty($element['#title']) && $element['#title'] == $instance['label']) {
$element['#title'] = $instance_current['label'];
}
if (isset($element['value']) && !empty($element['value']['#title']) && $element['value']['#title'] == $instance['label']) {
$element['value']['#title'] = $instance_current['label'];
if (!empty($alter_element['#title']) && $alter_element['#title'] == check_plain($instance['label'])) {
$alter_element['#title'] = check_plain($instance_current['label']);
}
}
// Translate field description if set and it is the default one.
// When cardinality is 1, $element['value'] is used instead.
if (!empty($instance_current['description']) && $instance_current['description'] != $instance['description']) {
if (!empty($element['#description']) && $element['#description'] == $instance['description']) {
$element['#description'] = $instance_current['description'];
}
if (isset($element['value']) && !empty($element['value']['#description']) && $element['value']['#description'] == $instance['description']) {
$element['value']['#description'] = $instance_current['description'];
if (!empty($alter_element['#description'])) {
// Allow single-value file fields and image fields to have their
// descriptions translated. file_field_widget_form() passes the
// description through theme('file_upload_help'), so i18n_field
// must do the same.
$filefield = in_array($field['type'], array('file', 'image'));
$single_value = ($field['cardinality'] == 1);
$no_default = empty($alter_element['#default_value']['fid']);
if ($filefield && $single_value && $no_default) {
$help_variables = array(
'description' => field_filter_xss($instance['description']),
'upload_validators' => isset($alter_element['#upload_validators']) ? $alter_element['#upload_validators'] : array(),
);
$original_description = theme('file_upload_help', $help_variables);
if ($alter_element['#description'] == $original_description) {
$help_variables = array(
'description' => field_filter_xss($instance_current['description']),
'upload_validators' => isset($alter_element['#upload_validators']) ? $alter_element['#upload_validators'] : array(),
);
$alter_element['#description'] = theme('file_upload_help', $help_variables);
}
}
elseif ($alter_element['#description'] == field_filter_xss($instance['description'])) {
$alter_element['#description'] = field_filter_xss($instance_current['description']);
}
}
}
// Translate list options
if (!empty($element['#options']) && ($translate = i18n_field_type_info($field['type'], 'translate_options')) && !empty($field['settings']['allowed_values'])) {
$element['#options'] = $translate($field, $i18n_langcode);
if (isset($element['#properties']) && !empty($element['#properties']['empty_option'])) {
$label = theme('options_none', array('instance' => $instance, 'option' => $element['#properties']['empty_option']));
$element['#options'] = array('_none' => $label) + $element['#options'];
// For some elements, change title to new translated option
if (!empty($element['#title']) && $field['type'] == 'list_boolean' && !empty($element['#on_value'])) {
$on_value = $element['#on_value'];
$element['#title'] = $element['#options'][$on_value];
// Translate list options.
$has_options = (!empty($alter_element['#options']) || $field['type'] == 'list_boolean');
$has_allowed_values = !empty($field['settings']['allowed_values']);
$translate = i18n_field_type_info($field['type'], 'translate_options');
if ($has_options && $has_allowed_values && $translate) {
$alter_element['#options'] = $translate($field, $i18n_langcode);
if (isset($alter_element['#properties']) && !empty($alter_element['#properties']['empty_option'])) {
$label = theme('options_none', array('instance' => $instance, 'option' => $alter_element['#properties']['empty_option']));
$alter_element['#options'] = array('_none' => $label) + $alter_element['#options'];
}
// Translate list_boolean fields using the checkboxes widget.
if (!empty($alter_element['#title']) && $field['type'] == 'list_boolean' && !empty($alter_element['#on_value'])) {
$on_value = $alter_element['#on_value'];
$alter_element['#options'];
$alter_element['#title'] = $alter_element['#options'][$on_value];
// For using label instead of "On value".
if ($instance['widget']['settings']['display_label']) {
$alter_element['#title'] = $instance_current['label'];
}
}
}
@@ -242,11 +301,15 @@ function i18n_field_field_widget_form_alter(&$element, &$form_state, $context) {
$delta = $context['delta'];
$items = $context['items'];
// Translate default value if exists and the current value is the default
if (isset($element['value']['#default_value']) && ($translate = i18n_field_type_info($field['type'], 'translate_default')) &&
!empty($instance['default_value'][$delta]['value']) && !empty($items[$delta]['value']) &&
$instance['default_value'][$delta]['value'] === $items[$delta]['value']) {
$element['value']['#default_value'] = $translate($instance, $items[$delta]['value'], $i18n_langcode);
// Translate default value.
$has_default_value = (isset($alter_element['#default_value']) && !empty($instance['default_value'][$delta]['value']));
$storage_has_value = !empty($items[$delta]['value']);
$translate = i18n_field_type_info($field['type'], 'translate_default');
if ($has_default_value && $storage_has_value && $translate) {
// Compare the default value with the value currently in storage.
if ($instance['default_value'][$delta]['value'] === $items[$delta]['value']) {
$alter_element['#default_value'] = $translate($instance, $items[$delta]['value'], $i18n_langcode);
}
}
}
@@ -344,10 +407,12 @@ function i18n_field_translate_allowed_values($field, $langcode = NULL) {
}
/**
* Translate field default
* Translate field default.
*/
function i18n_field_translate_default($instance, $value, $langcode = NULL) {
return i18n_string_translate(array('field', $instance['field_name'], $instance['bundle'], 'default_value'), $value, array('langcode' => $langcode));
// The default value does not need sanitizing in a text_textfield widget.
$sanitize = !($instance['widget']['type'] == 'text_textfield' && $instance['widget']['module'] == 'text');
return i18n_string_translate(array('field', $instance['field_name'], $instance['bundle'], 'default_value'), $value, array('langcode' => $langcode, 'sanitize' => $sanitize));
}
/**
@@ -360,7 +425,17 @@ function i18n_field_translate_property($instance, $property, $langcode = NULL) {
}
/**
* Get i18n information for fields
* Get i18n information for translating fields.
*
* @param $type
* Optional field type.
* @param $property
* Optional property to get from field type.
*
* @return
* - The property for the field if $type and $property set.
* - Array of properties for the field type if only $type is set.
* - Array of translation information for all field types.
*/
function i18n_field_type_info($type = NULL, $property = NULL) {
$info = &drupal_static(__FUNCTION__);
@@ -378,3 +453,120 @@ function i18n_field_type_info($type = NULL, $property = NULL) {
return $info;
}
}
/**
* Implements hook_field_info_alter().
*/
function i18n_field_field_info_alter(&$field_info) {
foreach(array_keys($field_info) as $type) {
$field_info[$type]['property_callbacks'][] = 'i18n_field_entity_property_callback';
}
}
/**
* Prime the cache to avoid single db queries for entity fields / properties.
*
* This is mainly uses when large operations are occuring like a flush of the
* entity_property_infos().
*/
function i18n_field_prime_caches() {
global $language;
static $cache_primed;
// Fill the cache. This should avoid single db queries when filling the
// properties.
if (empty($cache_primed)) {
$cache_primed = TRUE;
$text_group = i18n_string_textgroup('field');
// Load all strings at once to avoid callbacks for each individual string.
$text_group->load_strings();
$text_group->multiple_translation_search(array('type' => '*', 'objectid' => '*', 'property' => '*'), $language->language);
}
}
/**
* Callback to translate entity property info for a fields.
*
* @see entity_metadata_field_entity_property_info()
* @see entity_metadata_field_default_property_callback()
* @see i18n_field_i18n_object_info_alter()
* @see hook_module_implements_alter()
*/
function i18n_field_entity_property_callback(&$info, $entity_type, $field, $instance, $field_type) {
global $language;
// This could create a endless recursion if it's called during rebuilding the
// cache for i18n_object_info(). So if the cache of i18n_object_info isn't
// available yet we assume the worst case, leave the info alone but trigger a
// rebuild of the property when hook_i18n_object_info_alter is invoked. At
// that point the info is available and we can rely on it.
if (!$info = &drupal_static('i18n_object_info')) {
$i18n_field_entity_property_callback_fallback = &drupal_static(__FUNCTION__);
$i18n_field_entity_property_callback_fallback = TRUE;
return;
}
i18n_field_prime_caches();
$name = $field['field_name'];
$property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$name];
$property['label'] = i18n_field_translate_property($instance, 'label', $language->language);
}
/**
* Implements hook_i18n_object_info_alter().
*/
function i18n_field_i18n_object_info_alter(&$info) {
if (drupal_static('i18n_field_entity_property_callback')) {
if ($info = drupal_static('i18n_object_info')) {
// Clean static and permanent cache of the data and then re-run the property
// building.
// Use a lock to avoid stampeding.
$lock_name = 'i18n_field_entity_property_callback_fallback:' . $GLOBALS['language']->language;
// See if another request is already doing this. If so we bail out here as
// we won't help with anything at the moment.
if (!lock_may_be_available($lock_name)) {
return;
}
if (lock_acquire($lock_name)) {
i18n_field_prime_caches();
// Inject translated properties.
$entity_property_info = entity_get_property_info();
foreach ($entity_property_info as $entity_type => $properties) {
if (isset($properties['bundles'])) {
foreach ($properties['bundles'] as $bundle => $bundle_properties) {
if ($bundle_properties['properties']) {
foreach ($bundle_properties['properties'] as $bundle_property => $bundle_property_info) {
if ($instance = field_info_instance($entity_type, $bundle_property, $bundle)) {
$property = &$entity_property_info[$entity_type]['bundles'][$instance['bundle']]['properties'][$bundle_property];
$property['label'] = i18n_field_translate_property($instance, 'label', $GLOBALS['language']->language);
}
}
}
}
}
}
// Inject into static cache.
$entity_get_property_info = &drupal_static('entity_get_property_info', array());
$entity_get_property_info = $entity_property_info;
// Write permanent cache.
cache_set('entity_property_info:' . $GLOBALS['language']->language, $entity_property_info);
lock_release($lock_name);
}
}
else {
watchdog('i18n_field', 'Unable to run fall-back handling for entity property translation due missing "i18n_object_info" cache', array(), WATCHDOG_WARNING);
}
}
}
/**
* Implements hook_module_implements_alter().
*/
function i18n_field_module_implements_alter(&$implementations, $hook) {
if ($hook == 'i18n_object_info_alter') {
// Move our hook implementation to the bottom.
$group = $implementations['i18n_field'];
unset($implementations['i18n_field']);
$implementations['i18n_field'] = $group;
}
}
@@ -0,0 +1,86 @@
CONTENTS OF THIS FILE
---------------------
* Introduction
* Requirements
* Recommended modules
* Installation
* Configuration
* Maintainers
INTRODUCTION
------------
The Multilingual forum module, part of the Internationalization
(https://www.drupal.org/project/i18n) package, helps with the multilingual
configuration of the sites forums.
* For a full description of the module visit
https://www.drupal.org/node/1396988.
* To submit bug reports and feature suggestions, or to track changes visit
https://www.drupal.org/project/issues/i18n.
REQUIREMENTS
------------
This module requires the following module:
* Internationalization - https://www.drupal.org/project/i18n
RECOMMENDED MODULES
-------------------
* Internationalization Views - https://www.drupal.org/project/i18nviews
* Language Icons - https://www.drupal.org/project/languageicons
* Translation Overview - https://www.drupal.org/project/translation_overview
* Localization Client - https://www.drupal.org/project/l10n_client
* Internationalization contributions -
https://www.drupal.org/project/i18n_contrib
INSTALLATION
------------
This is a submodule of the Internationalization module. Install the
Internationalization module as you would normally install a contributed Drupal
module. Visit https://www.drupal.org/node/895232 for further information.
CONFIGURATION
-------------
To configure forum vocabulary
1. Enable the Multilingual forum module included with Internationalization.
2. Go to Administration > Structure > Taxonomy.
3. Select the "edit vocabulary" for the Forums vocabulary.
4. Choose the translation mode (Localize or Translate).
5. Select the "Save and translate" button.
6. Select the "translate" link for a language.
7. Translate the "Name" and "Description" for the forum.
8. Select the "Save translation" button.
9. Repeat steps 6 to 8 for each language.
To configure forum terms
1. Go to Administration > Structure > Forums.
2. Select the "edit" link for a forum or container.
3. Select the "Translate" tab.
4. Select the "translate" link for a language.
5. Translate the "Name" and "Description" for the term.
6. Select the "Save translation" button.
7. Repeat steps 4 to 6 for each language.
8. Repeat all steps for all terms.
MAINTAINERS
-----------
* Jose Reyero - https://www.drupal.org/u/jose-reyero
* Florian Weber (webflo) - https://www.drupal.org/u/webflo
* Peter Philipp - https://www.drupal.org/u/das-peter
* Joseph Olstad - https://www.drupal.org/u/joseph.olstad
* Nathaniel Catchpole - https://www.drupal.org/u/catch
@@ -7,9 +7,8 @@ package = Multilingual - Internationalization
core = 7.x
files[] = i18n_forum.test
; Information added by drupal.org packaging script on 2013-01-13
version = "7.x-1.8"
; Information added by Drupal.org packaging script on 2020-06-17
version = "7.x-1.27"
core = "7.x"
project = "i18n"
datestamp = "1358075001"
datestamp = "1592373390"
@@ -35,7 +35,7 @@ function i18n_forum_menu_local_tasks_alter(&$data, $router_item, $root_path) {
if ($vid && ($vocabulary = taxonomy_vocabulary_load($vid)) && ($field = field_info_field('taxonomy_' . $vocabulary->machine_name))) {
foreach ($field['bundles']['node'] as $type) {
if (isset($data['actions']['output'][$type])) {
$data['actions']['output'][$type]['#link']['title'] = t('Add new @node_type', array('@node_type' => i18n_node_type_name($type, node_type_get_name($type))));
$data['actions']['output'][$type]['#link']['title'] = t('Add new @node_type', array('@node_type' => i18n_node_type_name($type)));
}
}
}
@@ -0,0 +1,111 @@
CONTENTS OF THIS FILE
---------------------
* Introduction
* Requirements
* Recommended modules
* Installation
* Configuration
* Troubleshooting
* Maintainers
INTRODUCTION
------------
The Menu translation module, part of the Internationalization
(https://www.drupal.org/project/i18n) package, allows users to select a
translation mode for each menu.
* For a full description of the module, visit this page:
https://www.drupal.org/node/1113982.
* To submit bug reports and feature suggestions, or to track changes:
https://www.drupal.org/project/issues/i18n.
REQUIREMENTS
------------
This module requires the following module:
* Internationalization - https://www.drupal.org/project/i18n
RECOMMENDED MODULES
-------------------
* Internationalization Views - https://www.drupal.org/project/i18nviews
* Language Icons - https://www.drupal.org/project/languageicons
* Translation Overview - https://www.drupal.org/project/translation_overview
* Localization Client - https://www.drupal.org/project/l10n_client
* Internationalization contributions -
https://www.drupal.org/project/i18n_contrib
To link menu item menus to nodes, it is useful to have the following modules:
* Entity translation i18n menu module, a submodule of Entity translation -
https://www.drupal.org/project/entity_translation
* Menu translation node module - https://www.drupal.org/project/i18n_menu_node
INSTALLATION
------------
This is a submodule of the Internationalization module. Install the
Internationalization module as you would normally install a contributed Drupal
module. Visit https://www.drupal.org/node/895232 for further information.
CONFIGURATION
-------------
Language-specific menus
1. To create or edit a menu, navigate to Structure > Menus > (menu to edit) >
Edit.
2. In the Translation mode section, choose Fixed Language and a Language field
will appear.
3. Select a language, select Save, and add or update the menu items as needed.
The menu block will only appear when viewing content in the same language.
There are three modes available: Translate and Localize, Fixed Language, and No
Multilingual Options.
Translate and Localize:
The user can create one menu for all languages, and translate or localize each
menu item. There are two ways that menu items will be translated.
1. The user can set a language when creating a custom menu item so that the menu
item will only show up for that language. Menu items that link to nodes in a
particular language will be treated this way.
2. The user can localize other custom menu items without a language
(for example, menu items linking to Views pages). Use the Translate tab to
translate the menu item title and description. Translators can also use the
"Translate interface" pages to translate these menu items.
Fixed Language:
If the user chooses Fixed Language, they'll have to set up a separate menu in
each language. This could become tedious if have a lot of languages enabled on
the site, but is useful if the content or menu structure is different for each
language.
No Multilingual Options:
Only the menu will be translatable.
TROUBLESHOOTING
---------------
A menu item linked to a node will be displayed only when the node language
matches the page language. This is due to how the menu system works and the
"Language selection" feature in i18n. Therefore, to get translated menus items
that link to nodes, you first need translated content. For more information
visit https://www.drupal.org/docs/7/multilingual/translating-content.
MAINTAINERS
-----------
* Jose Reyero - https://www.drupal.org/u/jose-reyero
* Florian Weber (webflo) - https://www.drupal.org/u/webflo
* Peter Philipp - https://www.drupal.org/u/das-peter
* Joseph Olstad - https://www.drupal.org/u/joseph.olstad
* Nathaniel Catchpole - https://www.drupal.org/u/catch
@@ -10,7 +10,7 @@
* Produces a menu translation form.
*/
function i18n_menu_translation_form($form, $form_state, $translation_set = NULL, $item = NULL) {
$translation_set = $translation_set ? $translation_set : i18n_translation_set_create('menu_link');
$translation_set = $translation_set ? $translation_set : i18n_translation_set_build('menu_link');
$form['translation_set'] = array('#type' => 'value', '#value' => $translation_set);
$translations = $translation_set->get_translations();
// What to do with title? Let's make it a hidden field for now, some tests relay on it
@@ -55,7 +55,7 @@ function i18n_menu_translation_form($form, $form_state, $translation_set = NULL,
}
$form['actions'] = array('#type' => 'actions');
$form['actions']['update'] = array('#type' => 'submit', '#value' => t('Save'));
if ($translation_set) {
if (!empty($translation_set->tsid)) {
$form['actions']['delete'] = array('#type' => 'submit', '#value' => t('Delete'));
}
return $form;
@@ -244,12 +244,16 @@ function i18n_menu_translation_item_overview($item, $translation_set = NULL) {
$title = t('n/a');
$options[] = l(t('add translation'), 'admin/structure/menu/manage/' . $item['menu_name'] . '/add', array('query' => array('translation' => $item['mlid'], 'target' => $langcode) + drupal_get_destination()));
}
$rows[] = array($language_name, $title, implode(" | ", $options));
$rows[$langcode] = array(
'language' => $language_name,
'title' => $title,
'operations' => implode(" | ", $options)
);
}
drupal_set_title(t('Translations of menu item %title', array('%title' => $item['link_title'])), PASS_THROUGH);
$build['translation_item_overview'] = array(
$build['translation_overview'] = array(
'#theme' => 'table',
'#header' => $header,
'#rows' => $rows,
@@ -123,8 +123,8 @@ function i18n_menu_item_translation_page($type, $item) {
module_load_include('admin.inc', 'i18n_menu');
// If the item has a language code, we can only support translation sets.
$translation_set = !empty($item['i18n_tsid']) ? i18n_translation_set_load($item['i18n_tsid']) : NULL;
$build['overview'] = i18n_menu_translation_item_overview($item, $translation_set);
$build['translation_form'] = drupal_get_form('i18n_menu_translation_form', $translation_set, $item);
return $build;
$overview = i18n_menu_translation_item_overview($item, $translation_set);
$translation_form = drupal_get_form('i18n_menu_translation_form', $translation_set, $item);
return $overview + $translation_form;
}
@@ -69,6 +69,7 @@ class i18n_menu_link extends i18n_string_object_wrapper {
return I18N_MODE_NONE;
}
}
/**
* Access to object translation. This should check object properties and permissions
*/
@@ -10,9 +10,8 @@ core = 7.x
files[] = i18n_menu.inc
files[] = i18n_menu.test
; Information added by drupal.org packaging script on 2013-01-13
version = "7.x-1.8"
; Information added by Drupal.org packaging script on 2020-06-17
version = "7.x-1.27"
core = "7.x"
project = "i18n"
datestamp = "1358075001"
datestamp = "1592373390"
@@ -53,6 +53,24 @@ function i18n_menu_menu_alter(&$items) {
$items['admin/structure/menu/item/%menu_link'] = $items['admin/structure/menu/item/%menu_link/edit'];
$items['admin/structure/menu/item/%menu_link']['type'] = MENU_CALLBACK;
$items['admin/structure/menu/item/%menu_link/edit']['type'] = MENU_DEFAULT_LOCAL_TASK;
$items['admin/structure/menu/manage/%menu']['title callback'] = 'i18n_menu_menu_overview_title';
}
/**
* Preprocess theme_menu_admin_overview to translate menu name and description
*
* @param $variables
*/
function i18n_menu_preprocess_menu_admin_overview(&$variables) {
$variables['title'] = i18n_string(array('menu', 'menu', $variables['name'], 'title'), $variables['title'], array('sanitize' => FALSE));
$variables['description'] = i18n_string(array('menu', 'menu', $variables['name'], 'description'), $variables['description'], array('sanitize' => FALSE));
}
/**
* Title callback for the menu overview page and links.
*/
function i18n_menu_menu_overview_title($menu) {
return i18n_string(array('menu', 'menu', $menu['menu_name'], 'title'), $menu['title']);
}
/**
@@ -343,6 +361,8 @@ function i18n_menu_variable_info_alter(&$variables, $options) {
// Make menu variables translatable
$variables['menu_main_links_source']['localize'] = TRUE;
$variables['menu_secondary_links_source']['localize'] = TRUE;
$variables['menu_parent_[node_type]']['localize'] = TRUE;
$variables['menu_options_[node_type]']['localize'] = TRUE;
}
/**
@@ -377,7 +397,7 @@ function i18n_menu_localize_tree($tree, $langcode = NULL) {
if (_i18n_menu_link_process($item['link'])) {
if (!_i18n_menu_link_is_visible($item['link'], $langcode)) {
// Remove links for other languages than current.
// Links with language wont be localized.
// Links with language won't be localized.
unset($tree[$index]);
// @todo Research whether the above has any advantage over:
// $item['hidden'] = TRUE;
@@ -468,7 +488,8 @@ function i18n_menu_navigation_links($menu_name, $level = 0) {
* Get localized menu title
*/
function _i18n_menu_link_title($link, $langcode = NULL) {
return i18n_string_translate(array('menu', 'item', $link['mlid'], 'title'), $link['link_title'], array('langcode' => $langcode, 'sanitize' => FALSE));
$key = i18n_object_info('menu_link', 'key');
return i18n_string_translate(array('menu', 'item', $link[$key], 'title'), $link['link_title'], array('langcode' => $langcode, 'sanitize' => FALSE));
}
/**
@@ -500,7 +521,8 @@ function _i18n_menu_link_localize(&$link, $langcode = NULL) {
*/
function _i18n_menu_link_description($link, $langcode = NULL) {
if (!empty($link['options']['attributes']['title'])) {
return i18n_string_translate(array('menu', 'item', $link['mlid'], 'description'), $link['options']['attributes']['title'], array('langcode' => $langcode));
$key = i18n_object_info('menu_link', 'key');
return i18n_string_translate(array('menu', 'item', $link[$key], 'description'), $link['options']['attributes']['title'], array('langcode' => $langcode, 'sanitize' => FALSE));
}
else {
return NULL;
@@ -511,9 +533,11 @@ function _i18n_menu_link_description($link, $langcode = NULL) {
* Check whether this link is to be processed by i18n_menu and start processing.
*/
function _i18n_menu_link_process(&$link) {
// Only visible links that have a language property and haven't been processed
// before. We also check that they belong to a menu with language options.
if (empty($link['i18n_menu']) && !empty($link['language']) && !empty($link['access']) && empty($link['hidden']) && i18n_menu_mode($link['menu_name'])) {
// Only links that have a language property and haven't been processed before.
// We also translate links marked as hidden because core breadcrumbs ignore
// that flag and excluding them would basically interfere with core behaviour.
// We also check that they belong to a menu with language options.
if (empty($link['i18n_menu']) && !empty($link['language']) && !empty($link['access']) && i18n_menu_mode($link['menu_name'])) {
// Mark so it won't be processed twice.
$link['i18n_menu'] = TRUE;
// Skip if administering this menu or this menu item.
@@ -525,6 +549,10 @@ function _i18n_menu_link_process(&$link) {
return FALSE;
}
}
// Skip if administering this menu item through the node edit form.
elseif (arg(0) == 'node' && arg(2) == 'edit' && $link['link_path'] == arg(0) . '/' . arg(1)) {
return FALSE;
}
return TRUE;
}
else {
@@ -571,7 +599,7 @@ function _i18n_menu_link_is_visible($link, $langcode = NULL) {
}
/**
* Get localizable properties for menu link checking agains the router item.
* Get localizable properties for menu link checking against the router item.
*/
function _i18n_menu_link_localizable_properties($link) {
$props = array();
@@ -580,7 +608,7 @@ function _i18n_menu_link_localizable_properties($link) {
// If the title callback is 't' and the link title matches the router title
// it will be localized by core, not by i18n_menu.
if (!$router ||
(empty($router['title_callback']) || $router['title_callback'] != 't') ||
(empty($router['title_callback']) || ($router['title_callback'] != 't' || !empty($link['customized']))) ||
(empty($router['title']) || $router['title'] != $link['link_title'])
) {
$props[] = 'title';
@@ -589,7 +617,7 @@ function _i18n_menu_link_localizable_properties($link) {
if (!empty($link['options']['attributes']['title'])) {
// If the description matches the router description, it will be localized
// by core.
if (!$router || empty($router['description']) || $router['description'] != $link['options']['attributes']['title']) {
if (!$router || empty($router['description']) || ($router['description'] != $link['options']['attributes']['title']) || !empty($link['customized'])) {
$props[] = 'description';
}
}
@@ -709,6 +737,30 @@ function i18n_menu_form_menu_edit_item_alter(&$form, &$form_state) {
array_unshift($form['#validate'], 'i18n_menu_menu_item_prepare_normal_path');
}
/**
* Implements hook_form_FORM_ID_alter().
* FORM_ID = menu-overview-form.
* Add a "translate" link in operations column for each menu item.
*/
function i18n_menu_form_menu_overview_form_alter(&$form, &$form_state) {
if (i18n_menu_mode($form['#menu']['menu_name'], I18N_MODE_MULTIPLE)) {
foreach (element_children($form) as $element) {
if (substr($element, 0, 5) == 'mlid:') {
$item = $form[$element]["#item"];
$mlid = $form[$element]['#item']['mlid'];
if (i18n_get_object('menu', $mlid)->get_translate_access()) {
$form[$element]['operations']['translate'] = array(
'#type' => 'link',
'#title' => t('translate'),
'#href' => "admin/structure/menu/item/{$mlid}/translate",
);
$form[$element]['title']['#markup'] = l(_i18n_menu_link_title($item), $item['href'], $item['localized_options']);
}
}
}
}
}
/**
* Normal path should be checked with menu item's language to avoid
* troubles when a node and it's translation has the same url alias.
@@ -727,6 +779,9 @@ function i18n_menu_item_get_language($item) {
}
else {
$menu = menu_load($item['menu_name']);
if (!isset($menu['i18n_mode'])) {
return LANGUAGE_NONE;
}
switch ($menu['i18n_mode']) {
case I18N_MODE_LANGUAGE:
return $menu['language'];
@@ -859,93 +914,33 @@ function i18n_menu_link_load($path, $langcode) {
}
/**
* Implements hook_init().
* Implements hook_query_TAG_alter() for features_menu_links.
* Add needed fields to properly serialize localization information.
*/
function i18n_menu_init() {
function i18n_menu_query_features_menu_link_alter($query) {
$query->fields('menu_links', array('language', 'customized'));
}
// The only way to override the default preferred menu link for a path is to
// inject it into the static cache of the function menu_link_get_preferred().
/**
* Implements hook_query_TAG_alter()
*
* Using tag 'preferred_menu_links' added in menu_link_get_preferred().
* See http://drupal.org/node/1854134
*/
function i18n_menu_query_preferred_menu_links_alter(QueryAlterableInterface $query) {
global $language;
// Get queried tables.
$tables = $query->getTables();
// The problem with the default implementation is that it does not take the
// language of a menu link into account. Whe having different menu trees for
// different menus, this means that the active trail will not work for all but
// one language.
// The code below is identical to the mentioned function except the added
// language condition on the query.
// TODO: Adding an alter tag to the query would allow to do this with a simple
// hook_query_alter() implementation.
$preferred_links = &drupal_static('menu_link_get_preferred');
$path = $_GET['q'];
// Look for the correct menu link by building a list of candidate paths,
// which are ordered by priority (translated hrefs are preferred over
// untranslated paths). Afterwards, the most relevant path is picked from
// the menus, ordered by menu preference.
$item = menu_get_item($path);
$path_candidates = array();
// 1. The current item href.
$path_candidates[$item['href']] = $item['href'];
// 2. The tab root href of the current item (if any).
if ($item['tab_parent'] && ($tab_root = menu_get_item($item['tab_root_href']))) {
$path_candidates[$tab_root['href']] = $tab_root['href'];
}
// 3. The current item path (with wildcards).
$path_candidates[$item['path']] = $item['path'];
// 4. The tab root path of the current item (if any).
if (!empty($tab_root)) {
$path_candidates[$tab_root['path']] = $tab_root['path'];
}
// Retrieve a list of menu names, ordered by preference.
$menu_names = menu_get_active_menu_names();
// Use an illegal menu name as the key for the preferred menu link.
$selected_menu = MENU_PREFERRED_LINK;
// Put the selected menu at the front of the list.
array_unshift($menu_names, $selected_menu);
$query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC));
$query->leftJoin('menu_router', 'm', 'm.path = ml.router_path');
$query->fields('ml');
// Weight must be taken from {menu_links}, not {menu_router}.
$query->addField('ml', 'weight', 'link_weight');
$query->fields('m');
$query->condition('ml.link_path', $path_candidates, 'IN');
// Only look menu links with none or the current language.
$query->condition('ml.language', array(LANGUAGE_NONE, i18n_language_interface()->language), 'IN');
// Sort candidates by link path and menu name.
$candidates = array();
foreach ($query->execute() as $candidate) {
$candidate['weight'] = $candidate['link_weight'];
$candidates[$candidate['link_path']][$candidate['menu_name']] = $candidate;
// Add any menus not already in the menu name search list.
if (!in_array($candidate['menu_name'], $menu_names)) {
$menu_names[] = $candidate['menu_name'];
}
}
// Store the most specific link for each menu. Also save the most specific
// link of the most preferred menu in $preferred_link.
foreach ($path_candidates as $link_path) {
if (isset($candidates[$link_path])) {
foreach ($menu_names as $menu_name) {
if (empty($preferred_links[$path][$menu_name]) && isset($candidates[$link_path][$menu_name])) {
$candidate_item = $candidates[$link_path][$menu_name];
$map = explode('/', $path);
_menu_translate($candidate_item, $map);
if ($candidate_item['access']) {
$preferred_links[$path][$menu_name] = $candidate_item;
if (empty($preferred_links[$path][MENU_PREFERRED_LINK])) {
// Store the most specific link.
$preferred_links[$path][MENU_PREFERRED_LINK] = $candidate_item;
}
}
}
foreach ($tables as $alias => $table) {
if ($table['table'] == 'menu_links') {
// Add language filter, ensuring that we don't have any collision when
// determining the active menu trail when there are multiple menu items
// with same link path but different languages.
if ($language) {
$query->condition('language', array($language->language, LANGUAGE_NONE), 'IN');
}
break;
}
}
}
@@ -130,6 +130,8 @@ class i18nMenuTestCase extends Drupali18nTestCase {
$this->drupalGet('admin/structure/menu/item/' . $node->menu['mlid'] . '/edit');
$this->assertText(t('This menu item belongs to a node, so it will have the same language as the node and cannot be localized.'));
$this->assertNoField('language', 'We cannot edit language for menu items that belong to nodes.');
// Edit the node and submit it to see if the menu link stays enabled.
$this->drupalPost('node/' . $node->nid . '/edit', array(), t('Save'));
}
// Check menu items show up for the right language.
$this->drupalGet('<front>');
@@ -0,0 +1,96 @@
CONTENTS OF THIS FILE
---------------------
* Introduction
* Requirements
* Recommended modules
* Installation
* Configuration
* Maintainers
INTRODUCTION
------------
The Multilingual content module, part of the Internationalization
(https://www.drupal.org/project/i18n) package, provides extended multilingual
options for nodes. These options help accommodate a variety of translation
workflows by controlling how the language for nodes is set.
Note that the Multilingual content module lives in the i18n_node directory
in the Internationalization package. Don't confuse this module with the core
Content translation module.
* For a full description of the module visit:
https://www.drupal.org/node/1279644.
* To submit bug reports and feature suggestions, or to track changes visit
https://www.drupal.org/project/issues/i18n.
REQUIREMENTS
------------
This module requires the following module:
* Internationalization - https://www.drupal.org/project/i18n
RECOMMENDED MODULES
-------------------
* Internationalization Views - https://www.drupal.org/project/i18nviews
* Language Icons - https://www.drupal.org/project/languageicons
* Translation Overview - https://www.drupal.org/project/translation_overview
* Localization Client - https://www.drupal.org/project/l10n_client
* Internationalization contributions -
https://www.drupal.org/project/i18n_contrib
INSTALLATION
------------
This is a submodule of the Internationalization module. Install the
Internationalization module as you would normally install a contributed Drupal
module. Visit https://www.drupal.org/node/895232 for further information.
CONFIGURATION
-------------
For each content type, the following Extended language options are available
under the Multilingual Settings tab.
1. "Set current language as default for new content" can be useful for content
that is community-generated.
2. "Require language" prevents users from creating 'Language neutral' nodes.
3. "Lock language" prevents users from changing the language of a node after
it's created.
Site-wide Settings for Node Translation
1. There are also site-wide settings provided to help streamline how
multilingual content is created. Navigate to Config > Regional and language >
Multilingual settings > Node Options.
2. "Switch interface for translating" switches the language of the user
interface to the chosen language when a user translates a node. This is
useful if users speak the language in which the translation is written. It
means that after the node translation is saved, the language of the UI will
match the language of the node.
3. "Hide content translation links" will prevent the language switcher links
from appearing in nodes and teasers. This is useful if a language switcher
block is enabled on the site.
4. The user can also select the default language for new nodes if the
corresponding content type doesn't have language support. By default, it is
set to be in the default language of the site, but this can be changed to be
language neutral. This is a useful option when thinking about forward
compatibility for adding multilingual support to these content types in the
future.
MAINTAINERS
-----------
* Jose Reyero - https://www.drupal.org/u/jose-reyero
* Florian Weber (webflo) - https://www.drupal.org/u/webflo
* Peter Philipp - https://www.drupal.org/u/das-peter
* Joseph Olstad - https://www.drupal.org/u/joseph.olstad
* Nathaniel Catchpole - https://www.drupal.org/u/catch
@@ -9,9 +9,8 @@ configure = admin/config/regional/i18n/node
files[]=i18n_node.test
files[]=i18n_node.variable.inc
; Information added by drupal.org packaging script on 2013-01-13
version = "7.x-1.8"
; Information added by Drupal.org packaging script on 2020-06-17
version = "7.x-1.27"
core = "7.x"
project = "i18n"
datestamp = "1358075001"
datestamp = "1592373390"
@@ -54,7 +54,7 @@ function i18n_node_block_view_system_help_alter(&$block) {
$arg = drupal_help_arg(arg(NULL));
if ($arg[0] == 'node' && $arg[1] == 'add' && $arg[2]) {
if (($type = node_type_get_type(str_replace('-', '_', $arg[2]))) && !empty($type->help)) {
$help = i18n_node_translate_type($type, 'help');
$help = i18n_node_translate_type($type, 'help', NULL, array('sanitize' => FALSE));
if ($help !== $type->help) {
$block['content'] = str_replace(filter_xss_admin($type->help), filter_xss_admin($help), $block['content']);
}
@@ -64,7 +64,7 @@ function i18n_node_block_view_system_help_alter(&$block) {
$node = menu_get_object();
if ($node && isset($node->type)) {
$type = node_type_get_type($node->type);
$help = i18n_node_translate_type($type, 'help');
$help = i18n_node_translate_type($type, 'help', NULL, array('sanitize' => FALSE));
if ($help !== $type->help) {
$block['content'] = str_replace(filter_xss_admin($type->help), filter_xss_admin($help), $block['content']);
}
@@ -121,6 +121,8 @@ function i18n_node_i18n_translate_path($path) {
'href' => 'node/' . $node_translation->nid . $matches[2],
'title' => $node_translation->title,
'object' => $node_translation,
// Performance: for node view add access information right away.
'access' => !$matches[2] ? $node_translation->status : NULL,
);
}
return $result;
@@ -220,8 +222,16 @@ function i18n_node_language_mode($type) {
function i18n_node_node_prepare($node) {
$options = variable_get('i18n_node_options_' . $node->type, array());
if (i18n_node_type_enabled($node) && empty($node->nid) && !i18n_object_langcode($node) && in_array('current', $options)) {
$default = variable_get('i18n_node_default_language_for_' . $node->type, '-- current --');
// Set current language for new nodes if option enabled
$node->language = i18n_language_content()->language;
if ($default === '-- current --') {
$node->language = i18n_language_content()->language;
}
// If a custom language was specified, apply it.
else {
$node->language = $default;
}
}
}
@@ -247,13 +257,16 @@ function i18n_node_permission() {
/**
* Implements hook_node_view()
*/
function i18n_node_node_view($node) {
function i18n_node_node_view($node, $view_mode, $langcode) {
if (i18n_node_type_enabled($node)) {
$node->content['language'] = array(
'#type' => 'item',
'#title' => t('Language'),
'#markup' => i18n_language_name($node->language),
);
$extra_fields_display_settings = field_extra_fields_get_display('node', $node->type, $view_mode);
if ($extra_fields_display_settings['language']['visible']) {
$node->content['language'] = array(
'#type' => 'item',
'#title' => t('Language'),
'#markup' => i18n_language_name($node->language),
);
}
}
}
@@ -263,7 +276,9 @@ function i18n_node_node_view($node) {
* Handles links for extended languages. Sets current interface language.
*/
function i18n_node_node_view_alter(&$build) {
$node = $build['#node'];
if (isset($build['#node'])) {
$node = $build['#node'];
}
// Hide node translation links.
if (variable_get('i18n_hide_translation_links', 0)) {
if (isset($build['links']['translation'])) {
@@ -347,7 +362,7 @@ function i18n_node_textfield_process($element) {
static $sent = FALSE;
// Ensure we send the Javascript only once.
if (!$sent && isset($element['#autocomplete_path']) && !empty($element['#autocomplete_path']) && variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE) != LANGUAGE_NEGOTIATION_NONE) {
if (!$sent && isset($element['#autocomplete_path']) && !empty($element['#autocomplete_path']) && variable_get('language_negotiation', LANGUAGE_NEGOTIATION_DEFAULT) != LANGUAGE_NEGOTIATION_DEFAULT) {
// Add a JS file for node forms.
// Determine if we are either editing or translating an existing node.
// We can't act on regular node creation because we don't have a specified
@@ -415,7 +430,16 @@ function i18n_node_form_node_type_form_alter(&$form, &$form_state) {
// Some settings about node languages. Add variables for node type from variable definition
if ($form['#node_type']->type) {
variable_type_include('node_type');
$form['i18n'] += node_variable_type_subform($form['#node_type']->type, array('i18n_node_options', 'i18n_node_extended'));
$form['i18n'] += node_variable_type_subform($form['#node_type']->type, array('i18n_node_options', 'i18n_node_default_language_for', 'i18n_node_extended'));
// Only show custom default language field if "current" is checked.
$form['i18n']['i18n_node_default_language_for']['#states'] = array(
'visible' => array(
':input[name="i18n_node_options[current]"]' => array('checked' => TRUE),
),
'required' => array(
':input[name="i18n_node_options[current]"]' => array('checked' => TRUE),
),
);
}
// Add disabled message
if ($disabled) {
@@ -442,7 +466,7 @@ function i18n_node_form_node_form_alter(&$form, $form_state) {
*
* @see http://drupal.org/node/765860
*/
// Replace core's node submit callback with our own,
// in order to translate the node type name.
$key = array_search('node_form_submit', $form['actions']['submit']['#submit']);
@@ -464,8 +488,7 @@ function i18n_node_form_submit($form, &$form_state) {
$insert = empty($node->nid);
node_save($node);
$node_link = l(t('view'), 'node/' . $node->nid);
$type = node_type_get_type($node->type);
$type_name = i18n_node_translate_type($type);
$type_name = i18n_node_type_name($node->type);
$watchdog_args = array('@type' => $node->type, '%title' => $node->title);
$t_args = array('@type' => $type_name, '%title' => $node->title);
@@ -481,7 +504,7 @@ function i18n_node_form_submit($form, &$form_state) {
if ($node->nid) {
$form_state['values']['nid'] = $node->nid;
$form_state['nid'] = $node->nid;
$form_state['redirect'] = 'node/' . $node->nid;
$form_state['redirect'] = node_access('view', $node) ? 'node/' . $node->nid : '<front>';
}
else {
// In the unlikely case something went wrong on save, the node will be
@@ -505,8 +528,11 @@ function _i18n_node_form_node_form_alter($form, &$form_state) {
}
}
elseif (variable_get('i18n_node_default_language_none', 0) && !isset($form['#node']->nid)) {
// Override locale module setting default language to nodes. It is already in form_state.
$form['language']['#value'] = $form_state['values']['language'] = LANGUAGE_NONE;
// Only do this if the language is really disabled
if (variable_get('language_content_type_' . $node->type, 0) == 0) {
// Override locale module setting default language to nodes. It is already in form_state.
$form['language']['#value'] = $form_state['values']['language'] = LANGUAGE_NONE;
}
}
// Translate field names for title and body for the node edit form.
if (!empty($form['title']['#title'])) {
@@ -516,12 +542,11 @@ function _i18n_node_form_node_form_alter($form, &$form_state) {
$form['body_field']['body']['#title'] = i18n_node_translate_type($node->type, 'body', $form['body_field']['body']['#title']);
}
// Translate page title for node/add/% and node/%/edit pages.
$types = node_type_get_types();
if (empty($node->nid) && strpos($_GET['q'], 'node/add/' . str_replace('_', '-', $node->type)) === 0) {
drupal_set_title(t('Create @name', array('@name' => i18n_node_translate_type($types[$node->type], 'name'))), PASS_THROUGH);
drupal_set_title(t('Create @name', array('@name' => i18n_node_type_name($node->type))), PASS_THROUGH);
}
elseif (!empty($node->nid) && $_GET['q'] == 'node/' . $node->nid . '/edit') {
drupal_set_title(t('<em>Edit @type</em> @title', array('@type' => i18n_node_translate_type($types[$node->type], 'name'), '@title' => $node->title)), PASS_THROUGH);
drupal_set_title(t('<em>Edit @type</em> @title', array('@type' => i18n_node_type_name($node->type), '@title' => $node->title)), PASS_THROUGH);
}
return $form;
}
@@ -553,10 +578,16 @@ function i18n_node_translate_type($type, $property = 'name', $source = NULL, $op
}
/**
* Translate node type name (unfiltered)
* Get translated node type name (unfiltered)
*
* @param string $type
* Node type.
* @param string $name
* Optional node type name.
*/
function i18n_node_type_name($type, $name) {
return i18n_string_translate(array('node', 'type', $type, 'name'), $name);
function i18n_node_type_name($type, $name = NULL) {
$name = isset($name) ? $name : node_type_get_name($type);
return i18n_string_translate(array('node', 'type', $type, 'name'), $name, array('sanitize' => FALSE));
}
/**
@@ -25,7 +25,7 @@ function i18n_node_add_page() {
if ($type) {
// We just need to translate the description, the title is translated by the menu system
// The string will be filtered (xss_admin) on the theme layer
$item['description'] = i18n_node_translate_type($type, 'description', $item['description']);
$item['description'] = i18n_node_translate_type($type, 'description', $item['description'], array('sanitize' => FALSE));
}
}
return theme('node_add_list', array('content' => $content));
@@ -213,6 +213,7 @@ function i18n_node_select_translation_submit($form, &$form_state) {
))
->condition('nid', $add)
->execute();
entity_get_controller('node')->resetCache($add);
if (count($new)) {
drupal_set_message(format_plural(count($new), 'Added a node to the translation set.', 'Added @count nodes to the translation set.'));
}
@@ -224,6 +225,7 @@ function i18n_node_select_translation_submit($form, &$form_state) {
))
->condition('nid', $remove)
->execute();
entity_get_controller('node')->resetCache($remove);
drupal_set_message(format_plural(count($remove), 'Removed a node from the translation set.', 'Removed @count nodes from the translation set.'));
}
}
@@ -246,7 +248,7 @@ function i18n_node_autocomplete($type, $language, $string = '') {
*/
function i18n_node_nid2autocomplete($nid) {
if ($node = node_load($nid)) {
return check_plain($node->title) . ' [nid:' . $nid . ']';
return $node->title . ' [nid:' . $nid . ']';
}
else {
return t('Not found');
@@ -0,0 +1,61 @@
<?php
/**
* @file
* Builds placeholder replacement tokens for content types.
*/
/**
* Implements hook_token_info().
*/
function i18n_node_token_info() {
$content_type['i18n-name'] = array(
'name' => t("Name (localized)"),
'description' => t("The name of the content type."),
);
$content_type['i18n-description'] = array(
'name' => t("Description (localized)"),
'description' => t("The optional description of the content type."),
);
return array(
'tokens' => array(
'content-type' => $content_type,
),
);
}
/**
* Implements hook_tokens().
*/
function i18n_node_tokens($type, $tokens, array $data = array(), array $options = array()) {
$replacements = array();
$sanitize = !empty($options['sanitize']) ? TRUE : FALSE;
$langcode = isset($options['language']) ? $options['language']->language : i18n_langcode();
if ($type == 'content-type' && !empty($data['node_type'])) {
$node_type = $data['node_type'];
foreach ($tokens as $name => $original) {
switch ($name) {
case 'i18n-name':
$name = array('node', 'type', $node_type->type, 'name');
$options = array('sanitize' => $sanitize, 'langcode' => $langcode);
$name = i18n_string_translate($name, $node_type->name, $options);
$replacements[$original] = $name;
break;
case 'i18n-description':
$description = array('node', 'type', $node_type->type, 'description');
$options = array('sanitize' => $sanitize, 'langcode' => $langcode);
$description = i18n_string_translate($description, $node_type->description, $options);
$replacements[$original] = $description;
break;
}
}
}
return $replacements;
}
@@ -45,13 +45,30 @@ function i18n_node_variable_info($options = array()) {
'repeat' => array(
'type' => 'options',
'options' => array(
'current' => t('Set current language as default for new content.', array(), $options),
// Note: this was previously used only to mark new, translatable nodes
// with the current language of the user. Now, this setting is extended
// to allow a specific language to be chosen (defaulting to the current
// language). This was done for backwards compatibility reasons.
'current' => t('Set custom language as default for new content.', array(), $options),
'required' => t('Require language (Do not allow Language Neutral).', array(), $options),
'lock' => t('Lock language (Cannot be changed).', array(), $options),
),
),
'group' => 'i18n',
);
// This field will only be displayed if "current" is checked above.
$variables['i18n_node_default_language_for_[node_type]'] = array(
'type' => 'multiple',
'title' => t('Custom default language', array(), $options),
'repeat' => array(
'type' => 'select',
'options' => array_merge(array(
'-- current --' => t('Current language')
), locale_language_list('name')),
'default' => '-- current --',
),
'group' => 'i18n',
);
$variables['i18n_node_extended_[node_type]'] = array(
'type' => 'multiple',
'title' => t('Extended language support'),
@@ -6,9 +6,8 @@ core = 7.x
files[] = i18n_path.inc
files[] = i18n_path.test
; Information added by drupal.org packaging script on 2013-01-13
version = "7.x-1.8"
; Information added by Drupal.org packaging script on 2020-06-17
version = "7.x-1.27"
core = "7.x"
project = "i18n"
datestamp = "1358075001"
datestamp = "1592373390"
@@ -0,0 +1,82 @@
CONTENTS OF THIS FILE
---------------------
* Introduction
* Requirements
* Recommended modules
* Installation
* Configuration
* Maintainers
INTRODUCTION
------------
The Redirect translation module, part of the Internationalization
(https://www.drupal.org/project/i18n) package, improves search engine
optimization (SEO) for multilingual websites.
It redirects anonymous users (including web crawlers) to the translation of the
page in the requested language, if it exists, using a 301 redirect code.
* For a full description of the module, visit this page:
https://www.drupal.org/node/1280468.
* To submit bug reports and feature suggestions, or to track changes:
https://www.drupal.org/project/issues/i18n.
REQUIREMENTS
------------
This module requires the following module:
* Internationalization - https://www.drupal.org/project/i18n
The Translation redirect module requires the implementation of
hook_i18n_translate_path by another module for the redirect page to be
determined. Currently, the Multilingual content, Path translation, and Taxonomy
translation modules implement this hook.
RECOMMENDED MODULES
-------------------
* Internationalization Views - https://www.drupal.org/project/i18nviews
* Language Icons - https://www.drupal.org/project/languageicons
* Translation Overview - https://www.drupal.org/project/translation_overview
* Localization Client - https://www.drupal.org/project/l10n_client
* Internationalization contributions -
https://www.drupal.org/project/i18n_contrib
INSTALLATION
------------
This is a submodule of the Internationalization module. Install the
Internationalization module as you would normally install a contributed Drupal
module. Visit https://www.drupal.org/node/895232 for further information.
CONFIGURATION
-------------
No configuration is necessary.
For node translation, enable the Multilingual content and the Translation
redirect modules. For non-node pages, the redirection hook must be implemented
by the relevant module.
For example, for taxonomy pages, you should enable the Taxonomy translation
module because the module provides the necessary hook code. If you are using the
Path translation module to create translation sets for non-node pages, then it
implements the hook code for determining the redirection page.
MAINTAINERS
-----------
* Jose Reyero - https://www.drupal.org/u/jose-reyero
* Florian Weber (webflo) - https://www.drupal.org/u/webflo
* Peter Philipp - https://www.drupal.org/u/das-peter
* Joseph Olstad - https://www.drupal.org/u/joseph.olstad
* Nathaniel Catchpole - https://www.drupal.org/u/catch
@@ -4,9 +4,8 @@ dependencies[] = i18n
package = Multilingual - Internationalization
core = 7.x
; Information added by drupal.org packaging script on 2013-01-13
version = "7.x-1.8"
; Information added by Drupal.org packaging script on 2020-06-17
version = "7.x-1.27"
core = "7.x"
project = "i18n"
datestamp = "1358075001"
datestamp = "1592373390"
@@ -0,0 +1,82 @@
CONTENTS OF THIS FILE
---------------------
* Introduction
* Requirements
* Recommended modules
* Installation
* Configuration
* Maintainers
INTRODUCTION
------------
The Multilingual select module, part of the Internationalization
(https://www.drupal.org/project/i18n) package, allows the user to define whether
content is filtered by language on pages provided by Drupal core.
* For a full description of the module visit:
https://www.drupal.org/node/1279512.
* To submit bug reports and feature suggestions, or to track changes visit
https://www.drupal.org/project/issues/i18n.
REQUIREMENTS
------------
This module requires the following module:
* Internationalization - https://www.drupal.org/project/i18n
* Variable - https://www.drupal.org/project/variable
RECOMMENDED MODULES
-------------------
* Internationalization Views - https://www.drupal.org/project/i18nviews
* Language Icons - https://www.drupal.org/project/languageicons
* Translation Overview - https://www.drupal.org/project/translation_overview
* Localization Client - https://www.drupal.org/project/l10n_client
* Internationalization contributions -
https://www.drupal.org/project/i18n_contrib
INSTALLATION
------------
This is a submodule of the Internationalization module. Install the
Internationalization module as you would normally install a contributed Drupal
module. Visit https://www.drupal.org/node/895232 for further information.
CONFIGURATION
-------------
The module allows the user to configure whether or not to filter pages by the
current language. If the Taxonomy translation submodule is also enabled, an
option will be available for how taxonomy term pages are filtered. It also
allows the exclusion of the modules language selection handling for some
elements and certain paths.
1. Enable the Multilingual select module included with Internationalization.
2. Navigate to Configuration > Regional and language > Multilingual settings >
Selection.
3. Select nodes and taxonomy can be filtered by language in the Content to
Filter by Language field set.
4. The "Content Selection Mode" allows content with specific tags to be skipped
by entering a list of tags.
5. The "Enable for Specific Pages" field set allows specific pages to be
included by path.
6. After making choices, Save configuration.
MAINTAINERS
-----------
* Jose Reyero - https://www.drupal.org/u/jose-reyero
* Florian Weber (webflo) - https://www.drupal.org/u/webflo
* Peter Philipp - https://www.drupal.org/u/das-peter
* Joseph Olstad - https://www.drupal.org/u/joseph.olstad
* Nathaniel Catchpole - https://www.drupal.org/u/catch
@@ -6,9 +6,8 @@ core = 7.x
configure = admin/config/regional/i18n/select
files[] = i18n_select.test
; Information added by drupal.org packaging script on 2013-01-13
version = "7.x-1.8"
; Information added by Drupal.org packaging script on 2020-06-17
version = "7.x-1.27"
core = "7.x"
project = "i18n"
datestamp = "1358075001"
datestamp = "1592373390"
@@ -82,11 +82,11 @@ function i18n_select_mode($type = NULL) {
}
/**
* Check current path to enable selection
* Check current path to enable selection.
*
* This works pretty much like block visibility
* This works pretty much like block visibility.
*
* @return boolean
* @return bool
* TRUE if content selection should be enabled for this page.
*/
function i18n_select_page() {
@@ -100,8 +100,11 @@ function i18n_select_page() {
// with different case. Ex: /Page, /page, /PAGE.
$pages = drupal_strtolower($pages);
if ($visibility < I18N_SELECT_PAGE_PHP) {
// Convert the Drupal path to lowercase
$path = drupal_strtolower(drupal_get_path_alias($_GET['q']));
// @see views_ajax()
// @see I18NSelectAdminViewsAjax::testViewsAjaxWithoutSkippingTags()
$path = isset($_REQUEST['view_path']) ? $_REQUEST['view_path'] : $_GET['q'];
// Convert the Drupal path to lowercase.
$path = drupal_strtolower(drupal_get_path_alias($path));
// Compare the lowercase internal and lowercase path alias (if any).
$page_match = drupal_match_path($path, $pages);
if ($path != $_GET['q']) {
@@ -109,8 +112,8 @@ function i18n_select_page() {
}
// When $visibility has a value of 0 (I18N_SELECT_PAGE_NOTLISTED),
// the block is displayed on all pages except those listed in $pages.
// When set to 1 (I18N_SELECT_PAGE_LISTED), it is displayed only on those
// pages listed in $pages.
// When set to 1 (I18N_SELECT_PAGE_LISTED), it is displayed only on
// those pages listed in $pages.
$mode = !($visibility xor $page_match);
}
elseif (module_exists('php')) {
@@ -121,7 +124,7 @@ function i18n_select_page() {
}
}
else {
// No pages defined, still respect the setting (unlike blocks)
// No pages defined, still respect the setting (unlike blocks).
$mode = $visibility == I18N_SELECT_PAGE_NOTLISTED;
}
}
@@ -223,7 +226,7 @@ function _i18n_select_table_alias($table) {
function i18n_select_check_conditions($query, $table_alias = NULL) {
$conditions =& $query->conditions();
foreach ($conditions as $index => $condition) {
if (is_array($condition) && isset($condition['field'])) {
if (is_array($condition) && !empty($condition['field'])) {
if (strpos($condition['field'], '.') === FALSE) {
if ($table_alias) {
// Change the condition to include a table alias.
@@ -84,3 +84,87 @@ class i18nSelectTestCase extends Drupali18nTestCase {
}
}
}
/**
* Test case for AJAX queries on "views/ajax" when view on admin page.
*/
class I18NSelectAdminViewsAjax extends Drupali18nTestCase {
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => t('I18N select Admin Views (AJAX)'),
'group' => 'Internationalization',
'description' => t('Test AJAX requests to the "views/ajax" when view located on "admin/*" and list of skipping tags is empty.'),
// Skip this test when "admin_views" module does not exists.
'dependencies' => array('admin_views'),
);
}
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp('translation', 'i18n_variable', 'i18n_select', 'admin_views');
parent::setUpLanguages(array('access content overview'));
parent::setUpContentTranslation();
}
/**
* Test AJAX of a view without skipping tags for selection.
*
* @see i18n_select_page()
*/
public function testViewsAjaxWithoutSkippingTags() {
// If this variable will have the "views" value then this test will not
// have sense. For instance, we want apply language selection filter
// for views and remove "views" from "i18n_select_skip_tags" variable.
// In this case all AJAX for views, on administration part of the site,
// will be broken because the "i18n_select_page()" function will work
// with "views/ajax" path instead of, for example, "admin/content".
variable_set('i18n_select_skip_tags', '');
// Create one hundred of nodes.
for ($i = 1; $i <= 100; $i++) {
// Create every second node on Spanish language and
// every first - on English.
$node = $this->createNode('page', "Node $i", '', $i % 2 ? $this->default_language : $this->secondary_language);
// Update "changed" in order to sort the content by updating date. In
// other case all nodes will be with the same date and not arranged in
// order.
db_update('node')
->fields(array('changed' => strtotime("+ $i minute")))
->condition('nid', $node->nid)
->execute();
}
$this->drupalGet('admin/content');
// Check that latest node exists at the top.
$this->assertText('Node 100');
// Check that our page contains fifty nodes (the latest must be 51).
$this->assertNoText('Node 50');
// Test $_REQUEST['view_path']. There's no form to submit to, so
// drupalPost() won't work here. This just tests a direct $_POST
// request instead.
$this->curlExec(array(
CURLOPT_URL => $this->getAbsoluteUrl('views/ajax'),
CURLOPT_POST => TRUE,
CURLOPT_POSTFIELDS => http_build_query(array(
'page' => 1,
'view_path' => 'admin/content',
'view_name' => 'admin_views_node',
'view_display_id' => 'system_1',
)),
));
// Check that we are successfully switched to a new page of content.
$this->assertText('Node 50');
$this->assertNoText('Node 100');
}
}
@@ -0,0 +1,94 @@
CONTENTS OF THIS FILE
---------------------
* Introduction
* Requirements
* Recommended modules
* Installation
* Configuration
* Maintainers
INTRODUCTION
------------
The String translation module, part of the Internationalization
(https://www.drupal.org/project/i18n) package, provides support for other
modules to translate user-defined strings. This is an API module that must be
enabled only when required by other modules in the i18n package.
* For a full description of the module, visit this page:
https://www.drupal.org/node/1279668
* To submit bug reports and feature suggestions, or to track changes:
https://www.drupal.org/project/issues/i18n
REQUIREMENTS
------------
This module requires the following module:
* Internationalization - https://www.drupal.org/project/i18n
RECOMMENDED MODULES
-------------------
* Internationalization Views - https://www.drupal.org/project/i18nviews
* Language Icons - https://www.drupal.org/project/languageicons
* Translation Overview - https://www.drupal.org/project/translation_overview
* Localization Client - https://www.drupal.org/project/l10n_client
* Internationalization contributions -
https://www.drupal.org/project/i18n_contrib
INSTALLATION
------------
This is a submodule of the Internationalization module. Install the
Internationalization module as you would normally install a contributed Drupal
module. Visit https://www.drupal.org/node/895232 for further information.
CONFIGURATION
-------------
Strings will be translated from the source languages. By default the source
language is the site's default language, so changing the default language could
break these translations.
1. The user can set which language is used as the source language via
Administration > Configuration > Regional and language > Multilingual
settings > Strings. By default, only plain strings are enabled, so regular
blocks are not fully translatable.
2. To allow Filtered HTML, Full HTML or Plain text select the appropriate radio
box(es) and Save configuration.
3. To select the strings to be translated navigate to Administration >
Configuration > Regional and language > Translate interface and select on
Stings vertical tab. From here the user can select which text groups to
translate and select the Refresh strings tab.
FAQ
---
The String translation module allows you to configure which text formats are
translatable. Formats like PHP Filter and Full HTML are translated before they
are processed, so allowing a translator to edit these can be a security risk.
This is particularly problematic when importing translations in bulk from a CSV
file, since the translator's access to the import formats isn't verified by
Drupal. After updating this setting, be sure to refresh the strings via
Administration > Configuration > Regional and language > Translate interface >
Strings so that strings in forbidden formats are deleted.
MAINTAINERS
-----------
* Jose Reyero - https://www.drupal.org/u/jose-reyero
* Florian Weber (webflo) - https://www.drupal.org/u/webflo
* Peter Philipp - https://www.drupal.org/u/das-peter
* Joseph Olstad - https://www.drupal.org/u/joseph.olstad
* Nathaniel Catchpole - https://www.drupal.org/u/catch
@@ -28,7 +28,7 @@ class i18n_string_object {
// Properties from metadata
public $title;
// Array of translations to multiple languages
public $translations;
public $translations = array();
// Textgroup object
protected $_textgroup;
@@ -39,7 +39,49 @@ class i18n_string_object {
if ($data) {
$this->set_properties($data);
}
// Attempt to re-build the data from the persistent cache.
$this->rebuild_from_cache($data);
}
/**
* Rebuild the object data based on the persistent cache.
*
* Since the textgroup defines if a string is cacheable or not the caching
* of the string objects happens in the textgroup handler itself.
*
* @see i18n_string_textgroup_cached::__destruct()
*/
protected function rebuild_from_cache($data = NULL) {
// Check if we've the required information to repopulate the cache and do so
// if possible.
$meta_data_exist = isset($this->textgroup) && isset($this->type) && isset($this->objectid) && isset($this->property);
if ($meta_data_exist && ($cache = cache_get($this->get_cid())) && !empty($cache->data)) {
// Re-spawn the cached data.
// @TODO do we need a array_diff to ensure we don't overwrite the data
// provided by the $data parameter?
$this->set_properties($cache->data);
}
}
/**
* Reset cache, needed for tests.
*/
public function cache_reset() {
$this->translations = array();
// Ensure a possible persistent cache of this object is cleared too.
cache_clear_all($this->get_cid(), 'cache', TRUE);
}
/**
* Returns the caching id for this object.
*
* @return string
* The caching id.
*/
public function get_cid() {
return 'i18n:string:obj:' . $this->get_name();
}
/**
* Get message parameters from context and string.
*/
@@ -63,6 +105,10 @@ class i18n_string_object {
$this->objectkey = (int)$this->objectid;
// Remaining elements glued again with ':'
$this->property = $parts ? implode(':', $parts) : '';
// Attempt to re-build the other data from the persistent cache.
$this->rebuild_from_cache();
return $this;
}
/**
@@ -100,13 +146,14 @@ class i18n_string_object {
if (isset($string['format'])) {
$this->format = $string['format'];
}
if (isset($string['title'])) {
$this->title = $string['title'];
}
}
else {
$this->string = $string;
}
if (isset($string['title'])) {
$this->title = $string['title'];
}
return $this;
}
/**
@@ -279,9 +326,9 @@ class i18n_string_textgroup_default {
// Debug flag, set to true to print out more information.
public $debug;
// Cached or preloaded string objects
public $strings;
public $strings = array();
// Multiple translations search map
protected $cache_multiple;
protected $cache_multiple = array();
/**
* Class constructor.
@@ -400,8 +447,13 @@ class i18n_string_textgroup_default {
/**
* Filter array of strings
*
* @param $filter
* @param array $string_list
* Array of strings to be filtered.
* @param array $filter
* Array of name value conditions.
*
* @return array
* Strings from $string_list that match the filter conditions.
*/
protected static function string_filter($string_list, $filter) {
// Remove 'language' and '*' conditions.
@@ -566,7 +618,11 @@ class i18n_string_textgroup_default {
public function cache_reset() {
$this->strings = array();
$this->string_format = array();
$this->translations = array();
// Reset the persistent caches.
cache_clear_all('i18n:string:tgroup:' . $this->textgroup , 'cache', TRUE);
// Reset the complete string object cache too.
cache_clear_all('i18n:string:obj:', 'cache', TRUE);
}
/**
@@ -613,7 +669,7 @@ class i18n_string_textgroup_default {
public static function load_translation($i18nstring, $langcode) {
// Search the database using lid if we've got it or textgroup, context otherwise
if (!empty($i18nstring->lid)) {
// We've alreay got lid, we just need translation data
// We've already got lid, we just need translation data
$query = db_select('locales_target', 't');
$query->condition('t.lid', $i18nstring->lid);
}
@@ -647,9 +703,19 @@ class i18n_string_textgroup_default {
// Create source string so we get an lid
$this->save_source($string);
}
// Convert objectid to objectkey if it's numeric.
if (!isset($string->objectkey)) {
$string->objectkey = (int)$string->objectid;
if (is_numeric($string->objectid)) {
$string->objectkey = (int)$string->objectid;
}
}
// Make sure objectkey is numeric.
if (!is_numeric($string->objectkey)) {
$string->objectkey = 0;
}
if (!isset($string->format)) {
$string->format = '';
}
@@ -1110,10 +1176,17 @@ class i18n_string_object_wrapper extends i18n_object_wrapper {
$info = is_array($info) ? $info : array('title' => $info);
$field_name = isset($info['field']) ? $info['field'] : $field;
$value = $this->get_field($field_name);
if (is_array($value) && isset($value['value'])) {
$format = isset($value['format']) ? $value['format'] : NULL;
$value = $value['value'];
}
else {
$format = isset($info['format']) ? $this->get_field($info['format']) : NULL;
}
$strings[$this->get_textgroup()][$string_type][$object_id][$field] = array(
'string' => is_array($value) || isset($info['empty']) && $value === $info['empty'] ? NULL : $value,
'title' => $info['title'],
'format' => isset($info['format']) ? $this->get_field($info['format']) : NULL,
'format' => $format,
'name' => array_merge($object_keys, array($field)),
);
}
@@ -1188,6 +1261,10 @@ class i18n_string_object_wrapper extends i18n_object_wrapper {
* Translate access (localize strings)
*/
protected function localize_access() {
// We could check also whether the object has strings to translate:
// && $this->get_strings(array('empty' => TRUE))
// However it may be better to display the 'No available strings' message
// for the user to have a clue of what's going on. See i18n_string_translate_page_object()
return user_access('translate interface') && user_access('translate user-defined strings');
}
@@ -1280,3 +1357,168 @@ class i18n_string_object_wrapper extends i18n_object_wrapper {
return $this->textgroup()->load_strings(array('type' => $type, 'objectid' => $id));
}
}
/**
* Textgroup handler for i18n_string API which integrated persistent caching.
*/
class i18n_string_textgroup_cached extends i18n_string_textgroup_default {
/**
* Defines the timeout for the persistent caching.
* @var int
*/
public $caching_time = CACHE_TEMPORARY;
/**
* Extends the existing constructor with a cache handling.
*
* @param string $textgroup
* The name of this textgroup.
*/
public function __construct($textgroup) {
parent::__construct($textgroup);
// Fetch persistent caches, the persistent caches contain only metadata.
// Those metadata are processed by the related cache_get() methods.
foreach (array('cache_multiple', 'strings') as $caches_type) {
if (($cache = cache_get('i18n:string:tgroup:' . $this->textgroup . ':' . $caches_type)) && !empty($cache->data)) {
$this->{$caches_type} = $cache->data;
}
}
}
/**
* Class destructor.
*
* Updates the persistent caches for the next usage.
* This function not only stores the data of the textgroup objects but also
* of the string objects. That way we ensure that only cacheable string object
* go into the persistent cache.
*/
public function __destruct() {
// Reduce size to cache by removing NULL values.
$this->strings = array_filter($this->strings);
$strings_to_cache = array();
// Store the persistent caches. We just store the metadata the translations
// are stored by the string object itself. However storing the metadata
// reduces the number of DB queries executed during runtime.
$cache_data = array();
foreach ($this->strings as $context => $i18n_string_object) {
$cache_data[$context] = $context;
$strings_to_cache[$context] = $i18n_string_object;
}
cache_set('i18n:string:tgroup:' . $this->textgroup . ':strings', $cache_data, 'cache', $this->caching_time);
$cache_data = array();
foreach ($this->cache_multiple as $pattern => $strings) {
foreach ($strings as $context => $i18n_string_object) {
$cache_data[$pattern][$context] = $context;
$strings_to_cache[$context] = $i18n_string_object;
}
}
cache_set('i18n:string:tgroup:' . $this->textgroup . ':cache_multiple', $cache_data, 'cache', $this->caching_time);
// Cache the string objects related to this textgroup.
// Store only the public visible data into the persistent cache.
foreach ($strings_to_cache as $i18n_string_object) {
// If this isn't an object it's an unprocessed cache item and doesn't need
// to be stored again.
if (is_object($i18n_string_object)) {
cache_set($i18n_string_object->get_cid(), get_object_vars($i18n_string_object), 'cache', $this->caching_time);
}
}
}
/**
* Reset cache, needed for tests.
*
* Takes care of the persistent caches.
*/
public function cache_reset() {
// Reset the persistent caches.
cache_clear_all('i18n:string:tgroup:' . $this->textgroup , 'cache', TRUE);
// Reset the complete string object cache too. This will affect string
// objects of other textgroups as well.
cache_clear_all('i18n:string:obj:', 'cache', TRUE);
return parent::cache_reset();
}
/**
* Get translation from cache.
*
* Extends the original handler with persistent caching.
*
* @param string $context
* The context to look out for.
* @return i18n_string_object|NULL
* The string object if available or NULL otherwise.
*/
protected function cache_get($context) {
if (isset($this->strings[$context])) {
// If the cache contains a string re-build i18n_string_object.
if (is_string($this->strings[$context])) {
$i8n_string_object = new i18n_string_object(array('textgroup' => $this->textgroup));
$i8n_string_object->set_context($context);
$this->strings[$context] = $i8n_string_object;
}
// Now run the original handling.
return parent::cache_get($context);
}
return NULL;
}
/**
* Get strings from multiple cache.
*
* @param $context array
* String context as array with language property at the end.
*
* @return mixed
* Array of strings (may be empty) if we've got a cache hit.
* Null otherwise.
*/
protected function multiple_cache_get($context) {
// Ensure the values from the persistent cache are properly re-build.
$cache_key = implode(':', $context);
if (isset($this->cache_multiple[$cache_key])) {
foreach ($this->cache_multiple[$cache_key] as $cached_context) {
if (is_string($cached_context)) {
$i8n_string_object = new i18n_string_object(array('textgroup' => $this->textgroup));
$i8n_string_object->set_context($cached_context);
$this->cache_multiple[$cache_key][$cached_context] = $i8n_string_object;
}
}
}
else {
// Now we try more generic keys. For instance, if we are searching 'term:1:*'
// we may try too 'term:*:*' and filter out the results.
foreach ($context as $key => $value) {
if ($value != '*') {
$try = array_merge($context, array($key => '*'));
$cached_results = $this->multiple_cache_get($try);
// Now filter the ones that actually match.
if (!empty($cached_results)) {
$cached_results = $this->string_filter($cached_results, $context);
}
return $cached_results;
}
}
}
return parent::multiple_cache_get($context);
}
public function string_update($i18nstring, $options = array()) {
// Flush persistent cache.
cache_clear_all($i18nstring->get_cid(), 'cache', TRUE);
return parent::string_update($i18nstring, $options);
}
public function string_remove($i18nstring, $options = array()) {
// Flush persistent cache.
cache_clear_all($i18nstring->get_cid(), 'cache', TRUE);
return parent::string_remove($i18nstring, $options);
}
}
@@ -10,9 +10,8 @@ files[] = i18n_string.inc
files[] = i18n_string.test
configure = admin/config/regional/i18n/strings
; Information added by drupal.org packaging script on 2013-01-13
version = "7.x-1.8"
; Information added by Drupal.org packaging script on 2020-06-17
version = "7.x-1.27"
core = "7.x"
project = "i18n"
datestamp = "1358075001"
datestamp = "1592373390"
@@ -28,6 +28,10 @@ function i18n_string_install() {
i18n_string_update_7000();
i18n_string_update_7001();
}
// Create new index in {locales_source}, performance improvement in sites with i18n.
if (!db_index_exists('locales_source', 'textgroup_context')) {
db_add_index('locales_source', 'textgroup_context', array('textgroup', array('context', 50)));
}
}
/**
@@ -36,6 +40,8 @@ function i18n_string_install() {
function i18n_string_uninstall() {
// Drop custom field.
db_drop_field('locales_target', 'i18n_status');
// Drop custom index in locales_source table
db_drop_index('locales_source', 'textgroup_context');
}
/**
@@ -88,6 +94,7 @@ function i18n_string_schema() {
),
'objectindex' => array(
'type' => 'int',
'size' => 'big',
'not null' => TRUE,
'default' => 0,
'description' => 'Integer value of Object ID.',
@@ -102,7 +109,7 @@ function i18n_string_schema() {
),
'primary key' => array('lid'),
'indexes' => array(
'group_context' => array('textgroup', 'context'),
'group_context' => array('textgroup', array('context', 50)),
),
);
return $schema;
@@ -230,6 +237,34 @@ function i18n_string_update_7001() {
}
}
/**
* Create new index in {locales_source}, performance improvement in sites with i18n.
*/
function i18n_string_update_7002() {
if (!db_index_exists('locales_source', 'textgroup_context')) {
db_add_index('locales_source', 'textgroup_context', array('textgroup', array('context', 50)));
}
}
/**
* Removed due to buggy upgrade for #2200647.
*/
function i18n_string_update_7003() {
}
/**
* Change objectindex from int to bigint.
*/
function i18n_string_update_7004() {
db_change_field('i18n_string', 'objectindex', 'objectindex', array(
'type' => 'int',
'size' => 'big',
'not null' => TRUE,
'default' => 0,
'description' => 'Integer value of Object ID.',
));
}
/**
* Notes for update script
*/
@@ -257,4 +292,4 @@ function i18n_string_update_7001() {
* Node type
* nodetype:type:[type]:[property] -> node:type:[type]:[property]
* Property names: title -> title_label
*/
*/
@@ -118,13 +118,14 @@ function i18n_string_menu() {
'file' => 'i18n_string.admin.inc',
'access arguments' => array('translate interface'),
);
$items['admin/config/regional/i18n/strings'] = array(
'title' => 'Strings',
'description' => 'Options for user defined strings.',
'weight' => 20,
'type' => MENU_LOCAL_TASK,
'page callback' => 'drupal_get_form',
'page arguments' => array('variable_edit_form', array('i18n_string_allowed_formats', 'i18n_string_source_language')),
'page arguments' => array('variable_edit_form', array('i18n_string_allowed_formats', 'i18n_string_source_language', 'i18n_string_textgroup_class_[textgroup]')),
'access arguments' => array('administer site configuration'),
);
// AJAX callback path for strings.
@@ -258,6 +259,34 @@ function i18n_string_locale_translate_import_form_submit($form, &$form_state) {
}
}
/**
* Implements hook_element_info_alter().
*
* We need to do this on the element info level as wysiwyg also does so and form
* API (incorrectly) does not merge in the defaults for values that are arrays.
*/
function i18n_string_element_info_alter(&$types) {
$types['text_format']['#pre_render'][] = 'i18n_string_pre_render_text_format';
}
/**
* The '#pre_render' function to alter the text format element in a translation.
* The text format for a translation is taken form the original, so the text
* format drop down should be disabled.
*
* @param array $element
* The text_format element which will be rendered.
*
* @return array
* The altered text_format element with a disabled "Text format" select.
*/
function i18n_string_pre_render_text_format($element) {
if (!empty($element['#i18n_string_is_translation'])) {
$element['format']['format']['#attributes']['disabled'] = TRUE;
}
return $element;
}
/**
* Check if translation is required for this language code.
*
@@ -334,7 +363,10 @@ function i18n_string_update_context($oldname, $newname) {
}
/**
* Get textgroup handler
* Get textgroup handler.
*
* @return i18n_string_textgroup_default
*
*/
function i18n_string_textgroup($textgroup) {
$groups = &drupal_static(__FUNCTION__);
@@ -488,6 +520,19 @@ function i18n_string_group_info($group = NULL, $property = NULL, $default = NULL
}
}
/**
* Implements hook_i18n_string_info_alter().
*
* Set determined classes to use for the text group.
*/
function i18n_string_i18n_string_info_alter(&$info) {
foreach (array_keys($info) as $name) {
// If class is not defined. Classes from other modules, fixed classes and etc.
if (!isset($info[$name]['class'])) {
$info[$name]['class'] = variable_get('i18n_string_textgroup_class_' . $name, 'i18n_string_textgroup_default');
}
}
}
/**
* Translate / update multiple strings
@@ -519,11 +564,12 @@ function i18n_string_multiple($operation, $name, $strings, $options = array()) {
*
* This function is intended to return translations for plain strings that have NO text format
*
* @param $name
* @param array|string name
* Array or string concatenated with ':' that contains textgroup and string context
* @param $string
* String in default language or array of strings to be translated
* @param $options
* @param array|string $string
* A string in the default language, a string wth format (array with keys
* value and format),or an array of strings (without format) to be translated.
* @param array $options
* An associative array of additional options, with the following keys:
* - 'langcode' (defaults to the current language) The language code to translate to a language other than what is used to display the page.
* - 'filter' Filtering callback to apply to the translated string only
@@ -531,8 +577,13 @@ function i18n_string_multiple($operation, $name, $strings, $options = array()) {
* - 'callback' Callback to apply to the result (both to translated or untranslated string
* - 'sanitize' Whether to filter the translation applying the text format if any, default is TRUE
* - 'sanitize default' Whether to filter the default value if no translation found, default is FALSE
*
* @return string
*/
function i18n_string_translate($name, $string, $options = array()) {
if (is_array($string) && isset($string['value'])) {
$string = $string['value'];
}
if (is_array($string)) {
return i18n_string_translate_list($name, $string, $options);
}
@@ -583,10 +634,17 @@ function i18n_string_translate_access($string_format, $account = NULL) {
* Message if the user cannot translate that string.
*/
function i18n_string_translate_check_string($i18nstring, $account = NULL) {
if (!user_access('translate interface', $account) || !user_access('translate user-defined strings', $account)) {
// Check block translation permissions.
if ($i18nstring->textgroup == 'blocks') {
if (!user_access('translate interface', $account) && !user_access('translate blocks', $account)) {
return t('This is a user-defined string within a block. You are not allowed to translate blocks.');
}
}
elseif (!user_access('translate interface', $account) || !user_access('translate user-defined strings', $account)) {
return t('This is a user-defined string. You are not allowed to translate these strings.');
}
elseif (!empty($i18nstring->format)) {
if (!empty($i18nstring->format)) {
if (!i18n_string_allowed_format($i18nstring->format)) {
$format = filter_format_load($i18nstring->format);
return t('This string uses the %name text format. Strings with this format are not allowed for translation.', array('%name' => $format->name));
@@ -12,7 +12,14 @@ include_once DRUPAL_ROOT . '/includes/locale.inc';
include_once drupal_get_path('module', 'locale') . '/locale.admin.inc';
/**
* Generate translate page from object
* Generate translate page from object.
*
* @param string $object_type
* Obejct type as declared in hook_i18n_object_info().
* @param object $object_value
* Drupal object to translate.
* @param object $language
* Optional language object.
*/
function i18n_string_translate_page_object($object_type, $object_value, $language = NULL) {
// For backwards compatibility, ensure parameter is a language object
@@ -22,6 +29,13 @@ function i18n_string_translate_page_object($object_type, $object_value, $languag
$object = i18n_object($object_type, $object_value);
$strings = $object->get_strings(array('empty' => TRUE));
// If no localizable strings, print message and fail gracefully.
// Possibly this object comes from some other contrib module.
// See http://drupal.org/node/1889878
if (!$strings) {
return t('This object has no strings available for translation.');
}
if (empty($langcode)) {
drupal_set_title(t('Translate !name', array('!name' => i18n_object_info($object_type, 'title'))));
return i18n_string_translate_page_overview($object, $strings);
@@ -44,7 +58,6 @@ function i18n_string_translate_page_overview($object, $strings) {
* Provide a core translation module like overview page for this object.
*/
function i18n_string_translate_page_overview_form($form, &$form_state, $object, $strings) {
//include_once DRUPAL_ROOT . '/includes/language.inc';
// Set the default item key, assume it's the first.
$item_title = reset($strings);
$header = array(
@@ -157,14 +170,16 @@ function i18n_string_translate_page_form_base($form, $langcode, $redirect = NULL
/**
* Create field elements for strings
*
* @param i18n_string_object[] $strings
* @param string $langcode
*
* @return array
*/
function i18n_string_translate_page_form_strings($strings, $langcode) {
$formats = filter_formats();
global $user;
$form = array();
foreach ($strings as $item) {
// We may have a source or not. Load it, our string may get the format from it.
$source = $item->get_source();
$format_id = $source ? $source->format : $item->format;
$description = '';
// Check permissions to translate this string, depends on format, etc..
if ($message = $item->check_translate_access()) {
// We'll display a disabled element with the reason it cannot be translated.
@@ -175,27 +190,31 @@ function i18n_string_translate_page_form_strings($strings, $langcode) {
$disabled = FALSE;
$description = '';
// If we don't have a source and it can be translated, we create it.
if (!$source) {
if (!$item->get_source()) {
// Enable messages just as a reminder these strings are not being updated properly.
$status = $item->update(array('messages' => TRUE));
if ($status === FALSE || $status === SAVED_DELETED) {
// We don't have a source string so nothing to translate here
$disabled = TRUE;
}
else {
$source = $item->get_source();
}
}
}
$default_value = $item->format_translation($langcode, array('langcode' => $langcode, 'sanitize' => FALSE, 'debug' => FALSE));
$available_formats = array_keys(filter_formats($user));
if (!in_array($item->format, $available_formats)) {
$item->format = NULL;
}
$form[$item->get_name()] = array(
'#title' => $item->get_title(),
'#type' => 'textarea',
'#type' => $item->format ? 'text_format' : 'textarea',
'#default_value' => $default_value,
'#format' => $item->format,
// This will trigger i18n_string_pre_render_text_format() to actually
// alter the element.
'#i18n_string_is_translation' => TRUE,
'#disabled' => $disabled,
'#description' => $description . _i18n_string_translate_format_help($format_id),
//'#i18n_string_format' => $source ? $source->format : 0,
'#description' => $description,
// If disabled, provide smaller textarea (that can be expanded anyway).
'#rows' => $disabled ? 1 : min(ceil(str_word_count($default_value) / 12), 10),
// Change the parent for disabled strings so we don't get empty values later
@@ -213,6 +232,16 @@ function i18n_string_translate_page_form_submit($form, &$form_state) {
foreach ($form_state['values']['strings'] as $name => $value) {
$count++;
list($textgroup, $context) = i18n_string_context(explode(':', $name));
if (is_array($value)) {
if (isset($value['value'])) {
$value = $value['value'];
$form_state['values']['strings'][$name] = $value;
}
else {
form_set_error("strings][$name", t('Unable to get the translated string value.'));
watchdog('locale', 'Unable to get the translated string value, string array is: %string', array('%string' => var_dump($value)), WATCHDOG_WARNING);
}
}
$result = i18n_string_textgroup($textgroup)->update_translation($context, $form_state['values']['langcode'], $value);
$success += ($result ? 1 : 0);
}
@@ -385,6 +414,9 @@ function i18n_string_locale_translate_edit_form_submit($form, &$form_state) {
// Invoke locale submission.
locale_translate_edit_form_submit($form, $form_state);
$lid = $form_state['values']['lid'];
if ($i18n_string_object = i18n_string_get_by_lid($lid)) {
$i18n_string_object->cache_reset();
}
foreach ($form_state['values']['translations'] as $key => $value) {
if (!empty($value)) {
// An update has been made, so we assume the translation is now current.
@@ -24,7 +24,7 @@ class i18nStringTestCase extends Drupali18nTestCase {
function setUp() {
// We can use any of the modules that define a text group, to use it for testing
parent::setUp('i18n_string', 'i18n_menu');
parent::setUp('i18n_string', 'i18n_menu', 'i18n_test');
parent::setUpLanguages();
$this->translator = $this->drupalCreateUser(array('translate interface', 'translate user-defined strings'));
}
@@ -53,8 +53,130 @@ class i18nStringTestCase extends Drupali18nTestCase {
$this->assertEqual($translation, $translations[$key][$language->language], "The right $language->name ($language->language) translation has been retrieved for $name, $translation");
}
}
// Test that regular strings can be translated. Use 'Built-in interface' as
// filter, and translate first one.
$search = array(
'language' => 'all',
'translation' => 'all',
'group' => 'default',
'string' => '',
);
$this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
$this->clickLink(t('edit'));
// Just add a random translation.
$translation = $this->randomName();
$edit = array();
foreach ($this->getOtherLanguages() as $language) {
$langcode = $language->language;
$edit["translations[$langcode]"] = $translation;
}
$this->drupalPost(NULL, $edit, t('Save translations'));
$this->assertText(t('The string has been saved.'), t('The string has been saved.'));
$this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), t('Correct page redirection.'));
}
/**
* Test base i18n_string caching.
*/
function testCaching() {
// Create a bunch of strings for all languages.
$textgroup = 'test_cached';
$strings = $this->stringCreateArray(2);
$translations = array();
$textgroup_object = i18n_string_textgroup($textgroup);
// Save source strings and store translations.
foreach ($strings as $key => $string) {
$name = "$textgroup:item:$key:title";
i18n_string_update($name, $string);
$translations[$key] = $this->createStringTranslation($textgroup, $string);
}
// Now fetch the strings to fill the cache.
foreach ($textgroup_object->strings as $context => $string_object) {
$this->drupalGet('tests/i18n/i18n_string_build/' . $textgroup . ':' . $context);
}
foreach ($strings as $key => $string) {
$this->drupalGet('tests/i18n/i18n_string_translation_search/' . $textgroup . ':item:' . $key . ':*/es');
}
// Check the persistent cache for contents.
$cache = cache_get('i18n:string:tgroup:' . $textgroup . ':strings');
if ($this->assertNotEqual($cache, FALSE, 'Textgroup strings cache found')) {
foreach ($textgroup_object->strings as $context => $string_object) {
if ($this->assertTrue(isset($cache->data[$context]), format_string('Cached string %context found', array('%context' => $context)))) {
$this->assertEqual($cache->data[$context], $context, 'Cached string is a string and not an object');
}
// Check if the string object cache is also available.
$string_cache = cache_get($string_object->get_cid());
if ($this->assertNotEqual($string_cache, FALSE, format_string('Cached string object %cid found', array('%cid' => $string_object->get_cid())))) {
$this->assertTrue(is_array($string_cache->data), 'Cached string object is an array.');
}
}
}
$cache = cache_get('i18n:string:tgroup:' . $textgroup . ':cache_multiple');
if ($this->assertNotEqual($cache, FALSE, 'Textgroup cache_multiple cache found')) {
foreach ($strings as $key => $string) {
$pattern = 'item:' . $key . ':*:es';
if ($this->assertTrue(isset($cache->data[$pattern]), format_string('Cached multiple cache for pattern %pattern found', array('%pattern_key' => $pattern)))) {
$property_pattern = 'item:' . $key . ':title';
if ($this->assertTrue(isset($cache->data[$pattern][$property_pattern]), format_string('Cached multiple property title found', array('%pattern_key' => $pattern)))) {
$this->assertEqual($cache->data[$pattern][$property_pattern], $property_pattern);
}
}
}
}
// Test cache injection.
foreach ($textgroup_object->strings as $context => $string_object) {
// Check if the string object cache is also available.
$string_cache = cache_get($string_object->get_cid());
if (isset($string_cache->data)) {
// Modify cache.
$string_cache->data['string'] = "Injected value.";
cache_set($string_object->get_cid(), $string_cache->data, 'cache', CACHE_TEMPORARY);
// Check if modification is reflected on the next page call.
$this->drupalGet('tests/i18n/i18n_string_build/' . $textgroup . ':' . $context);
$this->assertText($string_cache->data['string']);
}
}
// Test that un-translated strings are cached correctly.
$textgroup = 'test_cached';
$key = 3;
$string = self::randomName(100);
$name = "$textgroup:item:$key:title";
i18n_string_update($name, $string);
// Generate the cache entry.
$string_object = i18n_string_build($name, $string);
$langcode = i18n_langcode();
$string_object->get_translation($langcode);
// Destroy the textgroup object to write the cache entry.
$textgroup_object = i18n_string_textgroup($textgroup);
$textgroup_object->__destruct();
$this->assertTrue(cache_get($string_object->get_cid()) !== FALSE, "Cache entry created.");
drupal_static_reset('i18n_string_textgroup');
// Reset the loaded translation variable.
variable_del('i18n_loaded_translations');
$loaded_translations = variable_get('i18n_loaded_translations', array());
$this->verbose(var_export($loaded_translations, TRUE));
// Rebuild the string.
$string_object = i18n_string_build($name, $string);
$string_object->get_translation($langcode);
// Check that the string hasn't been loaded.
$loaded_translations = variable_get('i18n_loaded_translations', array());
$this->verbose(var_export($loaded_translations, TRUE));
$this->assertFalse(isset($loaded_translations['test_cached:item:3:title']), "The untranslated string was correctly cached.");
}
/**
* Create strings for all languages
*/
@@ -76,7 +198,7 @@ class i18nStringTestCase extends Drupali18nTestCase {
/**
* Create and store one translation into the db
*/
public static function stringCreateTranslation($name, $lang, $length = 20) {
public function stringCreateTranslation($name, $lang, $length = 20) {
$translation = $this->randomName($length);
if (self::stringSaveTranslation($name, $lang, $translation)) {
return $translation;
@@ -34,9 +34,32 @@ function i18n_string_variable_info($options = array()) {
'default' => 0,
'group' => 'debug',
);
$variables['i18n_string_textgroup_class_[textgroup]'] = array(
'title' => t('Class to use for the text group'),
'description' => t('Determines which the class will be use for string translation in the text group.', array(), $options),
'repeat' => array(
'type' => 'select',
'default' => 'i18n_string_textgroup_default',
'options callback' => 'i18n_string_variable_textgroup_class_list',
),
'submit callback' => 'i18n_string_variable_textgroup_class_submit_callback',
'group' => 'i18n',
);
return $variables;
}
/**
* Implements hook_variable_type_info().
*/
function i18n_string_variable_type_info() {
$type['textgroup'] = array(
'title' => t('Text group'),
'type' => 'select',
'options callback' => 'i18n_string_variable_textgroup_list',
);
return $type;
}
/**
* Options callback, format list
*/
@@ -54,4 +77,33 @@ function i18n_string_variable_format_list() {
*/
function i18n_string_variable_format_default() {
return array(filter_fallback_format());
}
}
/**
* Options callback, text groups list.
*/
function i18n_string_variable_textgroup_list() {
$groups = array();
foreach (i18n_string_group_info() as $name => $info) {
$groups[$name] = $info['title'];
}
return $groups;
}
/**
* Options callback, text group classes list.
*/
function i18n_string_variable_textgroup_class_list($variable, $options = array()) {
return array(
'i18n_string_textgroup_default' => t('Text group handler default.', array(), $options),
'i18n_string_textgroup_cached' => t('Text group handler which include persistent caching.', array(), $options),
);
}
/**
* Submit callback. Execute Reset the persistent caches after save the text group class variables.
*/
function i18n_string_variable_textgroup_class_submit_callback($variable, $options, $form, $form_state) {
// Reset the persistent caches.
cache_clear_all('i18n:string:' , 'cache', TRUE);
}
@@ -3,7 +3,7 @@ README.txt
==========
Drupal module: i18n_sync (Synchronization)
This module will handle content synchronization accross translations.
This module will handle content synchronization across translations.
The available list of fields to synchronize will include standard node fields and cck fields.
To have aditional fields, add the list in a variable in the settings.php file, like this:
@@ -1,5 +1,5 @@
name = Synchronize translations
description = Synchronizes taxonomy and fields accross translations of the same content.
description = Synchronizes taxonomy and fields across translations of the same content.
dependencies[] = i18n
dependencies[] = translation
package = Multilingual - Internationalization
@@ -10,9 +10,8 @@ files[] = i18n_sync.install
files[] = i18n_sync.module.inc
files[] = i18n_sync.node.inc
files[] = i18n_sync.test
; Information added by drupal.org packaging script on 2013-01-13
version = "7.x-1.8"
; Information added by Drupal.org packaging script on 2020-06-17
version = "7.x-1.27"
core = "7.x"
project = "i18n"
datestamp = "1358075001"
datestamp = "1592373390"
@@ -11,8 +11,6 @@
*
* Notes:
* This module needs to run after taxonomy, i18n, translation. Check module weight.
*
* @ TODO Test with CCK when possible, api may have changed.
*/
/**
@@ -35,7 +33,7 @@ function i18n_sync($status = NULL) {
function i18n_sync_help($path, $arg) {
switch ($path) {
case 'admin/help#i18n_sync' :
$output = '<p>' . t('This module synchronizes content taxonomy and fields accross translations:') . '</p>';
$output = '<p>' . t('This module synchronizes content taxonomy and fields across translations:') . '</p>';
$output .= '<p>' . t('First you need to select which fields should be synchronized. Then, after a node has been updated, all enabled vocabularies and fields will be synchronized as follows:') . '</p>';
$output .= '<ul>';
$output .= '<li>' . t('All the node fields selected for synchronization will be set to the same value for all translations.') . '</li>';
@@ -71,25 +69,6 @@ function i18n_sync_field_info_alter(&$fields) {
}
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function i18n_sync_form_node_admin_content_alter(&$form, &$form_state) {
if (!empty($form['operation']) && $form['operation']['#value'] == 'delete') {
$form['#submit'] = array_merge(array('i18n_sync_node_delete_submit'), $form['#submit']);
}
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function i18n_sync_form_node_delete_confirm_alter(&$form, &$form_state) {
// Intercept form submission so we can handle uploads, replace callback
$form['#submit'] = array_merge(array('i18n_sync_node_delete_submit'), $form['#submit']);
}
/**
* Implements hook_form_FORM_ID_alter().
*/
@@ -112,6 +91,7 @@ function i18n_sync_form_node_type_form_alter(&$form, &$form_state) {
$form['i18n_sync']['i18n_sync_node_type'] = array(
'#tree' => TRUE,
'#weight' => 1,
);
// Each set provides title and options. We build a big checkboxes control for it to be
@@ -142,42 +122,21 @@ function i18n_sync_form_node_type_form_alter(&$form, &$form_state) {
);
}
}
}
}
/**
* Submit callback for
* - node delete confirm
* - node multiple delete confirm
*/
function i18n_sync_node_delete_submit($form, $form_state) {
if ($form_state['values']['confirm']) {
if (!empty($form_state['values']['nid'])) {
// Single node
i18n_sync_node_delete_prepare($form_state['values']['nid']);
}
elseif (!empty($form_state['values']['nodes'])) {
// Multiple nodes
foreach ($form_state['values']['nodes'] as $nid => $value) {
i18n_sync_node_delete_prepare($nid);
}
}
}
// Then it will go through normal form submission
}
/**
* Prepare node for deletion, work out synchronization issues
*/
function i18n_sync_node_delete_prepare($nid) {
$node = node_load($nid);
// Delete file associations when files are shared with existing translations
// so they are not removed by upload module
if (!empty($node->tnid) && module_exists('upload')) {
$result = db_query('SELECT u.* FROM {upload} u WHERE u.nid = %d AND u.fid IN (SELECT t.fid FROM {upload} t WHERE t.fid = u.fid AND t.nid <> u.nid)', $nid);
while ($up = db_fetch_object($result)) {
db_query("DELETE FROM {upload} WHERE fid = %d AND vid = %d", $up->fid, $up->vid);
}
// Add option to restrict syncing only when editing the translation source.
$form['i18n_sync']['i18n_sync_source_description'] = array(
'#prefix' => '<div>', '#suffix' => '</div>',
'#markup' => t('Restrict synchronization to the translation source.'),
'#weight' => 2,
);
$form['i18n_sync']['i18n_sync_source'] = array(
'#type' => 'checkbox',
'#title' => t('Synchronize translations only when saving the translation source'),
'#default_value' => variable_get('i18n_sync_source_' . $type, FALSE),
'#disabled' => $disabled,
'#weight' => 3,
'#description' => t('If not checked each node will trigger the synchronization, whether it\'s the source or not.'),
);
}
}
@@ -224,9 +183,17 @@ function i18n_sync_node_insert($node) {
*/
function i18n_sync_node_update($node) {
// Let's go with field synchronization.
if (i18n_sync_node_check($node) && !empty($node->tnid) && ($fields = i18n_sync_node_fields($node->type)) && ($translations = i18n_sync_node_get_translations($node, TRUE))) {
module_load_include('node.inc', 'i18n_sync');
i18n_sync_node_translation($node, $translations, $fields, 'update');
if (i18n_sync_node_check($node) && !empty($node->tnid) && (!variable_get('i18n_sync_source_' . $node->type, TRUE) || $node->tnid == $node->nid) && ($fields = i18n_sync_node_fields($node->type)) && ($translations = i18n_sync_node_get_translations($node, TRUE))) {
$do_sync = TRUE;
if (module_exists('entity_translation')) {
if (entity_translation_enabled_bundle('node', $node->type)) {
$do_sync = FALSE;
}
}
if ($do_sync) {
module_load_include('node.inc', 'i18n_sync');
i18n_sync_node_translation($node, $translations, $fields, 'update');
}
}
}
@@ -27,6 +27,7 @@
* Node operation (insert|update).
*/
function i18n_sync_node_translation($node, $translations, $field_names, $op) {
$total = count($translations);
$count = 0;
// Disable language selection and synchronization temporarily, enable it again later
$i18n_select = i18n_select(FALSE);
@@ -34,10 +35,12 @@ function i18n_sync_node_translation($node, $translations, $field_names, $op) {
foreach ($translations as $translation) {
// If translation is the same node, we cannot synchronize with itself
if ($node->nid == $translation->nid) {
$total--;
continue;
}
// Load full node, we need all data here.
$translation = node_load($translation->nid, NULL, TRUE);
entity_get_controller('node')->resetCache(array($translation->nid));
$translation = node_load($translation->nid);
$i18n_options = i18n_sync_node_options($node->type);
// Invoke callback for each field, the default is just copy over
foreach ($field_names as $field) {
@@ -53,6 +56,13 @@ function i18n_sync_node_translation($node, $translations, $field_names, $op) {
module_invoke_all('i18n_sync_translation', 'node', $translation, $translation->language, $node, $node->language, $field_names);
node_save($translation);
$count++;
// Flush each entity from the load cache after processing, to
// avoid exceeding PHP memory limits. It should be safe to keep
// at least one, however; so we retain the final translation in
// the cache after saving it.
if ($count < $total) {
entity_get_controller('node')->resetCache(array($translation->nid));
}
}
i18n_sync(TRUE);
i18n_select($i18n_select);
@@ -88,7 +98,7 @@ function i18n_sync_node_translation_nodereference_field(&$node, &$translation, $
* Example:
* English A references English B and English C.
* English A and B are translated to German A and B, but English C is not.
* The syncronization from English A to German A would it German B and English C.
* The synchronization from English A to German A would it German B and English C.
*/
function i18n_sync_node_translation_reference_field(&$reference_node, $default_value, $langcode) {
if (isset($reference_node->tnid) && translation_supported_type($reference_node->type)) {
@@ -0,0 +1,103 @@
CONTENTS OF THIS FILE
---------------------
* Introduction
* Requirements
* Recommended modules
* Installation
* Configuration
* Maintainers
INTRODUCTION
------------
The Taxonomy translation module, part of the Internationalization
(https://www.drupal.org/project/i18n) package, provides multiple options to
translate taxonomy vocabularies and terms. For each vocabulary, there are four
types of behaviors to choose from: Language-independent terms, Language-specific
terms, Localized terms, and Mixed-language vocabulary.
* For a full description of the module visit:
https://www.drupal.org/node/1114016
* To submit bug reports and feature suggestions, or to track changes visit:
https://www.drupal.org/project/issues/i18n
REQUIREMENTS
------------
This module requires the following module:
* Internationalization - https://www.drupal.org/project/i18n
RECOMMENDED MODULES
-------------------
* Internationalization Views - https://www.drupal.org/project/i18nviews
* Language Icons - https://www.drupal.org/project/languageicons
* Translation Overview - https://www.drupal.org/project/translation_overview
* Localization Client - https://www.drupal.org/project/l10n_client
* Internationalization contributions -
https://www.drupal.org/project/i18n_contrib
INSTALLATION
------------
This is a submodule of the Internationalization module. Install the
Internationalization module as you would normally install a contributed Drupal
module. Visit https://www.drupal.org/node/895232 for further information.
CONFIGURATION
-------------
Language-independent terms - only vocabulary will be translatable.
1. Navigate to Structure > Taxonomy.
2. Select the "edit vocabulary" link.
3. Select the "No multilingual options for terms".
Language-specific terms - vocabulary is only used for content in that language.
The terms will only be available if the term language matches the UI language.
1. Navigate to Structure > Taxonomy and select the "edit vocabulary link".
2. Choose "Fixed Language" and a Language drop-down field will be displayed.
3. Select the language.
4. Select "Fixed Language" and Save.
Localized terms - Terms are common for all languages, but their name and
description may be localized.
1. Navigate to Structure > Taxonomy > vocabulary-to-edit > Edit.
2. Select "Localize" and select Save.
3. Edit a term and there will be a Translate tab. Select this tab.
4. Select Translate, translate the Name and Description, select
"Save translation", and repeat for all languages.
5. Repeat the process for all terms.
6. Navigate to Structure > Content types > term-to-edit > Manage display.
7. By default, the term reference is set to Link. Change this to "Link
(localized)" and Save.
The vocabulary will be appropriate for the language.
Mixed-language vocabulary - Use for vocabularies with terms in multiple
languages.
1. Navigate to Structure > Taxonomy > vocabulary-to-edit > Edit.
2. Select the Translate radio button and Save.
3. Edit a vocabulary term and there will be a new Language field. Choose a
language and then select Save and translate.
4. There are two options, the user can either select "Add translation link" or
the user can select an existing term in the Select translations form.
5. Create translations for the terms and add terms for specific languages only.
Now if the user edits a node associated with this vocabulary, only the relevant
terms will appear.
MAINTAINERS
-----------
* Jose Reyero - https://www.drupal.org/u/jose-reyero
* Florian Weber (webflo) - https://www.drupal.org/u/webflo
* Peter Philipp - https://www.drupal.org/u/das-peter
* Joseph Olstad - https://www.drupal.org/u/joseph.olstad
* Nathaniel Catchpole - https://www.drupal.org/u/catch
@@ -48,7 +48,7 @@ function i18n_taxonomy_translation_term_form($form, $form_state, $vocabulary, $t
$form['translations'][$lang] = array(
'#title' => $langname,
'#type' => 'item',
'#markup' => $source->name,
'#markup' => check_plain($source->name),
'#langcode' => $lang,
);
}
@@ -199,7 +199,14 @@ function i18n_taxonomy_translation_term_overview($term) {
$path = 'taxonomy/term/' . $translation_term->tid;
$title = l($translation_term->name, $path);
$options[] = l(t('edit'), $path . '/edit');
$options['edit'] = array(
'#type' => 'link',
'#title' => t('edit'),
'#href' => $path . '/edit',
'#options' => array(
'query' => drupal_get_destination(),
),
);
if ($translation_term->tid == $i18n_tsid) {
$language_name = t('<strong>@language_name</strong> (source)', array('@language_name' => $language_name));
@@ -208,14 +215,25 @@ function i18n_taxonomy_translation_term_overview($term) {
else {
// No such translation in the set yet: help user to create it.
$title = t('n/a');
$options[] = l(t('add translation'), 'admin/structure/taxonomy/' . $term->vocabulary_machine_name . '/add', array('query' => array('translation' => $term->tid, 'target' => $langcode) + drupal_get_destination()));
$options['add'] = array(
'#type' => 'link',
'#title' => t('add translation'),
'#href' => 'admin/structure/taxonomy/' . $term->vocabulary_machine_name . '/add',
'#options' => array(
'query' => array('translation' => $term->tid, 'target' => $langcode) + drupal_get_destination()
),
);
}
$rows[] = array($language_name, $title, implode(" | ", $options));
$rows[$langcode] = array(
'language' => $language_name,
'title' => $title,
'operations' => array('data' => $options),
);
}
drupal_set_title(t('Translations of term %title', array('%title' => $term->name)), PASS_THROUGH);
$build['translation_node_overview'] = array(
$build['translation_overview'] = array(
'#theme' => 'table',
'#header' => $header,
'#rows' => $rows,
@@ -11,9 +11,8 @@ files[] = i18n_taxonomy.pages.inc
files[] = i18n_taxonomy.admin.inc
files[] = i18n_taxonomy.test
; Information added by drupal.org packaging script on 2013-01-13
version = "7.x-1.8"
; Information added by Drupal.org packaging script on 2020-06-17
version = "7.x-1.27"
core = "7.x"
project = "i18n"
datestamp = "1358075001"
datestamp = "1592373390"
@@ -12,7 +12,7 @@
*/
function i18n_taxonomy_install() {
module_load_install('i18n');
i18n_install_create_fields('taxonomy_vocabulary', array('language', 'i18n_mode'));
i18n_install_create_fields('taxonomy_vocabulary', array('language', 'i18n_mode'), TRUE);
i18n_install_create_fields('taxonomy_term_data', array('language', 'i18n_tsid'));
// Set module weight for it to run after core modules, but before views.
db_query("UPDATE {system} SET weight = 5 WHERE name = 'i18n_taxonomy' AND type = 'module'");
@@ -184,11 +184,11 @@ function i18n_taxonomy_field_formatter_info() {
}
/**
- * Implements hook_field_formatter_prepare_view().
- *
- * This preloads all taxonomy terms for multiple loaded objects at once and
- * unsets values for invalid terms that do not exist.
- */
* Implements hook_field_formatter_prepare_view().
*
* This preloads all taxonomy terms for multiple loaded objects at once and
* unsets values for invalid terms that do not exist.
*/
function i18n_taxonomy_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {
return taxonomy_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, $items, $displays);
}
@@ -213,7 +213,12 @@ function i18n_taxonomy_field_formatter_view($entity_type, $entity, $field, $inst
);
}
else {
$term = $item['taxonomy_term'];
if (isset($item['taxonomy_term'])) {
$term = $item['taxonomy_term'];
}
else {
$term = taxonomy_term_load($item['tid']);
}
$uri = entity_uri('taxonomy_term', $term);
$element[$delta] = array(
'#type' => 'link',
@@ -318,7 +323,7 @@ function i18n_taxonomy_field_storage_details_alter(&$details, &$field) {
/**
* Implements hook_field_attach_prepare_translation_alter().
*
* Prepare and synchronize translation for term reference fields.
*/
function i18n_taxonomy_field_attach_prepare_translation_alter(&$entity, $context) {
@@ -372,10 +377,19 @@ function i18n_taxonomy_field_prepare_translation($entity_type, $entity, $field,
* The array of valid terms for this field, keyed by term id.
*/
function i18n_taxonomy_allowed_values($field) {
global $language;
$options = array();
foreach ($field['settings']['allowed_values'] as $tree) {
if ($vocabulary = taxonomy_vocabulary_machine_name_load($tree['vocabulary'])) {
if ($terms = taxonomy_get_tree($vocabulary->vid, $tree['parent'])) {
if (i18n_taxonomy_vocabulary_mode($vocabulary->vid) == I18N_MODE_TRANSLATE) {
$parent = i18n_taxonomy_translation_term_tid($tree['parent'], NULL, $tree['parent']);
$language = i18n_language_context();
$terms = i18n_taxonomy_get_tree($vocabulary->vid, $language->language, $parent);
}
else {
$terms = taxonomy_get_tree($vocabulary->vid, $tree['parent']);
}
if ($terms) {
foreach ($terms as $term) {
$options[$term->tid] = str_repeat('-', $term->depth) . i18n_taxonomy_term_name($term);
}
@@ -452,23 +466,13 @@ function i18n_taxonomy_translate_path($path, $path_prefix = 'taxonomy/term/') {
return $links;
}
}
/**
* Implements hook_theme().
*/
function i18n_taxonomy_theme() {
return array(
'i18n_taxonomy_term_page' => array(
'arguments' => array('tids' => array(), 'result' => NULL),
'file' => 'i18n_taxonomy.pages.inc',
),
);
}
/**
* Get localized term name unfiltered.
*/
function i18n_taxonomy_term_name($term, $langcode = NULL) {
return i18n_taxonomy_vocabulary_mode($term->vid, I18N_MODE_LOCALIZE) ? i18n_string(array('taxonomy', 'term', $term->tid, 'name'), $term->name, array('langcode' => $langcode, 'sanitize' => FALSE)) : $term->name;
$key = i18n_object_info('taxonomy_term', 'key');
return i18n_taxonomy_vocabulary_mode($term->vid, I18N_MODE_LOCALIZE) ? i18n_string(array('taxonomy', 'term', $term->{$key}, 'name'), $term->name, array('langcode' => $langcode, 'sanitize' => FALSE)) : $term->name;
}
@@ -476,7 +480,8 @@ function i18n_taxonomy_term_name($term, $langcode = NULL) {
* Get localized term description unfiltered.
*/
function i18n_taxonomy_term_description($term, $langcode = NULL) {
return i18n_taxonomy_vocabulary_mode($term->vid, I18N_MODE_LOCALIZE) ? i18n_string(array('taxonomy', 'term', $term->tid, 'description'), $term->description, array('langcode' => $langcode, 'sanitize' => FALSE)) : $term->description;
$key = i18n_object_info('taxonomy_term', 'key');
return i18n_taxonomy_vocabulary_mode($term->vid, I18N_MODE_LOCALIZE) ? i18n_string(array('taxonomy', 'term', $term->{$key}, 'description'), $term->description, array('langcode' => $langcode, 'sanitize' => FALSE)) : $term->description;
}
/**
@@ -751,6 +756,28 @@ function i18n_taxonomy_form_taxonomy_form_term_alter(&$form, &$form_state) {
// Add language field or not depending on taxonomy mode.
switch (i18n_taxonomy_vocabulary_mode($vocabulary->vid)) {
case I18N_MODE_TRANSLATE:
// Set $form_state['storage'] default as empty array because we will add
// the translation and target from $_GET. So we still have it when the
// page partially reloads with ajax.
if(!isset($form_state['storage'])) {
$form_state['storage'] = array();
}
// get translation from $_GET or $form_state['storage']
$translation = null;
if(isset($_GET['translation'])) {
$translation = $_GET['translation'];
$form_state['storage']['translation'] = $translation;
} else if(isset($form_state['storage']) && isset($form_state['storage']['translation'])){
$translation = $form_state['storage']['translation'];
}
// get target from $_GET or $form_state['storage']
$target = null;
if(isset($_GET['target'])) {
$target = $_GET['target'];
$form_state['storage']['target'] = $target;
} else if(isset($form_state['storage']) && isset($form_state['storage']['target'])){
$target = $form_state['storage']['target'];
}
$form['language'] = array(
'#description' => t('This term belongs to a multilingual vocabulary. You can set a language for it.'),
) + i18n_element_language_select($term);
@@ -758,7 +785,7 @@ function i18n_taxonomy_form_taxonomy_form_term_alter(&$form, &$form_state) {
// If the term to be added will be a translation of a source term,
// set the default value of the option list to the target language and
// create a form element for storing the translation set of the source term.
if (empty($term->tid) && isset($_GET['translation']) && isset($_GET['target']) && ($source_term = taxonomy_term_load($_GET['translation'])) && ($target_language = i18n_language_object($_GET['target']))) {
if (empty($term->tid) && isset($translation) && isset($target) && ($source_term = taxonomy_term_load($translation)) && ($target_language = i18n_language_object($target))) {
// Set context language to target language.
i18n_language_context($target_language);
@@ -1000,7 +1027,7 @@ function i18n_taxonomy_translate_terms($taxonomy, $langcode, $fullterms = TRUE)
}
elseif ($term->language && $term->language != $langcode) {
$translation_set = i18n_translation_set_load($term->i18n_tsid);
$translations = $translation_set->get_translations();
$translations = ($translation_set) ? $translation_set->get_translations() : NULL;
if ($translations && !empty($translations[$langcode])) {
$newterm = $translations[$langcode];
@@ -1029,6 +1056,10 @@ function i18n_taxonomy_localize_terms($terms) {
if (!i18n_string_translate_langcode()) {
return $terms;
}
// $terms is not a valid array or term.
if (empty($terms)) {
return $terms;
}
$object_info = i18n_object_info('taxonomy_term');
$list = is_array($terms) ? $terms : array($terms);
foreach ($list as $index => $term) {
@@ -1096,7 +1127,7 @@ function i18n_taxonomy_get_tree($vid, $langcode, $parent = 0, $max_depth = NULL,
$query->join('taxonomy_term_hierarchy', 'h', 'h.tid = t.tid');
$result = $query
->addTag('translatable')
->addTag('term_access')
->addTag('taxonomy_term_access')
->fields('t')
->fields('h', array('parent'))
->condition('t.vid', $vid)
@@ -1225,10 +1256,18 @@ function i18n_taxonomy_field_uuid_presave($entity_type, $entity, $field, $instan
* Implements hook_entity_info_alter().
*/
function i18n_taxonomy_entity_info_alter(&$entity_info) {
if (isset($entity_info['taxonomy_vocabulary'])) {
// Add altered vocabulary schema fields.
$entity_info['taxonomy_vocabulary']['schema_fields_sql']['base table'][] = 'i18n_mode';
$entity_info['taxonomy_vocabulary']['schema_fields_sql']['base table'][] = 'language';
}
if (isset($entity_info['taxonomy_term'])) {
// Core doesn't provide a label callback for taxonomy terms. By setting one
// we can use it to return the correct localized term name.
$entity_info['taxonomy_term']['label callback'] = 'i18n_taxonomy_taxonomy_term_label';
// Also let core know terms have languages, now.
$entity_info['taxonomy_term']['entity keys']['language'] = 'language';
}
}
@@ -1260,10 +1299,29 @@ function i18n_taxonomy_modules_enabled($modules) {
$modules = drupal_map_assoc($modules);
if (isset($modules['i18n_taxonomy'])) {
foreach (field_info_fields() as $fieldname => $field) {
if ($field['type'] == 'taxonomy_term_reference') {
$field['settings']['options_list_callback'] = 'i18n_taxonomy_allowed_values';
field_update_field($field);
}
if ($field['type'] == 'taxonomy_term_reference') {
$field['settings']['options_list_callback'] = 'i18n_taxonomy_allowed_values';
field_update_field($field);
}
}
}
}
/**
* Implements hook_views_pre_render().
*/
function i18n_taxonomy_views_pre_render(&$view) {
if($view->base_table !== 'rules_scheduler') {
global $language;
foreach ($view->result as $delta => $term){
if (isset($term->tid)) {
i18n_string_translate_langcode($language->language);
$localized_term = i18n_taxonomy_localize_terms(taxonomy_term_load($term->tid));
$term->tid = $localized_term->tid;
$term->taxonomy_term_data_name = $localized_term->name;
$term->taxonomy_term_data_description = $localized_term->description;
}
}
}
}
@@ -62,37 +62,6 @@ function i18n_taxonomy_term_page($term) {
return $build;
}
/**
* Render a taxonomy term page HTML output.
*
* @param $tids
* An array of term ids.
* @param $result
* A pager_query() result, such as that performed by taxonomy_select_nodes().
*
* @ingroup themeable
*/
function theme_i18n_taxonomy_term_page($tids, $result) {
drupal_add_css(drupal_get_path('module', 'taxonomy') . '/taxonomy.css');
$output = '';
// Only display the description if we have a single term, to avoid clutter and confusion.
if (count($tids) == 1) {
$term = i18n_taxonomy_localize_terms(taxonomy_term_load($tids[0]));
// Check that a description is set.
if (!empty($term->description)) {
$output .= '<div class="taxonomy-term-description">';
$output .= filter_xss_admin($term->description);
$output .= '</div>';
}
}
$output .= taxonomy_render_nodes($result);
return $output;
}
/**
* Helper function for autocompletion. Replacement for taxonomy_autocomplete
*/
@@ -131,7 +100,7 @@ function _i18n_taxonomy_autocomplete($langcode, $vids, $tags_typed = '') {
$query = db_select('taxonomy_term_data', 't')
->fields('t', array('tid', 'name'));
$query->addTag('translatable');
$query->addTag('term_access');
$query->addTag('taxonomy_term_access');
// Disable i18n_select for this query
$query->addTag('i18n_select');
// Add language condition
@@ -15,6 +15,10 @@ function i18n_taxonomy_token_info() {
'name' => t("Name (localized)"),
'description' => t("The name of the taxonomy term."),
);
$term['localized-name'] = array(
'name' => t("Name in current language"),
'description' => t("The name of the taxonomy term in current language."),
);
$term['i18n-description'] = array(
'name' => t("Description (localized)"),
'description' => t("The optional description of the taxonomy term."),
@@ -69,6 +73,12 @@ function i18n_taxonomy_tokens($type, $tokens, array $data = array(), array $opti
$replacements[$original] = $sanitize ? check_plain($name) : $name;
break;
case 'localized-name':
$translated_term = i18n_taxonomy_term_get_translation($term, $langcode);
$name = i18n_taxonomy_term_name($translated_term, $langcode);
$replacements[$original] = $sanitize ? check_plain($name) : $name;
break;
case 'i18n-description':
$replacements[$original] = i18n_string_text(array('taxonomy', 'term', $term->tid, 'description'), $term->description, array('langcode' => $langcode, 'format' => $term->format, 'sanitize' => $sanitize, 'cache' => TRUE));
break;
@@ -6,9 +6,8 @@ core = 7.x
files[] = i18n_translation.inc
; Information added by drupal.org packaging script on 2013-01-13
version = "7.x-1.8"
; Information added by Drupal.org packaging script on 2020-06-17
version = "7.x-1.27"
core = "7.x"
project = "i18n"
datestamp = "1358075001"
datestamp = "1592373390"
@@ -20,7 +20,7 @@ class I18nTranslationSetController extends DrupalDefaultEntityController {
* @param $queried_entities
* Associative array of query results, keyed on the entity ID.
* @param $revision_id
* ID of the revision that was loaded, or FALSE if teh most current revision
* ID of the revision that was loaded, or FALSE if the most current revision
* was loaded.
*/
protected function attachLoad(&$queried_entities, $revision_id = FALSE) {
@@ -0,0 +1,70 @@
CONTENTS OF THIS FILE
---------------------
* Introduction
* Requirements
* Recommended modules
* Installation
* Configuration
* Maintainers
INTRODUCTION
------------
The User mail translation module, part of the Internationalization
(https://www.drupal.org/project/i18n) package, translates emails sent from the
User module.
* For a full description of the module, visit this page:
https://www.drupal.org/node/133977.
* To submit bug reports and feature suggestions, or to track changes:
https://www.drupal.org/project/issues/i18n.
REQUIREMENTS
------------
This module requires the following module:
* Internationalization - https://www.drupal.org/project/i18n
RECOMMENDED MODULES
-------------------
* Internationalization Views - https://www.drupal.org/project/i18nviews
* Language Icons - https://www.drupal.org/project/languageicons
* Translation Overview - https://www.drupal.org/project/translation_overview
* Localization Client - https://www.drupal.org/project/l10n_client
* Internationalization contributions -
https://www.drupal.org/project/i18n_contrib
INSTALLATION
------------
This is a submodule of the Internationalization module. Install the
Internationalization module as you would normally install a contributed Drupal
module. Visit https://www.drupal.org/node/895232 for further information.
CONFIGURATION
-------------
To configure email translations
1. Navigate to Administration > Configuration > Regional and language >
Multilingual settings and select the variables tab.
2. Select the "User emails" tab. Select the variables to be translated. Save
configuration.
MAINTAINERS
-----------
* Jose Reyero - https://www.drupal.org/u/jose-reyero
* Florian Weber (webflo) - https://www.drupal.org/u/webflo
* Peter Philipp - https://www.drupal.org/u/das-peter
* Joseph Olstad - https://www.drupal.org/u/joseph.olstad
* Nathaniel Catchpole - https://www.drupal.org/u/catch
@@ -4,9 +4,8 @@ core = 7.x
package = Multilingual - Internationalization
dependencies[] = i18n_variable
; Information added by drupal.org packaging script on 2013-01-13
version = "7.x-1.8"
; Information added by Drupal.org packaging script on 2020-06-17
version = "7.x-1.27"
core = "7.x"
project = "i18n"
datestamp = "1358075001"
datestamp = "1592373390"
@@ -9,7 +9,14 @@
*/
function i18n_user_mail_alter(&$message) {
if ($message['module'] == 'user') {
$language = $message['language'];
$message['language'] = language_default();
if (isset($message['params']['account'])) {
$user_preferred = user_preferred_language($message['params']['account']);
if (isset($user_preferred)) {
$message['language'] = $user_preferred;
}
}
$language = (isset($message['language']) ? $message['language'] : language_default());
$variables = array('user' => $message['params']['account']);
$key = $message['key'];
@@ -54,7 +61,7 @@ function i18n_user_user_mail_tokens(&$replacements, $data, $options) {
*/
function i18n_user_user_pass_reset_url($account) {
$timestamp = REQUEST_TIME;
return url("user/reset/$account->uid/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login), array('absolute' => TRUE, 'language' => i18n_language($account->language)));
return url("user/reset/$account->uid/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid), array('absolute' => TRUE, 'language' => i18n_language($account->language)));
}
/**
@@ -65,5 +72,5 @@ function i18n_user_user_pass_reset_url($account) {
*/
function i18n_user_user_cancel_url($account) {
$timestamp = REQUEST_TIME;
return url("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login), array('absolute' => TRUE, 'language' => i18n_language($account->language)));
return url("user/$account->uid/cancel/confirm/$timestamp/" . user_pass_rehash($account->pass, $timestamp, $account->login, $account->uid), array('absolute' => TRUE, 'language' => i18n_language($account->language)));
}
@@ -0,0 +1,84 @@
CONTENTS OF THIS FILE
---------------------
* Introduction
* Requirements
* Recommended modules
* Installation
* Configuration
* Maintainers
INTRODUCTION
------------
The Variable translation module, part of the Internationalization
(https://www.drupal.org/project/i18n) package, allows the user to translate text
and settings that are stored in Drupal as variables. These variables include
text such as 'site name' and 'site slogan', as well as settings like 'Default
front page' and 'Default 404 page'.
* For a full description of the module, visit:
https://www.drupal.org/node/1113374.
* To submit bug reports and feature suggestions, or to track changes visit:
https://www.drupal.org/project/issues/i18n.
REQUIREMENTS
------------
This module requires the following modules:
* Internationalization - https://www.drupal.org/project/i18n
* Variable - https://www.drupal.org/project/variable
RECOMMENDED MODULES
-------------------
* Internationalization Views - https://www.drupal.org/project/i18nviews
* Language Icons - https://www.drupal.org/project/languageicons
* Translation Overview - https://www.drupal.org/project/translation_overview
* Localization Client - https://www.drupal.org/project/l10n_client
* Internationalization contributions -
https://www.drupal.org/project/i18n_contrib
INSTALLATION
------------
This is a submodule of the Internationalization module. Install the
Internationalization module as you would normally install a contributed Drupal
module. Visit https://www.drupal.org/node/895232 for further information.
CONFIGURATION
-------------
To enable multilingual variables
1. Enable the Variable translation module included with the Internationalization
package.
2. Go to Administration > Configuration > Regional and language > Multilingual
settings.
3. Select on the Variables tab.
4. Select the variables that will be multilingual.
5. Save configuration.
Once the user has the correct settings, they'll be marked with "This is a
multilingual variable" when the user navigates to the corresponding
administration pages. The user must switch the site language while in the
administration pages to set the variables for each language. A language switcher
link will appear at the top of each administrative page that has multilingual
variables.
MAINTAINERS
-----------
* Jose Reyero - https://www.drupal.org/u/jose-reyero
* Florian Weber (webflo) - https://www.drupal.org/u/webflo
* Peter Philipp - https://www.drupal.org/u/das-peter
* Joseph Olstad - https://www.drupal.org/u/joseph.olstad
* Nathaniel Catchpole - https://www.drupal.org/u/catch
@@ -10,9 +10,8 @@ configure = admin/config/regional/i18n/variable
files[] = i18n_variable.class.inc
files[] = i18n_variable.test
; Information added by drupal.org packaging script on 2013-01-13
version = "7.x-1.8"
; Information added by Drupal.org packaging script on 2020-06-17
version = "7.x-1.27"
core = "7.x"
project = "i18n"
datestamp = "1358075001"
datestamp = "1592373390"
@@ -7,9 +7,8 @@ package = Testing
core = 6.x
hidden = TRUE
; Information added by drupal.org packaging script on 2013-01-13
version = "7.x-1.8"
; Information added by Drupal.org packaging script on 2020-06-17
version = "7.x-1.27"
core = "7.x"
project = "i18n"
datestamp = "1358075001"
datestamp = "1592373390"
@@ -36,6 +36,12 @@ function i18n_test_i18n_string_info() {
'format' => FALSE, // This group doesn't have strings with format
'refresh callback' => 'i18n_test_i18n_string_refresh',
);
$groups['test_cached'] = array(
'title' => t('Test Cached Strings'),
'description' => t('Translatable items of a textgroup with caching enabled.'),
'format' => FALSE, // This group doesn't have strings with format
'class' => variable_get('i18n_string_textgroup_class_test_cached', 'i18n_string_textgroup_cached_logged'),
);
return $groups;
}
/**
@@ -43,4 +49,53 @@ function i18n_test_i18n_string_info() {
*/
function i18n_test_i18n_string_refresh() {
return TRUE;
}
/**
* Implements hook_menu().
*/
function i18n_test_menu() {
// Required for the i18n_string caching tests.
$items['tests/i18n/i18n_string_build/%'] = array(
'title' => 'Load string',
'access callback' => TRUE,
'page callback' => 'i18n_string_build',
'page arguments' => array(3),
'type' => MENU_CALLBACK,
'delivery callback' => 'drupal_json_output',
);
$items['tests/i18n/i18n_string_build/%/%'] = array(
'title' => 'Load string',
'access callback' => TRUE,
'page callback' => 'i18n_string_build',
'page arguments' => array(3, 4),
'type' => MENU_CALLBACK,
'delivery callback' => 'drupal_json_output',
);
$items['tests/i18n/i18n_string_translation_search/%'] = array(
'title' => 'Search string translations',
'access callback' => TRUE,
'page callback' => 'i18n_string_translation_search',
'page arguments' => array(3),
'type' => MENU_CALLBACK,
'delivery callback' => 'drupal_json_output',
);
$items['tests/i18n/i18n_string_translation_search/%/%'] = array(
'title' => 'Search string translations',
'access callback' => TRUE,
'page callback' => 'i18n_string_translation_search',
'page arguments' => array(3, 4),
'type' => MENU_CALLBACK,
'delivery callback' => 'drupal_json_output',
);
return $items;
}
class i18n_string_textgroup_cached_logged extends i18n_string_textgroup_cached {
public static function load_translation($i18nstring, $langcode) {
$strings = variable_get('i18n_loaded_translations', array());
$strings[$i18nstring->get_name()] = true;
variable_set('i18n_loaded_translations', $strings);
parent::load_translation($i18nstring, $langcode);
}
}