diff --git a/entity_translation_i18n_menu/entity_translation_i18n_menu.info b/entity_translation_i18n_menu/entity_translation_i18n_menu.info
new file mode 100644
index 0000000..d855daf
--- /dev/null
+++ b/entity_translation_i18n_menu/entity_translation_i18n_menu.info
@@ -0,0 +1,8 @@
+name = Entity Translation Menu
+description = Allows menu items to be translated on the entity form.
+package = Multilingual
+core = 7.x
+dependencies[] = entity_translation
+dependencies[] = i18n
+dependencies[] = i18n_menu
+files[] = entity_translation_i18n_menu.test
diff --git a/entity_translation_i18n_menu/entity_translation_i18n_menu.module b/entity_translation_i18n_menu/entity_translation_i18n_menu.module
new file mode 100644
index 0000000..83ebb82
--- /dev/null
+++ b/entity_translation_i18n_menu/entity_translation_i18n_menu.module
@@ -0,0 +1,331 @@
+<?php
+
+/**
+ * @file
+ * The menu specific translation functions and hook implementations.
+ *
+ * @todo
+ * - translation deletion
+ */
+
+/**
+ * Menu specific implementation of hook_node_prepare().
+ *
+ * Translates the menu item shown on node edit forms if the node language does
+ * not equal the language of the menu item. This means either loading the
+ * respective menu item from the translation set or localizing the item.
+ */
+function entity_translation_i18n_menu_node_prepare($node) {
+  $langcode = entity_language('node', $node);
+  if (!empty($langcode) && !empty($node->menu['language']) && $node->menu['language'] != $langcode && entity_translation_i18n_menu_item($node->menu)) {
+    $handler = entity_translation_get_handler('node', $node);
+    $source_langcode = $handler->getSourceLanguage();
+    // If we are creating a translation we need to use the source language.
+    entity_translation_i18n_menu_node_menu_item_translate($node, $source_langcode ? $source_langcode : $langcode);
+  }
+}
+
+/**
+ * Implements hook_module_implements_alter().
+ *
+ * Move our implementation of hook_node_prepare() to end of list. Required so
+ * that the 'menu' key is populated when our implementation gets called.
+ */
+function entity_translation_i18n_menu_module_implements_alter(&$implementations, $hook) {
+  if ($hook == 'node_prepare') {
+    $group = $implementations['entity_translation_i18n_menu'];
+    unset($implementations['entity_translation_i18n_menu']);
+    $implementations['entity_translation_i18n_menu'] = $group;
+  }
+}
+
+/**
+ * Menu specific implementation of hook_node_presave().
+ */
+function entity_translation_i18n_menu_node_presave($node) {
+  $handler = entity_translation_get_handler('node', $node);
+  $translations = $handler->getTranslations();
+  $source_langcode = $handler->getSourceLanguage();
+  $tset = !empty($node->menu['tset']);
+
+  // If no translation is available the menu data is always supposed to be
+  // entered in the source string language. This way we avoid having unneeded
+  // string translations hanging around.
+  if (empty($source_langcode) && count($translations->data) < 2 && !$tset) {
+    return;
+  }
+
+  // If we change from menu localization to translation sets while working on
+  // a translation (i.e. not the source node), then leave the source menu item
+  // intact and create a new one.
+  $langcode = entity_language('node', $node);
+  if (!empty($node->menu) && $tset && $node->menu['language'] == LANGUAGE_NONE && !empty($source_langcode)) {
+    $node->source_menu = menu_link_load($node->menu['mlid']);
+    unset($node->menu['mlid']);
+  }
+
+  // Store the entity language for later reference when saving a translation.
+  // If we are editing a translation in the string source language, we can skip
+  // item processing since the proper values are already in place. Instead when
+  // creating the translation we need to process the link item before saving it.
+  if (!empty($node->menu) && !empty($langcode) && ($source_langcode || $langcode != i18n_string_source_language())) {
+    $node->menu['entity_language'] = $langcode;
+    $node->menu['entity_translation_handler'] = $handler;
+  }
+
+  // If we have a translation set here we should prepare it for storage,
+  // otherwise we need to ensure the menu item has no language so it can be
+  // localized.
+  if ($tset) {
+    entity_translation_i18n_menu_item_tset_prepare($node, $langcode);
+  }
+  else {
+    $node->menu['language'] = LANGUAGE_NONE;
+  }
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function entity_translation_i18n_menu_form_menu_edit_item_alter(&$form, &$form_state) {
+  $form['#validate'][] = 'entity_translation_i18n_menu_form_menu_edit_item_validate';
+}
+
+/**
+ * Menu specific implementation of hook_menu_link_alter().
+ */
+function entity_translation_i18n_menu_menu_link_alter(&$link) {
+  if (!empty($link['mlid']) && !empty($link['entity_language']) && $link['language'] == LANGUAGE_NONE && entity_translation_i18n_menu_item($link)) {
+    $sources = array();
+    foreach (array('title' => 'link_title', 'description' => 'description') as $key => $link_field) {
+      $name = array('menu', 'item', $link['mlid'], $key);
+      $source = i18n_string_get_source($name);
+      // The source might not exist yet.
+      $sources[$key] = is_object($source) ? $source->get_string() : $link[$link_field];
+    }
+
+    // If the link values to be saved are translated, we need to revert the
+    // localized menu link back to the original. This way they can be saved
+    // without accidentially storing a translation string as a source string.
+    // The translated values are put in a separate key for later reference.
+    if ($link['entity_language'] != i18n_string_source_language()) {
+      $link['entity_translation_strings'] = array(
+        'title' => $link['link_title'],
+        'description' => $link['description'],
+      );
+      $link['link_title'] = $sources['title'];
+      $link['options']['attributes']['title'] = $sources['description'];
+    }
+    // If the link values are in the string source language, we need to save
+    // the previous source values as translations. As a matter of fact this can
+    // happen only when initially creating a menu item in a language different
+    // from the source string one.
+    else {
+      $link['entity_translation_strings'] = array(
+        'title' => $sources['title'],
+        'description' => $sources['description'],
+      );
+      $link['entity_language'] = $link['entity_translation_handler']->getLanguage();
+    }
+  }
+}
+
+/**
+ * Menu specific implementation of hook_menu_link_update().
+ */
+function entity_translation_i18n_menu_menu_link_update($link) {
+  // Make sure localizations are saved properly.
+  if (entity_translation_i18n_menu_item($link) && !empty($link['entity_translation_strings'])) {
+    $string_langcode = isset($link['entity_language']) ? $link['entity_language'] : i18n_string_source_language();
+    $name = implode(':', array('menu', 'item', $link['mlid']));
+    foreach ($link['entity_translation_strings'] as $key => $translation) {
+      i18n_string_translation_update($name . ':' . $key, $translation, $string_langcode);
+    }
+  }
+}
+
+/**
+ * Menu specific alterations for the entity form.
+ *
+ * Adds to the regular menu item widget a checkbox to choose whether the current
+ * menu item should be localized or part of a translation set.
+ */
+function entity_translation_i18n_menu_form(&$form, &$form_state) {
+  $info = entity_translation_edit_form_info($form, $form_state);
+
+  if ($info && $info['entity type'] == 'node') {
+    $node = $info['entity'];
+    $source_menu = isset($node->source_menu) ? $node->source_menu : $node->menu;
+
+    // Check that the menu item of the source node is translatable.
+    if (isset($form['menu']) && !empty($source_menu) && i18n_menu_mode($source_menu['menu_name'], I18N_MODE_MULTIPLE)) {
+      $default = isset($source_menu['language']) && $source_menu['language'] != LANGUAGE_NONE;
+      $languages = language_list();
+      $handler = entity_translation_entity_form_get_handler($form, $form_state);
+      $langcode = $handler->getFormLanguage();
+      $language_name = isset($languages[$langcode]) ? t($languages[$langcode]->name) : t('current');
+
+      $form['menu']['link']['tset'] = array(
+        '#type' => 'checkbox',
+        '#title' => t('Menu link enabled only for the %language language', array('%language' => $language_name)),
+        '#prefix' => '<label>' . t('Menu translation') . '</label>',
+        '#default_value' => $default,
+        '#description' => t('Create a different menu link for each translation. Every link will have its own parent and weight, otherwise only title and description will be translated.'),
+        '#weight' => 10,
+      );
+
+      if (!empty($default)) {
+        $translation_set = i18n_menu_translation_load($source_menu['i18n_tsid']);
+        $translations = $translation_set ? $translation_set->get_translations() : FALSE;
+        if (!empty($translations) && (count($translations) > 1 || !isset($translations[$langcode]))) {
+          $form['menu']['link']['tset']['#disabled'] = TRUE;
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Validation handler for the menu item edit form.
+ */
+function entity_translation_i18n_menu_form_menu_edit_item_validate($form, &$form_state) {
+  $item = $form_state['values'];
+
+  // Localizable menu items should not be created when a translation set for the
+  // same path already exists.
+  if ($item['language'] == LANGUAGE_NONE) {
+    $count = db_select('menu_links', 'ml')
+      ->condition('ml.link_path', $item['link_path'])
+      ->condition('ml.i18n_tsid', 0, '<>')
+      ->countQuery()
+      ->execute()
+      ->fetchField();
+
+    if (!empty($count)) {
+      form_set_error('language', t('There are already one or more items with a language assigned for the given path. Remove them or assign a language to this item too.'));
+    }
+  }
+}
+
+/**
+ * Checks whether a given menu item is translatable through entity translation.
+ *
+ * @param array $item
+ *   A menu item.
+ *
+ * @todo
+ *   Find more generic way of determining whether ET is enabled for a link; add
+ *   support for other entities, e.g. taxonomy_term (?).
+ */
+function entity_translation_i18n_menu_item($item) {
+  $cache = &drupal_static(__FUNCTION__, array());
+
+  if (!isset($cache[$item['link_path']])) {
+    // First check that the item belongs to a menu which has translation
+    // enabled.
+    if (!i18n_menu_mode($item['menu_name'], I18N_MODE_MULTIPLE)) {
+      $cache[$item['link_path']] = FALSE;
+    }
+
+    // Check if the respective node type has entity translation enabled.
+    if (preg_match('!^node/(\d+)(/.+|)$!', $item['link_path'], $matches)) {
+      if (!entity_translation_enabled('node')) {
+        $cache[$item['link_path']] = FALSE;
+      }
+      else {
+        $type = db_select('node', 'n')
+          ->condition('nid', $matches[1])
+          ->fields('n', array('type'))
+          ->execute()->fetchField();
+        $cache[$item['link_path']] = entity_translation_node_supported_type($type);
+      }
+    }
+  }
+
+  return $cache[$item['link_path']];
+}
+
+/**
+ * Replace the menu item on the given node with a localized version.
+ *
+ * If the menu item is replaced by a different menu item from the translation
+ * set, the original item is stored in $node->source_menu.
+ *
+ * @param $node
+ *   A node object, with a menu item ($node->menu).
+ * @param $langcode
+ *   The language into which the menu item should be translates.
+ */
+function entity_translation_i18n_menu_node_menu_item_translate($node, $langcode) {
+  // Localization.
+  if ($node->menu['language'] == LANGUAGE_NONE) {
+    _i18n_menu_link_localize($node->menu, $langcode);
+  }
+  // Translation sets.
+  else {
+    $menu = NULL;
+    if (!empty($node->menu['i18n_tsid']) && $translation_set = i18n_menu_translation_load($node->menu['i18n_tsid'])) {
+      // Load menu item from translation set.
+      $menu = $translation_set->get_item($langcode);
+
+      // Set parent_depth_limit (required on node forms).
+      if (!empty($menu) && !isset($menu['parent_depth_limit'])) {
+        $menu['parent_depth_limit'] = _menu_parent_depth_limit($menu);
+      }
+    }
+
+    // Replace the menu item with the translated version, or null if there is
+    // no translated item. Store the original one in $node->source_menu.
+    $node->source_menu = $node->menu;
+    $node->menu = $menu;
+  }
+}
+
+/**
+ * Prepares the menu item attached to given entity for saving.
+ *
+ * - Ensures that different menu items attached to the entity and its
+ *   translations are stored within the same translation set.
+ * - Sets missing default values, and cleans out null values.
+ * - Sets the language of the menu item to given target language.
+ *
+ * @param $entity
+ *   Node object.
+ * @param $langcode
+ *   Target language.
+ */
+function entity_translation_i18n_menu_item_tset_prepare($entity, $langcode) {
+  // Load or create a translation set.
+  if (!empty($entity->source_menu)) {
+    if (!empty($entity->source_menu['i18n_tsid'])) {
+      $translation_set = i18n_translation_set_load($entity->source_menu['i18n_tsid']);
+    }
+    else {
+      // Make sure that the source menu item does have a language assigned.
+      if ($entity->source_menu['language'] == LANGUAGE_NONE) {
+        $entity->source_menu['language'] = $entity->menu['entity_translation_handler']->getSourceLanguage();
+        menu_link_save($entity->source_menu);
+      }
+
+      // Create new translation set.
+      $translation_set = i18n_translation_set_build('menu_link')
+        ->add_item($entity->source_menu);
+    }
+    $entity->menu['translation_set'] = $translation_set;
+  }
+
+  // Extract menu_name and pid from parent property.
+  if (!empty($entity->menu['parent'])) {
+    list($entity->menu['menu_name'], $entity->menu['plid']) = explode(':', $entity->menu['parent']);
+  }
+
+  // Remove null values.
+  $entity->menu = array_filter($entity->menu);
+
+  $entity->menu['language'] = $langcode;
+
+  $entity->menu += array(
+    'description' => '',
+    'customized' => 1,
+  );
+}
diff --git a/entity_translation_i18n_menu/entity_translation_i18n_menu.test b/entity_translation_i18n_menu/entity_translation_i18n_menu.test
new file mode 100644
index 0000000..b3dd4ab
--- /dev/null
+++ b/entity_translation_i18n_menu/entity_translation_i18n_menu.test
@@ -0,0 +1,217 @@
+<?php
+/**
+ * @file
+ * Tests for Entity translation module.
+ */
+
+/**
+ * Tests for the translation of menu items on entity forms.
+ */
+class EntityTranslationMenuTranslationTestCase extends EntityTranslationTestCase {
+
+  /**
+   * Return the test information.
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Menu translation',
+      'description' => 'Tests for the translation of menu items on entity forms.',
+      'group' => 'Entity translation',
+      'dependencies' => array('i18n_menu'),
+    );
+  }
+
+  function setUp() {
+    parent::setUp('locale', 'entity_translation', 'i18n_menu', 'entity_translation_i18n_menu');
+    $this->login($this->getAdminUser(array('administer menu')));
+    $this->addLanguage('en');
+    $this->addLanguage('es');
+    $this->configureContentType();
+    $this->configureMenu();
+    $this->enableUrlLanguageDetection();
+    $this->login($this->getTranslatorUser(array('administer menu')));
+  }
+
+  /**
+   * Configure the "Main Menu" for multilingual menu items ("Translate & Localize").
+   */
+  function configureMenu() {
+    $edit = array();
+    $edit['i18n_mode'] = I18N_MODE_MULTIPLE; // Translate & Localize.
+    $this->drupalPost('admin/structure/menu/manage/main-menu/edit', $edit, t('Save'));
+    $this->assertRaw(t('Your configuration has been saved.'), t('Menu settings have been saved.'));
+  }
+
+  /**
+   * Create page with menu item.
+   */
+  function createPage($link_title, $langcode) {
+    $edit = array();
+    $edit['title'] = $this->randomName();
+    $edit['language'] = $langcode;
+    $edit['menu[enabled]'] = TRUE;
+    $edit['menu[link_title]'] = $link_title;
+    $edit['menu[description]'] = 'link language = ' . $langcode;
+    $this->drupalPost('node/add/page', $edit, t('Save'));
+    $this->assertRaw(t('Basic page %title has been created.', array('%title' => $edit['title'])), t('Basic page created.'));
+
+    // Check to make sure the node was created.
+    $node = $this->drupalGetNodeByTitle($edit['title']);
+    $this->assertTrue($node, t('Node found in database.'));
+
+    // Check to make sure menu link was created.
+    $this->get($langcode, '<front>');
+    $this->assertText($link_title, 'New menu link found.');
+
+    return $node;
+  }
+
+  /**
+   * Create a translation with menu item.
+   */
+  function createTranslation($node, $link_title, $langcode, $use_tsets = FALSE) {
+    $this->drupalGet('node/' . $node->nid . '/edit/add/' . $node->language . '/' . $langcode);
+
+    $edit = array();
+    $edit['menu[enabled]'] = TRUE;
+    $edit['menu[link_title]'] = $link_title;
+    $edit['menu[description]'] = 'link language = ' . $langcode;
+    $edit['menu[tset]'] = $use_tsets;
+
+    $this->drupalPost(NULL, $edit, t('Save'));
+    $this->drupalGet('node/' . $node->nid . '/translate');
+    $this->assertLinkByHref('node/' . $node->nid . '/edit/' . $langcode, 0, t('Translation edit link found. Translation created.'));
+
+    // Check to make sure menu link was created.
+    $this->get($langcode, '<front>');
+    $this->assertText($link_title, 'Translation menu link found.');
+
+    return $node;
+  }
+
+  /**
+   * Edit a page menu item.
+   *
+   * Check that node form contains old menu link title, then replace with given
+   * new title.
+   */
+  function editPage($node, $old_link_title, $link_title, $langcode) {
+    $this->drupalGet('node/' . $node->nid . '/edit/' . $langcode);
+
+    $this->assertFieldByXPath("//input[@name='menu[link_title]']", $old_link_title, 'Old link title value correctly populated: ' . $old_link_title);
+
+    $edit = array();
+    $edit['menu[link_title]'] = $link_title;
+    $this->drupalPost(NULL, $edit, t('Save'));
+
+    // Check to make sure menu link was updated.
+    $this->get($langcode, '<front>');
+    $this->assertNoText($old_link_title, 'Old menu link title not found: ' . $old_link_title);
+    $this->assertText($link_title, 'New menu link title found: ' . $link_title);
+  }
+
+  /**
+   * Test if menu localization works.
+   */
+  function testMenuLocalization() {
+    // Create Basic page in English.
+    $link_title_en = $this->randomName();
+    $node = $this->createPage($link_title_en, 'en');
+
+    // Submit translation in Spanish.
+    $link_title_es = $this->randomName();
+    $node_translation = $this->createTranslation($node, $link_title_es, 'es');
+
+    // Check menu links in both languages.
+    $this->get('en', '<front>');
+    $this->assertText($link_title_en);
+    $this->get('es', '<front>');
+    $this->assertText($link_title_es);
+
+    // Edit English menu link.
+    $link_title_en2 = $this->randomName();
+    $this->editPage($node, $link_title_en, $link_title_en2, 'en');
+
+    // Check that Spanish menu link has not changed.
+    $this->get('es', '<front>');
+    $this->assertText($link_title_es);
+
+    // Edit Spanish menu link.
+    $link_title_es2 = $this->randomName();
+    $this->editPage($node, $link_title_es, $link_title_es2, 'es');
+
+    // Check that English menu link has not changed.
+    $this->get('en', '<front>');
+    $this->assertText($link_title_en2);
+  }
+
+  /**
+   * Test if menu localization works (source language != default language).
+   */
+  function testMenuLocalizationCustomSourceLanguage() {
+    // Create Basic page in Spanish.
+    $link_title_es = $this->randomName();
+    $node = $this->createPage($link_title_es, 'es');
+
+    // Submit translation in English.
+    $link_title_en = $this->randomName();
+    $node_translation = $this->createTranslation($node, $link_title_en, 'en');
+
+    // Check menu links in both languages.
+    $this->get('es', '<front>');
+    $this->assertText($link_title_es);
+    $this->get('en', '<front>');
+    $this->assertText($link_title_en);
+
+    // Edit Spanish menu link.
+    $link_title_es2 = $this->randomName();
+    $this->editPage($node, $link_title_es, $link_title_es2, 'es');
+
+    // Check that English menu link has not changed.
+    $this->get('en', '<front>');
+    $this->assertText($link_title_en);
+
+    // Edit English menu link.
+    $link_title_en2 = $this->randomName();
+    $this->editPage($node, $link_title_en, $link_title_en2, 'en');
+
+    // Check that Spanish menu link has not changed.
+    $this->get('es', '<front>');
+    $this->assertText($link_title_es2);
+  }
+
+  /**
+   * Test if menu translation works with separate menu items.
+   */
+  function testMenuTranslation() {
+    // Create Basic page in English.
+    $link_title_en = $this->randomName();
+    $node = $this->createPage($link_title_en, 'en');
+
+    // Submit translation in Spanish.
+    $link_title_es = $this->randomName();
+    $node_translation = $this->createTranslation($node, $link_title_es, 'es', TRUE);
+
+    // Check menu links in both languages.
+    $this->get('en', '<front>');
+    $this->assertText($link_title_en);
+    $this->get('es', '<front>');
+    $this->assertText($link_title_es);
+
+    // Edit English menu link.
+    $link_title_en2 = $this->randomName();
+    $this->editPage($node, $link_title_en, $link_title_en2, 'en');
+
+    // Check that Spanish menu link has not changed.
+    $this->get('es', '<front>');
+    $this->assertText($link_title_es);
+
+    // Edit Spanish menu link.
+    $link_title_es2 = $this->randomName();
+    $this->editPage($node, $link_title_es, $link_title_es2, 'es');
+
+    // Check that English menu link has not changed.
+    $this->get('en', '<front>');
+    $this->assertText($link_title_en2);
+  }
+}
diff --git a/includes/translation.handler.inc b/includes/translation.handler.inc
index 9ecc4a8..8e0a8c5 100644
--- a/includes/translation.handler.inc
+++ b/includes/translation.handler.inc
@@ -832,6 +832,11 @@ class EntityTranslationDefaultHandler implements EntityTranslationHandlerInterfa
       );
     }
 
+    // If Menu translation is available translate the menu strings.
+    if (module_exists('entity_translation_i18n_menu')) {
+      $this->menuForm($form, $form_state);
+    }
+
     // We need to process the posted form as early as possible to update the
     // form language value.
     array_unshift($form['#validate'], 'entity_translation_entity_form_validate');
@@ -844,6 +849,13 @@ class EntityTranslationDefaultHandler implements EntityTranslationHandlerInterfa
   }
 
   /**
+   * Performs alterations to the menu widget if available.
+   */
+  protected function menuForm(&$form, &$form_state) {
+    // By default do nothing: only nodes are currently supported.
+  }
+
+  /**
    * @see EntityTranslationHandlerInterface::entityFormValidate()
    */
   public function entityFormValidate($form, &$form_state) {
@@ -899,6 +911,11 @@ class EntityTranslationDefaultHandler implements EntityTranslationHandlerInterfa
     $translation['created'] = empty($values['created']) ? REQUEST_TIME : strtotime($values['created']);
     $this->setTranslation($translation);
 
+    // Save menu translation if i18n_menu is available.
+    if (module_exists('entity_translation_i18n_menu')) {
+      $this->menuFormSubmit($form, $form_state);
+    }
+
     // If no redirect has been explicitly set, go to the edit form for the
     // current form language.
     if ($new_translation && empty($form_state['redirect']) && !$this->isNewEntity()) {
@@ -907,6 +924,13 @@ class EntityTranslationDefaultHandler implements EntityTranslationHandlerInterfa
   }
 
   /**
+   * Performs alterations to the menu widget if available.
+   */
+  protected function menuFormSubmit($form, &$form_state) {
+    // By default do nothing: only nodes are currently supported.
+  }
+
+  /**
    * @see EntityTranslationHandlerInterface::localTasksAlter()
    */
   public function localTasksAlter(&$data, $router_item, $root_path) {
@@ -934,7 +958,7 @@ class EntityTranslationDefaultHandler implements EntityTranslationHandlerInterfa
       foreach ($translations->data as $langcode => $translation) {
         if ($this->getTranslationAccess($langcode)) {
           $links = $this->languageSwitchLinks($this->getEditPath($langcode));
-          $link = $links->links[$langcode];
+          $link = is_object($links) ? $links->links[$langcode] : array();
 
           if (isset($link['href'])) {
             $tab = array();
diff --git a/includes/translation.handler.node.inc b/includes/translation.handler.node.inc
index de91896..c6657db 100644
--- a/includes/translation.handler.node.inc
+++ b/includes/translation.handler.node.inc
@@ -55,6 +55,14 @@ class EntityTranslationNodeHandler extends EntityTranslationDefaultHandler {
   }
 
   /**
+   * @see EntityTranslationDefaultHandler::menuForm()
+   */
+  protected function menuForm(&$form, &$form_state) {
+    module_load_include('module', 'entity_translation_i18n_menu');
+    entity_translation_i18n_menu_form($form, $form_state);
+  }
+
+  /**
    * @see EntityTranslationDefaultHandler::entityFormSubmit()
    */
   public function entityFormSubmit($form, &$form_state) {
@@ -75,6 +83,15 @@ class EntityTranslationNodeHandler extends EntityTranslationDefaultHandler {
   }
 
   /**
+   * @see EntityTranslationDefaultHandler::menuFormSubmit()
+   */
+  protected function menuFormSubmit($form, &$form_state) {
+    // Ensure that the menu property has the 'menu_name' and 'plid' keys.
+    // @todo Remove this once hook implementation calling order is fixed.
+    menu_node_submit($this->entity, $form, $form_state);
+  }
+
+  /**
    * @see EntityTranslationDefaultHandler::entityFormTitle()
    */
   protected function entityFormTitle() {
diff --git a/tests/entity_translation.test b/tests/entity_translation.test
index 3b08b8d..9f066c9 100644
--- a/tests/entity_translation.test
+++ b/tests/entity_translation.test
@@ -14,6 +14,15 @@ class EntityTranslationTestCase extends DrupalWebTestCase {
   protected $admin_user;
   protected $translator_user;
 
+  function setUp() {
+    call_user_func_array(array('parent', 'setUp'), func_get_args());
+
+    // Reset user fields to make test object reusable.
+    unset($this->current_user);
+    unset($this->admin_user);
+    unset($this->translator_user);
+  }
+
   /**
    * Retrieves a Drupal path or an absolute path with language.
    *
@@ -37,10 +46,13 @@ class EntityTranslationTestCase extends DrupalWebTestCase {
 
   /**
    * Returns a user with administration rights.
+   *
+   * @param $permissions
+   *   Additional permissions for administrative user.
    */
-  function getAdminUser() {
+  function getAdminUser(array $permissions = array()) {
     if (!isset($this->admin_user)) {
-      $this->admin_user = $this->drupalCreateUser(array(
+      $this->admin_user = $this->drupalCreateUser(array_merge(array(
         'bypass node access',
         'administer nodes',
         'administer languages',
@@ -48,21 +60,25 @@ class EntityTranslationTestCase extends DrupalWebTestCase {
         'administer blocks',
         'access administration pages',
         'administer site configuration',
-      ));
+      ), $permissions));
     }
     return $this->admin_user;
   }
 
   /**
    * Returns a user with minimal translation rights.
+   *
+   * @param $permissions
+   *   Additional permissions for administrative user.
    */
-  function getTranslatorUser() {
+  function getTranslatorUser(array $permissions = array()) {
     if (!isset($this->translator_user)) {
-      $this->translator_user = $this->drupalCreateUser(array(
+      $this->translator_user = $this->drupalCreateUser(array_merge(array(
         'create page content',
         'edit own page content',
+        'edit original values', // @todo required for "edit own page content"
         'translate any entity',
-      ));
+      ), $permissions));
     }
     return $this->translator_user;
   }
@@ -78,6 +94,23 @@ class EntityTranslationTestCase extends DrupalWebTestCase {
   }
 
   /**
+   * Enable URL language detection.
+   */
+  function enableUrlLanguageDetection() {
+    // Enable URL language detection and selection.
+    $edit = array(
+      'language[enabled][locale-url]' => TRUE,
+      'language_content[enabled][locale-interface]' => TRUE
+    );
+    $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
+    $this->assertRaw(t('Language negotiation configuration saved.'), t('URL language detection enabled.'));
+    $this->drupalGet('admin/config/regional/language/configure');
+    // Reset caches.
+    drupal_static_reset('locale_url_outbound_alter');
+    drupal_static_reset('language_list');
+  }
+
+  /**
    * Get a language object from a language code.
    */
   public function getLanguage($langcode) {
