'.t('This module adds support for for multilingual taxonomy. You can set up multilingual options for each vocabulary:').'

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

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

'; return $output; case 'admin/settings/i18n': $output = ''; return $output; case 'admin/content/taxonomy/%': $vocabulary = taxonomy_vocabulary_load($arg[3]); switch (i18ntaxonomy_vocabulary($vocabulary->vid)) { case I18N_TAXONOMY_LOCALIZE: return '

'. t('%capital_name is a localizable vocabulary. You will be able to translate term names and descriptions using the localization interface.', array('%capital_name' => drupal_ucfirst($vocabulary->name), '%name' => $vocabulary->name)) .'

'; case I18N_TAXONOMY_LANGUAGE: return '

'. t('%capital_name is a vocabulary with a fixed language. All the terms in this vocabulary will have %language language.', array('%capital_name' => drupal_ucfirst($vocabulary->name), '%name' => $vocabulary->name, '%language' => i18n_language_property($vocabulary->language, 'name'))) .'

'; case I18N_TAXONOMY_TRANSLATE: return '

'. t('%capital_name is a full multilingual vocabulary. You will be able to set a language for each term and create translation relationships.', array('%capital_name' => drupal_ucfirst($vocabulary->name))) .'

'; } } } /** * Returns list of vocabulary modes */ function _i18ntaxonomy_vocabulary_options() { return array( I18N_TAXONOMY_NONE => t('None. No multilingual options for this vocabulary'), I18N_TAXONOMY_LOCALIZE => t('Localize terms. Terms are common for all languages but their name and description may be localized.'), I18N_TAXONOMY_TRANSLATE => t('Per language terms. Different terms will be allowed for each language and they can be translated.'), I18N_TAXONOMY_LANGUAGE => t('Set language to vocabulary. The vocabulary will have a global language and it will only show up for pages in that language.'), ); } /** * Implementation of hook_menu(). */ function i18ntaxonomy_menu() { $items['admin/content/taxonomy/%taxonomy_vocabulary/translation'] = array( 'title' => 'Translation', 'page callback' => 'i18ntaxonomy_page_vocabulary', 'page arguments' => array(3, 5, 6), 'type' => MENU_LOCAL_TASK, 'parent' => 'admin/content/taxonomy/%taxonomy_vocabulary', 'file' => 'i18ntaxonomy.admin.inc', ); return $items; } /** * Implementation of hook_locale(). */ function i18ntaxonomy_locale($op = 'groups') { switch ($op) { case 'groups': return array('taxonomy' => t('Taxonomy')); } } /** * Implementation of hook_alter_translation_link(). * * Replaces links with pointers to translated versions of the content. */ function i18ntaxonomy_translation_link_alter(&$links, $path) { if (preg_match("/^(taxonomy\/term\/)([^\/]*)(.*)$/", $path, $matches)) {//or at a taxonomy-listing? foreach ($links as $langcode => $link) { if ($str_tids = i18ntaxonomy_translation_tids($matches[2], $langcode)) { $links[$langcode]['href'] = "taxonomy/term/$str_tids". $matches[3]; } } } } /** * 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 i18ntaxonomy_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", $tid, $language ? $language : i18n_get_lang())); return $translation ? $translation : $default; } /** * Returns an url for the translated taxonomy-page, if exists */ function i18ntaxonomy_translation_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 = i18ntaxonomy_translation_term_tid($tid, $lang)) { $translated_tids[] = $translated_tid; } } return implode($separator, $translated_tids); } /** * Implementation of hook_taxonomy * * $edit parameter may be an array or an object !! */ function i18ntaxonomy_taxonomy($op, $type, $edit = NULL) { $edit = (array)$edit; switch ("$type/$op") { case 'term/insert': case 'term/update': $tid = $edit['tid']; $language = isset($edit['language']) ? $edit['language'] : ''; db_query("UPDATE {term_data} SET language='%s' WHERE tid=%d", $language, $tid); // From translation module if (!$language && !empty($edit['trid'])) { // Removed language, remove trid db_query('UPDATE {term_data} SET trid = 0 WHERE tid = %d', $tid); if(db_affected_rows()) drupal_set_message(t('Removed translation information from term')); } // Update strings for localizable vocabulary if (i18ntaxonomy_vocabulary($edit['vid']) == I18N_TAXONOMY_LOCALIZE) { tt("taxonomy:term:$tid:name", $edit['name'], NULL, TRUE); tt("taxonomy:term:$tid:description", $edit['description'], NULL, TRUE); } break; case 'vocabulary/insert': case 'vocabulary/update': $vid = $edit['vid']; // Update vocabulary settings if (isset($edit['i18nmode'])) { i18ntaxonomy_vocabulary($vid, $edit['i18nmode']); $language = isset($edit['language']) ? $edit['language'] : ''; db_query("UPDATE {vocabulary} SET language='%s' WHERE vid = %d", $language, $edit['vid']); if ($language && $op == 'update') { db_query("UPDATE {term_data} SET language='%s' WHERE vid = %d", $edit['language'], $edit['vid']); drupal_set_message(t('Reset language for all terms.')); } // Always add vocabulary translation if !$language if (!$language) { tt("taxonomy:vocabulary:$vid:name", $edit['name'], NULL, TRUE); } } break; } } /** * Implementation of hook_db_rewrite_sql() */ function i18ntaxonomy_db_rewrite_sql($query, $primary_table, $primary_key){ // No rewrite for administration pages if (arg(0) == 'admin') return; switch ($primary_table) { case 't': case 'v': // Taxonomy queries // When loading specific terms, vocabs, language conditions shouldn't apply if (preg_match("/WHERE.* $primary_table\.tid\s*(=\s*\d|IN)/", $query)) return; // Taxonomy for specific node if (preg_match("/WHERE r\.nid = \%d/", $query)) return; $result['where'] = i18n_db_rewrite_where($primary_table, 'taxonomy'); return $result; } } /** * Implementation of hook_form_alter * * This is the place to add language fields to all forms * * @ TO DO The vocabulary form needs some javascript */ function i18ntaxonomy_form_alter(&$form, $form_state, $form_id) { switch($form_id){ case 'taxonomy_overview_vocabularies': $vocabularies = taxonomy_get_vocabularies(); $languages = locale_language_list('name'); foreach ($vocabularies as $vocabulary) { if ($vocabulary->language) { $form[$vocabulary->vid]['types']['#value'] .= ' ('.$languages[$vocabulary->language].')'; } } break; case 'taxonomy_overview_terms': $mode = i18ntaxonomy_vocabulary($form['#vocabulary']['vid']); if ($mode == I18N_TAXONOMY_TRANSLATE) { $languages = locale_language_list('name'); foreach (element_children($form) as $key) { if (isset($form[$key]['#term']) && ($lang = $form[$key]['#term']['language'])) { $form[$key]['view']['#value'] .= ' ('.$languages[$lang].')'; } } } break; case 'taxonomy_form_vocabulary': // Taxonomy vocabulary if ($form['vid']['#value']) { $vocabulary = taxonomy_vocabulary_load($form['vid']['#value']); $mode = i18ntaxonomy_vocabulary($vocabulary->vid); } else { $vocabulary = NULL; $mode = I18N_TAXONOMY_NONE; } $form['i18n'] = array( '#type' => 'fieldset', '#title' => t('Multilingual options'), '#collapsible' => TRUE, '#weight' => 0, ); $form['i18n']['i18nmode'] = array( '#type' => 'radios', '#title' => t('Translation mode'), '#options' => _i18ntaxonomy_vocabulary_options(), '#default_value' => $mode, ); $form['identification']['language'] = array( '#type' => 'select', '#title' => t('Language'), '#default_value' => $vocabulary && !empty($vocabulary->language) ? $vocabulary->language : '', '#options' => array('' => '') + locale_language_list('name'), '#description' => t('Language for this vocbulary. If set, it will apply to all terms in this vocabulary.'), '#disabled' => ($mode != I18N_TAXONOMY_LANGUAGE), ); break; case 'taxonomy_form_term': // Taxonomy term $vocabulary = (object)$form['#vocabulary']; $term = (object)$form['#term']; // Add language field or not depending on taxonomy mode switch (i18ntaxonomy_vocabulary($vocabulary->vid)) { case I18N_TAXONOMY_TRANSLATE: $form['identification']['language'] = array( '#type' => 'select', '#title' => t('Language'), '#default_value' => isset($term) && !empty($term->language) ? $term->language : '', '#options' => array('' => '') + locale_language_list('name'), '#description' => t('This term belongs to a multilingual vocabulary. You can set a language for it.'), ); break; case I18N_TAXONOMY_LANGUAGE: $form['language'] = array('#type' => 'value', '#value' => $vocabulary->language); $form['identification']['language_info'] = array('#value' => t('All terms in this vocabulary have a fixed language: %language', array('%language' => i18n_language_property($vocabulary->language, 'name')))); break; case I18N_TAXONOMY_LOCALIZE: $form['language'] = array('#type' => 'value', '#value' => ''); $form['identification']['name']['#description'] .= ' '.t('This name be localizable').''; $form['identification']['description']['#description'] .= ' '.t('This description will be localizable').''; break; case I18N_TAXONOMY_NONE: default: $form['language'] = array('#type' => 'value', '#value' => ''); break; } break; default: if (isset($form['type']) && $form['type']['#value'] .'_node_form' == $form_id && ($node = $form['#node']) && isset($form['taxonomy']) ) { // Node form. Translate vocabularies i18ntaxonomy_node_form($form); } } } /** * Handle node form taxonomy */ function i18ntaxonomy_node_form(&$form) { $node = $form['#node']; if (!isset($node->taxonomy)) { if (!empty($node->nid)) { $terms = taxonomy_node_get_terms($node->nid); } else { $terms = array(); } } else { $terms = $node->taxonomy; } // Regenerate the whole field for translatable vocabularies foreach (element_children($form['taxonomy']) as $vid) { if (is_numeric($vid) && i18ntaxonomy_vocabulary($vid) == I18N_TAXONOMY_LOCALIZE) { // Rebuild this vocabulary's form $vocabulary = taxonomy_vocabulary_load($vid); // Extract terms belonging to the vocabulary in question. $default_terms = array(); foreach ($terms as $term) { if ($term->vid == $vid) { $default_terms[$term->tid] = $term; } } $form['taxonomy'][$vid] = i18ntaxonomy_vocabulary_form($vocabulary->vid, array_keys($default_terms)); $form['taxonomy'][$vid]['#weight'] = $vocabulary->weight; $form['taxonomy'][$vid]['#required'] = $vocabulary->required; } } } /** * Generate a form element for selecting terms from a vocabulary. * Translates all translatable strings. */ function i18ntaxonomy_vocabulary_form($vid, $value = 0, $help = NULL, $name = 'taxonomy') { $vocabulary = taxonomy_vocabulary_load($vid); $help = $vocabulary->help ? tt("taxonomy:vocabulary:$vid:help", $vocabulary->help) : ''; if ($vocabulary->required) { $blank = 0; } else { $blank = '<'. t('none') .'>'; } return _i18ntaxonomy_term_select(check_plain(tt("taxonomy:vocabulary:$vid:name", $vocabulary->name)), $name, $value, $vid, $help, intval($vocabulary->multiple), $blank); } // Produces translated tree function _i18ntaxonomy_term_select($title, $name, $value, $vocabulary_id, $description, $multiple, $blank, $exclude = array()) { $tree = taxonomy_get_tree($vocabulary_id); $options = array(); if ($blank) { $options[0] = $blank; } if ($tree) { foreach ($tree as $term) { if (!in_array($term->tid, $exclude)) { $choice = new stdClass(); $choice->option = array($term->tid => str_repeat('-', $term->depth) . tt("taxonomy:term:$term->tid:name", $term->name)); $options[] = $choice; } } if (!$blank && !$value) { // required but without a predefined value, so set first as predefined $value = $tree[0]->tid; } } return array('#type' => 'select', '#title' => $title, '#default_value' => $value, '#options' => $options, '#description' => $description, '#multiple' => $multiple, '#size' => $multiple ? min(9, count($options)) : 0, '#weight' => -15, '#theme' => 'taxonomy_term_select', ); } /** * Returns a list for terms for vocabulary, language */ function i18ntaxonomy_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; } /** * Multilingual Taxonomy * */ /** * Get term translations * * @return * An array of the from lang => Term */ function i18ntaxonomy_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; } /** * Like nat_get_terms() but without caching */ function i18ntaxonomy_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; } /** * Implementation of hook_nodeapi() * * Prepare node for translation */ function i18ntaxonomy_nodeapi(&$node, $op) { switch ($op) { case 'view': // This runs after taxonomy:nodeapi, so we just localize terms here if ($op == 'view' && array_key_exists('taxonomy', $node)) { $node->taxonomy = i18ntaxonomy_localize_terms($node->taxonomy); } break; case 'prepare translation': $source = $node->translation_source; // Taxonomy translation if (is_array($source->taxonomy)) { // Set translated taxonomy terms $node->taxonomy = i18ntaxonomy_translate_terms($source->taxonomy, $node->language); } break; } } /** * Translate an array of taxonomy terms * * Translates all terms with language, just passing over terms without it */ function i18ntaxonomy_translate_terms($taxonomy, $langcode) { $translation = array(); foreach ($taxonomy as $index => $term) { if ($term->language && $term->language != $langcode) { $translated_terms = i18ntaxonomy_term_get_translations(array('tid' => $term->tid)); if ($translated_terms && $newterm = $translated_terms[$langcode]) { $translation[$newterm->tid] = $newterm; } } else { // Term has no language. Should be ok $translation[$index] = $term; } } return $translation; } /** * Implementation of hook_views_pre_view(). * * Translate table header for taxonomy fields * //field[i][id] = term_node_1.name, translate table header * and replace handler for that field */ function i18ntaxonomy_views_pre_view(&$view, &$items) { //var_dump($view); $translate = variable_get('i18ntaxonomy_vocabularies', array()); foreach($view->field as $index => $data) { $matches = array(); if($data['id'] == 'term_node.name') { // That's a full taxonomy box $view->field[$index]['handler'] = 'i18ntaxonomy_views_handler_field_allterms'; } elseif(preg_match("/term_node_(\d+)\.name/", $data['id'], $matches)) { $vid = $matches[1]; if ($translate[$vid]) { // Set new handler for this field $view->field[$index]['handler'] = 'i18ntaxonomy_views_handler_field_allterms'; } } } } /** * Field handler for taxonomy term fields * * Remake of views_handler_field_allterms with term name translation */ function i18ntaxonomy_views_handler_field_allterms($fieldinfo, $fielddata, $value, $data) { if ($fieldinfo['vocabulary']) { $terms = taxonomy_node_get_terms_by_vocabulary($data->nid, $fieldinfo['vocabulary']); } else { $terms = taxonomy_node_get_terms($data->nid); } // Translate all these terms _i18ntaxonomy_translate_terms($terms); if ($fielddata['options'] == 'nolink') { foreach ($terms as $term) { $links[] = check_plain($term->name); } $links = !empty($links) ? implode(' | ', $links) : ''; } else { $node = new stdClass(); $node->taxonomy = $terms; $links = theme('links', taxonomy_link('taxonomy terms', $node)); } return $links; } /** * Localize taxonomy terms for localizable vocabularies * * @param $terms * Array of term objects * @param $fields * Object properties to localize * @return * Array of terms with the right ones localized */ function i18ntaxonomy_localize_terms($terms, $fields = array('name')) { $localize = i18ntaxonomy_vocabulary(NULL, I18N_TAXONOMY_LOCALIZE); foreach ($terms as $index => $term) { if (in_array($term->vid, $localize)) { foreach ($fields as $property) { $terms[$index]->$property = tt("taxonomy:term:$term->tid:$property", $term->name); } } } return $terms; } // Get a list of vocabularies and terms function _i18ntaxonomy_vocabulary_terms($vid = NULL, $fullname = TRUE) { $tids = array(); if (is_numeric($vid)) { $where = "WHERE td.vid = $vid"; } elseif(is_array($vid)) { $where = "WHERE td.vid IN(".implode(',', $vid).')'; } $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"); while ($obj = db_fetch_object($result)) { $tids[$obj->tid] = $fullname ? t($obj->vocabname).': '.t($obj->name) : t($obj->name); } return $tids; } /** * Taxonomy vocabulary settings * * - If $vid and not $value, returns mode for vid * - If $vid and $value, sets mode for vid * - If !$vid and !$value returns all settings * - If !$vid and $value returns all vids for this mode * * @param $vid * Vocabulary id * @param $value * Vocabulary mode * */ function i18ntaxonomy_vocabulary($vid = NULL, $mode = NULL) { $options = variable_get('i18ntaxonomy_vocabulary', array()); if ($vid && $mode) { $options[$vid] = $mode; variable_set('i18ntaxonomy_vocabulary', $options); } elseif ($vid) { return array_key_exists($vid, $options) ? $options[$vid] : I18N_TAXONOMY_NONE; } elseif ($mode) { return array_keys($options, $mode); } else { return $options; } }