Index: modules/system/system.install =================================================================== RCS file: /cvs/drupal/drupal/modules/system/system.install,v retrieving revision 1.119 diff -u -r1.119 system.install --- modules/system/system.install 30 May 2007 08:08:58 -0000 1.119 +++ modules/system/system.install 31 May 2007 21:43:18 -0000 @@ -3339,6 +3339,15 @@ return $ret; } + +function system_update_6023() { + $ret = array(); + + db_add_field($ret, 'node', 'snid', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0)); + db_add_field($ret, 'node', 'tr_status', array('type' => 'int', 'not null' => TRUE, 'default' => 0)); + return $ret; +} + /** * @} End of "defgroup updates-5.x-to-6.x" * The next series of updates should start at 7000. Index: modules/node/node.schema =================================================================== RCS file: /cvs/drupal/drupal/modules/node/node.schema,v retrieving revision 1.1 diff -u -r1.1 node.schema --- modules/node/node.schema 25 May 2007 12:46:45 -0000 1.1 +++ modules/node/node.schema 31 May 2007 21:43:16 -0000 @@ -16,7 +16,9 @@ 'comment' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), 'promote' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), 'moderate' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), - 'sticky' => array('type' => 'int', 'not null' => TRUE, 'default' => 0) + 'sticky' => array('type' => 'int', 'not null' => TRUE, 'default' => 0), + 'snid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0), + 'tr_status'=> array('type' => 'int', 'not null' => TRUE, 'default' => 0), ), 'indexes' => array( 'nid' => array('nid'), @@ -28,7 +30,8 @@ 'node_title_type' => array('title', array('type', 4)), 'node_type' => array(array('type', 4)), 'status' => array('status'), - 'uid' => array('uid') + 'uid' => array('uid'), + 'snid' => array('snid'), ), 'unique keys' => array( 'nid_vid' => array('nid', 'vid'), Index: modules/translation/translation.module =================================================================== RCS file: modules/translation/translation.module diff -N modules/translation/translation.module --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/translation/translation.module 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,321 @@ +'. t('The translation module allows content translation for multilingual sites.') .'
'; + $output .= ''. t('For more information please read the configuration and customization handbook Translation page.', array('@translation' => 'http://drupal.org/handbook/modules/translation/')) .'
'; + return $output; + } +} + +/** + * Implementation of hook_menu(). + */ +function translation_menu() { + $items = array(); + $items['node/%node/translate'] = array( + 'title' => 'Translate', + 'page callback' => 'translation_node_overview', + 'page arguments' => array(1), + 'access callback' => '_translation_tab_access', + 'access arguments' => array(1), + 'type' => MENU_LOCAL_TASK, + 'weight' => 2, + ); + return $items; +} + +/** + * Menu access callback. + * + * Only display translation tab for node types, where language is enabled + * and where the current node is not language neutral (which should span + * all languages). + */ +function _translation_tab_access($node) { + if (!empty($node->language) && variable_get('language_' . $node->type, 0) == TRANSLATION_ENABLED) { + return user_access('translate content'); + } + return FALSE; +} + +/** + * Implementation of hook_perm(). + */ +function translation_perm() { + return array('translate content'); +} + +/** + * Implementation of hook_form_alter(). + * + * Alters language fields on node forms when a translation is about to be created. + */ +function translation_form_alter(&$form, $form_state, $form_id) { + if ($form_id == 'node_type_form') { + $form['workflow']['language']['#options'][TRANSLATION_ENABLED] = t('Enabled, with translation'); +// TODO : update the description +// $form['workflow']['language']['#description'] = ''; + } + elseif ($form['#id'] == 'node-form' && variable_get('language_' . $form['#node']->type, 0) == TRANSLATION_ENABLED) { + $node = $form['#node']; + if (!empty($node->translation_source)) { + // We are creating a translation. Add values and lock language field. + $form['translation_source'] = array('#type' => 'value', '#value' => $node->translation_source); + $form['language']['#disabled'] = TRUE; + } + elseif (!empty($node->nid) && !empty($node->snid)) { + // Disable languages for existing translations, so it is not possible to switch this node + // to some language which is already in the translation set. + foreach (translation_node_get_translations($node) as $translation) { + if ($translation->nid != $node->nid) { + unset($form['language']['#options'][$translation->language]); + } + } + // Add translation values and workflow options + $form['translation'] = array('#type' => 'fieldset', + '#title' => t('Translation settings'), + '#access' => user_access('translate content'), + '#collapsible' => TRUE, + '#collapsed' => !$node->tr_status, + '#tree' => TRUE, + '#weight' => 30, + ); + if ($node->snid == $node->nid) { + // This is the source node of the translation + $form['translation']['retranslate'] = array('#type' => 'checkbox', + '#title' => t('Update translations'), + '#default_value' => 0, + '#description' => t('Mark translations of this content as outdated.'), + ); + $form['translation']['status'] = array('#type' => 'value', '#value' => 0); + } + else { + $form['translation']['status'] = array('#type' => 'checkbox', + '#title' => t('This translation needs to be updated'), + '#default_value' => $node->tr_status, + '#description' => t('This option checked means that this translation needs to be updated because the source content has been updated. Uncheck when the translation is up to date.'), + ); + } + $form['translation']['snid'] = array('#type' => 'value', '#value' => $node->snid); + } + } +} + +/** + * Implementation of hook_link(). + * + * Display translation links if this node is part of a translation set. + */ +function translation_link($type, $node = NULL, $teaser = FALSE) { + $links = array(); + if ($type == 'node' && ($node->snid) && $translations = translation_node_get_translations($node)) { + // Do not show link to the same node. + unset($translations[$node->language]); + $languages = locale_language_list('native'); + foreach ($translations as $language => $translation) { + $links["node_translation_$language"] = array( + 'title' => $languages[$language], + 'href' => "node/$translation->nid", + 'attributes' => array('title' => $translation->title) + ); + } + } + return $links; +} + +/** + * Overview page for a node's translations. + * + * @param $node + * Node object. + */ +function translation_node_overview($node) { + $translations = translation_node_get_translations($node); + + $output = '' . t('%title is in @language, so it is possible to translate it to the following languages enabled on your site:', array('%title' => $node->title, '@language' => locale_language_name($node->language))) . '
'; + + $header = array(t('Language'), t('Title'), t('Status'), t('Operations')); + + foreach (language_list() as $language) { + $options = array(); + $language_name = $language->name; + if (isset($translations[$language->language])) { + // We load the full node to check whether the user can edit it. + $trnode = node_load($translations[$language->language]->nid); + $title = l($trnode->title, 'node/'. $trnode->nid); + if (node_access('update', $trnode)) { + $options[] = l(t('edit'), "node/$trnode->nid/edit"); + } + $status = $trnode->status ? t('Published') : t('Not published'); + $status .= $trnode->tr_status ? ' - '. t('outdated') .'' : ''; + if ($trnode->nid == $trnode->snid) { + $language_name = ''. $language_name . ' (source)'; + } + } + else { + $title = t('n/a'); + if (node_access('create', $node)) { + $options[] = l(t('add translation'), 'node/add/'. $node->type, array('query' => "translation=$node->nid&language=$language->language")); + } + $status = t('Not translated'); + } + $rows[] = array($language_name, $title, $status, implode(" | ", $options)); + } + $output .= theme('table', $header, $rows); + + drupal_set_title(t('Translations of %title', array('%title' => $node->title))); + return $output; +} + +/** + * Get translations for a specific node id. + * + * @param $param + * Either a node object or the 'snid' of the translation set. + * @return + * Array of node translations indexed by language. + */ +function translation_node_get_translations($param) { + static $translations = array(); + $snid = (is_object($param) && $param->snid) ? $param->snid : $param; + if (is_numeric($snid) && $snid) { + if (!isset($translations[$snid])) { + $translations[$snid] = array(); + $result = db_query(db_rewrite_sql('SELECT * FROM {node} WHERE snid = %d'), $snid); + while ($node = db_fetch_object($result)) { + $translations[$snid][$node->language] = $node; + } + } + return $translations[$snid]; + } +} + +/** + * Implementation of hook_nodeapi(). + * + * Manages translation information for nodes. + */ +function translation_nodeapi(&$node, $op, $teaser, $page) { + // Only if languages are enabled for this node type. + if (variable_get('language_' . $node->type, 0)) { + switch ($op) { + + case 'prepare': + if (empty($node->nid) && isset($_GET['translation']) && isset($_GET['language']) && ($source_nid = $_GET['translation']) && ($language = $_GET['language']) && (user_access('translate content'))) { + // We are translating a node from a source node, so + // load the node to be translated and populate fields. + $node->language = $language; + $node->translation_source = node_load($source_nid); + $node->title = $node->translation_source->title; + $node->body = $node->translation_source->body; + // Let every module add translated fields. + node_invoke_nodeapi($node, 'prepare translation'); + } + break; + + case 'insert': + if (!empty($node->translation_source)) { + if ($node->translation_source->snid) { + // Add node to existing translation set. + $snid = $node->translation_source->snid; + } + else { + // Create new translation set, using nid from the source node. + $snid = $node->translation_source->nid; + db_query("UPDATE {node} SET snid = %d, tr_status = %d WHERE nid = %d", $snid, 0, $node->translation_source->nid); + } + db_query("UPDATE {node} SET snid = %d, tr_status = %d WHERE nid = %d", $snid, 0, $node->nid); + } + break; + + case 'update': + if (isset($node->translation) && $node->translation && !empty($node->language)) { + if ($node->snid) { + // Update translation information. + db_query("UPDATE {node} SET snid = %d, tr_status = %d WHERE nid = %d", $node->snid, $node->translation['status'], $node->nid); + if (!empty($node->translation['retranslate'])) { + // This is source content, requiring all translations to be marked outdated + db_query("UPDATE {node} SET tr_status = 1 WHERE snid = %d AND nid != %d", $node->snid, $node->nid); + } + } + else { + // The node may have been added to some existing translation set. + db_query("UPDATE {node} SET snid = %d, tr_status = %d WHERE nid = %d", $node->snid, $node->tr_status, $node->nid); + } + } + else { + // Remove node from translation set if existed. + translation_remove_from_set($node); + } + break; + + case 'delete': + translation_remove_from_set($node); + break; + } + } +} + +/** + * Remove a node from its translation set (if any) + * and update the set accordingly. + */ +function translation_remove_from_set($node) { + if ($node->snid) { + if (db_num_rows(db_query('SELECT * FROM {node} WHERE snid = %d', $node->snid)) <= 2) { + // There will be only one node left in the set : we remove all + db_query('UPDATE {node} SET snid = 0, tr_status = 0 WHERE snid = %d', $node->snid); + } + else { + db_query('UPDATE {node} SET snid = 0, tr_status = 0 WHERE nid = %d', $node->nid); + + // If the node being removed was the source of the translation set, + // we pick a new source - preferably one that is up to date. + if ($node->snid == $node->nid) { + $new_snid = db_result(db_query('SELECT nid FROM {node} WHERE snid = %d ORDER BY tr_status ASC, nid ASC', $node->snid, $node->snid)); + db_query('UPDATE {node} SET snid = %d WHERE snid = %d', $new_snid, $node->snid); + } + } + } +} + +/** + * Return path of translations for a node, based on its path. + * + * @param $path + * A Drupal path + */ +function translation_path_get_translations($path) { + $output = array(); + // Check for a node related path, and for its translations. + if ((preg_match("/^(node\/)([0-9]+)(.*)$/", $path, $matches)) && ($node = node_load($matches[2])) && !empty($node->translation)) { + foreach (translation_node_get_translations($node) as $language => $trnode) { + $output[$language] = 'node/'.$trnode->nid.$matches[3]; + } + } + return $output; +} Index: modules/translation/translation.info =================================================================== RCS file: modules/translation/translation.info diff -N modules/translation/translation.info --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/translation/translation.info 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,6 @@ +; $Id: translation.info Exp $ +name = Content translation +description = Allows content to be translated into different languages. +dependencies[] = locale +package = Core - optional +version = VERSION