diff --git a/core/modules/block_content/src/Tests/BlockContentTranslationUITest.php b/core/modules/block_content/src/Tests/BlockContentTranslationUITest.php
index 74fc537..800c10b 100644
--- a/core/modules/block_content/src/Tests/BlockContentTranslationUITest.php
+++ b/core/modules/block_content/src/Tests/BlockContentTranslationUITest.php
@@ -154,11 +154,9 @@ public function testDisabledBundle() {
     $enabled_block_content = $this->createBlockContent();
     $disabled_block_content = $this->createBlockContent(FALSE, $bundle->id());
 
-    // Make sure that only a single row was inserted into the
-    // {content_translation} table.
-    $rows = db_query('SELECT * FROM {content_translation}')->fetchAll();
+    // Make sure that only a single row was inserted into the block table.
+    $rows = db_query('SELECT * FROM {block_content_field_data} WHERE id = :id', array(':id' => $enabled_block_content->id()))->fetchAll();
     $this->assertEqual(1, count($rows));
-    $this->assertEqual($enabled_block_content->id(), reset($rows)->entity_id);
   }
 
 }
diff --git a/core/modules/comment/src/CommentTranslationHandler.php b/core/modules/comment/src/CommentTranslationHandler.php
index aa796b1..e9a538b 100644
--- a/core/modules/comment/src/CommentTranslationHandler.php
+++ b/core/modules/comment/src/CommentTranslationHandler.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\content_translation\ContentTranslationHandler;
+use Drupal\Core\Form\FormStateInterface;
 
 /**
  * Defines the translation handler for comments.
@@ -18,8 +19,36 @@ class CommentTranslationHandler extends ContentTranslationHandler {
   /**
    * {@inheritdoc}
    */
+  public function entityFormAlter(array &$form, FormStateInterface $form_state, EntityInterface $entity) {
+    parent::entityFormAlter($form, $form_state, $entity);
+
+    if (isset($form['content_translation'])) {
+      // We do not need to show these values on comment forms: they inherit the
+      // basic comment property values.
+      $form['content_translation']['status']['#access'] = FALSE;
+      $form['content_translation']['name']['#access'] = FALSE;
+      $form['content_translation']['created']['#access'] = FALSE;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   protected function entityFormTitle(EntityInterface $entity) {
     return t('Edit comment @subject', array('@subject' => $entity->label()));
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function entityFormEntityBuild($entity_type, EntityInterface $entity, array $form, FormStateInterface $form_state) {
+    if ($form_state->hasValue('content_translation')) {
+      $translation = &$form_state->getValue('content_translation');
+      /** @var \Drupal\comment\CommentInterface $entity */
+      $translation['status'] = $entity->isPublished();
+      $translation['name'] = $entity->getAuthorName();
+    }
+    parent::entityFormEntityBuild($entity_type, $entity, $form, $form_state);
+  }
+
 }
diff --git a/core/modules/comment/src/Tests/CommentTranslationUITest.php b/core/modules/comment/src/Tests/CommentTranslationUITest.php
index 64afbd7..50a48c4 100644
--- a/core/modules/comment/src/Tests/CommentTranslationUITest.php
+++ b/core/modules/comment/src/Tests/CommentTranslationUITest.php
@@ -119,31 +119,58 @@ protected function getNewEntityValues($langcode) {
   }
 
   /**
-   * Overrides \Drupal\content_translation\Tests\ContentTranslationUITest::assertPublishedStatus().
+   * {@inheritdoc}
    */
-  protected function assertPublishedStatus() {
-    parent::assertPublishedStatus();
-    $entity = entity_load($this->entityTypeId, $this->entityId);
-    $user = $this->drupalCreateUser(array('access comments'));
-    $this->drupalLogin($user);
-    $languages = $this->container->get('language_manager')->getLanguages();
+  protected function doTestPublishedStatus() {
+    $entity_manager = \Drupal::entityManager();
+    $storage = $entity_manager->getStorage($this->entityTypeId);
+
+    $storage->resetCache();
+    $entity = $storage->load($this->entityId);
+    $path = $entity->getSystemPath('edit-form');
 
-    // Check that simple users cannot see unpublished field translations.
-    $path = $entity->getSystemPath();
+    // Unpublish translations.
     foreach ($this->langcodes as $index => $langcode) {
-      $translation = $this->getTranslation($entity, $langcode);
-      $value = $this->getValue($translation, 'comment_body', $langcode);
-      $this->drupalGet($path, array('language' => $languages[$langcode]));
       if ($index > 0) {
-        $this->assertNoRaw($value, 'Unpublished field translation is not shown.');
-      }
-      else {
-        $this->assertRaw($value, 'Published field translation is shown.');
+        $edit = array('status' => 0);
+        $this->drupalPostForm($langcode . '/' . $path, $edit, $this->getFormSubmitAction($entity, $langcode));
+        $storage->resetCache();
+        $entity = $storage->load($this->entityId);
+        $this->assertFalse($this->manager->getTranslationMetadata($entity->getTranslation($langcode))->isPublished(), 'The translation has been correctly unpublished.');
       }
     }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function doTestAuthoringInfo() {
+    $entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
+    $path = $entity->getSystemPath('edit-form');
+    $languages = $this->container->get('language_manager')->getLanguages();
+    $values = array();
+
+    // Post different authoring information for each translation.
+    foreach ($this->langcodes as $langcode) {
+      $user = $this->drupalCreateUser();
+      $values[$langcode] = array(
+        'uid' => $user->id(),
+        'created' => REQUEST_TIME - mt_rand(0, 1000),
+      );
+      $edit = array(
+        'name' => $user->getUsername(),
+        'date[date]' => format_date($values[$langcode]['created'], 'custom', 'Y-m-d'),
+        'date[time]' => format_date($values[$langcode]['created'], 'custom', 'H:i:s'),
+      );
+      $this->drupalPostForm($path, $edit, $this->getFormSubmitAction($entity, $langcode), array('language' => $languages[$langcode]));
+    }
 
-    // Login as translator again to ensure subsequent tests do not break.
-    $this->drupalLogin($this->translator);
+    $entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
+    foreach ($this->langcodes as $langcode) {
+      $metadata = $this->manager->getTranslationMetadata($entity->getTranslation($langcode));
+      $this->assertEqual($metadata->getAuthor()->id(), $values[$langcode]['uid'], 'Translation author correctly stored.');
+      $this->assertEqual($metadata->getCreatedTime(), $values[$langcode]['created'], 'Translation date correctly stored.');
+    }
   }
 
   /**
diff --git a/core/modules/content_translation/content_translation.admin.inc b/core/modules/content_translation/content_translation.admin.inc
index d812652..21ff011 100644
--- a/core/modules/content_translation/content_translation.admin.inc
+++ b/core/modules/content_translation/content_translation.admin.inc
@@ -97,7 +97,8 @@ function _content_translation_form_language_content_settings_form_alter(array &$
       if ($fields) {
         foreach ($fields as $field_name => $definition) {
           // Allow to configure only fields supporting multilingual storage.
-          if (!empty($storage_definitions[$field_name]) && $storage_definitions[$field_name]->isTranslatable()) {
+          // We skip our own fields as they are always translatable.
+          if (!empty($storage_definitions[$field_name]) && $storage_definitions[$field_name]->isTranslatable() && $storage_definitions[$field_name]->getProvider() != 'content_translation') {
             $form['settings'][$entity_type_id][$bundle]['fields'][$field_name] = array(
               '#label' => $definition->getLabel(),
               '#type' => 'checkbox',
@@ -336,6 +337,25 @@ function content_translation_form_language_content_settings_submit(array $form,
   }
 
   // Ensure entity and menu router information are correctly rebuilt.
-  \Drupal::entityManager()->clearCachedDefinitions();
+  $entity_manager = \Drupal::entityManager();
+  $entity_manager->clearCachedDefinitions();
   \Drupal::service('router.builder_indicator')->setRebuildNeeded();
+
+  // Handle field storage definition creation, if needed.
+  // @todo Generalize this code in https://www.drupal.org/node/2346013.
+  // @todo Handle initial values in https://www.drupal.org/node/2346019.
+  if (\Drupal::service('entity.definition_update_manager')->needsUpdates()) {
+    foreach ($entity_types as $entity_type_id => $entity_type) {
+      $storage_definitions = $entity_manager->getFieldStorageDefinitions($entity_type_id);
+      $installed_storage_definitions = $entity_manager->getLastInstalledFieldStorageDefinitions($entity_type_id);
+      foreach (array_diff_key($storage_definitions, $installed_storage_definitions) as $storage_definition) {
+        /** @var $storage_definition \Drupal\Core\Field\FieldStorageDefinitionInterface */
+        if ($storage_definition->getProvider() == 'content_translation') {
+          $entity_manager->onFieldStorageDefinitionCreate($storage_definition);
+        }
+      }
+    }
+  }
+
+  drupal_set_message(t('Settings successfully updated.'));
 }
