5 * Internationalization (i18n) package.
7 * Translation module: translation
9 * @author Jose A. Reyero, 2004, http://www.reyero.net
13 // Status for translations
14 define('TRANSLATION_STATUS_NONE', 0);
15 define('TRANSLATION_STATUS_SOURCE', 1);
16 define('TRANSLATION_STATUS_WORKING', 2);
17 define('TRANSLATION_STATUS_TRANSLATED', 3);
18 define('TRANSLATION_STATUS_UPDATED', 4);
21 * Implementation of hook_help().
23 function translation_help($section = 'admin/help#translation' ) {
25 case
'admin/help#translation' :
26 $output = '<p>'.
t('This module is part of i18n package and provides support for translation relationships.').
'</p>';
27 $output .
= '<p>'.
t('The objects you can define translation relationships for are:').
'</p>';
29 $output .
= '<li>'.
t('Nodes.').
'</li>';
30 $output .
= '<li>'.
t('Taxonomy Terms').
'</li>';
32 $output .
= '<p>'.
t('Additional features:').
'</p>';
34 $output .
= '<li>'.
t('<i>Translations</i> block that looks like the language switcher provided by i18n module but also links to translations when available.').
'</li>';
35 $output .
= '<li>'.
t('Basic translation workflow and administration page for content translation.').
'</li>';
36 $output .
= '<li>'.
t('Links for node translations that can be displayed below each node, depending on module settings.').
'</li>';
38 $output .
= '<p>'.
t('For more information please read the <a href="@i18n">on-line help pages</a>.', array('@i18n' =>'http://drupal.org/node/31631')) .
'</p>';
41 case
'admin/access#translation':
42 $output = t('<h2>Translations</h2>');
43 $output = t('<strong>translate nodes</strong> <p>This one, combined with create content permissions, will allow to create node translation</p>');
49 * Implementation of hook_menu().
51 function translation_menu($may_cache) {
56 'path' => 'admin/settings/i18n/translation',
57 'title' => t('Translation'),
58 'callback' => 'drupal_get_form',
59 'callback arguments' => array('translation_admin_settings'),
60 'access' => user_access('administer site configuration'),
61 'type' => MENU_LOCAL_TASK
,
64 'path' => 'admin/content/translation',
65 'title' => t('Translations'),
66 'description' => t("Manage content translations."),
67 'callback' => 'translation_admin_content',
69 'access' => user_access('translate nodes'));
70 $items[] = array('path' => 'admin/content/translation/overview', 'title' => t('List'),
71 'type' => MENU_DEFAULT_LOCAL_TASK
, 'weight' => -10);
74 if (arg(0) == 'node' && is_numeric(arg(1)) && variable_get('i18n_node_'.
translation_get_node_type(arg(1)), 0)) {
75 $access = user_access('translate nodes');
76 $type = MENU_LOCAL_TASK
;
78 'path' => 'node/'.
arg(1) .
'/translation',
79 'title' => t('Translation'),
80 'callback' => 'translation_node_page',
85 if(arg(0) == 'admin' && arg(1) == 'content' && arg(2) == 'taxonomy' && is_numeric(arg(3)) ) {
87 'path' => 'admin/content/taxonomy/'.
arg(3).
'/translation',
88 'title' => t('Translation'),
89 'callback' => 'translation_taxonomy_admin',
90 'access' => user_access('administer taxonomy'),
91 'type' => MENU_LOCAL_TASK
);
94 // Special redirections and rewrite conditions
95 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()) ) {
96 // Special redirection for product types
97 if (arg(2) == 'product' && !arg(3) && is_numeric($source_nid) && $ptype = db_result(db_query("SELECT ptype FROM {ec_product} WHERE nid = %d", $source_nid))) {
98 drupal_goto(url("node/add/product/$ptype", "translation=$source_nid&language=$lang", NULL
, TRUE
));
100 // Change rewrite conditions when translating node
101 i18n_selection_mode('translation', db_escape_string($lang));
109 * Implementation of hook_perm
111 function translation_perm(){
112 return array('translate nodes');
116 * Form builder function: settings form
118 function translation_admin_settings(){
119 $form['i18n_translation_links'] = array(
121 '#title' => t('Language Management'),
122 '#default_value' => variable_get('i18n_translation_links', 0),
123 '#options' => array(t('Interface language depends on content.'), t('Interface language is independent')),
124 '#description' => t("Whether the whole page should switch language when clicking on a node translation or not."),
126 $form['i18n_translation_node_links'] = array(
128 '#title' => t('Links to node translations'),
129 '#default_value' => variable_get('i18n_translation_node_links', 0),
130 '#options' => array(t('None.'), t('Main page only'), t('Teaser and Main page')),
131 '#description' => t("Links from nodes to translated versions."),
133 $form['i18n_translation_workflow'] = array(
135 '#title' => t('Translation workflow'),
136 '#default_value' => variable_get('i18n_translation_workflow', 1),
137 '#options' => array(t('Disabled'), t('Enabled')),
138 '#description' => t("If enabled some worklow will be provided for content translation."),
140 return system_settings_form($form);
144 * Implementation of hook_block().
146 * This is a simple language switcher which knows nothing about translations
148 function translation_block($op = 'list', $delta = 0) {
150 $blocks[0]['info'] = t('Translations');
152 elseif($op == 'view') {
153 $blocks['subject'] = t('Languages');
154 $query = drupal_query_string_encode($_GET, array('q'));
155 $blocks['content'] = theme('item_list', translation_get_links($_GET['q'], empty($query) ? NULL
: $query));
162 * Implementation of hook_form_alter().
164 function translation_form_alter($form_id, &$form) {
165 // Node add/edit form
166 if (isset($form['type']) && $form['type']['#value'] .
'_node_form' == $form_id && variable_get('i18n_node_'.
$form['type']['#value'], 0)) {
167 $node = $form['#node'];
168 $languages = i18n_supported_languages();
169 // Translation workflow, default
170 if ($node->nid
&& $node->translation
) {
171 $translations = $node->translation
;
173 elseif (isset($_GET['translation']) && user_access('translate nodes')) {
174 // We are translating a node: node/add/type/translation/nid/lang
175 $translation_nid = $_GET['translation'];
176 $language = $_GET['language'];
178 // Load the node to be translated and populate fields
179 $trans = node_load($translation_nid);
180 $form['i18n']['translation_nid'] = array('#type' => 'hidden', '#value' => $translation_nid);
181 $form['i18n']['language']['#default_value'] = $language;
183 // Do not allow language change
184 $form['i18n']['language']['#disabled'] = TRUE
;
185 $form['i18n']['language']['#description'] = t('Language cannot be changed while creating a translation.');
186 //translation_node_populate_fields($trans, $form, $node);
189 $form['i18n']['trid'] = array('#type' => 'hidden', '#value' => $trans->trid
);
191 // Translations are taken from source node
192 $translations = $trans->translation
;
193 $translations[$trans->language
] = $trans;
195 // Display translations and restrict languages
197 // Unset invalid languages
198 foreach(array_keys($translations) as
$lang) {
199 unset($form['i18n']['language']['#options'][$lang]);
201 // Add translation list
202 $form['i18n']['#title'] = t('Language and translations');
203 $form['i18n']['translations'] = array(
205 '#value' => theme('translation_node_list', $translations, FALSE
)
208 // Translation workflow
209 if (variable_get('i18n_translation_workflow', 1) && (user_access('translate nodes') || user_access('administer nodes'))) {
210 $form['i18n']['i18n_status'] = array(
212 '#title' => t('Translation workflow'),
213 '#options' => _translation_status(),
214 '#description' => t('Use the translation workflow to keep track of content that needs translation.'));
216 $form['i18n']['i18n_status']['#default_value'] = isset($node->i18n_status
) ?
$node->i18n_status
: TRANSLATION_STATUS_NONE
;
217 } elseif(isset($trans)) {
218 $form['i18n']['i18n_status']['#default_value'] = TRANSLATION_STATUS_WORKING
;
221 $form['i18n']['i18n_status'] = array('#type' => 'value', '#value' => isset($node->i18n_status
) ?
$node->i18n_status
: TRANSLATION_STATUS_NONE
);
223 // Clone files for original node ?
224 if (isset($trans) && is_array($trans->files
) && count($trans->files
)) {
225 $form['i18n']['translation_files'] = array(
226 '#type' => 'fieldset',
227 '#title' => t('Files from translated content'),
229 '#prefix' => '<div class="attachments">',
230 '#suffix' => '</div>',
231 '#theme' => 'upload_form_current',
232 '#description' => t('You can remove the files for this translation or keep the original files and translate the description.')
234 foreach($trans->files as
$key => $file) {
235 $description = file_create_url((strpos($file->fid
, 'upload') === false ?
$file->filepath
: file_create_filename($file->filename
, file_create_path())));
236 $description = "<small>".
check_plain($description) .
"</small>";
237 $form['i18n']['translation_files'][$key]['description'] = array('#type' => 'textfield', '#default_value' => (strlen($file->description
)) ?
$file->description
: $file->filename
, '#maxlength' => 256, '#description' => $description );
238 $form['i18n']['translation_files'][$key]['size'] = array('#type' => 'markup', '#value' => format_size($file->filesize));
239 $form['i18n']['translation_files'][$key]['remove'] = array('#type' => 'checkbox', '#default_value' => 0);
240 $form['i18n']['translation_files'][$key]['list'] = array('#type' => 'checkbox', '#default_value' => $file->list);
241 $form['i18n']['translation_files'][$key]['fid'] = array('#type' => 'value', '#value' => $file->fid
);
248 * Implementation of hook_nodeapi().
250 * Delete case is now handled in i18n_nodeapi
252 function translation_nodeapi(&$node, $op, $arg = 0) {
253 if (variable_get("i18n_node_$node->type", 0)) {
256 $node->translation
= translation_node_get_translations(array('nid' =>$node->nid
), FALSE
);
259 if($node->translation_nid
) {
260 // If not existing translation set, update both nodes. Otherwise trid is saved by i18n module
262 $node->trid
= db_next_id('{i18n_node}_trid');
263 db_query("UPDATE {i18n_node} SET trid = %d WHERE nid=%d OR nid=%d", $node->trid
, $node->nid
, $node->translation_nid
);
265 // Clone files for node attachments
266 if(isset($node->translation_files
)) {
267 foreach($node->translation_files as
$fid => $file) {
268 if(!$file['remove']) {
269 // We are using revisions to have a file linked to different nodes, different descriptions
270 db_query("INSERT INTO {file_revisions} (fid, vid, list, description) VALUES (%d, %d, %d, '%s')", $file['fid'], $node->vid
, $file['list'], $file['description']);
276 case
'prepare': // When creating a translation
277 if (!$node->nid
&& isset($_GET['translation']) && ($nid = $_GET['translation']) && ($language = $_GET['language']) && user_access('translate nodes')) {
278 // We are translating a node from a source node
279 // Load the node to be translated and populate fields
280 $node->translation_nid
= $nid;
281 $node->language
= $language;
282 $source = $node->translation_source
= node_load($node->translation_nid
);
284 // Taxonomy translation
285 if(is_array($source->taxonomy
)) {
286 // Set translated taxonomy terms
287 $node->taxonomy
= array();
288 foreach ($source->taxonomy as
$tid => $term) {
289 if ($term->language
) {
290 $translated_terms = translation_term_get_translations(array('tid' =>$tid));
291 if($translated_terms && $newterm = $translated_terms[$node->language
]) {
292 $node->taxonomy
[$newterm->tid
] = $newterm;
293 //drupal_set_message("DEBUG: Translated term $tid to ". $newterm->tid);
296 // Term has no language. Should be ok
297 $node->taxonomy
[$tid] = $term;
302 // Translations are taken from source node
303 $node->translation
= $source->translation
;
304 $node->translation
[$source->language
] = $source;
306 // Rest of fields. Unset some known ones not to be copied over
307 unset($source->nid
, $source->vid
, $source->path
, $source->language
, $source->files
);
308 foreach($source as
$field => $value) {
309 if (!isset($node->$field)) {
310 $node->$field = $value;
320 * Fills up some fields from source node
322 function translation_node_populate_fields($source, &$form, &$node, $level = 0){
324 $language = $form['i18n']['language']['#default_value'];
325 foreach(element_children($form) as
$key) {
326 if($key == 'nid' || $key == 'vid' || $key == 'i18n'){
328 } elseif(isset($source->$key) && !isset($node->$key)) {
329 if($level == 0 && $key == 'parent' && is_numeric($source->parent
)) {
330 // Translate book outline
331 $trans = translation_node_get_translations(array('nid' => $source->parent
));
332 if(isset($trans[$language])) {
333 $form['parent']['#default_value'] = $trans[$language]->nid
;
336 $node->$key = $form[$key]['#default_value'] = $source->$key;
339 } elseif(!isset($form[$key]['#tree'])) {
340 translation_node_populate_fields($source, $form[$key], $node, $level +1);
344 //if($fields) drupal_set_message("Populated fields ($level): ". implode(', ', $fields));
348 * Multilingual Nodes support
352 * This is the callback for the tab 'translation' for nodes
354 function translation_node_page() {
355 $args = func_get_args();
358 $node = node_load($nid);
359 // If node has no language, just warning message. Function returns here
360 if(!$node->language
) {
361 form_set_error('language', t("You need to set a language before creating a translation."));
362 drupal_goto("node/$nid/edit");
364 drupal_set_title($node->title
);
368 $output .
= translation_node_overview($node);
369 $output .
= translation_node_select($node, $args[1]);
373 db_query("UPDATE {i18n_node} SET trid = NULL WHERE nid=%d", $node->nid
);
374 drupal_set_message("The node has been removed from the translation set");
375 drupal_goto("node/$node->nid/translation");
377 $output .
= translation_node_overview($node);
383 * Form builder function
385 function translation_node_form($node, $lang, $list){
386 $form['node'] = array('#type' => 'value', '#value' =>$node);
387 $form['language'] = array('#type' => 'hidden', '#value' => $lang);
388 $form['source_nid'] = array('#type' => 'hidden', '#value' => $node->nid
);
389 $form['trid'] = array('#type' => 'hidden', '#value' => $node->trid
);
390 $languages = i18n_supported_languages();
392 $form['nodes']['nid'] = array(
393 '#type' => 'radios', '#title' => t('Select translation for %language', array('%language' => $languages[$lang])),
394 '#default_value' => isset($node->translation
[$lang]) ?
$node->translation
[$lang]->nid
: '',
395 '#options' => $list);
396 $form['pager'] = array('#value' => theme('pager'));
397 $form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
402 * Menu callback: administration page for node translations
404 function translation_admin_content() {
406 $defaults = array('translation_language' => i18n_get_lang(), 'source_status' => TRANSLATION_STATUS_SOURCE
);
408 $output .
= drupal_get_form('node_filter_form');
409 $output .
= drupal_get_form('translation_filter_form', $defaults);
410 // Node listing depending on previous forms
411 $output .
= drupal_get_form('translation_admin_nodes');
417 * Form builder. Administrative form for node translations
419 function translation_admin_nodes() {
420 // First, translation filter because it may need parameters for join conditions
421 $filter = translation_build_filter_query();
422 $where = $filter['where'];
423 $params = $filter['args'];
424 $join = $filter['join'];
426 $filter = node_build_filter_query();
428 if($filter['where']) {
429 $where += array(str_replace('WHERE', '', $filter['where']));
432 $params = array_merge($params, $filter['args']);
434 $join .
= $filter['join'];
436 $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 ".
437 "FROM {node} n INNER JOIN {users} u ON n.uid = u.uid INNER JOIN {i18n_node} i ON n.nid = i.nid ".
438 "LEFT JOIN {i18n_node} i2 ON i.trid = i2.trid AND 0 != i2.trid AND i.language != i2.language $join".
439 (count($where) ?
' WHERE '.
implode(' AND ', $where) : '');
442 //drupal_set_message("DEBUG:query: $sql");
443 //drupal_set_message("DEBUG:params: ".implode(', ', $params));
444 i18n_selection_mode('off');
445 $result = pager_query(db_rewrite_sql($sql), 50, 0, NULL
, $params);
446 i18n_selection_mode('reset');
448 $languages = i18n_supported_languages();
449 $translation_status = _translation_status();
451 // Fetch language for translations
452 $language = isset($_SESSION['translation_filter']['translation_language']) ?
$_SESSION['translation_filter']['translation_language'] : i18n_get_lang();
453 $destination = drupal_get_destination();
454 while ($node = db_fetch_object($result)) {
455 $nodes[$node->nid
] = '';
456 $form['language'][$node->nid
] = array('#value' => $languages[$node->language
]);
457 $form['title'][$node->nid
] = array('#value' => l($node->title
, 'node/'.
$node->nid
) .
' '.
theme('mark', node_mark($node->nid
, $node->changed
)));
458 $form['name'][$node->nid
] = array('#value' => node_get_types('name', $node));
459 $form['username'][$node->nid
] = array('#value' => theme('username', $node));
460 $form['status'][$node->nid
] = array('#value' => ($node->status ?
t('published') : t('not published')));
461 if ($node->translation_nid
) {
462 $form['translation_status'][$node->nid
] = array('#value' => $translation_status[$node->translation_status
]);
463 $form['operations'][$node->nid
] = array('#value' => l(t('edit translation'), 'node/'.
$node->translation_nid .
'/edit', array(), $destination));
465 $form['translation_status'][$node->nid
] = array('#value' => '--');
466 if($language == $node->language
) {
467 $form['operations'][$node->nid
] = array('#value' => '--');
469 $form['operations'][$node->nid
] = array('#value' => l(t('create translation'), 'node/add/'.
$node->type
, array(), "translation=$node->nid&language=$language").
470 ' | '.
l(t('select node'), "node/$node->nid/translation/select/$language", array(), $destination));
475 $form['nodes'] = array('#type' => 'checkboxes', '#options' => $nodes);
477 $form['pager'] = array('#value' => theme('pager', NULL
, 50, 0));
483 * Build query for node administration filters based on session.
485 function translation_build_filter_query() {
488 $where = $args = array();
490 // This will produce an empty join
491 if (!is_array($_SESSION['translation_filter'])) {
492 $_SESSION['translation_filter'] = array();
494 foreach ($_SESSION['translation_filter'] as
$type => $value) {
496 case
'source_language':
497 $where[] = "i.language = '%s'";
500 case
'translation_language':
501 $join .
= " AND i2.language ='".
db_escape_string($value).
"' ";
503 case
'source_status':
504 $where[] = "i.status = %d";
507 case
'translation_status':
508 $join .
= " AND i2.status = ".
db_escape_string($value);
513 return array('where' => $where, 'join' => $join, 'args' => $args);
517 * Returns form for translation administration filters.
519 function translation_filter_form($defaults = array()) {
520 $session = &$_SESSION['translation_filter'];
521 $session = is_array($session) ?
$session : $defaults;
523 // Save defaults for form reset
524 $form['_defaults'] = array('#type' => 'value', '#value' => $defaults);
525 $form['filters'] = array('#type' => 'fieldset',
526 '#title' => t('And translation conditions are'),
528 $languages = i18n_supported_languages();
529 // Translation and language conditions
531 $form['filters']['source_language'] = array('#type' => 'select', '#title' => t('source language'),
532 '#options' => array('' => '') + $languages, '#default_value' => $session['source_language']);
533 $form['filters']['source_status'] = array('#type' => 'select', '#title' => t('source status'),
534 '#options' => array('' => '') + _translation_status(), '#default_value' => $session['source_status']);
535 $form['filters']['translation_language'] = array('#type' => 'select', '#title' => t('translation language'),
536 '#options' => array('' => '') + $languages, '#default_value' => $session['translation_language']);
537 $form['filters']['translation_status'] = array('#type' => 'select', '#title' => t('translation status'),
538 '#options' => array('' => '') + _translation_status(), '#default_value' => $session['translation_status']);
539 $form['filters']['buttons']['submit'] = array('#type' => 'submit', '#value' => t('Filter'));
540 if (count($session)) {
541 $form['filters']['buttons']['reset'] = array('#type' => 'submit', '#value' => t('Reset'));
546 function theme_translation_filter_form($form) {
547 $form['filters']['source_language']['#prefix'] = '<table><tr><td>';
548 $form['filters']['source_status']['#suffix'] = '</td><td>';
549 $form['filters']['buttons']['#prefix'] = '</td><td>';
550 $form['filters']['buttons']['#suffix'] = '</td></tr></table>';
551 return drupal_render($form);
554 * Builds translation admin filters
556 function translation_filter_form_submit($form_id, $form_values) {
558 $session = &$_SESSION['translation_filter'];
563 foreach($form_values as
$name => $value) {
564 if($value) $session[$name] = $value;
568 $session = $form_values['_defaults'];
575 * Theme node administration overview.
577 function theme_translation_admin_nodes($form) {
579 $header = array(t('Language'), t('Title'), t('Type'), t('Author'), t('Status'), t('Translation status'), t('Operations'));
581 $output .
= drupal_render($form['options']);
582 if (isset($form['title']) && is_array($form['title'])) {
583 foreach (element_children($form['title']) as
$key) {
585 $row[] = drupal_render($form['language'][$key]);
586 $row[] = drupal_render($form['title'][$key]);
587 $row[] = drupal_render($form['name'][$key]);
588 $row[] = drupal_render($form['username'][$key]);
589 $row[] = drupal_render($form['status'][$key]);
590 $row[] = drupal_render($form['translation_status'][$key]);
591 $row[] = drupal_render($form['operations'][$key]);
597 $rows[] = array(array('data' => t('No posts available.'), 'colspan' => '6'));
600 $output .
= theme('table', $header, $rows);
601 if ($form['pager']['#value']) {
602 $output .
= drupal_render($form['pager']);
605 $output .
= drupal_render($form);
611 * Shows overview of current translations plus remove button
613 function translation_node_overview($node) {
614 $languages = i18n_supported_languages();
615 unset($languages[$node->language
]);
616 // $output = t('<h2>Current translations</h2>');
617 $header = array(t('Language'), t('Title'), t('Status'), t('Options'));
618 // Special links for product nodes
619 $createlink = $node->type
== 'product' ?
"node/add/$node->type/$node->ptype" : "node/add/$node->type";
620 foreach($languages as
$lang => $langname){
622 if(isset($node->translation
[$lang])){
623 $trnode = $node->translation
[$lang];
624 $title = l($trnode->title
, 'node/'.
$trnode->nid
);
625 $status = $trnode->status ?
t('Published') : t('Not published');
627 $title = t('Not translated');
628 $options[] = l(t('create translation'), $createlink, array(), "translation=$node->nid&language=$lang");
631 $options[] = l(t('select node'), "node/$node->nid/translation/select/$lang");
632 $rows[] = array($langname, $title, $status, implode(" | ", $options));
634 $output .
= theme('table', $header, $rows);
636 $output .
= drupal_get_form('translation_node_remove', $node);
638 return theme('box', t('Current translations'), $output);
642 * Form to remove node from translation set
644 function translation_node_remove($node) {
645 $form['submit'] = array('#type' => 'submit', '#value' => t('Remove'), '#suffix' => t('Remove node from this translation set'));
646 $form['#action'] = url('node/'.
$node->nid.
'/translation/remove');
651 * Form to select a translation from existing nodes
653 function translation_node_select($node, $lang) {
654 $languages = i18n_supported_languages();
655 // Disable i18n rewrite. Order by trid to show first nodes with no translation
656 i18n_selection_mode('off');
657 $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);
658 i18n_selection_mode('reset');
659 while($trnode = db_fetch_object($result)){
660 $list[$trnode->nid
] = l($trnode->title
, "node/$trnode->nid") ;
663 return drupal_get_form('translation_node_form', $node, $lang, $list);
665 return t("<p>No nodes available in %language</p>", array('%language' => $languages[$lang]) );
670 * Process translation node form
672 function translation_node_form_submit($form_id, $form_values){
674 $source_nid = $form_values['source_nid'];
675 $language = $form_values['language'];
676 $nid = $form_values['nid'];
677 if( $source_nid && $language && $nid ) {
678 if($trid = $form_values['trid']){
679 // Delete old translations
680 db_query("UPDATE {i18n_node} SET trid = 0 WHERE trid = %d AND language = '%s'", $trid, $language);
682 $trid = db_next_id('{i18n_node}_trid');
684 db_query("UPDATE {i18n_node} SET trid = %d WHERE nid=%d OR nid=%d", $trid, $source_nid, $nid);
685 drupal_set_message(t('The translation has been saved'));
691 * Implementation of hook taxonomy.
693 function translation_taxonomy($op, $type = NULL
, $edit = NULL
) {
694 switch ("$type/$op") {
697 if (!$edit['language'] && $edit['trid']) {
698 // Removed language, remove trid
699 db_query('UPDATE {term_data} SET trid=0 WHERE tid=%d', $edit['tid']);
700 if(db_affected_rows()) drupal_set_message(t('Removed translation information from term'));
707 * Implementation of hook_link().
709 function translation_link($type, $node = NULL
, $teaser = FALSE
) {
710 $languages = i18n_supported_languages();
712 if ($type == 'node' && variable_get('i18n_translation_node_links', 0) > ($teaser ?
1 : 0) && $node->translation
) {
713 foreach ($node->translation as
$lang => $trnode) {
714 // Add node link if published
715 if($trnode->status
) {
716 $baselang = variable_get('i18n_translation_links', 0) ?
i18n_get_lang() : $lang;
717 $links['translation-'.
$lang]= theme('translation_node_link', $trnode , $lang, $baselang);
725 * Returns a list for terms for vocabulary, language
727 function translation_vocabulary_get_terms($vid, $lang, $status = 'all') {
730 $andsql = ' AND trid > 0';
733 $andsql = ' AND trid = 0';
738 $result = db_query("SELECT * FROM {term_data} WHERE vid=%d AND language='%s' $andsql", $vid, $lang);
740 while ($term = db_fetch_object($result)) {
741 $list[$term->tid
] = $term->name
;
750 * array of parameters
752 * TRUE to get the also node itself
754 function translation_node_get_translations($params, $getall = TRUE
) {
755 foreach($params as
$field => $value) {
756 $conds[] = "b.$field = '%s'";
759 if(!$getall){ // If not all, a parameter must be nid
760 $conds[] = "n.nid != %d";
761 $values[] = $params['nid'];
763 $conds[] = "b.trid != 0";
764 $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);
766 i18n_selection_mode('off');
767 $result = db_query(db_rewrite_sql($sql), $values);
768 i18n_selection_mode('reset');
771 while ($node = db_fetch_object($result)) {
772 $items[$node->language
] = $node;
778 * Returns node type for nid
780 function translation_get_node_type($nid) {
781 return db_result(db_query('SELECT type FROM {node} WHERE nid=%d', $nid));
785 * Multilingual Taxonomy
790 * This is the callback for taxonomy translations
793 * admin/content/taxonomy/i18n/term/xx
794 * admin/content/taxonomy/i18n/term/new/xx
795 * admin/content/taxonomy/vid/translation/op/trid
798 function translation_taxonomy_admin() {
800 $op = $_POST['op'] ?
$_POST['op'] : arg(5);
801 $edit = $_POST['edit'];
806 drupal_set_title(t('Edit term translations'));
807 $output = drupal_get_form('translation_taxonomy_term_form', $vid, arg(6), $edit);
810 drupal_set_title(t('Submit'));
811 translation_taxonomy_term_save($edit);
812 $output = translation_taxonomy_overview($vid);
815 //print theme('page', node_delete($edit), t('Delete'));
818 $output = translation_taxonomy_overview($vid);
824 * Generate a tabular listing of translations for vocabularies.
826 function translation_taxonomy_overview($vid) {
827 $vocabulary = taxonomy_get_vocabulary($vid);
828 drupal_set_title(check_plain($vocabulary->name
));
830 $languages = i18n_supported_languages();
831 $header = array_merge($languages, array(t('Operations')));
834 // Get terms/translations for this vocab
835 $result = db_query('SELECT * FROM {term_data} t WHERE vid=%d',$vocabulary->vid
);
837 while ($term = db_fetch_object($result)) {
838 if($term->trid
&& $term->language
) {
839 $terms[$term->trid
][$term->language
] = $term;
842 // Reorder data for rows and languages
843 foreach ($terms as
$trid => $terms) {
845 foreach ($languages as
$lang => $name) {
846 if (array_key_exists($lang, $terms)) {
847 $thisrow[] = $terms[$lang]->name
;
853 $thisrow[] = l(t('edit'), "admin/content/taxonomy/$vid/translation/edit/$trid");
856 $output .
= theme('table', $header, $rows);
857 $output .
= l(t('new translation'), "admin/content/taxonomy/$vid/translation/edit/new");
862 * Produces a vocabulary translation form
864 function translation_taxonomy_term_form($vid, $trid = NULL
, $edit = array()) {
865 $languages = i18n_supported_languages();
866 if ($trid == 'new') {
867 $translations = array();
869 $form['trid'] = array('#type' => 'hidden', '#value' => $trid);
870 $translations = translation_term_get_translations(array('trid' =>$trid));
872 //var_dump($translations);
873 $vocabulary = taxonomy_get_vocabulary($vid);
875 // List of terms for languages
876 foreach ($languages as
$lang => $langname) {
877 $current = isset($translations[$lang]) ?
$translations[$lang]->tid
: '';
878 $list = translation_vocabulary_get_terms($vid, $lang, 'all');
880 $form[$lang] = array('#type' => 'fieldset', '#tree' => TRUE
);
881 $form[$lang]['tid'] = array(
883 '#title' => $langname,
884 '#default_value' => $current,
887 $form[$lang]['old'] = array('#type' => 'hidden', '#value' =>$current);
889 $form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
890 $form['destination'] = array('#type' => 'hidden', '#value' => 'admin/content/taxonomy/'.
arg(3).
'/translation');
895 * Form callback: Process vocabulary translation form
897 function translation_taxonomy_term_form_submit($form_id, $form_values) {
898 $trid = $form_values['trid'];
899 $languages = i18n_supported_languages();
900 $translations = array();
901 // Delete old translations
903 db_query("UPDATE {term_data} SET trid = 0 WHERE trid= %d", $trid);
905 foreach ($languages as
$lang => $name) {
906 if (is_numeric($form_values[$lang]['tid'])) {
907 $translations[$lang] = $form_values[$lang]['tid'];
910 if(count($translations)) {
911 $trid = is_numeric($trid) ?
$trid : db_next_id('{term_data}_trid');
912 db_query('UPDATE {term_data} SET trid=%d WHERE tid IN(%s)', $trid, implode(',',$translations));
914 drupal_set_message(t('Term translations have been updated'));
918 * Converts a list of arrays to an array of the form keyfield => namefield
920 function translation_array2list($data, $keyfield, $namefield = 'name') {
921 foreach ($data as
$key => $value) {
922 if (is_array($data)) {
923 $list[$value[$keyfield]] = $value[$namefield];
926 $list[$value->$keyfield] = $value->$namefield;
933 * Get term translations
936 * An array of the from lang => Term
938 function translation_term_get_translations($params, $getall = TRUE
) {
939 foreach($params as
$field => $value) {
940 $conds[] = "i.$field = '%s'";
943 if(!$getall){ // If not all, a parameter must be tid
944 $conds[] = "t.tid != %d";
945 $values[] = $params['tid'];
947 $conds[] = "t.trid != 0";
948 $sql = 'SELECT t.* FROM {term_data} t INNER JOIN {term_data} i ON t.trid = i.trid WHERE '.
implode(' AND ', $conds);;
949 $result = db_query($sql, $values);
951 while ($data = db_fetch_object($result)) {
952 $items[$data->language
] = $data;
958 * Produces url of translated page
960 function translation_url($url, $lang) {
961 global $i18n_langpath;
962 // If !url get from original request
964 $url = _i18n_get_original_path();
966 // If url has lang_prefix, remove it
967 i18n_get_lang_prefix($url, true
);
968 // are we looking at a node?
969 if (preg_match("/^(node\/)([0-9]*)$/",$url,$matches)) {
970 if ($nid = translation_node_nid($matches[2], $lang)) {
974 // or a taxonomy term
975 elseif (preg_match("/^(taxonomy\/term\/)([^\/]*)$/",$url,$matches)) {//or at a taxonomy-listing?
976 if ($str_tids = translation_taxonomy_tids($matches[2], $lang)) {
977 $url = "taxonomy/term/$str_tids";
985 * Get translated node's nid
988 * Node nid to search for translation
990 * Language to search for the translation, defaults to current language
992 * Value that will be returned if no translation is found
994 * Translated node nid if exists, or $default
996 function translation_node_nid($nid, $language = NULL
, $default = NULL
) {
997 $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", $nid, $language ?
$language : i18n_get_lang()));
998 return $translation ?
$translation : $default;
1002 * Get translated term's tid
1005 * Node nid to search for translation
1007 * Language to search for the translation, defaults to current language
1009 * Value that will be returned if no translation is found
1011 * Translated term tid if exists, or $default
1013 function translation_term_tid($tid, $language = NULL
, $default = NULL
) {
1014 $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", $tid, $language ?
$language : i18n_get_lang()));
1015 return $translation ?
$translation : $default;
1019 * Returns an url for the translated taxonomy-page, if exists
1021 function translation_taxonomy_tids($str_tids, $lang) {
1022 if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str_tids)) {
1024 // The '+' character in a query string may be parsed as ' '.
1025 $tids = preg_split('/[+ ]/', $str_tids);
1027 else if (preg_match('/^([0-9]+,)*[0-9]+$/', $str_tids)) {
1029 $tids = explode(',', $str_tids);
1034 $translated_tids = array();
1035 foreach ($tids as
$tid) {
1036 if ($translated_tid = translation_term_tid($tid, $lang)) {
1037 $translated_tids[] = $translated_tid;
1040 return implode($separator, $translated_tids);
1044 * function translation_get_links
1046 * Returns an array of links for all languages, with or without names
1048 function translation_get_links($path = '', $query = NULL
) {
1049 $current = i18n_get_lang();
1050 foreach(i18n_supported_languages() as
$lang => $name){
1051 $url = translation_url($path, $lang);
1052 $links[]= theme('i18n_link', $name, i18n_path($url, $lang) , $lang, $query);
1058 * Return status for translations workflow
1060 function _translation_status(){
1062 TRANSLATION_STATUS_NONE
=> t('None'),
1063 TRANSLATION_STATUS_SOURCE
=> t('Source content (to be translated)'),
1064 TRANSLATION_STATUS_WORKING
=> t('Translation in progress'),
1065 TRANSLATION_STATUS_TRANSLATED
=> t('Translated content'),
1066 TRANSLATION_STATUS_UPDATED
=> t('Source updated (to update translation)'));
1070 * Theme a link to node translation
1072 * Since Drupal 5, returns array instead of html
1074 function theme_translation_node_link($node, $lang, $baselang = NULL
, $title = FALSE
){
1075 $baselang = $baselang ?
$baselang : $lang;
1078 $name = $node->title
;
1080 $languages = i18n_supported_languages();
1081 $name = $languages[$lang];
1085 'href' => i18n_path('node/'.
$node->nid
, $baselang)
1090 * Theme a link for a translation
1092 function theme_translation_link($text, $target, $lang, $separator=' ') {
1093 return theme('i18n_link', $text, $target, $lang, $separator);
1097 * Theme list of node translations
1099 function theme_translation_node_list($list){
1100 $header = array(t('Language'), t('Title'));
1101 $languages = i18n_supported_languages();
1102 foreach($list as
$lang => $node){
1103 $rows[] = array($languages[$lang], l($node->title
, 'node/'.
$node->nid
) );
1105 return theme('table', $header, $rows);