diff --git a/core/includes/entity.api.php b/core/includes/entity.api.php
index 2219d40..8b97b74 100644
--- a/core/includes/entity.api.php
+++ b/core/includes/entity.api.php
@@ -241,6 +241,35 @@ 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->getUntranslated()->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.
+ */
+function hook_entity_translation_delete(\Drupal\Core\Entity\EntityInterface $translation) {
+  $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 2f20916..bb0cf4c 100644
--- a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
+++ b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
@@ -14,7 +14,6 @@
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\DatabaseStorageController;
 use Drupal\Core\Entity\EntityStorageException;
-use Drupal\Core\TypedData\ComplexDataInterface;
 use Drupal\Component\Uuid\Uuid;
 use Drupal\Core\Database\Connection;
 
@@ -289,6 +288,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'])));
       }
@@ -302,6 +302,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
@@ -317,7 +318,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]));
       }
     }
   }
@@ -367,6 +368,9 @@ public function save(EntityInterface $entity) {
         $entity->postSave($this, TRUE);
         $this->invokeFieldMethod('update', $entity);
         $this->invokeHook('update', $entity);
+        if ($this->dataTable) {
+          $this->notifyTranslationChanges($entity);
+        }
       }
       else {
         $return = drupal_write_record($this->entityInfo['base_table'], $record);
@@ -412,7 +416,7 @@ public function save(EntityInterface $entity) {
    */
   protected function saveRevision(EntityInterface $entity) {
     $return = $entity->id();
-    $default_langcode = $entity->language()->id;
+    $default_langcode = $entity->getUntranslated()->language()->id;
 
     if (!$entity->isNewRevision()) {
       // Delete to handle removed values.
@@ -422,9 +426,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;
@@ -486,6 +490,28 @@ protected function savePropertyData(EntityInterface $entity) {
   }
 
   /**
+   * Checks translation statuses and invoke the related hooks if needed.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity being saved.
+   */
+  function notifyTranslationChanges(EntityInterface $entity) {
+    $translations = $entity->getTranslationLanguages(FALSE);
+    $original_translations = $entity->original->getTranslationLanguages(FALSE);
+    $all_translations = array_keys($translations + $original_translations);
+
+    // Notify modules of translation insertion/deletion.
+    foreach ($all_translations as $langcode) {
+      if (isset($translations[$langcode]) && !isset($original_translations[$langcode])) {
+        $this->invokeHook('translation_insert', $entity->getTranslation($langcode));
+      }
+      elseif (!isset($translations[$langcode]) && isset($original_translations[$langcode])) {
+        $this->invokeHook('translation_delete', $entity->getTranslation($langcode));
+      }
+    }
+  }
+
+  /**
    * Maps from an entity object to the storage record of the base table.
    *
    * @param \Drupal\Core\Entity\EntityInterface $entity
@@ -511,7 +537,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) {
@@ -534,10 +560,10 @@ protected function mapToRevisionStorageRecord(ComplexDataInterface $entity) {
    *   The record to store.
    */
   protected function mapToDataStorageRecord(EntityInterface $entity, $langcode) {
-    $default_langcode = $entity->language()->id;
+    $default_langcode = $entity->getUntranslated()->language()->id;
     // 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 54051a6..7712bf1 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;
   }
 
   /**
@@ -588,4 +592,46 @@ public static function postLoad(EntityStorageControllerInterface $storage_contro
   public function preSaveRevision(EntityStorageControllerInterface $storage_controller, \stdClass $record) {
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getUntranslated() {
+    return $this->getTranslation(Language::LANGCODE_DEFAULT);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasTranslation($langcode) {
+    $translations = $this->getTranslationLanguages();
+    return isset($translations[$langcode]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addTranslation($langcode, array $values = array()) {
+    // @todo Config entities do not support entity translation hence we need to
+    //   move the TranslatableInterface implementation to EntityNG. See
+    //   http://drupal.org/node/2004244
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function removeTranslation($langcode) {
+    // @todo Config entities do not support entity translation hence we need to
+    //   move the TranslatableInterface implementation to EntityNG. See
+    //   http://drupal.org/node/2004244
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function initTranslation($langcode) {
+    // @todo Config entities do not support entity translation hence we need to
+    //   move the TranslatableInterface implementation to EntityNG. See
+    //   http://drupal.org/node/2004244
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityBCDecorator.php b/core/lib/Drupal/Core/Entity/EntityBCDecorator.php
index e51f121..52d3f6f 100644
--- a/core/lib/Drupal/Core/Entity/EntityBCDecorator.php
+++ b/core/lib/Drupal/Core/Entity/EntityBCDecorator.php
@@ -138,7 +138,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()->id;
+      $langcode = $this->decorated->getUntranslated()->language()->id;
       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];
@@ -424,8 +424,8 @@ public function getTranslationLanguages($include_default = TRUE) {
   /**
    * 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);
   }
 
   /**
@@ -526,12 +526,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.
@@ -588,4 +582,47 @@ 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 getUntranslated() {
+    return $this->decorated->getUntranslated();
+  }
+
+  /**
+   * 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 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 9ad27f5..36fb318 100644
--- a/core/lib/Drupal/Core/Entity/EntityFormController.php
+++ b/core/lib/Drupal/Core/Entity/EntityFormController.php
@@ -146,6 +146,11 @@ protected function init(array &$form_state) {
     // module-provided form handlers there.
     $form_state['controller'] = $this;
 
+    // Ensure we act on the translation object corresponding to the current form
+    // language.
+    $this->entity = $this->getTranslatedEntity($form_state);
+
+    // Prepare the entity to be presented in the entity form.
     $this->prepareEntity();
 
     $form_display = entity_get_render_form_display($this->entity, $this->getOperation());
@@ -188,7 +193,7 @@ public function form(array $form, array &$form_state) {
       // new entities.
       $form['langcode'] = array(
         '#type' => 'value',
-        '#value' => !$entity->isNew() ? $entity->langcode : language_default()->id,
+        '#value' => !$entity->isNew() ? $entity->getUntranslated()->language()->id : language_default()->id,
       );
     }
     return $form;
@@ -393,7 +398,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'];
@@ -402,6 +406,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)->id;
       $fallback = language_multilingual() ? language_fallback_get_candidates() : array();
       while (!empty($langcode) && !isset($translations[$langcode])) {
@@ -411,14 +416,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()->id;
+    return !empty($langcode) ? $langcode : $entity->getUntranslated()->language()->id;
   }
 
   /**
    * Implements \Drupal\Core\Entity\EntityFormControllerInterface::isDefaultFormLangcode().
    */
   public function isDefaultFormLangcode(array $form_state) {
-    return $this->getFormLangcode($form_state) == $this->entity->language()->id;
+    return $this->getFormLangcode($form_state) == $this->entity->getUntranslated()->language()->id;
   }
 
   /**
@@ -447,13 +452,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()->id == $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'];
@@ -489,11 +492,23 @@ public function buildEntity(array $form, array &$form_state) {
    * Implements \Drupal\Core\Entity\EntityFormControllerInterface::getEntity().
    */
   public function getEntity() {
-    // @todo Pick the proper translation object based on the form language here.
     return $this->entity;
   }
 
   /**
+   * Returns the translation object corresponding to the form language.
+   *
+   * @param array $form_state
+   *   A keyed array containing the current state of the form.
+   */
+  protected function getTranslatedEntity(array $form_state) {
+    $langcode = $this->getFormLangcode($form_state);
+    $translation = $this->entity->getTranslation($langcode);
+    // Ensure that the entity object is a BC entity if the original one is.
+    return $this->entity instanceof EntityBCDecorator ? $translation->getBCEntity() : $translation;
+  }
+
+  /**
    * Implements \Drupal\Core\Entity\EntityFormControllerInterface::setEntity().
    */
   public function setEntity(EntityInterface $entity) {
@@ -521,7 +536,7 @@ protected function prepareInvokeAll($hook, array &$form_state) {
       if (function_exists($function)) {
         // Ensure we pass an updated translation object and form display at
         // each invocation, since they depend on form state which is alterable.
-        $args = array($this->getEntity(), $this->getFormDisplay($form_state), $this->operation, &$form_state);
+        $args = array($this->getTranslatedEntity($form_state), $this->getFormDisplay($form_state), $this->operation, &$form_state);
         call_user_func_array($function, $args);
       }
     }
diff --git a/core/lib/Drupal/Core/Entity/EntityFormControllerNG.php b/core/lib/Drupal/Core/Entity/EntityFormControllerNG.php
index 111511e..3b064f6 100644
--- a/core/lib/Drupal/Core/Entity/EntityFormControllerNG.php
+++ b/core/lib/Drupal/Core/Entity/EntityFormControllerNG.php
@@ -61,11 +61,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/EntityInterface.php b/core/lib/Drupal/Core/Entity/EntityInterface.php
index b8fe82b..cdb4cd3 100644
--- a/core/lib/Drupal/Core/Entity/EntityInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityInterface.php
@@ -323,4 +323,15 @@ public function getNGEntity();
    */
   public function isTranslatable();
 
+  /**
+   * 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
+   *   The language code identifying the translation to be initialized.
+   */
+  public function initTranslation($langcode);
+
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityNG.php b/core/lib/Drupal/Core/Entity/EntityNG.php
index c3687a0..ef8e7f8 100644
--- a/core/lib/Drupal/Core/Entity/EntityNG.php
+++ b/core/lib/Drupal/Core/Entity/EntityNG.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Entity;
 
 use Drupal\Core\Language\Language;
+use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\TypedData\TypedDataInterface;
 use ArrayIterator;
 use InvalidArgumentException;
@@ -26,6 +27,21 @@
 class EntityNG extends Entity {
 
   /**
+   * Status code indentifying a removed translation.
+   */
+  protected static $TRANSLATION_REMOVED = 0;
+
+  /**
+   * Status code indentifying an existing translation.
+   */
+  protected static $TRANSLATION_EXISTING = 1;
+
+  /**
+   * Status code indentifying a newly created translation.
+   */
+  protected static $TRANSLATION_CREATED = 2;
+
+  /**
    * Local cache holding the value of the bundle field.
    *
    * @var string
@@ -68,6 +84,13 @@ class EntityNG extends Entity {
   protected $language;
 
   /**
+   * Local cache for the available language objects.
+   *
+   * @var array
+   */
+  protected $languages;
+
+  /**
    * Local cache for field definitions.
    *
    * @see EntityNG::getPropertyDefinitions()
@@ -84,11 +107,41 @@ 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 bool
+   */
+  protected $translationInitialize = 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;
+    $this->languages = language_list(Language::STATE_ALL);
+
     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.
@@ -97,6 +150,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' => self::$TRANSLATION_EXISTING);
+    $this->translations[Language::LANGCODE_DEFAULT] = $data;
+    if ($translations) {
+      $default_langcode = $this->language()->id;
+      foreach ($translations as $langcode) {
+        if ($langcode != $default_langcode && $langcode != Language::LANGCODE_DEFAULT) {
+          $this->translations[$langcode] = $data;
+        }
+      }
+    }
+
     $this->init();
   }
 
@@ -118,10 +185,23 @@ protected function init() {
   }
 
   /**
+   * Clear entity translation object cache to remove stale references.
+   */
+  protected function clearTranslationCache() {
+    foreach ($this->translations as &$translation) {
+      unset($translation['entity']);
+    }
+  }
+
+  /**
    * Magic __wakeup() implementation.
    */
   public function __wakeup() {
     $this->init();
+    // @todo This should be done before serializing the entity, but we would
+    //   need to provide the full list of data to be serialized. See the
+    //   dedicated issue at https://drupal.org/node/2027795.
+    $this->clearTranslationCache();
   }
 
   /**
@@ -204,12 +284,10 @@ 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);
+    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];
   }
 
   /**
@@ -218,6 +296,10 @@ public function get($property_name) {
    * @return \Drupal\Core\Entity\Field\FieldInterface
    */
   protected function getTranslatedField($property_name, $langcode) {
+    if ($this->translations[$this->activeLangcode]['status'] == self::$TRANSLATION_REMOVED) {
+      $message = 'The entity object refers to a removed translation (@langcode) and cannot be manipulated.';
+      throw new \InvalidArgumentException(format_string($message, array('@langcode' => $this->activeLangcode)));
+    }
     // Populate $this->fields to speed-up further look-ups and to keep track of
     // fields objects, possibly holding changes to field values.
     if (!isset($this->fields[$property_name][$langcode])) {
@@ -236,9 +318,9 @@ protected function getTranslatedField($property_name, $langcode) {
           $value = $this->values[$property_name][$langcode];
         }
         // @todo Remove this once the BC decorator is gone.
-        elseif ($property_name != 'langcode') {
+        elseif ($property_name != 'langcode' && $langcode == Language::LANGCODE_DEFAULT) {
           $default_langcode = $this->language()->id;
-          if ($langcode == Language::LANGCODE_DEFAULT && isset($this->values[$property_name][$default_langcode])) {
+          if (isset($this->values[$property_name][$default_langcode])) {
             $value = $this->values[$property_name][$default_langcode];
           }
         }
@@ -337,15 +419,42 @@ public function isEmpty() {
   }
 
   /**
-   * Implements \Drupal\Core\TypedData\TranslatableInterface::language().
+   * {@inheritdoc}
+   */
+  public function access($operation = 'view', AccountInterface $account = NULL) {
+    return \Drupal::entityManager()
+      ->getAccessController($this->entityType)
+      ->access($this, $operation, $this->activeLangcode, $account);
+  }
+
+  /**
+   * {@inheritdoc}
    */
   public function language() {
+    if ($this->activeLangcode != Language::LANGCODE_DEFAULT) {
+      if (!isset($this->languages[$this->activeLangcode])) {
+        $this->languages += language_list(Language::STATE_ALL);
+      }
+      return $this->languages[$this->activeLangcode];
+    }
+    else {
+      return $this->language ?: $this->getDefaultLanguage();
+    }
+  }
+
+  /**
+   * Returns the entity original language.
+   *
+   * @return \Drupal\Core\Language\Language
+   *   A language object.
+   */
+  protected function getDefaultLanguage() {
     // Keep a local cache of the language object and clear it if the langcode
     // gets changed, see EntityNG::onChange().
     if (!isset($this->language)) {
       // Get the language code if the property exists.
-      if ($this->getPropertyDefinition('langcode')) {
-        $this->language = $this->get('langcode')->language;
+      if ($this->getPropertyDefinition('langcode') && ($item = $this->get('langcode')) && isset($item->language)) {
+        $this->language = $item->language;
       }
       if (empty($this->language)) {
         // Make sure we return a proper language object.
@@ -369,77 +478,182 @@ public function onChange($property_name) {
   /**
    * Implements \Drupal\Core\TypedData\TranslatableInterface::getTranslation().
    *
-   * @return \Drupal\Core\Entity\Plugin\DataType\EntityTranslation
-   */
-  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()->id, array(Language::LANGCODE_NOT_SPECIFIED, $langcode))) {
-      // No translation needed, return the entity.
-      return $this;
+   * @return \Drupal\Core\Entity\EntityInterface
+   */
+  public function getTranslation($langcode) {
+    // Ensure we always use the default language code when dealing with the
+    // original entity language.
+    if ($langcode != Language::LANGCODE_DEFAULT) {
+      $default_language = $this->language ?: $this->getDefaultLanguage();
+      if ($langcode == $default_language->id) {
+        $langcode = Language::LANGCODE_DEFAULT;
+      }
     }
-    // Check whether the language code is valid, thus is of an available
-    // language.
-    $languages = language_list(Language::STATE_ALL);
-    if (!isset($languages[$langcode])) {
-      throw new InvalidArgumentException("Unable to get translation for the invalid language '$langcode'.");
+
+    // Populate entity translation object cache so it will be available for all
+    // translation objects.
+    if ($langcode == $this->activeLangcode) {
+      $this->translations[$langcode]['entity'] = $this;
     }
-    $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);
+
+    // If we already have a translation object for the specified language we can
+    // just return it.
+    if (isset($this->translations[$langcode]['entity'])) {
+      $translation = $this->translations[$langcode]['entity'];
+    }
+    else {
+      if (isset($this->translations[$langcode])) {
+        $translation = $this->initializeTranslation($langcode);
+        $this->translations[$langcode]['entity'] = $translation;
       }
+      else {
+        // If we were given a valid language and there is no translation for it,
+        // we return a new one.
+        $languages = language_list(Language::STATE_ALL);
+        if (isset($languages[$langcode])) {
+          // If the entity or the requested language  is not a configured
+          // language, we fall back to the entity itself, since in this case it
+          // cannot have translations.
+          $translation = empty($this->getDefaultLanguage()->locked) && empty($languages[$langcode]->locked) ? $this->addTranslation($langcode) : $this;
+        }
+      }
+    }
+
+    if (empty($translation)) {
+      $message = 'Invalid translation language (@langcode) specified.';
+      throw new \InvalidArgumentException(format_string($message, array('@langcode' => $langcode)));
     }
-    // @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 ($definitions as $name => $definition) {
-      if (isset($this->fields[$name])) {
-        foreach ($this->fields[$name] as $langcode => $field) {
-          if (!$field->isEmpty()) {
-            $translations[$langcode] = TRUE;
-          }
-        }
+  public function getUntranslated() {
+    $langcode = Language::LANGCODE_DEFAULT;
+    return isset($this->translations[$langcode]['entity']) ? $this->translations[$langcode]['entity'] : $this->getTranslation($langcode);
+  }
+
+  /**
+   * Instantiates a translation object for an existing translation.
+   *
+   * The translated entity will be a clone of the current entity with the
+   * specified $langcode. All translations share the same field data structures
+   * to ensure that all of them deal with fresh data.
+   *
+   * @param string $langcode
+   *   The language code for the requested translation.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface
+   *   The translation object. The content properties of the translation object
+   *   are stored as references to the main entity.
+   */
+  protected function initializeTranslation($langcode) {
+    // If the requested translation is valid, clone it with the current language
+    // as the active language. The $translationInitialize flag triggers a
+    // shallow (non-recursive) clone.
+    $this->translationInitialize = TRUE;
+    $translation = clone $this;
+    $this->translationInitialize = FALSE;
+
+    $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->translationInitialize = FALSE;
+
+    return $translation;
+  }
+
+  /**
+   * {@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]) || $this->hasTranslation($langcode)) {
+      $message = 'Invalid translation language (@langcode) specified.';
+      throw new \InvalidArgumentException(format_string($message, array('@langcode' => $langcode)));
+    }
+
+    // Instantiate a new empty entity so default values will be populated in the
+    // specified language.
+    $info = $this->entityInfo();
+    $default_values = array($info['entity_keys']['bundle'] => $this->bundle, 'langcode' => $langcode);
+    $entity = \Drupal::entityManager()
+      ->getStorageController($this->entityType())
+      ->create($default_values);
+
+    foreach ($entity as $name => $field) {
+      if (!isset($values[$name]) && !$field->isEmpty()) {
+        $values[$name] = $field->value;
       }
-      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($definition['translatable']) && !(isset($this->fields[$name][$langcode]) && $this->fields[$name][$langcode]->isEmpty())) {
-            $translations[$langcode] = TRUE;
-          }
+    }
+
+    $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;
+      }
+    }
+
+    return $translation;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function removeTranslation($langcode) {
+    if (isset($this->translations[$langcode]) && $langcode != Language::LANGCODE_DEFAULT && $langcode != $this->getDefaultLanguage()->id) {
+      foreach ($this->getPropertyDefinitions() as $name => $definition) {
+        if (!empty($definition['translatable'])) {
+          unset($this->values[$langcode]);
+          unset($this->fields[$langcode]);
         }
       }
+      $this->translations[$langcode]['status'] = self::$TRANSLATION_REMOVED;
+    }
+    else {
+      $message = 'The specified translation (@langcode) cannot be removed.';
+      throw new \InvalidArgumentException(format_string($message, array('@langcode' => $langcode)));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function initTranslation($langcode) {
+    if ($langcode != Language::LANGCODE_DEFAULT && $langcode != $this->getDefaultLanguage()->id) {
+      $this->translations[$langcode]['status'] = self::$TRANSLATION_EXISTING;
     }
-    // We include the default language code instead of the
-    // Language::LANGCODE_DEFAULT constant.
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTranslationLanguages($include_default = TRUE) {
+    $translations = array_filter($this->translations, function($translation) { return $translation['status']; });
     unset($translations[Language::LANGCODE_DEFAULT]);
 
     if ($include_default) {
-      $translations[$this->language()->id] = TRUE;
+      $langcode = $this->getDefaultLanguage()->id;
+      $translations[$langcode] = TRUE;
     }
+
     // Now load language objects based upon translation langcodes.
     return array_intersect_key(language_list(Language::STATE_ALL), $translations);
   }
@@ -491,15 +705,15 @@ public function updateOriginalValues() {
   public function &__get($name) {
     // 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.
@@ -527,11 +741,11 @@ public function __set($name, $value) {
     }
     // 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.
@@ -568,6 +782,11 @@ public function __unset($name) {
    * Overrides Entity::createDuplicate().
    */
   public function createDuplicate() {
+    if ($this->translations[$this->activeLangcode]['status'] == self::$TRANSLATION_REMOVED) {
+      $message = 'The entity object refers to a removed translation (@langcode) and cannot be manipulated.';
+      throw new \InvalidArgumentException(format_string($message, array('@langcode' => $this->activeLangcode)));
+    }
+
     $duplicate = clone $this;
     $entity_info = $this->entityInfo();
     $duplicate->{$entity_info['entity_keys']['id']}->value = NULL;
@@ -591,11 +810,16 @@ public function createDuplicate() {
   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->translationInitialize) {
+      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();
     }
   }
 
@@ -605,6 +829,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);
     }
@@ -621,4 +848,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/Plugin/DataType/EntityTranslation.php b/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityTranslation.php
deleted file mode 100644
index 8a39126..0000000
--- a/core/lib/Drupal/Core/Entity/Plugin/DataType/EntityTranslation.php
+++ /dev/null
@@ -1,231 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\Entity\Plugin\DataType\EntityTranslation.
- */
-
-namespace Drupal\Core\Entity\Plugin\DataType;
-
-use Drupal\Core\TypedData\Annotation\DataType;
-use Drupal\Core\Annotation\Translation;
-use Drupal\Core\Session\AccountInterface;
-use Drupal\Core\TypedData\AccessibleInterface;
-use Drupal\Core\TypedData\ComplexDataInterface;
-use Drupal\Core\TypedData\TypedData;
-use ArrayIterator;
-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.
- *
- * @DataType(
- *   id = "entity_translation",
- *   label = @Translation("Entity translation"),
- *   description = @Translation("A translation of an entity.")
- * )
- */
-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..0d5332b 100644
--- a/core/lib/Drupal/Core/TypedData/TranslatableInterface.php
+++ b/core/lib/Drupal/Core/TypedData/TranslatableInterface.php
@@ -15,7 +15,7 @@
   /**
    * Returns the default language.
    *
-   * @return
+   * @return \Drupal\Core\Language\Language
    *   The language object.
    */
   public function language();
@@ -24,7 +24,8 @@ 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.
    *
    * @return
    *   An array of language objects, keyed by language codes.
@@ -42,15 +43,51 @@ 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 translatable object referring to the original language.
+   *
+   * @return \Drupal\Core\TypedData\TranslatableInterface
+   *   The translation object referring to the original language.
+   */
+  public function getUntranslated();
+
+  /**
+   * Returns TRUE there is a translation for the given language code.
+   *
+   * @param string $langcode
+   *   The language code identifiying the translation.
+   *
+   * @return bool
+   *   TRUE if the translation exists, FALSE otherwise.
+  */
+  public function hasTranslation($langcode);
+
+  /**
+   * Adds a new translation to the translatable 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
+   *   fields. 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
+   *   The language code identifying the translation to be removed.
+   */
+  public function removeTranslation($langcode);
 
 }
diff --git a/core/modules/comment/lib/Drupal/comment/CommentFormController.php b/core/modules/comment/lib/Drupal/comment/CommentFormController.php
index 2de2ed5..0584ae3 100644
--- a/core/modules/comment/lib/Drupal/comment/CommentFormController.php
+++ b/core/modules/comment/lib/Drupal/comment/CommentFormController.php
@@ -162,9 +162,10 @@ public function form(array $form, array &$form_state) {
     }
 
     // Add internal comment properties.
+    $original = $comment->getUntranslated();
     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/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module
index 84fbed5..cbd33e8 100644
--- a/core/modules/content_translation/content_translation.module
+++ b/core/modules/content_translation/content_translation.module
@@ -279,7 +279,7 @@ function _content_translation_menu_strip_loaders($path) {
  */
 function content_translation_translate_access(EntityInterface $entity) {
   $entity_type = $entity->entityType();
-  return empty($entity->language()->locked) && language_multilingual() && $entity->isTranslatable() &&
+  return empty($entity->getUntranslated()->language()->locked) && language_multilingual() && $entity->isTranslatable() &&
     (user_access('create content translations') || user_access('update content translations') || user_access('delete content translations'));
 }
 
@@ -332,7 +332,7 @@ function content_translation_edit_access(EntityInterface $entity, Language $lang
   $language = !empty($language) ? $language : language(Language::TYPE_CONTENT);
   $translations = $entity->getTranslationLanguages();
   $languages = language_list();
-  return isset($languages[$language->id]) && $language->id != $entity->language()->id && isset($translations[$language->id]) && content_translation_access($entity, 'update');
+  return isset($languages[$language->id]) && $language->id != $entity->getUntranslated()->language()->id && isset($translations[$language->id]) && content_translation_access($entity, 'update');
 }
 
 /**
@@ -348,7 +348,7 @@ function content_translation_delete_access(EntityInterface $entity, Language $la
   $language = !empty($language) ? $language : language(Language::TYPE_CONTENT);
   $translations = $entity->getTranslationLanguages();
   $languages = language_list();
-  return isset($languages[$language->id]) && $language->id != $entity->language()->id && isset($translations[$language->id]) && content_translation_access($entity, 'delete');
+  return isset($languages[$language->id]) && $language->id != $entity->getUntranslated()->language()->id && isset($translations[$language->id]) && content_translation_access($entity, 'delete');
 }
 
 /**
@@ -645,7 +645,7 @@ function content_translation_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()->id;
+    $entity_langcode = $entity->getUntranslated()->language()->id;
 
     foreach ($entity->translation as $langcode => $translation) {
       if ($langcode == $context['langcode'] || !content_translation_view_access($entity, $langcode)) {
@@ -705,7 +705,11 @@ function content_translation_load_translation_metadata(array $entities, $entity_
     // @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);
+        }
       }
     }
   }
@@ -865,10 +869,11 @@ function content_translation_field_info_alter(&$info) {
  * Implements hook_entity_presave().
  */
 function content_translation_entity_presave(EntityInterface $entity) {
-  $entity_info = $entity->entityInfo();
-  if ($entity->isTranslatable() && !empty($entity_info['fieldable'])) {
+  if ($entity->isTranslatable()) {
+    // @todo Avoid using request attributes once translation metadata become
+    //   regular fields.
     $attributes = Drupal::request()->attributes;
-    Drupal::service('content_translation.synchronizer')->synchronizeFields($entity, $attributes->get('working_langcode'), $attributes->get('source_langcode'));
+    Drupal::service('content_translation.synchronizer')->synchronizeFields($entity, $entity->language()->id, $attributes->get('source_langcode'));
   }
 }
 
diff --git a/core/modules/content_translation/content_translation.pages.inc b/core/modules/content_translation/content_translation.pages.inc
index ec03b45..e277791 100644
--- a/core/modules/content_translation/content_translation.pages.inc
+++ b/core/modules/content_translation/content_translation.pages.inc
@@ -19,7 +19,7 @@ function content_translation_overview(EntityInterface $entity) {
   $controller = content_translation_controller($entity->entityType());
   $entity_manager = Drupal::entityManager();
   $languages = language_list();
-  $original = $entity->language()->id;
+  $original = $entity->getUntranslated()->language()->id;
   $translations = $entity->getTranslationLanguages();
   $field_ui = module_exists('field_ui') && user_access('administer ' . $entity->entityType() . ' fields');
 
@@ -238,19 +238,13 @@ function content_translation_edit_page(EntityInterface $entity, Language $langua
  */
 function content_translation_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->id);
-    $target_translation = $entity->getTranslation($target->id);
-    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->id, $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/content_translation/lib/Drupal/content_translation/ContentTranslationController.php b/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationController.php
index b437ddc..cf58f6c 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationController.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationController.php
@@ -119,7 +119,7 @@ public function getSourceLangcode(array $form_state) {
   public function entityFormAlter(array &$form, array &$form_state, EntityInterface $entity) {
     $form_controller = content_translation_form_controller($form_state);
     $form_langcode = $form_controller->getFormLangcode($form_state);
-    $entity_langcode = $entity->language()->id;
+    $entity_langcode = $entity->getUntranslated()->language()->id;
     $source_langcode = $this->getSourceLangcode($form_state);
 
     $new_translation = !empty($source_langcode);
@@ -137,7 +137,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()->id) {
+      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);
       }
@@ -426,10 +426,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.
+    // @todo Remove this once translation metadata is converted to regular
+    //   fields.
     $attributes = \Drupal::request()->attributes;
-    $attributes->set('working_langcode', $form_langcode);
     $attributes->set('source_langcode', $source_langcode);
   }
 
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationControllerNG.php b/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationControllerNG.php
index 6f7f4f0..61e490d 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationControllerNG.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/ContentTranslationControllerNG.php
@@ -18,10 +18,7 @@ class ContentTranslationControllerNG extends ContentTranslationController {
    * Overrides \Drupal\content_translation\ContentTranslationControllerInterface::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/content_translation/lib/Drupal/content_translation/FieldTranslationSynchronizer.php b/core/modules/content_translation/lib/Drupal/content_translation/FieldTranslationSynchronizer.php
index b1902f8..9fd4b0e 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/FieldTranslationSynchronizer.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/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()->id != $entity_unchanged->language()->id) {
+    if ($entity->getUntranslated()->language()->id != $entity_unchanged->getUntranslated()->language()->id) {
       return;
     }
 
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationSyncImageTest.php b/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationSyncImageTest.php
index 568ee74..c9bf33d 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationSyncImageTest.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationSyncImageTest.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.
@@ -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,15 +148,16 @@ function testImageFieldSync() {
         'alt' => $langcode . '_' . $fid . '_' . $this->randomName(),
         'title' => $langcode . '_' . $fid . '_' . $this->randomName(),
       );
-      $entity->getTranslation($langcode)->{$this->fieldName}->offsetGet($delta)->setValue($item);
+      $translation->{$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;
@@ -167,7 +168,7 @@ function testImageFieldSync() {
     $fids = array();
     foreach ($entity->{$this->fieldName} as $delta => $item) {
       $value = $values[$default_langcode][$item->target_id];
-      $source_item = $entity->getTranslation($langcode)->{$this->fieldName}->offsetGet($delta);
+      $source_item = $translation->{$this->fieldName}->offsetGet($delta);
       $assert = $item->target_id == $source_item->target_id && $item->alt == $value['alt'] && $item->title == $value['title'];
       $this->assertTrue($assert, format_string('Field item @fid has been successfully synchronized.', array('@fid' => $item->target_id)));
       $fids[$item->target_id] = TRUE;
@@ -183,10 +184,11 @@ 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->{$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;
@@ -198,7 +200,7 @@ function testImageFieldSync() {
       // values instead of the target one.
       $fid_langcode = $item->target_id != $removed_fid ? $default_langcode : $langcode;
       $value = $values[$fid_langcode][$item->target_id];
-      $source_item = $entity->getTranslation($langcode)->{$this->fieldName}->offsetGet($delta);
+      $source_item = $translation->{$this->fieldName}->offsetGet($delta);
       $assert = $item->target_id == $source_item->target_id && $item->alt == $value['alt'] && $item->title == $value['title'];
       $this->assertTrue($assert, format_string('Field item @fid has been successfully synchronized.', array('@fid' => $item->target_id)));
     }
diff --git a/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationUITest.php b/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationUITest.php
index 6ffad4f..94006ea 100644
--- a/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationUITest.php
+++ b/core/modules/content_translation/lib/Drupal/content_translation/Tests/ContentTranslationUITest.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 Content 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/edit/lib/Drupal/edit/MetadataGenerator.php b/core/modules/edit/lib/Drupal/edit/MetadataGenerator.php
index 8930ace..4736b1a 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)->getRenderer($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 93bbe52..6be24b6 100644
--- a/core/modules/editor/lib/Drupal/editor/EditorController.php
+++ b/core/modules/editor/lib/Drupal/editor/EditorController.php
@@ -42,7 +42,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/field.module b/core/modules/field/field.module
index ce9c5fa..ad11478 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -235,9 +235,7 @@ function field_system_info_alter(&$info, $file, $type) {
 function field_entity_create(EntityInterface $entity) {
   $info = $entity->entityInfo();
   if (!empty($info['fieldable'])) {
-    foreach ($entity->getTranslationLanguages() as $langcode => $language) {
-      field_populate_default_values($entity, $langcode);
-    }
+    field_populate_default_values($entity, $entity->language()->id);
   }
 }
 
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/field/lib/Drupal/field/Tests/TranslationWebTest.php b/core/modules/field/lib/Drupal/field/Tests/TranslationWebTest.php
index 8fdf064..df93f12 100644
--- a/core/modules/field/lib/Drupal/field/Tests/TranslationWebTest.php
+++ b/core/modules/field/lib/Drupal/field/Tests/TranslationWebTest.php
@@ -106,6 +106,7 @@ function testFieldFormTranslationRevisions() {
     $entity = entity_create($this->entity_type, array());
     $available_langcodes = array_flip(field_available_languages($this->entity_type, $this->field));
     unset($available_langcodes[Language::LANGCODE_NOT_SPECIFIED]);
+    unset($available_langcodes[Language::LANGCODE_NOT_APPLICABLE]);
     $field_name = $this->field['field_name'];
 
     // Store the field translations.
@@ -116,11 +117,11 @@ function testFieldFormTranslationRevisions() {
     $entity->save();
 
     // Create a new revision.
-    $langcode = field_valid_language(NULL);
+    $langcode = $entity->language()->id;
     $edit = array(
       'user_id' => 1,
       'name' => $this->randomName(),
-      "{$field_name}[$langcode][0][value]" => $entity->getTranslation($langcode)->{$field_name}->value,
+      "{$field_name}[$langcode][0][value]" => $entity->{$field_name}->value,
       'revision' => TRUE,
     );
     $this->drupalPost($this->entity_type . '/manage/' . $entity->id() . '/edit', $edit, t('Save'));
diff --git a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Tests/FieldSqlStorageTest.php b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Tests/FieldSqlStorageTest.php
index 737daf3..e62b777 100644
--- a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Tests/FieldSqlStorageTest.php
+++ b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Tests/FieldSqlStorageTest.php
@@ -321,7 +321,6 @@ function testFieldAttachSaveMissingData() {
 
     // Update: Field translation is missing but field is not empty. Translation
     // data should survive.
-    $entity->getTranslation($unavailable_langcode)->{$this->field_name} = mt_rand(1, 127);
     unset($entity->{$this->field_name});
     field_attach_update($entity);
     $count = db_select($this->table)
diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module
index 11a1054..5f7c9d2 100644
--- a/core/modules/forum/forum.module
+++ b/core/modules/forum/forum.module
@@ -497,7 +497,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..c3ccc3c 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->getUntranslated()->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 ac063a0..4df3946 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\content_translation\ContentTranslationController;
+use Drupal\content_translation\ContentTranslationControllerNG;
 
 /**
  * Defines the translation controller class for nodes.
  */
-class NodeTranslationController extends ContentTranslationController {
+class NodeTranslationController extends ContentTranslationControllerNG {
 
   /**
    * Overrides ContentTranslationController::entityFormAlter().
diff --git a/core/modules/node/node.admin.inc b/core/modules/node/node.admin.inc
index c2b8524..1d80c2e 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 908cb11..ef6c81a 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 e0c3189..6bd3228 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,9 @@
 
 namespace Drupal\system\Tests\Entity;
 
-use InvalidArgumentException;
-
 use Drupal\Core\Language\Language;
+use Drupal\Core\TypedData\TranslatableInterface;
+use InvalidArgumentException;
 
 /**
  * Tests entity translation.
@@ -18,7 +18,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 +30,7 @@ public static function getInfo() {
 
   function setUp() {
     parent::setUp();
+
     $this->installSchema('system', 'variable');
     $this->installSchema('entity_test', array(
       'entity_test_mul',
@@ -46,7 +47,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 +162,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 +171,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 +322,145 @@ 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()->id, $langcode, 'The translation language matches the specified one.');
+    $this->assertEqual($translation->getUntranslated()->language()->id, $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 the language is the default one.
+    $entity = $this->reloadEntity($entity);
+    $this->assertEqual($entity->language()->id, $default_langcode, 'The loaded entity is the original one.');
+
+    // 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()->id == $langcode2 && $entity->hasTranslation($langcode2);
+    $this->assertTrue($value, 'A new translation object can be obtained also by specifying a valid language.');
+    $this->assertEqual($entity->language()->id, $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.');
+    $entity = $this->reloadEntity($entity);
+    $this->assertFalse($entity->hasTranslation($langcode2), 'The translation does not appear among available translations after saving the entity.');
+
+    // Check that removing an invalid translation causes an exception to be
+    // thrown.
+    foreach (array($default_langcode, Language::LANGCODE_DEFAULT, $this->randomName()) as $invalid_langcode) {
+      $message = format_string('Removing an invalid translation (@langcode) causes an exception to be thrown.', array('@langcode' => $invalid_langcode));
+      try {
+        $entity->removeTranslation($invalid_langcode);
+        $this->fail($message);
+      }
+      catch (\Exception $e) {
+        $this->pass($message);
+      }
+    }
+
+    // Check that hooks are fired only when actually storing data.
+    $entity = $this->reloadEntity($entity);
+    $entity->addTranslation($langcode2);
+    $entity->removeTranslation($langcode2);
+    $entity->save();
+    $hooks = $this->getHooksInfo();
+    $this->assertFalse($hooks, 'No hooks are run when adding and removing a translation without storing it.');
+
+    // 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->getUntranslated(), '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->getUntranslated(), 'A cloned entity object has no reference to the original one.');
+
+    // Check that per-language defaults are properly populated.
+    $entity = $this->reloadEntity($entity);
+    $instance_id = implode('.', array($entity->entityType(), $entity->bundle(), $this->field_name));
+    $instances = $this->entityManager->getStorageController('field_instance')->load(array($instance_id));
+    $instance = reset($instances);
+    $instance['default_value_function'] = 'entity_test_field_default_value';
+    $instance->save();
+    $translation = $entity->addTranslation($langcode2);
+    $this->assertEqual($translation->get($this->field_name)->value, $this->field_name . '_' . $langcode2, 'Language-aware default values correctly populated.');
+  }
+
 }
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 664b09c..9ac9cff 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,26 @@
    */
   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->state = $this->container->get('state');
+
     $this->installSchema('user', 'users');
     $this->installSchema('system', 'sequences');
     $this->installSchema('entity_test', 'entity_test');
@@ -62,4 +81,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.
+   *
+   * @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/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module
index 55d0dac..264787e 100644
--- a/core/modules/system/tests/modules/entity_test/entity_test.module
+++ b/core/modules/system/tests/modules/entity_test/entity_test.module
@@ -7,6 +7,8 @@
 
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\entity\Plugin\Core\Entity\EntityFormDisplay;
+use Drupal\field\Plugin\Core\Entity\Field;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
 
 /**
  * Filter that limits test entity list to revisable ones.
@@ -432,3 +434,63 @@ 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()->id);
+}
+
+/**
+ * Implements hook_entity_translation_delete().
+ */
+function entity_test_entity_translation_delete(EntityInterface $translation) {
+  _entity_test_record_hooks('entity_translation_delete', $translation->language()->id);
+}
+
+/**
+ * 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()->id);
+}
+
+/**
+ * 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()->id);
+}
+
+/**
+ * Field default value callback.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ *   The entity the field belongs to.
+ * @param \Drupal\field\Plugin\Core\Entity\Field $field
+ *   The field for which default values should be provided.
+ * @param \Drupal\field\Plugin\Core\Entity\FieldInstance $instance
+ *   The field instance for which default values should be provided.
+ * @param string $langcode
+ *   The field language code to fill-in with the default value.
+ */
+function entity_test_field_default_value(EntityInterface $entity, Field $field, FieldInstance $instance, $langcode) {
+  return array(array('value' => $field['field_name'] . '_' . $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 a05d00b..1dbb859 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()->id,
+      '#default_value' => $entity->getUntranslated()->language()->id,
       '#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..4f4043a 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->getUntranslated()->language()->id,
       '#access' => !is_null($language_configuration['language_show']) && $language_configuration['language_show'],
     );
 
diff --git a/core/modules/user/lib/Drupal/user/ProfileTranslationController.php b/core/modules/user/lib/Drupal/user/ProfileTranslationController.php
index 5cb9cc8..3392754 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\content_translation\ContentTranslationController;
+use Drupal\content_translation\ContentTranslationControllerNG;
 
 /**
  * Defines the translation controller class for terms.
  */
-class ProfileTranslationController extends ContentTranslationController {
+class ProfileTranslationController extends ContentTranslationControllerNG {
 
   /**
    * Overrides ContentTranslationController::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 15f0ef5..92fa27b 100644
--- a/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php
+++ b/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php
@@ -921,8 +921,9 @@ 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;
   }
 
   /**
@@ -1045,6 +1046,41 @@ public function isTranslatable() {
   }
 
   /**
+   * {@inheritdoc}
+   */
+  public function getUntranslated() {
+    return $this->storage->getUntranslated();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasTranslation($langcode) {
+    return $this->storage->hasTranslation($langcode);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addTranslation($langcode, array $values = array()) {
+    return $this->storage->addTranslation($langcode, $values);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function removeTranslation($langcode) {
+    $this->storage->removeTranslation($langcode);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function initTranslation($langcode) {
+    $this->storage->initTranslation($langcode);
+  }
+
+  /**
    * Implements \Drupal\Core\TypedData\TypedDataInterface::getType().
    */
   public function getType() {
