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/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/Plugin/Field/FieldType/ChangedItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/ChangedItem.php
index 81625d4..6f70759 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/ChangedItem.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/ChangedItem.php
@@ -30,7 +30,37 @@ 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) {
+        foreach ($entity->getFieldDefinitions() as $other_field_definition) {
+          $other_field_name = $other_field_definition->getName();
+          if ($other_field_name != $field_name && $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;
+            }
+          }
+        }
+      }
+    }
   }
 
 }
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/ContentTestTranslationUITest.php b/core/modules/content_translation/src/Tests/ContentTestTranslationUITest.php
index 0db24c8..2b4e3cc 100644
--- a/core/modules/content_translation/src/Tests/ContentTestTranslationUITest.php
+++ b/core/modules/content_translation/src/Tests/ContentTestTranslationUITest.php
@@ -26,7 +26,7 @@ class ContentTestTranslationUITest extends ContentTranslationUITest {
    */
   protected function setUp() {
     // Use the entity_test_mul as this has multilingual property support.
-    $this->entityTypeId = 'entity_test_mul';
+    $this->entityTypeId = 'entity_test_mul_changed';
     parent::setUp();
   }
 
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/shortcut/src/Tests/ShortcutTranslationUITest.php b/core/modules/shortcut/src/Tests/ShortcutTranslationUITest.php
index 9f81cb5..81b67d3 100644
--- a/core/modules/shortcut/src/Tests/ShortcutTranslationUITest.php
+++ b/core/modules/shortcut/src/Tests/ShortcutTranslationUITest.php
@@ -8,6 +8,7 @@
 namespace Drupal\shortcut\Tests;
 
 use Drupal\content_translation\Tests\ContentTranslationUITest;
+use Drupal\Core\Entity\EntityChangedInterface;
 use Drupal\Core\Language\Language;
 
 /**
@@ -102,4 +103,16 @@ protected function doTestTranslationEdit() {
     }
   }
 
+  /**
+   * Tests the basic translation workflow.
+   */
+  protected function doTestTranslationChanged() {
+    $entity = entity_load($this->entityTypeId, $this->entityId, TRUE);
+
+    $this->assertFalse(
+      $entity instanceof EntityChangedInterface,
+      format_string('%entity is not implementing EntityChangedInterface.' , array('%entity' => $this->entityTypeId))
+    );
+  }
+
 }
