diff --git a/core/includes/entity.inc b/core/includes/entity.inc
index b651e32..61a304e 100644
--- a/core/includes/entity.inc
+++ b/core/includes/entity.inc
@@ -521,7 +521,7 @@ function entity_form_submit_build_entity($entity_type, $entity, $form, &$form_st
   // Invoke all specified builders for copying form values to entity properties.
   if (isset($form['#entity_builders'])) {
     foreach ($form['#entity_builders'] as $function) {
-      $function($entity_type, $entity, $form, $form_state);
+      call_user_func_array($function, array($entity_type, $entity, &$form, &$form_state));
     }
   }
 
diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php
index fb75ee2..81e8f45 100644
--- a/core/lib/Drupal/Core/Entity/Entity.php
+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -156,22 +156,21 @@ class Entity implements EntityInterface {
    * Implements EntityInterface::translations().
    */
   public function translations() {
-    $languages = array();
+    $default_language = $this->language();
+    $languages = array($default_language->langcode => $default_language);
     $entity_info = $this->entityInfo();
-    if ($entity_info['fieldable'] && ($default_language = $this->language())) {
+    if ($entity_info['fieldable']) {
       // Go through translatable properties and determine all languages for
       // which translated values are available.
       foreach (field_info_instances($this->entityType, $this->bundle()) as $field_name => $instance) {
         $field = field_info_field($field_name);
         if (field_is_translatable($this->entityType, $field) && isset($this->$field_name)) {
-          foreach ($this->$field_name as $langcode => $value)  {
+          foreach (array_filter($this->$field_name) as $langcode => $value)  {
             $languages[$langcode] = TRUE;
           }
         }
       }
-      // Remove the default language from the translations.
-      unset($languages[$default_language->langcode]);
-      $languages = array_intersect_key(language_list(), $languages);
+      $languages = array_intersect_key(language_list(LANGUAGE_ALL), $languages);
     }
     return $languages;
   }
diff --git a/core/lib/Drupal/Core/Entity/EntityFormController.php b/core/lib/Drupal/Core/Entity/EntityFormController.php
index 2828d5f..73f5961 100644
--- a/core/lib/Drupal/Core/Entity/EntityFormController.php
+++ b/core/lib/Drupal/Core/Entity/EntityFormController.php
@@ -175,6 +175,7 @@ class EntityFormController implements EntityFormControllerInterface {
    *   A reference to a keyed array containing the current state of the form.
    */
   public function submit(array $form, array &$form_state) {
+    $this->submitEntityLanguage($form, $form_state);
     $entity = $this->buildEntity($form, $form_state);
     $this->setEntity($entity, $form_state);
     return $entity;
@@ -231,6 +232,49 @@ class EntityFormController implements EntityFormControllerInterface {
   }
 
   /**
+   * Implements EntityFormControllerInterface::isDefaultFormLangcode().
+   */
+  public function isDefaultFormLangcode($form_state) {
+    return $this->getFormLangcode($form_state) == $this->getEntity($form_state)->language()->langcode;
+  }
+
+  /**
+   * Handle possible entity language changes.
+   */
+  protected function submitEntityLanguage(array $form, array &$form_state) {
+    // Update the form language as it might have changed.
+    if (isset($form_state['values']['langcode']) && $this->isDefaultFormLangcode($form_state)) {
+      $form_state['langcode'] = $form_state['values']['langcode'];
+    }
+
+    $entity = $this->getEntity($form_state);
+    $entity_type = $entity->entityType();
+
+    if (field_has_translation_handler($entity_type)) {
+      $form_langcode = $this->getFormLangcode($form_state);
+
+      // If we are editing the default language values, we use the submitted
+      // entity language as the new language for fields to handle any language
+      // change. Otherwise the current form language is the proper value, since
+      // in this case it is not supposed to change.
+      $current_langcode = $entity->language()->langcode == $form_langcode ? $form_state['values']['langcode'] : $form_langcode;
+
+      foreach (field_info_instances($entity_type, $entity->bundle()) as $instance) {
+        $field_name = $instance['field_name'];
+        $field = field_info_field($field_name);
+        $previous_langcode = $form[$field_name]['#language'];
+
+        // Handle a possible language change: new language values are inserted,
+        // previous ones are deleted.
+        if ($field['translatable'] && $previous_langcode != $current_langcode) {
+          $form_state['values'][$field_name][$current_langcode] = $form_state['values'][$field_name][$previous_langcode];
+          $form_state['values'][$field_name][$previous_langcode] = array();
+        }
+      }
+    }
+  }
+
+  /**
    * Implements Drupal\Core\Entity\EntityFormControllerInterface::buildEntity().
    */
   public function buildEntity(array $form, array &$form_state) {
diff --git a/core/lib/Drupal/Core/Entity/EntityFormControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityFormControllerInterface.php
index 72c97f1..b550099 100644
--- a/core/lib/Drupal/Core/Entity/EntityFormControllerInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityFormControllerInterface.php
@@ -52,6 +52,11 @@ interface EntityFormControllerInterface {
   public function getFormLangcode(array $form_state);
 
   /**
+   * Returns TRUE if the enity form language is matches the entity one.
+   */
+  public function isDefaultFormLangcode($form_state);
+
+  /**
    * Returns the operation identifying the form controller.
    *
    * @return string
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index bd9c107..1b8588f 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -150,6 +150,12 @@ function comment_entity_info() {
     );
   }
 
+  if (module_exists('translation_entity')) {
+    $return['comment']['translation']['translation_entity'] = array(
+      'class' => 'Drupal\comment\CommentTranslationController',
+    );
+  }
+
   return $return;
 }
 
@@ -1247,10 +1253,24 @@ function comment_form_node_type_form_alter(&$form, $form_state) {
         DRUPAL_REQUIRED => t('Required'),
       ),
     );
+    if (module_exists('translation_entity')) {
+      $form['comment'] += translation_entity_enable_widget($form_state, 'comment_comment_node', $form['#node_type']->type, FALSE);
+      array_unshift($form['#submit'], 'comment_translation_entity_enable_submit');
+    }
   }
 }
 
 /**
+ * Checks if translation can be enabled.
+ */
+function comment_translation_entity_enable_submit($form, &$form_state) {
+  $comment_form_state = $form_state;
+  $comment_form_state['translation_entity']['variable_name'] = 'translation_entity_enabled_comment_comment_node_' . $form['#node_type']->type;
+  $comment_form_state['translation_entity']['element_name'] = 'translation_entity_enabled_comment_comment_node';
+  translation_entity_enable_widget_submit($form, $comment_form_state);
+}
+
+/**
  * Implements hook_form_BASE_FORM_ID_alter().
  */
 function comment_form_node_form_alter(&$form, $form_state) {
diff --git a/core/modules/comment/lib/Drupal/comment/CommentTranslationController.php b/core/modules/comment/lib/Drupal/comment/CommentTranslationController.php
new file mode 100644
index 0000000..1bd2e57
--- /dev/null
+++ b/core/modules/comment/lib/Drupal/comment/CommentTranslationController.php
@@ -0,0 +1,34 @@
+<?php
+
+
+/**
+ * @file
+ * Definition of Drupal\comment\CommentTranslationController.
+ */
+
+namespace Drupal\comment;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\translation_entity\EntityTranslationController;
+
+/**
+ * Defines the translation controller class for comments.
+ */
+class CommentTranslationController extends EntityTranslationController {
+
+  /**
+   * Overrides EntityTranslationController::entityFormAlter().
+   */
+  public function entityFormAlter(array &$form, array &$form_state, EntityInterface $entity) {
+    parent::entityFormAlter($form, $form_state, $entity);
+    // Adjust the translation fieldset weight to move it above the form actions.
+    $form['translation']['#weight'] = $form['actions']['#weight'] - 0.01;
+  }
+
+  /**
+   * Overrides EntityTranslationController::entityFormTitle().
+   */
+  protected function entityFormTitle(EntityInterface $entity) {
+    return t('Edit comment @subject', array('@subject' => $entity->label()));
+  }
+}
diff --git a/core/modules/node/content_types.inc b/core/modules/node/content_types.inc
index 355a022..e07b822 100644
--- a/core/modules/node/content_types.inc
+++ b/core/modules/node/content_types.inc
@@ -217,6 +217,13 @@ function node_type_form($form, &$form_state, $type = NULL) {
       '#title' => t('Hide language selector'),
       '#default_value' => variable_get('node_type_language_hidden_' . $type->type, TRUE),
     );
+
+    if (module_exists('translation_entity')) {
+      $widget = translation_entity_enable_widget($form_state, 'node', $form['#node_type']->type, FALSE);
+      $widget['#element_validate'][] = 'node_type_translation_entity_enable_widget_validate';
+      $form['#submit'][] = 'translation_entity_enable_widget_submit';
+      $form['language'] += $widget;
+    }
   }
   $form['display'] = array(
     '#type' => 'fieldset',
@@ -273,10 +280,27 @@ function node_type_form($form, &$form_state, $type = NULL) {
     }
   }
 
+  $form['#submit'][] = 'node_type_form_submit';
+
   return $form;
 }
 
 /**
+ * Checks if translation can be enabled.
+ *
+ * If language is set to one of the special languages and language selector is
+ * not hidden, translation cannot be enabled.
+ */
+function node_type_translation_entity_enable_widget_validate($element, &$form_state, $form) {
+  if (language_is_locked($form_state['values']['node_type_language_default']) && $form_state['values']['node_type_language_hidden'] && $form_state['values']['translation_entity_enabled_node']) {
+    foreach (language_list(LANGUAGE_LOCKED) as $language) {
+      $locked_languages[] = $language->name;
+    }
+    form_set_error('node_type_language_translation_entity_enabled', t('Translation is not supported if language is always one of: @locked_languages', array('@locked_languages' => implode(", ", $locked_languages))));
+  }
+}
+
+/**
  * Helper function for teaser length choices.
  */
 function _node_characters($length) {
diff --git a/core/modules/node/lib/Drupal/node/NodeFormController.php b/core/modules/node/lib/Drupal/node/NodeFormController.php
index b9bf7eb..1b6d1fb 100644
--- a/core/modules/node/lib/Drupal/node/NodeFormController.php
+++ b/core/modules/node/lib/Drupal/node/NodeFormController.php
@@ -316,8 +316,6 @@ class NodeFormController extends EntityFormController {
    * Overrides Drupal\Core\Entity\EntityFormController::submit().
    */
   public function submit(array $form, array &$form_state) {
-    $this->submitNodeLanguage($form, $form_state);
-
     // Build the node object from the submitted values.
     $node = parent::submit($form, $form_state);
 
@@ -331,36 +329,6 @@ class NodeFormController extends EntityFormController {
   }
 
   /**
-   * Handle possible node language changes.
-   */
-  protected function submitNodeLanguage(array $form, array &$form_state) {
-    if (field_has_translation_handler('node', 'node')) {
-      $bundle = $form_state['values']['type'];
-      $entity = $this->getEntity($form_state);
-      $form_langcode = $this->getFormLangcode($form_state);
-
-      // If we are editing the default language values, we use the submitted
-      // entity language as the new language for fields to handle any language
-      // change. Otherwise the current form language is the proper value, since
-      // in this case it is not supposed to change.
-      $current_langcode = $entity->language()->langcode == $form_langcode ? $form_state['values']['langcode'] : $form_langcode;
-
-      foreach (field_info_instances('node', $bundle) as $instance) {
-        $field_name = $instance['field_name'];
-        $field = field_info_field($field_name);
-        $previous_langcode = $form[$field_name]['#language'];
-
-        // Handle a possible language change: new language values are inserted,
-        // previous ones are deleted.
-        if ($field['translatable'] && $previous_langcode != $current_langcode) {
-          $form_state['values'][$field_name][$current_langcode] = $form_state['values'][$field_name][$previous_langcode];
-          $form_state['values'][$field_name][$previous_langcode] = array();
-        }
-      }
-    }
-  }
-
-  /**
    * Form submission handler for the 'preview' action.
    *
    * @param $form
diff --git a/core/modules/node/lib/Drupal/node/NodeTranslationController.php b/core/modules/node/lib/Drupal/node/NodeTranslationController.php
new file mode 100644
index 0000000..134800b
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/NodeTranslationController.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\node\NodeTranslationController.
+ */
+
+namespace Drupal\node;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\translation_entity\EntityTranslationController;
+
+/**
+ * Defines the translation controller class for nodes.
+ */
+class NodeTranslationController extends EntityTranslationController {
+
+  /**
+   * Overrides EntityTranslationController::getAccess().
+   */
+  public function getAccess(EntityInterface $entity, $op) {
+    return node_access($op, $entity);
+  }
+
+  /**
+   * Overrides EntityTranslationController::entityFormAlter().
+   */
+  public function entityFormAlter(array &$form, array &$form_state, EntityInterface $entity) {
+    parent::entityFormAlter($form, $form_state, $entity);
+
+    // Move the translation fieldset to a vertical tab.
+    if (isset($form['translation'])) {
+      $form['translation'] += array(
+        '#group' => 'additional_settings',
+        '#weight' => 100,
+        '#attributes' => array(
+          'class' => array('node-translation-options'),
+        ),
+      );
+    }
+  }
+
+  /**
+   * Overrides EntityTranslationController::entityFormTitle().
+   */
+  protected function entityFormTitle(EntityInterface $entity) {
+    $type_name = node_type_get_name($entity);
+    return t('<em>Edit @type</em> @title', array('@type' => $type_name, '@title' => $entity->label()));
+  }
+}
diff --git a/core/modules/node/node.js b/core/modules/node/node.js
index 0899d3c..33bc88a 100644
--- a/core/modules/node/node.js
+++ b/core/modules/node/node.js
@@ -42,6 +42,21 @@ Drupal.behaviors.nodeFieldsetSummaries = {
       }
       return vals.join(', ');
     });
+
+    $context.find('fieldset.node-translation-options').drupalSetSummary(function (context) {
+      var translate;
+      var $checkbox = $context.find('.form-item-translation-translate input');
+
+      if ($checkbox.size()) {
+        translate = $checkbox.is(':checked') ? Drupal.t('Needs to be updated') : Drupal.t('Does not need to be updated');
+      }
+      else {
+        $checkbox = $context.find('.form-item-translation-retranslate input');
+        translate = $checkbox.is(':checked') ? Drupal.t('Flag translations as outdated') : Drupal.t('Do not flag translations as outdated');
+      }
+
+      return translate;
+    });
   }
 };
 
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index bca8d9b..a9225f3 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -238,6 +238,12 @@ function node_entity_info() {
     $return['node']['translation']['node'] = TRUE;
   }
 
+  if (module_exists('translation_entity')) {
+    $return['node']['translation']['translation_entity'] = array(
+      'class' => 'Drupal\node\NodeTranslationController',
+    );
+  }
+
   // Search integration is provided by node.module, so search-related
   // view modes for nodes are defined here and not in search.module.
   if (module_exists('search')) {
@@ -2636,7 +2642,7 @@ function node_update_index() {
   $counter = 0;
   foreach (node_load_multiple($nids) as $node) {
     // Determine when the maximum number of indexable items is reached.
-    $counter += 1 + count($node->translations());
+    $counter += count($node->translations());
     if ($counter > $limit) {
       break;
     }
@@ -2656,7 +2662,7 @@ function _node_index_node(Node $node) {
   // results half-life calculation.
   variable_set('node_cron_last', $node->changed);
 
-  $languages = array_merge(array(language_load($node->langcode)), $node->translations());
+  $languages = $node->translations();
 
   foreach ($languages as $language) {
     // Render the node.
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationTest.php
index a571f61..ca2066a 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationTest.php
@@ -24,7 +24,7 @@ class EntityTranslationTest extends WebTestBase {
    *
    * @var array
    */
-  public static $modules = array('entity_test', 'locale');
+  public static $modules = array('entity_test', 'locale', 'node');
 
   protected $langcodes;
 
@@ -81,7 +81,7 @@ class EntityTranslationTest extends WebTestBase {
       'uid' => $GLOBALS['user']->uid,
     ));
     $this->assertEqual($entity->language()->langcode, LANGUAGE_NOT_SPECIFIED, 'Entity language not specified.');
-    $this->assertFalse($entity->translations(), 'No translations are available');
+    $this->assertEqual(count($entity->translations()), 1, 'No translations are available');
 
     // Set the value in default language.
     $entity->set($this->field_name, array(0 => array('value' => 'default value')));
@@ -111,7 +111,7 @@ class EntityTranslationTest extends WebTestBase {
     $entity->setLangcode($this->langcodes[0]);
     $entity->{$this->field_name} = array();
     $this->assertEqual($entity->language(), language_load($this->langcodes[0]), 'Entity language retrieved.');
-    $this->assertFalse($entity->translations(), 'No translations are available');
+    $this->assertEqual(count($entity->translations()), 1, 'No translations are available');
 
     // Set the value in default language.
     $entity->set($this->field_name, array(0 => array('value' => 'default value')));
@@ -127,6 +127,7 @@ class EntityTranslationTest extends WebTestBase {
     $value = $entity->get($this->field_name);
     $this->assertEqual($value, array(0 => array('value' => 'default value')), 'Untranslated value stays.');
 
+    $translations[$entity->language()->langcode] = $entity->language();
     $translations[$this->langcodes[1]] = language_load($this->langcodes[1]);
     $this->assertEqual($entity->translations(), $translations, 'Translations retrieved.');
 
@@ -270,4 +271,73 @@ class EntityTranslationTest extends WebTestBase {
     $result = $query->execute();
     $this->assertEqual(count($result), 1, 'One entity loaded by name, uid and field value using different language meta conditions.');
   }
+
+  /**
+   * Tests entity form language.
+   */
+  function testEntityFormLanguage() {
+    $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
+
+    $web_user = $this->drupalCreateUser(array('create page content', 'edit own page content', 'administer content types'));
+    $this->drupalLogin($web_user);
+
+    // Create a node with language LANGUAGE_NOT_SPECIFIED.
+    $edit = array();
+    $langcode = LANGUAGE_NOT_SPECIFIED;
+    $edit["title"] = $this->randomName(8);
+    $edit["body[$langcode][0][value]"] = $this->randomName(16);
+
+    $this->drupalGet('node/add/page');
+    $form_langcode = variable_get('entity_form_langcode', FALSE);
+    $this->drupalPost(NULL, $edit, t('Save'));
+
+    $node = $this->drupalGetNodeByTitle($edit["title"]);
+    $this->assertTrue($node->langcode == $form_langcode, 'Form language is the same as the entity language.');
+
+    // Edit the node and test the form language.
+    $this->drupalGet($this->langcodes[0] . '/node/' . $node->nid . '/edit');
+    $form_langcode = variable_get('entity_form_langcode', FALSE);
+    $this->assertTrue($node->langcode == $form_langcode, 'Form language is the same as the entity language.');
+
+    // Explicitly set form langcode.
+    $langcode = $this->langcodes[0];
+    entity_get_form($node, 'default', $langcode);
+    $form_langcode = variable_get('entity_form_langcode', FALSE);
+    $this->assertTrue($langcode == $form_langcode, 'Form language is the same as the language parameter.');
+
+    // Enable language selector.
+    $this->drupalGet('admin/structure/types/manage/page');
+    $edit = array('node_type_language_hidden' => FALSE, 'node_type_language_default' => LANGUAGE_NOT_SPECIFIED);
+    $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
+    $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Basic page')), t('Basic page content type has been updated.'));
+
+    // Create a node with language.
+    $edit = array();
+    $langcode = $this->langcodes[0];
+    $field_langcode = LANGUAGE_NOT_SPECIFIED;
+    $edit["title"] = $this->randomName(8);
+    $edit["body[$field_langcode][0][value]"] = $this->randomName(16);
+    $edit['langcode'] = $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.'));
+
+    // Make body translatable.
+    $field = field_info_field('body');
+    $field['translatable'] = TRUE;
+    field_update_field($field);
+    $field = field_info_field('body');
+    $this->assertTrue($field['translatable'], "Field body is translatable.");
+
+    // Create a body translation and check the form language.
+    $langcode2 = $this->langcodes[1];
+    $node->set('body', array(array('value' => $this->randomName(16))), $langcode2);
+    $node->save();
+    $this->drupalGet($langcode2 . '/node/' . $node->nid . '/edit');
+    $form_langcode = variable_get('entity_form_langcode', FALSE);
+    $this->assertTrue($langcode2 == $form_langcode, "Node edit form language is $langcode2.");
+  }
 }
diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module
index c634834..537f71f 100644
--- a/core/modules/system/tests/modules/entity_test/entity_test.module
+++ b/core/modules/system/tests/modules/entity_test/entity_test.module
@@ -23,11 +23,19 @@ function entity_test_entity_info() {
       'id' => 'id',
       'uuid' => 'uuid',
     ),
+    'menu base path' => 'entity-test/manage/%entity_test',
+    'translation' => array(
+      'translation_entity' => array(
+        'class' => 'Drupal\entity_test\EntityTestTranslationController',
+      ),
+    ),
   );
+
   // Optionally specify a translation handler for testing translations.
   if (variable_get('entity_test_translation')) {
     $items['entity_test']['translation']['entity_test'] = TRUE;
   }
+
   return $items;
 }
 
diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTest.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTest.php
index f88b685..9393d3a 100644
--- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTest.php
+++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTest.php
@@ -21,14 +21,14 @@ class EntityTest extends Entity {
    *
    * @var array
    */
-  protected $properties;
+  protected $properties = array();
 
   /**
    * An array of allowed language codes.
    *
    * @var array
    */
-  protected static $langcodes;
+  protected static $languages;
 
   /**
    * Constructs a new entity object.
@@ -36,9 +36,9 @@ class EntityTest extends Entity {
   public function __construct(array $values, $entity_type) {
     parent::__construct($values, $entity_type);
 
-    if (!isset(self::$langcodes)) {
+    if (!isset(self::$languages)) {
       // The allowed languages are simply all the available ones in the system.
-      self::$langcodes = drupal_map_assoc(array_keys(language_list(LANGUAGE_ALL)));
+      self::$languages = language_list(LANGUAGE_ALL);
     }
 
     // Initialize the original entity language with the provided value or fall
@@ -50,8 +50,17 @@ class EntityTest extends Entity {
     // Set initial values ensuring that only real properties are stored.
     // @todo For now we have no way to mark a property as multlingual hence we
     // just assume that all of them are.
-    unset($values['id'], $values['uuid'], $values['default_langcode']);
-    $this->setProperties($values, $this->langcode);
+    unset($values['id'], $values['uuid'], $values['langcode'], $values['default_langcode']);
+    if (!empty($values)) {
+      $this->setProperties($values, $this->langcode);
+    }
+  }
+
+  /**
+   * Overrides Drupal\entity\Entity::label().
+   */
+  public function label($langcode = NULL) {
+    return $this->get('name', $langcode);
   }
 
   /**
@@ -60,14 +69,16 @@ class EntityTest extends Entity {
    * @param $langcode
    */
   public function setLangcode($langcode) {
-    // If the original language is changed the related properties must change
-    // their language accordingly.
-    $prev_langcode = $this->langcode;
-    if (isset($this->properties[$prev_langcode])) {
-      $this->properties[$langcode] = $this->properties[$prev_langcode];
-      unset($this->properties[$prev_langcode]);
+    if ($langcode != $this->langcode) {
+      // If the original language is changed the related properties must change
+      // their language accordingly.
+      $prev_langcode = $this->langcode;
+      if (isset($this->properties[$prev_langcode])) {
+        $this->properties[$langcode] = $this->properties[$prev_langcode];
+        unset($this->properties[$prev_langcode]);
+      }
+      $this->langcode = $langcode;
     }
-    $this->langcode = $langcode;
   }
 
   /**
@@ -94,7 +105,7 @@ class EntityTest extends Entity {
    */
   public function set($property_name, $value, $langcode = NULL) {
     $langcode = !empty($langcode) ? $langcode : $this->langcode;
-    if (!isset(self::$langcodes[$langcode])) {
+    if (!isset(self::$languages[$langcode])) {
       throw new InvalidArgumentException("Detected an invalid language '$langcode' while setting '$property_name' to '$value'.");
     }
     $entity_info = $this->entityInfo();
@@ -111,8 +122,7 @@ class EntityTest extends Entity {
    */
   public function translations() {
     $translations = !empty($this->properties) ? $this->properties : array();
-    $languages = array_intersect_key(self::$langcodes, $translations);
-    unset($languages[$this->langcode]);
+    $languages = array_intersect_key(self::$languages, $translations);
     return $languages + parent::translations();
   }
 
diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php
index 0c70b13..b2cf580 100644
--- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php
+++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestFormController.php
@@ -44,6 +44,13 @@ class EntityTestFormController extends EntityFormController {
       '#weight' => -10,
     );
 
+    $form['langcode'] = array(
+      '#title' => t('Language'),
+      '#type' => 'language_select',
+      '#default_value' => $entity->language()->langcode,
+      '#languages' => LANGUAGE_ALL,
+    );
+
     return $form;
   }
 
@@ -52,6 +59,7 @@ class EntityTestFormController extends EntityFormController {
    */
   public function submit(array $form, array &$form_state) {
     $entity = parent::submit($form, $form_state);
+    $entity->setLangcode($form_state['values']['langcode']);
     $langcode = $this->getFormLangcode($form_state);
 
     // Updates multilingual properties.
diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php
index cef6593..f4bdbcb 100644
--- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php
+++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php
@@ -59,7 +59,7 @@ class EntityTestStorageController extends DatabaseStorageController {
         $entity->setLangcode($langcode);
       }
       // Make sure only real properties are stored.
-      unset($values['id'], $values['default_langcode']);
+      unset($values['id'], $values['langcode'], $values['default_langcode']);
       $entity->setProperties($values, $langcode);
     }
 
@@ -72,23 +72,32 @@ class EntityTestStorageController extends DatabaseStorageController {
   protected function postSave(EntityInterface $entity, $update) {
     $default_langcode = ($language = $entity->language()) ? $language->langcode : LANGUAGE_NOT_SPECIFIED;
     $langcodes = array_keys($entity->translations());
-    $langcodes[] = $default_langcode;
+
+    // Delete and insert to handle removed values.
+    db_delete('entity_test_property_data')
+      ->condition('id', $entity->id())
+      ->condition('langcode', $langcodes)
+      ->execute();
+
+    $query = db_insert('entity_test_property_data');
 
     foreach ($langcodes as $langcode) {
       $properties = $entity->getProperties($langcode);
 
-      $values = array(
-        'id' => $entity->id(),
-        'langcode' => $langcode,
-        'default_langcode' => intval($default_langcode == $langcode),
-      ) + $properties;
-
-      db_merge('entity_test_property_data')
-        ->fields($values)
-        ->condition('id', $values['id'])
-        ->condition('langcode', $values['langcode'])
-        ->execute();
+      if (!empty($properties)) {
+        $values = array(
+          'id' => $entity->id(),
+          'langcode' => $langcode,
+          'default_langcode' => intval($default_langcode == $langcode),
+        ) + $properties;
+
+        $query
+          ->fields(array_keys($values))
+          ->values($values);
+      }
     }
+
+    $query->execute();
   }
 
   /**
diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestTranslationController.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestTranslationController.php
new file mode 100644
index 0000000..54d93e6
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestTranslationController.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\translation_entity\EntityTranslationController.
+ */
+
+namespace Drupal\entity_test;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\translation_entity\EntityTranslationController;
+
+/**
+ * Test entity translation controller.
+ */
+class EntityTestTranslationController extends EntityTranslationController {
+
+  /**
+   * Overrides EntityTranslationControllerInterface::removeTranslation().
+   */
+  public function removeTranslation(EntityInterface $entity, $langcode) {
+    $entity->setProperties(array(), $langcode);
+    return parent::removeTranslation($entity, $langcode);
+  }
+}
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TermTranslationController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermTranslationController.php
new file mode 100644
index 0000000..a8e6dd7
--- /dev/null
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermTranslationController.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\taxonomy\TermTranslationController.
+ */
+
+namespace Drupal\taxonomy;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\translation_entity\EntityTranslationController;
+
+/**
+ * Defines the translation controller class for terms.
+ */
+class TermTranslationController extends EntityTranslationController {
+
+  /**
+   * Overrides EntityTranslationController::entityFormAlter().
+   */
+  public function entityFormAlter(array &$form, array &$form_state, EntityInterface $entity) {
+    parent::entityFormAlter($form, $form_state, $entity);
+    $form['actions']['submit']['#submit'][] = array($this, 'entityFormSave');
+  }
+
+  /**
+   * Submit handler for the save action.
+   */
+  function entityFormSave(array $form, array &$form_state) {
+    if ($this->getSourceLangcode($form_state)) {
+      $entity = translation_entity_form_controller($form_state)->getEntity($form_state);
+      // We need a redirect here, otherwise we would get an access denied page
+      // since the curret URL would be preserved and we would try to add a
+      // translation for a language that already has a translation.
+      $form_state['redirect'] = $this->getEditPath($entity);
+    }
+  }
+}
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyFormController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyFormController.php
index bdeea4c..c80d598 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyFormController.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyFormController.php
@@ -61,6 +61,10 @@ class VocabularyFormController extends EntityFormController {
       $form['vid'] = array('#type' => 'value', '#value' => $vocabulary->vid);
     }
 
+    if (module_exists('translation_entity')) {
+      $form += translation_entity_enable_widget($form_state, 'taxonomy_term', $vocabulary->machine_name);
+    }
+
     return parent::form($form, $form_state, $vocabulary);
   }
 
@@ -72,6 +76,9 @@ class VocabularyFormController extends EntityFormController {
     if (empty($form_state['confirm_delete'])) {
       $actions = parent::actions($form, $form_state);
       array_unshift($actions['delete']['#submit'], array($this, 'submit'));
+      if (module_exists('translation_entity')) {
+        array_unshift($actions['submit']['#submit'], 'translation_entity_enable_widget_submit');
+      }
       return $actions;
     }
     else {
@@ -139,6 +146,11 @@ class VocabularyFormController extends EntityFormController {
         break;
     }
 
+    $variable_name = 'translation_entity_enabled_taxonomy_term_' . $form_state['values']['machine_name'];
+    if (isset($form_state['values'][$variable_name])) {
+      variable_set($variable_name, $form_state['values'][$variable_name]);
+    }
+
     $form_state['values']['vid'] = $vocabulary->vid;
     $form_state['vid'] = $vocabulary->vid;
   }
diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
index d9aa9db..b57f10d 100644
--- a/core/modules/taxonomy/taxonomy.module
+++ b/core/modules/taxonomy/taxonomy.module
@@ -139,6 +139,7 @@ function taxonomy_entity_info() {
       ),
     ),
   );
+
   foreach (taxonomy_vocabulary_get_names() as $machine_name => $vocabulary) {
     $return['taxonomy_term']['bundles'][$machine_name] = array(
       'label' => $vocabulary->name,
@@ -150,6 +151,14 @@ function taxonomy_entity_info() {
       ),
     );
   }
+
+  if (module_exists('translation_entity')) {
+    $return['taxonomy_term']['menu base path'] = 'taxonomy/term/%taxonomy_term';
+    $return['taxonomy_term']['translation']['translation_entity'] = array(
+      'class' => 'Drupal\taxonomy\TermTranslationController',
+    );
+  }
+
   $return['taxonomy_vocabulary'] = array(
     'label' => t('Taxonomy vocabulary'),
     'entity class' => 'Drupal\taxonomy\Vocabulary',
diff --git a/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationController.php b/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationController.php
new file mode 100644
index 0000000..226d80c
--- /dev/null
+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationController.php
@@ -0,0 +1,314 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\translation_entity\EntityTranslationController.
+ */
+
+namespace Drupal\translation_entity;
+
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Base class for entity translation controllers.
+ */
+class EntityTranslationController implements EntityTranslationControllerInterface {
+
+  protected $entityType;
+  protected $entityInfo;
+
+  /**
+   * Initializes an instance of the entity translation controller.
+   *
+   * @param string $entity_type
+   *   The type of the entity being translated.
+   */
+  public function __construct($entity_type) {
+    $this->entityType = $entity_type;
+    $this->entityInfo = entity_get_info($entity_type);
+  }
+
+  /**
+   * Implements EntityTranslationControllerInterface::removeTranslation().
+   */
+  public function removeTranslation(EntityInterface $entity, $langcode) {
+    $translations = $entity->translations();
+    // @todo Handle properties.
+    // Remove field translations.
+    foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) {
+      $field_name = $instance['field_name'];
+      $field = field_info_field($field_name);
+      if ($field['translatable']) {
+        $entity->{$field_name}[$langcode] = array();
+      }
+    }
+  }
+
+  /**
+   * Implements EntityTranslationControllerInterface::retranslate().
+   */
+  public function retranslate(EntityInterface $entity, $langcode = NULL) {
+    $updated_langcode = !empty($langcode) ? $langcode : $entity->language()->langcode;
+    $translations = $entity->translations();
+    foreach ($translations as $langcode => $language) {
+      $entity->retranslate[$langcode] = $langcode != $updated_langcode;
+    }
+  }
+
+  /**
+   * Implements EntityTranslationControllerInterface::getBasePath().
+   */
+  public function getBasePath(EntityInterface $entity) {
+    return $this->getPathInstance($this->entityInfo['menu base path'], $entity->id());
+  }
+
+  /**
+   * Implements EntityTranslationControllerInterface::getEditPath().
+   */
+  public function getEditPath(EntityInterface $entity) {
+    return isset($this->entityInfo['menu edit path']) ? $this->getPathInstance($this->entityInfo['menu edit path'], $entity->id()) : FALSE;
+  }
+
+  /**
+   * Implements EntityTranslationControllerInterface::getViewPath().
+   */
+  public function getViewPath(EntityInterface $entity) {
+    return isset($this->entityInfo['menu view path']) ? $this->getPathInstance($this->entityInfo['menu view path'], $entity->id()) : FALSE;
+  }
+
+  /**
+   * Implements EntityTranslationControllerInterface::getAccess().
+   */
+  public function getAccess(EntityInterface $entity, $op) {
+    return TRUE;
+  }
+
+  /**
+   * Implements EntityTranslationControllerInterface::getTranslationAccess().
+   */
+  public function getTranslationAccess(EntityInterface $entity, $langcode) {
+    $entity_type = $entity->entityType();
+    return (user_access('translate any entity') || user_access("translate $entity_type entities")) && ($langcode != $entity->language()->langcode || user_access('edit original values'));
+  }
+
+  /**
+   * Implements EntityTranslationControllerInterface::getSourceLanguage().
+   */
+  public function getSourceLangcode(array $form_state) {
+    return isset($form_state['translation_entity']['source']) ? $form_state['translation_entity']['source']->langcode : FALSE;
+  }
+
+  /**
+   * Implements EntityTranslationControllerInterface::entityFormAlter().
+   */
+  public function entityFormAlter(array &$form, array &$form_state, EntityInterface $entity) {
+    $form_controller = translation_entity_form_controller($form_state);
+    $form_langcode = $form_controller->getFormLangcode($form_state);
+    $entity_langcode = $entity->language()->langcode;
+    $source_langcode = $this->getSourceLangcode($form_state);
+
+    $new_translation = !empty($source_langcode);
+    $translations = $entity->translations();
+    if ($new_translation) {
+      // Make sure a new translation does not appear as existing yet.
+      unset($translations[$form_langcode]);
+    }
+    $is_translation = !$form_controller->isDefaultFormLangcode($form_state);
+    $no_translations = count($translations) < 2;
+
+    // Adjust page title to specify the current language being edited, if we
+    // have at least one translation.
+    $languages = language_list();
+    if (isset($languages[$form_langcode]) && (!$no_translations || $new_translation)) {
+      drupal_set_title($this->entityFormTitle($entity) . ' [' . $languages[$form_langcode]->name . ']', PASS_THROUGH);
+    }
+
+    // Display source language selector only if we are creating a new
+    // translation and there are at least two translations available.
+    if (!$no_translations && $new_translation) {
+      $form['source_langcode'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('Source language'),
+        '#collapsible' => TRUE,
+        '#collapsed' => TRUE,
+        '#tree' => TRUE,
+        '#weight' => -100,
+        'source' => array(
+          '#type' => 'select',
+          '#default_value' => $source_langcode,
+          '#options' => array(),
+        ),
+        'submit' => array(
+          '#type' => 'submit',
+          '#value' => t('Change'),
+          '#submit' => array(array($this, 'entityFormSourceChange')),
+        ),
+      );
+      foreach (language_list(LANGUAGE_CONFIGURABLE) as $language) {
+        if (isset($translations[$language->langcode])) {
+          $form['source_langcode']['source']['#options'][$language->langcode] = $language->name;
+        }
+      }
+    }
+
+    // Disable languages for existing translations, so it is not possible to
+    // switch this node to some language which is already in the translation
+    // set.
+    $language_widget = isset($form['langcode']) && $form['langcode']['#type'] == 'language_select';
+    if ($language_widget && count($translations) > 1) {
+      $form['langcode']['#options'] = array();
+      foreach (language_list(LANGUAGE_CONFIGURABLE) as $language) {
+        if (empty($translations[$language->langcode]) || $language->langcode == $entity_langcode) {
+          $form['langcode']['#options'][$language->langcode] = $language->name;
+        }
+      }
+    }
+
+    if ($is_translation) {
+      if ($language_widget) {
+        $form['langcode']['#disabled'] = TRUE;
+      }
+
+      // Replace the delete button with the delete translation one.
+      if (!$new_translation) {
+        $weight = 100;
+        foreach (array('delete', 'submit') as $key) {
+          if (isset($form['actions'][$key]['weight'])) {
+            $weight = $form['actions'][$key]['weight'];
+            break;
+          }
+        }
+        $form['actions']['delete_translation'] = array(
+          '#type' => 'submit',
+          '#value' => t('Delete translation'),
+          '#weight' => $weight,
+          '#submit' => array(array($this, 'entityFormDeleteTranslation')),
+        );
+      }
+
+      // Always remove the delete button on translation forms.
+      unset($form['actions']['delete']);
+    }
+
+    // We need to display the translation tab only when there is at least one
+    // translation available or a new one is about to be created.
+    if ($new_translation || count($translations) > 1) {
+      $form['translation'] = array(
+        '#type' => 'fieldset',
+        '#title' => t('Translation'),
+        '#collapsible' => TRUE,
+        '#collapsed' => TRUE,
+        '#tree' => TRUE,
+        '#weight' => 10,
+        '#access' => $this->getTranslationAccess($entity, $form_langcode),
+      );
+
+      $translate = !$new_translation && $entity->retranslate[$form_langcode];
+      if (!$translate) {
+        $form['translation']['retranslate'] = array(
+          '#type' => 'checkbox',
+          '#title' => t('Flag translations as outdated'),
+          '#default_value' => FALSE,
+          '#description' => t('If you made a significant change, which means translations should be updated, you can flag all translations of this post as outdated. This will not change any other property of those posts, like whether they are published or not.'),
+        );
+      }
+      else {
+        $form['translation']['translate'] = array(
+          '#type' => 'checkbox',
+          '#title' => t('This translation needs to be updated'),
+          '#default_value' => $translate,
+          '#description' => t('When this option is checked, this translation needs to be updated because the source post has changed. Uncheck when the translation is up to date again.'),
+        );
+      }
+    }
+
+    // Process the submitted values before they are stored.
+    $form['#entity_builders'][] = array($this, 'entityFormEntityBuild');
+
+    // Handle entity deletion.
+    if (isset($form['actions']['delete'])) {
+      $form['actions']['delete']['#submit'][] = array($this, 'entityFormDelete');
+    }
+  }
+
+  /**
+   * Entity builder method.
+   */
+  public function entityFormEntityBuild($entity_type, $entity, $form, &$form_state) {
+    $form_controller = translation_entity_form_controller($form_state);
+    $form_langcode = $form_controller->getFormLangcode($form_state);
+    $source_langcode = $this->getSourceLangcode($form_state);
+
+    if ($source_langcode) {
+      // @todo Use the entity setter when all entities support multilingual
+      // properties.
+      $entity->source[$form_langcode] = $source_langcode;
+    }
+
+    // Ensure every key has at least a default value. Subclasses may provide
+    // entity-specific values to alter them.
+    $values = isset($form_state['values']['translation']) ? $form_state['values']['translation'] : array();
+    $entity->retranslate[$form_langcode] = isset($values['translate']) && $values['translate'];
+
+    if (!empty($values['retranslate'])) {
+      $this->retranslate($entity, $form_langcode);
+    }
+  }
+
+  /**
+   * Submit handler for the source language change.
+   */
+  public function entityFormSourceChange($form, &$form_state) {
+    $form_controller = translation_entity_form_controller($form_state);
+    $entity = $form_controller->getEntity($form_state);
+    $langcode = $form_state['values']['source_langcode']['source'];
+    $path = $this->getBasePath($entity) . '/translations/add/' . $langcode;
+    $form_state['redirect'] = array('path' => $path);
+    $languages = language_list();
+    drupal_set_message(t('Source language set to: %language', array('%language' => t($languages[$langcode]->name))));
+  }
+
+  /**
+   * Submit handler for the entity deletion.
+   */
+  function entityFormDelete($form, &$form_state) {
+    $form_controller = translation_entity_form_controller($form_state);
+    $entity = $form_controller->getEntity($form_state);
+    if (count($entity->translations()) > 1) {
+      $info = $entity->entityInfo();
+      drupal_set_message(t('This will delete all the @entity_type translations.', array('@entity_type' => drupal_strtolower($info['label']))), 'warning');
+    }
+  }
+
+  /**
+   * Submit handler for the entity translation deletion.
+   */
+  function entityFormDeleteTranslation($form, &$form_state) {
+    $form_controller = translation_entity_form_controller($form_state);
+    $entity = $form_controller->getEntity($form_state);
+    $base_path = $this->getBasePath($entity);
+    $form_langcode = $form_controller->getFormLangcode($form_state);
+    $form_state['redirect'] = $base_path . '/translations/delete/' . $form_langcode;
+  }
+
+  /**
+   * Returns the title to be used for the entity form page.
+   */
+  protected function entityFormTitle(EntityInterface $entity) {
+    return $entity->label();
+  }
+
+  /**
+   * Returns an instance of the given path.
+   *
+   * @param $path
+   *   An internal path containing the entity id wildcard.
+   *
+   * @return
+   *   The instantiated path.
+   */
+  protected function getPathInstance($path, $entity_id) {
+    $wildcard = $this->entityInfo['menu path wildcard'];
+    return str_replace($wildcard, $entity_id, $path);
+  }
+}
diff --git a/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationControllerInterface.php b/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationControllerInterface.php
new file mode 100644
index 0000000..6c7ebc9
--- /dev/null
+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationControllerInterface.php
@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\translation_entity\EntityTranslationControllerInterface.
+ */
+
+namespace Drupal\translation_entity;
+
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Interface for providing entity translation.
+ *
+ * Defines a set of methods to allow any entity to be processed by the entity
+ * translation UI.
+ */
+interface EntityTranslationControllerInterface {
+
+  /**
+   * Returns the base path for the current entity.
+   *
+   * @param EntityInterface $entity
+   *   The entity to the path should refer to.
+   */
+  public function getBasePath(EntityInterface $entity);
+
+  /**
+   * Returns the path of the entity edit form.
+   *
+   * @param EntityInterface $entity
+   *   The entity to the path should refer to.
+   */
+  public function getEditPath(EntityInterface $entity);
+
+  /**
+   * Returns the path of the entity view page.
+   *
+   * @param EntityInterface $entity
+   *   The entity to the path should refer to.
+   */
+  public function getViewPath(EntityInterface $entity);
+
+  /**
+   * Checks if the user can perform the given operation on the wrapped entity.
+   *
+   * @param Drupal\Core\Entity\EntityInterface $entity
+   *   The entity access should be checked for.
+   * @param $op
+   *   The operation to be performed.
+   *
+   * @return
+   *   TRUE if the user is allowed to perform the given operation, FALSE
+   *   otherwise.
+   */
+  public function getAccess(EntityInterface $entity, $op);
+
+  /**
+   * Checks if a user is allowed to edit the given translation.
+   */
+  public function getTranslationAccess(EntityInterface $entity, $langcode);
+
+  /**
+   * Retrieves the source language for the translation being created.
+   *
+   * @param array $form_state
+   *   The form state array.
+   */
+  public function getSourceLangcode(array $form_state);
+
+  /**
+   * Remove the translation values from the given entity.
+   *
+   * @param Drupal\Core\Entity\EntityInterface $entity
+   *   The entity whose values should be removed.
+   * @param $langcode
+   *   The language code identifying the translation being deleted.
+   */
+  public function removeTranslation(EntityInterface $entity, $langcode);
+
+  /**
+   * Marks translations as outdated.
+   *
+   * @param Drupal\Core\Entity\EntityInterface $entity
+   *   The entity being translated.
+   * @param string $langcode
+   *   (optional) The language code of the updated language: all the other
+   *   translations will be marked as outdated. Defaults to the entity language.
+   */
+  public function retranslate(EntityInterface $entity, $langcode = NULL);
+
+  /**
+   * Performs the needed alterations to the entity form.
+   */
+  public function entityFormAlter(array &$form, array &$form_state, EntityInterface $entity);
+}
diff --git a/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationUITest.php b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationUITest.php
new file mode 100644
index 0000000..9000503
--- /dev/null
+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationUITest.php
@@ -0,0 +1,186 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\entity\Tests\EntityTranslationUITest.
+ */
+
+namespace Drupal\translation_entity\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests the Entity Translation UI.
+ */
+class EntityTranslationUITest extends WebTestBase {
+
+  /**
+   * The enabled languages.
+   *
+   * @var array
+   */
+  protected $langcodes;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('language', 'translation_entity', 'entity_test');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Translation UI',
+      'description' => 'Tests the basic entity translation UI.',
+      'group' => 'Entity Translation UI',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+
+    $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
+    $this->drupalLogin($admin_user);
+
+    $languages = array('it' => 'Italian', 'fr' => 'French');
+    $this->langcodes = array_keys($languages);
+    array_unshift($this->langcodes, language_default()->langcode);
+
+    // Add predefined language.
+    foreach ($languages as $langcode => $name) {
+      $edit = array('predefined_langcode' => $langcode);
+      $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
+      $this->assertText($name, 'Language added successfully.');
+    }
+
+    // @todo This should not be need.
+    // Enable URL language detection for content language.
+    $edit = array('language_content[enabled][language-url]' => TRUE);
+    $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings'));
+
+    // Enable translation for the entity_test entity type and ensure the change
+    // is picked up.
+    variable_set('translation_entity_enabled_entity_test_entity_test', TRUE);
+    drupal_static_reset();
+    entity_info_cache_clear();
+    menu_router_rebuild();
+
+    $translator = $this->drupalCreateUser(array('administer entity_test content', 'translate entity_test entities', 'edit original values'));
+    $this->drupalLogin($translator);
+  }
+
+  /**
+   * Tests the basic translation UI.
+   */
+  function testTranslationUI() {
+    // Make the test field translatable.
+    $field = field_info_field('field_test_text');
+    $field['translatable'] = TRUE;
+    field_update_field($field);
+
+    // Create a new test entity with original values in the default language. 
+    $default_langcode = language_default()->langcode;
+
+    $values[$default_langcode] = array(
+      'langcode' => $default_langcode,
+      'name' => $this->randomName(8),
+      'uid' => mt_rand(0, 128),
+      'field_test_text' => $this->randomName(16),
+    );
+
+    $this->drupalPost('entity-test/add', $this->getEditValues($values, $default_langcode, TRUE), t('Save'));
+    $entity = current(entity_load_multiple_by_properties('entity_test', array('name' => $values[$default_langcode]['name'])));
+    $this->assertTrue($entity, t('Entity found in the database.'));
+
+    foreach ($values[$default_langcode] as $property => $value) {
+      if ($property != 'langcode') {
+        $stored_value = $entity->get($property, $default_langcode);
+        $stored_value = is_array($stored_value) ? $stored_value[0]['value'] : $stored_value;
+        $message = format_string('@property correctly stored in the default language.', array('@property' => $property));
+        $this->assertEqual($stored_value, $value, $message);
+      }
+    }
+
+    // Add an entity translation.
+    $langcode = 'it';
+    $values[$langcode] = array(
+      'name' => $this->randomName(8),
+      'uid' => mt_rand(0, 128),
+      'field_test_text' => $this->randomName(16),
+    );
+
+    $this->drupalPost($langcode . '/entity-test/manage/' . $entity->id() . '/translations/add/' . $default_langcode, $this->getEditValues($values, $langcode), t('Save'));
+    $this->assertFieldByXPath('//select[@id="edit-langcode" and @disabled]', $default_langcode, 'Language selector correclty disabled on translations.');
+    $entity = entity_test_load($entity->id(), TRUE);
+
+    // Switch the source language.
+    $langcode = 'fr';
+    $source_langcode = 'it';
+    $edit = array(
+      'source_langcode[source]' => $source_langcode,
+      // @todo Remove these once we support source values for properties. For
+      // now we need them as they are required to submit the form.
+      'name' => $this->randomName(8),
+      'uid' => mt_rand(0, 128),
+    );
+    $this->drupalPost($langcode . '/entity-test/manage/' . $entity->id() . '/translations/add/' . $default_langcode, $edit, t('Change'));
+    $this->assertFieldByXPath('//input[@name="field_test_text[fr][0][value]"]', $values[$source_langcode]['field_test_text'], 'Source language correctly switched.');
+
+    // Add another translation and mark the other ones as outdated.
+    $values[$langcode] = array(
+      'name' => $this->randomName(8),
+      'uid' => mt_rand(0, 128),
+      'field_test_text' => $this->randomName(16),
+    );
+    $edit = $this->getEditValues($values, $langcode) + array('translation[retranslate]' => TRUE);
+    $this->drupalPost(NULL, $edit, t('Save'));
+    $entity = entity_test_load($entity->id(), TRUE);
+
+    // Check that the entered values have been correctly stored.
+    foreach ($values as $langcode => $property_values) {
+      foreach ($property_values as $property => $value) {
+        if ($property != 'langcode') {
+          $stored_value = $entity->get($property, $langcode);
+          $stored_value = is_array($stored_value) ? $stored_value[0]['value'] : $stored_value;
+          $message = format_string('%property correctly stored with language %language.', array('%property' => $property, '%language' => $langcode));
+          $this->assertEqual($stored_value, $value, $message);
+        }
+      }
+    }
+
+    // Check that every translation has the correct "outdated" status.
+    foreach ($this->langcodes as $enabled_langcode) {
+      $prefix = $enabled_langcode != $default_langcode ? $enabled_langcode . '/' : '';
+      $this->drupalGet($prefix . 'entity-test/manage/' . $entity->id() . '/edit');
+      if ($enabled_langcode == $langcode) {
+        $this->assertFieldByXPath('//input[@name="translation[retranslate]"]', FALSE, 'The retranslate flag is not checked by default.');
+      }
+      else {
+        $this->assertFieldByXPath('//input[@name="translation[translate]"]', TRUE, 'The translate flag is checked by default.');
+        $edit = array('translation[translate]' => FALSE);
+        $this->drupalPost(NULL, $edit, t('Save'));
+        $this->assertFieldByXPath('//input[@name="translation[retranslate]"]', FALSE, 'The retranslate flag is now shown.');
+        $entity = entity_test_load($entity->id(), TRUE);
+        $this->assertFalse($entity->retranslate[$enabled_langcode], 'The "outdated" status has been correctly stored.');
+      }
+    }
+
+    // Confirm and delete a translation.
+    $this->drupalPost(NULL, array(), t('Delete translation'));
+    $this->drupalPost(NULL, array(), t('Delete'));
+    $entity = entity_test_load($entity->id(), TRUE);
+    $translations = $entity->translations();
+    $this->assertTrue(count($translations) == 2 && empty($translations[$enabled_langcode]), 'Translation successfully deleted.');
+  }
+
+  /**
+   * Loads a test entity by name always resetting the storage controller cache.
+   */
+  protected function getEditValues($values, $langcode, $new = FALSE) {
+    $edit = $values[$langcode];
+    $langcode = $new ? LANGUAGE_NOT_SPECIFIED : $langcode;
+    $edit["field_test_text[$langcode][0][value]"] = $edit['field_test_text'];
+    unset($edit['field_test_text']);
+    return $edit;
+  }
+}
diff --git a/core/modules/translation_entity/tests/modules/translation_entity_test/translation_entity_test.info b/core/modules/translation_entity/tests/modules/translation_entity_test/translation_entity_test.info
new file mode 100644
index 0000000..25026b2
--- /dev/null
+++ b/core/modules/translation_entity/tests/modules/translation_entity_test/translation_entity_test.info
@@ -0,0 +1,8 @@
+name = Entity Translation test module
+description = Implements hooks to test the Entity Translaion functionality.
+package = Testing
+version = VERSION
+core = 8.x
+dependencies[] = entity_test
+dependencies[] = translation_entity
+hidden = TRUE
diff --git a/core/modules/translation_entity/tests/modules/translation_entity_test/translation_entity_test.module b/core/modules/translation_entity/tests/modules/translation_entity_test/translation_entity_test.module
new file mode 100644
index 0000000..240954b
--- /dev/null
+++ b/core/modules/translation_entity/tests/modules/translation_entity_test/translation_entity_test.module
@@ -0,0 +1,6 @@
+<?php
+
+/**
+ * @file
+ * Test module for the Entity Translation UI.
+ */
diff --git a/core/modules/translation_entity/translation_entity.admin.inc b/core/modules/translation_entity/translation_entity.admin.inc
new file mode 100644
index 0000000..b61b57a
--- /dev/null
+++ b/core/modules/translation_entity/translation_entity.admin.inc
@@ -0,0 +1,222 @@
+<?php
+
+/**
+ * @file
+ * The entity translation administration forms.
+ */
+
+use Drupal\Core\Entity\EntityFieldQuery;
+
+/*
+ * Confirm form for changing field translatability.
+*/
+function translation_entity_translatable_form($form, &$form_state, $field_name) {
+  $field = field_info_field($field_name);
+  $t_args = array('%name' => $field_name);
+
+  $warning = t('By submitting this form you will trigger a batch operation.');
+  if ($field['translatable']) {
+    $title = t('Are you sure you want to disable translation for the %name field?', $t_args);
+    $warning .= "<br>" . t("<strong>All the existing translations of this field will be deleted.</strong><br>This action cannot be undone.");
+  }
+  else {
+    $title = t('Are you sure you want to enable translation for the %name field?', $t_args);
+  }
+
+  // We need to keep some information for later processing.
+  $form_state['field'] = $field;
+
+  // Store the 'translatable' status on the client side to prevent outdated form
+  // submits from toggling translatability.
+  $form['translatable'] = array(
+    '#type' => 'hidden',
+    '#default_value' => $field['translatable'],
+  );
+
+  return confirm_form($form, $title, '', $warning);
+}
+
+/**
+ * Submit handler for the field settings form.
+ *
+ * This submit handler maintains consistency between the translatability of an
+ * entity and the language under which the field data is stored. When a field is
+ * marked as translatable, all the data in $entity->{field_name}[LANGUAGE_NONE]
+ * is moved to $entity->{field_name}[$entity_language]. When a field is marked
+ * as untranslatable the opposite process occurs. Note that marking a field as
+ * untranslatable will cause all of its translations to be permanently removed,
+ * with the exception of the one corresponding to the entity language.
+ */
+function translation_entity_translatable_form_submit($form, $form_state) {
+  // This is the current state that we want to reverse.
+  $translatable = $form_state['values']['translatable'];
+  $field_name = $form_state['field']['field_name'];
+  $field = field_info_field($field_name);
+
+  if ($field['translatable'] !== $translatable) {
+    // Field translatability has changed since form creation, abort.
+    $t_args = array('%field_name' => $field_name, '!translatable' => $translatable ? t('untranslatable') : t('translatable'));
+    drupal_set_message(t('The field %field_name is already !translatable. No change was performed.', $t_args), 'warning');
+    return;
+  }
+
+  // If a field is untranslatable, it can have no data except under
+  // LANGUAGE_NOT_SPECIFIED. Thus we need a field to be translatable before we convert
+  // data to the entity language. Conversely we need to switch data back to
+  // LANGUAGE_NOT_SPECIFIED before making a field untranslatable lest we lose
+  // information.
+  $operations = array(
+    array('translation_entity_translatable_batch', array(!$translatable, $field_name)),
+    array('translation_entity_translatable_switch', array(!$translatable, $field_name)),
+  );
+  $operations = $translatable ? $operations : array_reverse($operations);
+
+  $t_args = array('%field' => $field_name);
+  $title = !$translatable ? t('Enabling translation for the %field field', $t_args) : t('Disabling translation for the %field field', $t_args);
+
+  $batch = array(
+    'title' => $title,
+    'operations' => $operations,
+    'finished' => 'translation_entity_translatable_batch_done',
+    'file' => drupal_get_path('module', 'translation_entity') . '/translation_entity.admin.inc',
+  );
+
+  batch_set($batch);
+}
+
+/*
+ * Toggle translatability of the given field.
+ *
+ * This is called from a batch operation, but should only run once per field.
+ */
+function translation_entity_translatable_switch($translatable, $field_name) {
+  $field = field_info_field($field_name);
+
+  if ($field['translatable'] === $translatable) {
+    return;
+  }
+
+  $field['translatable'] = $translatable;
+  field_update_field($field);
+}
+
+
+/**
+ * Batch operation. Convert field data to or from LANGUAGE_NOT_SPECIFIED.
+ */
+function translation_entity_translatable_batch($translatable, $field_name, &$context) {
+  if (empty($context['sandbox'])) {
+    $context['sandbox']['progress'] = 0;
+
+    // How many entities will need processing?
+    $query = new EntityFieldQuery();
+    $count = $query
+      ->fieldCondition($field_name)
+      ->count()
+      ->execute();
+
+    if (intval($count) === 0) {
+      // Nothing to do.
+      $context['finished'] = 1;
+      return;
+    }
+    $context['sandbox']['max'] = $count;
+  }
+
+  // Number of entities to be processed for each step.
+  $limit = variable_get('translation_entity_translatable_batch_limit', 10);
+
+  $offset = $context['sandbox']['progress'];
+  $query = new EntityFieldQuery();
+  $result = $query
+    ->fieldCondition($field_name)
+    ->entityOrderBy('entity_id')
+    ->range($offset, $limit)
+    ->execute();
+
+  foreach ($result as $entity_type => $entities) {
+    foreach (entity_load_multiple($entity_type, array_keys($entities)) as $id => $entity) {
+      $context['sandbox']['progress']++;
+      $langcode = $entity->language()->langcode;
+
+      // Skip process for language neutral entities.
+      if ($langcode == LANGUAGE_NOT_SPECIFIED) {
+        continue;
+      }
+
+      // We need a two-steps approach while updating field translations: given
+      // that field-specific update functions might rely on the stored values to
+      // perform their processing, see for instance file_field_update(), first
+      // we need to store the new translations and only after we can remove the
+      // old ones. Otherwise we might have data loss, since the removal of the
+      // old translations might occur before the new ones are stored.
+      if ($translatable && isset($entity->{$field_name}[LANGUAGE_NOT_SPECIFIED])) {
+        // If the field is being switched to translatable and has data for
+        // LANGUAGE_NONE then we need to move the data to the right language.
+        $entity->{$field_name}[$langcode] = $entity->{$field_name}[LANGUAGE_NOT_SPECIFIED];
+        // Store the original value.
+        _translation_entity_update_field($entity_type, $entity, $field_name);
+        $entity->{$field_name}[LANGUAGE_NOT_SPECIFIED] = array();
+        // Remove the language neutral value.
+        _translation_entity_update_field($entity_type, $entity, $field_name);
+      }
+      elseif (!$translatable && isset($entity->{$field_name}[$langcode])) {
+        // The field has been marked untranslatable and has data in the entity
+        // language: we need to move it to LANGUAGE_NONE and drop the other
+        // translations.
+        $entity->{$field_name}[LANGUAGE_NOT_SPECIFIED] = $entity->{$field_name}[$langcode];
+        // Store the original value.
+        _translation_entity_update_field($entity_type, $entity, $field_name);
+        // Remove translations.
+        foreach ($entity->{$field_name} as $langcode => $items) {
+          if ($langcode != LANGUAGE_NOT_SPECIFIED) {
+            $entity->{$field_name}[$langcode] = array();
+          }
+        }
+        _translation_entity_update_field($entity_type, $entity, $field_name);
+      }
+      else {
+        // No need to save unchanged entities.
+        continue;
+      }
+    }
+  }
+
+  $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+}
+
+/**
+ * Stores the given field translations.
+ */
+function _translation_entity_update_field($entity_type, $entity, $field_name) {
+  $empty = 0;
+  $field = field_info_field($field_name);
+
+  // Ensure that we are trying to store only valid data.
+  foreach ($entity->{$field_name} as $langcode => $items) {
+    $entity->{$field_name}[$langcode] = _field_filter_items($field, $entity->{$field_name}[$langcode]);
+    $empty += empty($entity->{$field_name}[$langcode]);
+  }
+
+  // Save the field value only if there is at least one item available,
+  // otherwise any stored empty field value would be deleted. If this happens
+  // the range queries would be messed up.
+  if ($empty < count($entity->{$field_name})) {
+    field_attach_presave($entity_type, $entity);
+    field_attach_update($entity_type, $entity);
+  }
+}
+
+/**
+ * Check the exit status of the batch operation.
+ */
+function translation_entity_translatable_batch_done($success, $results, $operations) {
+  if ($success) {
+    drupal_set_message(t("Data successfully processed."));
+  }
+  else {
+    // @todo: Do something about this case.
+    drupal_set_message(t("Something went wrong while processing data. Some nodes may appear to have lost fields."));
+  }
+}
+
diff --git a/core/modules/translation_entity/translation_entity.info b/core/modules/translation_entity/translation_entity.info
new file mode 100644
index 0000000..4a28def
--- /dev/null
+++ b/core/modules/translation_entity/translation_entity.info
@@ -0,0 +1,6 @@
+name = Entity Translation
+description =  Allows entities to be translated into different languages.
+dependencies[] = language
+package = Core
+version = VERSION
+core = 8.x
diff --git a/core/modules/translation_entity/translation_entity.install b/core/modules/translation_entity/translation_entity.install
new file mode 100644
index 0000000..c86a5f5
--- /dev/null
+++ b/core/modules/translation_entity/translation_entity.install
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Installation functions for Entity Translation module.
+ */
+
+/**
+ * Implements hook_schema().
+ */
+function translation_entity_schema() {
+  $schema['translation_entity'] = array(
+    'description' => 'Table to track entity translations',
+    'fields' => array(
+      'entity_type' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The entity type this translation relates to',
+      ),
+      'entity_id' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => 'The entity id this translation relates to',
+      ),
+      'langcode' => array(
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The target language for this translation.',
+      ),
+      'source' => array(
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The source language from which this translation was created.',
+      ),
+      'translate' => array(
+        'description' => 'A boolean indicating whether this translation needs to be updated.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'primary key' => array('entity_type', 'entity_id', 'langcode'),
+  );
+  return $schema;
+}
diff --git a/core/modules/translation_entity/translation_entity.module b/core/modules/translation_entity/translation_entity.module
new file mode 100644
index 0000000..13ff511
--- /dev/null
+++ b/core/modules/translation_entity/translation_entity.module
@@ -0,0 +1,486 @@
+<?php
+
+/**
+ * @file
+ * Allows entities to be translated into different languages.
+ */
+
+use Drupal\Core\Language\Language;
+use Drupal\Core\Entity\EntityFormControllerInterface;
+use Drupal\Core\Entity\EntityInterface;
+
+
+/**
+ * Implements hook_language_type_info_alter().
+ */
+function translation_entity_language_types_info_alter(array &$language_types) {
+  unset($language_types[LANGUAGE_TYPE_CONTENT]['fixed']);
+}
+
+/**
+ * Implements hook_entity_info_alter().
+ */
+function translation_entity_entity_info_alter(&$entity_info) {
+  $edit_form_info = array();
+
+  // Provide defaults for translation info.
+  foreach ($entity_info as $entity_type => $info) {
+    if (!isset($entity_info[$entity_type]['translation']['translation_entity'])) {
+      $entity_info[$entity_type]['translation']['translation_entity'] = array();
+    }
+
+    // Every fieldable entity type must have a translation controller class, no
+    // matter if it is enabled for translation or not. As a matter of fact we
+    // might need it to correctly switch field translatability when a field is
+    // shared accross different entities.
+    $entity_info[$entity_type]['translation']['translation_entity'] += array('class' => 'Drupal\translation_entity\EntityTranslationController');
+
+    if (translation_entity_enabled($entity_type, NULL, TRUE)) {
+      // If no menu base path is provided we default to the common "node/%node"
+      // pattern.
+      if (!isset($entity_info[$entity_type]['menu base path'])) {
+        $path = "$entity_type/%$entity_type";
+        $entity_info[$entity_type]['menu base path'] = $path;
+      }
+
+      $path = $entity_info[$entity_type]['menu base path'];
+
+      $entity_info[$entity_type] += array(
+        'menu view path' => $path,
+        'menu edit path' => "$path/edit",
+        'menu path wildcard' => "%$entity_type",
+      );
+
+      $entity_position = count(explode('/', $path)) - 1;
+      $entity_info[$entity_type]['translation']['translation_entity'] += array(
+        'access callback' => 'translation_entity_tab_access',
+        'access arguments' => array($entity_position),
+        'theme callback' => 'variable_get',
+        'theme arguments' => array('admin_theme'),
+      );
+    }
+  }
+}
+
+/**
+ * Implements hook_menu().
+ */
+function translation_entity_menu() {
+  $items = array();
+
+  // Create tabs for all possible entity types.
+  foreach (entity_get_info() as $entity_type => $info) {
+    // Provide the translation UI only for enabled types.
+    if (translation_entity_enabled($entity_type)) {
+      $path = $info['menu base path'];
+      $entity_position = count(explode('/', $path)) - 1;
+      $keys = array_flip(array('theme callback', 'theme arguments', 'access callback', 'access arguments', 'load arguments'));
+      $item = array_intersect_key($info['translation']['translation_entity'], $keys) + array('file' => 'translation_entity.pages.inc');
+
+      $items["$path/translations"] = array(
+        'title' => 'Translations',
+        'page callback' => 'translation_entity_overview',
+        'page arguments' => array($entity_position),
+        'type' => MENU_LOCAL_TASK,
+        'weight' => 2,
+      ) + $item;
+
+      // Add translation callback.
+      // @todo Add the access callback instead of replacing it as soon as the
+      // routing system supports multiple callbacks.
+      $add_path = "$path/translations/add/%language";
+      $language_position = $entity_position + 3;
+      $args = array($entity_position, $language_position);
+      $items[$add_path] = array(
+        'title' => 'Add',
+        'page callback' => 'translation_entity_add_page',
+        'page arguments' => $args,
+        'access callback' => 'translation_entity_add_access',
+        'access arguments' => $args,
+      ) + $item;
+
+      // Delete translation callback.
+      $items["$path/translations/delete/%language"] = array(
+        'title' => 'Delete',
+        'page callback' => 'drupal_get_form',
+        'page arguments' => array('translation_entity_delete_confirm', $entity_position, $language_position),
+      ) + $item;
+    }
+  }
+
+  $items['admin/config/regional/translation_entity/translatable/%'] = array(
+    'title' => 'Confirm change in translatability.',
+    'description' => 'Confirm page for changing field translatability.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('translation_entity_translatable_form', 5),
+    'access arguments' => array('toggle field translatability'),
+    'file' => 'translation_entity.admin.inc',
+  );
+
+  return $items;
+}
+
+/**
+ * Implements hook_menu_alter().
+ */
+function translation_entity_menu_alter(&$items) {
+  // Some menu loaders in the item paths might have been altered: we need to
+  // replace any menu loader with a plain % to check if base paths are still
+  // compatible.
+  $paths = array();
+  $regex = '|%[^/]+|';
+  foreach ($items as $path => $item) {
+    $path = preg_replace($regex, '%', $path);
+    $paths[$path] = $path;
+  }
+
+  // Check that the declared menu base paths are actually valid.
+  foreach (entity_get_info() as $entity_type => $info) {
+    if (translation_entity_enabled($entity_type)) {
+      $path = $info['menu base path'];
+
+      // If the base path is not defined or is not compatible with any defined
+      // one we cannot provide the translation UI for this entity type.
+      if (!isset($paths[preg_replace($regex, '%', $path)])) {
+        drupal_set_message(t('The entities of type %entity_type do not define a valid base path: it will not be possible to translate them.', array('%entity_type' => $info['label'])), 'warning');
+        unset(
+          $items["$path/translations"],
+          $items["$path/translations/add/%language"],
+          $items["$path/translations/delete/%language"]
+        );
+      }
+      else {
+        $entity_position = count(explode('/', $path)) - 1;
+        $edit_path = $info['menu edit path'];
+
+        if (isset($items[$edit_path])) {
+          // If the edit path is a default local task we need to find the parent
+          // item.
+          $edit_path_split = explode('/', $edit_path);
+          do {
+            $entity_form_item = &$items[implode('/', $edit_path_split)];
+            array_pop($edit_path_split);
+          }
+          while (!empty($entity_form_item['type']) && $entity_form_item['type'] == MENU_DEFAULT_LOCAL_TASK);
+
+          // Make the "Translate" tab follow the "Edit" one when possibile.
+          if (isset($entity_form_item['weight'])) {
+            $items["$path/translations"]['weight'] = $entity_form_item['weight'] + 0.01;
+          }
+        }
+      }
+    }
+  }
+}
+
+/**
+ * Access callback.
+ */
+function translation_entity_tab_access(EntityInterface $entity) {
+  $entity_type = $entity->entityType();
+  return empty($entity->language()->locked) && language_multilingual() && (user_access('translate any entity') || user_access("translate $entity_type entities"));
+}
+
+/**
+ * Access callback.
+ */
+function translation_entity_add_access(EntityInterface $entity, Language $source = NULL, Language $target = NULL) {
+  $source = !empty($source) ? $source : $entity->language();
+  $target = !empty($target) ? $target : language(LANGUAGE_TYPE_CONTENT);
+  $translations = $entity->translations();
+  $languages = language_list();
+  return $source->langcode != $target->langcode && isset($languages[$source->langcode]) && isset($languages[$target->langcode]) && !isset($translations[$target->langcode]) && translation_entity_access($entity, $target->langcode);
+}
+
+/**
+ * Helper function. Determines whether the given entity type is translatable.
+ */
+function translation_entity_enabled($entity_type, $bundle = NULL, $skip_handler = FALSE) {
+  $enabled = FALSE;
+
+  if (!empty($bundle)) {
+    $enabled = variable_get("translation_entity_enabled_{$entity_type}_{$bundle}");
+  }
+  else {
+    $entity_info = entity_get_info($entity_type);
+    $bundles = isset($entity_info['bundles']) ? $entity_info['bundles'] : array($entity_type => NULL);
+    foreach ($bundles as $bundle => $info) {
+      if (variable_get("translation_entity_enabled_{$entity_type}_{$bundle}")) {
+        $enabled = TRUE;
+        break;
+      }
+    }
+  }
+
+  return $enabled && ($skip_handler || field_has_translation_handler($entity_type, 'translation_entity'));
+}
+
+/**
+ * Entity translation controller factory.
+ *
+ * @param $entity
+ *   The entity being translated.
+ *
+ * @return Drupal\translation_entity\EntityTranslationControllerInterface
+ *   An instance of the entity translation controller interface.
+ */
+function translation_entity_controller($entity_type) {
+  $entity_info = entity_get_info($entity_type);
+  // @todo Throw an exception if the key is missing.
+  $class = $entity_info['translation']['translation_entity']['class'];
+  return new $class($entity_type);
+}
+
+/**
+ * Returns the entity form controller for the given form.
+ *
+ * @param array $form_state
+ *   The form state array holding the entity form controller.
+ *
+ * @return Drupal\Core\Entity\EntityFormControllerInterface;
+ *   An instance of the entity translation form interface or FALSE if not an
+ *   entity form.
+ */
+function translation_entity_form_controller(array $form_state) {
+  return isset($form_state['controller']) && $form_state['controller'] instanceof EntityFormControllerInterface ? $form_state['controller'] : FALSE;
+}
+
+/**
+ * Checks whether an entity translation is accessible.
+ *
+ * @param $entity
+ *   The entity to be accessed.
+ * @param $langcode
+ *   The language of the translation to be accessed.
+ *
+ * @return
+ *   TRUE if the current user is allowed to view the translation.
+ */
+function translation_entity_access(EntityInterface $entity, $langcode) {
+  return translation_entity_controller($entity->entityType())->getTranslationAccess($entity, $langcode) ;
+}
+
+/**
+ * Implements hook_permission().
+ */
+function translation_entity_permission() {
+  $permission = array(
+    'edit original values' => array(
+      'title' => t('Edit original values'),
+      'description' => t('Access the entity form in the original language.'),
+    ),
+    'toggle field translatability' => array(
+      'title' => t('Toggle field translatability'),
+      'description' => t('Toggle translatability of fields performing a bulk update.'),
+    ),
+    'translate any entity' => array(
+      'title' => t('Translate any entity'),
+      'description' => t('Translate field content for any fieldable entity.'),
+    ),
+  );
+
+  foreach (entity_get_info() as $entity_type => $info) {
+    if (translation_entity_enabled($entity_type)) {
+      $label = !empty($info['label']) ? t($info['label']) : $entity_type;
+      $permission["translate $entity_type entities"] = array(
+        'title' => t('Translate entities of type @type', array('@type' => $label)),
+        'description' => t('Translate field content for entities of type @type.', array('@type' => $label)),
+      );
+    }
+  }
+
+  return $permission;
+}
+
+/**
+ * Implements hook_form_alter().
+ */
+function translation_entity_form_alter(&$form, &$form_state) {
+  if (($form_controller = translation_entity_form_controller($form_state)) && ($entity = $form_controller->getEntity($form_state)) && !$entity->isNew()) {
+    $controller = translation_entity_controller($entity->entityType());
+    $controller->entityFormAlter($form, $form_state, $entity);
+  }
+}
+
+/**
+ * Implements hook_entity_load().
+ */
+function translation_entity_entity_load(array $entities, $entity_type) {
+  $enabled_entities = array();
+
+  if (translation_entity_enabled($entity_type)) {
+    foreach ($entities as $entity) {
+      if (translation_entity_enabled($entity_type, $entity->bundle())) {
+        $enabled_entities[$entity->id()] = $entity;
+      }
+    }
+  }
+
+  if (!empty($enabled_entities)) {
+    translation_entity_load_translation_data($enabled_entities, $entity_type);
+  }
+}
+
+/**
+ * Loads translation data into the given entities.
+ *
+ * @param array $entities
+ *   The entities keyed by entity ID.
+ * @param string $entity_type
+ *   The type of the entities.
+ */
+function translation_entity_load_translation_data(array $entities, $entity_type) {
+  $result = db_select('translation_entity', 'te')
+    ->fields('te', array())
+    ->condition('te.entity_type', $entity_type)
+    ->condition('te.entity_id', array_keys($entities))
+    ->execute();
+
+  foreach ($result as $record) {
+    $entity = $entities[$record->entity_id];
+    // @todo Declare these as entity (translation?) properties.
+    $entity->source[$record->langcode] = $record->source;
+    // @todo Rename to 'translate' when the column is removed from the node
+    // schema.
+    $entity->retranslate[$record->langcode] = (boolean) $record->translate;
+  }
+}
+
+/**
+ * Implements hook_entity_insert().
+ */
+function translation_entity_entity_insert(EntityInterface $entity) {
+  $entity_type = $entity->entityType();
+  $id = $entity->id();
+  $query = db_insert('translation_entity')
+    ->fields(array('entity_type', 'entity_id', 'langcode', 'source', 'translate'));
+
+  foreach ($entity->translations() as $langcode => $language) {
+    // @todo Declare these as entity (translation?) properties.
+    $source = (isset($entity->source[$langcode]) ? $entity->source[$langcode] : NULL) . '';
+    $retranslate = intval(!empty($entity->retranslate[$langcode]));
+    $query->values(array($entity_type, $id, $langcode, $source, $retranslate));
+  }
+
+  $query->execute();
+}
+
+/**
+ * Implements hook_entity_delete().
+ */
+function translation_entity_entity_delete(EntityInterface $entity) {
+  db_delete('translation_entity')
+    ->condition('entity_type', $entity->entityType())
+    ->condition('entity_id', $entity->id())
+    ->execute();
+}
+
+/**
+ * Implements hook_entity_update().
+ */
+function translation_entity_entity_update(EntityInterface $entity) {
+  // Delete and create to ensure no stale value remains behind.
+  translation_entity_entity_delete($entity);
+  translation_entity_entity_insert($entity);
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function translation_entity_form_field_ui_field_edit_form_alter(&$form, &$form_state, $form_id) {
+  $field = $form['#field'];
+  $field_name = $field['field_name'];
+  $translatable = $field['translatable'];
+  $label = t('Field translation');
+  $title = t('Users may translate this field.');
+
+  if (field_has_data($field)) {
+    $path = "admin/config/regional/translation_entity/translatable/$field_name";
+    $status = $translatable ? $title : t('This field is shared among the entity translations.');
+    $link_title = !$translatable ? t('Enable translation') : t('Disable translation');
+
+    $form['field']['translatable'] = array(
+      '#prefix' => '<div class="translatable"><label>' . $label . '</label>',
+      '#suffix' => '</div>',
+      'message' => array(
+        '#markup' => $status . ' ',
+      ),
+      'link' => array(
+        '#type' => 'link',
+        '#title' => $link_title,
+        '#href' => $path,
+        '#options' => array('query' => drupal_get_destination()),
+        '#access' => user_access('toggle field translatability'),
+      ),
+    );
+  }
+  else {
+    $form['field']['translatable'] = array(
+      '#prefix' => '<label>' . $label . '</label>',
+      '#type' => 'checkbox',
+      '#title' => $title,
+      '#default_value' => $translatable,
+    );
+  }
+}
+
+/**
+ * Widget to enable entity translation per entity bundle.
+ *
+ * The suffix and bundle parameter are supposed to be provided in a way that the
+ * resulting variable name is always in the following form:
+ * 'translation_entity_enabled_ENTITY_TYPE_BUNDLE'.
+ *
+ * @param $suffix
+ *   An element name suffix, usually the entity type such as 'node' or 'user'.
+ * @param $bundle
+ *   The bundle name.
+ * @param $append_bundle
+ *   (optional) If FALSE the bundle name is not appended to variable name.
+ *   For instance node_type_form() automatically saves the form values with the
+ *   bundle name associated.
+ *
+ * @return array
+ *   The widget to enable the translation
+ */
+function translation_entity_enable_widget(&$form_state, $suffix, $bundle, $append_bundle = TRUE) {
+  $element_name = 'translation_entity_enabled_' . $suffix;
+  $variable_name = $element_name . '_' . $bundle;
+  if ($append_bundle) {
+    $element_name .= '_' . $bundle;
+  }
+
+  $form_state['translation_entity']['element_name'] = $element_name;
+  $form_state['translation_entity']['variable_name'] = $variable_name;
+
+  return array(
+    $element_name => array(
+      '#type' => 'checkbox',
+      '#title' => t('Enable translation'),
+      '#default_value' => variable_get($variable_name, FALSE),
+      '#element_validate' => array('translation_entity_enable_widget_validate'),
+      '#prefix' => '<label>' . t('Translation') . '</label>',
+    ),
+  );
+}
+
+/**
+ * Checks if translation can be enabled.
+ */
+function translation_entity_enable_widget_validate($element, &$form_state, $form) {
+  // @todo Add common widget validation when entity language settings will be
+  // generalizated. See node_type_translation_entity_enable_widget_validate().
+}
+
+/**
+ * Checks if translation can be enabled.
+ */
+function translation_entity_enable_widget_submit($form, &$form_state) {
+  $variable_name = $form_state['translation_entity']['variable_name'];
+  $enabled = $form_state['values'][$form_state['translation_entity']['element_name']];
+  if (variable_get($variable_name) != $enabled) {
+    variable_set($variable_name, $enabled);
+    entity_info_cache_clear();
+    menu_router_rebuild();
+  }
+}
diff --git a/core/modules/translation_entity/translation_entity.pages.inc b/core/modules/translation_entity/translation_entity.pages.inc
new file mode 100644
index 0000000..782af7d
--- /dev/null
+++ b/core/modules/translation_entity/translation_entity.pages.inc
@@ -0,0 +1,198 @@
+<?php
+
+/**
+ * @file
+ * The entity translation user interface.
+ */
+
+use Drupal\Core\Language\Language;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\translation_entity\EntityTranslationController;
+
+/**
+ * Translations overview page callback.
+ */
+function translation_entity_overview(EntityInterface $entity) {
+  $controller = translation_entity_controller($entity->entityType());
+  $languages = language_list();
+  $original = $entity->language()->langcode;
+  $translations = $entity->translations();
+
+  $path = $controller->getViewPath($entity);
+  $base_path = $controller->getBasePath($entity);
+  $edit_path = $controller->getEditPath($entity);
+
+  $header = array(t('Language'), t('Source language'), t('Translation'), t('Status'), t('Operations'));
+  $rows = array();
+
+  if (language_multilingual()) {
+    // If we have a view path defined for the current entity get the switch
+    // links based on it.
+    if ($path) {
+      $links = _translation_entity_get_switch_links($path);
+    }
+
+    foreach ($languages as $language) {
+      $options = array();
+      $language_name = $language->name;
+      $langcode = $language->langcode;
+      $add_path = "$base_path/translations/add/$original";
+
+      if ($base_path) {
+        $add_links = _translation_entity_get_switch_links($add_path);
+        $edit_links = _translation_entity_get_switch_links($edit_path);
+      }
+
+      if (isset($translations[$langcode])) {
+        // Existing translation in the translation set: display status.
+        $source = isset($entity->source[$langcode]) ? $entity->source[$langcode] : '';
+        $is_original = $langcode == $original;
+        $translation = $translations[$langcode];
+        $label = $entity->label($langcode);
+        $link = isset($links->links[$langcode]['href']) ? $links->links[$langcode] : array('href' => $path, 'language' => $language);
+        $row_title = l($label, $link['href'], $link);
+
+        if (empty($link['href'])) {
+          $row_title = $is_original ? $label : t('n/a');
+        }
+
+        if ($edit_path && $controller->getAccess($entity, 'update') && $controller->getTranslationAccess($entity, $langcode)) {
+          $link = isset($edit_links->links[$langcode]['href']) ? $edit_links->links[$langcode] : array('href' => $edit_path, 'language' => $language);
+          $options[] = l(t('edit'), $link['href'], $link);
+        }
+
+        // @todo Consider supporting the ability to track translation publishing
+        // status independently from entity status, as it may not exist.
+        $translation_status = $entity->get('status', $langcode);
+        $status = !isset($translation_status) || $translation_status ? t('Published') : t('Not published');
+        // @todo Add a theming function here.
+        $status .= !empty($entity->retranslate[$langcode]) ? ' - <span class="marker">' . t('outdated') . '</span>' : '';
+
+        if ($is_original) {
+          $language_name = t('<strong>@language_name</strong>', array('@language_name' => $language_name));
+          $source_name = t('(original content)');
+        }
+        else {
+          $source_name = isset($languages[$source]) ? $languages[$source]->name : t('n/a');
+        }
+      }
+      else {
+        // No such translation in the set yet: help user to create it.
+        $row_title = $source_name = t('n/a');
+        $source = $entity->language()->langcode;
+
+        if ($source != $langcode && $controller->getAccess($entity, 'update')) {
+          $translatable = FALSE;
+
+          foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) {
+            $field_name = $instance['field_name'];
+            $field = field_info_field($field_name);
+            if ($field['translatable']) {
+              $translatable = TRUE;
+              break;
+            }
+          }
+
+          $link = isset($add_links->links[$langcode]['href']) ? $add_links->links[$langcode] : array('href' => $add_path, 'language' => $language);
+          $options[] = $translatable ? l(t('add'), $link['href'], $link) : t('No translatable fields');
+        }
+        $status = t('Not translated');
+      }
+      $rows[] = array($language_name, $source_name, $row_title, $status, implode(" | ", $options));
+    }
+  }
+
+  drupal_set_title(t('Translations of %label', array('%label' => $entity->label())), PASS_THROUGH);
+
+  // Add metadata to the build render array to let other modules know about
+  // which entity this is.
+  $build['#entity'] = $entity;
+
+  $build['translation_entity_overview'] = array(
+    '#theme' => 'table',
+    '#header' => $header,
+    '#rows' => $rows,
+  );
+
+  return $build;
+}
+
+/**
+ * Returns the localized links for the given path.
+ */
+function _translation_entity_get_switch_links($path) {
+  $links = language_negotiation_get_switch_links(LANGUAGE_TYPE_CONTENT, $path);
+  if (empty($links)) {
+    // If content language is set up to fall back to the interface language,
+    // then there will be no switch links for LANGUAGE_TYPE_CONTENT, ergo we
+    // also need to use interface switch links.
+    $links = language_negotiation_get_switch_links(LANGUAGE_TYPE_INTERFACE, $path);
+  }
+  return $links;
+}
+
+/**
+ * Page callback.
+ */
+function translation_entity_add_page(EntityInterface $entity, Language $source = NULL, Language $target = NULL) {
+  $source = !empty($source) ? $source : $entity->language();
+  $target = !empty($target) ? $target : language(LANGUAGE_TYPE_CONTENT);
+  translation_entity_prepare_translation($entity, $source, $target);
+  $info = $entity->entityInfo();
+  $operation = isset($info['default operation']) ? $info['default operation'] : 'default';
+  $form_state = entity_form_state_defaults($entity, $operation, $target->langcode);
+  $form_state['translation_entity']['source'] = $source;
+  $form_state['translation_entity']['target'] = $target;
+  $form_id = entity_form_id($entity);
+  return drupal_build_form($form_id, $form_state);
+}
+
+/**
+ * Populates target values with the source values.
+ */
+function translation_entity_prepare_translation(EntityInterface $entity, Language $source, Language $target) {
+  // @todo Exploit the upcoming hook_entity_prepare() when available.
+  foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $field_name => $instance) {
+    $field = field_info_field($field_name);
+    if (!empty($field['translatable'])) {
+      $items = $entity->get($field_name, $source->langcode);
+      $entity->set($field_name, $items, $target->langcode);
+    }
+  }
+}
+
+/**
+ * Translation deletion confirmation form.
+ */
+function translation_entity_delete_confirm(array $form, array $form_state, EntityInterface $entity, Language $language) {
+  $langcode = $language->langcode;
+  $controller = translation_entity_controller($entity->entityType());
+
+  return confirm_form(
+    $form,
+    t('Are you sure you want to delete the @language translation of %label?', array('@language' => $language->name, '%label' => $entity->label())),
+    $controller->getEditPath($entity),
+    t('This action cannot be undone.'),
+    t('Delete'),
+    t('Cancel')
+  );
+}
+
+/**
+ * Submit handler for the translation deletion confirmation.
+ */
+function translation_entity_delete_confirm_submit($form, &$form_state) {
+  list($entity, $language) = $form_state['build_info']['args'];
+  $controller = translation_entity_controller($entity->entityType());
+
+  // Remove the translated values.
+  $controller->removeTranslation($entity, $language->langcode);
+  $entity->save();
+
+  // Remove any existing path alias for the removed translation.
+  if (module_exists('path')) {
+    path_delete(array('source' => $controller->getViewPath($entity), 'langcode' => $language->langcode));
+  }
+
+  $form_state['redirect'] = $controller->getBasePath($entity) . '/translations';
+}
diff --git a/core/modules/user/lib/Drupal/user/ProfileTranslationController.php b/core/modules/user/lib/Drupal/user/ProfileTranslationController.php
new file mode 100644
index 0000000..f10d58e
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/ProfileTranslationController.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\user\ProfileTranslationController.
+ */
+
+namespace Drupal\user;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\translation_entity\EntityTranslationController;
+
+/**
+ * Defines the translation controller class for terms.
+ */
+class ProfileTranslationController extends EntityTranslationController {
+
+  /**
+   * Overrides EntityTranslationController::entityFormAlter().
+   */
+  public function entityFormAlter(array &$form, array &$form_state, EntityInterface $entity) {
+    parent::entityFormAlter($form, $form_state, $entity);
+    $form['actions']['submit']['#submit'][] = array($this, 'entityFormSave');
+  }
+
+  /**
+   * Submit handler for the save action.
+   */
+  function entityFormSave(array $form, array &$form_state) {
+    if ($this->getSourceLangcode($form_state)) {
+      $entity = translation_entity_form_controller($form_state)->getEntity($form_state);
+      // We need a redirect here, otherwise we would get an access denied page
+      // since the curret URL would be preserved and we would try to add a
+      // translation for a language that already has a translation.
+      $form_state['redirect'] = $this->getEditPath($entity);
+    }
+  }
+}
diff --git a/core/modules/user/user.admin.inc b/core/modules/user/user.admin.inc
index f9b1c23..fe868d8 100644
--- a/core/modules/user/user.admin.inc
+++ b/core/modules/user/user.admin.inc
@@ -298,6 +298,16 @@ function user_admin_settings($form, &$form_state) {
     '#description' => t('This role will be automatically assigned new permissions whenever a module is enabled. Changing this setting will not affect existing permissions.'),
   );
 
+  if (module_exists('translation_entity')) {
+    $form['language'] = array(
+      '#type' => 'fieldset',
+      '#title' => t('Language settings'),
+    );
+
+    $form['language'] += translation_entity_enable_widget($form_state, 'user', 'user');
+    $form['#submit'][] = 'translation_entity_enable_widget_submit';
+  }
+
   // User registration settings.
   $form['registration_cancellation'] = array(
     '#type' => 'fieldset',
@@ -678,6 +688,11 @@ function user_admin_settings_submit($form, &$form_state) {
     ->set('status_canceled.body', $form_state['values']['user_mail_status_canceled_body'])
     ->set('status_canceled.subject', $form_state['values']['user_mail_status_canceled_subject'])
     ->save();
+
+  // @todo Move this to the config system.
+  if (isset($form_state['values']['translation_entity_enabled_user_user'])) {
+    variable_set('translation_entity_enabled_user_user', $form_state['values']['translation_entity_enabled_user_user']);
+  }
 }
 
 /**
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 3b69966..a5e8478 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -142,7 +142,7 @@ function user_theme() {
  * Implements hook_entity_info().
  */
 function user_entity_info() {
-  return array(
+  $return = array(
     'user' => array(
       'label' => t('User'),
       'controller class' => 'Drupal\user\UserStorageController',
@@ -150,6 +150,9 @@ function user_entity_info() {
         'profile' => 'Drupal\user\ProfileFormController',
         'register' => 'Drupal\user\RegisterFormController',
       ),
+      // @todo Remove this once the profile form controller is associated to the
+      // default operation.
+      'default operation' => 'profile',
       'base table' => 'users',
       'uri callback' => 'user_uri',
       'label callback' => 'user_label',
@@ -176,6 +179,14 @@ function user_entity_info() {
       ),
     ),
   );
+
+  if (module_exists('translation_entity')) {
+    $return['user']['translation']['translation_entity'] = array(
+      'class' => 'Drupal\user\ProfileTranslationController',
+    );
+  }
+
+  return $return;
 }
 
 /**
