diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
index 40da4b8..a4b9a84 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
@@ -52,6 +52,16 @@
   protected $values = array();
 
   /**
+   * The list of fields that have changed.
+   *
+   * The list is keyed by field name and language code, for example
+   * $changedFields['body']['it'] = TRUE
+   *
+   * @var array
+   */
+  protected $changedFields = array();
+
+  /**
    * The array of fields, each being an instance of FieldItemListInterface.
    *
    * @var array
@@ -428,6 +438,14 @@ public function getFields($include_computed = TRUE) {
   /**
    * {@inheritdoc}
    */
+  public function resetChangedFieldList() {
+    $this->changedFields = array();
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getIterator() {
     return new \ArrayIterator($this->getFields());
   }
@@ -592,6 +610,41 @@ public function onChange($name) {
         }
         break;
     }
+
+    // Detect if the value really changed or if it has been set using the
+    // original value again. Real changes are tracked using
+    // $this->changedFields.
+    if (!isset($this->changedFields[$name][$this->activeLangcode]) && isset($this->values[$name][$this->activeLangcode])) {
+      $items = $this->get($name);
+      $value = $this->values[$name][$this->activeLangcode];
+      // If the field items does not consist of the original value, we mark them
+      // changed.
+      if (!$items->valuesEqual($value)) {
+        $this->changedFields[$name][$this->activeLangcode] = TRUE;
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasChanges() {
+    if ($this->hasChangesAcrossTranslations()) {
+      foreach ($this->changedFields as $langcodes) {
+        if (isset($langcodes[$this->activeLangcode])) {
+          return TRUE;
+        }
+      }
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasChangesAcrossTranslations() {
+    return (bool) count($this->changedFields);
   }
 
   /**
@@ -728,6 +781,12 @@ public function addTranslation($langcode, array $values = array()) {
     $values[$this->defaultLangcodeKey] = FALSE;
 
     $this->translations[$langcode]['status'] = static::TRANSLATION_CREATED;
+    // To ensure a correct changed timestamp on a new translation we need
+    // hasChanges() to return TRUE in ChangedItem::preSave().
+    // ChangedItem can not use isNew() for that because it doesn't work for
+    // translations. After the entity has been saved resetChangedFields() will
+    // remove this dummy entry.
+    $this->changedFields['__dummy'][$langcode] = TRUE;
     $translation = $this->getTranslation($langcode);
     $definitions = $translation->getFieldDefinitions();
 
@@ -752,6 +811,12 @@ public function removeTranslation($langcode) {
         }
       }
       $this->translations[$langcode]['status'] = static::TRANSLATION_REMOVED;
+      foreach (array_keys($this->changedFields) as $name) {
+        unset($this->changedFields[$name][$langcode]);
+        if (empty($this->changedFields[$name])) {
+          unset($this->changedFields[$name]);
+        }
+      }
     }
     else {
       $message = 'The specified translation (@langcode) cannot be removed.';
@@ -1020,4 +1085,12 @@ public static function bundleFieldDefinitions(EntityTypeInterface $entity_type,
     return array();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function postSave(EntityStorageInterface $storage, $update = TRUE) {
+    parent::postSave($storage, $update);
+    $this->resetChangedFieldList();
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityChangedInterface.php b/core/lib/Drupal/Core/Entity/EntityChangedInterface.php
index f5ee395..f8892e3 100644
--- a/core/lib/Drupal/Core/Entity/EntityChangedInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityChangedInterface.php
@@ -22,11 +22,20 @@
 interface EntityChangedInterface {
 
   /**
-   * Returns the timestamp of the last entity change.
+   * Returns the timestamp of the last entity change of the current
+   * translation.
    *
    * @return int
    *   The timestamp of the last entity save operation.
    */
   public function getChangedTime();
 
+  /**
+   * Returns the timestamp of the last entity change across all translations.
+   *
+   * @return int
+   *   The timestamp of the last entity save operation across all
+   *   translations.
+   */
+  public function getChangedTimeAcrossTranslations();
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityChangedTrait.php b/core/lib/Drupal/Core/Entity/EntityChangedTrait.php
new file mode 100644
index 0000000..5b34d1a
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityChangedTrait.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\EntityChangedTrait.
+ */
+
+namespace Drupal\Core\Entity;
+
+/**
+ * Provides a trait for accessing changed time.
+ */
+trait EntityChangedTrait {
+
+  /**
+   * Returns the timestamp of the last entity change across all translations.
+   *
+   * @return int
+   *   The timestamp of the last entity save operation across all
+   *   translations.
+   */
+  public function getChangedTimeAcrossTranslations() {
+    $changed = $this->getUntranslated()->getChangedTime();
+    foreach ($this->getTranslationLanguages(FALSE) as $language) {
+      $translation_changed = $this->getTranslation($language->getId())->getChangedTime();
+      $changed = max($translation_changed, $changed);
+    }
+    return $changed;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/FieldableEntityInterface.php b/core/lib/Drupal/Core/Entity/FieldableEntityInterface.php
index 566f9b2..a2d9297 100644
--- a/core/lib/Drupal/Core/Entity/FieldableEntityInterface.php
+++ b/core/lib/Drupal/Core/Entity/FieldableEntityInterface.php
@@ -186,6 +186,13 @@ public function set($field_name, $value, $notify = TRUE);
   public function getFields($include_computed = TRUE);
 
   /**
+   * Resets the list an array of changed field names.
+   *
+   * @return $this
+   */
+  public function resetChangedFieldList();
+
+  /**
    * Reacts to changes to a field.
    *
    * Note that this is invoked after any changes have been applied.
@@ -204,6 +211,25 @@ public function getFields($include_computed = TRUE);
   public function onChange($field_name);
 
   /**
+   * Determines if at least one field item (of the current translation) has been
+   * changed.
+   *
+   * @return bool
+   *   TRUE if the current translation of the entity has changes;
+   *   FALSE otherwise.
+   */
+  public function hasChanges();
+
+  /**
+   * Determines if at least one field item has been changed.
+   *
+   * @return bool
+   *   TRUE if the the entity has any changes across all translations;
+   *   FALSE otherwise.
+   */
+  public function hasChangesAcrossTranslations();
+
+  /**
    * Validates the currently set values.
    *
    * @return \Symfony\Component\Validator\ConstraintViolationListInterface
diff --git a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityChangedConstraintValidator.php b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityChangedConstraintValidator.php
index fe81a63..8fd44da 100644
--- a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityChangedConstraintValidator.php
+++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityChangedConstraintValidator.php
@@ -23,8 +23,9 @@ public function validate($entity, Constraint $constraint) {
       /** @var \Drupal\Core\Entity\EntityInterface $entity */
       if (!$entity->isNew()) {
         $saved_entity = \Drupal::entityManager()->getStorage($entity->getEntityTypeId())->loadUnchanged($entity->id());
-
-        if ($saved_entity && $saved_entity->getChangedTime() > $entity->getChangedTime()) {
+        // A change to any other translation must add a violation to the current
+        // translation because there might be untranslatable shared fields.
+        if ($saved_entity && $saved_entity->getChangedTimeAcrossTranslations() > $entity->getChangedTime()) {
           $this->context->addViolation($constraint->message);
         }
       }
diff --git a/core/lib/Drupal/Core/Field/FieldItemList.php b/core/lib/Drupal/Core/Field/FieldItemList.php
index 86bac79..16cdcc9 100644
--- a/core/lib/Drupal/Core/Field/FieldItemList.php
+++ b/core/lib/Drupal/Core/Field/FieldItemList.php
@@ -375,9 +375,19 @@ protected function defaultValueWidget(FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function equals(FieldItemListInterface $list_to_compare) {
+    return $this->valuesEqual($list_to_compare->getValue());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function valuesEqual($value2) {
+    if (!is_array($value2)) {
+      $value2 = array(0 => $value2);
+    }
     $columns = $this->getFieldDefinition()->getFieldStorageDefinition()->getColumns();
     $count1 = count($this);
-    $count2 = count($list_to_compare);
+    $count2 = count($value2);
     if ($count1 === 0 && $count2 === 0) {
       // Both are empty we can safely assume that it did not change.
       return TRUE;
@@ -387,7 +397,6 @@ public function equals(FieldItemListInterface $list_to_compare) {
       return FALSE;
     }
     $value1 = $this->getValue();
-    $value2 = $list_to_compare->getValue();
     if ($value1 === $value2) {
       return TRUE;
     }
diff --git a/core/lib/Drupal/Core/Field/FieldItemListInterface.php b/core/lib/Drupal/Core/Field/FieldItemListInterface.php
index e4ea12c..ac913fe 100644
--- a/core/lib/Drupal/Core/Field/FieldItemListInterface.php
+++ b/core/lib/Drupal/Core/Field/FieldItemListInterface.php
@@ -271,4 +271,15 @@ public static function processDefaultValue($default_value, FieldableEntityInterf
    */
   public function equals(FieldItemListInterface $list_to_compare);
 
+  /**
+   * Determines if the field items match a given value list.
+   *
+   * @param array $values
+   *   The value list to compare to.
+   *
+   * @return bool
+   *   TRUE if the field item list matches $values, FALSE if not.
+   */
+  public function valuesEqual($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 81625d4..15898c0 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/ChangedItem.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/ChangedItem.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\Core\Field\Plugin\Field\FieldType;
 
+use Drupal\Core\Entity\FieldableEntityInterface;
+
 /**
  * Defines the 'changed' entity field type.
  *
@@ -30,7 +32,29 @@ class ChangedItem extends CreatedItem {
    */
   public function preSave() {
     parent::preSave();
-    $this->value = REQUEST_TIME;
+
+    $entity = $this->getEntity();
+    if ($entity->isNew() || !$this->getFieldDefinition()->isTranslatable()) {
+      $this->value = REQUEST_TIME;
+    }
+    else {
+      $field_name = $this->getFieldDefinition()->getName();
+      // We have to clone the original because getTranslation() will modify
+      // the state of original.
+      $original = clone $entity->original;
+      if ($this->getFieldDefinition()->isTranslatable()) {
+        $original = $original->getTranslation($entity->language()->getId());
+      }
+      // If the timestamp has not been set explicitly auto detect a modification
+      // of the current translation and set the timestamp if needed.
+      // An example of setting the timestamp explicitly is
+      // \Drupal\content_translation\ContentTranslationMetadataWrapperInterface::setChangedTime()
+      if ($this->value == $original->{$field_name}->value) {
+        if ($entity->hasChanges()) {
+          $this->value = REQUEST_TIME;
+        }
+      }
+    }
   }
 
 }
diff --git a/core/modules/block_content/src/Entity/BlockContent.php b/core/modules/block_content/src/Entity/BlockContent.php
index 046640c..c221863 100644
--- a/core/modules/block_content/src/Entity/BlockContent.php
+++ b/core/modules/block_content/src/Entity/BlockContent.php
@@ -8,6 +8,7 @@
 namespace Drupal\block_content\Entity;
 
 use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityChangedTrait;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
@@ -77,6 +78,8 @@ class BlockContent extends ContentEntityBase implements BlockContentInterface {
    */
   protected $theme;
 
+  use EntityChangedTrait;
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/comment/src/CommentInterface.php b/core/modules/comment/src/CommentInterface.php
index c141706..0d558b5 100644
--- a/core/modules/comment/src/CommentInterface.php
+++ b/core/modules/comment/src/CommentInterface.php
@@ -197,14 +197,6 @@ public function getCreatedTime();
   public function setCreatedTime($created);
 
   /**
-   * Returns the timestamp of when the comment was updated.
-   *
-   * @return int
-   *   The timestamp of when the comment was updated.
-   */
-  public function getChangedTime();
-
-  /**
    * Checks if the comment is published.
    *
    * @return bool
diff --git a/core/modules/comment/src/CommentStatistics.php b/core/modules/comment/src/CommentStatistics.php
index 588c831..e1f609f 100644
--- a/core/modules/comment/src/CommentStatistics.php
+++ b/core/modules/comment/src/CommentStatistics.php
@@ -126,8 +126,9 @@ public function create(FieldableEntityInterface $entity, $fields) {
       }
       // Default to REQUEST_TIME when entity does not have a changed property.
       $last_comment_timestamp = REQUEST_TIME;
+      // @todo make comment statistics language aware.
       if ($entity instanceof EntityChangedInterface) {
-        $last_comment_timestamp = $entity->getChangedTime();
+        $last_comment_timestamp = $entity->getChangedTimeAcrossTranslations();
       }
       $query->values(array(
         'entity_id' => $entity->id(),
@@ -243,9 +244,9 @@ public function update(CommentInterface $comment) {
         ->fields(array(
           'cid' => 0,
           'comment_count' => 0,
-          // Use the created date of the entity if it's set, or default to
+          // Use the changed date of the entity if it's set, or default to
           // REQUEST_TIME.
-          'last_comment_timestamp' => ($entity instanceof EntityChangedInterface) ? $entity->getChangedTime() : REQUEST_TIME,
+          'last_comment_timestamp' => ($entity instanceof EntityChangedInterface) ? $entity->getChangedTimeAcrossTranslations() : REQUEST_TIME,
           'last_comment_name' => '',
           'last_comment_uid' => $last_comment_uid,
         ))
diff --git a/core/modules/comment/src/Entity/Comment.php b/core/modules/comment/src/Entity/Comment.php
index 798a69f..c05b85d 100644
--- a/core/modules/comment/src/Entity/Comment.php
+++ b/core/modules/comment/src/Entity/Comment.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Utility\Number;
 use Drupal\Core\Entity\ContentEntityBase;
 use Drupal\comment\CommentInterface;
+use Drupal\Core\Entity\EntityChangedTrait;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
@@ -63,6 +64,8 @@ class Comment extends ContentEntityBase implements CommentInterface {
    */
   protected $threadLock = '';
 
+  use EntityChangedTrait;
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/comment/src/Form/CommentAdminOverview.php b/core/modules/comment/src/Form/CommentAdminOverview.php
index 9205de8..c01e32f 100644
--- a/core/modules/comment/src/Form/CommentAdminOverview.php
+++ b/core/modules/comment/src/Form/CommentAdminOverview.php
@@ -213,7 +213,7 @@ public function buildForm(array $form, FormStateInterface $form_state, $type = '
             '#url' => $commented_entity->urlInfo(),
           ),
         ),
-        'changed' => $this->dateFormatter->format($comment->getChangedTime(), 'short'),
+        'changed' => $this->dateFormatter->format($comment->getChangedTimeAcrossTranslations(), 'short'),
       );
       $comment_uri_options = $comment->urlInfo()->getOptions();
       $links = array();
diff --git a/core/modules/comment/src/Tests/CommentTokenReplaceTest.php b/core/modules/comment/src/Tests/CommentTokenReplaceTest.php
index a5eaad8..c45c0f7 100644
--- a/core/modules/comment/src/Tests/CommentTokenReplaceTest.php
+++ b/core/modules/comment/src/Tests/CommentTokenReplaceTest.php
@@ -61,7 +61,7 @@ function testCommentTokenReplacement() {
     $tests['[comment:url]'] = $comment->url('canonical', $url_options + array('fragment' => 'comment-' . $comment->id()));
     $tests['[comment:edit-url]'] = $comment->url('edit-form', $url_options);
     $tests['[comment:created:since]'] = \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $comment->getCreatedTime(), 2, $language_interface->getId());
-    $tests['[comment:changed:since]'] = \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $comment->getChangedTime(), 2, $language_interface->getId());
+    $tests['[comment:changed:since]'] = \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $comment->getChangedTimeAcrossTranslations(), 2, $language_interface->getId());
     $tests['[comment:parent:cid]'] = $comment->hasParentComment() ? $comment->getParentComment()->id() : NULL;
     $tests['[comment:parent:title]'] = SafeMarkup::checkPlain($parent_comment->getSubject());
     $tests['[comment:entity]'] = SafeMarkup::checkPlain($node->getTitle());
diff --git a/core/modules/content_translation/src/ContentTranslationMetadataWrapperInterface.php b/core/modules/content_translation/src/ContentTranslationMetadataWrapperInterface.php
index 4a0a65b..cfc8622 100644
--- a/core/modules/content_translation/src/ContentTranslationMetadataWrapperInterface.php
+++ b/core/modules/content_translation/src/ContentTranslationMetadataWrapperInterface.php
@@ -7,8 +7,6 @@
 
 namespace Drupal\content_translation;
 
-use Drupal\Core\Entity\EntityChangedInterface;
-use Drupal\Core\Entity\EntityInterface;
 use Drupal\user\UserInterface;
 
 /**
@@ -17,7 +15,7 @@
  * This acts as a wrapper for an entity translation object, encapsulating the
  * logic needed to retrieve translation metadata.
  */
-interface ContentTranslationMetadataWrapperInterface extends EntityChangedInterface {
+interface ContentTranslationMetadataWrapperInterface {
 
   /**
    * Retrieves the source language for this translation.
@@ -110,6 +108,15 @@ public function getCreatedTime();
   public function setCreatedTime($timestamp);
 
   /**
+   * Returns the timestamp of the last entity change of the current
+   * translation.
+   *
+   * @return int
+   *   The timestamp of the last entity save operation.
+   */
+  public function getChangedTime();
+
+  /**
    * Sets the translation modification timestamp.
    *
    * @param int $timestamp
diff --git a/core/modules/content_translation/src/Tests/ContentTranslationUITest.php b/core/modules/content_translation/src/Tests/ContentTranslationUITest.php
index 5a7429c..dcd0807 100644
--- a/core/modules/content_translation/src/Tests/ContentTranslationUITest.php
+++ b/core/modules/content_translation/src/Tests/ContentTranslationUITest.php
@@ -42,6 +42,7 @@ function testTranslationUI() {
     $this->doTestPublishedStatus();
     $this->doTestAuthoringInfo();
     $this->doTestTranslationEdit();
+    $this->doTestTranslationChanged();
     $this->doTestTranslationDeletion();
   }
 
@@ -384,4 +385,63 @@ protected function doTestTranslationEdit() {
     }
   }
 
+  /**
+   * Tests the basic translation workflow.
+   */
+  protected function doTestTranslationChanged() {
+    $entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
+    $changed_field_name = $entity->hasField('content_translation_changed') ? 'content_translation_changed' : 'changed';
+    $translatable_changed_field = $entity->getFieldDefinition($changed_field_name)->isTranslatable();
+
+    for ($i = 1; $i <= 2; $i++) {
+      foreach ($entity->getTranslationLanguages() as $language) {
+        // Ensure different timestamps.
+        sleep(1);
+
+        $langcode = $language->getId();
+
+        // Set the custom translatable field and the revision log to predictable
+        // values containing a counter.
+        $edit = array(
+          $this->fieldName . '[0][value]' => $this->randomString(),
+        );
+        $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);
+        $this->assertEqual(
+          $entity->getChangedTimeAcrossTranslations(), $entity->getTranslation($langcode)->getChangedTime(),
+          format_string('Changed time for language %language is the latest change over all languages.', array('%language' => $language->getName()))
+        );
+
+        if ($i > 1) {
+          // Because the base of this test created the translations
+          // without a sleep command, the changed timestamps might not differ
+          // until the second run of that loop.
+          $timestamps = array();
+          foreach ($entity->getTranslationLanguages() as $language) {
+            $next_timestamp = $entity->getTranslation($language->getId())
+              ->getChangedTime();
+            if (!in_array($next_timestamp, $timestamps)) {
+              $timestamps[] = $next_timestamp;
+            }
+          }
+
+          if ($translatable_changed_field) {
+            $this->assertEqual(
+              count($timestamps), count($entity->getTranslationLanguages()),
+              'All timestamps from all languages are different.'
+            );
+          }
+          else {
+            $this->assertEqual(
+              count($timestamps), 1,
+              'All timestamps from all languages are identical.'
+            );
+          }
+        }
+      }
+    }
+  }
+
 }
diff --git a/core/modules/file/src/Entity/File.php b/core/modules/file/src/Entity/File.php
index 42de0ae..9306ee5 100644
--- a/core/modules/file/src/Entity/File.php
+++ b/core/modules/file/src/Entity/File.php
@@ -8,10 +8,10 @@
 namespace Drupal\file\Entity;
 
 use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityChangedTrait;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
-use Drupal\Core\Language\LanguageInterface;
 use Drupal\file\FileInterface;
 use Drupal\user\UserInterface;
 
@@ -38,6 +38,8 @@
  */
 class File extends ContentEntityBase implements FileInterface {
 
+  use EntityChangedTrait;
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/menu_link_content/src/Entity/MenuLinkContent.php b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
index 5570a2a..a3ca073 100644
--- a/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
+++ b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
@@ -8,6 +8,7 @@
 namespace Drupal\menu_link_content\Entity;
 
 use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityChangedTrait;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
@@ -59,6 +60,8 @@ class MenuLinkContent extends ContentEntityBase implements MenuLinkContentInterf
    */
   protected $insidePlugin = FALSE;
 
+  use EntityChangedTrait;
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 4550a6d..16b0c02 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -741,8 +741,8 @@ function node_page_title(NodeInterface $node) {
  * @param $nid
  *   The ID of a node.
  * @param string $langcode
- *   (optional) The language the node has been last modified in. Defaults to the
- *   node language.
+ *   (optional) The language the node has been last modified in. If omitted the
+ *   last changed time across all translations will be returned.
  *
  * @return string
  *   A unix timestamp indicating the last time the node was changed.
@@ -751,8 +751,9 @@ function node_page_title(NodeInterface $node) {
  *       for validation, which will be done by EntityChangedConstraintValidator.
  */
 function node_last_changed($nid, $langcode = NULL) {
-  $changed = \Drupal::entityManager()->getStorage('node')->loadUnchanged($nid)->getChangedTime();
-  return $changed ? $changed : FALSE;
+  $node = \Drupal::entityManager()->getStorage('node')->loadUnchanged($nid);
+  $changed = $langcode ? $node->getTranslation($langcode)->getChangedTime() : $node->getChangedTimeAcrossTranslations();
+  return $changed ?: FALSE;
 }
 
 /**
diff --git a/core/modules/node/src/Entity/Node.php b/core/modules/node/src/Entity/Node.php
index 7ae8f36..6dd1229 100644
--- a/core/modules/node/src/Entity/Node.php
+++ b/core/modules/node/src/Entity/Node.php
@@ -8,6 +8,7 @@
 namespace Drupal\node\Entity;
 
 use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityChangedTrait;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
@@ -69,6 +70,8 @@
  */
 class Node extends ContentEntityBase implements NodeInterface {
 
+  use EntityChangedTrait;
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/node/src/NodeForm.php b/core/modules/node/src/NodeForm.php
index 44fccbf..d88393d 100644
--- a/core/modules/node/src/NodeForm.php
+++ b/core/modules/node/src/NodeForm.php
@@ -289,7 +289,7 @@ protected function actions(array $form, FormStateInterface $form_state) {
   public function validate(array $form, FormStateInterface $form_state) {
     $node = parent::validate($form, $form_state);
 
-    if ($node->id() && (node_last_changed($node->id(), $this->getFormLangcode($form_state)) > $node->getChangedTime())) {
+    if ($node->id() && (node_last_changed($node->id()) > $node->getChangedTimeAcrossTranslations())) {
       $form_state->setErrorByName('changed', $this->t('The content on this page has either been modified by another user, or you have already submitted modifications using this form. As a result, your changes cannot be saved.'));
     }
 
diff --git a/core/modules/node/src/Tests/NodeLastChangedTest.php b/core/modules/node/src/Tests/NodeLastChangedTest.php
index 4444cc1..23fa4b4 100644
--- a/core/modules/node/src/Tests/NodeLastChangedTest.php
+++ b/core/modules/node/src/Tests/NodeLastChangedTest.php
@@ -38,7 +38,7 @@ function testNodeLastChanged() {
 
     // Test node last changed timestamp.
     $changed_timestamp = node_last_changed($node->id());
-    $this->assertEqual($changed_timestamp, $node->getChangedTime(), 'Expected last changed timestamp returned.');
+    $this->assertEqual($changed_timestamp, $node->getChangedTimeAcrossTranslations(), 'Expected last changed timestamp returned.');
 
     $changed_timestamp = node_last_changed($node->id(), $node->language()->getId());
     $this->assertEqual($changed_timestamp, $node->getChangedTime(), 'Expected last changed timestamp returned.');
diff --git a/core/modules/node/src/Tests/NodeSaveTest.php b/core/modules/node/src/Tests/NodeSaveTest.php
index c9dc9c0..d1dafb5 100644
--- a/core/modules/node/src/Tests/NodeSaveTest.php
+++ b/core/modules/node/src/Tests/NodeSaveTest.php
@@ -130,7 +130,10 @@ function testTimestamps() {
     $node->save();
     $node = $this->drupalGetNodeByTitle($edit['title'], TRUE);
     $this->assertEqual($node->getCreatedTime(), 979534800, 'Updating a node uses user-set "created" timestamp.');
-    $this->assertNotEqual($node->getChangedTime(), 280299600, 'Updating a node does not use user-set "changed" timestamp.');
+    // Allowing setting changed timestamps is required see
+    // Drupal\content_translation\ContentTranslationMetadataWrapper::setChangedTime($timestamp)
+    // for example
+    $this->assertEqual($node->getChangedTime(), 280299600, 'Updating a node uses user-set "changed" timestamp.');
   }
 
   /**
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestConstraints.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestConstraints.php
index 9f92e7a..61200f7 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestConstraints.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestConstraints.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\entity_test\Entity;
 
+use Drupal\Core\Entity\EntityChangedTrait;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityChangedInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
@@ -32,6 +33,8 @@
  */
 class EntityTestConstraints extends EntityTest implements EntityChangedInterface {
 
+  use EntityChangedTrait;
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/taxonomy/src/Entity/Term.php b/core/modules/taxonomy/src/Entity/Term.php
index 7ca9dd7..db02d8a 100644
--- a/core/modules/taxonomy/src/Entity/Term.php
+++ b/core/modules/taxonomy/src/Entity/Term.php
@@ -8,6 +8,7 @@
 namespace Drupal\taxonomy\Entity;
 
 use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityChangedTrait;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
@@ -56,6 +57,8 @@
  */
 class Term extends ContentEntityBase implements TermInterface {
 
+  use EntityChangedTrait;
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/user/src/Entity/User.php b/core/modules/user/src/Entity/User.php
index 1a78c98..3389c80 100644
--- a/core/modules/user/src/Entity/User.php
+++ b/core/modules/user/src/Entity/User.php
@@ -8,6 +8,7 @@
 namespace Drupal\user\Entity;
 
 use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityChangedTrait;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
@@ -76,6 +77,8 @@ class User extends ContentEntityBase implements UserInterface {
    */
   protected $hostname;
 
+  use EntityChangedTrait;
+
   /**
    * {@inheritdoc}
    */
