diff --git a/core/lib/Drupal/Core/Entity/EntityBCDecorator.php b/core/lib/Drupal/Core/Entity/EntityBCDecorator.php
index 7e941a9..57215cb 100644
--- a/core/lib/Drupal/Core/Entity/EntityBCDecorator.php
+++ b/core/lib/Drupal/Core/Entity/EntityBCDecorator.php
@@ -156,6 +156,13 @@ public function __unset($name) {
   }
 
   /**
+   * Implements the magic method for clone().
+   */
+  function __clone() {
+    $this->decorated = clone $this->decorated;
+  }
+
+  /**
    * Forwards the call to the decorated entity.
    */
   public function access($operation = 'view', \Drupal\user\Plugin\Core\Entity\User $account = NULL) {
diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentTranslationUITest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentTranslationUITest.php
index 60cb474..a8918a0 100644
--- a/core/modules/comment/lib/Drupal/comment/Tests/CommentTranslationUITest.php
+++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentTranslationUITest.php
@@ -91,11 +91,39 @@ protected function getNewEntityValues($langcode) {
     // Comment subject is not translatable hence we use a fixed value.
     return array(
       'subject' => $this->subject,
-      'comment_body' => array(array('value' => $this->randomString(16))),
+      'comment_body' => array(array('value' => $this->randomName(16))),
     ) + parent::getNewEntityValues($langcode);
   }
 
   /**
+   * Overrides \Drupal\translation_entity\Tests\EntityTranslationUITest::assertPublishedStatus().
+   */
+  protected function assertPublishedStatus() {
+    parent::assertPublishedStatus();
+    $entity = entity_load($this->entityType, $this->entityId);
+    $user = $this->drupalCreateUser(array('access comments'));
+    $this->drupalLogin($user);
+    $languages = language_list();
+
+    // Check that simple users cannot see unpublished field translations.
+    $path = $this->controller->getViewPath($entity);
+    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.');
+      }
+    }
+
+    // Login as translator again to ensure subsequent tests do not break.
+    $this->drupalLogin($this->translator);
+  }
+
+  /**
    * Tests translate link on comment content admin page.
    */
   function testTranslateLinkCommentAdminPage() {
diff --git a/core/modules/node/lib/Drupal/node/NodeTranslationController.php b/core/modules/node/lib/Drupal/node/NodeTranslationController.php
index da078ac..a9e6b7c 100644
--- a/core/modules/node/lib/Drupal/node/NodeTranslationController.php
+++ b/core/modules/node/lib/Drupal/node/NodeTranslationController.php
@@ -29,14 +29,20 @@ public function entityFormAlter(array &$form, array &$form_state, EntityInterfac
     parent::entityFormAlter($form, $form_state, $entity);
 
     // Move the translation fieldset to a vertical tab.
-    if (isset($form['translation'])) {
-      $form['translation'] += array(
+    if (isset($form['translation_entity'])) {
+      $form['translation_entity'] += array(
         '#group' => 'additional_settings',
         '#weight' => 100,
         '#attributes' => array(
           'class' => array('node-translation-options'),
         ),
       );
+
+      // We do not need to show these values on node forms: they inherit the
+      // basic node property values.
+      $form['translation_entity']['status']['#access'] = FALSE;
+      $form['translation_entity']['name']['#access'] = FALSE;
+      $form['translation_entity']['created']['#access'] = FALSE;
     }
   }
 
@@ -47,4 +53,19 @@ protected function entityFormTitle(EntityInterface $entity) {
     $type_name = node_get_type_label($entity);
     return t('<em>Edit @type</em> @title', array('@type' => $type_name, '@title' => $entity->label()));
   }
+
+  /**
+   * Overrides EntityTranslationController::entityFormEntityBuild().
+   */
+  public function entityFormEntityBuild($entity_type, EntityInterface $entity, array $form, array &$form_state) {
+    if (isset($form_state['values']['translation_entity'])) {
+      $form_controller = translation_entity_form_controller($form_state);
+      $translation = &$form_state['values']['translation_entity'];
+      $translation['status'] = $form_controller->getEntity($form_state)->status;
+      $translation['name'] = $form_state['values']['name'];
+      $translation['created'] = $form_state['values']['date'];
+    }
+    parent::entityFormEntityBuild($entity_type, $entity, $form, $form_state);
+  }
+
 }
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeTranslationUITest.php b/core/modules/node/lib/Drupal/node/Tests/NodeTranslationUITest.php
index df99749..3c09a44 100644
--- a/core/modules/node/lib/Drupal/node/Tests/NodeTranslationUITest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeTranslationUITest.php
@@ -56,7 +56,84 @@ protected function setupBundle() {
    * Overrides \Drupal\translation_entity\Tests\EntityTranslationUITest::getTranslatorPermission().
    */
   function getTranslatorPermissions() {
-    return array("edit any $this->bundle content", "translate $this->entityType entities", 'edit original values');
+    return array('administer nodes', "edit any $this->bundle content", "translate $this->entityType entities", 'edit original values');
+  }
+
+  /**
+   * Overrides \Drupal\translation_entity\Tests\EntityTranslationUITest::getNewEntityValues().
+   */
+  protected function getNewEntityValues($langcode) {
+    // Node title is not translatable yet, hence we use a fixed value.
+    return array('title' => $this->title) + parent::getNewEntityValues($langcode);
+  }
+
+  /**
+   * Overrides \Drupal\translation_entity\Tests\EntityTranslationUITest::getFormSubmitAction().
+   */
+  protected function getFormSubmitAction() {
+    return t('Save and keep unpublished');
+  }
+
+  /**
+   * Overrides \Drupal\translation_entity\Tests\EntityTranslationUITest::assertPublishedStatus().
+   */
+  protected function assertPublishedStatus() {
+    $entity = entity_load($this->entityType, $this->entityId, TRUE);
+    $path = $this->controller->getEditPath($entity);
+    $languages = language_list();
+
+    $actions = array(
+      array(t('Save and publish'), t('Save and keep published')),
+      array(t('Save and unpublish'), t('Save and keep unpublished')),
+    );
+
+    foreach ($actions as $index => $status_actions) {
+      // (Un)publish the node trnslations and check that the translation
+      // statuses are (un)published accordingly.
+      foreach ($this->langcodes as $langcode) {
+        if (!empty($status_actions)) {
+          $action = array_shift($status_actions);
+        }
+        $this->drupalPost($path, array(), $action, array('language' => $languages[$langcode]));
+      }
+      $entity = entity_load($this->entityType, $this->entityId, TRUE);
+      foreach ($this->langcodes as $langcode) {
+        // The node is created as unpulished thus we switch to the published
+        // status first.
+        $status = !$index;
+        $this->assertEqual($status, $entity->translation[$langcode]['status'], 'The translation has been correctly unpublished.');
+      }
+    }
+  }
+
+  /**
+   * Overrides \Drupal\translation_entity\Tests\EntityTranslationUITest::assertAuthoringInfo().
+   */
+  protected function assertAuthoringInfo() {
+    $entity = entity_load($this->entityType, $this->entityId, TRUE);
+    $path = $this->controller->getEditPath($entity);
+    $languages = language_list();
+    $values = array();
+
+    // Post different authoring information for each translation.
+    foreach ($this->langcodes as $index => $langcode) {
+      $user = $this->drupalCreateUser();
+      $values[$langcode] = array(
+        'uid' => $user->uid,
+        'created' => REQUEST_TIME - mt_rand(0, 1000),
+      );
+      $edit = array(
+        'name' => $user->name,
+        'date' => format_date($values[$langcode]['created'], 'custom', 'Y-m-d H:i:s O'),
+      );
+      $this->drupalPost($path, $edit, $this->getFormSubmitAction(), array('language' => $languages[$langcode]));
+    }
+
+    $entity = entity_load($this->entityType, $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.');
+    }
   }
 
   /**
@@ -98,14 +175,6 @@ function testFieldTranslationForm() {
   }
 
   /**
-   * Overrides \Drupal\translation_entity\Tests\EntityTranslationUITest::getNewEntityValues().
-   */
-  protected function getNewEntityValues($langcode) {
-    // Node title is not translatable yet, hence we use a fixed value.
-    return array('title' => $this->title) + parent::getNewEntityValues($langcode);
-  }
-
-  /**
    * Test that no metadata is stored for a disabled bundle.
    */
   public function testDisabledBundle() {
diff --git a/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationController.php b/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationController.php
index 36202eb..438aa8f 100644
--- a/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationController.php
+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/EntityTranslationController.php
@@ -64,7 +64,7 @@ public function retranslate(EntityInterface $entity, $langcode = NULL) {
     $updated_langcode = !empty($langcode) ? $langcode : $entity->language()->langcode;
     $translations = $entity->getTranslationLanguages();
     foreach ($translations as $langcode => $language) {
-      $entity->retranslate[$langcode] = $langcode != $updated_langcode;
+      $entity->translation[$langcode]['outdated'] = $langcode != $updated_langcode;
     }
   }
 
@@ -213,7 +213,7 @@ public function entityFormAlter(array &$form, array &$form_state, EntityInterfac
     // We need to display the translation tab only when there is at least one
     // translation available or a new one is about to be created.
     if ($new_translation || $has_translations) {
-      $form['translation'] = array(
+      $form['translation_entity'] = array(
         '#type' => 'details',
         '#title' => t('Translation'),
         '#collapsible' => TRUE,
@@ -224,9 +224,35 @@ public function entityFormAlter(array &$form, array &$form_state, EntityInterfac
         '#multilingual' => TRUE,
       );
 
-      $translate = !$new_translation && $entity->retranslate[$form_langcode];
+      // A new translation is enabled by default.
+      $status = $new_translation || $entity->translation[$form_langcode]['status'];
+      // 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;
+        foreach ($entity->translation as $langcode => $translation) {
+          $published += $translation['status'];
+        }
+        $enabled = $published > 1;
+      }
+      $description = $enabled ?
+        t('An unpublished translation will not be visible without translation permissions.') :
+        t('Only this translation is published. You must publish at least one more translation to unpublish this one.');
+
+      $form['translation_entity']['status'] = array(
+        '#type' => 'checkbox',
+        '#title' => t('This translation is published'),
+        '#default_value' => $status,
+        '#description' => $description,
+        '#disabled' => !$enabled,
+      );
+
+      $translate = !$new_translation && $entity->translation[$form_langcode]['outdated'];
       if (!$translate) {
-        $form['translation']['retranslate'] = array(
+        $form['translation_entity']['retranslate'] = array(
           '#type' => 'checkbox',
           '#title' => t('Flag other translations as outdated'),
           '#default_value' => FALSE,
@@ -234,7 +260,7 @@ public function entityFormAlter(array &$form, array &$form_state, EntityInterfac
         );
       }
       else {
-        $form['translation']['translate'] = array(
+        $form['translation_entity']['outdated'] = array(
           '#type' => 'checkbox',
           '#title' => t('This translation needs to be updated'),
           '#default_value' => $translate,
@@ -242,6 +268,25 @@ public function entityFormAlter(array &$form, array &$form_state, EntityInterfac
         );
       }
 
+      $name = $new_translation ? $GLOBALS['user']->name : user_load($entity->translation[$form_langcode]['uid'])->name;
+      $form['translation_entity']['name'] = array(
+        '#type' => 'textfield',
+        '#title' => t('Authored by'),
+        '#maxlength' => 60,
+        '#autocomplete_path' => 'user/autocomplete',
+        '#default_value' => $name,
+        '#description' => t('Leave blank for %anonymous.', array('%anonymous' => variable_get('anonymous', t('Anonymous')))),
+      );
+
+      $date = $new_translation ? REQUEST_TIME : $entity->translation[$form_langcode]['created'];
+      $form['translation_entity']['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'),
+      );
+
       if ($language_widget) {
         $form['langcode']['#multilingual'] = TRUE;
       }
@@ -252,6 +297,11 @@ public function entityFormAlter(array &$form, array &$form_state, EntityInterfac
     // Process the submitted values before they are stored.
     $form['#entity_builders'][] = array($this, 'entityFormEntityBuild');
 
+    // Handle entity validation.
+    if (isset($form['actions']['submit'])) {
+      $form['actions']['submit']['#validate'][] = array($this, 'entityFormValidate');
+    }
+
     // Handle entity deletion.
     if (isset($form['actions']['delete'])) {
       $form['actions']['delete']['#submit'][] = array($this, 'entityFormDelete');
@@ -348,25 +398,51 @@ protected function addTranslatabilityClue(&$element) {
   public function entityFormEntityBuild($entity_type, EntityInterface $entity, array $form, array &$form_state) {
     $form_controller = translation_entity_form_controller($form_state);
     $form_langcode = $form_controller->getFormLangcode($form_state);
-    $source_langcode = $this->getSourceLangcode($form_state);
 
-    if ($source_langcode) {
-      // @todo Use the entity setter when all entities support multilingual
-      // properties.
-      $entity->source[$form_langcode] = $source_langcode;
+    if (!isset($entity->translation[$form_langcode])) {
+      $entity->translation[$form_langcode] = array();
     }
+    $values = isset($form_state['values']['translation_entity']) ? $form_state['values']['translation_entity'] : array();
+    $translation = &$entity->translation[$form_langcode];
 
-    // Ensure every key has at least a default value. Subclasses may provide
-    // entity-specific values to alter them.
-    $values = isset($form_state['values']['translation']) ? $form_state['values']['translation'] : array();
-    $entity->retranslate[$form_langcode] = isset($values['translate']) && $values['translate'];
+    // @todo Use the entity setter when all entities support multilingual
+    // properties.
+    $translation['uid'] = !empty($values['name']) && ($account = user_load_by_name($values['name'])) ? $account->uid : 0;
+    $translation['status'] = !empty($values['status']);
+    $translation['created'] = !empty($values['created']) ? strtotime($values['created']) : REQUEST_TIME;
+    $translation['changed'] = REQUEST_TIME;
 
+    $source_langcode = $this->getSourceLangcode($form_state);
+    if ($source_langcode) {
+      $translation['source'] = $source_langcode;
+    }
+
+    $translation['outdated'] = !empty($values['outdated']);
     if (!empty($values['retranslate'])) {
       $this->retranslate($entity, $form_langcode);
     }
   }
 
   /**
+   * Form validation handler for EntityTranslationController::entityFormAlter().
+   *
+   * Validates the submitted entity translation metadata.
+   */
+  function entityFormValidate($form, &$form_state) {
+    if (!empty($form_state['values']['translation_entity'])) {
+      $translation = $form_state['values']['translation_entity'];
+      // Validate the "authored by" field.
+      if (!empty($translation['name']) && !($account = user_load_by_name($translation['name']))) {
+        form_set_error('translation_entity][name', t('The translation authoring username %name does not exist.', array('%name' => $translation['name'])));
+      }
+      // Validate the "authored on" field.
+      if (!empty($translation['created']) && strtotime($translation['created']) === FALSE) {
+        form_set_error('translation_entity][created', t('You have to specify a valid translation authoring date.'));
+      }
+    }
+  }
+
+  /**
    * Form submission handler for EntityTranslationController::entityFormAlter().
    *
    * Takes care of the source language change.
diff --git a/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationUITest.php b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationUITest.php
index 8c2dcef..493f14f 100644
--- a/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationUITest.php
+++ b/core/modules/translation_entity/lib/Drupal/translation_entity/Tests/EntityTranslationUITest.php
@@ -27,6 +27,13 @@
   protected $langcodes;
 
   /**
+   * The translator account to use for the tests.
+   *
+   * @var \Drupal\user\Plugin\Core\Entity\User
+   */
+  protected $translator;
+
+  /**
    * The entity type being tested.
    *
    * @var string
@@ -41,6 +48,20 @@
   protected $bundle;
 
   /**
+   * The id of the entity being translated.
+   *
+   * @var mixed
+   */
+  protected $entityId;
+
+  /**
+   * The translation controller for the current entity type.
+   *
+   * @var \Drupal\translation_entity\EntityTranslationControllerInterface
+   */
+  protected $controller;
+
+  /**
    * The name of the field used to test translation.
    *
    * @var string
@@ -66,6 +87,8 @@ function setUp() {
     $this->enableTranslation();
     $this->setupTranslator();
     $this->setupTestFields();
+
+    $this->controller = translation_entity_controller($this->entityType);
   }
 
   /**
@@ -109,8 +132,8 @@ protected function enableTranslation() {
    * Creates and activates a translator user.
    */
   protected function setupTranslator() {
-    $translator = $this->drupalCreateUser($this->getTranslatorPermissions());
-    $this->drupalLogin($translator);
+    $this->translator = $this->drupalCreateUser($this->getTranslatorPermissions());
+    $this->drupalLogin($this->translator);
   }
 
   /**
@@ -144,11 +167,22 @@ protected function setupTestFields() {
    * Tests the basic translation UI.
    */
   function testTranslationUI() {
+    $this->assertBasicTranslation();
+    $this->assertOutdatedStatus();
+    $this->assertPublishedStatus();
+    $this->assertAuthoringInfo();
+    $this->assertTranslationDeletion();
+  }
+
+  /**
+   * Tests the basic translation workflow.
+   */
+  protected function assertBasicTranslation() {
     // Create a new test entity with original values in the default language.
     $default_langcode = $this->langcodes[0];
     $values[$default_langcode] = $this->getNewEntityValues($default_langcode);
-    $id = $this->createEntity($values[$default_langcode], $default_langcode);
-    $entity = entity_load($this->entityType, $id, TRUE);
+    $this->entityId = $this->createEntity($values[$default_langcode], $default_langcode);
+    $entity = entity_load($this->entityType, $this->entityId, TRUE);
     $this->assertTrue($entity, t('Entity found in the database.'));
 
     $translation = $this->getTranslation($entity, $default_langcode);
@@ -163,14 +197,13 @@ function testTranslationUI() {
     $langcode = 'it';
     $values[$langcode] = $this->getNewEntityValues($langcode);
 
-    $controller = translation_entity_controller($this->entityType);
-    $base_path = $controller->getBasePath($entity);
+    $base_path = $this->controller->getBasePath($entity);
     $path = $langcode . '/' . $base_path . '/translations/add/' . $default_langcode . '/' . $langcode;
-    $this->drupalPost($path, $this->getEditValues($values, $langcode), t('Save'));
+    $this->drupalPost($path, $this->getEditValues($values, $langcode), $this->getFormSubmitAction());
     if ($this->testLanguageSelector) {
       $this->assertNoFieldByXPath('//select[@id="edit-langcode"]', NULL, 'Language selector correclty disabled on translations.');
     }
-    $entity = entity_load($this->entityType, $entity->id(), TRUE);
+    $entity = entity_load($this->entityType, $this->entityId, TRUE);
 
     // Switch the source language.
     $langcode = 'fr';
@@ -180,11 +213,11 @@ function testTranslationUI() {
     $this->drupalPost($path, $edit, t('Change'));
     $this->assertFieldByXPath("//input[@name=\"{$this->fieldName}[fr][0][value]\"]", $values[$source_langcode][$this->fieldName][0]['value'], 'Source language correctly switched.');
 
-    // Add another translation and mark the other ones as outdated.
+    // Add another translation.
     $values[$langcode] = $this->getNewEntityValues($langcode);
-    $edit = $this->getEditValues($values, $langcode) + array('translation[retranslate]' => TRUE);
-    $this->drupalPost($path, $edit, t('Save'));
-    $entity = entity_load($this->entityType, $entity->id(), TRUE);
+    $edit = $this->getEditValues($values, $langcode);
+    $this->drupalPost($path, $edit, $this->getFormSubmitAction());
+    $entity = entity_load($this->entityType, $this->entityId, TRUE);
 
     // Check that the entered values have been correctly stored.
     foreach ($values as $langcode => $property_values) {
@@ -196,33 +229,118 @@ function testTranslationUI() {
         $this->assertEqual($stored_value, $value, $message);
       }
     }
+  }
+
+  /**
+   * Tests up-to-date status tracking.
+   */
+  protected function assertOutdatedStatus() {
+    $entity = entity_load($this->entityType, $this->entityId, TRUE);
+    $langcode = 'fr';
+    $default_langcode = $this->langcodes[0];
+
+    // Mark translations as outdated.
+    $edit = array('translation_entity[retranslate]' => TRUE);
+    $this->drupalPost($langcode . '/' . $this->controller->getEditPath($entity), $edit, $this->getFormSubmitAction());
+    $entity = entity_load($this->entityType, $this->entityId, TRUE);
 
     // Check that every translation has the correct "outdated" status.
     foreach ($this->langcodes as $enabled_langcode) {
       $prefix = $enabled_langcode != $default_langcode ? $enabled_langcode . '/' : '';
-      $path = $prefix . $controller->getEditPath($entity);
+      $path = $prefix . $this->controller->getEditPath($entity);
       $this->drupalGet($path);
       if ($enabled_langcode == $langcode) {
-        $this->assertFieldByXPath('//input[@name="translation[retranslate]"]', FALSE, 'The retranslate flag is not checked by default.');
+        $this->assertFieldByXPath('//input[@name="translation_entity[retranslate]"]', FALSE, 'The retranslate flag is not checked by default.');
       }
       else {
-        $this->assertFieldByXPath('//input[@name="translation[translate]"]', TRUE, 'The translate flag is checked by default.');
-        $edit = array('translation[translate]' => FALSE);
-        $this->drupalPost($path, $edit, t('Save'));
+        $this->assertFieldByXPath('//input[@name="translation_entity[outdated]"]', TRUE, 'The translate flag is checked by default.');
+        $edit = array('translation_entity[outdated]' => FALSE);
+        $this->drupalPost($path, $edit, $this->getFormSubmitAction());
         $this->drupalGet($path);
-        $this->assertFieldByXPath('//input[@name="translation[retranslate]"]', FALSE, 'The retranslate flag is now shown.');
-        $entity = entity_load($this->entityType, $entity->id(), TRUE);
-        $this->assertFalse($entity->retranslate[$enabled_langcode], 'The "outdated" status has been correctly stored.');
+        $this->assertFieldByXPath('//input[@name="translation_entity[retranslate]"]', FALSE, 'The retranslate flag is now shown.');
+        $entity = entity_load($this->entityType, $this->entityId, TRUE);
+        $this->assertFalse($entity->translation[$enabled_langcode]['outdated'], 'The "outdated" status has been correctly stored.');
       }
     }
+  }
 
+  /**
+   * Tests the translation publishing status.
+   */
+  protected function assertPublishedStatus() {
+    $entity = entity_load($this->entityType, $this->entityId, TRUE);
+    $path = $this->controller->getEditPath($entity);
+
+    // Unpublish translations.
+    foreach ($this->langcodes as $index => $langcode) {
+      if ($index > 0) {
+        $edit = array('translation_entity[status]' => FALSE);
+        $this->drupalPost($langcode . '/' . $path, $edit, $this->getFormSubmitAction());
+        $entity = entity_load($this->entityType, $this->entityId, TRUE);
+        $this->assertFalse($entity->translation[$langcode]['status'], 'The translation has been correctly unpublished.');
+      }
+    }
+
+    // Check that the last published translation cannot be unpublished.
+    $this->drupalGet($path);
+    $this->assertFieldByXPath('//input[@name="translation_entity[status]" and @disabled="disabled"]', TRUE, 'The last translation is published and cannot be unpublished.');
+  }
+
+  /**
+   * Tests the translation authoring information.
+   */
+  protected function assertAuthoringInfo() {
+    $entity = entity_load($this->entityType, $this->entityId, TRUE);
+    $path = $this->controller->getEditPath($entity);
+    $values = array();
+
+    // Post different authoring information for each translation.
+    foreach ($this->langcodes as $index => $langcode) {
+      $user = $this->drupalCreateUser();
+      $values[$langcode] = array(
+        'uid' => $user->uid,
+        'created' => REQUEST_TIME - mt_rand(0, 1000),
+      );
+      $edit = array(
+        'translation_entity[name]' => $user->name,
+        'translation_entity[created]' => format_date($values[$langcode]['created'], 'custom', 'Y-m-d H:i:s O'),
+      );
+      $prefix = $index > 0 ? $langcode . '/' : '';
+      $this->drupalPost($prefix . $path, $edit, $this->getFormSubmitAction());
+    }
+
+    $entity = entity_load($this->entityType, $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.');
+    }
+
+    // Try to post non valid values and check that they are rejected.
+    $langcode = 'en';
+    $edit = array(
+      // User names have by default length 8.
+      'translation_entity[name]' => $this->randomName(12),
+      'translation_entity[created]' => $this->randomName(),
+    );
+    $this->drupalPost($path, $edit, $this->getFormSubmitAction());
+    $this->assertTrue($this->xpath('//div[@id="messages"]//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.');
+  }
+
+  /**
+   * Tests translation deletion.
+   */
+  protected function assertTranslationDeletion() {
     // Confirm and delete a translation.
-    $this->drupalPost($path, array(), t('Delete translation'));
+    $langcode = 'fr';
+    $entity = entity_load($this->entityType, $this->entityId, TRUE);
+    $this->drupalPost($langcode . '/' . $this->controller->getEditPath($entity), array(), t('Delete translation'));
     $this->drupalPost(NULL, array(), t('Delete'));
-    $entity = entity_load($this->entityType, $entity->id(), TRUE);
+    $entity = entity_load($this->entityType, $this->entityId, TRUE);
     if ($this->assertTrue(is_object($entity), 'Entity found')) {
       $translations = $entity->getTranslationLanguages();
-      $this->assertTrue(count($translations) == 2 && empty($translations[$enabled_langcode]), 'Translation successfully deleted.');
+      $this->assertTrue(count($translations) == 2 && empty($translations[$langcode]), 'Translation successfully deleted.');
     }
   }
 
@@ -283,6 +401,13 @@ protected function getEditValues($values, $langcode, $new = FALSE) {
   }
 
   /**
+   * Returns the form action value to be used to submit the entity form.
+   */
+  protected function getFormSubmitAction() {
+    return t('Save');
+  }
+
+  /**
    * Returns the translation object to use to retrieve the translated values.
    *
    * @param \Drupal\Core\Entity\EntityInterface $entity
diff --git a/core/modules/translation_entity/translation_entity.install b/core/modules/translation_entity/translation_entity.install
index b791071..0279b92 100644
--- a/core/modules/translation_entity/translation_entity.install
+++ b/core/modules/translation_entity/translation_entity.install
@@ -40,12 +40,36 @@ function translation_entity_schema() {
         'default' => '',
         'description' => 'The source language from which this translation was created.',
       ),
-      'translate' => array(
+      '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'),
   );
diff --git a/core/modules/translation_entity/translation_entity.module b/core/modules/translation_entity/translation_entity.module
index ee16a9c..18fdbf3 100644
--- a/core/modules/translation_entity/translation_entity.module
+++ b/core/modules/translation_entity/translation_entity.module
@@ -261,6 +261,22 @@ function translation_entity_translate_access(EntityInterface $entity) {
 }
 
 /**
+ * Checks whether the given user can view the specified translation.
+ *
+ * @param \Drupal\Core\Entity\EntityInterface $entity
+ *   The entity whose translation overview should be displayed.
+ * @param $langcode
+ *   The language code of the translation to be displayed.
+ * @param \Drupal\user\Plugin\Core\Entity\User $account
+ *   (optional) The account for which view access should be checked. Defaults to
+ *   the current user.
+ */
+function translation_entity_view_access(EntityInterface $entity, $langcode, $account = NULL) {
+  $entity_type = $entity->entityType();
+  return !empty($entity->translation[$langcode]['status']) || user_access('translate any entity', $account) || user_access("translate $entity_type entities", $account);
+}
+
+/**
  * Access callback for the translation addition page.
  *
  * @param \Drupal\Core\Entity\EntityInterface $entity
@@ -500,7 +516,7 @@ function translation_entity_permission() {
  * Implements hook_form_alter().
  */
 function translation_entity_form_alter(array &$form, array &$form_state) {
-  if (($form_controller = translation_entity_form_controller($form_state)) && ($entity = $form_controller->getEntity($form_state)) && !$entity->isNew()) {
+  if (($form_controller = translation_entity_form_controller($form_state)) && ($entity = $form_controller->getEntity($form_state)) && !$entity->isNew() && translation_entity_enabled($entity->entityType(), $entity->bundle())) {
     $controller = translation_entity_controller($entity->entityType());
     $controller->entityFormAlter($form, $form_state, $entity);
 
@@ -532,6 +548,43 @@ function translation_entity_form_alter(array &$form, array &$form_state) {
 }
 
 /**
+ * Implements hook_field_language_alter().
+ *
+ * Performs language fallback for unaccessible translations.
+ */
+function translation_entity_field_language_alter(&$display_language, $context) {
+  $entity = $context['entity'];
+  $entity_type = $entity->entityType();
+
+  if (isset($entity->translation[$context['langcode']]) && translation_entity_enabled($entity_type, $entity->bundle()) && !translation_entity_view_access($entity, $context['langcode'])) {
+    $instances = field_info_instances($entity_type, $entity->bundle());
+    // Avoid altering the real entity.
+    $entity = clone($entity);
+    $entity_langcode = $entity->language()->langcode;
+
+    foreach ($entity->translation as $langcode => $translation) {
+      if ($langcode == $context['langcode'] || !translation_entity_view_access($entity, $langcode)) {
+        // Unset unaccessible field translations: if the field is untranslatable
+        // unsetting a language different from LANGUAGE_NOT_SPECIFIED has no
+        // effect.
+        foreach ($instances as $instance) {
+          // @todo BC entities have the same value accessibile both with the
+          //   entity language and with LANGUAGE_DEFAULT. We need need to unset
+          //   both until we remove the BC layer.
+          if ($langcode == $entity_langcode) {
+            unset($entity->{$instance['field_name']}[LANGUAGE_DEFAULT]);
+          }
+          unset($entity->{$instance['field_name']}[$langcode]);
+        }
+      }
+    }
+
+    // Find the new fallback values.
+    field_language_fallback($display_language, $entity, $context['langcode']);
+  }
+}
+
+/**
  * Implements hook_entity_load().
  */
 function translation_entity_entity_load(array $entities, $entity_type) {
@@ -546,7 +599,7 @@ function translation_entity_entity_load(array $entities, $entity_type) {
   }
 
   if (!empty($enabled_entities)) {
-    translation_entity_load_translation_data($enabled_entities, $entity_type);
+    translation_entity_load_translation_metadata($enabled_entities, $entity_type);
   }
 }
 
@@ -558,16 +611,18 @@ function translation_entity_entity_load(array $entities, $entity_type) {
  * @param string $entity_type
  *   The type of the entities.
  */
-function translation_entity_load_translation_data(array $entities, $entity_type) {
+function translation_entity_load_translation_metadata(array $entities, $entity_type) {
   $query = 'SELECT * FROM {translation_entity} 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.
-    $entity->source[$record->langcode] = $record->source;
-    // @todo Rename to 'translate' when the column is removed from the node
-    //   schema.
-    $entity->retranslate[$record->langcode] = (boolean) $record->translate;
+    foreach ($record as $field_name => $value) {
+      if (!in_array($field_name, $exclude)) {
+        $entity->translation[$record->langcode][$field_name] = $value;
+      }
+    }
   }
 }
 
@@ -580,16 +635,32 @@ function translation_entity_entity_insert(EntityInterface $entity) {
     return;
   }
 
-  $entity_type = $entity->entityType();
-  $id = $entity->id();
-  $query = db_insert('translation_entity')
-    ->fields(array('entity_type', 'entity_id', 'langcode', 'source', 'translate'));
+  $fields = array('entity_type', 'entity_id', 'langcode', 'source', 'outdated', 'uid', 'status', 'created', 'changed');
+  $query = db_insert('translation_entity')->fields($fields);
 
   foreach ($entity->getTranslationLanguages() as $langcode => $language) {
-    // @todo Declare these as entity (translation?) properties.
-    $source = (isset($entity->source[$langcode]) ? $entity->source[$langcode] : NULL) . '';
-    $retranslate = intval(!empty($entity->retranslate[$langcode]));
-    $query->values(array($entity_type, $id, $langcode, $source, $retranslate));
+    $translation = isset($entity->translation[$langcode]) ? $entity->translation[$langcode] : array();
+
+    $translation += array(
+      'source' => '',
+      'uid' => $GLOBALS['user']->uid,
+      'outdated' => FALSE,
+      'status' => TRUE,
+      'created' => REQUEST_TIME,
+      'changed' => REQUEST_TIME,
+    );
+
+    $translation['entity_type'] = $entity->entityType();
+    $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();
diff --git a/core/modules/translation_entity/translation_entity.pages.inc b/core/modules/translation_entity/translation_entity.pages.inc
index e2e76cc..49bc29b 100644
--- a/core/modules/translation_entity/translation_entity.pages.inc
+++ b/core/modules/translation_entity/translation_entity.pages.inc
@@ -69,7 +69,7 @@ function translation_entity_overview(EntityInterface $entity) {
 
       if (isset($translations[$langcode])) {
         // Existing translation in the translation set: display status.
-        $source = isset($entity->source[$langcode]) ? $entity->source[$langcode] : '';
+        $source = isset($entity->translation[$langcode]['source']) ? $entity->translation[$langcode]['source'] : '';
         $is_original = $langcode == $original;
         $translation = $translations[$langcode];
         $label = $entity->label($langcode);
@@ -85,12 +85,10 @@ function translation_entity_overview(EntityInterface $entity) {
           $links['edit']['title'] = t('edit');
         }
 
-        // @todo Consider supporting the ability to track translation publishing
-        // status independently from entity status, as it may not exist.
-        $translation = $entity->getTranslation($langcode, FALSE);
-        $status = !isset($translation->status) || $translation->status ? t('Published') : t('Not published');
+        $translation = $entity->translation[$langcode];
+        $status = !empty($translation['status']) ? t('Published') : t('Not published');
         // @todo Add a theming function here.
-        $status = '<span class="status">' . $status . '</span>' . (!empty($entity->retranslate[$langcode]) ? ' <span class="marker">' . t('outdated') . '</span>' : '');
+        $status = '<span class="status">' . $status . '</span>' . (!empty($translation['outdated']) ? ' <span class="marker">' . t('outdated') . '</span>' : '');
 
         if ($is_original) {
           $language_name = t('<strong>@language_name</strong>', array('@language_name' => $language_name));
@@ -255,8 +253,10 @@ function translation_entity_delete_confirm_submit(array $form, array &$form_stat
   $entity->save();
 
   // Remove any existing path alias for the removed translation.
+  // @todo This should be taken care of by the Path module.
   if (module_exists('path')) {
-    path_delete(array('source' => $controller->getViewPath($entity), 'langcode' => $language->langcode));
+    $conditions = array('source' => $controller->getViewPath($entity), 'langcode' => $language->langcode);
+    drupal_container()->get('path.crud')->delete($conditions);
   }
 
   $form_state['redirect'] = $controller->getBasePath($entity) . '/translations';
