diff --git a/core/includes/entity.api.php b/core/includes/entity.api.php
index 726d1b1..dc3cb2b 100644
--- a/core/includes/entity.api.php
+++ b/core/includes/entity.api.php
@@ -241,6 +241,37 @@ function hook_entity_update(Drupal\Core\Entity\EntityInterface $entity) {
 }
 
 /**
+ * Acts after storing a new entity translation.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $translation
+ *   The entity object of the translation just stored.
+ */
+function hook_entity_translation_insert(\Drupal\Core\Entity\EntityInterface $translation) {
+  $variables = array(
+    '@language' => $translation->language()->name,
+    '@label' => $translation->getOriginal()->label(),
+  );
+  watchdog('example', 'The @language translation of @label has just been stored.', $variables);
+}
+
+/**
+ * Acts after deleting an entity translation from the storage.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ *   The original entity object.
+ * @param string $langcode
+ *   The language code identifying the translation just removed.
+ */
+function hook_entity_translation_delete(\Drupal\Core\Entity\EntityInterface $entity, $langcode) {
+  $languages = language_list();
+  $variables = array(
+    '@language' => $languages[$langcode]->name,
+    '@label' => $entity->label(),
+  );
+  watchdog('example', 'The @language translation of @label has just been deleted.', $variables);
+}
+
+/**
  * Act before entity deletion.
  *
  * This hook runs after the entity type-specific predelete hook.
diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
index e89cb02..31e81e8 100644
--- a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
+++ b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
@@ -14,7 +14,7 @@
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\DatabaseStorageController;
 use Drupal\Core\Entity\EntityStorageException;
-use Drupal\Core\TypedData\ComplexDataInterface;
+use Drupal\Core\TypedData\TranslatableInterface;
 use Drupal\Component\Uuid\Uuid;
 use Drupal\Core\Database\Connection;
 
@@ -310,6 +310,7 @@ protected function attachPropertyData(array &$entities, $revision_id = FALSE) {
 
       $data = $query->execute();
       $field_definition = \Drupal::entityManager()->getFieldDefinitions($this->entityType);
+      $translations = array();
       if ($this->revisionTable) {
         $data_fields = array_flip(array_diff(drupal_schema_fields_sql($this->entityInfo['revision_table']), drupal_schema_fields_sql($this->entityInfo['base_table'])));
       }
@@ -323,6 +324,7 @@ protected function attachPropertyData(array &$entities, $revision_id = FALSE) {
         // Field values in default language are stored with
         // Language::LANGCODE_DEFAULT as key.
         $langcode = empty($values['default_langcode']) ? $values['langcode'] : Language::LANGCODE_DEFAULT;
+        $translations[$id][$langcode] = TRUE;
 
         foreach ($field_definition as $name => $definition) {
           // Set only translatable properties, unless we are dealing with a
@@ -338,7 +340,7 @@ protected function attachPropertyData(array &$entities, $revision_id = FALSE) {
       foreach ($entities as $id => $values) {
         $bundle = $this->bundleKey ? $values[$this->bundleKey][Language::LANGCODE_DEFAULT] : FALSE;
         // Turn the record into an entity class.
-        $entities[$id] = new $this->entityClass($values, $this->entityType, $bundle);
+        $entities[$id] = new $this->entityClass($values, $this->entityType, $bundle, array_keys($translations[$id]));
       }
     }
   }
@@ -386,6 +388,9 @@ public function save(EntityInterface $entity) {
         $this->resetCache(array($entity->id()));
         $entity->postSave($this, TRUE);
         $this->invokeHook('update', $entity);
+        if ($this->dataTable) {
+          $this->notifyTranslationChanges($entity);
+        }
       }
       else {
         $return = drupal_write_record($this->entityInfo['base_table'], $record);
@@ -430,7 +435,7 @@ public function save(EntityInterface $entity) {
    */
   protected function saveRevision(EntityInterface $entity) {
     $return = $entity->id();
-    $default_langcode = $entity->language()->langcode;
+    $default_langcode = $entity->getOriginal()->language()->langcode;
 
     if (!$entity->isNewRevision()) {
       // Delete to handle removed values.
@@ -440,9 +445,9 @@ protected function saveRevision(EntityInterface $entity) {
         ->execute();
     }
 
-    $languages = $this->dataTable ? $entity->getTranslationLanguages(TRUE) : array($default_langcode => $entity->language());
+    $languages = $this->dataTable ? $entity->getTranslationLanguages() : array($default_langcode => $entity->language());
     foreach ($languages as $langcode => $language) {
-      $translation = $entity->getTranslation($langcode, FALSE);
+      $translation = $entity->getTranslation($langcode);
       $record = $this->mapToRevisionStorageRecord($translation);
       $record->langcode = $langcode;
       $record->default_langcode = $langcode == $default_langcode;
@@ -504,6 +509,28 @@ protected function savePropertyData(EntityInterface $entity) {
   }
 
   /**
+   * Checks translation statuses and invoke the related hooks if needed.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   */
+  function notifyTranslationChanges(EntityInterface $entity) {
+    // Notify modules of translation creation/removal.
+    foreach ($entity->getTranslationLanguages(FALSE, TRUE) as $langcode => $language) {
+      $translation = $entity->getTranslation($langcode);
+
+      switch ($translation->getTranslationStatus()) {
+        case TranslatableInterface::TRANSLATION_CREATED:
+          $this->invokeHook('translation_insert', $translation);
+          break;
+
+        case TranslatableInterface::TRANSLATION_REMOVED:
+          $this->invokeHook('translation_delete', $translation);
+          break;
+      }
+    }
+  }
+
+  /**
    * Overrides DatabaseStorageController::invokeHook().
    *
    * Invokes field API attachers with a BC entity.
@@ -551,7 +578,7 @@ protected function mapToStorageRecord(EntityInterface $entity) {
    * @return \stdClass
    *   The record to store.
    */
-  protected function mapToRevisionStorageRecord(ComplexDataInterface $entity) {
+  protected function mapToRevisionStorageRecord(EntityInterface $entity) {
     $record = new \stdClass();
     $definitions = $entity->getPropertyDefinitions();
     foreach (drupal_schema_fields_sql($this->entityInfo['revision_table']) as $name) {
@@ -574,10 +601,10 @@ protected function mapToRevisionStorageRecord(ComplexDataInterface $entity) {
    *   The record to store.
    */
   protected function mapToDataStorageRecord(EntityInterface $entity, $langcode) {
-    $default_langcode = $entity->language()->langcode;
+    $default_langcode = $entity->getOriginal()->language()->langcode;
     // Don't use strict mode, this way there's no need to do checks here, as
     // non-translatable properties are replicated for each language.
-    $translation = $entity->getTranslation($langcode, FALSE);
+    $translation = $entity->getTranslation($langcode);
     $definitions = $translation->getPropertyDefinitions();
     $schema = drupal_get_schema($this->entityInfo['data_table']);
 
diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php
index 8af2040..48d5a53 100644
--- a/core/lib/Drupal/Core/Entity/Entity.php
+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Uuid\Uuid;
 use Drupal\Core\Language\Language;
+use Drupal\Core\TypedData\TranslatableInterface;
 use Drupal\Core\TypedData\TypedDataInterface;
 use Drupal\user\UserInterface;
 use IteratorAggregate;
@@ -294,10 +295,13 @@ public function language() {
 
   /**
    * Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslation().
+   *
+   * @return \Drupal\Core\Entity\EntityInterface
    */
-  public function getTranslation($langcode, $strict = TRUE) {
+  public function getTranslation($langcode) {
     // @todo: Replace by EntityNG implementation once all entity types have been
     // converted to use the entity field API.
+    return $this;
   }
 
   /**
@@ -314,7 +318,7 @@ public function translations() {
   /**
    * Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslationLanguages().
    */
-  public function getTranslationLanguages($include_default = TRUE) {
+  public function getTranslationLanguages($include_default = TRUE, $include_removed = FALSE) {
     // @todo: Replace by EntityNG implementation once all entity types have been
     // converted to use the entity field API.
     $default_language = $this->language();
@@ -588,4 +592,41 @@ public static function postLoad(EntityStorageControllerInterface $storage_contro
   public function preSaveRevision(EntityStorageControllerInterface $storage_controller, \stdClass $record) {
   }
 
+  /**
+   * Implements \Drupal\Core\TypedData\TranslatableInterface::getOriginal().
+   */
+  public function getOriginal() {
+    return $this->getTranslation(Language::LANGCODE_DEFAULT);
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\TranslatableInterface::hasTranslation().
+   */
+  public function hasTranslation($langcode) {
+    $translations = $this->getTranslationLanguages();
+    return isset($translations[$langcode]);
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\TranslatableInterface::addTranslation().
+   */
+  public function addTranslation($langcode, array $values = array()) {}
+
+  /**
+   * Implements \Drupal\Core\TypedData\TranslatableInterface::removeTranslation().
+   */
+  public function removeTranslation($langcode) {}
+
+  /**
+   * Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslationStatus().
+   */
+  public function getTranslationStatus() {
+    return TranslatableInterface::TRANSLATION_EXISTING;
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\TranslatableInterface::initTranslation().
+   */
+  public function initTranslation($langcode) {}
+
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityBCDecorator.php b/core/lib/Drupal/Core/Entity/EntityBCDecorator.php
index d292a73..2453a34 100644
--- a/core/lib/Drupal/Core/Entity/EntityBCDecorator.php
+++ b/core/lib/Drupal/Core/Entity/EntityBCDecorator.php
@@ -139,7 +139,7 @@ public function &__get($name) {
       // Language::LANGCODE_DEFAULT. This is necessary as EntityNG always keys
       // default language values with Language::LANGCODE_DEFAULT while field API
       // expects them to be keyed by langcode.
-      $langcode = $this->decorated->language()->langcode;
+      $langcode = $this->decorated->getOriginal()->language()->langcode;
       if ($langcode != Language::LANGCODE_DEFAULT && isset($this->decorated->values[$name]) && is_array($this->decorated->values[$name])) {
         if (isset($this->decorated->values[$name][Language::LANGCODE_DEFAULT]) && !isset($this->decorated->values[$name][$langcode])) {
           $this->decorated->values[$name][$langcode] = &$this->decorated->values[$name][Language::LANGCODE_DEFAULT];
@@ -418,15 +418,15 @@ public function language() {
   /**
    * Forwards the call to the decorated entity.
    */
-  public function getTranslationLanguages($include_default = TRUE) {
-    return $this->decorated->getTranslationLanguages($include_default);
+  public function getTranslationLanguages($include_default = TRUE, $include_removed = FALSE) {
+    return $this->decorated->getTranslationLanguages($include_default, $include_removed);
   }
 
   /**
    * Forwards the call to the decorated entity.
    */
-  public function getTranslation($langcode, $strict = TRUE) {
-    return $this->decorated->getTranslation($langcode, $strict);
+  public function getTranslation($langcode) {
+    return $this->decorated->getTranslation($langcode);
   }
 
   /**
@@ -527,12 +527,6 @@ public function onChange($property_name) {
     $this->decorated->onChange($property_name);
   }
 
-  /**
-   * Forwards the call to the decorated entity.
-   */
-  public function isTranslatable() {
-    return $this->decorated->isTranslatable();
-  }
 
   /**
    * Forwards the call to the decorated entity.
@@ -589,4 +583,54 @@ public static function postDelete(EntityStorageControllerInterface $storage_cont
    */
   public static function postLoad(EntityStorageControllerInterface $storage_controller, array $entities) {
   }
+
+  /**
+   * Forwards the call to the decorated entity.
+   */
+  public function isTranslatable() {
+    return $this->decorated->isTranslatable();
+  }
+
+  /**
+   * Forwards the call to the decorated entity.
+   */
+  public function getOriginal() {
+    return $this->decorated->getOriginal();
+  }
+
+  /**
+   * Forwards the call to the decorated entity.
+   */
+  public function hasTranslation($langcode) {
+    return $this->decorated->hasTranslation($langcode);
+  }
+
+  /**
+   * Forwards the call to the decorated entity.
+   */
+  public function addTranslation($langcode, array $values = array()) {
+    return $this->decorated->addTranslation($langcode, $values);
+  }
+
+  /**
+   * Forwards the call to the decorated entity.
+   */
+  public function removeTranslation($langcode) {
+    $this->decorated->removeTranslation($langcode);
+  }
+
+  /**
+   * Forwards the call to the decorated entity.
+   */
+  public function getTranslationStatus() {
+   return $this->decorated->getTranslationStatus();
+  }
+
+  /**
+   * Forwards the call to the decorated entity.
+   */
+  public function initTranslation($langcode) {
+    $this->decorated->initTranslation($langcode);
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityFormController.php b/core/lib/Drupal/Core/Entity/EntityFormController.php
index 7c5f7ed..cef253b 100644
--- a/core/lib/Drupal/Core/Entity/EntityFormController.php
+++ b/core/lib/Drupal/Core/Entity/EntityFormController.php
@@ -120,6 +120,13 @@ protected function init(array &$form_state) {
     // Add the controller to the form state so it can be easily accessed by
     // module-provided form handlers there.
     $form_state['controller'] = $this;
+
+    // Ensure we act on the translation object corresponding to the current form
+    // language.
+    $translation = $this->entity->getTranslation($this->getFormLangcode($form_state));
+    $this->entity = $this->entity instanceof EntityBCDecorator ? $translation->getBCEntity() : $translation;
+
+    // Prepare the entity to be presented in the entity form.
     $this->prepareEntity();
 
     // @todo Allow the usage of different form modes by exposing a hook and the
@@ -164,7 +171,7 @@ public function form(array $form, array &$form_state) {
       // new entities.
       $form['langcode'] = array(
         '#type' => 'value',
-        '#value' => !$entity->isNew() ? $entity->langcode : language_default()->langcode,
+        '#value' => !$entity->isNew() ? $entity->getOriginal()->language()->langcode : language_default()->langcode,
       );
     }
     return $form;
@@ -309,7 +316,6 @@ public function delete(array $form, array &$form_state) {
    */
   public function getFormLangcode(array $form_state) {
     $entity = $this->entity;
-    $translations = $entity->getTranslationLanguages();
 
     if (!empty($form_state['langcode'])) {
       $langcode = $form_state['langcode'];
@@ -318,6 +324,7 @@ public function getFormLangcode(array $form_state) {
       // If no form langcode was provided we default to the current content
       // language and inspect existing translations to find a valid fallback,
       // if any.
+      $translations = $entity->getTranslationLanguages();
       $langcode = language(Language::TYPE_CONTENT)->langcode;
       $fallback = language_multilingual() ? language_fallback_get_candidates() : array();
       while (!empty($langcode) && !isset($translations[$langcode])) {
@@ -327,14 +334,14 @@ public function getFormLangcode(array $form_state) {
 
     // If the site is not multilingual or no translation for the given form
     // language is available, fall back to the entity language.
-    return !empty($langcode) ? $langcode : $entity->language()->langcode;
+    return !empty($langcode) ? $langcode : $entity->getOriginal()->language()->langcode;
   }
 
   /**
    * Implements \Drupal\Core\Entity\EntityFormControllerInterface::isDefaultFormLangcode().
    */
   public function isDefaultFormLangcode(array $form_state) {
-    return $this->getFormLangcode($form_state) == $this->entity->language()->langcode;
+    return $this->getFormLangcode($form_state) == $this->entity->getOriginal()->language()->langcode;
   }
 
   /**
@@ -363,13 +370,11 @@ protected function submitEntityLanguage(array $form, array &$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;
+      $current_langcode = $this->isDefaultFormLangcode($form_state) ? $form_state['values']['langcode'] : $this->getFormLangcode($form_state);
 
       foreach (field_info_instances($entity_type, $entity->bundle()) as $instance) {
         $field_name = $instance['field_name'];
diff --git a/core/lib/Drupal/Core/Entity/EntityFormControllerNG.php b/core/lib/Drupal/Core/Entity/EntityFormControllerNG.php
index 0c9dabd..192beff 100644
--- a/core/lib/Drupal/Core/Entity/EntityFormControllerNG.php
+++ b/core/lib/Drupal/Core/Entity/EntityFormControllerNG.php
@@ -84,11 +84,10 @@ public function buildEntity(array $form, array &$form_state) {
     // edited by this form. Values of fields handled by field API are copied
     // by field_attach_extract_form_values() below.
     $values_excluding_fields = $info['fieldable'] ? array_diff_key($form_state['values'], field_info_instances($entity_type, $entity->bundle())) : $form_state['values'];
-    $translation = $entity->getTranslation($this->getFormLangcode($form_state), FALSE);
-    $definitions = $translation->getPropertyDefinitions();
+    $definitions = $entity->getPropertyDefinitions();
     foreach ($values_excluding_fields as $key => $value) {
       if (isset($definitions[$key])) {
-        $translation->$key = $value;
+        $entity->$key = $value;
       }
     }
 
diff --git a/core/lib/Drupal/Core/Entity/EntityNG.php b/core/lib/Drupal/Core/Entity/EntityNG.php
index 355114e..97e271e 100644
--- a/core/lib/Drupal/Core/Entity/EntityNG.php
+++ b/core/lib/Drupal/Core/Entity/EntityNG.php
@@ -7,7 +7,10 @@
 
 namespace Drupal\Core\Entity;
 
+use Drupal\Component\Uuid\Uuid;
 use Drupal\Core\Language\Language;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\TypedData\TranslatableInterface;
 use Drupal\Core\TypedData\TypedDataInterface;
 use ArrayIterator;
 use InvalidArgumentException;
@@ -76,12 +79,43 @@ class EntityNG extends Entity {
    */
   protected $uriPlaceholderReplacements;
 
+
+
+  /**
+   * Language code identifying the entity active language.
+   *
+   * This is the language field accessors will use to determine which field
+   * values manipulate.
+   *
+   * @var string
+   */
+  protected $activeLangcode = Language::LANGCODE_DEFAULT;
+
+  /**
+   * An array of entity translation metadata.
+   *
+   * An associative array keyed by translation language code. Every value is an
+   * array containg the translation status and the translation object, if it has
+   * already been instantiated.
+   *
+   * @var array
+   */
+  protected $translations = array();
+
+  /**
+   * A flag indicating whether a translation object is being initialized.
+   *
+   * @var boolean
+   */
+  protected $translationInit = FALSE;
+
   /**
    * Overrides Entity::__construct().
    */
-  public function __construct(array $values, $entity_type, $bundle = FALSE) {
+  public function __construct(array $values, $entity_type, $bundle = FALSE, $translations = array()) {
     $this->entityType = $entity_type;
     $this->bundle = $bundle ? $bundle : $this->entityType;
+
     foreach ($values as $key => $value) {
       // If the key matches an existing property set the value to the property
       // to ensure non converted properties have the correct value.
@@ -90,6 +124,20 @@ public function __construct(array $values, $entity_type, $bundle = FALSE) {
       }
       $this->values[$key] = $value;
     }
+
+    // Initialize translations. Ensure we have at least an entry for the entity
+    // original language.
+    $data = array('status' => TranslatableInterface::TRANSLATION_EXISTING);
+    $this->translations[Language::LANGCODE_DEFAULT] = $data;
+    if ($translations) {
+      $default_langcode = $this->language()->langcode;
+      foreach ($translations as $langcode) {
+        if ($langcode != $default_langcode && $langcode != Language::LANGCODE_DEFAULT) {
+          $this->translations[$langcode] = $data;
+        }
+      }
+    }
+
     $this->init();
   }
 
@@ -111,10 +159,20 @@ protected function init() {
   }
 
   /**
+   * Clear entity translation object cache to ensure we do not have stale references.
+   */
+  protected function clearTranslationCache() {
+    foreach ($this->translations as &$translation) {
+      unset($translation['entity']);
+    }
+  }
+
+  /**
    * Magic __wakeup() implementation.
    */
   public function __wakeup() {
     $this->init();
+    $this->clearTranslationCache();
   }
 
   /**
@@ -197,12 +255,11 @@ protected function uriPlaceholderReplacements() {
    * Implements \Drupal\Core\TypedData\ComplexDataInterface::get().
    */
   public function get($property_name) {
-    // Values in default language are always stored using the
-    // Language::LANGCODE_DEFAULT constant.
-    if (!isset($this->fields[$property_name][Language::LANGCODE_DEFAULT])) {
-      return $this->getTranslatedField($property_name, Language::LANGCODE_DEFAULT);
+    $this->checkTranslationStatus();
+    if (!isset($this->fields[$property_name][$this->activeLangcode])) {
+      return $this->getTranslatedField($property_name, $this->activeLangcode);
     }
-    return $this->fields[$property_name][Language::LANGCODE_DEFAULT];
+    return $this->fields[$property_name][$this->activeLangcode];
   }
 
   /**
@@ -323,12 +380,38 @@ public function isEmpty() {
   }
 
   /**
+   * @inheritdoc
+   */
+  public function access($operation = 'view', AccountInterface $account = NULL) {
+    return \Drupal::entityManager()
+      ->getAccessController($this->entityType)
+      ->access($this, $operation, $this->activeLangcode, $account);
+  }
+
+  /**
    * Implements \Drupal\Core\TypedData\TranslatableInterface::language().
    */
   public function language() {
+    if ($this->activeLangcode != Language::LANGCODE_DEFAULT) {
+      $languages = language_list(Language::STATE_ALL);
+      if (isset($languages[$this->activeLangcode])) {
+        return $languages[$this->activeLangcode];
+      }
+    }
+    return $this->getOriginalLanguage();
+  }
+
+  /**
+   * Returns the entity original language.
+   *
+   * @return \Drupal\Core\Language\Language
+   *   The entity language object.
+   */
+  protected function getOriginalLanguage() {
+    $language = NULL;
     // Get the language code if the property exists.
-    if ($this->getPropertyDefinition('langcode')) {
-      $language = $this->get('langcode')->language;
+    if ($this->getPropertyDefinition('langcode') && ($item = $this->get('langcode')) && isset($item->language)) {
+      $language = $item->language;
     }
     if (empty($language)) {
       // Make sure we return a proper language object.
@@ -340,75 +423,169 @@ public function language() {
   /**
    * Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslation().
    *
-   * @return \Drupal\Core\Entity\Field\Type\EntityTranslation
+   * @return \Drupal\Core\Entity\EntityInterface
    */
-  public function getTranslation($langcode, $strict = TRUE) {
-    // If the default language is Language::LANGCODE_NOT_SPECIFIED, the entity is not
-    // translatable, so we use Language::LANGCODE_DEFAULT.
-    if ($langcode == Language::LANGCODE_DEFAULT || in_array($this->language()->langcode, array(Language::LANGCODE_NOT_SPECIFIED, $langcode))) {
-      // No translation needed, return the entity.
-      return $this;
+  public function getTranslation($langcode) {
+    // Ensure we always use the default language code when dealing with the
+    // original entity language.
+    if ($langcode != Language::LANGCODE_DEFAULT && $langcode == $this->getOriginalLanguage()->langcode) {
+      $langcode = Language::LANGCODE_DEFAULT;
+    }
+
+    // Populate entity translation object cache so it will be available for all
+    // translation objects.
+    if ($langcode == $this->activeLangcode) {
+      $this->translations[$langcode]['entity'] = $this;
+    }
+
+    // If we already have a translation object for the specified language we can
+    // just return it.
+    if (isset($this->translations[$langcode]['entity'])) {
+      return $this->translations[$langcode]['entity'];
+    }
+
+    // If the requested translation is valid, we instantiate a new translation
+    // object being a clone of the current one but with the specified language
+    // as active language. Before cloning we specify we are initializing a
+    // translation object to perform a shallow clone, in fact all the field data
+    // structures need to be shared among the translation objects to ensure all
+    // of them deal with fresh data.
+    if (isset($this->translations[$langcode])) {
+      $this->translationInit = TRUE;
+      $translation = clone $this;
+      unset($translation->bcEntity);
+      $translation->activeLangcode = $langcode;
+      // Ensure that changes to fields, values and translations are propagated
+      // to all the translation objects.
+      // @todo Consider converting these to ArrayObject.
+      $translation->values = &$this->values;
+      $translation->fields = &$this->fields;
+      $translation->translations = &$this->translations;
+      $translation->translationInit = FALSE;
+      $this->translations[$langcode]['entity'] = $translation;
+      $this->translationInit = FALSE;
+      return $translation;
     }
-    // Check whether the language code is valid, thus is of an available
-    // language.
+
+    // If we were given a valid language and there is no translation for it, we
+    // return a new one.
+    $languages = language_list();
+    if (isset($languages[$langcode])) {
+      // If the entity language is not a configured language we fall back to the
+      // entity itself, since in this case it cannot have translations.
+      return isset($languages[$this->getOriginalLanguage()->langcode]) ? $this->addTranslation($langcode) : $this;
+    }
+
+    // TODO Do we want to return $this instead?
+    throw new InvalidArgumentException("Invalid '$langcode' specified.");
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasTranslation($langcode) {
+    return !empty($this->translations[$langcode]['status']);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addTranslation($langcode, array $values = array()) {
     $languages = language_list(Language::STATE_ALL);
-    if (!isset($languages[$langcode])) {
-      throw new InvalidArgumentException("Unable to get translation for the invalid language '$langcode'.");
+    if (!isset($languages[$langcode]) || $this->hasTranslation($langcode)) {
+      throw new InvalidArgumentException("Invalid translation language '$langcode'");
+    }
+
+    // Instantiate a new empty entity so default values will be populated in the
+    // specified language.
+    $info = $this->entityInfo();
+    // @todo Use the actual translation language once the BC decorator is gone.
+    $default_values = array($info['entity_keys']['bundle'] => $this->bundle, 'langcode' => Language::LANGCODE_NOT_SPECIFIED);
+    $entity = entity_create($this->entityType(), $default_values);
+    foreach ($entity as $name => $field) {
+      if (!isset($values[$name]) && !$field->isEmpty()) {
+        $values[$name] = $field->value;
+      }
     }
-    $fields = array();
-    foreach ($this->getPropertyDefinitions() as $name => $definition) {
-      // Load only translatable properties in strict mode.
-      if (!empty($definition['translatable']) || !$strict) {
-        $fields[$name] = $this->getTranslatedField($name, $langcode);
+
+    $this->translations[$langcode]['status'] = self::TRANSLATION_CREATED;
+    $translation = $this->getTranslation($langcode);
+    $definitions = $translation->getPropertyDefinitions();
+
+    foreach ($values as $name => $value) {
+      if (isset($definitions[$name]) && !empty($definitions[$name]['translatable'])) {
+        $translation->$name = $value;
       }
     }
-    // @todo: Add a way to get the definition of a translation to the
-    // TranslatableInterface and leverage TypeDataManager::getPropertyInstance
-    // also.
-    $translation_definition = array(
-      'type' => 'entity_translation',
-      'constraints' => array(
-        'entity type' => $this->entityType(),
-        'bundle' => $this->bundle(),
-      ),
-    );
-    $translation = \Drupal::typedData()->create($translation_definition, $fields);
-    $translation->setStrictMode($strict);
-    $translation->setContext('@' . $langcode, $this);
+
     return $translation;
   }
 
   /**
-   * Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslationLanguages().
+   * {@inheritdoc}
    */
-  public function getTranslationLanguages($include_default = TRUE) {
-    $translations = array();
-    $definitions = $this->getPropertyDefinitions();
-    // Build an array with the translation langcodes set as keys. Empty
-    // translations should not be included and must be skipped.
-    foreach ($this->getProperties() as $name => $property) {
-      foreach ($this->fields[$name] as $langcode => $field) {
-        if (!$field->isEmpty()) {
-          $translations[$langcode] = TRUE;
-        }
-        if (isset($this->values[$name])) {
-          foreach ($this->values[$name] as $langcode => $values) {
-            // If a value is there but the field object is empty, it has been
-            // unset, so we need to skip the field also.
-            if ($values && !empty($definitions[$name]['translatable']) && !(isset($this->fields[$name][$langcode]) && $this->fields[$name][$langcode]->isEmpty())) {
-              $translations[$langcode] = TRUE;
-            }
-          }
-        }
+  public function removeTranslation($langcode) {
+    $translation = $this->getTranslation($langcode);
+    foreach ($translation->getPropertyDefinitions() as $name => $definition) {
+      if (!empty($definition['translatable'])) {
+        $translation->$name = array();
       }
     }
-    // We include the default language code instead of the
-    // Language::LANGCODE_DEFAULT constant.
+    $this->translations[$langcode]['status'] = self::TRANSLATION_REMOVED;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTranslationStatus() {
+    return $this->translations[$this->activeLangcode]['status'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function initTranslation($langcode) {
+    if ($langcode != Language::LANGCODE_DEFAULT && $langcode != $this->getOriginalLanguage()->langcode) {
+      $this->translations[$langcode]['status'] = self::TRANSLATION_EXISTING;
+    }
+  }
+
+  /**
+   * Checks wether the current translation object has a valid status.
+   *
+   * To avoid manipulating stale data, we invalidate a translation object as
+   * soon as the related translation has been removed. Any attempt to access
+   * invalid data causes an exception to be thrown.
+   *
+   * @return boolean
+   *   TRUE if the translation object has a valid status.
+   */
+  protected function checkTranslationStatus() {
+    if ($this->translations[$this->activeLangcode]['status'] == self::TRANSLATION_REMOVED) {
+      // TODO Use a more specific exception.
+      throw new \Exception('The entity object refers to a removed translation and cannot be manipulated.');
+    }
+    return TRUE;
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslationLanguages().
+   */
+  public function getTranslationLanguages($include_default = TRUE, $include_removed = FALSE) {
+    if (!$include_removed) {
+      $translations = array_filter($this->translations, function($translation) { return $translation['status']; });
+    }
+    else {
+      $translations = $this->translations;
+    }
+
     unset($translations[Language::LANGCODE_DEFAULT]);
 
     if ($include_default) {
-      $translations[$this->language()->langcode] = TRUE;
+      $langcode = $this->getOriginalLanguage()->langcode;
+      $translations[$langcode] = TRUE;
     }
+
     // Now load language objects based upon translation langcodes.
     return array_intersect_key(language_list(Language::STATE_ALL), $translations);
   }
@@ -458,17 +635,18 @@ public function updateOriginalValues() {
    * For compatibility mode to work this must return a reference.
    */
   public function &__get($name) {
+    $this->checkTranslationStatus();
     // If this is an entity field, handle it accordingly. We first check whether
     // a field object has been already created. If not, we create one.
-    if (isset($this->fields[$name][Language::LANGCODE_DEFAULT])) {
-      return $this->fields[$name][Language::LANGCODE_DEFAULT];
+    if (isset($this->fields[$name][$this->activeLangcode])) {
+      return $this->fields[$name][$this->activeLangcode];
     }
     // Inline getPropertyDefinition() to speed up things.
     if (!isset($this->fieldDefinitions)) {
       $this->getPropertyDefinitions();
     }
     if (isset($this->fieldDefinitions[$name])) {
-      $return = $this->getTranslatedField($name, Language::LANGCODE_DEFAULT);
+      $return = $this->getTranslatedField($name, $this->activeLangcode);
       return $return;
     }
     // Allow the EntityBCDecorator to directly access the values and fields.
@@ -490,17 +668,18 @@ public function &__get($name) {
    * Uses default language always.
    */
   public function __set($name, $value) {
+    $this->checkTranslationStatus();
     // Support setting values via property objects.
     if ($value instanceof TypedDataInterface && !$value instanceof EntityInterface) {
       $value = $value->getValue();
     }
     // If this is an entity field, handle it accordingly. We first check whether
     // a field object has been already created. If not, we create one.
-    if (isset($this->fields[$name][Language::LANGCODE_DEFAULT])) {
-      $this->fields[$name][Language::LANGCODE_DEFAULT]->setValue($value);
+    if (isset($this->fields[$name][$this->activeLangcode])) {
+      $this->fields[$name][$this->activeLangcode]->setValue($value);
     }
     elseif ($this->getPropertyDefinition($name)) {
-      $this->getTranslatedField($name, Language::LANGCODE_DEFAULT)->setValue($value);
+      $this->getTranslatedField($name, $this->activeLangcode)->setValue($value);
     }
     // Else directly read/write plain values. That way, fields not yet converted
     // to the entity field API can always be directly accessed.
@@ -537,6 +716,7 @@ public function __unset($name) {
    * Overrides Entity::createDuplicate().
    */
   public function createDuplicate() {
+    $this->checkTranslationStatus();
     $duplicate = clone $this;
     $entity_info = $this->entityInfo();
     $duplicate->{$entity_info['entity_keys']['id']}->value = NULL;
@@ -558,13 +738,19 @@ public function createDuplicate() {
    * Magic method: Implements a deep clone.
    */
   public function __clone() {
-    $this->bcEntity = NULL;
-
-    foreach ($this->fields as $name => $properties) {
-      foreach ($properties as $langcode => $property) {
-        $this->fields[$name][$langcode] = clone $property;
-        $this->fields[$name][$langcode]->setContext($name, $this);
+    // Avoid deep-cloning when we are initializing a translation object, since
+    // it will represent the same entity, only with a different active language.
+    if (!$this->translationInit) {
+      $this->bcEntity = NULL;
+
+      foreach ($this->fields as $name => $properties) {
+        foreach ($properties as $langcode => $property) {
+          $this->fields[$name][$langcode] = clone $property;
+          $this->fields[$name][$langcode]->setContext($name, $this);
+        }
       }
+
+      $this->clearTranslationCache();
     }
   }
 
@@ -574,6 +760,9 @@ public function __clone() {
   public function label($langcode = NULL) {
     $label = NULL;
     $entity_info = $this->entityInfo();
+    if (!isset($langcode)) {
+      $langcode = $this->activeLangcode;
+    }
     if (isset($entity_info['label_callback']) && function_exists($entity_info['label_callback'])) {
       $label = $entity_info['label_callback']($this->entityType, $this, $langcode);
     }
@@ -590,4 +779,5 @@ public function validate() {
     // @todo: Add the typed data manager as proper dependency.
     return \Drupal::typedData()->getValidator()->validate($this);
   }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/Field/Type/EntityTranslation.php b/core/lib/Drupal/Core/Entity/Field/Type/EntityTranslation.php
deleted file mode 100644
index 859fa9a..0000000
--- a/core/lib/Drupal/Core/Entity/Field/Type/EntityTranslation.php
+++ /dev/null
@@ -1,224 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\Entity\Type\EntityTranslation.
- */
-
-namespace Drupal\Core\Entity\Field\Type;
-
-use Drupal\Core\Session\AccountInterface;
-use Drupal\Core\TypedData\AccessibleInterface;
-use Drupal\Core\TypedData\ComplexDataInterface;
-use Drupal\Core\TypedData\TypedData;
-use ArrayIterator;
-use Drupal\Core\TypedData\TypedDataInterface;
-use IteratorAggregate;
-use InvalidArgumentException;
-
-/**
- * Allows accessing and updating translated entity fields.
- *
- * Via this object translated entity fields may be read and updated in the same
- * way as untranslatable entity fields on the entity object.
- */
-class EntityTranslation extends TypedData implements IteratorAggregate, AccessibleInterface, ComplexDataInterface {
-
-  /**
-   * The array of translated fields, each being an instance of
-   * \Drupal\Core\Entity\FieldInterface.
-   *
-   * @var array
-   */
-  protected $fields = array();
-
-  /**
-   * Whether the entity translation acts in strict mode.
-   *
-   * @var boolean
-   */
-  protected $strict = TRUE;
-
-  /**
-   * Returns whether the entity translation acts in strict mode.
-   *
-   * @return boolean
-   *   Whether the entity translation acts in strict mode.
-   */
-  public function getStrictMode() {
-    return $this->strict;
-  }
-
-  /**
-   * Sets whether the entity translation acts in strict mode.
-   *
-   * @param boolean $strict
-   *   Whether the entity translation acts in strict mode.
-   *
-   * @see \Drupal\Core\TypedData\TranslatableInterface::getTranslation()
-   */
-  public function setStrictMode($strict = TRUE) {
-    $this->strict = $strict;
-  }
-
-  /**
-   * Overrides \Drupal\Core\TypedData\TypedData::getValue().
-   */
-  public function getValue() {
-    // The plain value of the translation is the array of translated field
-    // objects.
-    return $this->fields;
-  }
-
-  /**
-   * Overrides \Drupal\Core\TypedData\TypedData::setValue().
-   */
-  public function setValue($values, $notify = TRUE) {
-    // Notify the parent of any changes to be made.
-    if ($notify && isset($this->parent)) {
-      $this->parent->onChange($this->name);
-    }
-    $this->fields = $values;
-  }
-
-  /**
-   * Overrides \Drupal\Core\TypedData\TypedData::getString().
-   */
-  public function getString() {
-    $strings = array();
-    foreach ($this->getProperties() as $property) {
-      $strings[] = $property->getString();
-    }
-    return implode(', ', array_filter($strings));
-  }
-
-  /**
-   * Implements \Drupal\Core\TypedData\ComplexDataInterface::get().
-   */
-  public function get($property_name) {
-    $definitions = $this->getPropertyDefinitions();
-    if (!isset($definitions[$property_name])) {
-      throw new InvalidArgumentException(format_string('Field @name is unknown or not translatable.', array('@name' => $property_name)));
-    }
-    return $this->fields[$property_name];
-  }
-
-  /**
-   * Implements \Drupal\Core\TypedData\ComplexDataInterface::set().
-   */
-  public function set($property_name, $value, $notify = TRUE) {
-    $this->get($property_name)->setValue($value, FALSE);
-  }
-
-  /**
-   * Implements \Drupal\Core\TypedData\ComplexDataInterface::getProperties().
-   */
-  public function getProperties($include_computed = FALSE) {
-    $properties = array();
-    foreach ($this->getPropertyDefinitions() as $name => $definition) {
-      if ($include_computed || empty($definition['computed'])) {
-        $properties[$name] = $this->get($name);
-      }
-    }
-    return $properties;
-  }
-
-  /**
-   * Magic method: Gets a translated field.
-   */
-  public function __get($name) {
-    return $this->get($name);
-  }
-
-  /**
-   * Magic method: Sets a translated field.
-   */
-  public function __set($name, $value) {
-    $this->get($name)->setValue($value);
-  }
-
-  /**
-   * Implements \IteratorAggregate::getIterator().
-   */
-  public function getIterator() {
-    return new ArrayIterator($this->getProperties());
-  }
-
-  /**
-   * Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinition().
-   */
-  public function getPropertyDefinition($name) {
-    $definitions = $this->getPropertyDefinitions();
-    if (isset($definitions[$name])) {
-      return $definitions[$name];
-    }
-    else {
-      return FALSE;
-    }
-  }
-
-  /**
-   * Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinitions().
-   */
-  public function getPropertyDefinitions() {
-    $definitions = array();
-    foreach ($this->parent->getPropertyDefinitions() as $name => $definition) {
-      if (!empty($definition['translatable']) || !$this->strict) {
-        $definitions[$name] = $definition;
-      }
-    }
-    return $definitions;
-  }
-
-  /**
-   * Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyValues().
-   */
-  public function getPropertyValues() {
-    return $this->getValue();
-  }
-
-  /**
-   * Implements \Drupal\Core\TypedData\ComplexDataInterface::setPropertyValues().
-   */
-  public function setPropertyValues($values) {
-    foreach ($values as $name => $value) {
-      $this->get($name)->setValue($value);
-    }
-  }
-
-  /**
-   * Implements \Drupal\Core\TypedData\ComplexDataInterface::isEmpty().
-   */
-  public function isEmpty() {
-    foreach ($this->getProperties() as $property) {
-      if ($property->getValue() !== NULL) {
-        return FALSE;
-      }
-    }
-    return TRUE;
-  }
-
-  /**
-   * Implements \Drupal\Core\TypedData\ComplexDataInterface::onChange().
-   */
-  public function onChange($property_name) {
-    // Notify the parent of changes.
-    if (isset($this->parent)) {
-      $this->parent->onChange($this->name);
-    }
-  }
-
-  /**
-   * Implements \Drupal\Core\TypedData\AccessibleInterface::access().
-   */
-  public function access($operation = 'view', AccountInterface $account = NULL) {
-    // Determine the language code of this translation by cutting of the
-    // leading "@" from the property name to get the langcode.
-    // @todo Add a way to set and get the langcode so that's more obvious what
-    // we're doing here.
-    $langcode = substr($this->getName(), 1);
-    return \Drupal::entityManager()
-      ->getAccessController($this->parent->entityType())
-      ->access($this->parent, $operation, $langcode, $account);
-  }
-}
diff --git a/core/lib/Drupal/Core/TypedData/TranslatableInterface.php b/core/lib/Drupal/Core/TypedData/TranslatableInterface.php
index f86068a..d3da690 100644
--- a/core/lib/Drupal/Core/TypedData/TranslatableInterface.php
+++ b/core/lib/Drupal/Core/TypedData/TranslatableInterface.php
@@ -13,9 +13,24 @@
 interface TranslatableInterface {
 
   /**
+   * Status code indentifying a removed translation.
+   */
+  const TRANSLATION_REMOVED = 0;
+
+  /**
+   * Status code indentifying an existing translation.
+   */
+  const TRANSLATION_EXISTING = 1;
+
+  /**
+   * Status code indentifying a newly created translation.
+   */
+  const TRANSLATION_CREATED = 2;
+
+  /**
    * Returns the default language.
    *
-   * @return
+   * @return \Drupal\Core\Language\Language
    *   The language object.
    */
   public function language();
@@ -24,12 +39,16 @@ public function language();
    * Returns the languages the data is translated to.
    *
    * @param bool $include_default
-   *   Whether the default language should be included.
+   *   (optional) Whether the default language should be included. Defaults to
+   *   TRUE.
+   * @param bool $include_removed
+   *   Whether languages referring to removed translations should be included.
+   *   Defaults to FALSE.
    *
    * @return
    *   An array of language objects, keyed by language codes.
    */
-  public function getTranslationLanguages($include_default = TRUE);
+  public function getTranslationLanguages($include_default = TRUE, $include_removed = FALSE);
 
   /**
    * Gets a translation of the data.
@@ -42,15 +61,66 @@ public function getTranslationLanguages($include_default = TRUE);
    * @param $langcode
    *   The language code of the translation to get or Language::LANGCODE_DEFAULT
    *   to get the data in default language.
-   * @param $strict
-   *   (optional) If the data is complex, whether the translation should include
-   *   only translatable properties. If set to FALSE, untranslatable properties
-   *   are included (in default language) as well as translatable properties in
-   *   the specified language. Defaults to TRUE.
    *
    * @return \Drupal\Core\TypedData\TypedDataInterface
    *   A typed data object for the translated data.
    */
-  public function getTranslation($langcode, $strict = TRUE);
+  public function getTranslation($langcode);
+
+
+  /**
+   * Returns the entity object referring to the original language.
+   *
+   * @return \Drupal\Core\TypedData\TranslatableInterface
+   */
+  public function getOriginal();
+
+  /**
+   * Returns TRUE if the entity has a translation for the given language code.
+   *
+   * @param string $langcode
+   *   The language code identifiying the translation.
+   *
+   * @return bool
+  */
+  public function hasTranslation($langcode);
+
+  /**
+   * Adds a new translation to the entity object.
+   *
+   * @param string $langcode
+   *   The language code identifying the translation.
+   * @param array $values
+   *   (optional) An array of initial values to be assigned to the translatable
+   *   field. Defaults to none.
+   *
+   * @return \Drupal\Core\TypedData\TranslatableInterface
+   */
+  public function addTranslation($langcode, array $values = array());
+
+  /**
+   * Removes the translation identified by the given language code.
+   *
+   * @param string $langcode
+   */
+  public function removeTranslation($langcode);
+
+  /**
+   * Returns the current status of the entity translation object.
+   *
+   * @return integer
+   *   A translation status code as defined in TranslatableInterface.
+   */
+  public function getTranslationStatus();
+
+  /**
+   * Marks the translation identified by the given language code as existing.
+   *
+   * @todo Remove this as soon as translation metadata have been converted to
+   *    regular fields.
+   *
+   * @param string $langcode
+   */
+  public function initTranslation($langcode);
 
 }
diff --git a/core/modules/comment/lib/Drupal/comment/CommentFormController.php b/core/modules/comment/lib/Drupal/comment/CommentFormController.php
index e2040d5..c4632ea 100644
--- a/core/modules/comment/lib/Drupal/comment/CommentFormController.php
+++ b/core/modules/comment/lib/Drupal/comment/CommentFormController.php
@@ -158,9 +158,10 @@ public function form(array $form, array &$form_state) {
     }
 
     // Add internal comment properties.
+    $original = $comment->getOriginal();
     foreach (array('cid', 'pid', 'nid', 'uid', 'node_type', 'langcode') as $key) {
       $key_name = key($comment->$key->offsetGet(0)->getPropertyDefinitions());
-      $form[$key] = array('#type' => 'value', '#value' => $comment->$key->{$key_name});
+      $form[$key] = array('#type' => 'value', '#value' => $original->$key->{$key_name});
     }
 
     return parent::form($form, $form_state, $comment);
diff --git a/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php b/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php
index cecc676..65a50c2 100644
--- a/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php
+++ b/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php
@@ -69,7 +69,7 @@ public function generate(EntityInterface $entity, FieldInstance $instance, $lang
 
     // Early-return if no editor is available.
     $formatter_id = entity_get_render_display($entity, $view_mode)->getFormatter($instance['field_name'])->getPluginId();
-    $items = $entity->getTranslation($langcode, FALSE)->get($field_name)->getValue();
+    $items = $entity->getTranslation($langcode)->get($field_name)->getValue();
     $editor_id = $this->editorSelector->getEditor($formatter_id, $instance, $items);
     if (!isset($editor_id)) {
       return array('access' => FALSE);
diff --git a/core/modules/editor/lib/Drupal/editor/EditorController.php b/core/modules/editor/lib/Drupal/editor/EditorController.php
index 2968454..22164b6 100644
--- a/core/modules/editor/lib/Drupal/editor/EditorController.php
+++ b/core/modules/editor/lib/Drupal/editor/EditorController.php
@@ -37,7 +37,7 @@ public function getUntransformedText(EntityInterface $entity, $field_name, $lang
     $response = new AjaxResponse();
 
     // Direct text editing is only supported for single-valued fields.
-    $field = $entity->getTranslation($langcode, FALSE)->$field_name;
+    $field = $entity->getTranslation($langcode)->$field_name;
     $editable_text = check_markup($field->value, $field->format, $langcode, FALSE, array(FILTER_TYPE_TRANSFORM_REVERSIBLE, FILTER_TYPE_TRANSFORM_IRREVERSIBLE));
     $response->addCommand(new GetUntransformedTextCommand($editable_text));
 
diff --git a/core/modules/field/lib/Drupal/field/Tests/FieldTestBase.php b/core/modules/field/lib/Drupal/field/Tests/FieldTestBase.php
index 949a108..1287cb0 100644
--- a/core/modules/field/lib/Drupal/field/Tests/FieldTestBase.php
+++ b/core/modules/field/lib/Drupal/field/Tests/FieldTestBase.php
@@ -52,7 +52,7 @@ function assertFieldValues(EntityInterface $entity, $field_name, $langcode, $exp
     // Re-load the entity to make sure we have the latest changes.
     entity_get_controller($entity->entityType())->resetCache(array($entity->id()));
     $e = entity_load($entity->entityType(), $entity->id());
-    $field = $values = $e->getTranslation($langcode, FALSE)->$field_name;
+    $field = $values = $e->getTranslation($langcode)->$field_name;
     // Filter out empty values so that they don't mess with the assertions.
     $field->filterEmptyValues();
     $values = $field->getValue();
diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module
index c06edde..94720e0 100644
--- a/core/modules/forum/forum.module
+++ b/core/modules/forum/forum.module
@@ -511,7 +511,7 @@ function forum_field_storage_pre_insert(EntityInterface $entity, &$skip_fields)
   if ($entity->entityType() == 'node' && $entity->status && _forum_node_check_node_type($entity)) {
     $query = db_insert('forum_index')->fields(array('nid', 'title', 'tid', 'sticky', 'created', 'comment_count', 'last_comment_timestamp'));
     foreach ($entity->getTranslationLanguages() as $langcode => $language) {
-      $translation = $entity->getTranslation($langcode, FALSE);
+      $translation = $entity->getTranslation($langcode);
       $query->values(array(
         'nid' => $entity->id(),
         'title' => $translation->title->value,
diff --git a/core/modules/node/lib/Drupal/node/NodeAccessController.php b/core/modules/node/lib/Drupal/node/NodeAccessController.php
index d7cfd2c..e36743e 100644
--- a/core/modules/node/lib/Drupal/node/NodeAccessController.php
+++ b/core/modules/node/lib/Drupal/node/NodeAccessController.php
@@ -40,8 +40,8 @@ protected function checkAccess(EntityInterface $node, $operation, $langcode, Acc
     $uid = isset($node->uid) ? $node->uid : NULL;
     // If it is a proper EntityNG object, use the proper methods.
     if ($node instanceof EntityNG) {
-      $status = $node->getTranslation($langcode, FALSE)->status->value;
-      $uid = $node->getTranslation($langcode, FALSE)->uid->value;
+      $status = $node->getTranslation($langcode)->status->value;
+      $uid = $node->getTranslation($langcode)->uid->value;
     }
 
     // Check if authors can view their own unpublished nodes.
diff --git a/core/modules/node/lib/Drupal/node/NodeStorageController.php b/core/modules/node/lib/Drupal/node/NodeStorageController.php
index 524036b..a8b4586 100644
--- a/core/modules/node/lib/Drupal/node/NodeStorageController.php
+++ b/core/modules/node/lib/Drupal/node/NodeStorageController.php
@@ -75,7 +75,7 @@ protected function attachLoad(&$queried_entities, $load_revision = FALSE) {
    * Overrides Drupal\Core\Entity\DatabaseStorageController::invokeHook().
    */
   protected function invokeHook($hook, EntityInterface $node) {
-    $node = $node->getBCEntity();
+    $node = $node->getOriginal()->getBCEntity();
 
     // Inline parent::invokeHook() to pass on BC-entities to node-specific
     // hooks.
diff --git a/core/modules/node/lib/Drupal/node/NodeTranslationController.php b/core/modules/node/lib/Drupal/node/NodeTranslationController.php
index 4531a8e..6ed7182 100644
--- a/core/modules/node/lib/Drupal/node/NodeTranslationController.php
+++ b/core/modules/node/lib/Drupal/node/NodeTranslationController.php
@@ -8,12 +8,12 @@
 namespace Drupal\node;
 
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\translation_entity\EntityTranslationController;
+use Drupal\translation_entity\EntityTranslationControllerNG;
 
 /**
  * Defines the translation controller class for nodes.
  */
-class NodeTranslationController extends EntityTranslationController {
+class NodeTranslationController extends EntityTranslationControllerNG {
 
   /**
    * Overrides EntityTranslationController::getAccess().
diff --git a/core/modules/node/node.admin.inc b/core/modules/node/node.admin.inc
index 821f41a..fb9593e 100644
--- a/core/modules/node/node.admin.inc
+++ b/core/modules/node/node.admin.inc
@@ -103,7 +103,7 @@ function _node_mass_update_helper(NodeInterface $node, array $updates, $langcode
   $node->original = clone $node;
   foreach ($langcodes as $langcode) {
     foreach ($updates as $name => $value) {
-      $node->getTranslation($langcode, FALSE)->$name = $value;
+      $node->getTranslation($langcode)->$name = $value;
     }
   }
   $node->save();
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationFormTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationFormTest.php
index 80d00b8..2fea5fc 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationFormTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationFormTest.php
@@ -20,7 +20,7 @@ class EntityTranslationFormTest extends WebTestBase {
    *
    * @var array
    */
-  public static $modules = array('entity_test', 'locale', 'node');
+  public static $modules = array('entity_test', 'language', 'node');
 
   protected $langcodes;
 
@@ -112,7 +112,7 @@ function testEntityFormLanguage() {
 
     // Create a body translation and check the form language.
     $langcode2 = $this->langcodes[1];
-    $node->body[$langcode2][0]['value'] = $this->randomName(16);
+    $node->getTranslation($langcode2)->body->value = $this->randomName(16);
     $node->save();
     $this->drupalGet($langcode2 . '/node/' . $node->nid . '/edit');
     $form_langcode = \Drupal::state()->get('entity_test.form_langcode') ?: FALSE;
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 a235add..a44c5c9 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityTranslationTest.php
@@ -7,9 +7,10 @@
 
 namespace Drupal\system\Tests\Entity;
 
-use InvalidArgumentException;
-
 use Drupal\Core\Language\Language;
+use Drupal\Core\TypedData\TranslatableInterface;
+use Exception;
+use InvalidArgumentException;
 
 /**
  * Tests entity translation.
@@ -18,7 +19,7 @@ class EntityTranslationTest extends EntityUnitTestBase {
 
   protected $langcodes;
 
-  public static $modules = array('language', 'locale');
+  public static $modules = array('language', 'entity_test');
 
   public static function getInfo() {
     return array(
@@ -30,6 +31,9 @@ public static function getInfo() {
 
   function setUp() {
     parent::setUp();
+
+    $this->state = $this->container->get('state');
+
     $this->installSchema('system', 'variable');
     $this->installSchema('language', 'language');
     $this->installSchema('entity_test', array(
@@ -46,7 +50,7 @@ function setUp() {
     entity_test_install();
 
     // Enable translations for the test entity type.
-    \Drupal::state()->set('entity_test.translation', TRUE);
+    $this->state->set('entity_test.translation', TRUE);
 
     // Create a translatable test field.
     $this->field_name = drupal_strtolower($this->randomName() . '_field_name');
@@ -161,22 +165,6 @@ protected function assertEntityLanguageMethods($entity_type) {
       $this->pass('A translation for an invalid language is NULL.');
     }
 
-    // Try to get an untranslatable value from a translation in strict mode.
-    try {
-      $field_name = 'field_test_text';
-      $value = $entity->getTranslation($this->langcodes[1])->get($field_name);
-      $this->fail(format_string('%entity_type: Getting an untranslatable value from a translation in strict mode throws an exception.', array('%entity_type' => $entity_type)));
-    }
-    catch (InvalidArgumentException $e) {
-      $this->pass(format_string('%entity_type: Getting an untranslatable value from a translation in strict mode throws an exception.', array('%entity_type' => $entity_type)));
-    }
-
-    // Try to get an untranslatable value from a translation in non-strict
-    // mode.
-    $entity->set($field_name, array(0 => array('value' => 'default value')));
-    $value = $entity->getTranslation($this->langcodes[1], FALSE)->get($field_name)->value;
-    $this->assertEqual($value, 'default value', format_string('%entity_type: Untranslated value retrieved from translation in non-strict mode.', array('%entity_type' => $entity_type)));
-
     // Try to set a value using an invalid language code.
     try {
       $entity->getTranslation('invalid')->set($this->field_name, NULL);
@@ -186,17 +174,9 @@ protected function assertEntityLanguageMethods($entity_type) {
       $this->pass(format_string('%entity_type: Setting a translation for an invalid language throws an exception.', array('%entity_type' => $entity_type)));
     }
 
-    // Try to set an untranslatable value into a translation in strict mode.
-    try {
-      $entity->getTranslation($this->langcodes[1])->set($field_name, NULL);
-      $this->fail(format_string('%entity_type: Setting an untranslatable value into a translation in strict mode throws an exception.', array('%entity_type' => $entity_type)));
-    }
-    catch (InvalidArgumentException $e) {
-      $this->pass(format_string('%entity_type: Setting an untranslatable value into a translation in strict mode throws an exception.', array('%entity_type' => $entity_type)));
-    }
-
     // Set the value in default language.
-    $entity->getTranslation($this->langcodes[1], FALSE)->set($field_name, array(0 => array('value' => 'default value2')));
+    $field_name = 'field_test_text';
+    $entity->getTranslation($this->langcodes[1])->set($field_name, array(0 => array('value' => 'default value2')));
     // Get the value.
     $this->assertEqual($entity->get($field_name)->value, 'default value2', format_string('%entity_type: Untranslated value set into a translation in non-strict mode.', array('%entity_type' => $entity_type)));
   }
@@ -345,4 +325,117 @@ protected function assertMultilingualProperties($entity_type) {
     $this->assertEqual(count($result), 1, format_string('%entity_type: One entity loaded by name, uid and field value using different language meta conditions.', array('%entity_type' => $entity_type)));
   }
 
+  /**
+   * Tests the Entity Translation API behavior.
+   */
+  function testEntityTranslationAPI() {
+    $default_langcode = $this->langcodes[0];
+    $langcode = $this->langcodes[1];
+    $entity = $this->entityManager
+      ->getStorageController('entity_test_mul')
+      ->create(array('name' => $this->randomName()));
+
+    $entity->save();
+    $hooks = $this->getHooksInfo();
+    $this->assertFalse($hooks, 'No entity translation hooks are fired when creating an entity.');
+
+    // Verify that we obtain the entity object itself when we attempt to
+    // retrieve a translation referring to it.
+    $translation = $entity->getTranslation($langcode);
+    $this->assertEqual($entity, $translation, 'The translation object corresponding to a non-default language is the entity object itself when the entity is language-neutral.');
+    $entity->langcode->value = $default_langcode;
+    $translation = $entity->getTranslation($default_langcode);
+    $this->assertEqual($entity, $translation, 'The translation object corresponding to the default language (explicit) is the entity object itself.');
+    $translation = $entity->getTranslation(Language::LANGCODE_DEFAULT);
+    $this->assertEqual($entity, $translation, 'The translation object corresponding to the default language (implicit) is the entity object itself.');
+
+    // Create a translation and verify that the translation object and the
+    // original object behave independently.
+    $name = $default_langcode . '_' . $this->randomName();
+    $entity->name->value = $name;
+    $name_translated = $langcode . '_' . $this->randomName();
+    $translation = $entity->addTranslation($langcode);
+    $this->assertNotEqual($entity, $translation, 'The entity and the translation object differ from one another.');
+    $this->assertTrue($entity->hasTranslation($langcode), 'The new translation exists.');
+    $this->assertEqual($translation->language()->langcode, $langcode, 'The translation language matches the specified one.');
+    $this->assertEqual($translation->getOriginal()->language()->langcode, $default_langcode, 'The original language can still be retrieved.');
+    $translation->name->value = $name_translated;
+    $this->assertEqual($entity->name->value, $name, 'The original name is retained after setting a translated value.');
+    $entity->name->value = $name;
+    $this->assertEqual($translation->name->value, $name_translated, 'The translated name is retained after setting the original value.');
+
+    // Save the translation and check that the expecte hooks are fired.
+    $translation->save();
+    $hooks = $this->getHooksInfo();
+    $this->assertEqual($hooks['entity_translation_insert'], $langcode, 'The generic entity translation insertion hook has fired.');
+    $this->assertEqual($hooks['entity_test_mul_translation_insert'], $langcode, 'The entity-type-specific entity translation insertion hook has fired.');
+
+    // Check that after loading an entity all the translations have the expected
+    // status.
+    $entity = $this->reloadEntity($entity);
+    $this->assertEqual($entity->language()->langcode, $default_langcode, 'The loaded entity is the original one.');
+    foreach ($entity->getTranslationLanguages() as $language) {
+      $translation = $entity->getTranslation($language->langcode);
+      $this->assertEqual($translation->getTranslationStatus(), TranslatableInterface::TRANSLATION_EXISTING, 'The translations are loaded with the correct status.');
+    }
+
+    // Add another translation and check that everything works as expected. A
+    // new translation object can be obtained also by just specifying a valid
+    // language.
+    $langcode2 = $this->langcodes[2];
+    $translation = $entity->getTranslation($langcode2);
+    $value = $entity != $translation && $translation->language()->langcode == $langcode2 && $entity->hasTranslation($langcode2);
+    $this->assertTrue($value, 'A new translation object can be obtained also by specifying a valid language.');
+    $this->assertEqual($entity->language()->langcode, $default_langcode, 'The original language has been preserved.');
+    $translation->save();
+    $hooks = $this->getHooksInfo();
+    $this->assertEqual($hooks['entity_translation_insert'], $langcode2, 'The generic entity translation insertion hook has fired.');
+    $this->assertEqual($hooks['entity_test_mul_translation_insert'], $langcode2, 'The entity-type-specific entity translation insertion hook has fired.');
+
+    // Verify that trying to manipulate a translation object referring to a
+    // removed translation results in exceptions being thrown.
+    $entity = $this->reloadEntity($entity);
+    $translation = $entity->getTranslation($langcode2);
+    $entity->removeTranslation($langcode2);
+    foreach (array('get', 'set', '__get', '__set', 'createDuplicate') as $method) {
+      $message = format_string('The @method method raises an exception when trying to manipulate a removed translation.', array('@method' => $method));
+      try {
+        $translation->{$method}('name', $this->randomName());
+        $this->fail($message);
+      }
+      catch (Exception $e) {
+        $this->pass($message);
+      }
+    }
+
+    // Verify that deletion hooks are fired when saving an entity with a removed
+    // translation.
+    $entity->save();
+    $hooks = $this->getHooksInfo();
+    $this->assertEqual($hooks['entity_translation_delete'], $langcode2, 'The generic entity translation deletion hook has fired.');
+    $this->assertEqual($hooks['entity_test_mul_translation_delete'], $langcode2, 'The entity-type-specific entity translation deletion hook has fired.');
+
+    // Verify that entity serialization does not cause stale references to be
+    // left around.
+    $entity = $this->reloadEntity($entity);
+    $translation = $entity->getTranslation($langcode);
+    $entity = unserialize(serialize($entity));
+    $entity->name->value = $this->randomName();
+    $name = $default_langcode . '_' . $this->randomName();
+    $entity->getTranslation($default_langcode)->name->value = $name;
+    $this->assertEqual($entity->name->value, $name, 'No stale reference for the translation object corresponding to the original language.');
+    $translation2 = $entity->getTranslation($langcode);
+    $translation2->name->value .= $this->randomName();
+    $this->assertNotEqual($translation->name->value, $translation2->name->value, 'No stale reference for the actual translation object.');
+    $this->assertEqual($entity, $translation2->getOriginal(), 'No stale reference in the actual translation object.');
+
+    // Verify that deep-cloning is still available when we are not instantiating
+    // a translation object, which instead relies on shallow cloning.
+    $entity = $this->reloadEntity($entity);
+    $entity->getTranslation($langcode);
+    $cloned = clone $entity;
+    $translation = $cloned->getTranslation($langcode);
+    $this->assertNotEqual($entity, $translation->getOriginal(), 'A cloned entity object has no reference to the original one.');
+  }
+
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityUnitTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityUnitTestBase.php
index 5e73dbc..4ce3d06 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityUnitTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityUnitTestBase.php
@@ -8,6 +8,7 @@
 namespace Drupal\system\Tests\Entity;
 
 use Drupal\simpletest\DrupalUnitTestBase;
+use Drupal\Core\Entity\EntityInterface;
 
 /**
  * Defines an abstract test base for entity unit tests.
@@ -21,8 +22,23 @@
    */
   public static $modules = array('entity', 'user', 'system', 'field', 'text', 'field_sql_storage', 'entity_test');
 
+  /**
+   * The entity manager service.
+   *
+   * @var \Drupal\Core\Entity\EntityManager
+   */
+  protected $entityManager;
+
+  /**
+   * The state service.
+   *
+   * @var \Drupal\Core\KeyValueStore\KeyValueStoreInterface
+   */
+  protected $state;
+
   public function setUp() {
     parent::setUp();
+    $this->entityManager = $this->container->get('plugin.manager.entity');
     $this->installSchema('user', 'users');
     $this->installSchema('system', 'sequences');
     $this->installSchema('entity_test', 'entity_test');
@@ -63,4 +79,34 @@ protected function createUser($values = array(), $permissions = array()) {
     return $account;
   }
 
+  /**
+   * Reloads the given entity from the storage and returns it.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to be reloaded.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface
+   *   The reloaded entity.
+   */
+  protected function reloadEntity(EntityInterface $entity) {
+    $ids = array($entity->id());
+    $controller = $this->entityManager->getStorageController($entity->entityType());
+    $controller->resetCache($ids);
+    $entities = $controller->load($ids);
+    return reset($entities);
+  }
+
+  /**
+   * Returns the entity_test hook invocation info recorded through the state service.
+   *
+   * @return array
+   *   An associative array of arbitrary hook data keyed by hook name.
+   */
+  protected function getHooksInfo() {
+    $key = 'entity_test.hooks';
+    $hooks = $this->state->get($key);
+    $this->state->set($key, array());
+    return $hooks;
+  }
+
 }
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index abc9f9c..44a372c 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -2244,11 +2244,6 @@ function system_data_type_info() {
       'description' => t('All kind of entities, e.g. nodes, comments or users.'),
       'class' => '\Drupal\Core\Entity\Field\Type\EntityWrapper',
     ),
-    'entity_translation' => array(
-      'label' => t('Entity translation'),
-      'description' => t('A translation of an entity'),
-      'class' => '\Drupal\Core\Entity\Field\Type\EntityTranslation',
-    ),
     'boolean_field' => array(
       'label' => t('Boolean field item'),
       'description' => t('An entity field containing a boolean value.'),
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 55d0dac..5b616ab 100644
--- a/core/modules/system/tests/modules/entity_test/entity_test.module
+++ b/core/modules/system/tests/modules/entity_test/entity_test.module
@@ -432,3 +432,48 @@ function entity_test_entity_operation_alter(array &$operations, EntityInterface
     'weight' => 50,
   );
 }
+
+/**
+ * Implements hook_entity_translation_insert().
+ */
+function entity_test_entity_translation_insert(EntityInterface $translation) {
+  _entity_test_record_hooks('entity_translation_insert', $translation->language()->langcode);
+}
+
+/**
+ * Implements hook_entity_translation_delete().
+ */
+function entity_test_entity_translation_delete(EntityInterface $translation) {
+  _entity_test_record_hooks('entity_translation_delete', $translation->language()->langcode);
+}
+
+/**
+ * Implements hook_ENTITY_TYPE_translation_insert().
+ */
+function entity_test_entity_test_mul_translation_insert(EntityInterface $translation) {
+  _entity_test_record_hooks('entity_test_mul_translation_insert', $translation->language()->langcode);
+}
+
+/**
+ * Implements hook_ENTITY_TYPE_translation_delete().
+ */
+function entity_test_entity_test_mul_translation_delete(EntityInterface $translation) {
+  _entity_test_record_hooks('entity_test_mul_translation_delete', $translation->language()->langcode);
+}
+
+/**
+ * Helper function to be used to record hook invocations.
+ *
+ * @param string $hook
+ *   The hook name.
+ *
+ * @param mixed $data
+ *   Arbitrary data associated to the hook invocation.
+ */
+function _entity_test_record_hooks($hook, $data) {
+  $state = \Drupal::state();
+  $key = 'entity_test.hooks';
+  $hooks = $state->get($key);
+  $hooks[$hook] = $data;
+  $state->set($key, $hooks);
+}
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 0ad1da0..7d27c48 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
@@ -19,15 +19,12 @@ class EntityTestFormController extends EntityFormControllerNG {
    */
   public function form(array $form, array &$form_state) {
     $form = parent::form($form, $form_state);
-
     $entity = $this->entity;
-    $langcode = $this->getFormLangcode($form_state);
-    $translation = $entity->getTranslation($langcode);
 
     $form['name'] = array(
       '#type' => 'textfield',
       '#title' => t('Name'),
-      '#default_value' => $translation->name->value,
+      '#default_value' => $entity->name->value,
       '#size' => 60,
       '#maxlength' => 128,
       '#required' => TRUE,
@@ -37,7 +34,7 @@ public function form(array $form, array &$form_state) {
     $form['user_id'] = array(
       '#type' => 'textfield',
       '#title' => 'UID',
-      '#default_value' => $translation->user_id->target_id,
+      '#default_value' => $entity->user_id->target_id,
       '#size' => 60,
       '#maxlength' => 128,
       '#required' => TRUE,
@@ -47,7 +44,7 @@ public function form(array $form, array &$form_state) {
     $form['langcode'] = array(
       '#title' => t('Language'),
       '#type' => 'language_select',
-      '#default_value' => $entity->language()->langcode,
+      '#default_value' => $entity->getOriginal()->language()->langcode,
       '#languages' => Language::STATE_ALL,
     );
 
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php
index 832a0cf..8e98c4f 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermFormController.php
@@ -47,7 +47,7 @@ public function form(array $form, array &$form_state) {
       '#type' => 'language_select',
       '#title' => t('Language'),
       '#languages' => Language::STATE_ALL,
-      '#default_value' => $term->langcode->value,
+      '#default_value' => $term->getOriginal()->language()->langcode,
       '#access' => !is_null($language_configuration['language_show']) && $language_configuration['language_show'],
     );
 
diff --git a/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationController.php b/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationController.php
index 1fb68e5..d6885b1 100644
--- a/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationController.php
+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationController.php
@@ -126,7 +126,7 @@ public function getSourceLangcode(array $form_state) {
   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;
+    $entity_langcode = $entity->getOriginal()->language()->langcode;
     $source_langcode = $this->getSourceLangcode($form_state);
 
     $new_translation = !empty($source_langcode);
@@ -144,7 +144,7 @@ public function entityFormAlter(array &$form, array &$form_state, EntityInterfac
     if (isset($languages[$form_langcode]) && ($has_translations || $new_translation)) {
       $title = $this->entityFormTitle($entity);
       // When editing the original values display just the entity label.
-      if ($form_langcode != $entity->language()->langcode) {
+      if ($form_langcode != $entity_langcode) {
         $t_args = array('%language' => $languages[$form_langcode]->name, '%title' => $entity->label());
         $title = empty($source_langcode) ? $title . ' [' . t('%language translation', $t_args) . ']' : t('Create %language translation of %title', $t_args);
       }
@@ -433,10 +433,9 @@ public function entityFormEntityBuild($entity_type, EntityInterface $entity, arr
     }
 
     // Set contextual information that can be reused during the storage phase.
-    // @todo Remove this once we have an EntityLanguageDecorator to deal with
-    //   the active language.
-    $attributes = drupal_container()->get('request')->attributes;
-    $attributes->set('working_langcode', $form_langcode);
+    // @todo Remove this once translation metadata is converted to regular
+    //   fields.
+    $attributes = \Drupal::request()->attributes;
     $attributes->set('source_langcode', $source_langcode);
   }
 
diff --git a/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationControllerNG.php b/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationControllerNG.php
index fd007cd..2243466 100644
--- a/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationControllerNG.php
+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationControllerNG.php
@@ -25,10 +25,7 @@ public function getAccess(EntityInterface $entity, $op) {
    * Overrides \Drupal\translation_entity\EntityTranslationControllerInterface::removeTranslation().
    */
   public function removeTranslation(EntityInterface $entity, $langcode) {
-    $translation = $entity->getTranslation($langcode);
-    foreach ($translation->getPropertyDefinitions() as $property_name => $langcode) {
-      $translation->$property_name = array();
-    }
+    $entity->removeTranslation($langcode);
   }
 
 }
diff --git a/core/modules/translation_entity/lib/Drupal/translation_entity/FieldTranslationSynchronizer.php b/core/modules/translation_entity/lib/Drupal/translation_entity/FieldTranslationSynchronizer.php
index 39d1a21..632124f 100644
--- a/core/modules/translation_entity/lib/Drupal/translation_entity/FieldTranslationSynchronizer.php
+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/FieldTranslationSynchronizer.php
@@ -48,14 +48,14 @@ public function synchronizeFields(EntityInterface $entity, $sync_langcode, $orig
     // If we have no information about what to sync to, if we are creating a new
     // entity, if we have no translations for the current entity and we are not
     // creating one, then there is nothing to synchronize.
-    if (empty($sync_langcode) || $entity->isNew() || (count($translations) < 2 && !$original_langcode)) {
+    if (empty($sync_langcode) || $entity->isNew() || count($translations) < 2) {
       return;
     }
 
     // If the entity language is being changed there is nothing to synchronize.
     $entity_type = $entity->entityType();
     $entity_unchanged = isset($entity->original) ? $entity->original : $this->entityManager->getStorageController($entity_type)->loadUnchanged($entity->id());
-    if ($entity->language()->langcode != $entity_unchanged->language()->langcode) {
+    if ($entity->getOriginal()->language()->langcode != $entity_unchanged->getOriginal()->language()->langcode) {
       return;
     }
 
diff --git a/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSyncImageTest.php b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSyncImageTest.php
index 6e9b82e..4be8830 100644
--- a/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSyncImageTest.php
+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationSyncImageTest.php
@@ -87,7 +87,6 @@ function testImageFieldSync() {
 
     // Populate the required contextual values.
     $attributes = $this->container->get('request')->attributes;
-    $attributes->set('working_langcode', $langcode);
     $attributes->set('source_langcode', $default_langcode);
 
     // Populate the test entity with some random initial values.
@@ -123,7 +122,7 @@ function testImageFieldSync() {
         'alt' => $default_langcode . '_' . $fid . '_' . $this->randomName(),
         'title' => $default_langcode . '_' . $fid . '_' . $this->randomName(),
       );
-      $entity->{$this->fieldName}->offsetGet($delta)->setValue($item);
+      $entity->get($this->fieldName)->offsetGet($delta)->setValue($item);
 
       // Store the generated values keying them by fid for easier lookup.
       $values[$default_langcode][$fid] = $item;
@@ -134,6 +133,7 @@ function testImageFieldSync() {
     // items will be one less than the original values to check that only the
     // translated ones will be preserved. In fact we want the same fids and
     // items order for both languages.
+    $translation = $entity->getTranslation($langcode);
     for ($delta = 0; $delta < $this->cardinality - 1; $delta++) {
       // Simulate a field reordering: items are shifted of one position ahead.
       // The modulo operator ensures we start from the beginning after reaching
@@ -148,26 +148,27 @@ function testImageFieldSync() {
         'alt' => $langcode . '_' . $fid . '_' . $this->randomName(),
         'title' => $langcode . '_' . $fid . '_' . $this->randomName(),
       );
-      $entity->getTranslation($langcode)->{$this->fieldName}->offsetGet($delta)->setValue($item);
+      $translation->get($this->fieldName)->offsetGet($delta)->setValue($item);
 
       // Again store the generated values keying them by fid for easier lookup.
       $values[$langcode][$fid] = $item;
     }
 
     // Perform synchronization: the translation language is used as source,
-    // while the default langauge is used as target.
-    $entity = $this->saveEntity($entity);
+    // while the default language is used as target.
+    $entity = $this->saveEntity($translation);
+    $translation = $entity->getTranslation($langcode);
 
     // Check that one value has been dropped from the original values.
-    $assert = count($entity->{$this->fieldName}) == 2;
+    $assert = count($entity->get($this->fieldName)) == 2;
     $this->assertTrue($assert, 'One item correctly removed from the synchronized field values.');
 
     // Check that fids have been synchronized and translatable column values
     // have been retained.
     $fids = array();
-    foreach ($entity->{$this->fieldName} as $delta => $item) {
+    foreach ($entity->get($this->fieldName) as $delta => $item) {
       $value = $values[$default_langcode][$item->fid];
-      $source_item = $entity->getTranslation($langcode)->{$this->fieldName}->offsetGet($delta);
+      $source_item = $translation->get($this->fieldName)->offsetGet($delta);
       $assert = $item->fid == $source_item->fid && $item->alt == $value['alt'] && $item->title == $value['title'];
       $this->assertTrue($assert, format_string('Field item @fid has been successfully synchronized.', array('@fid' => $item->fid)));
       $fids[$item->fid] = TRUE;
@@ -183,22 +184,23 @@ function testImageFieldSync() {
       'alt' => $langcode . '_' . $removed_fid . '_' . $this->randomName(),
       'title' => $langcode . '_' . $removed_fid . '_' . $this->randomName(),
     );
-    $entity->getTranslation($langcode)->{$this->fieldName}->setValue(array_values($values[$langcode]));
+    $translation->get($this->fieldName)->setValue(array_values($values[$langcode]));
     // When updating an entity we do not have a source language defined.
     $attributes->remove('source_langcode');
-    $entity = $this->saveEntity($entity);
+    $entity = $this->saveEntity($translation);
+    $translation = $entity->getTranslation($langcode);
 
     // Check that the value has been added to the default language.
-    $assert = count($entity->{$this->fieldName}->getValue()) == 3;
+    $assert = count($entity->get($this->fieldName)->getValue()) == 3;
     $this->assertTrue($assert, 'One item correctly added to the synchronized field values.');
 
-    foreach ($entity->{$this->fieldName} as $delta => $item) {
+    foreach ($entity->get($this->fieldName) as $delta => $item) {
       // When adding an item its value is copied over all the target languages,
       // thus in this case the source language needs to be used to check the
       // values instead of the target one.
       $fid_langcode = $item->fid != $removed_fid ? $default_langcode : $langcode;
       $value = $values[$fid_langcode][$item->fid];
-      $source_item = $entity->getTranslation($langcode)->{$this->fieldName}->offsetGet($delta);
+      $source_item = $translation->get($this->fieldName)->offsetGet($delta);
       $assert = $item->fid == $source_item->fid && $item->alt == $value['alt'] && $item->title == $value['title'];
       $this->assertTrue($assert, format_string('Field item @fid has been successfully synchronized.', array('@fid' => $item->fid)));
     }
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
index 50e8bbd..57c39ce 100644
--- a/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationUITest.php
+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationUITest.php
@@ -10,7 +10,6 @@
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityNG;
 use Drupal\Core\Language\Language;
-use Drupal\Core\TypedData\ComplexDataInterface;
 
 /**
  * Tests the Entity Translation UI.
@@ -261,13 +260,13 @@ protected function getFormSubmitAction(EntityInterface $entity) {
   protected function getTranslation(EntityInterface $entity, $langcode) {
     // @todo remove once EntityBCDecorator is gone.
     $entity = $entity->getNGEntity();
-    return $entity instanceof EntityNG ? $entity->getTranslation($langcode, FALSE) : $entity;
+    return $entity instanceof EntityNG ? $entity->getTranslation($langcode) : $entity;
   }
 
   /**
    * Returns the value for the specified property in the given language.
    *
-   * @param \Drupal\Core\TypedData\TranslatableInterface $translation
+   * @param \Drupal\Core\Entity\EntityInterface $translation
    *   The translation object the property value should be retrieved from.
    * @param string $property
    *   The property name.
@@ -277,7 +276,7 @@ protected function getTranslation(EntityInterface $entity, $langcode) {
    * @return
    *   The property value.
    */
-  protected function getValue(ComplexDataInterface $translation, $property, $langcode) {
+  protected function getValue(EntityInterface $translation, $property, $langcode) {
     $key = $property == 'user_id' ? 'target_id' : 'value';
     // @todo remove EntityBCDecorator condition once EntityBCDecorator is gone.
     if (($translation instanceof EntityInterface) && !($translation instanceof EntityNG) && !($translation instanceof EntityBCDecorator)) {
diff --git a/core/modules/translation_entity/translation_entity.module b/core/modules/translation_entity/translation_entity.module
index dec0460..b636016 100644
--- a/core/modules/translation_entity/translation_entity.module
+++ b/core/modules/translation_entity/translation_entity.module
@@ -279,7 +279,7 @@ function _translation_entity_menu_strip_loaders($path) {
  */
 function translation_entity_translate_access(EntityInterface $entity) {
   $entity_type = $entity->entityType();
-  return empty($entity->language()->locked) && language_multilingual() && $entity->isTranslatable() &&
+  return empty($entity->getOriginal()->language()->locked) && language_multilingual() && $entity->isTranslatable() &&
     (user_access('create entity translations') || user_access('update entity translations') || user_access('delete entity translations'));
 }
 
@@ -332,7 +332,7 @@ function translation_entity_edit_access(EntityInterface $entity, Language $langu
   $language = !empty($language) ? $language : language(Language::TYPE_CONTENT);
   $translations = $entity->getTranslationLanguages();
   $languages = language_list();
-  return isset($languages[$language->langcode]) && $language->langcode != $entity->language()->langcode && isset($translations[$language->langcode]) && translation_entity_access($entity, 'update');
+  return isset($languages[$language->langcode]) && $language->langcode != $entity->getOriginal()->language()->langcode && isset($translations[$language->langcode]) && translation_entity_access($entity, 'update');
 }
 
 /**
@@ -348,7 +348,7 @@ function translation_entity_delete_access(EntityInterface $entity, Language $lan
   $language = !empty($language) ? $language : language(Language::TYPE_CONTENT);
   $translations = $entity->getTranslationLanguages();
   $languages = language_list();
-  return isset($languages[$language->langcode]) && $language->langcode != $entity->language()->langcode && isset($translations[$language->langcode]) && translation_entity_access($entity, 'delete');
+  return isset($languages[$language->langcode]) && $language->langcode != $entity->getOriginal()->language()->langcode && isset($translations[$language->langcode]) && translation_entity_access($entity, 'delete');
 }
 
 /**
@@ -645,7 +645,7 @@ function translation_entity_field_language_alter(&$display_language, $context) {
     $instances = field_info_instances($entity_type, $entity->bundle());
     // Avoid altering the real entity.
     $entity = clone($entity);
-    $entity_langcode = $entity->language()->langcode;
+    $entity_langcode = $entity->getOriginal()->language()->langcode;
 
     foreach ($entity->translation as $langcode => $translation) {
       if ($langcode == $context['langcode'] || !translation_entity_view_access($entity, $langcode)) {
@@ -705,7 +705,11 @@ function translation_entity_load_translation_metadata(array $entities, $entity_t
     // @todo Declare these as entity (translation?) properties.
     foreach ($record as $field_name => $value) {
       if (!in_array($field_name, $exclude)) {
-        $entity->translation[$record->langcode][$field_name] = $value;
+        $langcode = $record->langcode;
+        $entity->translation[$langcode][$field_name] = $value;
+        if (!$entity->hasTranslation($langcode)) {
+          $entity->initTranslation($langcode);
+        }
       }
     }
   }
@@ -866,8 +870,10 @@ function translation_entity_field_info_alter(&$info) {
  */
 function translation_entity_field_attach_presave(EntityInterface $entity) {
   if ($entity->isTranslatable()) {
-    $attributes = drupal_container()->get('request')->attributes;
-    Drupal::service('translation_entity.synchronizer')->synchronizeFields($entity, $attributes->get('working_langcode'), $attributes->get('source_langcode'));
+    // @todo Avoid using request attributes once translation metadata become
+    //   regular fields.
+    $attributes = Drupal::request()->attributes;
+    Drupal::service('translation_entity.synchronizer')->synchronizeFields($entity, $entity->language()->langcode, $attributes->get('source_langcode'));
   }
 }
 
diff --git a/core/modules/translation_entity/translation_entity.pages.inc b/core/modules/translation_entity/translation_entity.pages.inc
index cd212eb..9797fff 100644
--- a/core/modules/translation_entity/translation_entity.pages.inc
+++ b/core/modules/translation_entity/translation_entity.pages.inc
@@ -19,7 +19,7 @@ function translation_entity_overview(EntityInterface $entity) {
   $controller = translation_entity_controller($entity->entityType());
   $entity_manager = Drupal::entityManager();
   $languages = language_list();
-  $original = $entity->language()->langcode;
+  $original = $entity->getOriginal()->language()->langcode;
   $translations = $entity->getTranslationLanguages();
   $field_ui = module_exists('field_ui') && user_access('administer ' . $entity->entityType() . ' fields');
 
@@ -238,19 +238,13 @@ function translation_entity_edit_page(EntityInterface $entity, Language $languag
  */
 function translation_entity_prepare_translation(EntityInterface $entity, Language $source, Language $target) {
   // @todo Unify field and property handling.
-  $instances = field_info_instances($entity->entityType(), $entity->bundle());
   $entity = $entity->getNGEntity();
   if ($entity instanceof EntityNG) {
     $source_translation = $entity->getTranslation($source->langcode);
-    $target_translation = $entity->getTranslation($target->langcode);
-    foreach ($target_translation->getPropertyDefinitions() as $property_name => $definition) {
-      // @todo The "key" part should not be needed. Remove it as soon as things
-      // do not break.
-      $key = key($entity->{$property_name}[0]->getProperties());
-      $target_translation->$property_name->{$key} = $source_translation->$property_name->{$key};
-    }
+    $entity->addTranslation($target->langcode, $source_translation->getPropertyValues());
   }
   else {
+    $instances = field_info_instances($entity->entityType(), $entity->bundle());
     foreach ($instances as $field_name => $instance) {
       $field = field_info_field($field_name);
       if (!empty($field['translatable'])) {
diff --git a/core/modules/user/lib/Drupal/user/ProfileTranslationController.php b/core/modules/user/lib/Drupal/user/ProfileTranslationController.php
index c1b2414..59a34f9 100644
--- a/core/modules/user/lib/Drupal/user/ProfileTranslationController.php
+++ b/core/modules/user/lib/Drupal/user/ProfileTranslationController.php
@@ -8,12 +8,12 @@
 namespace Drupal\user;
 
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\translation_entity\EntityTranslationController;
+use Drupal\translation_entity\EntityTranslationControllerNG;
 
 /**
  * Defines the translation controller class for terms.
  */
-class ProfileTranslationController extends EntityTranslationController {
+class ProfileTranslationController extends EntityTranslationControllerNG {
 
   /**
    * Overrides EntityTranslationController::entityFormAlter().
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php b/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php
index c83e5f7..adf8749 100644
--- a/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php
+++ b/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php
@@ -923,15 +923,16 @@ public function getExportProperties() {
   /**
    * Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslation().
    */
-  public function getTranslation($langcode, $strict = TRUE) {
-    return $this->storage->getTranslation($langcode, $strict);
+  public function getTranslation($langcode) {
+    // @todo Revisit this once config entities are converted to NG.
+    return $this;
   }
 
   /**
    * Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslationLanguages().
    */
-  public function getTranslationLanguages($include_default = TRUE) {
-    return $this->storage->getTranslationLanguages($include_default);
+  public function getTranslationLanguages($include_default = TRUE, $include_removed = FALSE) {
+    return $this->storage->getTranslationLanguages($include_default, $include_removed);
   }
 
   /**
@@ -1047,6 +1048,48 @@ public function isTranslatable() {
   }
 
   /**
+   * Implements \Drupal\Core\TypedData\TranslatableInterface::getOriginal().
+   */
+  public function getOriginal() {
+    return $this->storage->getOriginal();
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\TranslatableInterface::hasTranslation().
+   */
+  public function hasTranslation($langcode) {
+    return $this->storage->hasTranslation($langcode);
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\TranslatableInterface::addTranslation().
+   */
+  public function addTranslation($langcode, array $values = array()) {
+    return $this->storage->addTranslation($langcode, $values);
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\TranslatableInterface::removeTranslation().
+   */
+  public function removeTranslation($langcode) {
+    $this->storage->removeTranslation($langcode);
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslationStatus().
+   */
+  public function getTranslationStatus() {
+    return $this->storage->getTranslationStatus();
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslationStatus().
+   */
+  public function initTranslation($langcode) {
+    $this->storage->initTranslation($langcode);
+  }
+
+  /**
    * Implements \Drupal\Core\TypedData\TypedDataInterface::getType().
    */
   public function getType() {
