Index: i18nmenu/i18nmenu.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/i18n/i18nmenu/Attic/i18nmenu.install,v
retrieving revision 1.1.2.4
diff -u -p -r1.1.2.4 i18nmenu.install
--- i18nmenu/i18nmenu.install	18 Jan 2009 17:52:23 -0000	1.1.2.4
+++ i18nmenu/i18nmenu.install	7 Jun 2010 16:25:49 -0000
@@ -14,4 +14,18 @@
 function i18nmenu_enable() {
   drupal_load('module', 'i18nstrings');
   i18nmenu_locale_refresh();
-}
\ No newline at end of file
+  i18nmenu_node_item_translations_refresh_all();
+}
+
+/**
+ * Implementation of hook_uninstall().
+ *
+ * Delete all variables.
+ */
+function i18nmenu_uninstall() {
+  $result = db_query('SELECT name FROM {variable} WHERE name LIKE \'i18nmenu_node_translation_%%\'');
+  while ($row = db_fetch_object($result)) {
+    variable_del($row->name);
+  }
+  i18nmenu_node_item_translations_refresh_all();
+}
Index: i18nmenu/i18nmenu.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/i18n/i18nmenu/i18nmenu.module,v
retrieving revision 1.2.2.21
diff -u -p -r1.2.2.21 i18nmenu.module
--- i18nmenu/i18nmenu.module	25 May 2010 13:57:20 -0000	1.2.2.21
+++ i18nmenu/i18nmenu.module	8 Jun 2010 09:20:51 -0000
@@ -58,7 +58,37 @@ function i18nmenu_menu_link_alter(&$item
     // Setting the alter option to true ensures that
     // hook_translated_menu_link_alter() will be called.
     $item['options']['alter'] = TRUE;
+    // We store here the current translation set to allow node translation.
+    $map = explode('/', $item['link_path']);
+    if ($map[0] == 'node' && isset($map[1]) && intval($map[1])) {
+      // Get the translation set id from the menu item path.
+      $tnid = _i18nmenu_node_get_tnid($map[1]);
+      if ($tnid) {
+        // We always store the source node nid to allow a correct handling of
+        // the active trail in i18nmenu_nodeapi() and to improve performance in
+        // i18nmenu_node_item_translations_refresh_set().
+        $map[1] = $tnid;
+        // Use the source node nid in the link path.
+        $item['link_path'] = implode('/', $map);
+        // We store here the current translation set to allow later translation.
+        $item['options']['translations'] = i18nmenu_node_get_translations($tnid, NULL);
+      }
+      else {
+        unset($item['options']['translations']);
+      }
+    }
+  }
+}
+
+/**
+ * Return the translation set id for the give nid.
+ */
+function _i18nmenu_node_get_tnid($nid) {
+  static $tnids = array();
+  if (!isset($tnids[$nid])) {
+    $tnids[$nid] = db_fetch_object(db_query("SELECT n.tnid FROM {node} n WHERE n.nid = %d", $nid))->tnid;
   }
+  return $tnids[$nid];
 }
 
 /**
@@ -71,6 +101,11 @@ function i18nmenu_menu_link_alter(&$item
 function i18nmenu_translated_menu_link_alter(&$item, $map) {
   if ($item['module'] == 'menu') {
     $item['title'] = _i18nmenu_get_item($item);
+    if ($map[1] = i18nmenu_node_translation($item, $map)) {
+      // If there is a node translation available for the current language we
+      // replace its nid with the current one.
+      $item['href'] = implode('/', $map);
+    }
   }
 }
 
@@ -233,14 +268,22 @@ function i18nmenu_get_router($path) {
 /**
  * Implementation of hook_form_form_id_alter().
  *
- * Register a submit callback to process menu title.
+ * Add a checkbox to the menu_edit_menu form to enable path translation for node
+ * links and register a submit callback to process menu title.
  */
 function i18nmenu_form_menu_edit_menu_alter(&$form, $form_state) {
+  $form['submit']['#weight'] = 9;
+  $form['i18nmenu_node_translation'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Enable node translation'),
+    '#description' => t('If this is checked node link paths will be replaced with the node translation paths where available.'),
+    '#default_value' => _i18nmenu_node_translation_enabled($form['menu_name']['#value']),
+  );
   $form['#submit'][] = 'i18nmenu_menu_edit_menu_submit';
 }
 
 /**
- * Submit handler for the menu_edit_item form.
+ * Submit handler for the menu_edit_menu form.
  *
  * On menu item insert or update, save a translation record.
  */
@@ -254,6 +297,24 @@ function i18nmenu_menu_edit_menu_submit(
     }
     i18nstrings_update_string($context, $form_state['values']['title']);
   }
