6 * Internationalization (i18n) package
8 * This module groups together all existing i18n taxonomy functionality
9 * providing several options for taxonomy translation
11 * Translates taxonomy term for selected vocabularies running them through the localization system
12 * It also translates terms for views filters and views results
14 * @author Jose A. Reyero, 2004
18 * Modes for multilingual vocabularies
20 // No multilingual options
21 define('I18N_TAXONOMY_NONE', 0);
22 // Run through the localization system
23 define('I18N_TAXONOMY_LOCALIZE', 1);
24 // Predefined language for all terms
25 define('I18N_TAXONOMY_LANGUAGE', 2);
26 // Multilingual terms, translatable
27 define('I18N_TAXONOMY_TRANSLATE', 3);
30 * Returns list of vocabulary modes
32 function _i18ntaxonomy_vocabulary_options() {
34 I18N_TAXONOMY_NONE
=> t('No translation'),
35 I18N_TAXONOMY_LOCALIZE
=> t('Localize terms'),
36 I18N_TAXONOMY_TRANSLATE
=> t('Per language terms'),
37 I18N_TAXONOMY_LANGUAGE
=> t('Set language to vocabulary'),
41 * Implementation of hook_menu().
43 function i18ntaxonomy_menu() {
44 $items['admin/content/taxonomy/%taxonomy_vocabulary/translation'] = array(
45 'title' => 'Add term',
46 'page callback' => 'i18n_taxonomy_page_vocabulary',
47 'page arguments' => array(3),
48 'type' => MENU_LOCAL_TASK
,
49 'parent' => 'admin/content/taxonomy/%taxonomy_vocabulary',
50 'file' => 'taxonomy.admin.inc',
56 * Implementation of hook_locale().
58 function i18ntaxonomy_locale($op = 'groups') {
61 return array('taxonomy' => t('Taxonomy'));
65 * Implementation of hook_alter_translation_link().
67 * Replaces links with pointers to translated versions of the content.
69 function i18ntaxonomy_translation_link_alter(&$links, $path) {
70 if (preg_match("/^(taxonomy\/term\/)([^\/]*)(.*)$/", $path, $matches)) {//or at a taxonomy-listing?
71 foreach ($links as
$langcode => $link) {
72 if ($str_tids = i18ntaxonomy_translation_tids($matches[2], $langcode)) {
73 $links[$langcode]['href'] = "taxonomy/term/$str_tids".
$matches[3];
80 * Get translated term's tid
83 * Node nid to search for translation
85 * Language to search for the translation, defaults to current language
87 * Value that will be returned if no translation is found
89 * Translated term tid if exists, or $default
91 function i18ntaxonomy_translation_term_tid($tid, $language = NULL
, $default = NULL
) {
92 $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()));
93 return $translation ?
$translation : $default;
97 * Returns an url for the translated taxonomy-page, if exists
99 function i18ntaxonomy_translation_tids($str_tids, $lang) {
100 if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str_tids)) {
102 // The '+' character in a query string may be parsed as ' '.
103 $tids = preg_split('/[+ ]/', $str_tids);
105 else if (preg_match('/^([0-9]+,)*[0-9]+$/', $str_tids)) {
107 $tids = explode(',', $str_tids);
112 $translated_tids = array();
113 foreach ($tids as
$tid) {
114 if ($translated_tid = i18ntaxonomy_translation_term_tid($tid, $lang)) {
115 $translated_tids[] = $translated_tid;
118 return implode($separator, $translated_tids);
121 * Implementation of hook_taxonomy
123 * $edit parameter may be an array or an object !!
125 function i18ntaxonomy_taxonomy($op, $type, $edit = NULL
) {
126 $edit = (array)$edit;
127 switch ("$type/$op") {
131 $language = isset($edit['language']) ?
$edit['language'] : '';
132 db_query("UPDATE {term_data} SET language='%s' WHERE tid=%d", $language, $tid);
133 // From translation module
134 if (!$language && !empty($edit['trid'])) {
135 // Removed language, remove trid
136 db_query('UPDATE {term_data} SET trid = 0 WHERE tid = %d', $tid);
137 if(db_affected_rows()) drupal_set_message(t('Removed translation information from term'));
139 // Update strings for localizable vocabulary
140 if (i18ntaxonomy_vocabulary($edit['vid']) == I18N_TAXONOMY_LOCALIZE
) {
141 tt("taxonomy:term:$tid:name", $edit['name'], NULL
, TRUE
);
142 tt("taxonomy:term:$tid:description", $edit['description'], NULL
, TRUE
);
145 case
'vocabulary/insert':
146 case
'vocabulary/update':
148 // Update vocabulary settings
149 i18ntaxonomy_vocabulary($vid, $edit['i18nmode']);
150 $language = isset($edit['language']) ?
$edit['language'] : '';
151 db_query("UPDATE {vocabulary} SET language='%s' WHERE vid = %d", $language, $edit['vid']);
152 if ($language && $op == 'update') {
153 db_query("UPDATE {term_data} SET language='%s' WHERE vid = %d", $edit['language'], $edit['vid']);
154 drupal_set_message(t('Reset language for all terms.'));
156 // Always add vocabulary translation if !$language
158 tt("taxonomy:vocabulary:$vid:name", $edit['name'], NULL
, TRUE
);
165 * Implementation of hook_db_rewrite_sql()
167 function i18ntaxonomy_db_rewrite_sql($query, $primary_table, $primary_key){
168 switch ($primary_table) {
172 // When loading specific terms, vocabs, language conditions shouldn't apply
173 if (preg_match("/WHERE.* $primary_table\.tid\s*(=\s*\d|IN)/", $query)) return;
174 // Taxonomy for specific node
175 if (preg_match("/WHERE r\.nid = \%d/", $query)) return;
176 $result['where'] = i18n_db_rewrite_where($primary_table, 'taxonomy');
182 * Implementation of hook_form_alter
184 * This is the place to add language fields to all forms
186 function i18ntaxonomy_form_alter(&$form, $form_state, $form_id) {
188 case
'taxonomy_overview_vocabularies':
189 $vocabularies = taxonomy_get_vocabularies();
190 $languages = i18n_supported_languages();
191 foreach ($vocabularies as
$vocabulary) {
192 if($vocabulary->language
) $form[$vocabulary->vid
]['type']['#value'] = $form[$vocabulary->vid
]['type']['#value'].
' ('.
$languages[$vocabulary->language
].
')';
195 case
'taxonomy_form_vocabulary': // Taxonomy vocabulary
196 if (isset($form['vid'])) {
197 $vocabulary = taxonomy_vocabulary_load($form['vid']['#value']);
198 $mode = i18ntaxonomy_vocabulary($vocabulary->vid
);
200 $mode = I18N_TAXONOMY_NONE
;
203 $form['i18n'] = array('#type' => 'fieldset', '#title' => t('Multilingual options'), '#collapsible' => TRUE
);
204 $form['i18n']['i18nmode'] = array(
206 '#title' => t('Translation mode'),
207 '#options' => _i18ntaxonomy_vocabulary_options(),
208 '#default_value' => $mode,
213 case
'taxonomy_form_term': // Taxonomy term
214 $vocabulary = taxonomy_vocabulary_load($form['vid']['#value']);
215 if (isset($form['tid']) && is_numeric($form['tid']['#value'])) {
216 $term = taxonomy_get_term($form['tid']['#value']);
218 // Add language field or not depending on taxonomy mode
219 switch (i18ntaxonomy_vocabulary($vocabulary->vid
)) {
220 case I18N_TAXONOMY_TRANSLATE
:
221 $form['language'] = array(
223 '#title' => t('Language'),
224 '#default_value' => (isset($term) ?
$term->language
: ''),
225 '#options' => array('' => '') + locale_language_list('name'),
228 case I18N_TAXONOMY_LANGUAGE
:
229 $form['language'] = array('#type' => 'value', '#value' => $vocabulary->language
);
231 case I18N_TAXONOMY_NONE
:
232 case I18N_TAXONOMY_LOCALIZE
:
233 $form['language'] = array('#type' => 'value', '#value' => '');
238 if (isset($form['type']) && $form['type']['#value'] .
'_node_form' == $form_id
239 && ($node = $form['#node']) && isset($form['taxonomy']) ) {
240 // Node form. Translate vocabularies
241 i18ntaxonomy_node_form($form);
242 } else if($form_id == 'views_filters' && $translate = variable_get('i18ntaxonomy_vocabularies', array())) {
243 // We only translate exposed filters here
244 $view = $form['view']['#value'];
245 if($view->exposed_filter
) {
246 foreach($view->exposed_filter as
$index => $filter) {
248 if($filter['field'] == 'term_node.tid') {
249 // That's a full taxonomy box. Translate options: arary(tid => "Vocabulary: Term")
250 // First, we get a translated list. Then we replace on the options array
251 $replace = _i18ntaxonomy_vocabulary_terms(array_keys($translate));
252 foreach($replace as
$tid => $name) {
253 if(isset($form["filter$index"]['#options'][$tid])) {
254 $form["filter$index"]['#options'][$tid] = $name;
257 } elseif(preg_match("/term_node_(\d+)\.tid/", $filter['field'], $matches)) {
259 if ($translate[$vid]) {
260 // Translate this vocabulary terms, field name is filter$index vid = $matches[1]
261 foreach ($form["filter$index"]['#options'] as
$value => $option) {
262 if ($value != '**ALL**') { // ALL option should be already localized
263 // This may be an object with an option property being an array (tid => name)
264 if (is_object($option) && is_array($option->option
)) {
265 foreach (array_keys($option->option
) as
$tid) {
266 $option->option
[$tid] = t($option->option
[$tid]);
268 $form["filter$index"]['#options'][$value] = $option;
269 // But it used to be a plain string, so let's keep this just in case...
270 } elseif(is_string($option)) {
271 $form["filter$index"]['#options'][$value] = t($option);
283 * Handle node form taxonomy
285 function i18ntaxonomy_node_form(&$form) {
286 $node = $form['#node'];
287 if (!isset($node->taxonomy
)) {
288 if (!empty($node->nid
)) {
289 $terms = taxonomy_node_get_terms($node->nid
);
296 $terms = $node->taxonomy
;
298 // Regenerate the whole field for translatable vocabularies
299 foreach (element_children($form['taxonomy']) as
$vid) {
300 if (is_numeric($vid) && i18ntaxonomy_vocabulary($vid) == I18N_TAXONOMY_LOCALIZE
) {
301 // Rebuild this vocabulary's form
302 $vocabulary = taxonomy_vocabulary_load($vid);
303 // Extract terms belonging to the vocabulary in question.
304 $default_terms = array();
305 foreach ($terms as
$term) {
306 if ($term->vid
== $vid) {
307 $default_terms[$term->tid
] = $term;
311 $form['taxonomy'][$vid] = i18ntaxonomy_vocabulary_form($vocabulary->vid
, array_keys($default_terms));
312 $form['taxonomy'][$vid]['#weight'] = $vocabulary->weight
;
313 $form['taxonomy'][$vid]['#required'] = $vocabulary->required
;
319 * Generate a form element for selecting terms from a vocabulary.
320 * Translates all translatable strings.
322 function i18ntaxonomy_vocabulary_form($vid, $value = 0, $help = NULL
, $name = 'taxonomy') {
323 $vocabulary = taxonomy_vocabulary_load($vid);
324 $help = $vocabulary->help ?
tt("taxonomy:vocabulary:$vid:help", $vocabulary->help
) : '';
325 if ($vocabulary->required
) {
329 $blank = '<'.
t('none') .
'>';
332 return _i18ntaxonomy_term_select(check_plain(tt("taxonomy:vocabulary:$vid:name", $vocabulary->name
)), $name, $value, $vid, $help, intval($vocabulary->multiple
), $blank);
335 // Produces translated tree
336 function _i18ntaxonomy_term_select($title, $name, $value, $vocabulary_id, $description, $multiple, $blank, $exclude = array()) {
337 $tree = taxonomy_get_tree($vocabulary_id);
341 $options[0] = $blank;
344 foreach ($tree as
$term) {
345 if (!in_array($term->tid
, $exclude)) {
346 $choice = new
stdClass();
347 $choice->option
= array($term->tid
=> str_repeat('-', $term->depth
) .
tt("taxonomy:term:$term->tid:name", $term->name
));
348 $options[] = $choice;
351 if (!$blank && !$value) {
352 // required but without a predefined value, so set first as predefined
353 $value = $tree[0]->tid
;
357 return array('#type' => 'select',
359 '#default_value' => $value,
360 '#options' => $options,
361 '#description' => $description,
362 '#multiple' => $multiple,
363 '#size' => $multiple ?
min(9, count($options)) : 0,
365 '#theme' => 'taxonomy_term_select',
369 * Returns a list for terms for vocabulary, language
371 function i18ntaxonomy_vocabulary_get_terms($vid, $lang, $status = 'all') {
374 $andsql = ' AND trid > 0';
377 $andsql = ' AND trid = 0';
382 $result = db_query("SELECT * FROM {term_data} WHERE vid=%d AND language='%s' $andsql", $vid, $lang);
384 while ($term = db_fetch_object($result)) {
385 $list[$term->tid
] = $term->name
;
391 * Multilingual Taxonomy
396 * This is the callback for taxonomy translations
399 * admin/content/taxonomy/i18n/term/xx
400 * admin/content/taxonomy/i18n/term/new/xx
401 * admin/content/taxonomy/vid/translation/op/trid
404 function i18ntaxonomy_page_vocabulary($vocabulary) {
405 $op = $_POST['op'] ?
$_POST['op'] : arg(5);
406 $edit = $_POST['edit'];
411 drupal_set_title(t('Edit term translations'));
412 $output = drupal_get_form('i18ntaxonomy_translation_term_form', $vocabulary->vid
, arg(6), $edit);
415 drupal_set_title(t('Submit'));
416 i18ntaxonomy_translation_term_save($edit);
417 $output = i18ntaxonomy_translation_overview($vocabulary->vid
);
420 //print theme('page', node_delete($edit), t('Delete'));
423 $output = i18ntaxonomy_translation_overview($vocabulary->vid
);
429 * Generate a tabular listing of translations for vocabularies.
431 function i18ntaxonomy_translation_overview($vid) {
432 $vocabulary = taxonomy_vocabulary_load($vid);
433 drupal_set_title(check_plain($vocabulary->name
));
435 $languages = i18n_supported_languages();
436 $header = array_merge($languages, array(t('Operations')));
439 // Get terms/translations for this vocab
440 $result = db_query('SELECT * FROM {term_data} t WHERE vid=%d',$vocabulary->vid
);
442 while ($term = db_fetch_object($result)) {
443 if($term->trid
&& $term->language
) {
444 $terms[$term->trid
][$term->language
] = $term;
447 // Reorder data for rows and languages
448 foreach ($terms as
$trid => $terms) {
450 foreach ($languages as
$lang => $name) {
451 if (array_key_exists($lang, $terms)) {
452 $thisrow[] = $terms[$lang]->name
;
458 $thisrow[] = l(t('edit'), "admin/content/taxonomy/$vid/translation/edit/$trid");
461 $output .
= theme('table', $header, $rows);
462 $output .
= l(t('new translation'), "admin/content/taxonomy/$vid/translation/edit/new");
467 * Produces a vocabulary translation form
469 function i18ntaxonomy_translation_term_form($vid, $trid = NULL
, $edit = array()) {
470 $languages = i18n_supported_languages();
471 if ($trid == 'new') {
472 $translations = array();
474 $form['trid'] = array('#type' => 'hidden', '#value' => $trid);
475 $translations = i18ntaxonomy_term_get_translations(array('trid' =>$trid));
477 //var_dump($translations);
478 $vocabulary = taxonomy_vocabulary_load($vid);
480 // List of terms for languages
481 foreach ($languages as
$lang => $langname) {
482 $current = isset($translations[$lang]) ?
$translations[$lang]->tid
: '';
483 $list = translation_vocabulary_get_terms($vid, $lang, 'all');
485 $form[$lang] = array('#type' => 'fieldset', '#tree' => TRUE
);
486 $form[$lang]['tid'] = array(
488 '#title' => $langname,
489 '#default_value' => $current,
492 $form[$lang]['old'] = array('#type' => 'hidden', '#value' =>$current);
494 $form['submit'] = array('#type' => 'submit', '#value' => t('Save'));
495 $form['destination'] = array('#type' => 'hidden', '#value' => 'admin/content/taxonomy/'.
arg(3).
'/translation');
500 * Form callback: Process vocabulary translation form
502 function i18ntaxonomy_translation_term_form_submit($form_id, $form_values) {
503 i18ntaxonomy_translation_save($form_values, $form_values['trid']);
504 drupal_set_message(t('Term translations have been updated'));
508 * Save taxonomy term translations
511 * Array of terms indexed by language
513 * Optional translation set id
515 function i18ntaxonomy_translation_save($terms, $trid = 0) {
516 // Delete old translations for this trid
518 db_query("UPDATE {term_data} SET trid = 0 WHERE trid= %d", $trid);
520 // Now pick up all the tids in an array
521 $translations = array();
522 foreach (i18n_supported_languages() as
$lang => $name) {
523 if (isset($terms[$lang]) && ($term = (array)$terms[$lang]) && $tid = $term['tid']) {
524 $translations[$lang] = $tid;
527 // Now set a translation set with all these terms.
528 if (count($translations)) {
529 $trid = (is_numeric($trid) && $trid) ?
$trid : db_next_id('{term_data}_trid');
530 db_query('UPDATE {term_data} SET trid = %d WHERE tid IN(%s)', $trid, implode(',',$translations));
536 * Get term translations
539 * An array of the from lang => Term
541 function i18ntaxonomy_term_get_translations($params, $getall = TRUE
) {
542 foreach($params as
$field => $value) {
543 $conds[] = "i.$field = '%s'";
546 if(!$getall){ // If not all, a parameter must be tid
547 $conds[] = "t.tid != %d";
548 $values[] = $params['tid'];
550 $conds[] = "t.trid != 0";
551 $sql = 'SELECT t.* FROM {term_data} t INNER JOIN {term_data} i ON t.trid = i.trid WHERE '.
implode(' AND ', $conds);;
552 $result = db_query($sql, $values);
554 while ($data = db_fetch_object($result)) {
555 $items[$data->language
] = $data;
562 * Like nat_get_terms() but without caching
564 function i18ntaxonomy_nat_get_terms($nid) {
567 $result = db_query("SELECT td.* FROM {nat} n INNER JOIN {term_data} td USING (tid) WHERE n.nid = %d", $nid);
568 while ($term = db_fetch_object($result)) {
569 $return[$term->tid
] = $term;
577 * Implementation of hook_nodeapi()
579 * Prepare node for translation
581 function i18ntaxonomy_nodeapi(&$node, $op) {
584 // This runs after taxonomy:nodeapi, so we just localize terms here
585 if ($op == 'view' && array_key_exists('taxonomy', $node)) {
586 $node->taxonomy
= i18ntaxonomy_localize_terms($node->taxonomy
);
589 case
'prepare translation':
590 $source = $node->translation_source
;
591 // Taxonomy translation
592 if (is_array($source->taxonomy
)) {
593 // Set translated taxonomy terms
594 $node->taxonomy
= i18ntaxonomy_translate_terms($source->taxonomy
, $node->language
);
601 * Translate an array of taxonomy terms
603 * Translates all terms with language, just passing over terms without it
605 function i18ntaxonomy_translate_terms($taxonomy, $langcode) {
606 $translation = array();
607 foreach ($taxonomy as
$index => $term) {
608 if ($term->language
&& $term->language
!= $langcode) {
609 $translated_terms = i18ntaxonomy_term_get_translations(array('tid' => $term->tid
));
610 if ($translated_terms && $newterm = $translated_terms[$langcode]) {
611 $translation[$newterm->tid
] = $newterm;
614 // Term has no language. Should be ok
615 $translation[$index] = $term;
621 * Implementation of hook_views_pre_view().
623 * Translate table header for taxonomy fields
624 * //field[i][id] = term_node_1.name, translate table header
625 * and replace handler for that field
627 function i18ntaxonomy_views_pre_view(&$view, &$items) {
629 $translate = variable_get('i18ntaxonomy_vocabularies', array());
630 foreach($view->field as
$index => $data) {
632 if($data['id'] == 'term_node.name') {
633 // That's a full taxonomy box
634 $view->field
[$index]['handler'] = 'i18ntaxonomy_views_handler_field_allterms';
635 } elseif(preg_match("/term_node_(\d+)\.name/", $data['id'], $matches)) {
637 if ($translate[$vid]) {
638 // Set new handler for this field
639 $view->field
[$index]['handler'] = 'i18ntaxonomy_views_handler_field_allterms';
647 * Field handler for taxonomy term fields
649 * Remake of views_handler_field_allterms with term name translation
651 function i18ntaxonomy_views_handler_field_allterms($fieldinfo, $fielddata, $value, $data) {
652 if ($fieldinfo['vocabulary']) {
653 $terms = taxonomy_node_get_terms_by_vocabulary($data->nid
, $fieldinfo['vocabulary']);
656 $terms = taxonomy_node_get_terms($data->nid
);
658 // Translate all these terms
659 _i18ntaxonomy_translate_terms($terms);
661 if ($fielddata['options'] == 'nolink') {
662 foreach ($terms as
$term) {
663 $links[] = check_plain($term->name
);
665 $links = !empty($links) ?
implode(' | ', $links) : '';
668 $node = new
stdClass();
669 $node->taxonomy
= $terms;
670 $links = theme('links', taxonomy_link('taxonomy terms', $node));
675 // Translate an array of term objects
676 function i18ntaxonomy_localize_terms($terms, $fields = array('name')) {
677 $translation = array();
678 $localize = i18ntaxonomy_vocabulary(NULL
, I18N_TAXONOMY_LOCALIZE
);
679 foreach ($terms as
$index => $term) {
680 if (in_array($term->vid
, $localize)) {
681 foreach ($fields as
$property ) {
682 $translation[$index]->$property = tt("taxonomy:term:$term->tid:$property", $term->name
);
685 $translation[$index] = $term;
691 // Get a list of vocabularies and terms
692 function _i18ntaxonomy_vocabulary_terms($vid = NULL
, $fullname = TRUE
) {
694 if (is_numeric($vid)) {
695 $where = "WHERE td.vid = $vid";
696 } elseif(is_array($vid)) {
697 $where = "WHERE td.vid IN(".
implode(',', $vid).
')';
699 $result = db_query("SELECT DISTINCT(td.tid), td.name, td.weight, v.name as vocabname, v.weight FROM {term_data} td LEFT JOIN {vocabulary} v ON v.vid = td.vid $where ORDER BY v.weight, v.name, td.weight, td.name");
700 while ($obj = db_fetch_object($result)) {
701 $tids[$obj->tid
] = $fullname ?
t($obj->vocabname
).
': '.
t($obj->name
) : t($obj->name
);
708 * Taxonomy vocabulary settings
710 * - If $vid and not $value, returns mode for vid
711 * - If $vid and $value, sets mode for vid
712 * - If !$vid and !$value returns all settings
713 * - If !$vid and $value returns all vids for this mode
721 function i18ntaxonomy_vocabulary($vid = NULL
, $mode = NULL
) {
722 $options = variable_get('i18ntaxonomy_vocabulary', array());
724 $options[$vid] = $mode;
725 variable_set('i18ntaxonomy_vocabulary', $options);
727 return array_key_exists($vid, $options) ?
$options[$vid] : I18N_TAXONOMY_NONE
;
729 return array_keys($options, $mode);