first import

This commit is contained in:
Bachir Soussi Chiadmi
2015-04-08 11:40:19 +02:00
commit 1bc61b12ad
8435 changed files with 1582817 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
<?php
/**
* @file
* Feature integration
*/
/**
* Implements hook_features_pipe_node_alter().
*/
function i18n_node_features_pipe_node_alter(&$pipe, $data, $export) {
if (!empty($data) && module_exists('variable')) {
variable_include();
foreach (variable_list_module('i18n_node') as $variable) {
if (isset($variable['multiple']) && $variable['multiple'] === 'node_type') {
$children = variable_build($variable['name']);
if (!empty($children['children'])) {
foreach ($children['children'] as $child_variable) {
if (in_array($child_variable['index'], $data)) {
$pipe['variable'][] = $child_variable['name'];
}
}
}
}
}
}
}

View File

@@ -0,0 +1,50 @@
<?php
/**
* @file
* Internationalization (i18n) hooks
*/
/**
* Implements hook_i18n_object_info().
*/
function i18n_node_i18n_object_info() {
$info['node_type'] = array(
'title' => t('Node type'),
'key' => 'type',
'load callback' => 'node_type_get_type',
'placeholders' => array(
'%node_type' => 'type',
),
'edit path' => 'admin/structure/types/manage/%node_type',
'translate tab' => 'admin/structure/types/manage/%node_type/translate',
// We can easily list all these objects
'list callback' => 'node_type_get_types',
// Metadata for string translation
'string translation' => array(
'textgroup' => 'node',
'type' => 'type',
'properties' => array(
'name' => t('Name'),
'title_label' => t('Title label'),
'description' => t('Description'),
'help' => t('Help text'),
),
'translate path' => 'admin/structure/types/manage/%node_type/translate/%i18n_language',
)
);
return $info;
}
/**
* Implements hook_i18n_string_info()
*/
function i18n_node_i18n_string_info() {
$groups['node'] = array(
'title' => t('Node types'),
'description' => t('Content type names, descriptions, help texts.'),
//'format' => TRUE, // This group has strings with format (block body)
'list' => TRUE, // This group can list all strings
);
return $groups;
}

View File

@@ -0,0 +1,17 @@
name = Multilingual content
description = Extended node options for multilingual content
dependencies[] = translation
dependencies[] = i18n
dependencies[] = i18n_string
package = Multilingual - Internationalization
core = 7.x
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"
core = "7.x"
project = "i18n"
datestamp = "1358075001"

View File

@@ -0,0 +1,64 @@
<?php
/**
* @file
* Installation file for Internationalization (i18n) module.
*/
/**
* Implements hook_install().
*/
function i18n_node_install() {
// If updating from D6, module changed name
if (variable_get('i18n_drupal6_update')) {
i18n_node_update_7000();
}
}
/**
* Implements hook_uninstall().
*/
function i18n_node_uninstall() {
variable_del('i18n_hide_translation_links');
variable_del('i18n_selection_mode');
foreach (array_keys(node_type_get_types()) as $type) {
variable_del('i18n_node_' . $type);
}
}
/**
* Implements hook_update_dependencies()
*/
function i18n_node_update_dependencies() {
$dependencies['i18n_node'][7000] = array(
'i18n_string' => 7001,
);
return $dependencies;
}
/**
* Implements hook_i18n_update_drupal6().
*
* Update old string names
*/
function i18n_node_update_7000() {
// @todo Update from D6 i18n
// Variables:
// i18n_newnode_current, i18n_required_node, i18n_lock_node => i18n_node_options_[node_type]
// i18n_node => 'i18n_node_extended_[node_type]'
// Update string names
// - nodetype:type:[type]:[property] -> node:type:[type]:[property]
// - Property names: title -> title_label
module_load_install('i18n_string');
i18n_string_install_update_context('nodetype:type:*:*', 'node:type:*:*');
i18n_string_install_update_context('node:type:*:title', 'node:type:*:title_label');
}
/**
* Delete obsoleted variable for switch interface for translating.
*/
/*
function i18n_node_update_7001() {
variable_del('i18n_node_translation_switch');
}
*/

View File

@@ -0,0 +1,20 @@
(function ($) {
/**
* Rewrite autocomplete inputs to pass the language of the node currently being
* edited in the path.
*
* This functionality ensures node autocompletes get suggestions for the node's
* language rather than the current interface language.
*/
Drupal.behaviors.i18n = {
attach : function (context) {
if (Drupal.settings && Drupal.settings.i18n) {
$('form[id^=node-form]', context).find('input.autocomplete[value^=' + Drupal.settings.i18n.interface_path + ']').each(function () {
$(this).val($(this).val().replace(Drupal.settings.i18n.interface_path, Drupal.settings.i18n.content_path));
});
}
}
};
})(jQuery);

View File

