'Translation', 'page callback' => 'i18n_menu_translation_overview', 'page arguments' => array(4), 'access callback' => 'i18n_menu_translation_menu_access', 'access arguments' => array(4), 'type' => MENU_LOCAL_TASK, 'file' => 'i18n_menu.admin.inc', ); $items['admin/structure/menu/manage/%menu/translation/add'] = array( 'title' => 'Add translation', 'page callback' => 'drupal_get_form', 'page arguments' => array('i18n_menu_translation_form', 4, NULL, NULL), 'access callback' => 'i18n_menu_translation_menu_access', 'access arguments' => array(4), 'type' => MENU_LOCAL_ACTION, 'file' => 'i18n_menu.admin.inc', ); $items['admin/structure/menu/translation'] = array( 'title' => 'Translations', 'page callback' => 'drupal_get_form', 'page arguments' => array('i18n_translation_set_overview', 'menu_link'), 'access arguments' => array('administer menu'), 'type' => MENU_LOCAL_TASK, ); $items['admin/structure/menu/translation/add'] = array( 'title' => 'Add translation', 'page callback' => 'drupal_get_form', 'page arguments' => array('i18n_menu_translation_form', NULL, NULL, NULL), 'access arguments' => array('administer menu'), 'type' => MENU_LOCAL_ACTION, 'file' => 'i18n_menu.admin.inc', ); $items['admin/structure/menu/translation/%i18n_menu_translation'] = array( 'title' => 'Translation', 'page callback' => 'drupal_get_form', 'page arguments' => array('i18n_menu_translation_form', NULL, NULL, 4), 'access arguments' => array('administer menu'), 'type' => MENU_CALLBACK, 'file' => 'i18n_menu.admin.inc', ); $items['admin/structure/menu/item/%menu_link/translate'] = array( 'title' => 'Translations', 'page callback' => 'drupal_get_form', 'page arguments' => array('i18n_menu_translation_form', NULL, 4, NULL), 'access callback' => 'i18n_menu_translation_item_access', 'access arguments' => array(4), 'file' => 'i18n_menu.admin.inc', ); return $items; } /** * Menu access callback */ function i18n_menu_translation_menu_access($menu) { return user_access('administer menu') && $menu['i18n_mode'] & I18N_MODE_TRANSLATE; } /** * Menu access callback */ function i18n_menu_translation_item_access($item) { return user_access('administer menu') && i18n_object_langcode($item); } /** * Implements hook_block_view() */ function i18n_menu_block_view_alter(&$data, $block) { if ($block->module == 'menu' && !empty($block->i18n_mode)) { if (!$block->title) { $menus = menu_get_menus(FALSE); $data['subject'] = i18n_string_plain(array('menu', 'menu', $block->delta, 'title'), $menus[$block->delta]); } //i18n_menu_localize_elements($data['content']); $data['content'] = i18n_menu_translated_tree($block->delta, TRUE); } } /** * Implements hook i18n_object_info() */ function i18n_menu_i18n_object_info() { $info['menu'] = array( 'title' => t('Menu'), 'key' => 'menu_name', 'string translation' => array( 'textgroup' => 'menu', 'type' => 'menu', 'properties' => array( 'title' => t('Title'), 'description' => t('Description'), ), ) ); $info['menu_link'] = array( 'title' => t('Menu link'), 'key' => 'mlid', 'string translation' => array( 'textgroup' => 'menu', 'type' => 'item', 'properties' => array( 'title' => t('Title'), 'description' => t('Description'), ), ), 'translation set' => array( 'class' => 'i18n_menu_link_translation_set', 'table' => 'menu_links', 'field' => 'i18n_tsid', 'parent' => 'menu', 'edit path' => 'admin/structure/menu/translation', ), ); return $info; } /** * Implements hook_i18n_string_info() */ function i18n_menu_i18n_string_info() { $groups['menu'] = array( 'title' => t('Menu'), 'description' => t('Translatable menu items: title and description.'), 'format' => FALSE, // This group doesn't have strings with format 'list' => TRUE, // This group can list all strings ); return $groups; } /** * Implements hook_i18n_string_list() */ function i18n_menu_i18n_string_list($group) { if ($group == 'menu' || $group == 'all') { $strings = array(); // Add all menus foreach (menu_load_all() as $name => $menu) { $strings['menu']['menu'][$name] = array( 'title' => $menu['title'], 'description' => $menu['description'] ); } $query = db_select('menu_links', 'm') ->fields('m') ->condition('language', LANGUAGE_NONE) ->condition('customized', 1); foreach ($query->execute()->fetchAll() as $link) { $options = unserialize($link->options); $strings['menu']['item'][$link->mlid]['title'] = $link->link_title; if (isset($options['attributes']['title'])) { $strings['menu']['item'][$link->mlid]['description'] = $options['attributes']['title']; } } return $strings; } } /** * Implements hook_i18n_translate_path() */ function i18n_menu_i18n_translate_path($path, $language) { // @todo } /** * Implements hook_menu_link_alter(). * * Catch changed links, update language and set alter option. */ function i18n_menu_menu_link_alter(&$item) { // If we set option to language it causes an error with the link system // This should handle language only as the links are being manually updated if (i18n_object_langcode($item)) { $item['options']['langcode'] = $item['language']; } elseif (isset($item['language'])) { unset($item['options']['langcode']); } // If we are handling custom menu items of menu module and no language is set, // invoke translation via i18n_string module. if (isset($item['language']) && empty($item['language']) && $item['module'] == 'menu') { // Set title_callback to FALSE to avoid calling t(). $item['title_callback'] = FALSE; // Setting the alter option to true ensures that // hook_translated_menu_link_alter() will be called. $item['options']['alter'] = TRUE; } } /** * Implements hook_menu_insert() */ function i18n_menu_menu_insert($menu) { i18n_menu_menu_update($menu); } /** * Implements hook_menu_update() */ function i18n_menu_menu_update($menu) { db_update('menu_custom') ->fields(array('language' => $menu['language'], 'i18n_mode' => $menu['i18n_mode'])) ->condition('menu_name', $menu['menu_name']) ->execute(); if (!$menu['i18n_mode']) { $update['language'] = LANGUAGE_NONE; } elseif ($menu['i18n_mode'] & I18N_MODE_LANGUAGE) { $update['language'] = $menu['language']; } if (!empty($update)) { db_update('menu_links') ->fields(array('language' => $menu['language'])) ->condition('menu_name', $menu['menu_name']) ->condition('customized', 1) ->execute(); } // Update strings, always add translation if no language if (!i18n_object_langcode($menu)) { i18n_string_object_update('menu', $menu); } } /** * Implements hook_menu_insert() */ function i18n_menu_menu_delete($menu) { i18n_string_object_remove('menu', $menu); } /** * Implements hook_menu_link_insert() */ function i18n_menu_menu_link_insert($link) { i18n_menu_menu_link_update($link); } /** * Implements hook_menu_update() */ function i18n_menu_menu_link_update($link) { if (isset($link['language'])) { db_update('menu_links') ->fields(array('language' => $link['language'])) ->condition('mlid', $link['mlid']) ->execute(); } if (!empty($link['customized']) && empty($link['language'])) { i18n_string_update(array('menu', 'item', $link['mlid']), array( 'title' => $link['link_title'], 'description' => $link['options']['attributes']['title'], )); } } /** * Implements hook_menu_insert() */ function i18n_menu_menu_link_delete($link) { i18n_string_object_remove('menu_link', $link); } /** * Get menu mode or compare with given one */ function i18n_menu_mode($name, $mode = NULL) { $menu = menu_load($name); if (isset($mode)) { return $menu ? $menu['i18n_mode'] & $mode : FALSE; } else { return $menu ? $menu['i18n_mode'] : I18N_MODE_NONE; } } /** * Implements hook_translated_menu_link_alter(). * * Translate menu links on the fly. * * @see i18n_menu_menu_link_alter() */ function i18n_menu_translated_menu_link_alter(&$item, $map) { if (!empty($item['customized']) && empty($item['language']) && !empty($item['access']) && empty($item['hidden']) && empty($item['i18n_menu'])) { $item['title'] = _i18n_menu_link_title($item); if ($description = _i18n_menu_link_description($item)) { $item['localized_options']['attributes']['title'] = $description; } // Mark to skip localizing twice $item['i18n_menu'] = TRUE; } } /** * Implements hook_help(). */ function i18n_menu_help($path, $arg) { switch ($path) { case 'admin/help#i18n_menu' : $output = '
' . t('This module provides support for translatable custom menu items:') . '
'; $output .= '' . t('To search and translate strings, use the translation interface pages.', array('@translate-interface' => url('admin/build/translate'))) . '
'; return $output; } } /** * Implements hook_variable_info_alter() */ function i18n_menu_variable_info_alter(&$variables, $options) { // Make menu variables translatable $variables['menu_main_links_source']['localize'] = TRUE; $variables['menu_secondary_links_source']['localize'] = TRUE; } /** * Get localized menu tree. * @param $menu_name * The menu the translated tree has to be fetched from. * @param $reset * Whether to reset the internal cache for the translated tree. */ function i18n_menu_translated_tree($menu_name, $reset = FALSE) { $menu_output = &drupal_static(__FUNCTION__); if (!isset($menu_output[$menu_name]) || $reset) { $tree = menu_tree_page_data($menu_name); $tree = i18n_menu_localize_tree($tree); $menu_output[$menu_name] = menu_tree_output($tree); } return $menu_output[$menu_name]; } /** * Localize menu tree. */ function i18n_menu_localize_tree($tree, $langcode = NULL) { $langcode = i18n_langcode($langcode); foreach ($tree as $index => &$item) { $link = $item['link']; if ($link['customized'] && !empty($link['access']) && empty($link['hidden']) && empty($link['i18n_menu'])) { // Remove links for other languages than current. // Links with language wont be localized. if (!empty($link['language'])) { if ($link['language'] != $langcode) { unset($tree[$index]); } } else { $router = i18n_menu_get_router($link['router_path']); // If the title is the same it will be localized by the menu system. if ($link['link_title'] != $router['title']) { $item['link']['title'] = _i18n_menu_link_title($link, $langcode); } if ($description = _i18n_menu_link_description($link, $langcode)) { $item['link']['localized_options']['attributes']['title'] = $description; } // Localize subtree. if ($item['below'] !== FALSE) { $item['below'] = i18n_menu_localize_tree($item['below'], $langcode); } } } } return $tree; } /** * Localize menu renderable array */ function i18n_menu_localize_elements(&$elements) { foreach (element_children($elements) as $mlid) { $elements[$mlid]['#title'] = i18n_string(array('menu', 'item', $mlid, 'title'), $elements[$mlid]['#title']); if (!empty($tree[$mlid]['#localized_options']['attributes']['title'])) { $elements[$mlid]['#localized_options']['attributes']['title'] = i18n_string(array('menu', 'item', $mlid, 'description'), $tree[$mlid]['#localized_options']['attributes']['title']); } i18n_menu_localize_elements($elements[$mlid]); } } /** * Return an array of localized links for a navigation menu. */ function i18n_menu_menu_navigation_links($menu_name, $level = 0) { // Don't even bother querying the menu table if no menu is specified. if (empty($menu_name)) { return array(); } // Get the menu hierarchy for the current page. $tree = menu_tree_page_data($menu_name); $tree = i18n_menu_localize_tree($tree); // Go down the active trail until the right level is reached. while ($level-- > 0 && $tree) { // Loop through the current level's items until we find one that is in // trail. while ($item = array_shift($tree)) { if ($item['link']['in_active_trail']) { // If the item is in the active trail, we continue in the subtree. $tree = empty($item['below']) ? array() : $item['below']; break; } } } // Create a single level of links. $links = array(); foreach ($tree as $item) { if (!$item['link']['hidden']) { $class = ''; $l = $item['link']['localized_options']; $l['href'] = $item['link']['href']; $l['title'] = $item['link']['title']; if ($item['link']['in_active_trail']) { $class = ' active-trail'; } // Keyed with the unique mlid to generate classes in theme_links(). $links['menu-' . $item['link']['mlid'] . $class] = $l; } } return $links; } /** * Replace standard primary and secondary links. */ function i18n_menu_preprocess_page(&$vars) { if (theme_get_setting('toggle_primary_links')) { $vars['primary_links'] = i18n_menu_menu_navigation_links(variable_get('menu_primary_links_source', 'primary-links')); } // If the secondary menu source is set as the primary menu, we display the // second level of the primary menu. if (theme_get_setting('toggle_secondary_links')) { if (variable_get('menu_secondary_links_source', 'secondary-links') == variable_get('menu_primary_links_source', 'primary-links')) { $vars['secondary_links'] = i18n_menu_menu_navigation_links(variable_get('menu_primary_links_source', 'primary-links'), 1); } else { $vars['secondary_links'] = i18n_menu_menu_navigation_links(variable_get('menu_secondary_links_source', 'secondary-links'), 0); } } } /** * Get localized menu title */ function _i18n_menu_link_title($link, $langcode = NULL) { return i18n_string_translate(array('menu', 'item', $link['mlid'], 'title'), $link['link_title'], array('langcode' => $langcode)); } /** * Localize menu item title and description */ function _i18n_menu_link_localize(&$link, $langcode = NULL) { $link['link_title'] = _i18n_menu_link_title($link, $langcode); if ($description = _i18n_menu_link_description($link, $langcode)) { $link['options']['attributes']['title'] = $description; } if (!empty($link['options']['attributes']['title'])) { } } /** * Get localized menu description */ function _i18n_menu_link_description($link, $langcode = NULL) { if (!empty($link['options']['attributes']['title'])) { return i18n_string_translate(array('menu', 'item', $link['mlid'], 'description'), $link['options']['attributes']['title'], array('langcode' => $langcode)); } else { return NULL; } } /** * Get the menu router for this router path. * * We need the untranslated title to compare, and this will be fast. * There's no api function to do this? * * @param $path * The patch to fetch from the router. * @param $reset * Whether to reset the internal cache for the menu router. */ function i18n_menu_get_router($path, $reset = FALSE) { $cache = &drupal_static(__FUNCTION__ , array(), $reset); if (!array_key_exists($path, $cache)) { $cache[$path] = db_select('menu_router', 'mr') ->fields('mr', array('title')) ->condition('path', $path) ->execute() ->fetchAssoc(); } return $cache[$path]; } /** * Implements hook_form_FORM_ID_alter(). */ function i18n_menu_form_menu_edit_menu_alter(&$form, &$form_state) { $menu = menu_load($form['old_name']['#value']); $i18n_mode = $menu && isset($menu['i18n_mode']) ? $menu['i18n_mode'] : I18N_MODE_NONE; $form['i18n'] = array( '#type' => 'fieldset', '#title' => t('Multilingual options'), '#collapsible' => TRUE, '#weight' => 10, ); $form['i18n']['i18n_mode'] = array( '#type' => 'radios', '#title' => t('Translation mode'), '#options' => i18n_translation_options(), '#default_value' => $i18n_mode, '#description' => t('For localizable menus, to have all items available for translation visit the translation refresh page.', array('@locale-refresh' => url('admin/build/translate/refresh'))), ); $form['i18n']['language'] = array( '#default_value' => $menu && isset($menu['language']) ? $menu['language'] : LANGUAGE_NONE, '#description' => t('Language for this menu. If set, it will apply to all items in this menu.'), '#disabled' => !($i18n_mode & I18N_MODE_LANGUAGE), ) + i18n_element_language_select(); } /** * Implements hook_form_FORM_ID_alter(). * * Add a language selector to the menu_edit_item form and register a submit * callback to process items. */ function i18n_menu_form_menu_edit_item_alter(&$form, &$form_state) { $item = &$form['original_item']['#value']; $item['language'] = i18n_menu_item_get_language($item); if (i18n_menu_mode($item['menu_name'], I18N_MODE_TRANSLATE)) { $form['language'] = array( '#description' => t('This item belongs to a multilingual menu. You can set a language for it.'), ) + i18n_element_language_select($item); $form['translations'] = i18n_translation_set_element('menu_link', $item); if (!empty($item['mlid']) && i18n_object_langcode($item)) { $form['translations']['add']['#markup'] = l(t('Add translations'), 'admin/structure/menu/item/' . $item['mlid'] . '/translate'); } } else { $form['language'] = array( '#type' => 'value', '#value' => $item['language'], ); } if (i18n_menu_mode($item['menu_name'], I18N_MODE_LOCALIZE)) { i18n_string_element_mark($form['title']); i18n_string_element_mark($form['description']); } array_unshift($form['#validate'], 'i18n_menu_menu_item_prepare_normal_path'); } /** * Normal path should be checked with menu item's language to avoid * troubles when a node and it's translation has the same url alias. */ function i18n_menu_menu_item_prepare_normal_path($form, &$form_state) { $item = &$form_state['values']; $normal_path = drupal_get_normal_path($item['link_path'], $item['language']); if ($item['link_path'] != $normal_path) { drupal_set_message(t('The menu system stores system paths only, but will use the URL alias for display. %link_path has been stored as %normal_path', array('%link_path' => $item['link_path'], '%normal_path' => $normal_path))); $item['link_path'] = $normal_path; } } /** * Get language for menu item */ function i18n_menu_item_get_language($item) { if (isset($item['language'])) { return $item['language']; } else { $menu = menu_load($item['menu_name']); switch ($menu['i18n_mode']) { case I18N_MODE_LANGUAGE: return $menu['language']; case I18N_MODE_NONE: case I18N_MODE_LOCALIZE: return LANGUAGE_NONE; default: if (!empty($item['mlid'])) { return db_select('menu_links', 'm') ->fields('m', array('language')) ->condition('mlid', $item['mlid']) ->execute() ->fetchField(); } else { return LANGUAGE_NONE; } } } } /** * Implements hook_form_node_form_alter(). * * Add language to menu settings of the node form, as well as setting defaults * to match the translated item's menu settings. */ function i18n_menu_form_node_form_alter(&$form, &$form_state, $form_id) { $node = $form['#node']; if (!empty($form['menu'])) { // Set menu language to node language. $form['menu']['language'] = array('#type' => 'value', '#value' => $node->language); // Customized must be set to 1 to save language. $form['menu']['customized'] = array('#type' => 'value', '#value' => 1); } } /** * Implements hook_node_presave() * * Set menu link language to node language */ function i18n_menu_node_presave($node) { if (!empty($node->menu) && isset($node->language)) { $node->menu['language'] = $node->language && $node->language !== LANGUAGE_NONE ? $node->language : ''; } } /** * Implements hook_node_prepare_translation(). */ function i18n_menu_node_prepare_translation($node) { if (empty($node->menu['mlid']) && !empty($node->translation_source)) { $tnode = $node->translation_source; // Prepare the tnode so the menu item will be available. node_object_prepare($tnode); $node->menu['link_title'] = $tnode->menu['link_title']; $node->menu['weight'] = $tnode->menu['weight']; } } /** * Process menu and menu item add/edit form submissions. * * @todo See where this fits */ /* function i18n_menu_edit_item_form_submit($form, &$form_state) { $mid = menu_edit_item_save($form_state['values']); db_query("UPDATE {menu} SET language = '%s' WHERE mid = %d", array($form_state['values']['language'], $mid)); return 'admin/build/menu'; } */ /** * Load translation set. Menu loading callback. */ function i18n_menu_translation_load($tsid) { return i18n_translation_set_load($tsid, 'menu_link'); } /** * Implements hook_language_switch_links_alter(). * * Replaces links with pointers to translated versions of the content. */ function i18n_menu_language_switch_links_alter(array &$links, $type, $path) { $item = i18n_menu_link_load($path, i18n_langcode()); if ($item && ($set = i18n_translation_object('menu_link', $item))) { foreach ($set->get_translations() as $lang => $link) { if (!isset($links[$lang]) || !isset($links[$lang]['href']) || $links[$lang]['href'] == $path) { $links[$lang] = i18n_translation_link($link['link_path'], $lang); } } } } /** * Load menu item by path, language */ function i18n_menu_link_load($path, $langcode) { $query = db_select('menu_links', 'ml'); $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path'); $query->fields('ml'); // Weight should be taken from {menu_links}, not {menu_router}. $query->addField('ml', 'weight', 'link_weight'); $query->fields('m'); $query->condition('ml.link_path', $path); $query->condition('ml.language', $langcode); if ($item = $query->execute()->fetchAssoc()) { $item['weight'] = $item['link_weight']; _menu_link_translate($item); return $item; } }