'.t('This module is part of i18n package and provides support for translation relationships.').'

'; $output .= '

'.t('The objects you can define translation relationships for are:').'

'; $output .= ''; $output .= '

'.t('Additional features:').'

'; $output .= ''; $output .= '

'.t('For more information please read the on-line help pages.', array('@i18n' =>'http://drupal.org/node/31631')) .'

'; return $output; break; case 'admin/access#translation': $output = t('

Translations

'); $output = t('translate nodes

This one, combined with create content permissions, will allow to create node translation

'); } return $output; } /** * Implementation of hook_menu(). */ function translation_menu($may_cache) { $items = array(); if ($may_cache) { $items[] = array( 'path' => 'translation/autocomplete', 'title' => t('translation node autocomplete'), 'callback' => 'translation_node_autocomplete', 'access' => user_access('translate nodes'), 'type' => MENU_CALLBACK, ); $items[] = array( 'path' => 'admin/settings/i18n/translation', 'title' => t('Translation'), 'callback' => 'drupal_get_form', 'callback arguments' => array('translation_admin_settings'), 'access' => user_access('administer site configuration'), 'type' => MENU_LOCAL_TASK, ); $items[] = array( 'path' => 'admin/content/translation', 'title' => t('Translations'), 'description' => t("Manage content translations."), 'callback' => 'translation_admin_content', 'position' => 'left', 'access' => user_access('translate nodes')); $items[] = array('path' => 'admin/content/translation/overview', 'title' => t('List'), 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -10); } else { if (arg(0) == 'node' && is_numeric(arg(1)) && ($node = node_load(arg(1))) && variable_get('i18n_node_'.translation_get_node_type(arg(1)), 0)) { $access = translation_access($node) && node_access('view', $node); $type = MENU_LOCAL_TASK; $items[] = array( 'path' => 'node/'. arg(1) .'/translation', 'title' => t('Translation'), 'callback' => 'translation_node_page', 'access' => $access, 'type' => $type, 'weight' => 3); } if (arg(0) == 'admin' && arg(1) == 'content' && arg(2) == 'taxonomy' && is_numeric(arg(3)) ) { $items[] = array( 'path' => 'admin/content/taxonomy/'.arg(3).'/translation', 'title' => t('Translation'), 'callback' => 'translation_taxonomy_admin', 'access' => user_access('administer taxonomy'), 'type' => MENU_LOCAL_TASK); } // Special redirections and rewrite conditions if (arg(0) == 'node' && arg(1) == 'add' && isset($_GET['translation']) && ($source_nid = $_GET['translation']) && isset($_GET['language']) && ($lang = $_GET['language']) && array_key_exists($lang, i18n_supported_languages()) ) { // Special redirection for product types. Check for product module because there may be 'product' node types created by cck if (arg(2) == 'product' && module_exists('product') && !arg(3) && is_numeric($source_nid) && $ptype = db_result(db_query("SELECT ptype FROM {ec_product} WHERE nid = %d", $source_nid))) { drupal_goto(url("node/add/product/$ptype", "translation=$source_nid&language=$lang", NULL, TRUE)); } // Change rewrite conditions when translating node i18n_selection_mode('translation', db_escape_string($lang)); } } return $items; } /** * Implementation of hook_perm(). */ function translation_perm(){ return array('translate nodes', 'translate own nodes'); } /** * Implementation of hook_access(). */ function translation_access($node) { global $user; return user_access('translate nodes') || $node->uid == $user->uid && user_access('translate own nodes'); } /** * Form builder function: settings form */ function translation_admin_settings(){ $form['i18n_translation_links'] = array( '#type' => 'radios', '#title' => t('Language Management'), '#default_value' => variable_get('i18n_translation_links', 0), '#options' => array(t('Interface language depends on content.'), t('Interface language is independent')), '#description' => t("Whether the whole page should switch language when clicking on a node translation or not."), ); $form['i18n_translation_node_links'] = array( '#type' => 'radios', '#title' => t('Links to node translations'), '#default_value' => variable_get('i18n_translation_node_links', 0), '#options' => array(t('None.'), t('Main page only'), t('Teaser and Main page')), '#description' => t("Links from nodes to translated versions."), ); $form['i18n_translation_workflow'] = array( '#type' => 'radios', '#title' => t('Translation workflow'), '#default_value' => variable_get('i18n_translation_workflow', 1), '#options' => array(t('Disabled'), t('Enabled')), '#description' => t("If enabled some worklow will be provided for content translation."), ); return system_settings_form($form); } /** * Implementation of hook_block(). * * This is a simple language switcher which knows nothing about translations */ function translation_block($op = 'list', $delta = 0) { if ($op == 'list') { $blocks[0]['info'] = t('Translations for Content'); } elseif($op == 'view') { $blocks['subject'] = t('Languages'); $query = drupal_query_string_encode($_GET, array('q')); $blocks['content'] = theme('item_list', translation_get_links($_GET['q'], empty($query) ? NULL : $query)); } return $blocks; } /** * Implementation of hook_form_alter(). */ function translation_form_alter($form_id, &$form) { // Node add/edit form if (isset($form['type']) && $form['type']['#value'] .'_node_form' == $form_id && variable_get('i18n_node_'.$form['type']['#value'], 0)) { $node = $form['#node']; // Allow node to set it's own language list $languages = i18n_node_language_list($node); // Translation workflow, default if ($node->nid && $node->translation) { $translations = $node->translation; } elseif (isset($node->translation_source) && translation_access($node->translation_source)) { // We are translating a node: node/add/$type?translation=$nid&language=$language $translation_nid = $node->translation_nid; $language = $node->language; // Get the node to be translated and populate fields $trans = $node->translation_source; $form['i18n']['translation_nid'] = array('#type' => 'hidden', '#value' => $translation_nid); $form['i18n']['language']['#default_value'] = $language; // Do not allow language change $form['i18n']['language']['#disabled'] = TRUE; $form['i18n']['language']['#description'] = t('Language cannot be changed while creating a translation.'); if($trans->trid){ $form['i18n']['trid'] = array('#type' => 'hidden', '#value' => $trans->trid); } // Translations are taken from source node $translations = $trans->translation; $translations[$trans->language] = $trans; } // Display translations and restrict languages if ($translations) { // Unset invalid languages foreach(array_keys($translations) as $lang) { unset($form['i18n']['language']['#options'][$lang]); } // Add translation list $form['i18n']['#title'] = t('Language and translations'); $form['i18n']['translations'] = array( '#type' => 'markup', '#value' => theme('translation_node_list', $translations, FALSE) ); } // Translation workflow if (variable_get('i18n_translation_workflow', 1) && (user_access('translate nodes') || user_access('administer nodes'))) { $form['i18n']['i18n_status'] = array( '#type' => 'select', '#title' => t('Translation workflow'), '#options' => _translation_status(), '#description' => t('Use the translation workflow to keep track of content that needs translation.')); if($node->nid) { $form['i18n']['i18n_status']['#default_value'] = isset($node->i18n_status) ? $node->i18n_status : TRANSLATION_STATUS_NONE; } elseif(isset($trans)) { $form['i18n']['i18n_status']['#default_value'] = TRANSLATION_STATUS_WORKING; } } else { $form['i18n']['i18n_status'] = array('#type' => 'value', '#value' => isset($node->i18n_status) ? $node->i18n_status : TRANSLATION_STATUS_NONE); } // Clone files for original node ? if (isset($trans) && is_array($trans->files) && count($trans->files)) { $form['i18n']['translation_files'] = array( '#type' => 'fieldset', '#title' => t('Files from translated content'), '#tree' => TRUE, '#prefix' => '
', '#suffix' => '
', '#theme' => 'upload_form_current', '#description' => t('You can remove the files for this translation or keep the original files and translate the description.') ); foreach($trans->files as $key => $file) { $description = file_create_url((strpos($file->fid, 'upload') === false ? $file->filepath : file_create_filename($file->filename, file_create_path()))); $description = "". check_plain($description) .""; $form['i18n']['translation_files'][$key]['description'] = array('#type' => 'textfield', '#default_value' => (strlen($file->description)) ? $file->description : $file->filename, '#maxlength' => 256, '#description' => $description ); $form['i18n']['translation_files'][$key]['size'] = array('#type' => 'markup', '#value' => format_size($file->filesize)); $form['i18n']['translation_files'][$key]['remove'] = array('#type' => 'checkbox', '#default_value' => 0); $form['i18n']['translation_files'][$key]['list'] = array('#type' => 'checkbox', '#default_value' => $file->list); $form['i18n']['translation_files'][$key]['fid'] = array('#type' => 'value', '#value' => $file->fid); } } } } /** * Implementation of hook_nodeapi(). * * Delete case is now handled in i18n_nodeapi */ function translation_nodeapi(&$node, $op, $arg = 0) { if (variable_get("i18n_node_$node->type", 0)) { switch ($op) { case 'load': return array('translation' => translation_node_get_translations(array('nid' =>$node->nid), FALSE)); break; case 'insert': if($node->translation_nid) { // If not existing translation set, update both nodes. Otherwise trid is saved by i18n module if(!$node->trid){ $node->trid = db_next_id('{i18n_node}_trid'); db_query("UPDATE {i18n_node} SET trid = %d WHERE nid=%d OR nid=%d", $node->trid, $node->nid, $node->translation_nid); } // Clone files for node attachments if(isset($node->translation_files)) { foreach($node->translation_files as $fid => $file) { if(!$file['remove']) { // We are using revisions to have a file linked to different nodes, different descriptions db_query("INSERT INTO {file_revisions} (fid, vid, list, description) VALUES (%d, %d, %d, '%s')", $file['fid'], $node->vid, $file['list'], $file['description']); } } } } break; case 'prepare': // When creating a translation if (empty($node->nid) && isset($_GET['translation']) && is_numeric($_GET['translation']) && !empty($_GET['language'])) { // We are translating a node from a source node // Load the node to be translated and populate fields translation_node_prepare($node, $_GET['translation'], $_GET['language']); } break; } // Add nat integration if (module_exists('nat')) translation_nodeapi_nat($node, $op); } } /** * NAT (Node As Term) integration for node translations * * This will run after i18n, translation and nat have done their job */ function translation_nodeapi_nat(&$node, $op) { $nat_config = variable_get('nat_config', array()); // TO-DO: nat_get_terms has caching, look into it if (($op == 'insert' || $op == 'update') && isset($nat_config['types'][$node->type]) && !empty($nat_config['types'][$node->type]) && ($nat_terms = translation_nat_get_terms($node->nid))) { //drupal_set_message("DEBUG:translation_nodeapi_nat op=$op trid=$node->trid"); $translations = $term_trids = array(); // First of all, just update language for this node's terms. foreach ($nat_terms as $term) { db_query("UPDATE {term_data} SET language = '%s' WHERE tid = %d", $node->language, $term->tid); $translations[$term->vid][$node->language] = $term; if ($term->trid) { $term_trids[$term->vid] = $term->trid; } } // If the node is not in a translation set, there's nothing else to do if ($node->trid) { // Get node translations $node_translations = $node->trid ? translation_node_get_translations(array('nid' => $node->nid)) : array(); // Now we build an array of the form $translations[vid][language] = $term, foreach ($node_translations as $trnode) { foreach (nat_get_terms($trnode->nid) as $term) { // If the term doesn't have a language we set that of the node it belongs to. // This should work for previously created terms. if (!$term->language) { $term->language = $trnode->language; db_query("UPDATE {term_data} SET language = '%s' WHERE tid = %d", $term->language, $term->tid); } $translations[$term->vid][$term->language] = $term; if ($term->trid) { // This will have some problem in the cases where terms for different nodes are in different // translation sets. That shouldn't happen though; so we force all of them in the same set. $term_trids[$term->vid] = $term->trid; } } } // Update the terms, assigning translations in the same vocabulary if possible foreach ($nat_terms as $term) { if (count($translations[$term->vid]) > 1) { $translation_set = $translations[$term->vid]; $trid = isset($term_trids[$term->vid]) ? $term_trids[$term->vid] : 0; translation_taxonomy_save($translation_set, $trid); //drupal_set_message("DEBUG:translation_nodeapi_nat, trid=$trid data=".print_r($translation_set, TRUE)); } } } } } /** * Like nat_get_terms() but without caching */ function translation_nat_get_terms($nid) { $return = array(); $result = db_query("SELECT td.* FROM {nat} n INNER JOIN {term_data} td USING (tid) WHERE n.nid = %d", $nid); while ($term = db_fetch_object($result)) { $return[$term->tid] = $term; } return $return; } /** * Prepare node for translation */ function translation_node_prepare(&$node, $sourcenid, $language) { // Validate source and language. $source = node_load($sourcenid); if (empty($source) || !translation_access($source) || !node_access('view', $source)) { // Source node not found or no access to view. return; } $language_list = i18n_node_language_list($node); if (!isset($language_list[$language]) || ($source->language == $language)) { // If not supported language, or same language as source node, break. return; } $node->translation_source = drupal_clone($source); $node->translation_nid = $sourcenid; $node->language = $language; // Taxonomy translation if(is_array($source->taxonomy)) { // Set translated taxonomy terms $node->taxonomy = array(); foreach ($source->taxonomy as $tid => $term) { if ($term->language) { $translated_terms = translation_term_get_translations(array('tid' =>$tid)); if($translated_terms && $newterm = $translated_terms[$node->language]) { $node->taxonomy[$newterm->tid] = $newterm; //drupal_set_message("DEBUG: Translated term $tid to ". $newterm->tid); } } else { // Term has no language. Should be ok $node->taxonomy[$tid] = $term; } } } // Book outlines, translating parent page if exists if ($source->parent && ($translations = translation_node_get_translations(array('nid' => $source->parent))) && isset($translations[$language])) { $node->parent = $translations[$language]->nid; } // Translations are taken from source node $node->translation = $source->translation; $node->translation[$source->language] = $source; // Only copy the body if the translator can access the format. if (filter_access($source->format)) { $node->body = $source->body; } // Rest of fields. Unset some known ones not to be copied over unset($source->nid, $source->vid, $source->path, $source->language, $source->files, $source->body); foreach($source as $field => $value) { if (!isset($node->$field)) { $node->$field = $value; } } } /** * Multilingual Nodes support */ /** * This is the callback for the tab 'translation' for nodes */ function translation_node_page() { $args = func_get_args(); $op = $args[0]; $nid = arg(1); $node = node_load($nid); // If node has no language, just warning message. Function returns here if(!$node->language) { form_set_error('language', t("You need to set a language before creating a translation.")); drupal_goto("node/$nid/edit"); } drupal_set_title(check_plain($node->title)); $output = ''; switch($op){ case 'select': $output .= translation_node_overview($node); $output .= translation_node_select($node, $args[1]); break; default: $output .= translation_node_overview($node); } return $output; } /** * Form builder function */ function translation_node_form($node, $lang, $list){ $form['node'] = array('#type' => 'value', '#value' =>$node); $form['language'] = array('#type' => 'hidden', '#value' => $lang); $form['source_nid'] = array('#type' => 'hidden', '#value' => $node->nid); $form['trid'] = array('#type' => 'hidden', '#value' => $node->trid); $languages = i18n_node_language_list($node); $form['select'] = array( '#type' => 'fieldset', '#title' => t('Select translation for %language', array('%language' => $languages[$lang])) ); $form['select']['lookup'] = array( '#type' => 'textfield', '#title' => t('Search for node'), '#description' => t('Enter node id or title'), '#size' => 80, '#maxlength' => 254, '#autocomplete_path' => 'translation/autocomplete/' . $lang, ); $form['select']['nodes']['nid'] = array( '#type' => 'radios', '#title' => t('Or select from the list'), '#default_value' => isset($node->translation[$lang]) ? $node->translation[$lang]->nid : '', '#options' => $list); $form['select']['pager'] = array('#value' => theme('pager')); $form['select']['submit'] = array('#type' => 'submit', '#value' => t('Save')); return $form; } /** * Menu callback: autocomplete for selecting node translations */ function translation_node_autocomplete($lang = NULL, $string = NULL){ $languages = i18n_supported_languages(); if ($lang && $string && in_array($lang, array_keys($languages))) { $matches = array(); $result = db_query(db_rewrite_sql("SELECT n.nid, n.title FROM {node} n INNER JOIN {i18n_node} i ON n.nid = i.nid WHERE (LOWER(n.title) LIKE LOWER('%s%%') OR n.nid = %d) AND i.language = '%s'"), $string, $string, $lang); while($nodelist = db_fetch_object($result)){ $title = check_plain($nodelist->title); $matches["$nodelist->nid - $title"] = "$title [nid=$nodelist->nid]"; } print drupal_to_js($matches); } exit(); } /** * Menu callback: administration page for node translations */ function translation_admin_content() { $output = ''; $defaults = array('translation_language' => i18n_get_lang(), 'source_status' => TRANSLATION_STATUS_SOURCE); // Filter forms $output .= drupal_get_form('node_filter_form'); $output .= drupal_get_form('translation_filter_form', $defaults); // Node listing depending on previous forms $output .= drupal_get_form('translation_admin_nodes'); return $output; } /** * Form builder. Administrative form for node translations */ function translation_admin_nodes() { // First, translation filter because it may need parameters for join conditions $filter = translation_build_filter_query(); $where = $filter['where']; $params = $filter['args']; $join = $filter['join']; // Now we add in conditions from the node admin filter subform $filter = node_build_filter_query(); // Remove WHERE and merge all conditions. The WHERE part from node filter was a string with 'WHERE..' if (!empty($filter['where'])) { $where += array_merge($where, array(str_replace('WHERE', '', $filter['where']))); } if (!empty($filter['args'])) { $params = array_merge($params, $filter['args']); } if (!empty($filter['join'])) { $join .= ' ' . $filter['join']; } $sql = "SELECT n.nid, n.type, n.title, n.status, u.name, u.uid, i.language, i2.nid AS translation_nid, i2.status AS translation_status ". "FROM {node} n INNER JOIN {users} u ON n.uid = u.uid INNER JOIN {i18n_node} i ON n.nid = i.nid ". "LEFT JOIN {i18n_node} i2 ON i.trid = i2.trid AND 0 <> i2.trid AND i.language <> i2.language $join". (count($where) ? ' WHERE '.implode(' AND ', $where) : ''); // Fetch data. Temporarily disable language query rewriting before i18n_selection_mode('off'); $result = pager_query(db_rewrite_sql($sql), 50, 0, NULL, $params); i18n_selection_mode('reset'); $languages = i18n_supported_languages(); $translation_status = _translation_status(); // Fetch language for translations $language = isset($_SESSION['translation_filter']['translation_language']) ? $_SESSION['translation_filter']['translation_language'] : i18n_get_lang(); $destination = drupal_get_destination(); while ($node = db_fetch_object($result)) { $nodes[$node->nid] = ''; $form['language'][$node->nid] = array('#value' => $languages[$node->language]); $form['title'][$node->nid] = array('#value' => l($node->title, 'node/'. $node->nid) .' '. theme('mark', node_mark($node->nid, $node->changed))); $form['name'][$node->nid] = array('#value' => node_get_types('name', $node)); $form['username'][$node->nid] = array('#value' => theme('username', $node)); $form['status'][$node->nid] = array('#value' => ($node->status ? t('published') : t('not published'))); if ($node->translation_nid) { $form['translation_status'][$node->nid] = array('#value' => $translation_status[$node->translation_status]); $form['operations'][$node->nid] = array('#value' => l(t('edit translation'), 'node/'. $node->translation_nid .'/edit', array(), $destination)); } else { $form['translation_status'][$node->nid] = array('#value' => '--'); if($language == $node->language) { $form['operations'][$node->nid] = array('#value' => '--'); } else { $prefix = variable_get('i18n_translation_links', 0) ? i18n_get_lang() : $language; $form['operations'][$node->nid] = array('#value' => l(t('create translation'), $prefix.'/node/add/'.$node->type, array(), "translation=$node->nid&language=$language"). ' | '.l(t('select node'), "node/$node->nid/translation/select/$language", array(), $destination)); } } } /* $form['nodes'] = array('#type' => 'checkboxes', '#options' => $nodes); */ $form['pager'] = array('#value' => theme('pager', NULL, 50, 0)); return $form; } /** * Build query for node administration filters based on session. */ function translation_build_filter_query() { // Build query $where = $args = array(); $join = ''; // This will produce an empty join if (!is_array($_SESSION['translation_filter'])) { $_SESSION['translation_filter'] = array(); } foreach ($_SESSION['translation_filter'] as $type => $value) { switch($type) { case 'source_language': $where[] = "i.language = '%s'"; $args[] = $value; break; case 'translation_language': $join .= " AND i2.language ='".db_escape_string($value)."' "; break; case 'source_status': $where[] = "i.status = %d"; $args[] = $value; break; case 'translation_status': $join .= " AND i2.status = ".db_escape_string($value); break; } } return array('where' => $where, 'join' => $join, 'args' => $args); } /** * Returns form for translation administration filters. */ function translation_filter_form($defaults = array()) { $session = &$_SESSION['translation_filter']; $session = is_array($session) ? $session : $defaults; // Save defaults for form reset $form['_defaults'] = array('#type' => 'value', '#value' => $defaults); $form['filters'] = array('#type' => 'fieldset', '#title' => t('And translation conditions are'), '#theme' => 'translation_filter_form_layout', ); $languages = i18n_supported_languages(); // Translation and language conditions $form['filters']['source_language'] = array('#type' => 'select', '#title' => t('source language'), '#options' => array('' => '') + $languages, '#default_value' => $session['source_language']); $form['filters']['source_status'] = array('#type' => 'select', '#title' => t('source status'), '#options' => array('' => '') + _translation_status(), '#default_value' => $session['source_status']); $form['filters']['translation_language'] = array('#type' => 'select', '#title' => t('translation language'), '#options' => array('' => '') + $languages, '#default_value' => $session['translation_language']); $form['filters']['translation_status'] = array('#type' => 'select', '#title' => t('translation status'), '#options' => array('' => '') + _translation_status(), '#default_value' => $session['translation_status']); $form['filters']['buttons']['submit'] = array('#type' => 'submit', '#value' => t('Filter')); if (!empty($session)) { $form['filters']['buttons']['reset'] = array('#type' => 'submit', '#value' => t('Reset')); } return $form; } // Quick and dirty way to get some form layout function theme_translation_filter_form_layout($form) { $output = ''; $output .= ''; $output .= ''; $output .= ''; $output .= ''; $output .= ''; $output .= '
' . drupal_render($form['source_language']) . '' . drupal_render($form['translation_language']) . '
' . drupal_render($form['source_status']) . '' . drupal_render($form['translation_status']) . '
'; $output .= drupal_render($form); return $output; } /** * Builds translation admin filters */ function translation_filter_form_submit($form_id, $form_values) { $op = $_POST['op']; $session = &$_SESSION['translation_filter']; switch ($op) { case t('Filter'): $session = array(); foreach ($form_values as $name => $value) { if ($value) $session[$name] = $value; } break; case t('Reset'): $session = $form_values['_defaults']; break; } } /** * Theme node administration overview. */ function theme_translation_admin_nodes($form) { // Overview table: $header = array(t('Language'), t('Title'), t('Type'), t('Author'), t('Status'), t('Translation status'), t('Operations')); $output .= drupal_render($form['options']); if (isset($form['title']) && is_array($form['title'])) { foreach (element_children($form['title']) as $key) { $row = array(); $row[] = drupal_render($form['language'][$key]); $row[] = drupal_render($form['title'][$key]); $row[] = drupal_render($form['name'][$key]); $row[] = drupal_render($form['username'][$key]); $row[] = drupal_render($form['status'][$key]); $row[] = drupal_render($form['translation_status'][$key]); $row[] = drupal_render($form['operations'][$key]); $rows[] = $row; } } else { $rows[] = array(array('data' => t('No posts available.'), 'colspan' => '6')); } $output .= theme('table', $header, $rows); if ($form['pager']['#value']) { $output .= drupal_render($form['pager']); } $output .= drupal_render($form); return $output; } /** * Shows overview of current translations plus remove button * @param $node * Node to show translations for * @param $languages * Alternate list of languages */ function translation_node_overview($node) { $languages = i18n_node_language_list($node); unset($languages[$node->language]); // $output = t('