@@ -0,0 +1,572 @@
<?php
/**
* @file
* Internationalization (i18n) module - Node type handling
*
* The i18n strings created by this module are:
* - node:type:[type]:[name,title,body,help]
*/
/**
* Implements hook_field_extra_fields().
*/
function i18n_node_field_extra_fields() {
$return = array();
$info = entity_get_info('node');
foreach (array_keys($info['bundles']) as $bundle) {
if (i18n_node_type_enabled($bundle)) {
$return['node'][$bundle] = i18n_language_field_extra();
}
}
return $return;
}
/**
* Implements hook_menu().
*/
function i18n_node_menu() {
$items['admin/config/regional/i18n/node'] = array(
'title' => 'Node options',
'description' => 'Configure extended options for multilingual content and translations.',
'page callback' => 'drupal_get_form',
'page arguments' => array('variable_group_form', 'i18n_node'),
'access arguments' => array('administer site configuration'),
'type' => MENU_LOCAL_TASK,
'weight' => 10,
);
$items['i18n/node/autocomplete'] = array(
'page callback' => 'i18n_node_autocomplete',
'file' => 'i18n_node.pages.inc',
'access arguments' => array('administer content translations'),
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Implements hook_block_view_MODULE_DELTA_alter().
*
* Set translated help text for node/add pages, replace node_help() text.
*/
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');
if ($help !== $type->help) {
$block['content'] = str_replace(filter_xss_admin($type->help), filter_xss_admin($help), $block['content']);
}
}
}
elseif ($arg[0] == 'node' && $arg[2] == 'edit') {
$node = menu_get_object();
if ($node && isset($node->type)) {
$type = node_type_get_type($node->type);
$help = i18n_node_translate_type($type, 'help');
if ($help !== $type->help) {
$block['content'] = str_replace(filter_xss_admin($type->help), filter_xss_admin($help), $block['content']);
}
}
}
}
/**
* Implements hook_help().
*/
function i18n_node_help($path, $arg) {
switch ($path) {
case 'admin/help#i18n_node':
$output = '<p>' . t('Provides some extended multilingual options for nodes.') . '</p>';
$output .= '<p>' . t('Additionally, if <em>String translation</em> enabled, this module will localize all content type configuration texts.') . '</p>';
$output .= '<ul>';
$output .= '<li>' . t('Content type names') . '</li>';
$output .= '<li>' . t('Submission guidelines') . '</li>';
$output .= '<li>' . t("Content type descriptions were previously localized so they won't be affected.") . '</li>';
$output .= '</ul>';
$output .= '<p>' . t('To search and translate strings, use the <a href="@translate-interface">translation interface</a> pages.', array('@translate-interface' => url('admin/config/regional/translate'))) . '</p>';
return $output;
case 'admin/config/regional/i18n':
case 'admin/config/regional/i18n/node':
$output = '<p>' . t('You can find some more per content type options on the <a href="@configure_content">Content types administration page</a>.', array('@configure_content' => url('admin/structure/types'))) . '</p>';
return $output;
}
}
/**
* Implements hook_i18n_context_language().
*/
function i18n_node_i18n_context_language() {
// Node language when loading specific nodes or creating translations.
if (arg(0) == 'node' ) {
if (($node = menu_get_object('node')) && !empty($node->language) && i18n_node_type_enabled($node)) {
return i18n_language_object($node->language);
}
elseif (arg(1) == 'add' && !empty($_GET['translation']) && !empty($_GET['target']) && ($source = node_load($_GET['translation'])) && i18n_node_type_enabled($source)) {
return i18n_language_object($_GET['target']);
}
}
}
/**
* Implements hook_i18n_translate_path()
*/
function i18n_node_i18n_translate_path($path) {
if (preg_match("!^node/(\d+)(/.+|)!", $path, $matches) && ($node = node_load((int) $matches[1])) && i18n_object_langcode($node) && !empty($node->tnid)) {
if ($translations = translation_node_get_translations($node->tnid)) {
$result = array();
foreach ($translations as $langcode => $node_translation) {
$result[$langcode] = array(
'href' => 'node/' . $node_translation->nid . $matches[2],
'title' => $node_translation->title,
'object' => $node_translation,
);
}
return $result;
}
}
}
/**
* Implements hook_menu_alter().
*
* Take over the node translation page.
*/
function i18n_node_menu_alter(&$items) {
if (isset($items['node/%node/translate'])) {
$items['node/%node/translate']['page callback'] = 'i18n_node_translation_overview';
$items['node/%node/translate']['file'] = 'i18n_node.pages.inc';
$items['node/%node/translate']['module'] = 'i18n_node';
}
// Take over node/add pages for string translation
$items['node/add']['page callback'] = 'i18n_node_add_page';
$items['node/add']['file'] = 'i18n_node.pages.inc';
$items['node/add']['file path'] = drupal_get_path('module', 'i18n_node');
// @TODO avoid iterating over every router path.
foreach (node_type_get_types() as $type) {
$path = 'node/add/' . str_replace('_', '-', $type->type);
if (isset($items[$path])) {
$items[$path]['title callback'] = 'i18n_node_type_name';
$items[$path]['title arguments'] = array($type->type, $type->name);
}
}
}
/**
* Get node language.
*/
function i18n_node_get_lang($nid, $default = '') {
$lang = db_query('SELECT language FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetchField();
return $lang ? $lang : $default ;
}
/**
* Get allowed languages for node.
*
* This allows node types to define its own language list implementing hook 'language_list'.
*
* @param $node
* Node to retrieve language list for.
* @param $translate
* Only languages available for translation.
* @param $select
* Only languages that can be selected for this node
*/
function i18n_node_language_list($node, $translate = FALSE, $select = FALSE) {
// Check if the node module manages its own language list.
$languages = node_invoke($node, 'language_list', $translate);
if (!$languages) {
$languages = i18n_language_list('name', i18n_node_language_mode($node));
if ($translate && isset($node->tnid) && $node->tnid && ($translations = translation_node_get_translations($node->tnid))) {
unset($translations[$node->language]);
foreach (array_keys($translations) as $langcode) {
unset($languages[$langcode]);
}
}
// Language may be locked for this node type, restrict options to current one
if ($select && i18n_node_language_options($node, 'lock') && !empty($node->language) && !empty($languages[$node->language])) {
$languages = array($node->language => $languages[$node->language]);
}
// Check language required for this type (no language neutral)
elseif (!i18n_node_language_options($node, 'required')) {
$languages = array(LANGUAGE_NONE => t('Language neutral')) + $languages;
}
}
return $languages;
}
/**
* Check options for node language
*/
function i18n_node_language_options($node, $option) {
$options = variable_get('i18n_node_options_' . $node->type, array());
return in_array($option, $options, TRUE);
}
/**
* Get language mode for node or node type
*/
function i18n_node_language_mode($type) {
$type = is_object($type) ? $type->type : $type;
return variable_get('i18n_node_extended_' . $type, I18N_LANGUAGE_ENABLED);
}
/**
* Implements hook_node_prepare().
*/
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)) {
// Set current language for new nodes if option enabled
$node->language = i18n_language_content()->language;
}
}
/**
* Implements hook_permission().
*
* Permissions defined
* - administer all languages
* Disables language conditions for administration pages, so the user can view objects for all languages at the same time.
* This applies for: menu items, taxonomy
* - administer translations
* Will allow to add/remove existing nodes to/from translation sets.
*/
function i18n_node_permission() {
return array(
'administer content translations' => array(
'title' => t('Administer content translations'),
'description' => t('Add or remove existing content to translation sets.'),
),
);
}
/**
* Implements hook_node_view()
*/
function i18n_node_node_view($node) {
if (i18n_node_type_enabled($node)) {
$node->content['language'] = array(
'#type' => 'item',
'#title' => t('Language'),
'#markup' => i18n_language_name($node->language),
);
}
}
/**
* Implements hook_node_view_alter().
*
* Handles links for extended languages. Sets current interface language.
*/
function i18n_node_node_view_alter(&$build) {
$node = $build['#node'];
// Hide node translation links.
if (variable_get('i18n_hide_translation_links', 0)) {
if (isset($build['links']['translation'])) {
unset($build['links']['translation']);
}
}
elseif (!empty($node->tnid) && !empty($build['links']['translation']) && i18n_node_language_mode($node) == I18N_LANGUAGE_EXTENDED) {
// We only get links for translations for enabled languages
// Set the right languages if language support is extended but not visible.
$links = &$build['links']['translation']['#links'];
$translations = translation_node_get_translations($node->tnid);
foreach (i18n_node_language_list($node) as $langcode => $langname) {
$index = 'translation_' . $langcode;
if ($langcode != $node->language && isset($translations[$langcode]) && $translations[$langcode]->status && !isset($links[$index])) {
// This a published translation to a disabled language. As the node is language extended, display the linkso
// These links shouldn't switch the interface, though they have a language so the right language icon will be added
$language = i18n_language_object($langcode);
$links[$index] = array(
'href' => 'node/' . $translations[$langcode]->nid,
'title' => $language->native,
'language' => $language,
'attributes' => array(
'title' => $translations[$langcode]->title,
'class' => array('translation-link'),
),
);
}
}
}
}
/**
* Implements hook_node_type_insert()
*/
function i18n_node_node_type_insert($info) {
i18n_node_node_type_update($info);
}
/**
* Implements hook_node_type_update()
*/
function i18n_node_node_type_update($info) {
if (!empty($info->old_type) && $info->old_type != $info->type) {
i18n_string_update_context("node:type:$info->old_type:*", "node:type:$info->type:*");
}
i18n_string_object_update('node_type', $info);
}
/**
* Implements hook_node_type_delete()
*/
function i18n_node_node_type_delete($info) {
i18n_string_object_remove('node_type', $info);
}
/**
* Implements hook_elements().
*
* Add a process callback for textfields.
*
* * @todo Update D7
*/
/*
function i18n_node_elements() {
$type = array();
$type['textfield'] = array('#process' => array('i18n_node_textfield_process'));
return $type;
}
*/
/**
* Process callback for textfield elements.
*
* When editing or translating a node, set Javascript to rewrite autocomplete
* paths to use the node language prefix rather than the current content one.
*
* @todo Update D7
*/
function i18n_node_textfield_process($element) {
global $language;
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) {
// 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
// node language.
$node_edit = $node = menu_get_object() && arg(2) == 'edit' && isset($node->language) && !empty($node->language);
$node_translate = arg(0) == 'node' && arg(1) == 'add' && !empty($_GET['translation']) && !empty($_GET['language']);
if ($node_edit || $node_translate) {
$node_language = $node_edit ? $node->language : $_GET['language'];
// Only needed if the node language is different from the interface one.
if ($node_language != $language->language) {
$languages = language_list();
if (isset($languages[$node_language])) {
drupal_add_js(drupal_get_path('module', 'i18n_node') . '/i18n_node.js');
// Pass the interface and content language base paths.
// Remove any trailing forward slash. Doing so prevents a mismatch
// that occurs when a language has no prefix and hence gets a path
// with a trailing forward slash.
$interface = rtrim(url('', array('absolute' => TRUE)), '/');
$content = rtrim(url('', array('absolute' => TRUE, 'language' => $languages[$node_language])), '/');
$data = array('interface_path' => $interface, 'content_path' => $content);
drupal_add_js(array('i18n' => $data), 'setting');
}
}
}
$sent = TRUE;
}
return $element;
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function i18n_node_form_search_form_alter(&$form, &$form_state) {
// Advanced search form. Add language and localize content type names
if ($form['module']['#value'] == 'node' && !empty($form['advanced'])) {
// @todo Handle language search conditions
//$form['advanced']['language'] = _i18n_language_select();
if (!empty($form['advanced']['type'])) {
foreach ($form['advanced']['type']['#options'] as $type => $name) {
$form['advanced']['type']['#options'][$type] = i18n_node_translate_type($type, 'name', $name);
}
}
}
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function i18n_node_form_node_type_form_alter(&$form, &$form_state) {
if (isset($form['type'])) {
$disabled = !i18n_node_type_enabled($form['#node_type']);
$form['i18n'] = array(
'#type' => 'fieldset',
'#title' => t('Multilingual settings'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#group' => 'additional_settings',
'#attributes' => array(
'class' => array('i18n-node-type-settings-form'),
),
'#description' => t('Extended multilingual options provided by Internationalization module.'),
'#disabled' => $disabled,
);
// 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'));
}
// Add disabled message
if ($disabled) {
$form['i18n']['#description'] .= ' <em>' . t('These will be available only when you enable Multilingual support in Publishing options above.') . '</em>';
foreach (element_children($form['i18n']) as $key) {
$form['i18n'][$key]['#disabled'] = TRUE;
}
}
}
}
/**
* Implements hook_form_BASE_FORM_ID_alter().
*/
function i18n_node_form_node_form_alter(&$form, $form_state) {
$node = $form['#node'];
/**
* i18n has to override locale.module
* drupal_alter() fails to order modules correctly in some cases
* for example specific hooks like hook_form_BASE_FORM_ID_alter
*
* its not possbile to reorder hook_form_BASE_FORM_ID_alter with
* hook_module_implements_alter
*
* @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']);
if ($key !== FALSE) {
$form['actions']['submit']['#submit'][$key] = 'i18n_node_form_submit';
}
// call a 'private' implemenation of i18n_node_form_node_form_alter()
$form['#after_build'][] = '_i18n_node_form_node_form_alter';
}
/**
* Replacement for core's node_form_submit(), taking care of
* translating node type names.
*/
function i18n_node_form_submit($form, &$form_state) {
$node = node_form_submit_build_node($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);
$watchdog_args = array('@type' => $node->type, '%title' => $node->title);
$t_args = array('@type' => $type_name, '%title' => $node->title);
if ($insert) {
watchdog('content', '@type: added %title.', $watchdog_args, WATCHDOG_NOTICE, $node_link);
drupal_set_message(t('@type %title has been created.', $t_args));
}
else {
watchdog('content', '@type: updated %title.', $watchdog_args, WATCHDOG_NOTICE, $node_link);
drupal_set_message(t('@type %title has been updated.', $t_args));
}
if ($node->nid) {
$form_state['values']['nid'] = $node->nid;
$form_state['nid'] = $node->nid;
$form_state['redirect'] = 'node/' . $node->nid;
}
else {
// In the unlikely case something went wrong on save, the node will be
// rebuilt and node form redisplayed the same way as in preview.
drupal_set_message(t('The post could not be saved.'), 'error');
$form_state['rebuild'] = TRUE;
}
// Clear the page and block caches.
cache_clear_all();
}
/**
* Implements hook_form_BASE_FORM_ID_alter().
* Called by i18n_node_form_node_form_alter
*/
function _i18n_node_form_node_form_alter($form, &$form_state) {
$node = $form['#node'];
if (i18n_node_type_enabled($node)) {
if (!empty($form['language']['#options'])) {
$form['language']['#options'] = i18n_node_language_list($node, TRUE, TRUE);
}
}
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;
}
// Translate field names for title and body for the node edit form.
if (!empty($form['title']['#title'])) {
$form['title']['#title'] = i18n_node_translate_type($node->type, 'title_label', $form['title']['#title']);
}
if (!empty($form['body_field']['body']['#title'])) {
$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);
}
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);
}
return $form;
}
/**
* Implements hook_theme().
*/
function i18n_node_theme() {
return array(
'i18n_node_select_translation' => array(
'render element' => 'element',
'file' => 'i18n_node.pages.inc',
),
);
}
/**
* Shorthand for translating node type strings
*
* @param $type
* Node type name or full object
*/
function i18n_node_translate_type($type, $property = 'name', $source = NULL, $options = array()) {
if (is_object($type)) {
$source = $type->$property;
$type = $type->type;
}
return i18n_string_translate(array('node', 'type', $type, $property), $source, $options);
}
/**
* Translate node type name (unfiltered)
*/
function i18n_node_type_name($type, $name) {
return i18n_string_translate(array('node', 'type', $type, 'name'), $name);
}
/**
* Check whether this is a language enabled node type
*
* @param $type
* Node, node type object, or node type name
*/
function i18n_node_type_enabled($type) {
$type = is_object($type) ? $type->type : $type;
$mode = variable_get('language_content_type_' . $type, 0);
return $mode == 1 || $mode == 2; // 2 == TRANSLATION_ENABLED
}