+
+  // Check the language selection mode and display a warning if it will not play
+  // well with node translation.
+  if (variable_get('i18n_selection_mode', 'simple') != 'off' && $form_state['values']['i18nmenu_node_translation']) {
+    module_load_include('inc', 'i18n', 'i18n.admin');
+    $modes = _i18n_selection_mode();
+    $args = array(
+      '!url' => url('admin/settings/language/i18n'),
+      '!mode' => $modes['off'],
+    );
+    $message = t('Node translation will work properly only if <a href="!url">content language negotiation</a> is set to <em>!mode</em>', $args);
+    drupal_set_message($message, 'warning');
+  }
+
+  // Save the node translation setting for the current menu.
+  $menu = $form['menu_name']['#value'];
+  $value = $form_state['values']['i18nmenu_node_translation'];
+  variable_set("i18nmenu_node_translation_$menu", $value);
 }
 
 /**
@@ -349,9 +410,12 @@ function i18nmenu_item_delete_submit($fo
 function i18nmenu_form_alter(&$form, &$form_state, $form_id) {
   if (isset($form['type']) && isset($form['#node']) && $form['type']['#value'] .'_node_form' == $form_id) {
     $node = $form['#node'];
-
+    
     // Do nothing if the node already has a menu.
     if (!empty($node->menu['mlid'])) {
+      if (_i18nmenu_node_translation_enabled($node->menu['menu_name'])) {
+        $form['menu']['#description'] = t('These settings will be shared among all the node translations.');
+      }
       return;
     }
 
@@ -374,28 +438,46 @@ function i18nmenu_form_alter(&$form, &$f
     node_object_prepare($tnode);
 
     if ($tnode->menu) {
-      // Set default values based on translation source's menu.
-      $form['menu']['link_title']['#default_value'] = $tnode->menu['link_title'];
-      $form['menu']['weight']['#default_value'] = $tnode->menu['weight'];
-      $form['menu']['parent']['#default_value'] = $tnode->menu['menu_name'] .':'. $tnode->menu['plid'];
+      if (_i18nmenu_node_translation_enabled($tnode->menu['menu_name'])) {
+        // If node translation is enabled we share one single menu item among
+        // all the nodes belonging to the translation set.
+        unset($form['menu']);
+        $form['#submit'][] = 'i18nmenu_form_node_submit';
+      }
+      else {
+        // Set default values based on translation source's menu.
+        $form['menu']['link_title']['#default_value'] = $tnode->menu['link_title'];
+        $form['menu']['weight']['#default_value'] = $tnode->menu['weight'];
+        $form['menu']['parent']['#default_value'] = $tnode->menu['menu_name'] .':'. $tnode->menu['plid'];
 
-      // Set menu language to node language.
-      $form['menu']['language'] = array('#type' => 'value', '#value' => $node->language);
+        // 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);
+        // Customized must be set to 1 to save language.
+        $form['menu']['customized'] = array('#type' => 'value', '#value' => 1);
+      }
     }
   }
 }
 
 /**
+ * Submit handler for the node form.
+ */
+function i18nmenu_form_node_submit($form, &$form_state) {
+  $form_state['values']['status_changed'] = $form['#node']->status != $form_state['values']['status'];
+}
+
+/**
  * Implementation of hook_nodeapi().
  *
  * Save or delete menu item strings associated with nodes.
  */