diff --git a/core/modules/system/src/Tests/Entity/ContentEntityChangedTest.php b/core/modules/system/src/Tests/Entity/ContentEntityChangedTest.php
new file mode 100644
index 0000000..c62daa5
--- /dev/null
+++ b/core/modules/system/src/Tests/Entity/ContentEntityChangedTest.php
@@ -0,0 +1,136 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\Tests\Entity\ContentEntityChangedTest.
+ */
+
+namespace Drupal\system\Tests\Entity;
+
+use Drupal\Core\Entity\EntityStorageException;
+use Drupal\language\Entity\ConfigurableLanguage;
+use Drupal\user\UserInterface;
+
+/**
+ * Tests basic CRUD functionality.
+ *
+ * @group Entity
+ */
+class ContentEntityChangedTest extends EntityUnitTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['language', 'user', 'system', 'field', 'text', 'filter', 'entity_test'];
+
+  /**
+   * @inheritdoc
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // Enable an additional language.
+    ConfigurableLanguage::createFromLangcode('de')->save();
+
+    $this->installEntitySchema('entity_test_mul_changed');
+  }
+
+  /**
+   * Tests basic ContentEntityChanged functionality.
+   */
+  public function testChanged() {
+    $user1 = $this->createUser();
+    $user2 = $this->createUser();
+
+    // Create some test entities.
+    $entity = entity_create('entity_test_mul_changed', array(
+      'name' => $this->randomString(),
+      'user_id' => $user1->id(),
+      'language' => 'en',
+    ));
+    $entity->save();
+
+    $this->assertTrue(
+      $entity->getChangedTime() >= REQUEST_TIME,
+      'Changed time of original language is valid.'
+    );
+
+    // We can't assert equality here because the created time is set to
+    // REQUEST_TIME while instances of ChangedTestItem use the current
+    // timestamp every time.
+    $this->assertTrue(
+      ($entity->getChangedTime() >= $entity->get('created')->value) &&
+      (($entity->getChangedTime() - $entity->get('created')->value) <= time() - REQUEST_TIME),
+      'Changed and created time of original language can be assumed to be identical.'
+    );
+
+    $this->assertEqual(
+      $entity->getChangedTime(), $entity->getChangedTimeAcrossTranslations(),
+      'Changed time of original language is the same as changed time across all translations.'
+    );
+
+    $changed_en = $entity->getChangedTime();
+
+    $entity->addTranslation('de');
+
+    $german = $entity->getTranslation('de');
+
+    $entity->save();
+
+    $this->assertEqual(
+      $entity->getChangedTime(), $changed_en,
+      'Changed time of original language did not change.'
+    );
+
+    $this->assertTrue(
+      $german->getChangedTime() > $entity->getChangedTime(),
+      'Changed time of German translation is newer then the original language.'
+    );
+
+    $this->assertEqual(
+      $german->getChangedTime(), $entity->getChangedTimeAcrossTranslations(),
+      'Changed time of the German translation is the newest time across all translations.'
+    );
+
+    $changed_de = $german->getChangedTime();
+
+    $entity->save();
+
+    $this->assertEqual(
+      $entity->getChangedTime(), $changed_en,
+      'Changed time of original language did not change.'
+    );
+
+    $this->assertEqual(
+      $german->getChangedTime(), $changed_de,
+      'Changed time of the German translation did not change.'
+    );
+
+    $entity->setOwner($user2);
+
+    $entity->save();
+
+    $this->assertTrue(
+      $entity->getChangedTime() > $changed_en,
+      'Changed time of original language did change.'
+    );
+
+    $this->assertEqual(
+      $german->getChangedTime(), $changed_de,
+      'Changed time of the German translation did not change.'
+    );
+
+    $this->assertTrue(
+      $entity->getChangedTime() > $german->getChangedTime(),
+      'Changed time of original language is newer then the German translation.'
+    );
+
+    $this->assertEqual(
+      $entity->getChangedTime(), $entity->getChangedTimeAcrossTranslations(),
+      'Changed time of the original language is the newest time across all translations.'
+    );
+  }
+
+}
diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module
index 4b26cfb..8f78283 100644
--- a/core/modules/system/tests/modules/entity_test/entity_test.module
+++ b/core/modules/system/tests/modules/entity_test/entity_test.module
@@ -58,6 +58,7 @@ function entity_test_entity_types($filter = NULL) {
   if ($filter != ENTITY_TEST_TYPES_REVISABLE) {
     $types[] = 'entity_test_mul';
     $types[] = 'entity_test_mul_langcode_key';
+    $types[] = 'entity_test_mul_changed';
   }
   if ($filter != ENTITY_TEST_TYPES_MULTILINGUAL) {
     $types[] = 'entity_test_rev';
@@ -459,6 +460,20 @@ function entity_test_entity_test_mul_translation_delete(EntityInterface $transla
 /**
  * Implements hook_ENTITY_TYPE_translation_insert() for 'entity_test_mulrev'.
  */
+function entity_test_entity_test_mul_changed_translation_insert(EntityInterface $translation) {
+  _entity_test_record_hooks('entity_test_mul_changed_translation_insert', $translation->language()->getId());
+}
+
+/**
+ * Implements hook_ENTITY_TYPE_translation_delete().
+ */
+function entity_test_entity_test_mul_changed_translation_delete(EntityInterface $translation) {
+  _entity_test_record_hooks('entity_test_mul_changed_translation_delete', $translation->language()->getId());
+}
+
+/**
+ * Implements hook_ENTITY_TYPE_translation_insert().
+ */
 function entity_test_entity_test_mulrev_translation_insert(EntityInterface $translation) {
   _entity_test_record_hooks('entity_test_mulrev_translation_insert', $translation->language()->getId());
 }
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/system/tests/modules/entity_test/src/Entity/EntityTestMulChanged.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulChanged.php
new file mode 100644
index 0000000..93af073
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulChanged.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\entity_test\Entity\EntityTestMulDefaultValue.
+ */
+
+namespace Drupal\entity_test\Entity;
+
+use Drupal\Core\Entity\EntityChangedInterface;
+use Drupal\Core\Entity\EntityChangedTrait;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
+
+/**
+ * Defines the test entity class.
+ *
+ * @ContentEntityType(
+ *   id = "entity_test_mul_changed",
+ *   label = @Translation("Test entity - data table"),
+ *   handlers = {
+ *     "view_builder" = "Drupal\entity_test\EntityTestViewBuilder",
+ *     "access" = "Drupal\entity_test\EntityTestAccessControlHandler",
+ *     "form" = {
+ *       "default" = "Drupal\entity_test\EntityTestForm",
+ *       "delete" = "Drupal\entity_test\EntityTestDeleteForm"
+ *     },
+ *     "translation" = "Drupal\content_translation\ContentTranslationHandler",
+ *     "views_data" = "Drupal\views\EntityViewsData"
+ *   },
+ *   base_table = "entity_test_mul_changed",
+ *   data_table = "entity_test_mul_changed_property",
+ *   translatable = TRUE,
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "uuid" = "uuid",
+ *     "bundle" = "type",
+ *     "label" = "name",
+ *     "langcode" = "langcode"
+ *   },
+ *   links = {
+ *     "canonical" = "/entity_test_mul_changed/manage/{entity_test_mul_changed}",
+ *     "edit-form" = "/entity_test_mul_changed/manage/{entity_test_mul_changed}",
+ *     "delete-form" = "/entity_test/delete/entity_test_mul_changed/{entity_test_mul_changed}",
+ *   },
+ *   field_ui_base_route = "entity.entity_test_mul_changed.admin_form",
+ * )
+ */
+class EntityTestMulChanged extends EntityTestMul implements EntityChangedInterface {
+
+  use EntityChangedTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
+    $fields = parent::baseFieldDefinitions($entity_type);
+
+    $fields['changed'] = BaseFieldDefinition::create('changed_test')
+      ->setLabel(t('Changed'))
+      ->setDescription(t('The time that the entity was last edited.'))
+      ->setTranslatable(TRUE);
+
+    return $fields;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save() {
+    // Ensure a new timestamp.
+    sleep(1);
+    parent::save();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getChangedTime() {
+    return $this->get('changed')->value;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/ChangedTestItem.php b/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/ChangedTestItem.php
new file mode 100644
index 0000000..0051adf
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test/src/Plugin/Field/FieldType/ChangedTestItem.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Plugin\Field\FieldType\ChangedItem.
+ */
+
+namespace Drupal\entity_test\Plugin\Field\FieldType;
+
+use Drupal\Core\Field\Plugin\Field\FieldType\ChangedItem;
+
+/**
+ * Defines the 'changed_test' entity field type.
+ *
+ * Wraps Drupal\Core\Field\Plugin\Field\FieldType\ChangedItem.
+ *
+ * @FieldType(
+ *   id = "changed_test",
+ *   label = @Translation("Last changed"),
+ *   description = @Translation("An entity field containing a UNIX timestamp of when the entity has been last updated."),
+ *   no_ui = TRUE,
+ *   list_class = "\Drupal\Core\Field\ChangedFieldItemList"
+ * )
+ *
+ * @see \Drupal\Core\Entity\EntityChangedInterface
+ */
+class ChangedTestItem extends ChangedItem {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function preSave() {
+    parent::preSave();
+
+    if ($this->value == REQUEST_TIME) {
+      $this->value = time();
+    }
+  }
+
+}
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}
    */
