 core/lib/Drupal/Core/Entity/ContentEntityBase.php  | 34 +++++++++++++++++++---
 .../Core/Entity/ContentEntityStorageBase.php       |  2 ++
 .../Core/Entity/EntityWithDirtyFieldsInterface.php | 27 +++++++++++++++++
 .../Core/Entity/FieldableEntityInterface.php       |  4 +--
 4 files changed, 61 insertions(+), 6 deletions(-)

diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
index 3e94b7d..10f4f1e 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
@@ -8,6 +8,7 @@
 use Drupal\Core\Field\ChangedFieldItemList;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\Core\TypedData\TranslationStatusInterface;
@@ -18,7 +19,7 @@
  *
  * @ingroup entity_api
  */
-abstract class ContentEntityBase extends Entity implements \IteratorAggregate, ContentEntityInterface, TranslationStatusInterface {
+abstract class ContentEntityBase extends Entity implements \IteratorAggregate, ContentEntityInterface, TranslationStatusInterface, EntityWithDirtyFieldsInterface {
 
   /**
    * The plain data values of the contained fields.
@@ -157,6 +158,13 @@
   protected $loadedRevisionId;
 
   /**
+   * The list of field names of any fields that have changed, keyed by langcode.
+   *
+   * @var string[]
+   */
+  protected $dirtyFields = [];
+
+  /**
    * {@inheritdoc}
    */
   public function __construct(array $values, $entity_type, $bundle = FALSE, $translations = []) {
@@ -254,7 +262,11 @@ protected function getLanguages() {
    * {@inheritdoc}
    */
   public function postCreate(EntityStorageInterface $storage) {
-    $this->newRevision = TRUE;
+    // @todo TEST COVERAGE FOR CHANGES IN THIS HUNK
+    if ($this->isDefaultTranslation()) {
+      $this->newRevision = TRUE;
+    }
+    $this->dirtyFields[$this->activeLangcode] = [];
   }
 
   /**
@@ -390,6 +402,8 @@ public function preSaveRevision(EntityStorageInterface $storage, \stdClass $reco
   public function postSave(EntityStorageInterface $storage, $update = TRUE) {
     parent::postSave($storage, $update);
 
+    $this->dirtyFields[$this->activeLangcode] = [];
+
     // Update the status of all saved translations.
     $removed = [];
     foreach ($this->translations as $langcode => &$data) {
@@ -567,6 +581,13 @@ public function getFields($include_computed = TRUE) {
   /**
    * {@inheritdoc}
    */
+  public function getDirtyFields() {
+    return array_intersect_key($this->getFields(), $this->dirtyFields[$this->activeLangcode]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getTranslatableFields($include_computed = TRUE) {
     $fields = [];
     foreach ($this->getFieldDefinitions() as $name => $definition) {
@@ -701,6 +722,9 @@ protected function updateFieldLangcodes($langcode) {
    * {@inheritdoc}
    */
   public function onChange($name) {
+    // Add the field to the dirty fields list for this translation.
+    $this->dirtyFields[$this->activeLangcode][$name] = TRUE;
+
     // Check if the changed name is the value of an entity key and if the value
     // of that is currently cached, if so, reset it. Exclude the bundle from
     // that check, as it ready only and must not change, unsetting it could
@@ -974,7 +998,9 @@ public function &__get($name) {
   /**
    * Implements the magic method for setting object properties.
    *
-   * Uses default language always.
+   * Always:
+   * - uses default language
+   * - notifies parent
    */
   public function __set($name, $value) {
     // Inline getFieldDefinition() to speed things up.
@@ -989,7 +1015,7 @@ public function __set($name, $value) {
       }
       // If a FieldItemList object already exists, set its value.
       if (isset($this->fields[$name][$this->activeLangcode])) {
-        $this->fields[$name][$this->activeLangcode]->setValue($value);
+        $this->fields[$name][$this->activeLangcode]->setValue($value, TRUE);
       }
       // If not, create one.
       else {
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
index 9d6b8d6..25804d2 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
@@ -135,6 +135,8 @@ public function createTranslation(ContentEntityInterface $entity, $langcode, arr
     $values[$this->getEntityType()->getKey('default_langcode')] = FALSE;
     $this->initFieldValues($translation, $values, $field_names);
     $this->invokeHook('translation_create', $translation);
+    // @todo TEST COVERAGE FOR CHANGES IN THIS HUNK
+    $translation->postCreate($this);
     return $translation;
   }
 
diff --git a/core/lib/Drupal/Core/Entity/EntityWithDirtyFieldsInterface.php b/core/lib/Drupal/Core/Entity/EntityWithDirtyFieldsInterface.php
new file mode 100644
index 0000000..ddadeb3
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityWithDirtyFieldsInterface.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Drupal\Core\Entity;
+
+/**
+ * Interface for entities having fields that can become dirty.
+ *
+ * This interface extends the general fieldable entity interface. So that that
+ * interface is not modified during the Drupal 8.x cycle.
+ *
+ * @todo Merge this interface with FieldableEntityInterface before Drupal 9.0.
+ *
+ * @ingroup entity_api
+ */
+interface EntityWithDirtyFieldsInterface extends FieldableEntityInterface {
+
+  /**
+   * Gets a list of dirty field item lists.
+   *
+   * @return \Drupal\Core\Field\FieldItemListInterface[]
+   *   An array of dirty field item lists, keyed by field name.
+   *
+   * @see \Drupal\Core\Entity\FieldableEntityInterface::getFields()
+   */
+  public function getDirtyFields();
+
+}
diff --git a/core/lib/Drupal/Core/Entity/FieldableEntityInterface.php b/core/lib/Drupal/Core/Entity/FieldableEntityInterface.php
index d21a2a8..b218ea8 100644
--- a/core/lib/Drupal/Core/Entity/FieldableEntityInterface.php
+++ b/core/lib/Drupal/Core/Entity/FieldableEntityInterface.php
@@ -170,13 +170,13 @@ public function get($field_name);
   public function set($field_name, $value, $notify = TRUE);
 
   /**
-   * Gets an array of all field item lists.
+   * Gets a list of all field item lists.
    *
    * @param bool $include_computed
    *   If set to TRUE, computed fields are included. Defaults to TRUE.
    *
    * @return \Drupal\Core\Field\FieldItemListInterface[]
-   *   An array of field item lists implementing, keyed by field name.
+   *   An array of field item lists, keyed by field name.
    */
   public function getFields($include_computed = TRUE);
 