diff --git a/core/modules/content_translation/content_translation.install b/core/modules/content_translation/content_translation.install
index 34d2717..cd8f8f0 100644
--- a/core/modules/content_translation/content_translation.install
+++ b/core/modules/content_translation/content_translation.install
@@ -5,82 +5,10 @@
  * Installation functions for Content Translation module.
  */
 
-use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
 
 /**
- * Implements hook_schema().
- */
-function content_translation_schema() {
-  $schema['content_translation'] = array(
-    'description' => 'Table to track content translations',
-    'fields' => array(
-      'entity_type' => array(
-        'type' => 'varchar',
-        'length' => EntityTypeInterface::ID_MAX_LENGTH,
-        'not null' => TRUE,
-        'default' => '',
-        'description' => 'The entity type this translation relates to',
-      ),
-      'entity_id' => array(
-        'type' => 'varchar',
-        'length' => 128,
-        'not null' => TRUE,
-        'default' => '',
-        'description' => 'The entity id this translation relates to',
-      ),
-      'langcode' => array(
-        'type' => 'varchar',
-        'length' => 32,
-        'not null' => TRUE,
-        'default' => '',
-        'description' => 'The target language for this translation.',
-      ),
-      'source' => array(
-        'type' => 'varchar',
-        'length' => 32,
-        'not null' => TRUE,
-        'default' => '',
-        'description' => 'The source language from which this translation was created.',
-      ),
-      'outdated' => array(
-        'description' => 'A boolean indicating whether this translation needs to be updated.',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'uid' => array(
-        'description' => 'The author of this translation.',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'status' => array(
-        'description' => 'Boolean indicating whether the translation is visible to non-translators.',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 1,
-      ),
-      'created' => array(
-        'description' => 'The Unix timestamp when the translation was created.',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'changed' => array(
-        'description' => 'The Unix timestamp when the translation was most recently saved.',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-    ),
-    'primary key' => array('entity_type', 'entity_id', 'langcode'),
-  );
-  return $schema;
-}
-
-/**
  * Implements hook_install().
  */
 function content_translation_install() {
diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module
index 7ca20d4..4f59ad3 100644
--- a/core/modules/content_translation/content_translation.module
+++ b/core/modules/content_translation/content_translation.module
@@ -9,6 +9,7 @@
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\ContentEntityFormInterface;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
@@ -85,13 +86,23 @@ function content_translation_language_types_info_alter(array &$language_types) {
  * "/taxonomy/term/{taxonomy_term}"), in which case at least the 'canonical' key
  * in the 'links' entity info property must be defined.
  *
- * Every entity type needs a translation controller to be translated. This can
+ * Every entity type needs a translation handler to be translated. This can
  * be specified through the 'translation' key in the 'handlers' entity
  * annotation property. If an entity type is translatable and no translation
  * handler is defined, \Drupal\content_translation\ContentTranslationHandler
  * will be assumed. Every translation handler must implement
  * \Drupal\content_translation\ContentTranslationHandlerInterface.
  *
+ * To implement its business logic the content translation UI relies on various
+ * metadata items describing the translation state. The default implementation
+ * is provided by \Drupal\content_translation\ContentTranslationMetadataWrapper,
+ * which is relying on one field for each metadata item (field definitions are
+ * provided by the translation handler). Entity types needing to customize this
+ * behavior can specify an alternative class through the
+ * 'content_translation_metadata' key in the entity type definition. Every
+ * content translation metadata wrapper needs to implement
+ * \Drupal\content_translation\ContentTranslationMetadataWrapperInterface.
+ *
  * If the entity paths match the default pattern above and there is no need for
  * an entity-specific translation handler, Content Translation will
  * provide built-in support for the entity. However enabling translation for
@@ -107,6 +118,9 @@ function content_translation_entity_type_alter(array &$entity_types) {
       if (!$entity_type->hasHandlerClass('translation')) {
         $entity_type->setHandlerClass('translation', 'Drupal\content_translation\ContentTranslationHandler');
       }
+      if (!$entity_type->get('content_translation_metadata')) {
+        $entity_type->set('content_translation_metadata', 'Drupal\content_translation\ContentTranslationMetadataWrapper');
+      }
 
       $translation = $entity_type->get('translation');
       if (!$translation || !isset($translation['content_translation'])) {
@@ -141,6 +155,29 @@ function content_translation_entity_bundle_info_alter(&$bundles) {
 }
 
 /**
+ * Implements hook_entity_base_field_info().
+ */
+function content_translation_entity_base_field_info(EntityTypeInterface $entity_type) {
+  /** @var \Drupal\content_translation\ContentTranslationManagerInterface $manager */
+  $manager = \Drupal::service('content_translation.manager');
+  $entity_type_id = $entity_type->id();
+  if ($manager->isSupported($entity_type_id)) {
+    $definitions = $manager->getTranslationHandler($entity_type_id)->getFieldDefinitions();
+    $installed_storage_definitions = \Drupal::entityManager()->getLastInstalledFieldStorageDefinitions($entity_type_id);
+    // We return metadata storage fields whenever content translation is enabled
+    // or it was enabled before, so that we keep translation metadata around
+    // when translation is disabled.
+    // @todo Re-evaluate this approach and consider removing field storage
+    //   definitions and the related field data if the entity type has no bundle
+    //   enabled for translation, once base field purging is supported.
+    //   See https://www.drupal.org/node/2282119.
+    if ($manager->isEnabled($entity_type_id) || array_intersect_key($definitions, $installed_storage_definitions)) {
+      return $definitions;
+    }
+  }
+}
+
+/**
  * Implements hook_field_info_alter().
  *
  * Content translation extends the @FieldType annotation with following key:
@@ -231,21 +268,20 @@ function content_translation_translate_access(EntityInterface $entity) {
 }
 
 /**
- * Content translation controller factory.
+ * Content translation handler factory.
  *
  * @param string $entity_type_id
  *   The type of the entity being translated.
  *
  * @return \Drupal\content_translation\ContentTranslationHandlerInterface
- *   An instance of the content translation controller interface.
+ *   An instance of the content translation handler.
  *
- * @todo Move to \Drupal\content_translation\ContentTranslationManager.
+ * @deprecated in Drupal 8.0, will be removed in Drupal 9.0.
+ *
+ * @see \Drupal\content_translation\ContentTranslationManagerInterface::getTranslationHandler()
  */
 function content_translation_controller($entity_type_id) {
-  $entity_type = \Drupal::entityManager()->getDefinition($entity_type_id);
-  // @todo Throw an exception if the key is missing.
-  $class = $entity_type->getHandlerClass('translation');
-  return new $class($entity_type);
+  return \Drupal::service('content_translation.manager')->getTranslationHandler($entity_type_id);
 }
 
 /**
@@ -311,58 +347,17 @@ function content_translation_language_fallback_candidates_entity_view_alter(&$ca
   /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
   $entity = $context['data'];
   $entity_type_id = $entity->getEntityTypeId();
-  $entity_type = $entity->getEntityType();
-  $permission = $entity_type->getPermissionGranularity() == 'bundle' ? $permission = "translate {$entity->bundle()} $entity_type_id" : "translate $entity_type_id";
-  $current_user = \Drupal::currentuser();
-  if (!$current_user->hasPermission('translate any entity') && !$current_user->hasPermission($permission)) {
-    foreach ($entity->getTranslationLanguages() as $langcode => $language) {
-      if (empty($entity->translation[$langcode]['status'])) {
-        unset($candidates[$langcode]);
-      }
-    }
-  }
-}
-
-/**
- * Implements hook_entity_storage_load().
- */
-function content_translation_entity_storage_load(array $entities, $entity_type) {
-  $enabled_entities = array();
-
-  if (\Drupal::service('content_translation.manager')->isEnabled($entity_type)) {
-    foreach ($entities as $entity) {
-      if ($entity instanceof ContentEntityInterface && $entity->isTranslatable()) {
-        $enabled_entities[$entity->id()] = $entity;
-      }
-    }
-  }
-
-  if (!empty($enabled_entities)) {
-    content_translation_load_translation_metadata($enabled_entities, $entity_type);
-  }
-}
-
-/**
- * Loads translation data into the given entities.
- *
- * @param array $entities
- *   The entities keyed by entity ID.
- * @param string $entity_type
- *   The type of the entities.
- */
-function content_translation_load_translation_metadata(array $entities, $entity_type) {
-  $query = 'SELECT * FROM {content_translation} te WHERE te.entity_type = :entity_type AND te.entity_id IN (:entity_id)';
-  $result = db_query($query, array(':entity_type' => $entity_type, ':entity_id' => array_keys($entities)));
-  $exclude = array('entity_type', 'entity_id', 'langcode');
-  foreach ($result as $record) {
-    $entity = $entities[$record->entity_id];
-    // @todo Declare these as entity (translation?) properties.
-    foreach ($record as $field_name => $value) {
-      if (!in_array($field_name, $exclude)) {
-        $langcode = $record->langcode;
-        $entity->translation[$langcode][$field_name] = $value;
-        if (!$entity->hasTranslation($langcode)) {
-          $entity->initTranslation($langcode);
+  /** @var \Drupal\content_translation\ContentTranslationManagerInterface $manager */
+  $manager = \Drupal::service('content_translation.manager');
+  if ($manager->isEnabled($entity_type_id, $entity->bundle())) {
+    $entity_type = $entity->getEntityType();
+    $permission = $entity_type->getPermissionGranularity() == 'bundle' ? $permission = "translate {$entity->bundle()} $entity_type_id" : "translate $entity_type_id";
+    $current_user = \Drupal::currentuser();
+    if (!$current_user->hasPermission('translate any entity') && !$current_user->hasPermission($permission)) {
+      foreach ($entity->getTranslationLanguages() as $langcode => $language) {
+        $metadata = $manager->getTranslationMetadata($entity->getTranslation($langcode));
+        if (!$metadata->isPublished()) {
+          unset($candidates[$langcode]);
         }
       }
     }
@@ -370,75 +365,6 @@ function content_translation_load_translation_metadata(array $entities, $entity_
 }
 
 /**
- * Implements hook_entity_insert().
- */
-function content_translation_entity_insert(EntityInterface $entity) {
-  // Only do something if translation support for the given entity is enabled.
-  if (!($entity instanceof ContentEntityInterface) || !$entity->isTranslatable()) {
-    return;
-  }
-
-  $fields = array('entity_type', 'entity_id', 'langcode', 'source', 'outdated', 'uid', 'status', 'created', 'changed');
-  $query = db_insert('content_translation')->fields($fields);
-
-  foreach ($entity->getTranslationLanguages() as $langcode => $language) {
-    $translation = isset($entity->translation[$langcode]) ? $entity->translation[$langcode] : array();
-
-    $translation += array(
-      'source' => '',
-      'uid' => \Drupal::currentUser()->id(),
-      'outdated' => FALSE,
-      'status' => TRUE,
-      'created' => REQUEST_TIME,
-      'changed' => REQUEST_TIME,
-    );
-
-    $translation['entity_type'] = $entity->getEntityTypeId();
-    $translation['entity_id'] = $entity->id();
-    $translation['langcode'] = $langcode;
-
-    // Reorder values to match the schema.
-    $values = array();
-    foreach ($fields as $field_name) {
-      $value = is_bool($translation[$field_name]) ? intval($translation[$field_name]) : $translation[$field_name];
-      $values[$field_name] = $value;
-    }
-    $query->values($values);
-  }
-
-  $query->execute();
-}
-
-/**
- * Implements hook_entity_delete().
- */
-function content_translation_entity_delete(EntityInterface $entity) {
-  // Only do something if translation support for the given entity is enabled.
-  if (!($entity instanceof ContentEntityInterface) || !$entity->isTranslatable()) {
-    return;
-  }
-
-  db_delete('content_translation')
-    ->condition('entity_type', $entity->getEntityTypeId())
-    ->condition('entity_id', $entity->id())
-    ->execute();
-}
-
-/**
- * Implements hook_entity_update().
- */
-function content_translation_entity_update(EntityInterface $entity) {
-  // Only do something if translation support for the given entity is enabled.
-  if (!($entity instanceof ContentEntityInterface) || !$entity->isTranslatable()) {
-    return;
-  }
-
-  // Delete and create to ensure no stale value remains behind.
-  content_translation_entity_delete($entity);
-  content_translation_entity_insert($entity);
-}
-
-/**
  * Implements hook_entity_extra_field_info().
  */
 function content_translation_entity_extra_field_info() {
@@ -499,11 +425,18 @@ function content_translation_form_field_ui_field_edit_form_alter(array &$form, F
  * Implements hook_entity_presave().
  */
 function content_translation_entity_presave(EntityInterface $entity) {
-  if ($entity instanceof ContentEntityInterface && $entity->isTranslatable()) {
-    // @todo Avoid using request attributes once translation metadata become
-    //   regular fields.
-    $attributes = \Drupal::request()->attributes;
-    \Drupal::service('content_translation.synchronizer')->synchronizeFields($entity, $entity->language()->getId(), $attributes->get('source_langcode'));
+  if ($entity instanceof ContentEntityInterface && $entity->isTranslatable() && !$entity->isNew()) {
+    // If we are creating a new translation we need to use the source language
+    // as original language, since source values are the only ones available to
+    // compare against.
+    if (!isset($entity->original)) {
+      $entity->original = entity_load_unchanged($entity->entityType(), $entity->id());
+    }
+    $langcode = $entity->language()->getId();
+    /** @var \Drupal\content_translation\ContentTranslationManagerInterface $manager */
+    $manager = \Drupal::service('content_translation.manager');
+    $source_langcode = !$entity->original->hasTranslation($langcode) ? $manager->getTranslationMetadata($entity)->getSource() : NULL;
+    \Drupal::service('content_translation.synchronizer')->synchronizeFields($entity, $langcode, $source_langcode);
   }
 }
 
diff --git a/core/modules/content_translation/src/ContentTranslationHandler.php b/core/modules/content_translation/src/ContentTranslationHandler.php
index 88f57ae..5faafb7 100644
--- a/core/modules/content_translation/src/ContentTranslationHandler.php
+++ b/core/modules/content_translation/src/ContentTranslationHandler.php
@@ -8,11 +8,15 @@
 namespace Drupal\content_translation;
 
 use Drupal\Core\Access\AccessResult;
+use Drupal\Core\DependencyInjection\DependencySerializationTrait;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Render\Element;
+use Drupal\user\Entity\User;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Base class for content translation handlers.
@@ -20,6 +24,7 @@
  * @ingroup entity_api
  */
 class ContentTranslationHandler implements ContentTranslationHandlerInterface {
+  use DependencySerializationTrait;
 
   /**
    * The type of the entity being translated.
@@ -36,14 +41,130 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface {
   protected $entityType;
 
   /**
+   * The content translation manager.
+   *
+   * @var \Drupal\content_translation\ContentTranslationManagerInterface
+   */
+  protected $manager;
+
+  /**
    * Initializes an instance of the content translation controller.
    *
    * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
    *   The info array of the given entity type.
+   * @param \Drupal\content_translation\ContentTranslationManagerInterface $manager
+   *   The content translation manager service.
    */
-  public function __construct(EntityTypeInterface $entity_type) {
+  public function __construct(EntityTypeInterface $entity_type, ContentTranslationManagerInterface $manager) {
     $this->entityTypeId = $entity_type->id();
     $this->entityType = $entity_type;
+    $this->manager = $manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
+    return new static($entity_type, $container->get('content_translation.manager'));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFieldDefinitions() {
+    $definitions = array();
+
+    $definitions['content_translation_source'] = BaseFieldDefinition::create('language')
+      ->setLabel(t('Translation source'))
+      ->setDescription(t('The source language from which this translation was created.'))
+      ->setDefaultValue(LanguageInterface::LANGCODE_NOT_SPECIFIED)
+      ->setRevisionable(TRUE)
+      ->setTranslatable(TRUE);
+
+    $definitions['content_translation_outdated'] = BaseFieldDefinition::create('boolean')
+      ->setLabel(t('Translation outdated'))
+      ->setDescription(t('A boolean indicating whether this translation needs to be updated.'))
+      ->setDefaultValue(FALSE)
+      ->setRevisionable(TRUE)
+      ->setTranslatable(TRUE);
+
+    if (!$this->hasAuthor()) {
+      $definitions['content_translation_uid'] = BaseFieldDefinition::create('entity_reference')
+        ->setLabel(t('Translation author'))
+        ->setDescription(t('The author of this translation.'))
+        ->setSetting('target_type', 'user')
+        ->setSetting('handler', 'default')
+        ->setRevisionable(TRUE)
+        ->setTranslatable(TRUE);
+    }
+
+    if (!$this->hasPublishedStatus()) {
+      $definitions['content_translation_status'] = BaseFieldDefinition::create('boolean')
+        ->setLabel(t('Translation status'))
+        ->setDescription(t('A boolean indicating whether the translation is visible to non-translators.'))
+        ->setDefaultValue(TRUE)
+        ->setRevisionable(TRUE)
+        ->setTranslatable(TRUE);
+    }
+
+    if (!$this->hasCreatedTime()) {
+      $definitions['content_translation_created'] = BaseFieldDefinition::create('created')
+        ->setLabel(t('Translation created time'))
+        ->setDescription(t('The Unix timestamp when the translation was created.'))
+        ->setRevisionable(TRUE)
+        ->setTranslatable(TRUE);
+    }
+
+    if (!$this->hasChangedTime()) {
+      $definitions['content_translation_changed'] = BaseFieldDefinition::create('changed')
+        ->setLabel(t('Translation changed time'))
+        ->setDescription(t('The Unix timestamp when the translation was most recently saved.'))
+        ->setPropertyConstraints('value', array('EntityChanged' => array()))
+        ->setRevisionable(TRUE)
+        ->setTranslatable(TRUE);
+    }
+
+    return $definitions;
+  }
+
+  /**
+   * Checks whether the entity type supports author natively.
+   *
+   * @return bool
+   *   TRUE if metadata is natively supported, FALSE otherwise.
+   */
+  protected function hasAuthor() {
+    return is_subclass_of($this->entityType->getClass(), '\Drupal\user\EntityOwnerInterface');
+  }
+
+  /**
+   * Checks whether the entity type supports published status natively.
+   *
+   * @return bool
+   *   TRUE if metadata is natively supported, FALSE otherwise.
+   */
+  protected function hasPublishedStatus() {
+    return array_key_exists('status', \Drupal::entityManager()->getLastInstalledFieldStorageDefinitions($this->entityType->id()));
+  }
+
+  /**
+   * Checks whether the entity type supports modification time natively.
+   *
+   * @return bool
+   *   TRUE if metadata is natively supported, FALSE otherwise.
+   */
+  protected function hasChangedTime() {
+    return is_subclass_of($this->entityType->getClass(), '\Drupal\Core\Entity\EntityChangedInterface');
+  }
+
+  /**
+   * Checks whether the entity type supports creation time natively.
+   *
+   * @return bool
+   *   TRUE if metadata is natively supported, FALSE otherwise.
+   */
+  protected function hasCreatedTime() {
+    return array_key_exists('created', \Drupal::entityManager()->getLastInstalledFieldStorageDefinitions($this->entityType->id()));
   }
 
   /**
@@ -51,9 +172,9 @@ public function __construct(EntityTypeInterface $entity_type) {
    */
   public function retranslate(EntityInterface $entity, $langcode = NULL) {
     $updated_langcode = !empty($langcode) ? $langcode : $entity->language()->getId();
-    $translations = $entity->getTranslationLanguages();
-    foreach ($translations as $langcode => $language) {
-      $entity->translation[$langcode]['outdated'] = $langcode != $updated_langcode;
+    foreach ($entity->getTranslationLanguages() as $langcode => $language) {
+      $this->manager->getTranslationMetadata($entity->getTranslation($langcode))
+        ->setOutdated($langcode != $updated_langcode);
     }
   }
 
@@ -208,20 +329,16 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En
       );
 
       // A new translation is enabled by default.
-      $status = $new_translation || $entity->translation[$form_langcode]['status'];
+      $metadata = $this->manager->getTranslationMetadata($entity);
+      $status = $new_translation || $metadata->isPublished();
       // If there is only one published translation we cannot unpublish it,
       // since there would be nothing left to display.
       $enabled = TRUE;
       if ($status) {
-        // A new translation is not available in the translation metadata, hence
-        // it should count as one more.
-        $published = $new_translation;
-        // When creating a brand new translation, $entity->translation is not
-        // set.
-        if (!$new_translation) {
-          foreach ($entity->translation as $translation) {
-            $published += $translation['status'];
-          }
+        $published = 0;
+        foreach ($entity->getTranslationLanguages() as $langcode => $language) {
+          $published += $this->manager->getTranslationMetadata($entity->getTranslation($langcode))
+            ->isPublished();
         }
         $enabled = $published > 1;
       }
@@ -237,7 +354,7 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En
         '#disabled' => !$enabled,
       );
 
-      $translate = !$new_translation && $entity->translation[$form_langcode]['outdated'];
+      $translate = !$new_translation && $metadata->isOutdated();
       if (!$translate) {
         $form['content_translation']['retranslate'] = array(
           '#type' => 'checkbox',
@@ -261,8 +378,8 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En
       if ($new_translation) {
         $name = \Drupal::currentUser()->getUsername();
       }
-      elseif ($entity->translation[$form_langcode]['uid']) {
-        $name = user_load($entity->translation[$form_langcode]['uid'])->getUsername();
+      elseif (($account = $metadata->getAuthor()) && $account->id()) {
+        $name = $account->getUsername();
       }
       $form['content_translation']['name'] = array(
         '#type' => 'textfield',
@@ -273,13 +390,13 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En
         '#description' => t('Leave blank for %anonymous.', array('%anonymous' => \Drupal::config('user.settings')->get('anonymous'))),
       );
 
-      $date = $new_translation ? REQUEST_TIME : $entity->translation[$form_langcode]['created'];
+      $date = $new_translation ? REQUEST_TIME : $metadata->getCreatedTime();
       $form['content_translation']['created'] = array(
         '#type' => 'textfield',
         '#title' => t('Authored on'),
         '#maxlength' => 25,
-        '#description' => t('Format: %time. The date format is YYYY-MM-DD and %timezone is the time zone offset from UTC. Leave blank to use the time of form submission.', array('%time' => format_date($date, 'custom', 'Y-m-d H:i:s O'), '%timezone' => format_date($date, 'custom', 'O'))),
-        '#default_value' => $new_translation ? '' : format_date($date, 'custom', 'Y-m-d H:i:s O'),
+        '#description' => t('Format: %time. The date format is YYYY-MM-DD and %timezone is the time zone offset from UTC. Leave blank to use the time of form submission.', array('%time' => format_date(REQUEST_TIME, 'custom', 'Y-m-d H:i:s O'), '%timezone' => format_date(REQUEST_TIME, 'custom', 'O'))),
+        '#default_value' => $new_translation || !$date ? '' : format_date($date, 'custom', 'Y-m-d H:i:s O'),
       );
 
       if (isset($language_widget)) {
@@ -395,35 +512,27 @@ protected function addTranslatabilityClue(&$element) {
   public function entityFormEntityBuild($entity_type, EntityInterface $entity, array $form, FormStateInterface $form_state) {
     $form_object = $form_state->getFormObject();
     $form_langcode = $form_object->getFormLangcode($form_state);
+    $values = &$form_state->getValue('content_translation', array());
 
-    if (!isset($entity->translation[$form_langcode])) {
-      $entity->translation[$form_langcode] = array();
+    if ($values['name'] == \Drupal::config('user.settings')->get('anonymous')) {
+      $values['name'] = '';
     }
-    $values = $form_state->getValue('content_translation', array());
-    $translation = &$entity->translation[$form_langcode];
 
-    // @todo Use the entity setter when all entities support multilingual
-    // properties.
-    $translation['uid'] = !empty($values['name']) && ($account = user_load_by_name($values['name'])) ? $account->id() : 0;
-    $translation['status'] = !empty($values['status']);
-    $translation['created'] = !empty($values['created']) ? strtotime($values['created']) : REQUEST_TIME;
-    $translation['changed'] = REQUEST_TIME;
+    $metadata = $this->manager->getTranslationMetadata($entity);
+    $metadata->setAuthor(!empty($values['name']) && ($account = user_load_by_name($values['name'])) ? $account : User::load(0));
+    $metadata->setPublished(!empty($values['status']));
+    $metadata->setCreatedTime(!empty($values['created']) ? strtotime($values['created']) : REQUEST_TIME);
+    $metadata->setChangedTime(REQUEST_TIME);
 
     $source_langcode = $this->getSourceLangcode($form_state);
     if ($source_langcode) {
-      $translation['source'] = $source_langcode;
+      $metadata->setSource($source_langcode);
     }
 
-    $translation['outdated'] = !empty($values['outdated']);
+    $metadata->setOutdated(!empty($values['outdated']));
     if (!empty($values['retranslate'])) {
       $this->retranslate($entity, $form_langcode);
     }
-
-    // Set contextual information that can be reused during the storage phase.
-    // @todo Remove this once translation metadata are converted to regular
-    //   fields.
-    $attributes = \Drupal::request()->attributes;
-    $attributes->set('source_langcode', $source_langcode);
   }
 
   /**
diff --git a/core/modules/content_translation/src/ContentTranslationHandlerInterface.php b/core/modules/content_translation/src/ContentTranslationHandlerInterface.php
index 5ab8858..6e53355 100644
--- a/core/modules/content_translation/src/ContentTranslationHandlerInterface.php
+++ b/core/modules/content_translation/src/ContentTranslationHandlerInterface.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\content_translation;
 
+use Drupal\Core\Entity\EntityHandlerInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Form\FormStateInterface;
 
@@ -16,7 +17,14 @@
  * Defines a set of methods to allow any entity to be processed by the entity
  * translation UI.
  */
-interface ContentTranslationHandlerInterface {
+interface ContentTranslationHandlerInterface extends EntityHandlerInterface {
+
+  /**
+   * Returns a set of field definitions to be used to store metadata items.
+   *
+   * @return \Drupal\Core\Field\FieldDefinitionInterface[]
+   */
+  public function getFieldDefinitions();
 
   /**
    * Checks if the user can perform the given operation on translations of the
diff --git a/core/modules/content_translation/src/ContentTranslationManager.php b/core/modules/content_translation/src/ContentTranslationManager.php
index cff64c3..a4d529a 100644
--- a/core/modules/content_translation/src/ContentTranslationManager.php
+++ b/core/modules/content_translation/src/ContentTranslationManager.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\content_translation;
 
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\language\Entity\ContentLanguageSettings;
 
 /**
@@ -35,6 +37,23 @@ public function __construct(EntityManagerInterface $manager) {
   /**
    * {@inheritdoc}
    */
+  function getTranslationHandler($entity_type_id) {
+    return $this->entityManager->getHandler($entity_type_id, 'translation');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTranslationMetadata(EntityInterface $translation) {
+    // We need a new instance of the metadata handler wrapping each translation.
+    $entity_type = $translation->getEntityType();
+    $class = $entity_type->get('content_translation_metadata');
+    return new $class($translation, $this->getTranslationHandler($entity_type->id()));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function isSupported($entity_type_id) {
     $entity_type = $this->entityManager->getDefinition($entity_type_id);
     return $entity_type->isTranslatable() && $entity_type->hasLinkTemplate('drupal:content-translation-overview');
diff --git a/core/modules/content_translation/src/ContentTranslationManagerInterface.php b/core/modules/content_translation/src/ContentTranslationManagerInterface.php
index e3ae29f..5afa2de 100644
--- a/core/modules/content_translation/src/ContentTranslationManagerInterface.php
+++ b/core/modules/content_translation/src/ContentTranslationManagerInterface.php
@@ -7,6 +7,9 @@
 
 namespace Drupal\content_translation;
 
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+
 /**
  * Provides an interface for common functionality for content translation.
  */
@@ -32,6 +35,28 @@ public function getSupportedEntityTypes();
   public function isSupported($entity_type_id);
 
   /**
+   * Returns an instance of the Content translation handler.
+   *
+   * @param string $entity_type_id
+   *   The type of the entity being translated.
+   *
+   * @return \Drupal\content_translation\ContentTranslationHandlerInterface
+   *   An instance of the content translation handler.
+   */
+  public function getTranslationHandler($entity_type_id);
+
+  /**
+   * Returns an instance of the Content translation metadata.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $translation
+   *   The entity translation whose metadata needs to be retrieved.
+   *
+   * @return \Drupal\content_translation\ContentTranslationMetadataWrapperInterface
+   *   An instance of the content translation metadata.
+   */
+  public function getTranslationMetadata(EntityInterface $translation);
+
+  /**
    * Sets the value for translatability of the given entity type bundle.
    *
    * @param string $entity_type_id
diff --git a/core/modules/content_translation/src/ContentTranslationMetadataWrapper.php b/core/modules/content_translation/src/ContentTranslationMetadataWrapper.php
new file mode 100644
index 0000000..4b4d43e
--- /dev/null
+++ b/core/modules/content_translation/src/ContentTranslationMetadataWrapper.php
@@ -0,0 +1,145 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\content_translation\ContentTranslationMetadata.
+ */
+
+namespace Drupal\content_translation;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\user\UserInterface;
+
+/**
+ * Base class for content translation metadata wrappers.
+ */
+class ContentTranslationMetadataWrapper implements ContentTranslationMetadataWrapperInterface {
+
+  /**
+   * The wrapped entity translation.
+   *
+   * @var \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\FieldableEntityInterface|\Drupal\Core\TypedData\TranslatableInterface
+   */
+  protected $translation;
+
+  /**
+   * The content translation handler.
+   *
+   * @var \Drupal\content_translation\ContentTranslationHandlerInterface
+   */
+  protected $handler;
+
+  /**
+   * Initializes an instance of the content translation metadata handler.
+   *
+   * @param EntityInterface $translation
+   *   The entity translation to be wrapped.
+   * @param ContentTranslationHandlerInterface $handler
+   *   The content translation handler.
+   */
+  public function __construct(EntityInterface $translation, ContentTranslationHandlerInterface $handler) {
+    $this->translation = $translation;
+    $this->handler = $handler;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSource() {
+    return $this->translation->get('content_translation_source')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setSource($source) {
+    $this->translation->set('content_translation_source', $source);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isOutdated() {
+    return (bool) $this->translation->get('content_translation_outdated')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setOutdated($outdated) {
+    $this->translation->set('content_translation_outdated', $outdated);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAuthor() {
+    return $this->translation->hasField('content_translation_uid') ? $this->translation->get('content_translation_uid')->entity : $this->translation->getOwner();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setAuthor(UserInterface $account) {
+    if ($this->translation->hasField('content_translation_uid')) {
+      $this->translation->set('content_translation_uid', $account->id());
+    }
+    else {
+      $this->translation->setOwner($account);
+    }
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isPublished() {
+    $field_name = $this->translation->hasField('content_translation_status') ? 'content_translation_status' : 'status';
+    return (bool) $this->translation->get($field_name)->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setPublished($published) {
+    $field_name = $this->translation->hasField('content_translation_status') ? 'content_translation_status' : 'status';
+    $this->translation->set($field_name, $published);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCreatedTime() {
+    $field_name = $this->translation->hasField('content_translation_created') ? 'content_translation_created' : 'created';
+    return $this->translation->get($field_name)->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setCreatedTime($timestamp) {
+    $field_name = $this->translation->hasField('content_translation_created') ? 'content_translation_created' : 'created';
+    $this->translation->set($field_name, $timestamp);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getChangedTime() {
+    return $this->translation->hasField('content_translation_changed') ? $this->translation->get('content_translation_changed')->value : $this->translation->getChangedTime();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setChangedTime($timestamp) {
+    $field_name = $this->translation->hasField('content_translation_changed') ? 'content_translation_changed' : 'changed';
+    $this->translation->set($field_name, $timestamp);
+    return $this;
+  }
+
+}
diff --git a/core/modules/content_translation/src/ContentTranslationMetadataWrapperInterface.php b/core/modules/content_translation/src/ContentTranslationMetadataWrapperInterface.php
new file mode 100644
index 0000000..4a0a65b
--- /dev/null
+++ b/core/modules/content_translation/src/ContentTranslationMetadataWrapperInterface.php
@@ -0,0 +1,122 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\content_translation\ContentTranslationMetadataInterface.
+ */
+
+namespace Drupal\content_translation;
+
+use Drupal\Core\Entity\EntityChangedInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\user\UserInterface;
+
+/**
+ * Common interface for content translation metadata wrappers.
+ *
+ * This acts as a wrapper for an entity translation object, encapsulating the
+ * logic needed to retrieve translation metadata.
+ */
+interface ContentTranslationMetadataWrapperInterface extends EntityChangedInterface {
+
+  /**
+   * Retrieves the source language for this translation.
+   *
+   * @return string
+   *   The source language code.
+   */
+  public function getSource();
+
+  /**
+   * Sets the source language for this translation.
+   *
+   * @param string $source
+   *   The source language code.
+   *
+   * @return $this
+   */
+  public function setSource($source);
+
+  /**
+   * Returns the translation outdated status.
+   *
+   * @return bool
+   *   TRUE if the translation is outdated, FALSE otherwise.
+   */
+  public function isOutdated();
+
+  /**
+   * Sets the translation outdated status.
+   *
+   * @param bool $outdated
+   *   TRUE if the translation is outdated, FALSE otherwise.
+   *
+   * @return $this
+   */
+  public function setOutdated($outdated);
+
+  /**
+   * Returns the translation author.
+   *
+   * @return \Drupal\user\UserInterface
+   *   The user entity for the translation author.
+   */
+  public function getAuthor();
+
+  /**
+   * Sets the translation author.
+   *
+   * @param \Drupal\user\UserInterface $account
+   *   The translation author user entity.
+   *
+   * @return $this
+   */
+  public function setAuthor(UserInterface $account);
+
+  /**
+   * Returns the translation published status.
+   *
+   * @return bool
+   *   TRUE if the translation is published, FALSE otherwise.
+   */
+  public function isPublished();
+
+  /**
+   * Sets the translation published status.
+   *
+   * @param bool $published
+   *   TRUE if the translation is published, FALSE otherwise.
+   *
+   * @return $this
+   */
+  public function setPublished($published);
+
+  /**
+   * Returns the translation creation timestamp.
+   *
+   * @return int
+   *   The UNIX timestamp of when the translation was created.
+   */
+  public function getCreatedTime();
+
+  /**
+   * Sets the translation creation timestamp.
+   *
+   * @param int $timestamp
+   *   The UNIX timestamp of when the translation was created.
+   *
+   * @return $this
+   */
+  public function setCreatedTime($timestamp);
+
+  /**
+   * Sets the translation modification timestamp.
+   *
+   * @param int $timestamp
+   *   The UNIX timestamp of when the translation was last modified.
+   *
+   * @return $this
+   */
+  public function setChangedTime($timestamp);
+
+}
diff --git a/core/modules/content_translation/src/Controller/ContentTranslationController.php b/core/modules/content_translation/src/Controller/ContentTranslationController.php
index e3622dc..b3f81dd 100644
--- a/core/modules/content_translation/src/Controller/ContentTranslationController.php
+++ b/core/modules/content_translation/src/Controller/ContentTranslationController.php
@@ -7,10 +7,12 @@
 
 namespace Drupal\content_translation\Controller;
 
+use Drupal\content_translation\ContentTranslationManagerInterface;
 use Drupal\Core\Controller\ControllerBase;
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Url;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
 
 /**
@@ -19,6 +21,30 @@
 class ContentTranslationController extends ControllerBase {
 
   /**
+   * The content translation manager.
+   *
+   * @var \Drupal\content_translation\ContentTranslationManagerInterface
+   */
+  protected $manager;
+
+  /**
+   * Initializes a content translation controller.
+   *
+   * @param \Drupal\content_translation\ContentTranslationManagerInterface
+   *   A content translation manager instance.
+   */
+  public function __construct(ContentTranslationManagerInterface $manager) {
+    $this->manager = $manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static($container->get('content_translation.manager'));
+  }
+
+  /**
    * Populates target values with the source values.
    *
    * @param \Drupal\Core\Entity\ContentEntityInterface $entity
@@ -46,9 +72,11 @@ public function prepareTranslation(ContentEntityInterface $entity, LanguageInter
    *   Array of page elements to render.
    */
   public function overview(Request $request, $entity_type_id = NULL) {
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
     $entity = $request->attributes->get($entity_type_id);
     $account = $this->currentUser();
     $handler = $this->entityManager()->getHandler($entity_type_id, 'translation');
+    $manager = $this->manager;
 
     $languages = $this->languageManager()->getLanguages();
     $original = $entity->getUntranslated()->language()->getId();
@@ -69,8 +97,9 @@ public function overview(Request $request, $entity_type_id = NULL) {
       }
 
       // Show source-language column if there are non-original source langcodes.
-      $additional_source_langcodes = array_filter($entity->translation, function ($translation) use ($original) {
-        return !empty($translation['source']) && $translation['source'] != $original;
+      $additional_source_langcodes = array_filter(array_keys($translations), function ($langcode) use ($entity, $original, $manager) {
+        $source = $manager->getTranslationMetadata($entity->getTranslation($langcode))->getSource();
+        return $source != $original && $source != LanguageInterface::LANGCODE_NOT_SPECIFIED;
       });
       $show_source_column = !empty($additional_source_langcodes);
 
@@ -119,7 +148,9 @@ public function overview(Request $request, $entity_type_id = NULL) {
         $links = &$operations['data']['#links'];
         if (array_key_exists($langcode, $translations)) {
           // Existing translation in the translation set: display status.
-          $source = isset($entity->translation[$langcode]['source']) ? $entity->translation[$langcode]['source'] : '';
+          $translation = $entity->getTranslation($langcode);
+          $metadata = $manager->getTranslationMetadata($translation);
+          $source = $metadata->getSource() ?: LanguageInterface::LANGCODE_NOT_SPECIFIED;
           $is_original = $langcode == $original;
           $label = $entity->getTranslation($langcode)->label();
           $link = isset($links->links[$langcode]['url']) ? $links->links[$langcode] : array('url' => $entity->urlInfo());
@@ -146,13 +177,12 @@ public function overview(Request $request, $entity_type_id = NULL) {
           if (isset($links['edit'])) {
             $links['edit']['title'] = $this->t('Edit');
           }
-          $translation = $entity->translation[$langcode];
           $status = array('data' => array(
             '#type' => 'inline_template',
             '#template' => '<span class="status">{% if status %}{{ "Published"|t }}{% else %}{{ "Not published"|t }}{% endif %}</span>{% if outdated %} <span class="marker">{{ "outdated"|t }}</span>{% endif %}',
             '#context' => array(
-              'status' => $translation['status'],
-              'outdated' => $translation['outdated'],
+              'status' => $metadata->isPublished(),
+              'outdated' => $metadata->isOutdated(),
             ),
           ));
 
diff --git a/core/modules/content_translation/src/Tests/ContentTranslationSyncImageTest.php b/core/modules/content_translation/src/Tests/ContentTranslationSyncImageTest.php
index a343190..dfa2b4d 100644
--- a/core/modules/content_translation/src/Tests/ContentTranslationSyncImageTest.php
+++ b/core/modules/content_translation/src/Tests/ContentTranslationSyncImageTest.php
@@ -117,10 +117,6 @@ function testImageFieldSync() {
     $default_langcode = $this->langcodes[0];
     $langcode = $this->langcodes[1];
 
-    // Populate the required contextual values.
-    $attributes = \Drupal::request()->attributes;
-    $attributes->set('source_langcode', $default_langcode);
-
     // Populate the test entity with some random initial values.
     $values = array(
       'name' => $this->randomMachineName(),
@@ -188,6 +184,7 @@ function testImageFieldSync() {
 
     // Perform synchronization: the translation language is used as source,
     // while the default language is used as target.
+    $this->manager->getTranslationMetadata($translation)->setSource($default_langcode);
     $entity = $this->saveEntity($translation);
     $translation = $entity->getTranslation($langcode);
 
@@ -217,8 +214,6 @@ function testImageFieldSync() {
       'title' => $langcode . '_' . $removed_fid . '_' . $this->randomMachineName(),
     );
     $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($translation);
     $translation = $entity->getTranslation($langcode);
 
diff --git a/core/modules/content_translation/src/Tests/ContentTranslationTestBase.php b/core/modules/content_translation/src/Tests/ContentTranslationTestBase.php
index dda1ff2..0f287a6 100644
--- a/core/modules/content_translation/src/Tests/ContentTranslationTestBase.php
+++ b/core/modules/content_translation/src/Tests/ContentTranslationTestBase.php
@@ -79,6 +79,11 @@
    */
   protected $controller;
 
+  /**
+   * @var \Drupal\content_translation\ContentTranslationManagerInterface
+   */
+  protected $manager;
+
   protected function setUp() {
     parent::setUp();
 
@@ -88,6 +93,7 @@ protected function setUp() {
     $this->setupUsers();
     $this->setupTestFields();
 
+    $this->manager = $this->container->get('content_translation.manager');
     $this->controller = content_translation_controller($this->entityTypeId);
 
     // Rebuild the container so that the new languages are picked up by services
@@ -167,6 +173,7 @@ protected function enableTranslation() {
     drupal_static_reset();
     \Drupal::entityManager()->clearCachedDefinitions();
     \Drupal::service('router.builder')->rebuild();
+    \Drupal::service('entity.definition_update_manager')->applyUpdates();
   }
 
   /**
diff --git a/core/modules/content_translation/src/Tests/ContentTranslationUITest.php b/core/modules/content_translation/src/Tests/ContentTranslationUITest.php
index 0581893..bf8a026 100644
--- a/core/modules/content_translation/src/Tests/ContentTranslationUITest.php
+++ b/core/modules/content_translation/src/Tests/ContentTranslationUITest.php
@@ -161,7 +161,7 @@ protected function doTestOutdatedStatus() {
         $this->drupalGet($path, $options);
         $this->assertFieldByXPath('//input[@name="content_translation[retranslate]"]', FALSE, 'The retranslate flag is now shown.');
         $entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
-        $this->assertFalse($entity->translation[$added_langcode]['outdated'], 'The "outdated" status has been correctly stored.');
+        $this->assertFalse($this->manager->getTranslationMetadata($entity->getTranslation($added_langcode))->isOutdated(), 'The "outdated" status has been correctly stored.');
       }
     }
   }
@@ -179,7 +179,7 @@ protected function doTestPublishedStatus() {
         $edit = array('content_translation[status]' => FALSE);
         $this->drupalPostForm($langcode . '/' . $path, $edit, $this->getFormSubmitAction($entity, $langcode));
         $entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
-        $this->assertFalse($entity->translation[$langcode]['status'], 'The translation has been correctly unpublished.');
+        $this->assertFalse($this->manager->getTranslationMetadata($entity->getTranslation($langcode))->isPublished(), 'The translation has been correctly unpublished.');
       }
     }
 
@@ -213,8 +213,9 @@ protected function doTestAuthoringInfo() {
 
     $entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
     foreach ($this->langcodes as $langcode) {
-      $this->assertEqual($entity->translation[$langcode]['uid'], $values[$langcode]['uid'], 'Translation author correctly stored.');
-      $this->assertEqual($entity->translation[$langcode]['created'], $values[$langcode]['created'], 'Translation date correctly stored.');
+      $metadata = $this->manager->getTranslationMetadata($entity->getTranslation($langcode));
+      $this->assertEqual($metadata->getAuthor()->id(), $values[$langcode]['uid'], 'Translation author correctly stored.');
+      $this->assertEqual($metadata->getCreatedTime(), $values[$langcode]['created'], 'Translation date correctly stored.');
     }
 
     // Try to post non valid values and check that they are rejected.
@@ -226,8 +227,9 @@ protected function doTestAuthoringInfo() {
     );
     $this->drupalPostForm($path, $edit, $this->getFormSubmitAction($entity, $langcode));
     $this->assertTrue($this->xpath('//div[contains(@class, "error")]//ul'), 'Invalid values generate a list of form errors.');
-    $this->assertEqual($entity->translation[$langcode]['uid'], $values[$langcode]['uid'], 'Translation author correctly kept.');
-    $this->assertEqual($entity->translation[$langcode]['created'], $values[$langcode]['created'], 'Translation date correctly kept.');
+    $metadata = $this->manager->getTranslationMetadata($entity->getTranslation($langcode));
+    $this->assertEqual($metadata->getAuthor()->id(), $values[$langcode]['uid'], 'Translation author correctly kept.');
+    $this->assertEqual($metadata->getCreatedTime(), $values[$langcode]['created'], 'Translation date correctly kept.');
   }
 
   /**
diff --git a/core/modules/entity_reference/src/Tests/EntityReferenceFieldTranslatedReferenceViewTest.php b/core/modules/entity_reference/src/Tests/EntityReferenceFieldTranslatedReferenceViewTest.php
index 8480073..0ed4af2 100644
--- a/core/modules/entity_reference/src/Tests/EntityReferenceFieldTranslatedReferenceViewTest.php
+++ b/core/modules/entity_reference/src/Tests/EntityReferenceFieldTranslatedReferenceViewTest.php
@@ -171,6 +171,7 @@ protected function enableTranslation() {
     drupal_static_reset();
     \Drupal::entityManager()->clearCachedDefinitions();
     \Drupal::service('router.builder')->rebuild();
+    \Drupal::service('entity.definition_update_manager')->applyUpdates();
   }
 
   /**
diff --git a/core/modules/file/src/Tests/PrivateFileOnTranslatedEntityTest.php b/core/modules/file/src/Tests/PrivateFileOnTranslatedEntityTest.php
index 1b5bef2..4841d02 100644
--- a/core/modules/file/src/Tests/PrivateFileOnTranslatedEntityTest.php
+++ b/core/modules/file/src/Tests/PrivateFileOnTranslatedEntityTest.php
@@ -68,6 +68,7 @@ protected function setUp() {
       "settings[node][page][fields][$this->fieldName]" => 1,
     );
     $this->drupalPostForm('admin/config/regional/content-language', $edit, t('Save configuration'));
+    \Drupal::entityManager()->clearCachedDefinitions();
   }
 
   /**
diff --git a/core/modules/node/src/Tests/NodeTranslationUITest.php b/core/modules/node/src/Tests/NodeTranslationUITest.php
index 153e87c..ed03f87 100644
--- a/core/modules/node/src/Tests/NodeTranslationUITest.php
+++ b/core/modules/node/src/Tests/NodeTranslationUITest.php
@@ -111,9 +111,8 @@ protected function doTestPublishedStatus() {
         // The node is created as unpublished thus we switch to the published
         // status first.
         $status = !$index;
-        $this->assertEqual($status, $entity->translation[$langcode]['status'], 'The translation has been correctly unpublished.');
         $translation = $entity->getTranslation($langcode);
-        $this->assertEqual($status, $translation->isPublished(), 'The status of the translation has been correctly saved.');
+        $this->assertEqual($status, $this->manager->getTranslationMetadata($translation)->isPublished(), 'The translation has been correctly unpublished.');
       }
     }
   }
@@ -148,11 +147,10 @@ protected function doTestAuthoringInfo() {
 
     $entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
     foreach ($this->langcodes as $langcode) {
-      $this->assertEqual($entity->translation[$langcode]['uid'], $values[$langcode]['uid'], 'Translation author correctly stored.');
-      $this->assertEqual($entity->translation[$langcode]['created'], $values[$langcode]['created'], 'Translation date correctly stored.');
       $translation = $entity->getTranslation($langcode);
-      $this->assertEqual($translation->getOwnerId(), $values[$langcode]['uid'], 'Author of translation correctly stored.');
-      $this->assertEqual($translation->getCreatedTime(), $values[$langcode]['created'], 'Date of Translation correctly stored.');
+      $metadata = $this->manager->getTranslationMetadata($translation);
+      $this->assertEqual($metadata->getAuthor()->id(), $values[$langcode]['uid'], 'Translation author correctly stored.');
+      $this->assertEqual($metadata->getCreatedTime(), $values[$langcode]['created'], 'Translation date correctly stored.');
       $this->assertEqual($translation->isSticky(), $values[$langcode]['sticky'], 'Sticky of Translation correctly stored.');
       $this->assertEqual($translation->isPromoted(), $values[$langcode]['promote'], 'Promoted of Translation correctly stored.');
     }
@@ -199,7 +197,7 @@ public function testDisabledBundle() {
     ));
 
     // Make sure that nothing was inserted into the {content_translation} table.
-    $rows = db_query('SELECT * FROM {content_translation}')->fetchAll();
+    $rows = db_query('SELECT nid, count(nid) AS count FROM {node_field_data} WHERE type <> :type GROUP BY nid HAVING count >= 2', array(':type' => $this->bundle))->fetchAll();
     $this->assertEqual(0, count($rows));
 
     // Ensure the translation tab is not accessible.
diff --git a/core/modules/path/src/Tests/PathLanguageTest.php b/core/modules/path/src/Tests/PathLanguageTest.php
index e5f7065..153393b 100644
--- a/core/modules/path/src/Tests/PathLanguageTest.php
+++ b/core/modules/path/src/Tests/PathLanguageTest.php
@@ -59,6 +59,7 @@ protected function setUp() {
       'settings[node][page][settings][language][language_alterable]' => 1,
     );
     $this->drupalPostForm('admin/config/regional/content-language', $edit, t('Save configuration'));
+    \Drupal::entityManager()->clearCachedDefinitions();
 
     $definitions = \Drupal::entityManager()->getFieldDefinitions('node', 'page');
     $this->assertTrue($definitions['path']->isTranslatable(), 'Node path is translatable.');
diff --git a/core/modules/taxonomy/src/Tests/TermTranslationFieldViewTest.php b/core/modules/taxonomy/src/Tests/TermTranslationFieldViewTest.php
index fc3267c..9078880 100644
--- a/core/modules/taxonomy/src/Tests/TermTranslationFieldViewTest.php
+++ b/core/modules/taxonomy/src/Tests/TermTranslationFieldViewTest.php
@@ -144,6 +144,7 @@ protected function enableTranslation() {
     drupal_static_reset();
     \Drupal::entityManager()->clearCachedDefinitions();
     \Drupal::service('router.builder')->rebuild();
+    \Drupal::service('entity.definition_update_manager')->applyUpdates();
   }
 
   /**
diff --git a/core/modules/taxonomy/src/Tests/TermTranslationUITest.php b/core/modules/taxonomy/src/Tests/TermTranslationUITest.php
index f96f357..18616e1 100644
--- a/core/modules/taxonomy/src/Tests/TermTranslationUITest.php
+++ b/core/modules/taxonomy/src/Tests/TermTranslationUITest.php
@@ -95,9 +95,9 @@ public function testTranslationUI() {
 
     // Make sure that no row was inserted for taxonomy vocabularies which do
     // not have translations enabled.
-    $rows = db_query('SELECT * FROM {content_translation}')->fetchAll();
+    $rows = db_query('SELECT tid, count(tid) AS count FROM {taxonomy_term_field_data} WHERE vid <> :vid GROUP BY tid', array(':vid' => $this->bundle))->fetchAll();
     foreach ($rows as $row) {
-      $this->assertEqual('taxonomy_term', $row->entity_type, 'Row contains a taxonomy term.');
+      $this->assertTrue($row->count < 2, 'Term does not have translations.');
     }
   }
 
diff --git a/core/modules/taxonomy/src/Tests/Views/TaxonomyTermViewTest.php b/core/modules/taxonomy/src/Tests/Views/TaxonomyTermViewTest.php
index 5899705..9d8233e 100644
--- a/core/modules/taxonomy/src/Tests/Views/TaxonomyTermViewTest.php
+++ b/core/modules/taxonomy/src/Tests/Views/TaxonomyTermViewTest.php
@@ -105,6 +105,7 @@ public function testTaxonomyTermView() {
     drupal_static_reset();
     \Drupal::entityManager()->clearCachedDefinitions();
     \Drupal::service('router.builder')->rebuild();
+    \Drupal::service('entity.definition_update_manager')->applyUpdates();
 
     $edit['title[0][value]'] = $translated_title = $this->randomMachineName();
 
@@ -123,6 +124,7 @@ public function testTaxonomyTermView() {
     // Uninstall language module and ensure that the language is not part of the
     // query anymore.
     // @see \Drupal\views\Plugin\views\filter\LanguageFilter::query()
+    $node->delete();
     \Drupal::service('module_installer')->uninstall(['content_translation', 'language']);
 
     $view = Views::getView('taxonomy_term');
diff --git a/core/modules/user/src/Entity/User.php b/core/modules/user/src/Entity/User.php
index 2ac5fec..65c9273 100644
--- a/core/modules/user/src/Entity/User.php
+++ b/core/modules/user/src/Entity/User.php
@@ -52,7 +52,7 @@
  *     "edit-form" = "entity.user.edit_form",
  *     "cancel-form" = "entity.user.cancel_form",
  *   },
- *   field_ui_base_route = "entity.user.admin_form",
+ *   field_ui_base_route = "entity.user.admin_form"
  * )
  */
 class User extends ContentEntityBase implements UserInterface {
diff --git a/core/modules/user/src/ProfileTranslationHandler.php b/core/modules/user/src/ProfileTranslationHandler.php
index cd8befc..b24e634 100644
--- a/core/modules/user/src/ProfileTranslationHandler.php
+++ b/core/modules/user/src/ProfileTranslationHandler.php
@@ -19,6 +19,22 @@ class ProfileTranslationHandler extends ContentTranslationHandler {
   /**
    * {@inheritdoc}
    */
+  protected function hasPublishedStatus() {
+    // User status has nothing to do with translations visibility.
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function hasCreatedTime() {
+    // User creation date has nothing to do with translation creation date.
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function entityFormAlter(array &$form, FormStateInterface $form_state, EntityInterface $entity) {
     parent::entityFormAlter($form, $form_state, $entity);
     $form['actions']['submit']['#submit'][] = array($this, 'entityFormSave');
@@ -31,7 +47,7 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En
    *
    * @see \Drupal\Core\Entity\EntityForm::build().
    */
-  function entityFormSave(array $form, FormStateInterface $form_state) {
+  public function entityFormSave(array $form, FormStateInterface $form_state) {
     if ($this->getSourceLangcode($form_state)) {
       $entity = $form_state->getFormObject()->getEntity();
       // We need a redirect here, otherwise we would get an access denied page
@@ -40,4 +56,5 @@ function entityFormSave(array $form, FormStateInterface $form_state) {
       $form_state->setRedirectUrl($entity->urlInfo());
     }
   }
+
 }
diff --git a/core/modules/views/src/Tests/SearchMultilingualTest.php b/core/modules/views/src/Tests/SearchMultilingualTest.php
index 4750f0b..ac0ff64 100644
--- a/core/modules/views/src/Tests/SearchMultilingualTest.php
+++ b/core/modules/views/src/Tests/SearchMultilingualTest.php
@@ -55,6 +55,7 @@ public function testMultilingualSearchFilter() {
       'settings[node][' . $type->type . '][fields][body]' => TRUE,
     );
     $this->drupalPostForm('admin/config/regional/content-language', $edit, t('Save configuration'));
+    \Drupal::entityManager()->clearCachedDefinitions();
 
     // Add a node in English, with title "sandwich".
     $values = array(