View File

@@ -0,0 +1,356 @@
<?php
/**
* @file
* User page callbacks for the translation module.
*/
/**
* Replacement for node_add_page.
*/
function i18n_node_add_page() {
$item = menu_get_item();
$content = system_admin_menu_block($item);
// Bypass the node/add listing if only one content type is available.
if (count($content) == 1) {
$item = array_shift($content);
drupal_goto($item['href']);
}
foreach ($content as &$item) {
// Type machine name will be the first page argument
$page_arguments = unserialize($item['page_arguments']);
// Check whether this has a node type, other items may be here too, see #1264662
$type = isset($page_arguments[0]) ? $page_arguments[0] : NULL;
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']);
}
}
return theme('node_add_list', array('content' => $content));
}
/**
* Overview page for a node's translations.
*
* @param $node
* Node object.
*/
function i18n_node_translation_overview($node) {
include_once DRUPAL_ROOT . '/includes/language.inc';
if (!empty($node->tnid)) {
// Already part of a set, grab that set.
$tnid = $node->tnid;
$translations = translation_node_get_translations($node->tnid);
}
else {
// We have no translation source nid, this could be a new set, emulate that.
$tnid = $node->nid;
$translations = array($node->language => $node);
}
$type = variable_get('translation_language_type', LANGUAGE_TYPE_INTERFACE);
$header = array(t('Language'), t('Title'), t('Status'), t('Operations'));
// Modes have different allowed languages
foreach (i18n_node_language_list($node) as $langcode => $language_name) {
if ($langcode == LANGUAGE_NONE) {
// Never show language neutral on the overview.
continue;
}
$options = array();
if (isset($translations[$langcode])) {
// Existing translation in the translation set: display status.
// We load the full node to check whether the user can edit it.
$translation_node = node_load($translations[$langcode]->nid);
$path = 'node/' . $translation_node->nid;
$title = i18n_node_translation_link($translation_node->title, $path, $langcode);
if (node_access('update', $translation_node)) {
$text = t('edit');
$path = 'node/' . $translation_node->nid . '/edit';
$options[] = i18n_node_translation_link($text, $path, $langcode);
}
$status = $translation_node->status ? t('Published') : t('Not published');
$status .= $translation_node->translate ? ' - <span class="marker">' . t('outdated') . '</span>' : '';
if ($translation_node->nid == $tnid) {
$language_name = t('<strong>@language_name</strong> (source)', array('@language_name' => $language_name));
}
}
else {
// No such translation in the set yet: help user to create it.
$title = t('n/a');
if (node_access('create', $node->type)) {
$text = t('add translation');
$path = 'node/add/' . str_replace('_', '-', $node->type);
$query = array('query' => array('translation' => $node->nid, 'target' => $langcode));
$options[] = i18n_node_translation_link($text, $path, $langcode, $query);
}
$status = t('Not translated');
}
$rows[] = array($language_name, $title, $status, implode(" | ", $options));
}
drupal_set_title(t('Translations of %title', array('%title' => $node->title)), PASS_THROUGH);
$build['translation_node_overview'] = array(
'#theme' => 'table',
'#header' => $header,
'#rows' => $rows,
);
if (user_access('administer content translations')) {
$build['translation_node_select'] = drupal_get_form('i18n_node_select_translation', $node, $translations);
}
return $build;
}
/**
* Create link for node translation. This may be a 'edit' or a 'add translation' link.
*/
function i18n_node_translation_link($text, $path, $langcode, $options = array()) {
$type = variable_get('translation_language_type', LANGUAGE_TYPE_INTERFACE);
$links = language_negotiation_get_switch_links($type, $path);
// When node not published, links don't have href so we use path instead
// Note: this is a bug in Core translation module, see http://drupal.org/node/1137074
if (!empty($links->links[$langcode]) && !empty($links->links[$langcode]['href'])) {
$options += array('attributes' => array(), 'html' => FALSE);
$options['attributes'] += $links->links[$langcode]['attributes'];
$options += $links->links[$langcode];
$path = $links->links[$langcode]['href'];
}
return l($text, $path, $options);
}
/**
* Form to select existing nodes as translation
*
* This one uses autocomplete fields for all languages
*/
function i18n_node_select_translation($form, &$form_state, $node, $translations) {
$form['node'] = array('#type' => 'value', '#value' => $node);
$form['translations'] = array(
'#type' => 'fieldset',
'#title' => t('Select translations for %title', array('%title' => $node->title)),
'#tree' => TRUE,
'#theme' => 'i18n_node_select_translation',
'#description' => t("Alternatively, you can select existing nodes as translations of this one or remove nodes from this translation set. Only nodes that have the right language and don't belong to other translation set will be available here.")
);
foreach (i18n_node_language_list($node) as $langcode => $language_name) {
if ($langcode != $node->language && $langcode != LANGUAGE_NONE) {
$nid = isset($translations[$langcode]) ? $translations[$langcode]->nid : 0;
$form['translations']['nid'][$langcode] = array(
'#type' => 'value',
'#value' => $nid,
);
$form['translations']['language'][$langcode] = array(
'#type' => 'value',
'#value' => $language_name,
);
$form['translations']['node'][$langcode] = array(
'#type' => 'textfield',
'#maxlength' => 255,
'#autocomplete_path' => 'i18n/node/autocomplete/' . $node->type . '/' . $langcode,
'#default_value' => $nid ? i18n_node_nid2autocomplete($nid) : '',
);
}
}
$form['actions'] = array('#type' => 'actions');
$form['actions']['update'] = array(
'#type' => 'submit',
'#value' => t('Update translations'),
);
//$form['buttons']['clean'] = array('#type' => 'submit', '#value' => t('Delete translation set'));
return $form;
}
/**
* Form validation
*/
function i18n_node_select_translation_validate($form, &$form_state) {
foreach ($form_state['values']['translations']['node'] as $lang => $title) {
if (!$title) {
$nid = 0;
}
else {
$nid = i18n_node_autocomplete2nid($title, "translations][node][$lang", array($form_state['values']['node']->type), array($lang));
}
$form_state['values']['translations']['nid'][$lang] = $nid;
}
}
/**
* Form submission: update / delete the translation set
*/
function i18n_node_select_translation_submit($form, &$form_state) {
$op = isset($form_state['values']['op']) ? $form_state['values']['op'] : NULL;
$node = $form_state['values']['node'];
$translations = $node->tnid ? translation_node_get_translations($node->tnid) : array($node->language => $node);
foreach ($translations as $trans) {
$current[$trans->language] = $trans->nid;
}
$update = array($node->language => $node->nid) + array_filter($form_state['values']['translations']['nid']);
// Compute the difference to see which are the new translations and which ones to remove
$new = array_diff_assoc($update, $current);
$remove = array_diff_assoc($current, $update);
// The tricky part: If the existing source is not in the new set, we need to create a new tnid
if ($node->tnid && in_array($node->tnid, $update)) {
$tnid = $node->tnid;
$add = $new;
}
else {
// Create new tnid, which is the source node
$tnid = $node->nid;
$add = $update;
}
// Now update values for all nodes
if ($add) {
db_update('node')
->fields(array(
'tnid' => $tnid,
))
->condition('nid', $add)
->execute();
if (count($new)) {
drupal_set_message(format_plural(count($new), 'Added a node to the translation set.', 'Added @count nodes to the translation set.'));
}
}
if ($remove) {
db_update('node')
->fields(array(
'tnid' => 0,
))
->condition('nid', $remove)
->execute();
drupal_set_message(format_plural(count($remove), 'Removed a node from the translation set.', 'Removed @count nodes from the translation set.'));
}
}
/**
* Node title autocomplete callback
*/
function i18n_node_autocomplete($type, $language, $string = '') {
$params = array('type' => $type, 'language' => $language, 'tnid' => 0);
$matches = array();
foreach (_i18n_node_references($string, 'contains', $params) as $id => $row) {
// Add a class wrapper for a few required CSS overrides.
$matches[$row['title'] . " [nid:$id]"] = '<div class="reference-autocomplete">' . $row['rendered'] . '</div>';
}
drupal_json_output($matches);
}
/**
* Generates 'title [nid:$nid]' for the autocomplete field
*/
function i18n_node_nid2autocomplete($nid) {
if ($node = node_load($nid)) {
return check_plain($node->title) . ' [nid:' . $nid . ']';
}
else {
return t('Not found');
}
}
/**
* Reverse mapping from node title to nid
*
* We also handle autocomplete values (title [nid:x]) and validate the form
*/
function i18n_node_autocomplete2nid($name, $field = NULL, $type, $language) {
if (!empty($name)) {
preg_match('/^(?:\s*|(.*) )?\[\s*nid\s*:\s*(\d+)\s*\]$/', $name, $matches);
if (!empty($matches)) {
// Explicit [nid:n].
list(, $title, $nid) = $matches;
if (!empty($title) && ($node = node_load($nid)) && $title != $node->title) {
if ($field) {
form_set_error($field, t('Node title mismatch. Please check your selection.'));
}
$nid = NULL;
}
}
else {
// No explicit nid.
$reference = _i18n_node_references($name, 'equals', array('type' => $type, 'language' => $language), 1);
if (!empty($reference)) {
$nid = key($reference);
}
elseif ($field) {
form_set_error($field, t('Found no valid post with that title: %title', array('%title' => $name)));
}
}
}
return !empty($nid) ? $nid : NULL;
}
/**
* Find node title matches.
*
* @param $string
* String to match against node title
* @param $match
* Match mode: 'contains', 'equals', 'starts_with'
* @param $params
* Other query arguments: type, language or numeric ones
*/
function _i18n_node_references($string, $match = 'contains', $params = array(), $limit = 10) {
$query = db_select('node', 'n')
->fields('n', array('nid', 'title' , 'type'))
->orderBy('n.title')
->orderBy('n.type')
->range(0, $limit);
foreach ($params as $key => $value) {
$query->condition($key, $value);
}
switch ($match) {
case 'equals':
$query->condition('n.title', $string);
break;
case 'starts_with':
$query->condition('n.title', $string . '%', 'LIKE');
break;
case 'contains':
default:
$query->condition('n.title', '%' . $string . '%', 'LIKE');
break;
}
// Disable and reenable i18n selection mode so no language conditions are inserted
i18n_select(false);
$references = array();
foreach ($query->execute() as $node) {
$references[$node->nid] = array(
'title' => $node->title,
'rendered' => check_plain($node->title),
);
}
i18n_select(true);
return $references;
}
/**
* Theme select translation form
* @ingroup themeable
*/
function theme_i18n_node_select_translation($variables) {
$elements = $variables['element'];
$output = '';
if (isset($elements['nid'])) {
$rows = array();
foreach (element_children($elements['nid']) as $lang) {
$rows[] = array(
$elements['language'][$lang]['#value'],
drupal_render($elements['node'][$lang]),
);
}
$output .= theme('table', array('rows' => $rows));
$output .= drupal_render_children($elements);
}
return $output;
}

