diff --git a/core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php b/core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php
index 83c1834..16a2eac 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php
@@ -91,7 +91,7 @@ protected function doLoadFieldItems($entities, $age) {
   /**
    * {@inheritdoc}
    */
-  protected function doSaveFieldItems(EntityInterface $entity, $update) {
+  protected function doSaveFieldItems(ContentEntityInterface $entity, array $names) {
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
index 25d4a5a..5986666 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Field\FieldDefinitionInterface;
+use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -158,6 +159,24 @@ public function purgeFieldData(FieldDefinitionInterface $field_definition, $batc
   public function finalizePurge(FieldStorageDefinitionInterface $storage_definition) { }
 
   /**
+   * {@inheritdoc}
+   */
+  protected function doPostSave(EntityInterface $entity, $update) {
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+
+    if ($update && $this->entityType->isTranslatable()) {
+      $this->invokeTranslationHooks($entity);
+    }
+
+    parent::doPostSave($entity, $update);
+
+    // The revision is stored, it should no longer be marked as new now.
+    if ($this->entityType->isRevisionable()) {
+      $entity->setNewRevision(FALSE);
+    }
+  }
+
+  /**
    * Checks translation statuses and invoke the related hooks if needed.
    *
    * @param \Drupal\Core\Entity\ContentEntityInterface $entity
@@ -183,30 +202,86 @@ protected function invokeTranslationHooks(ContentEntityInterface $entity) {
    * {@inheritdoc}
    */
   protected function invokeHook($hook, EntityInterface $entity) {
-    if ($hook == 'presave') {
-      $this->invokeFieldMethod('preSave', $entity);
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+
+    switch ($hook) {
+      case 'presave':
+        $this->invokeFieldMethod('preSave', $entity);
+        break;
+
+      case 'insert':
+        $this->invokeFieldPostSave($entity, FALSE);
+        break;
+
+      case 'update':
+        $this->invokeFieldPostSave($entity, TRUE);
+        break;
     }
+
     parent::invokeHook($hook, $entity);
   }
 
   /**
    * Invokes a method on the Field objects within an entity.
    *
-   * @param string $method
-   *   The method name.
+   * @param string|callable $method
+   *   The name of the method to be invoked or a callable to which processed
+   *   items will be passed.
    * @param \Drupal\Core\Entity\ContentEntityInterface $entity
    *   The entity object.
    */
   protected function invokeFieldMethod($method, ContentEntityInterface $entity) {
+    $method_is_callable = is_callable($method);
     foreach (array_keys($entity->getTranslationLanguages()) as $langcode) {
       $translation = $entity->getTranslation($langcode);
-      foreach ($translation->getFields() as $field) {
-        $field->$method();
+      foreach ($translation->getFields() as $name => $items) {
+        if ($method_is_callable) {
+          call_user_func($method, $items, $name, $langcode);
+        }
+        else {
+          $items->$method();
+        }
       }
     }
   }
 
   /**
+   * Invokes the post save method on the Field objects within an entity.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   The entity object.
+   * @param bool $update
+   *   Specifies whether the entity is being updated or created.
+   */
+  protected function invokeFieldPostSave(ContentEntityInterface $entity, $update) {
+    $fields = [];
+
+    $this->invokeFieldMethod(
+      function (FieldItemListInterface $items, $name) use (&$fields, $update) {
+        if ($items->postSave($update)) {
+          $fields[$name] = $name;
+        }
+      },
+      $entity
+    );
+
+
+    if ($fields) {
+      $this->doSaveFieldItems($entity, $fields);
+    }
+  }
+
+  /**
+   * Writes entity field values to the storage.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   The entity object.
+   * @param string[] $names
+   *   The name of the fields to be written.
+   */
+  abstract protected function doSaveFieldItems(ContentEntityInterface $entity, array $names);
+
+  /**
    * Checks whether the field values changed compared to the original entity.
    *
    * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
index 55d5317..b25ec4a 100644
--- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
@@ -410,21 +410,9 @@ public function save(EntityInterface $entity) {
 
     // Perform the save and reset the static cache for the changed entity.
     $return = $this->doSave($id, $entity);
-    $this->resetCache(array($id));
-
-    // The entity is no longer new.
-    $entity->enforceIsNew(FALSE);
 
-    // Allow code to run after saving.
-    $entity->postSave($this, !$is_new);
-    $this->invokeHook($is_new ? 'insert' : 'update', $entity);
-
-    // After saving, this is now the "original entity", and subsequent saves
-    // will be updates instead of inserts, and updates must always be able to
-    // correctly identify the original entity.
-    $entity->setOriginalId($entity->id());
-
-    unset($entity->original);
+    // Execute post save logic and invoke the related hooks.
+    $this->doPostSave($entity, !$is_new);
 
     return $return;
   }
@@ -444,6 +432,32 @@ public function save(EntityInterface $entity) {
   abstract protected function doSave($id, EntityInterface $entity);
 
   /**
+   * Performs post save entity processing.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The saved entity.
+   * @param $update
+   *   Specifies whether the entity is being updated or created.
+   */
+  protected function doPostSave(EntityInterface $entity, $update) {
+    $this->resetCache(array($entity->id()));
+
+    // The entity is no longer new.
+    $entity->enforceIsNew(FALSE);
+
+    // Allow code to run after saving.
+    $entity->postSave($this, $update);
+    $this->invokeHook($update ? 'update' : 'insert', $entity);
+
+    // After saving, this is now the "original entity", and subsequent saves
+    // will be updates instead of inserts, and updates must always be able to
+    // correctly identify the original entity.
+    $entity->setOriginalId($entity->id());
+
+    unset($entity->original);
+  }
+
+  /**
    * Builds an entity query.
    *
    * @param \Drupal\Core\Entity\Query\QueryInterface $entity_query
diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
index c780267..0490598 100644
--- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
@@ -990,17 +990,64 @@ protected function doSave($id, EntityInterface $entity) {
         $this->saveToSharedTables($entity, $this->revisionDataTable);
       }
     }
-    $this->invokeFieldMethod($is_new ? 'insert' : 'update', $entity);
     $this->saveToDedicatedTables($entity, !$is_new);
 
-    if (!$is_new && $this->dataTable) {
-      $this->invokeTranslationHooks($entity);
+    return $return;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function doSaveFieldItems(ContentEntityInterface $entity, array $names) {
+    $table_mapping = $this->getTableMapping();
+    $storage_definitions = $this->entityManager->getFieldStorageDefinitions($this->entityTypeId);
+    $shared_table_fields = FALSE;
+    $dedicated_table_fields = [];
+
+    // Collect the name of fields to be written in dedicated tables and check
+    // whether shared table records need to be updated.
+    foreach ($names as $name) {
+      $storage_definition = $storage_definitions[$name];
+      if ($table_mapping->allowsSharedTableStorage($storage_definition)) {
+        $shared_table_fields = TRUE;
+      }
+      elseif ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
+        $dedicated_table_fields[] = $name;
+      }
     }
-    $entity->enforceIsNew(FALSE);
-    if ($this->revisionTable) {
-      $entity->setNewRevision(FALSE);
+
+    // Update shared table records if necessary.
+    if ($shared_table_fields) {
+      $default_revision = $entity->isDefaultRevision();
+      if ($default_revision) {
+        $record = $this->mapToStorageRecord($entity->getUntranslated(), $this->baseTable);
+        $this->database
+          ->update($this->baseTable)
+          ->fields((array) $record)
+          ->condition($this->idKey, $record->{$this->idKey})
+          ->execute();
+      }
+      if ($this->revisionTable) {
+        $record = $this->mapToStorageRecord($entity->getUntranslated(), $this->revisionTable);
+        $entity->preSaveRevision($this, $record);
+        $this->database
+          ->update($this->revisionTable)
+          ->fields((array) $record)
+          ->condition($this->revisionKey, $record->{$this->revisionKey})
+          ->execute();
+      }
+      if ($default_revision && $this->dataTable) {
+        $this->saveToSharedTables($entity);
+      }
+      if ($this->revisionDataTable) {
+        $this->saveToSharedTables($entity, $this->revisionDataTable, FALSE);
+      }
+    }
+
+    // Update dedicated table records if necessary.
+    if ($dedicated_table_fields) {
+      $this->saveToDedicatedTables($entity, TRUE, $dedicated_table_fields);
     }
-    return $return;
   }
 
   /**
@@ -1017,14 +1064,20 @@ protected function has($id, EntityInterface $entity) {
    *   The entity object.
    * @param string $table_name
    *   (optional) The table name to save to. Defaults to the data table.
+   * @param bool $new_revision
+   *   (optional) Whether we are dealing with a new revision. By default fetches
+   *   the information from the entity object.
    */
-  protected function saveToSharedTables(ContentEntityInterface $entity, $table_name = NULL) {
+  protected function saveToSharedTables(ContentEntityInterface $entity, $table_name = NULL, $new_revision = NULL) {
     if (!isset($table_name)) {
       $table_name = $this->dataTable;
     }
+    if (!isset($new_revision)) {
+      $new_revision = $entity->isNewRevision();
+    }
     $revision = $table_name != $this->dataTable;
 
-    if (!$revision || !$entity->isNewRevision()) {
+    if (!$revision || !$new_revision) {
       $key = $revision ? $this->revisionKey : $this->idKey;
       $value = $revision ? $entity->getRevisionId() : $entity->id();
       // Delete and insert to handle removed values.
@@ -1301,8 +1354,11 @@ protected function loadFromDedicatedTables(array &$values, $load_from_revision)
    *   The entity.
    * @param bool $update
    *   TRUE if the entity is being updated, FALSE if it is being inserted.
+   * @param string[] $names
+   *   (optional) The names of the fields to be stored. Defaults to all the
+   *   available fields.
    */
-  protected function saveToDedicatedTables(ContentEntityInterface $entity, $update = TRUE) {
+  protected function saveToDedicatedTables(ContentEntityInterface $entity, $update = TRUE, $names = array()) {
     $vid = $entity->getRevisionId();
     $id = $entity->id();
     $bundle = $entity->bundle();
@@ -1317,7 +1373,13 @@ protected function saveToDedicatedTables(ContentEntityInterface $entity, $update
 
     $original = !empty($entity->original) ? $entity->original: NULL;
 
-    foreach ($this->entityManager->getFieldDefinitions($entity_type, $bundle) as $field_name => $field_definition) {
+    // Determine which fields should be actually stored.
+    $definitions = $this->entityManager->getFieldDefinitions($entity_type, $bundle);
+    if ($names) {
+      $definitions = array_intersect_key($definitions, array_flip($names));
+    }
+
+    foreach ($definitions as $field_name => $field_definition) {
       $storage_definition = $field_definition->getFieldStorageDefinition();
       if (!$table_mapping->requiresDedicatedTableStorage($storage_definition)) {
         continue;
diff --git a/core/lib/Drupal/Core/Field/FieldItemBase.php b/core/lib/Drupal/Core/Field/FieldItemBase.php
index fd521a9..c1441bb 100644
--- a/core/lib/Drupal/Core/Field/FieldItemBase.php
+++ b/core/lib/Drupal/Core/Field/FieldItemBase.php
@@ -201,12 +201,9 @@ public function preSave() { }
   /**
    * {@inheritdoc}
    */
-  public function insert() { }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function update() { }
+  public function postSave($update) {
+    return FALSE;
+  }
 
   /**
    * {@inheritdoc}
diff --git a/core/lib/Drupal/Core/Field/FieldItemInterface.php b/core/lib/Drupal/Core/Field/FieldItemInterface.php
index a2339ac..afd1371 100644
--- a/core/lib/Drupal/Core/Field/FieldItemInterface.php
+++ b/core/lib/Drupal/Core/Field/FieldItemInterface.php
@@ -189,20 +189,19 @@ public function view($display_options = array());
   public function preSave();
 
   /**
-   * Defines custom insert behavior for field values.
+   * Defines custom post-save behavior for field values.
    *
-   * This method is called during the process of inserting an entity, just
-   * before values are written into storage.
-   */
-  public function insert();
-
-  /**
-   * Defines custom update behavior for field values.
-   *
-   * This method is called during the process of updating an entity, just before
+   * This method is called during the process of saving an entity, just after
    * values are written into storage.
+   *
+   * @param bool $update
+   *   Specifies whether the entity is being updated or created.
+   *
+   * @return bool
+   *   Whether field items should be rewritten to the storage as a consequence
+   *   of the logic implemented by the custom behavior.
    */
-  public function update();
+  public function postSave($update);
 
   /**
    * Defines custom delete behavior for field values.
diff --git a/core/lib/Drupal/Core/Field/FieldItemList.php b/core/lib/Drupal/Core/Field/FieldItemList.php
index aa10c6f..a895d4f 100644
--- a/core/lib/Drupal/Core/Field/FieldItemList.php
+++ b/core/lib/Drupal/Core/Field/FieldItemList.php
@@ -7,14 +7,12 @@
 
 namespace Drupal\Core\Field;
 
-use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Session\AccountInterface;
-use Drupal\Core\TypedData\DataDefinitionInterface;
 use Drupal\Core\TypedData\Plugin\DataType\ItemList;
-use Drupal\Core\TypedData\TypedDataInterface;
 
 /**
  * Represents an entity field; that is, a list of field item objects.
@@ -212,15 +210,11 @@ public function preSave() {
   /**
    * {@inheritdoc}
    */
-  public function insert() {
-    $this->delegateMethod('insert');
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function update() {
-    $this->delegateMethod('update');
+  public function postSave($update) {
+    $result = $this->delegateMethod(function(FieldItemInterface $item) use ($update) {
+      return $item->postSave($update);
+    });
+    return (bool) array_filter($result);
   }
 
   /**
@@ -240,13 +234,20 @@ public function deleteRevision() {
   /**
    * Calls a method on each FieldItem.
    *
-   * @param string $method
-   *   The name of the method.
+   * @param string|callable $method
+   *   The name of the method to be invoked or a callable to which processed
+   *   items will be passed.
+   *
+   * @return array
+   *   An array of results keyed by delta.
    */
   protected function delegateMethod($method) {
-    foreach ($this->list as $item) {
-      $item->{$method}();
+    $result = [];
+    $method_is_callable = is_callable($method);
+    foreach ($this->list as $delta => $item) {
+      $result[$delta] = $method_is_callable ? $method($item) : $item->{$method}();
     }
+    return $result;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Field/FieldItemListInterface.php b/core/lib/Drupal/Core/Field/FieldItemListInterface.php
index e4ea12c..18609cf 100644
--- a/core/lib/Drupal/Core/Field/FieldItemListInterface.php
+++ b/core/lib/Drupal/Core/Field/FieldItemListInterface.php
@@ -136,20 +136,19 @@ public function __unset($property_name);
   public function preSave();
 
   /**
-   * Defines custom insert behavior for field values.
+   * Defines custom post-save behavior for field values.
    *
-   * This method is called after the save() method, and before values are
-   * written into storage.
-   */
-  public function insert();
-
-  /**
-   * Defines custom update behavior for field values.
+   * This method is called during the process of saving an entity, just after
+   * values are written into storage.
    *
-   * This method is called after the save() method, and before values are
-   * written into storage.
+   * @param bool $update
+   *   Specifies whether the entity is being updated or created.
+   *
+   * @return bool
+   *   Whether field items should be rewritten to the storage as a consequence
+   *   of the logic implemented by the custom behavior.
    */
-  public function update();
+  public function postSave($update);
 
   /**
    * Defines custom delete behavior for field values.
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/ChangedItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/ChangedItem.php
index 1a120f8..4add194 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/ChangedItem.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/ChangedItem.php
@@ -26,50 +26,148 @@
 class ChangedItem extends CreatedItem {
 
   /**
+   * The unchanged original entity.
+   *
+   * @var \Drupal\Core\Entity\ContentEntityInterface
+   */
+  protected $originalEntity = NULL;
+
+  /**
+   * Tracking of "new" changed timestamps during save.
+   *
+   * Two revisions of an entity could have the same changed timestamp if both
+   * revisions have been saved within the same second. Therefore we need to
+   * track these "changes" differently than just comparing values.
+   *
+   * @var array
+   */
+  protected $changedOnPreSave = array();
+
+  /**
    * {@inheritdoc}
    */
   public function preSave() {
     parent::preSave();
 
+    $entity = $this->getEntity();
+    if (!$entity->isNew()) {
+      // Clone $entity->original to avoid modifying it when calling
+      // getTranslation(). This property only exists during save. See
+      // Drupal\Core\Entity\EntityStorageBase::save().
+      $this->originalEntity = clone $entity->original;
+    }
+    $this->changedOnPreSave[$entity->language()->getId()] = FALSE;
+
     // Set the timestamp to request time if it is not set.
-    if (!$this->value) {
+    // On an existing entity the changed timestamp will only be set to request
+    // time automatically if at least one other field value of the entity has
+    // changed. This detection doesn't run on new entities and will be turned
+    // off if the changed timestamp is set manually before save, for example
+    // during migrations or using
+    // \Drupal\content_translation\ContentTranslationMetadataWrapperInterface::setChangedTime().
+    if (!$this->value || (!$entity->isNew() && $this->hasEntityChanges())) {
       $this->value = REQUEST_TIME;
+      $this->changedOnPreSave[$entity->language()->getId()] = TRUE;
     }
-    else {
-      // On an existing entity the changed timestamp will only be set to request
-      // time automatically if at least one other field value of the entity has
-      // changed. This detection doesn't run on new entities and will be turned
-      // off if the changed timestamp is set manually before save, for example
-      // during migrations or using
-      // \Drupal\content_translation\ContentTranslationMetadataWrapperInterface::setChangedTime().
-      // @todo Knowing if the current translation was modified or not is
-      //   generally useful. There's a follow-up issue to reduce the nesting
-      //   here and to offer an accessor for this information. See
-      //   https://www.drupal.org/node/2453153
-      $entity = $this->getEntity();
-      if (!$entity->isNew()) {
-        $field_name = $this->getFieldDefinition()->getName();
-        // Clone $entity->original to avoid modifying it when calling
-        // getTranslation().
-        $original = clone $entity->original;
-        $translatable = $this->getFieldDefinition()->isTranslatable();
-        if ($translatable) {
-          $original = $original->getTranslation($entity->language()->getId());
-        }
-        if ($this->value == $original->get($field_name)->value) {
-          foreach ($entity->getFieldDefinitions() as $other_field_name => $other_field_definition) {
-            if ($other_field_name != $field_name && !$other_field_definition->isComputed() && (!$translatable || $other_field_definition->isTranslatable())) {
-              $items = $entity->get($other_field_name)->filterEmptyItems();
-              $original_items = $original->get($other_field_name)->filterEmptyItems();
-              if (!$items->equals($original_items)) {
-                $this->value = REQUEST_TIME;
-                break;
-              }
-            }
+  }
+
+  /**
+   * Determines if the field value has changed during save.
+   *
+   * Two revisions of an entity could have the same changed timestamp if both
+   * revisions have been saved within the same second. Using
+   * hasChangedOnPreSave() it's possible to track these "changes" differently
+   * than just comparing values.
+   * 
+   * @return bool
+   *   TRUE if the entity has changes, FALSE otherwise.
+   */
+  public function hasChangedOnPreSave() {
+    $entity = $this->getEntity();
+    if (!isset($this->changedOnPreSave[$entity->language()->getId()])) {
+      throw new \LogicException('hasChangedOnPreSave() must not be called on a new entity translation unless it has been saved.');
+    }
+    return $this->changedOnPreSave[$entity->language()->getId()];
+  }
+
+  /**
+   * Determines if the field value has changed across translations during save.
+   *
+   * Two revisions of an entity could have the same changed timestamp if both
+   * revisions have been saved within the same second. Using
+   * hasChangedOnPreSaveAcrossTranslations() it's possible to track these
+   * "changes" differently than just comparing values.
+   * 
+   * @return bool
+   *   TRUE if the entity has changes, FALSE otherwise.
+   */
+  public function hasChangedOnPreSaveAcrossTranslations() {
+    return array_reduce($this->changedOnPreSave, function($result, $changed) {
+        return $result | $changed;      
+      },
+      FALSE);
+  }
+
+  /**
+   * Determines if the entity has changes.
+   *
+   * @return bool
+   *   TRUE if the entity has changes, FALSE otherwise.
+   */
+  public function hasEntityChanges() {
+    $entity = $this->getEntity();
+    if ($entity->isNew()) {
+      return TRUE;
+    }
+
+    if (!$this->originalEntity) {
+      $id = ($entity->getOriginalId() !== NULL) ? $entity->getOriginalId() : $entity->id();
+      // The originalEntity will be reset by $this->preSave().
+      $this->originalEntity = \Drupal::entityManager()->getStorage($entity->getEntityTypeId())->loadUnchanged($id);
+    }
+    $field_name = $this->getFieldDefinition()->getName();
+    $translatable = $this->getFieldDefinition()->isTranslatable();
+    $original = $translatable ? $this->originalEntity->getTranslation($entity->language()->getId()) : $this->originalEntity;
+
+    if ($this->value == $original->get($field_name)->value) {
+      foreach ($entity->getFieldDefinitions() as $other_field_name => $other_field_definition) {
+        if ($other_field_name != $field_name && !$other_field_definition->isComputed() && (!$translatable || $other_field_definition->isTranslatable())) {
+          $items = $entity->get($other_field_name)->filterEmptyItems();
+          $original_items = $original->get($other_field_name)->filterEmptyItems();
+          if (!$items->equals($original_items)) {
+            return TRUE;
           }
         }
       }
     }
+    else {
+      return TRUE;
+    }
+
+    return FALSE;
   }
 
-}
+  /**
+   * Determines if the entity has changes across all translations.
+   *
+   * @return bool
+   *   TRUE if the entity has changes across translations, FALSE otherwise.
+   */
+  public function hasEntityChangesAcrossTranslations() {
+    if (!$this->getFieldDefinition()->isTranslatable()) {
+      return $this->hasEntityChanges();
+    }
+    else {
+      $entity = $this->getEntity();
+      $field_name = $this->getFieldDefinition()->getName();
+      foreach ($entity->getTranslations() as $language) {
+        $translation = $entity->getTranslation($language->getId());
+        if ($translation->get($field_name)->first()->hasEntityChanges()) {
+          return TRUE;
+        }
+      }
+    }
+
+    return FALSE;
+  }
+}
\ No newline at end of file
diff --git a/core/modules/block_content/tests/modules/block_content_test/block_content_test.module b/core/modules/block_content/tests/modules/block_content_test/block_content_test.module
index d714cb2..3450b5f 100644
--- a/core/modules/block_content/tests/modules/block_content_test/block_content_test.module
+++ b/core/modules/block_content/tests/modules/block_content_test/block_content_test.module
@@ -60,6 +60,7 @@ function block_content_test_block_content_insert(BlockContent $block_content) {
   // Set the block_content title to the block_content ID and save.
   if ($block_content->label() == 'new') {
     $block_content->setInfo('BlockContent ' . $block_content->id());
+    $block_content->setNewRevision(FALSE);
     $block_content->save();
   }
   if ($block_content->label() == 'fail_creation') {
diff --git a/core/modules/content_translation/src/ContentTranslationHandler.php b/core/modules/content_translation/src/ContentTranslationHandler.php
index 76e05bd..9211cec 100644
--- a/core/modules/content_translation/src/ContentTranslationHandler.php
+++ b/core/modules/content_translation/src/ContentTranslationHandler.php
@@ -104,6 +104,15 @@ public function getFieldDefinitions() {
       ->setRevisionable(TRUE)
       ->setTranslatable(TRUE);
 
+    $definitions['content_translation_revision'] = BaseFieldDefinition::create('content_translation_revision_pointer')
+      ->setLabel(t('Translation revision'))
+      ->setDescription(t('Revision ID the translation has been edited in.'))
+      ->setSetting('unsigned', TRUE)
+      ->setSetting('field_name', $this->entityType->getKey('revision'))
+      ->setDefaultValue(0)
+      ->setRevisionable(TRUE)
+      ->setTranslatable(TRUE);
+
     if (!$this->hasAuthor()) {
       $definitions['content_translation_uid'] = BaseFieldDefinition::create('entity_reference')
         ->setLabel(t('Translation author'))
diff --git a/core/modules/content_translation/src/Plugin/Field/FieldType/ContentTranslationRevisionPointerItem.php b/core/modules/content_translation/src/Plugin/Field/FieldType/ContentTranslationRevisionPointerItem.php
new file mode 100644
index 0000000..6787de2
--- /dev/null
+++ b/core/modules/content_translation/src/Plugin/Field/FieldType/ContentTranslationRevisionPointerItem.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\content_translation\Plugin\Field\FieldType\ContentTranslationRevisionPointerItem.
+ */
+
+namespace Drupal\content_translation\Plugin\Field\FieldType;
+
+use Drupal\Core\Field\Plugin\Field\FieldType\IntegerItem;
+
+/**
+ * Defines the 'content_translation_revision_pointer' field type.
+ *
+ * @FieldType(
+ *   id = "content_translation_revision_pointer",
+ *   label = @Translation("Revision Pointer"),
+ *   description = @Translation("The revision ID a content translation belongs to."),
+ *   no_ui = TRUE,
+ *   default_formatter = "integer"
+ * )
+ */
+class ContentTranslationRevisionPointerItem extends IntegerItem {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function defaultFieldSettings() {
+    return array(
+      'field_name' => 'changed',
+    ) + parent::defaultFieldSettings();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __get($name) {
+    // Ensure a valid integer value in the sql storage instead of NULL.
+    return parent::__get($name) ?: 0;
+  }
+  
+  /**
+   * {@inheritdoc}
+   */
+  public function postSave($update) {
+    $entity = $this->getEntity();
+    $field_name = $entity->hasField('content_translation_changed') ? 'content_translation_changed' : 'changed';
+    if (!$update || $entity->get($field_name)->first()->hasChangedOnPreSave()) {
+      $settings = $this->getSettings();
+      $this->value = $entity->get($settings['field_name'])->value;
+      return TRUE;
+    }
+
+    return FALSE;
+  }
+
+}
diff --git a/core/modules/file/src/Plugin/Field/FieldType/FileFieldItemList.php b/core/modules/file/src/Plugin/Field/FieldType/FileFieldItemList.php
index f8ee03c..5626ee5 100644
--- a/core/modules/file/src/Plugin/Field/FieldType/FileFieldItemList.php
+++ b/core/modules/file/src/Plugin/Field/FieldType/FileFieldItemList.php
@@ -23,61 +23,59 @@ public function defaultValuesForm(array &$form, FormStateInterface $form_state)
   /**
    * {@inheritdoc}
    */
-  public function insert() {
-    parent::insert();
+  public function postSave($update) {
+    $resave = parent::postSave($update);
     $entity = $this->getEntity();
 
-    // Add a new usage for newly uploaded files.
-    foreach ($this->referencedEntities() as $file) {
-      \Drupal::service('file.usage')->add($file, 'file', $entity->getEntityTypeId(), $entity->id());
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function update() {
-    parent::update();
-    $entity = $this->getEntity();
-
-    // Get current target file entities and file IDs.
-    $files = $this->referencedEntities();
-    $fids = array();
-
-    foreach ($files as $file) {
-      $fids[] = $file->id();
+    if (!$update) {
+      // Add a new usage for newly uploaded files.
+      foreach ($this->referencedEntities() as $file) {
+        \Drupal::service('file.usage')->add($file, 'file', $entity->getEntityTypeId(), $entity->id());
+      }
     }
+    else {
+      // Get current target file entities and file IDs.
+      $files = $this->referencedEntities();
+      $ids = array();
 
-    // On new revisions, all files are considered to be a new usage and no
-    // deletion of previous file usages are necessary.
-    if (!empty($entity->original) && $entity->getRevisionId() != $entity->original->getRevisionId()) {
+      /** @var \Drupal\file\Entity\File $file */
       foreach ($files as $file) {
-        \Drupal::service('file.usage')->add($file, 'file', $entity->getEntityTypeId(), $entity->id());
+        $ids[] = $file->id();
       }
-      return;
-    }
 
-    // Get the file IDs attached to the field before this update.
-    $field_name = $this->getFieldDefinition()->getName();
-    $original_fids = array();
-    $original_items = $entity->original->getTranslation($this->getLangcode())->$field_name;
-    foreach ($original_items as $item) {
-      $original_fids[] = $item->target_id;
-    }
+      // On new revisions, all files are considered to be a new usage and no
+      // deletion of previous file usages are necessary.
+      if (!empty($entity->original) && $entity->getRevisionId() != $entity->original->getRevisionId()) {
+        foreach ($files as $file) {
+          \Drupal::service('file.usage')->add($file, 'file', $entity->getEntityTypeId(), $entity->id());
+        }
+        return;
+      }
 
-    // Decrement file usage by 1 for files that were removed from the field.
-    $removed_fids = array_filter(array_diff($original_fids, $fids));
-    $removed_files = \Drupal::entityManager()->getStorage('file')->loadMultiple($removed_fids);
-    foreach ($removed_files as $file) {
-      \Drupal::service('file.usage')->delete($file, 'file', $entity->getEntityTypeId(), $entity->id());
-    }
+      // Get the file IDs attached to the field before this update.
+      $field_name = $this->getFieldDefinition()->getName();
+      $original_ids = array();
+      $original_items = $entity->original->getTranslation($this->getLangcode())->$field_name;
+      foreach ($original_items as $item) {
+        $original_ids[] = $item->target_id;
+      }
 
-    // Add new usage entries for newly added files.
-    foreach ($files as $file) {
-      if (!in_array($file->id(), $original_fids)) {
-        \Drupal::service('file.usage')->add($file, 'file', $entity->getEntityTypeId(), $entity->id());
+      // Decrement file usage by 1 for files that were removed from the field.
+      $removed_ids = array_filter(array_diff($original_ids, $ids));
+      $removed_files = \Drupal::entityManager()->getStorage('file')->loadMultiple($removed_ids);
+      foreach ($removed_files as $file) {
+        \Drupal::service('file.usage')->delete($file, 'file', $entity->getEntityTypeId(), $entity->id());
+      }
+
+      // Add new usage entries for newly added files.
+      foreach ($files as $file) {
+        if (!in_array($file->id(), $original_ids)) {
+          \Drupal::service('file.usage')->add($file, 'file', $entity->getEntityTypeId(), $entity->id());
+        }
       }
     }
+
+    return $resave;
   }
 
   /**
diff --git a/core/modules/node/node.routing.yml b/core/modules/node/node.routing.yml
index 9f3af90..aea8965 100644
--- a/core/modules/node/node.routing.yml
+++ b/core/modules/node/node.routing.yml
@@ -68,6 +68,16 @@ node.revision_revert_confirm:
   options:
     _node_operation_route: TRUE
 
+node.revision_revert_language_confirm:
+  path: '/node/{node}/revisions/{node_revision}/revert_language/{langcode}'
+  defaults:
+    _form: '\Drupal\node\Form\NodeRevisionRevertLanguageForm'
+    _title: 'Revert to earlier revision for given language only'
+  requirements:
+    _access_node_revision: 'update'
+  options:
+    _node_operation_route: TRUE
+
 node.revision_delete_confirm:
   path: '/node/{node}/revisions/{node_revision}/delete'
   defaults:
diff --git a/core/modules/node/src/Controller/NodeController.php b/core/modules/node/src/Controller/NodeController.php
index 1cb43be..3bb42c4 100644
--- a/core/modules/node/src/Controller/NodeController.php
+++ b/core/modules/node/src/Controller/NodeController.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Controller\ControllerBase;
 use Drupal\Core\Datetime\DateFormatter;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Render\RendererInterface;
 use Drupal\Core\Url;
 use Drupal\node\NodeTypeInterface;
@@ -38,16 +39,26 @@ class NodeController extends ControllerBase implements ContainerInjectionInterfa
   protected $renderer;
 
   /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languageManager;
+
+  /**
    * Constructs a NodeController object.
    *
    * @param \Drupal\Core\Datetime\DateFormatter $date_formatter
    *   The date formatter service.
    * @param \Drupal\Core\Render\RendererInterface $renderer
    *   The renderer service.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
    */
-  public function __construct(DateFormatter $date_formatter, RendererInterface $renderer) {
+  public function __construct(DateFormatter $date_formatter, RendererInterface $renderer, LanguageManagerInterface $language_manager) {
     $this->dateFormatter = $date_formatter;
     $this->renderer = $renderer;
+    $this->languageManager = $language_manager;
   }
 
   /**
@@ -56,7 +67,8 @@ public function __construct(DateFormatter $date_formatter, RendererInterface $re
   public static function create(ContainerInterface $container) {
     return new static(
       $container->get('date.formatter'),
-      $container->get('renderer')
+      $container->get('renderer'),
+      $container->get('language_manager')
     );
   }
 
@@ -162,7 +174,12 @@ public function revisionOverview(NodeInterface $node) {
 
     $build = array();
     $build['#title'] = $this->t('Revisions for %title', array('%title' => $node->label()));
-    $header = array($this->t('Revision'), $this->t('Operations'));
+
+    $header[] = $this->t('Revision');
+    if ($node->isTranslatable()) {
+      $header[] = $this->t('Languages Edited');
+    }
+    $header[] = $this->t('Operations');
 
     $revert_permission = (($account->hasPermission("revert $type revisions") || $account->hasPermission('revert all revisions') || $account->hasPermission('administer nodes')) && $node->access('update'));
     $delete_permission =  (($account->hasPermission("delete $type revisions") || $account->hasPermission('delete all revisions') || $account->hasPermission('administer nodes')) && $node->access('delete'));
@@ -174,8 +191,17 @@ public function revisionOverview(NodeInterface $node) {
     foreach (array_reverse($vids) as $vid) {
       if ($revision = $node_storage->loadRevision($vid)) {
         $row = array();
+        $links = array();
 
         $revision_author = $revision->uid->entity;
+        $language_names = array();
+        foreach ($revision->getTranslationLanguages() as $language) {
+          $translation = $revision->getTranslation($language->getId());
+          if ($translation->content_translation_revision->value == $revision->vid->value) {
+            $language_names[$language->getId()] = $this->languageManager->getLanguageName($language->getId());
+          }
+        }
+        asort($language_names);
 
         if ($vid == $node->getRevisionId()) {
           $username = array(
@@ -185,6 +211,11 @@ public function revisionOverview(NodeInterface $node) {
           $row[] = array('data' => $this->t('!date by !username', array('!date' => $node->link($this->dateFormatter->format($revision->revision_timestamp->value, 'short')), '!username' => drupal_render($username)))
             . (($revision->revision_log->value != '') ? '<p class="revision-log">' . Xss::filter($revision->revision_log->value) . '</p>' : ''),
             'class' => array('revision-current'));
+
+          if ($node->isTranslatable()) {
+            $row[] = array('data' => implode(', ', $language_names), 'class' => array('revision-current'));
+          }
+
           $row[] = array('data' => SafeMarkup::placeholder($this->t('current revision')), 'class' => array('revision-current'));
         }
         else {
@@ -196,8 +227,22 @@ public function revisionOverview(NodeInterface $node) {
             . (($revision->revision_log->value != '') ? '<p class="revision-log">' . Xss::filter($revision->revision_log->value) . '</p>' : '');
 
           if ($revert_permission) {
+            if ($node->isTranslatable()) {
+              foreach ($language_names as $langcode => $language_name) {
+                if (isset($language_names[$langcode])) {
+                  $links['revert ' . $langcode] = array(
+                    'title' => $this->t('Revert !language', array('!language' => $language_name)),
+                    'url' => Url::fromRoute('node.revision_revert_language_confirm', [
+                      'node' => $node->id(),
+                      'node_revision' => $vid,
+                      'langcode' => $langcode
+                    ]),
+                  );
+                }
+              }
+            }
             $links['revert'] = array(
-              'title' => $this->t('Revert'),
+              'title' => $node->isTranslatable() ? $this->t('Revert all languages') : $this->t('Revert'),
               'url' => Url::fromRoute('node.revision_revert_confirm', ['node' => $node->id(), 'node_revision' => $vid]),
             );
           }
@@ -209,6 +254,10 @@ public function revisionOverview(NodeInterface $node) {
             );
           }
 
+          if ($node->isTranslatable()) {
+            $row[] = implode(', ', $language_names);
+          }
+
           $row[] = array(
             'data' => array(
               '#type' => 'operations',
diff --git a/core/modules/node/src/Form/NodeRevisionRevertLanguageForm.php b/core/modules/node/src/Form/NodeRevisionRevertLanguageForm.php
new file mode 100644
index 0000000..c9913a7
--- /dev/null
+++ b/core/modules/node/src/Form/NodeRevisionRevertLanguageForm.php
@@ -0,0 +1,193 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\Form\NodeRevisionRevertLanguageForm.
+ */
+
+namespace Drupal\node\Form;
+
+use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Form\ConfirmFormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\content_translation\ContentTranslationManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides a form for reverting a node revision for a given language but leave
+ * other translations untouched.
+ */
+class NodeRevisionRevertLanguageForm extends ConfirmFormBase {
+
+  /**
+   * The old node revision the node should be reverted to.
+   *
+   * @var \Drupal\node\NodeInterface
+   */
+  protected $old_revision;
+
+  /**
+   * The latest node revision which reflects the current state of the node.
+   *
+   * @var \Drupal\node\NodeInterface
+   */
+  protected $latest_revision;
+
+  /**
+   * The language to be reverted.
+   *
+   * @var string
+   */
+  protected $langcode;
+
+  /**
+   * The node storage.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected $nodeStorage;
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languageManager;
+
+  /**
+   * The content translation manager.
+   *
+   * @var \Drupal\content_translation\ContentTranslationManagerInterface
+   */
+  protected $contentTranslationManager;
+
+  /**
+   * Constructs a new NodeRevisionRevertLanguageForm.
+   *
+   * @param \Drupal\Core\Entity\EntityStorageInterface $node_storage
+   *   The node storage.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
+   * @param \Drupal\content_translation\ContentTranslationManagerInterface $content_translation_manager
+   *   The content translation manager service.
+   */
+  public function __construct(EntityStorageInterface $node_storage, LanguageManagerInterface $language_manager, ContentTranslationManagerInterface $content_translation_manager) {
+    $this->nodeStorage = $node_storage;
+    $this->languageManager = $language_manager;
+    $this->contentTranslationManager = $content_translation_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity.manager')->getStorage('node'),
+      $container->get('language_manager'),
+      $container->get('content_translation.manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'node_revision_revert_language_confirm';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQuestion() {
+    $language_name = $this->languageManager->getLanguageName($this->langcode);
+    return $this->t('Are you sure you want to revert to the revision from %revision-date for %language?', array('%revision-date' => format_date($this->old_revision->getRevisionCreationTime()), '%language' => $language_name));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCancelUrl() {
+    if ($this->latest_revision->hasLinkTemplate('version-history')) {
+      // If available, return the version-history URL.
+      return $this->latest_revision->urlInfo('version-history');
+    }
+    else {
+      // Otherwise fall back to the default link template.
+      return $this->latest_revision->urlInfo();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getConfirmText() {
+    return $this->t('Revert');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDescription() {
+    return '';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, $node_revision = NULL, $langcode = NULL) {
+    $this->old_revision = $this->nodeStorage->loadRevision($node_revision);
+    $this->latest_revision = $this->nodeStorage->load($this->old_revision->id());
+    $this->langcode = $langcode;
+
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $tmp_revision = NULL;
+
+    if ($this->old_revision->language()->getId() == $this->langcode) {
+      // Revert the default language.
+      $tmp_revision = $this->old_revision;
+      $translation_languages = $this->latest_revision->getTranslationLanguages(FALSE);
+      foreach (array_keys($translation_languages) as $translation_langcode) {
+        if ($tmp_revision->hasTranslation($translation_langcode)) {
+          $tmp_revision->removeTranslation($translation_langcode);
+        }
+        $tmp_revision->addTranslation($translation_langcode, $this->latest_revision->getTranslation($translation_langcode)->toArray());
+      }
+    }
+    else {
+      // Revert a translation.
+      $tmp_revision = $this->latest_revision;
+      $tmp_revision->removeTranslation($this->langcode);
+      $tmp_revision->addTranslation($this->langcode, $this->old_revision->getTranslation($this->langcode)->toArray());
+    }
+
+    $this->latest_revision = $tmp_revision->getTranslation($this->langcode);
+    $this->latest_revision->setNewRevision();
+    // Make this the new default revision for the node.
+    $this->latest_revision->isDefaultRevision(TRUE);
+    $this->latest_revision->setRevisionCreationTime(REQUEST_TIME);
+    $metadata = $this->contentTranslationManager->getTranslationMetadata($this->latest_revision);
+
+    // The revision timestamp will be updated when the revision is saved.
+    // Keep the original one for the confirmation message.
+    $original_revision_timestamp = $this->latest_revision->getRevisionCreationTime();
+    $language_name = $this->languageManager->getLanguageName($this->langcode);
+
+    $this->latest_revision->revision_log = $this->t('Copy of the revision from %date for %language.', array('%date' => format_date($original_revision_timestamp), '%language' => $language_name));
+
+    $this->latest_revision->save();
+
+    $this->logger('content')->notice('@type: reverted %title revision %revision for %language.', array('@type' => $this->latest_revision->bundle(), '%title' => $this->latest_revision->label(), '%revision' => $this->latest_revision->getRevisionId(), '%language' => $language_name));
+    drupal_set_message($this->t('@type %title has been reverted back to the revision from %revision-date for %language.', array('@type' => node_get_type_label($this->latest_revision), '%title' => $this->latest_revision->label(), '%revision-date' => format_date($original_revision_timestamp), '%language' => $language_name)));
+
+    // The redirect target after revert is the same as on cancel.
+    $form_state->setRedirectUrl($this->getCancelUrl());
+  }
+
+}
diff --git a/core/modules/node/src/Tests/NodeTranslationUITest.php b/core/modules/node/src/Tests/NodeTranslationUITest.php
index 55214c2..b173830 100644
--- a/core/modules/node/src/Tests/NodeTranslationUITest.php
+++ b/core/modules/node/src/Tests/NodeTranslationUITest.php
@@ -54,6 +54,7 @@ protected function setUp() {
    */
   function testTranslationUI() {
     parent::testTranslationUI();
+    $this->doTestTranslationRevision();
     $this->doUninstallTest();
   }
 
@@ -61,7 +62,7 @@ function testTranslationUI() {
    * Overrides \Drupal\content_translation\Tests\ContentTranslationUITestBase::getTranslatorPermission().
    */
   protected function getTranslatorPermissions() {
-    return array_merge(parent::getTranslatorPermissions(), array('administer nodes', "edit any $this->bundle content"));
+    return array_merge(parent::getTranslatorPermissions(), array('administer nodes', "edit any $this->bundle content", 'view page revisions', 'revert page revisions', 'delete page revisions', 'edit any page content', 'delete any page content'));
   }
 
   /**
@@ -398,4 +399,108 @@ protected function doTestTranslationEdit() {
     }
   }
 
+  /**
+   * Tests the basic translation revision workflow.
+   */
+  protected function doTestTranslationRevision() {
+    $entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
+    $counters = array();
+    $revisions = array();
+    $counter = 1;
+
+    // Create two revisions per language.
+    for ($i = 1; $i <= 2; $i++) {
+      foreach ($entity->getTranslationLanguages() as $language) {
+        $langcode = $language->getId();
+        $counters[$langcode] = $i;
+
+        // Set the title and the custom translatable field and the revision log
+        // to predictable values containing a counter.
+        $edit = array(
+          'title[0][value]' => 'title ' . $langcode . ' ' . $i,
+          $this->fieldName . '[0][value]' => $this->fieldName . ' ' . $langcode . ' ' . $i,
+          'revision' => '1',
+          'revision_log[0][value]' => 'log ' . $langcode . ' ' . $i,
+        );
+        $edit_path = $entity->urlInfo('edit-form', array('language' => $language));
+        $this->drupalPostForm($edit_path, $edit, $this->getFormSubmitAction($entity, $langcode));
+        $entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
+
+        $revisions[$counter++] = array(
+          'vid' => $entity->getRevisionId(),
+          'langcode' => $langcode,
+        );
+
+        foreach ($entity->getTranslationLanguages() as $language2) {
+          $translation = $entity->getTranslation($language2->getId());
+          if ($langcode == $language2->getId()) {
+            $message = format_string('Affected language of revision %vid is %language.', array(
+              '%vid' => $entity->getRevisionId(),
+              '%language' => $language2->getName(),
+            ));
+            $this->assertEqual($entity->getRevisionId(), $translation->content_translation_revision->value, $message);
+          }
+          else {
+            $message = format_string('Affected language of revision %vid is not %language.', array(
+              '%vid' => $entity->getRevisionId(),
+              '%language' => $language2->getName(),
+            ));
+            $this->assertNotEqual($entity->getRevisionId(), $translation->content_translation_revision->value, $message);
+          }
+        }
+      }
+    }
+
+    // All values in all languages need to contain the highest counter.
+    $this->verifyValuesForAllLanguagesOfLatestRevision($entity, $counters);
+
+    $num_translations = count($entity->getTranslationLanguages());
+
+    // Revert the revision for only one language.
+    $langcode = $revisions[$num_translations - 1]['langcode'];
+    $this->drupalPostForm('/node/' . $entity->id() . '/revisions/' . $revisions[$num_translations - 1]['vid'] . '/revert_language/' . $langcode, array(), t('Revert'));
+    $entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
+    $counters[$langcode] = 1;
+
+    // The counter in the values of the language that has been reverted has to
+    // lower than in all other languages.
+    $this->verifyValuesForAllLanguagesOfLatestRevision($entity, $counters);
+
+    // Revert all languages to the their first version.
+    $this->drupalPostForm('/node/' . $entity->id() . '/revisions/' . $revisions[$num_translations]['vid'] . '/revert', array(), t('Revert'));
+    $entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
+    foreach (array_keys($counters) as $langcode) {
+     $counters[$langcode] = 1;
+    }
+
+    // All counter values for all languages are set to 1.
+    $this->verifyValuesForAllLanguagesOfLatestRevision($entity, $counters);
+  }
+
+  /**
+   * Helper function to verify values of some fields of an entity revision.
+   *
+   * The values are checked for all languages at once.
+   *
+   * @param $entity
+   * @param $counters
+   */
+  protected function verifyValuesForAllLanguagesOfLatestRevision($entity, $counters) {
+    foreach ($entity->getTranslationLanguages() as $language) {
+      $langcode = $language->getId();
+      $translation = $entity->getTranslation($langcode);
+
+      foreach (array('title', $this->fieldName) as $property) {
+        $stored_value = $this->getValue($translation, $property, NULL);
+        $stored_value = is_array($stored_value) ? $stored_value[0]['value'] : $stored_value;
+        $message = format_string('%property correctly stored for language %language in revision %vid.', array(
+          '%property' => $property,
+          '%language' => $language->getName(),
+          '%vid' => $entity->getRevisionId(),
+        ));
+        $this->assertEqual($stored_value, $property . ' ' . $langcode . ' ' . $counters[$langcode], $message);
+      }
+    }
+  }
+
 }
diff --git a/core/modules/node/tests/modules/node_test/node_test.module b/core/modules/node/tests/modules/node_test/node_test.module
index ed9af44..7f2cd53 100644
--- a/core/modules/node/tests/modules/node_test/node_test.module
+++ b/core/modules/node/tests/modules/node_test/node_test.module
@@ -170,6 +170,7 @@ function node_test_node_insert(NodeInterface $node) {
   // Set the node title to the node ID and save.
   if ($node->getTitle() == 'new') {
     $node->setTitle('Node '. $node->id());
+    $node->setNewRevision(FALSE);
     $node->save();
   }
 }
diff --git a/core/modules/path/src/Plugin/Field/FieldType/PathItem.php b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php
index c06271d..e6cef58 100644
--- a/core/modules/path/src/Plugin/Field/FieldType/PathItem.php
+++ b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php
@@ -55,29 +55,30 @@ public function preSave() {
   /**
    * {@inheritdoc}
    */
-  public function insert() {
-    if ($this->alias) {
-      $entity = $this->getEntity();
+  public function postSave($update) {
+    $resave = parent::postSave($update);
 
-      if ($path = \Drupal::service('path.alias_storage')->save($entity->urlInfo()->getInternalPath(), $this->alias, $this->getLangcode())) {
-        $this->pid = $path['pid'];
+    if (!$update) {
+      if ($this->alias) {
+        $entity = $this->getEntity();
+        if ($path = \Drupal::service('path.alias_storage')->save($entity->urlInfo()->getInternalPath(), $this->alias, $this->getLangcode())) {
+          $this->pid = $path['pid'];
+        }
       }
     }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function update() {
-    // Delete old alias if user erased it.
-    if ($this->pid && !$this->alias) {
-      \Drupal::service('path.alias_storage')->delete(array('pid' => $this->pid));
-    }
-    // Only save a non-empty alias.
-    elseif ($this->alias) {
-      $entity = $this->getEntity();
-      \Drupal::service('path.alias_storage')->save($entity->urlInfo()->getInternalPath(), $this->alias, $this->getLangcode(), $this->pid);
+    else {
+      // Delete old alias if user erased it.
+      if ($this->pid && !$this->alias) {
+        \Drupal::service('path.alias_storage')->delete(array('pid' => $this->pid));
+      }
+      // Only save a non-empty alias.
+      elseif ($this->alias) {
+        $entity = $this->getEntity();
+        \Drupal::service('path.alias_storage')->save($entity->urlInfo()->getInternalPath(), $this->alias, $this->getLangcode(), $this->pid);
+      }
     }
+
+    return $resave;
   }
 
   /**