-function i18nmenu_nodeapi(&$node, $op) {
+function i18nmenu_nodeapi(&$node, $op, $a3, $page) {
   switch ($op) {
     case 'insert':
+      if ($node->translation_source) {
+        i18nmenu_node_item_translations_refresh_set($node->translation_source->nid);
+      }
     case 'update':
       if (isset($node->menu)) {
         $item = $node->menu;
@@ -408,6 +490,9 @@ function i18nmenu_nodeapi(&$node, $op) {
           _i18nmenu_update_item($item);
         }
       }
+      if ($node->tnid && !empty($node->status_changed)) {
+        i18nmenu_node_item_translations_refresh_set($node->tnid);
+      }
       break;
     case 'delete':
       // Delete all menu item link translations that point to this node.
@@ -415,6 +500,9 @@ function i18nmenu_nodeapi(&$node, $op) {
       while ($m = db_fetch_array($result)) {
         _i18nmenu_delete_item($m['mlid']);
       }
+      if ($node->tnid) {
+        i18nmenu_node_item_translations_refresh_set($node->tnid);
+      }
       break;
     case 'prepare translation':
       if (empty($node->menu['mlid']) && !empty($node->translation_source)) {
@@ -425,5 +513,169 @@ function i18nmenu_nodeapi(&$node, $op) {
         $node->menu['weight'] = $tnode->menu['weight'];
       }
       break;
+    case 'view':
+      if ($page) {
+        i18nmenu_node_set_item($node);
+      }
+      break;
+  }
+}
+
+/**
+ * Set as current menu item the source node to correctly handle the active
+ * trail.
+ */
+function i18nmenu_node_set_item($node) {
+  if (!empty($node->tnid) && $node->tnid != $node->nid) {
+    $item = menu_get_item();
+    $href = explode('/', $item['href']);
+    $item['original_href'] = $href;
+    $href[1] = $node->tnid;
+    $item['href'] = implode('/', $href);
+    menu_set_item(NULL, $item);
+  }
+}
+
+/**
+ * Return the nid of the node translation for the given language if available.
+ */
+function i18nmenu_node_translation($item, $map, $langcode = NULL) {
+  if (empty($item['options']['translations']) || !_i18nmenu_node_translation_enabled($item['menu_name'])) {
+    return FALSE;
+  }
+
+  // If the current item is also the active item, in $map[1] we have a node
+  // object instead of a simple nid.
+  $nid = is_object($map[1]) ? $map[1]->nid : $map[1];
+
+  if (empty($langcode)) {
+    // Retrieve the current language.
+    $langcode = i18n_get_lang();
+  }
+
+  // If the translation is available and is not the current element we use it.
+  // For performance reasons we check only if the node status is pusblished or
+  // if the user can see unpublished nodes. Uncommon cases of fine grained
+  // access controlled translated node menu items might lead to "access denied"
+  // pages.
+  $translations = $item['options']['translations'];
+  if (is_array($translations) && isset($translations[$langcode]) && $translations[$langcode]->nid != $nid && ($translations[$langcode]->status || user_access('administer nodes'))) {
+    return $translations[$langcode]->nid;
+  }
+
+  return FALSE;
+}
+
+/**
+ * Return TRUE if node item translation is enabled for the given menu.
+ */
+function _i18nmenu_node_translation_enabled($menu_name) {
+  return variable_get("i18nmenu_node_translation_$menu_name", FALSE);
+}
+
+/**
+ * Return an array of node translation nids keyed by language.
+ */
+function i18nmenu_node_get_translations($tnid, $field = 'nid') {
+  static $translations = array();
+
+  if (!isset($translations[$tnid])) {
+    $translations[$tnid] = array();
+    if ($tnid) {
+      $result = db_query("SELECT n.language, n.nid, n.status FROM {node} n WHERE n.tnid = %d", $tnid);
+      while ($row = db_fetch_object($result)) {
+        $translations[$tnid][$row->language] = $row;
+      }
+    }
+  }
+
+  if (!$field) {
+    return $translations[$tnid];
+  }
+  else {
+    $result = array();
+    foreach ($translations[$tnid] as $langcode => $node) {
+      $result[$langcode] = $node->$field;
+    }
+    return $result;
+  }
+}
+
+/**
+ * Implementation of hook_form_form_id_alter().
+ */
+function i18nmenu_form_i18n_node_select_translation_alter(&$form, $form_state) {
+  $form['#submit'][] = 'i18nmenu_node_translation_set_update';
+}
+
+/**
+ * Submit handler for i18n_node_select_translation form.
+ *
+ * Refresh menu items' translation cache if the translation set has changed.
+ */
+function i18nmenu_node_translation_set_update($form, &$form_state) {
+  $changed = FALSE;
+  $nids = array($form['node']['#value']->nid);
+
+  foreach ($form_state['values']['translations']['nid'] as $langcode => $nid) {
+    $previous = $form['translations']['nid'][$langcode]['#value'];
+    if ($previous != $nid) {
+      $changed = TRUE;
+    }
+    if ($previous) {
+      $nids[] = $previous;
+    }
+    if ($nid) {
+      $nids[] = $nid;
+    }
+  }
+
+  if ($changed) {
+    $nids = array_unique($nids);
+    i18nmenu_node_item_translations_refresh($nids);
+  }
+}
+
+/**
+ * Refresh the translation cache for any menu item belonging to the translation
+ * sets identified by the given tnid.
+ */
+function i18nmenu_node_item_translations_refresh_set($tnid) {
+  $nids = i18nmenu_node_get_translations($tnid);
+  if (empty($nids)) {
+    $nids = array($tnid);
+  }
+  i18nmenu_node_item_translations_refresh($nids);
+}
+
+/**
+ * Refresh the translation cache for any menu item belonging to the translation
+ * sets identified by the given nids.
+ */
+function i18nmenu_node_item_translations_refresh($nids) {
+  $conditions = implode(' OR ', array_fill(0, count($nids), "link_path LIKE 'node/%d%%'"));
+  $query = 'SELECT mlid FROM {menu_links} WHERE '. $conditions .' ORDER by mlid';
+  _i18nmenu_node_item_translations_refresh($query, $nids);
+}
+
+/**
+ * Refresh the translation cache for any menu item pointing to a node page.
+ */
+function i18nmenu_node_item_translations_refresh_all() {
+  // TODO: Consider using a batch process here.
+  $query = 'SELECT mlid FROM {menu_links} WHERE link_path LIKE \'node/%%\' ORDER by mlid';
+  _i18nmenu_node_item_translations_refresh($query);
+  watchdog('i18nmenu', 'Translation cache has been refreshed for all node menu items.', array(), WATCHDOG_INFO);
+}
+
+/**
+ * Helper function: actually refresh the menu items returned by the given query.
+ */
+function _i18nmenu_node_item_translations_refresh($query, $args = NULL) {
+  $result = db_query($query, $args);
+  while ($row = db_fetch_object($result)) {
+    // Refresh the item translation cache.
+    $item = menu_link_load($row->mlid);
+    menu_link_save($item);
   }
 }