View File

@@ -0,0 +1,99 @@
<?php
/**
* @file
* Contains test cases for the i18n_node module.
*/
class I18nNodeTestCase extends Drupali18nTestCase {
public static function getInfo() {
return array(
'name' => 'Content translation',
'group' => 'Internationalization',
'description' => 'Content translation functions',
);
}
function setUp() {
parent::setUp('translation', 'i18n_node');
parent::setUpLanguages(array('administer content translations', 'translate content'));
parent::setUpContentTranslation();
$this->addLanguage('pt-br');
// Add a disabled language.
$this->addLanguage('it');
$edit = array('enabled[it]' => FALSE);
$this->drupalPost('admin/config/regional/language', $edit, t('Save configuration'));
}
/**
* Tests for adding content to an existing translation set.
*/
function testAddContentToTranslationSet() {
module_load_include('inc', 'i18n_node', 'i18n_node.pages');
// Create 3 nodes in different languages.
$en_title = $this->randomName(10);
$en_body = $this->randomString(50);
$en_node = $this->createNode('page', $en_title, $en_body, 'en');
$es_title = $this->randomName(10);
$es_body = $this->randomString(50);
$es_node = $this->createNode('page', $es_title, $es_body, 'es');
$ptbr_title = $this->randomName(10);
$ptbr_body = $this->randomString(50);
$ptbr_node = $this->createNode('page', $ptbr_title, $ptbr_body, 'pt-br');
// Check the autocomplete suggestions.
$this->drupalGet('i18n/node/autocomplete/page/es/' . substr($es_title, 0, 3));
$this->assertText($es_title);
$this->assertNoText($en_title);
$this->assertNoText($ptbr_title);
$this->drupalGet('i18n/node/autocomplete/page/es/' . substr($en_title, 0, 3));
$this->assertNoText($es_title);
$this->assertNoText($en_title);
$this->assertNoText($ptbr_title);
$this->drupalGet('i18n/node/autocomplete/page/pt-br/' . substr($ptbr_title, 0, 3));
$this->assertNoText($es_title);
$this->assertNoText($en_title);
$this->assertText($ptbr_title);
// Go to the translations tab.
$this->drupalGet('node/' . $en_node->nid);
$this->clickLink(t('Translate'));
// Make sure that the disabled language doesn't show up.
$this->assertNoText(t('Italian'));
// Test validation.
$edit = array(
'translations[node][es]' => $ptbr_title,
);
$this->drupalPost(NULL, $edit, t('Update translations'));
$this->assertText(t('Found no valid post with that title: @title', array('@title' => $ptbr_title)));
// Add two translated nodes.
$edit = array(
'translations[node][pt-br]' => $ptbr_title,
'translations[node][es]' => $es_title,
);
$this->drupalPost(NULL, $edit, t('Update translations'));
$this->assertText(t('Added @count nodes to the translation set.', array('@count' => 2)));
$this->assertFieldByName('translations[node][es]', i18n_node_nid2autocomplete($es_node->nid));
$this->assertFieldByName('translations[node][pt-br]', i18n_node_nid2autocomplete($ptbr_node->nid));
// Remove a translation node again.
$edit = array(
'translations[node][pt-br]' => '',
);
$this->drupalPost(NULL, $edit, t('Update translations'));
$this->assertText(t('Removed a node from the translation set.'));
$this->assertFieldByName('translations[node][es]', i18n_node_nid2autocomplete($es_node->nid));
$this->assertFieldByName('translations[node][pt-br]', '');
}
}