Current translations

'); $header = array(t('Language'), t('Title'), t('Status'), t('Options')); // Special links for product nodes $createlink = $node->type == 'product' ? "node/add/$node->type/$node->ptype" : "node/add/$node->type"; foreach ($languages as $lang => $langname){ $options = array(); if (isset($node->translation[$lang])){ $trnode = $node->translation[$lang]; $title = l($trnode->title, 'node/'.$trnode->nid); $status = $trnode->status ? t('Published') : t('Not published'); } else { $title = t('Not translated'); $prefix = variable_get('i18n_translation_links', 0) ? i18n_get_lang() : $lang; $options[] = l(t('create translation'), $prefix . '/' . $createlink , array(), "translation=$node->nid&language=$lang"); $status = '--'; } $options[] = l(t('select node'), "node/$node->nid/translation/select/$lang"); $rows[] = array($langname, $title, $status, implode(" | ", $options)); } $output .= theme('table', $header, $rows); if($node->trid){ $output .= drupal_get_form('translation_node_remove', $node); } return theme('box', t('Current translations'), $output); } /** * Form to remove node from translation set */ function translation_node_remove($node) { $form['remove'] = array('#type' => 'fieldset', '#title' => t('Remove from translation'), '#description' => t('This will just unlink the node from this translation set.') ); $form['remove']['node'] = array('#type' => 'value', '#value' => $node); $form['remove']['submit'] = array('#type' => 'submit', '#value' => t('Remove')); return $form; } /** * Form submit. Remove from translation set without confirmation. */ function translation_node_remove_submit($form_id, $form_values) { $node = $form_values['node']; db_query("UPDATE {i18n_node} SET trid = 0 WHERE nid = %d", $node->nid); drupal_set_message(t("The node has been removed from the translation set")); drupal_goto("node/$node->nid/translation"); } /** * Form to select a translation from existing nodes */ function translation_node_select($node, $lang) { $languages = i18n_supported_languages(); // Disable i18n rewrite. Order by trid to show first nodes with no translation i18n_selection_mode('off'); $result = pager_query(db_rewrite_sql("SELECT n.nid, n.title FROM {node} n INNER JOIN {i18n_node} i ON n.nid = i.nid WHERE i.language = '%s' ORDER BY i.trid"), 40, 0, NULL, $lang); i18n_selection_mode('reset'); while($trnode = db_fetch_object($result)){ $list[$trnode->nid] = l($trnode->title, "node/$trnode->nid") ; } if($list){ return drupal_get_form('translation_node_form', $node, $lang, $list); } else { return t("