View File

@@ -0,0 +1,79 @@
<?php
/**
* @file
* Variable information
*/
/**
* Implements hook_variable_group_info().
*/
function i18n_node_variable_group_info() {
$groups['i18n_node'] = array(
'title' => t('Multilingual node options'),
'description' => t('Extended node options for multilingual sites.'),
'access' => 'administer site configuration',
'path' => 'admin/config/regional/i18n/node',
);
return $groups;
}
/**
* Implements hook_variable_info().
*/
function i18n_node_variable_info($options = array()) {
$variables['i18n_hide_translation_links'] = array(
'type' => 'boolean',
'title' => t('Hide content translation links', array(), $options),
'description' => t('Hide the links to translations in content body and teasers. If you choose this option, switching language will only be available from the language switcher block.', array(), $options),
'default' => 0,
'group' => 'i18n_node',
);
$variables['i18n_node_default_language_none'] = array(
'title' => t('Default language for content types with Multilingual support disabled.', array(), $options),
'description' => t('Determines which language will be set for newly created content of types that don\'t have Multilingual support enabled.', array(), $options),
'type' => 'select',
'options' => array(
0 => t('The site\'s default language (Default behaviour).', array(), $options),
1 => t('Language neutral (Recommended).', array(), $options)
),
'default' => 0,
'group' => 'i18n_node',
);
$variables['i18n_node_options_[node_type]'] = array(
'type' => 'multiple',
'title' => t('Extended language options', array(), $options),
'repeat' => array(
'type' => 'options',
'options' => array(
'current' => t('Set current 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',
);
$variables['i18n_node_extended_[node_type]'] = array(
'type' => 'multiple',
'title' => t('Extended language support'),
'repeat' => array(
'type' => 'select',
'options callback' => 'i18n_node_variable_extended_options',
'default' => I18N_LANGUAGE_ENABLED,
),
'description' => t('If enabled, all defined languages will be allowed for this content type in addition to only enabled ones. This is useful to have more languages for content than for the interface.', array(), $options),
'group' => 'i18n',
);
return $variables;
}
/**
* Options callback for i18n_node_extended_
*/
function i18n_node_variable_extended_options($variable, $options) {
return array(
I18N_LANGUAGE_ENABLED => t('Normal - All enabled languages will be allowed.', array(), $options),
I18N_LANGUAGE_EXTENDED => t('Extended - All defined languages will be allowed.', array(), $options),
I18N_LANGUAGE_EXTENDED | I18N_LANGUAGE_HIDDEN => t('Extended, but not displayed - All defined languages will be allowed for input, but not displayed in links.', array(), $options),
);
}