No nodes available in %language

", array('%language' => $languages[$lang]) ); } } /** * Process translation node form */ function translation_node_form_submit($form_id, $form_values){ $op = $_POST['op']; $source_nid = $form_values['source_nid']; $language = $form_values['language']; if($form_values['nid']) { $nid = $form_values['nid']; } if ($form_values['lookup'] && ($number = intval($form_values['lookup']))) { $nid = $number; } if ($source_nid && $language && $nid && ($node = node_load($nid)) && $node->language == $language) { if($trid = $form_values['trid']){ // Delete old translations db_query("UPDATE {i18n_node} SET trid = 0 WHERE trid = %d AND language = '%s'", $trid, $language); } else { $trid = db_next_id('{i18n_node}_trid'); } db_query("UPDATE {i18n_node} SET trid = %d WHERE nid=%d OR nid=%d", $trid, $source_nid, $nid); drupal_set_message(t('The translation has been saved')); } } /** * Implementation of hook taxonomy. */ function translation_taxonomy($op, $type = NULL, $edit = NULL) { switch ("$type/$op") { case 'term/insert': case 'term/update': if (!$edit['language'] && $edit['trid']) { // Removed language, remove trid db_query('UPDATE {term_data} SET trid = 0 WHERE tid = %d', $edit['tid']); if(db_affected_rows()) drupal_set_message(t('Removed translation information from term')); } break; } } /** * Implementation of hook_link(). */ function translation_link($type, $node = NULL, $teaser = FALSE) { $languages = i18n_supported_languages(); $links = array(); if ($type == 'node' && variable_get('i18n_translation_node_links', 0) > ($teaser ? 1 : 0) && $node->translation) { foreach ($node->translation as $lang => $trnode) { // Add node link if published if($trnode->status) { // If language is not enabled, we'll use current language for the prefix $baselang = variable_get('i18n_translation_links', 0) || !isset($languages[$lang]) ? i18n_get_lang() : $lang; $links['translation-'.$lang]= theme('translation_node_link', $trnode , $lang, $baselang); } } } return $links; } /** * Returns a list for terms for vocabulary, language */ function translation_vocabulary_get_terms($vid, $lang, $status = 'all') { switch($status){ case 'translated': $andsql = ' AND trid > 0'; break; case 'untranslated': $andsql = ' AND trid = 0'; break; default: $andsql = ''; } $result = db_query("SELECT * FROM {term_data} WHERE vid=%d AND language='%s' $andsql", $vid, $lang); $list = array(); while ($term = db_fetch_object($result)) { $list[$term->tid] = $term->name; } return $list; } /** * Get translations * * @param $params * array of parameters * @param $getall * TRUE to get the also node itself */ function translation_node_get_translations($params, $getall = TRUE) { foreach($params as $field => $value) { $conds[] = "b.$field = '%s'"; $values[] = $value; } if(!$getall){ // If not all, a parameter must be nid $conds[] = "n.nid <> %d"; $values[] = $params['nid']; } $conds[] = "b.trid <> 0"; $sql = 'SELECT n.nid, n.title, n.status, a.language FROM {node} n INNER JOIN {i18n_node} a ON n.nid = a.nid INNER JOIN {i18n_node} b ON a.trid = b.trid WHERE '. implode(' AND ', $conds); i18n_selection_mode('off'); $result = db_query(db_rewrite_sql($sql), $values); i18n_selection_mode('reset'); $items = array(); while ($node = db_fetch_object($result)) { $items[$node->language] = $node; } return $items; } /** * Returns node type for nid */ function translation_get_node_type($nid) { return db_result(db_query('SELECT type FROM {node} WHERE nid=%d', $nid)); } /** * Multilingual Taxonomy * */ /** * This is the callback for taxonomy translations * * Gets the urls: * admin/content/taxonomy/[vid]/translation/edit/[trid] * admin/content/taxonomy/[vid]/translation/new */ function translation_taxonomy_admin($op = NULL, $trid = NULL) { $vid = arg(3); $op = arg(5); $trid = arg(6); $output = ''; switch ($op) { case 'edit': case 'new': drupal_set_title(t('Edit term translations')); $output .= drupal_get_form('translation_taxonomy_term_form', $vid, $trid); break; default: $output .= translation_taxonomy_overview($vid); } return $output; } /** * Generate a tabular listing of translations for vocabularies. */ function translation_taxonomy_overview($vid) { $vocabulary = taxonomy_get_vocabulary($vid); drupal_set_title(check_plain($vocabulary->name)); $languages = i18n_supported_languages(); $header = array_merge($languages, array(t('Operations'))); $links = array(); $types = array(); // Get terms/translations for this vocab $result = db_query('SELECT * FROM {term_data} t WHERE vid=%d',$vocabulary->vid); $terms = array(); while ($term = db_fetch_object($result)) { if($term->trid && $term->language) { $terms[$term->trid][$term->language] = $term; } } // Reorder data for rows and languages foreach ($terms as $trid => $terms) { $thisrow = array(); foreach ($languages as $lang => $name) { if (array_key_exists($lang, $terms)) { $thisrow[] = l($terms[$lang]->name, 'taxonomy/term/'.$terms[$lang]->tid); } else { $thisrow[] = '--'; } } $thisrow[] = l(t('edit'), "admin/content/taxonomy/$vid/translation/edit/$trid"); $rows[] = $thisrow; } $output .= theme('table', $header, $rows); $output .= l(t('new translation'), "admin/content/taxonomy/$vid/translation/new"); return $output; } /** * Produces a vocabulary translation form */ function translation_taxonomy_term_form($vid, $trid = NULL) { $languages = i18n_supported_languages(); if (!$trid) { $translations = array(); } else { $form['trid'] = array('#type' => 'hidden', '#value' => $trid); $translations = translation_term_get_translations(array('trid' =>$trid)); } //var_dump($translations); $vocabulary = taxonomy_get_vocabulary($vid); // List of terms for languages foreach ($languages as $lang => $langname) { $current = isset($translations[$lang]) ? $translations[$lang]->tid : ''; $list = translation_vocabulary_get_terms($vid, $lang, 'all'); $list[''] = '--'; $form[$lang] = array('#type' => 'fieldset', '#tree' => TRUE); $form[$lang]['tid'] = array( '#type' => 'select', '#title' => $langname, '#default_value' => $current, '#options' => $list ); $form[$lang]['old'] = array('#type' => 'hidden', '#value' =>$current); } $form['submit'] = array('#type' => 'submit', '#value' => t('Save')); $form['#redirect'] = 'admin/content/taxonomy/'.$vid.'/translation'; return $form; } /** * Form callback: Process vocabulary translation form */ function translation_taxonomy_term_form_submit($form_id, $form_values) { translation_taxonomy_save($form_values, $form_values['trid']); drupal_set_message(t('Term translations have been updated')); } /** * Save taxonomy term translations * * @param $terms * Array of terms indexed by language * @param $trid * Optional translation set id */ function translation_taxonomy_save($terms, $trid = 0) { // Delete old translations for this trid if ($trid){ db_query("UPDATE {term_data} SET trid = 0 WHERE trid= %d", $trid); } // Now pick up all the tids in an array $translations = array(); foreach (i18n_supported_languages() as $lang => $name) { if (isset($terms[$lang]) && ($term = (array)$terms[$lang]) && $tid = $term['tid']) { $translations[$lang] = $tid; } } // Now set a translation set with all these terms. if (count($translations)) { $trid = (is_numeric($trid) && $trid) ? $trid : db_next_id('{term_data}_trid'); db_query('UPDATE {term_data} SET trid = %d WHERE tid IN(%s)', $trid, implode(',',$translations)); } } /** * Converts a list of arrays to an array of the form keyfield => namefield */ function translation_array2list($data, $keyfield, $namefield = 'name') { foreach ($data as $key => $value) { if (is_array($data)) { $list[$value[$keyfield]] = $value[$namefield]; } else { $list[$value->$keyfield] = $value->$namefield; } } return $list; } /** * Get term translations * * @return * An array of the from lang => Term */ function translation_term_get_translations($params, $getall = TRUE) { foreach($params as $field => $value) { $conds[] = "i.$field = '%s'"; $values[] = $value; } if(!$getall){ // If not all, a parameter must be tid $conds[] = "t.tid <> %d"; $values[] = $params['tid']; } $conds[] = "t.trid <> 0"; $sql = 'SELECT t.* FROM {term_data} t INNER JOIN {term_data} i ON t.trid = i.trid WHERE '. implode(' AND ', $conds);; $result = db_query($sql, $values); $items = array(); while ($data = db_fetch_object($result)) { $items[$data->language] = $data; } return $items; } /** * Produces url of translated page */ function translation_url($url, $lang) { global $i18n_langpath; // If !url get from original request if (!$url) { $url = _i18n_get_original_path(); } // If url has lang_prefix, remove it i18n_get_lang_prefix($url, true); // are we looking at a node? if (preg_match("/^(node\/)([0-9]*)(.*)$/",$url,$matches)) { if ($nid = translation_node_nid($matches[2], $lang)) { $url = "node/$nid".$matches[3]; } } // or a taxonomy term elseif (preg_match("/^(taxonomy\/term\/)([^\/]*)(.*)$/",$url,$matches)) {//or at a taxonomy-listing? if ($str_tids = translation_taxonomy_tids($matches[2], $lang)) { $url = "taxonomy/term/$str_tids". $matches[3]; } } return $url; } /** * Get translated node's nid * * @param $nid * Node nid to search for translation * @param $language * Language to search for the translation, defaults to current language * @param $default * Value that will be returned if no translation is found * @return * Translated node nid if exists, or $default */ function translation_node_nid($nid, $language = NULL, $default = NULL) { $translation = db_result(db_query("SELECT n.nid FROM {i18n_node} n INNER JOIN {i18n_node} a ON n.trid = a.trid AND n.nid <> a.nid WHERE a.nid = %d AND n.language = '%s' AND n.trid is not null AND n.trid <> 0", $nid, $language ? $language : i18n_get_lang())); return $translation ? $translation : $default; } /** * Get translated term's tid * * @param $tid * Node nid to search for translation * @param $language * Language to search for the translation, defaults to current language * @param $default * Value that will be returned if no translation is found * @return * Translated term tid if exists, or $default */ function translation_term_tid($tid, $language = NULL, $default = NULL) { $translation = db_result(db_query("SELECT t.tid FROM {term_data} t INNER JOIN {term_data} a ON t.trid = a.trid AND t.tid <> a.tid WHERE a.tid = %d AND t.language = '%s' AND t.trid is not null AND t.trid <> 0", $tid, $language ? $language : i18n_get_lang())); return $translation ? $translation : $default; } /** * Returns an url for the translated taxonomy-page, if exists */ function translation_taxonomy_tids($str_tids, $lang) { if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str_tids)) { $separator = '+'; // The '+' character in a query string may be parsed as ' '. $tids = preg_split('/[+ ]/', $str_tids); } else if (preg_match('/^([0-9]+,)*[0-9]+$/', $str_tids)) { $separator = ','; $tids = explode(',', $str_tids); } else { return; } $translated_tids = array(); foreach ($tids as $tid) { if ($translated_tid = translation_term_tid($tid, $lang)) { $translated_tids[] = $translated_tid; } } return implode($separator, $translated_tids); } /** * Returns an array of links for all languages * * @param $path * Drupal internal path * @param $query * Query string * @param $names * Names to use for the links. Defaults to native language names */ function translation_get_links($path = '', $query = NULL, $names = NULL) { $current = i18n_get_lang(); $names = $names ? $names : i18n_languages('native'); foreach (array_keys(i18n_supported_languages()) as $lang){ $url = translation_url($path, $lang); $links[]= theme('i18n_link', $names[$lang], i18n_path($url, $lang) , $lang, $query); } return $links; } /** * Return status for translations workflow */ function _translation_status(){ return array( TRANSLATION_STATUS_NONE => t('None'), TRANSLATION_STATUS_SOURCE => t('Source content (to be translated)'), TRANSLATION_STATUS_WORKING => t('Translation in progress'), TRANSLATION_STATUS_TRANSLATED => t('Translated content'), TRANSLATION_STATUS_UPDATED => t('Source updated (to update translation)') ); } /** * Theme a link to node translation * * Since Drupal 5, returns array instead of html */ function theme_translation_node_link($node, $lang, $baselang = NULL, $title = FALSE){ $baselang = $baselang ? $baselang : $lang; if($title){ $name = $node->title; } else { $languages = i18n_languages('native'); $name = $languages[$lang]; } return array( 'title' => $name, 'href' => i18n_path('node/'.$node->nid, $baselang) ); } /** * Theme a link for a translation */ function theme_translation_link($text, $target, $lang, $separator=' ') { return theme('i18n_link', $text, $target, $lang, $separator); } /** * Theme list of node translations */ function theme_translation_node_list($list){ $header = array(t('Language'), t('Title')); // Use full language list with English names, then localize $languages = i18n_languages('name'); foreach($list as $lang => $node){ $rows[] = array($languages[$lang], l($node->title, 'node/'.$node->nid) ); } return theme('table', $header, $rows); }