diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
index 13c452cac5..ad6c62dbb1 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
@@ -20,6 +20,10 @@
  */
 abstract class ContentEntityBase extends Entity implements \IteratorAggregate, ContentEntityInterface, TranslationStatusInterface {
 
+  use EntityChangesDetectionTrait {
+    getFieldsToSkipFromTranslationChangesCheck as traitGetFieldsToSkipFromTranslationChangesCheck;
+  }
+
   /**
    * The plain data values of the contained fields.
    *
@@ -1373,17 +1377,7 @@ public static function bundleFieldDefinitions(EntityTypeInterface $entity_type,
    *   An array of field names.
    */
   protected function getFieldsToSkipFromTranslationChangesCheck() {
-    /** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
-    $entity_type = $this->getEntityType();
-    // A list of known revision metadata fields which should be skipped from
-    // the comparision.
-    $fields = [
-      $entity_type->getKey('revision'),
-      'revision_translation_affected',
-    ];
-    $fields = array_merge($fields, array_values($entity_type->getRevisionMetadataKeys()));
-
-    return $fields;
+    return $this->traitGetFieldsToSkipFromTranslationChangesCheck($this);
   }
 
   /**
@@ -1423,10 +1417,15 @@ public function hasTranslationChanges() {
     // The list of fields to skip from the comparision.
     $skip_fields = $this->getFieldsToSkipFromTranslationChangesCheck();
 
+    // We also check untranslatable fields, so that a change to those will mark
+    // all translations as affected, unless they are configured to only affect
+    // the default translation.
+    $skip_untranslatable_fields = !$this->isDefaultTranslation() && $this->isDefaultTranslationAffectedOnly();
+
     foreach ($this->getFieldDefinitions() as $field_name => $definition) {
       // @todo Avoid special-casing the following fields. See
       //    https://www.drupal.org/node/2329253.
-      if (in_array($field_name, $skip_fields, TRUE)) {
+      if (in_array($field_name, $skip_fields, TRUE) || ($skip_untranslatable_fields && !$definition->isTranslatable())) {
         continue;
       }
       $field = $this->get($field_name);
@@ -1447,4 +1446,14 @@ public function hasTranslationChanges() {
     return FALSE;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function isDefaultTranslationAffectedOnly() {
+    $bundle_name = $this->bundle();
+    $bundle_info = \Drupal::service('entity_type.bundle.info')
+      ->getBundleInfo($this->getEntityTypeId());
+    return !empty($bundle_info[$bundle_name]['untranslatable_fields.default_translation_affected']);
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityForm.php b/core/lib/Drupal/Core/Entity/ContentEntityForm.php
index 92bbbae42f..e2be1013af 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityForm.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityForm.php
@@ -127,6 +127,8 @@ public function form(array $form, FormStateInterface $form_state) {
       $this->addRevisionableFormFields($form);
     }
 
+    $this->addLatestRevisionIdWidget($form);
+
     $form['footer'] = [
       '#type' => 'container',
       '#weight' => 99,
@@ -139,6 +141,30 @@ public function form(array $form, FormStateInterface $form_state) {
     return $form;
   }
 
+  /**
+   * Adds an hidden input field storing the (current) latest revision ID.
+   *
+   * @param array $form
+   *   A form array reference.
+   *
+   * @internal This widget and the related property should be considered
+   *   internal. They will be replaced by a regular field in
+   *   https://www.drupal.org/project/drupal/issues/2784201.
+   */
+  protected function addLatestRevisionIdWidget(array& $form) {
+    // Store the latest revision id at the time the form is built, so it can be
+    // used later to perform checks on concurrent edits.
+    $entity_type = $this->entity->getEntityType();
+    if ($entity_type->isRevisionable()) {
+      /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
+      $storage = $this->entityTypeManager->getStorage($entity_type->id());
+      $form['original_latest_revision_id'] = [
+        '#type' => 'hidden',
+        '#default_value' => $storage->getLatestRevisionId($this->entity->id()),
+      ];
+    }
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -155,6 +181,15 @@ public function buildEntity(array $form, FormStateInterface $form_state) {
     /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
     $entity = parent::buildEntity($form, $form_state);
 
+    // Ensure the original latest revision identifier is available for
+    // validators.
+    $original_latest_revision_id = $form_state->getValue('original_latest_revision_id');
+    if ($original_latest_revision_id) {
+      // @todo Update the "latest_revision_id" field once we have introduced it
+      //   in https://www.drupal.org/project/drupal/issues/2784201.
+      $entity->original_latest_revision_id = $original_latest_revision_id;
+    }
+
     // Mark the entity as requiring validation.
     $entity->setValidationRequired(!$form_state->getTemporaryValue('entity_validated'));
 
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
index c85c7252ac..c2c3c17467 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
@@ -223,17 +223,17 @@ public function createRevision(RevisionableInterface $entity, $default = TRUE, $
     // translation and the other translations in the default revision. This
     // permits the creation of pending revisions that can always be saved as the
     // new default revision without reverting changes in other languages.
-    if (!$entity->isNew() && !$entity->isDefaultRevision() && $entity->isTranslatable() && $this->isAnyRevisionTranslated($entity)) {
+    if (!$entity->isNew() && !$entity->isDefaultRevision() && $entity->isTranslatable() && ($entity->getTranslationLanguages(FALSE) || $this->isAnyRevisionTranslated($entity))) {
       $active_langcode = $entity->language()->getId();
       $skipped_field_names = array_flip($this->getRevisionTranslationMergeSkippedFieldNames());
 
-      // Default to preserving the untranslatable field values in the default
-      // revision, otherwise we may expose data that was not meant to be
-      // accessible.
+      // By default we copy untranslatable field values from the default
+      // revision, unless they are configured to affect only the default
+      // translation. This way we can ensure we always have only one affected
+      // translation in pending revisions. This constraint is enforced by
+      // EntityUntranslatableFieldsConstraintValidator.
       if (!isset($keep_untranslatable_fields)) {
-        // @todo Implement a more complete default logic in
-        //    https://www.drupal.org/project/drupal/issues/2878556.
-        $keep_untranslatable_fields = FALSE;
+        $keep_untranslatable_fields = $entity->isDefaultTranslation() && $entity->isDefaultTranslationAffectedOnly();
       }
 
       /** @var \Drupal\Core\Entity\ContentEntityInterface $default_revision */
@@ -262,6 +262,11 @@ public function createRevision(RevisionableInterface $entity, $default = TRUE, $
         // No need to copy untranslatable field values more than once.
         $keep_untranslatable_fields = TRUE;
       }
+
+      // Populate the "original" property with the current values, given that
+      // the new revision is not stored anywhere. This way we can detect changes
+      // properly.
+      $new_revision->original = clone $new_revision;
     }
 
     // Eventually mark the new revision as such.
@@ -550,6 +555,14 @@ protected function doPreSave(EntityInterface $entity) {
       $entity->updateLoadedRevisionId();
     }
 
+    // Make sure that the original entity is the revision the current entity was
+    // originated from, otherwise we would always compare the current values
+    // with the default values, which is not correct for pending revisions.
+    // TODO Add test coverage.
+    if (!isset($entity->original) && $this->entityType->isRevisionable()) {
+      $entity->original = $this->loadRevision($entity->getLoadedRevisionId());
+    }
+
     $id = parent::doPreSave($entity);
 
     if (!$entity->isNew()) {
diff --git a/core/lib/Drupal/Core/Entity/EntityChangesDetectionTrait.php b/core/lib/Drupal/Core/Entity/EntityChangesDetectionTrait.php
new file mode 100644
index 0000000000..e598e8c089
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EntityChangesDetectionTrait.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Drupal\Core\Entity;
+
+/**
+ * Provides helper methods to detect changes in an entity object.
+ *
+ * @internal This may be replaced by a proper entity comparison handler.
+ */
+trait EntityChangesDetectionTrait {
+
+  /**
+   * Returns an array of field names to skip when checking for changes.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   A content entity object.
+   *
+   * @return string[]
+   *   An array of field names.
+   */
+  protected function getFieldsToSkipFromTranslationChangesCheck(ContentEntityInterface $entity) {
+    /** @var \Drupal\Core\Entity\ContentEntityTypeInterface $entity_type */
+    $entity_type = $entity->getEntityType();
+
+    // A list of known revision metadata fields which should be skipped from
+    // the comparision.
+    $fields = [
+      $entity_type->getKey('revision'),
+      $entity_type->getKey('revision_translation_affected'),
+    ];
+    $fields = array_merge($fields, array_values($entity_type->getRevisionMetadataKeys()));
+
+    return $fields;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/EntityType.php b/core/lib/Drupal/Core/Entity/EntityType.php
index 19a52ca0fa..f5204f8241 100644
--- a/core/lib/Drupal/Core/Entity/EntityType.php
+++ b/core/lib/Drupal/Core/Entity/EntityType.php
@@ -311,11 +311,16 @@ public function __construct($definition) {
       $this->checkStorageClass($this->handlers['storage']);
     }
 
-    // Automatically add the EntityChanged constraint if the entity type tracks
-    // the changed time.
+    // Automatically add the "EntityChanged" constraint if the entity type
+    // tracks the changed time.
     if ($this->entityClassImplements(EntityChangedInterface::class)) {
       $this->addConstraint('EntityChanged');
     }
+    // Automatically add the "EntityUntranslatableFields" constraint if we have
+    // an entity type supporting translatable fields and pending revisions.
+    if ($this->entityClassImplements(ContentEntityInterface::class)) {
+      $this->addConstraint('EntityUntranslatableFields');
+    }
 
     // Ensure a default list cache tag is set.
     if (empty($this->list_cache_tags)) {
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 28d81ba6e4..47a30b29de 100644
--- a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityChangedConstraintValidator.php
+++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityChangedConstraintValidator.php
@@ -2,6 +2,9 @@
 
 namespace Drupal\Core\Entity\Plugin\Validation\Constraint;
 
+use Drupal\Core\Entity\EntityChangedInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\RevisionableInterface;
 use Symfony\Component\Validator\Constraint;
 use Symfony\Component\Validator\ConstraintValidator;
 
@@ -10,34 +13,121 @@
  */
 class EntityChangedConstraintValidator extends ConstraintValidator {
 
+  /**
+   * Flag determining whether changed time validation should be performed.
+   *
+   * @var bool
+   */
+  protected $checkChangedTime;
+
   /**
    * {@inheritdoc}
    */
   public function validate($entity, Constraint $constraint) {
-    if (isset($entity)) {
-      /** @var \Drupal\Core\Entity\EntityInterface $entity */
-      if (!$entity->isNew()) {
-        $saved_entity = \Drupal::entityManager()->getStorage($entity->getEntityTypeId())->loadUnchanged($entity->id());
-        // Ensure that all the entity translations are the same as or newer
-        // than their current version in the storage in order to avoid
-        // reverting other changes. In fact the entity object that is being
-        // saved might contain an older entity translation when different
-        // translations are being concurrently edited.
-        if ($saved_entity) {
-          $common_translation_languages = array_intersect_key($entity->getTranslationLanguages(), $saved_entity->getTranslationLanguages());
-          foreach (array_keys($common_translation_languages) as $langcode) {
-            // Merely comparing the latest changed timestamps across all
-            // translations is not sufficient since other translations may have
-            // been edited and saved in the meanwhile. Therefore, compare the
-            // changed timestamps of each entity translation individually.
-            if ($saved_entity->getTranslation($langcode)->getChangedTime() > $entity->getTranslation($langcode)->getChangedTime()) {
-              $this->context->addViolation($constraint->message);
-              break;
-            }
+    if ($entity instanceof EntityInterface && !$entity->isNew()) {
+      $valid = $this->validateChangedRevisions($entity);
+      if ($this->checkChangedTime) {
+        $valid = $this->validateChangedTime($entity);
+      }
+      if (!$valid) {
+        $this->context->addViolation($constraint->message);
+      }
+    }
+  }
+
+  /**
+   * Performs validation based on revision changes.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity being validated.
+   *
+   * @return bool
+   *   TRUE if validation is successful, FALSE otherwise.
+   */
+  protected function validateChangedRevisions(EntityInterface $entity) {
+    $valid = TRUE;
+    $this->checkChangedTime = TRUE;
+
+    if ($entity instanceof RevisionableInterface && $this->isRevisionValidationEnabled($entity)) {
+      /** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
+      $storage = \Drupal::entityTypeManager()
+        ->getStorage($entity->getEntityTypeId());
+
+      if (isset($entity->original_latest_revision_id)) {
+        // If the entity is revisionable, we need to make sure that no new
+        // revision was created while the entity was being changed.
+        // @todo Check the "latest_revision_id" field once we have introduced it
+        //   in https://www.drupal.org/project/drupal/issues/2784201.
+        $valid = $entity->original_latest_revision_id == $storage->getLatestRevisionId($entity->id());
+        // If the current revision was created starting from a past revision
+        // there is no point in checking the changed time, as in this case it
+        // may very well be less than the default revision's one. This should
+        // happen only when changing different translations and is legal as long
+        // as translation changes are merged into default revisions.
+        // @todo Check the "latest_revision_id" field once we have introduced it
+        //   in https://www.drupal.org/project/drupal/issues/2784201.
+        $this->checkChangedTime = $valid && $entity->original_latest_revision_id == $entity->getLoadedRevisionId();
+      }
+    }
+
+    return $valid;
+  }
+
+  /**
+   * Checks whether this validator applies to the specified bundle.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   An entity object.
+   *
+   * @return bool
+   *   TRUE if the bundle is enabled, FALSE otherwise.
+   */
+  protected function isRevisionValidationEnabled(EntityInterface $entity) {
+    $bundle_name = $entity->bundle();
+    $bundle_info = \Drupal::service('entity_type.bundle.info')
+      ->getBundleInfo($entity->getEntityTypeId());
+    return !empty($bundle_info[$bundle_name]['entity_validation_constraints.EntityChanged.changed_revision']);
+  }
+
+  /**
+   * Performs validation based on timestamp changes.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity being validated.
+   *
+   * @return bool
+   *   TRUE if validation is successful, FALSE otherwise.
+   */
+  protected function validateChangedTime(EntityInterface $entity) {
+    $valid = TRUE;
+
+    if ($entity instanceof EntityChangedInterface && !$entity->isNew()) {
+      /** @var \Drupal\Core\Entity\EntityInterface|\Drupal\Core\Entity\EntityChangedInterface $saved_entity */
+      $saved_entity = \Drupal::entityTypeManager()
+        ->getStorage($entity->getEntityTypeId())
+        ->loadUnchanged($entity->id());
+
+      // Ensure that all the entity translations are the same as or newer
+      // than their current version in the storage in order to avoid
+      // reverting other changes. In fact the entity object that is being
+      // saved might contain an older entity translation when different
+      // translations are being concurrently edited.
+      if ($saved_entity) {
+        $common_translation_languages = array_intersect_key($entity->getTranslationLanguages(), $saved_entity->getTranslationLanguages());
+        foreach (array_keys($common_translation_languages) as $langcode) {
+          // Merely comparing the latest changed timestamps across all
+          // translations is not sufficient since other translations may have
+          // been edited and saved in the meanwhile. Therefore, compare the
+          // changed timestamps of each entity translation individually.
+          if ($saved_entity->getTranslation($langcode)->getChangedTime() > $entity->getTranslation($langcode)->getChangedTime()) {
+            $valid = FALSE;
+            break;
           }
         }
       }
     }
+
+    return $valid;
   }
 
 }
diff --git a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityUntranslatableFieldsConstraint.php b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityUntranslatableFieldsConstraint.php
new file mode 100644
index 0000000000..0044b6b4fb
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityUntranslatableFieldsConstraint.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Drupal\Core\Entity\Plugin\Validation\Constraint;
+
+use Symfony\Component\Validator\Constraint;
+
+/**
+ * Validation constraint for the entity changed timestamp.
+ *
+ * @Constraint(
+ *   id = "EntityUntranslatableFields",
+ *   label = @Translation("Entity untranslatable fields", context = "Validation"),
+ *   type = {"entity"}
+ * )
+ */
+class EntityUntranslatableFieldsConstraint extends Constraint {
+
+  public $message = 'Non translatable fields can only be changed when updating the current revision or the original language.';
+
+}
diff --git a/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityUntranslatableFieldsConstraintValidator.php b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityUntranslatableFieldsConstraintValidator.php
new file mode 100644
index 0000000000..39ed9e00a4
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Plugin/Validation/Constraint/EntityUntranslatableFieldsConstraintValidator.php
@@ -0,0 +1,125 @@
+<?php
+
+namespace Drupal\Core\Entity\Plugin\Validation\Constraint;
+
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityChangesDetectionTrait;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Field\ChangedFieldItemList;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+
+/**
+ * Validates the EntityChanged constraint.
+ */
+class EntityUntranslatableFieldsConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
+
+  use EntityChangesDetectionTrait;
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Constructs an EntityUntranslatableFieldsConstraintValidator object.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_type_manager) {
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity_type.manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate($entity, Constraint $constraint) {
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+
+    // Untranslatable field restrictions apply only to pending revisions of
+    // multilingual entities.
+    if ($entity->isNew() || $entity->isDefaultRevision() || !$entity->isTranslatable() || !$entity->getEntityType()->isRevisionable()) {
+      return;
+    }
+
+    // To avoid unintentional reverts and data losses, we forbid changes to
+    // untranslatable fields in pending revisions for multilingual entities. The
+    // only case where changes in pending revisions are acceptable is when
+    // untranslatable fields affect only the default translation, in which case
+    // a pending revision contains only one affected translation. Even in this
+    // case, multiple translations would be affected in a single revision, if we
+    // allowed changes to untranslatable fields while editing non-default
+    // translations, so that is forbidden too.
+    if ($this->hasUntranslatableFieldsChanges($entity)) {
+      if ($entity->isDefaultTranslationAffectedOnly()) {
+        foreach ($entity->getTranslationLanguages(FALSE) as $langcode => $language) {
+          if ($entity->getTranslation($langcode)->hasTranslationChanges()) {
+            $this->context->addViolation($constraint->message);
+            break;
+          }
+        }
+      }
+      else {
+        $this->context->addViolation($constraint->message);
+      }
+    }
+  }
+
+  /**
+   * Checks whether an entity has untranslatable field changes.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   A content entity object.
+   *
+   * @return bool
+   *   TRUE if untranslatable fields have changes, FALSE otherwise.
+   */
+  protected function hasUntranslatableFieldsChanges(ContentEntityInterface $entity) {
+    $skip_fields = $this->getFieldsToSkipFromTranslationChangesCheck($entity);
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $original */
+    $original = isset($entity->original) ?
+      $entity->original :
+      $this->entityTypeManager
+        ->getStorage($entity->getEntityTypeId())
+        ->loadRevision($entity->getLoadedRevisionId());
+
+    foreach ($entity->getFieldDefinitions() as $field_name => $definition) {
+      if (in_array($field_name, $skip_fields, TRUE) || $definition->isTranslatable() || $definition->isComputed()) {
+        continue;
+      }
+
+      // When saving entities in the user interface, the changed timestamp is
+      // automatically incremented by ContentEntityForm::submitForm() even if
+      // nothing was actually changed. Thus, the changed time needs to be
+      // ignored when determining whether there are any actual changes in the
+      // entity.
+      $field = $entity->get($field_name);
+      if ($field instanceof ChangedFieldItemList) {
+        continue;
+      }
+
+      $items = $field->filterEmptyItems();
+      $original_items = $original->get($field_name)->filterEmptyItems();
+      if (!$items->equals($original_items)) {
+        return TRUE;
+      }
+    }
+
+    return FALSE;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/TranslatableRevisionableInterface.php b/core/lib/Drupal/Core/Entity/TranslatableRevisionableInterface.php
index e1bc1e375c..7460eeb16b 100644
--- a/core/lib/Drupal/Core/Entity/TranslatableRevisionableInterface.php
+++ b/core/lib/Drupal/Core/Entity/TranslatableRevisionableInterface.php
@@ -71,4 +71,13 @@ public function isRevisionTranslationAffectedEnforced();
    */
   public function setRevisionTranslationAffectedEnforced($enforced);
 
+  /**
+   * Checks if untranslatable fields should affect only the default translation.
+   *
+   * @return bool
+   *   TRUE if untranslatable fields should affect only the default translation,
+   *   FALSE otherwise.
+   */
+  public function isDefaultTranslationAffectedOnly();
+
 }
diff --git a/core/lib/Drupal/Core/ParamConverter/EntityConverter.php b/core/lib/Drupal/Core/ParamConverter/EntityConverter.php
index f76b327a86..e00229a784 100644
--- a/core/lib/Drupal/Core/ParamConverter/EntityConverter.php
+++ b/core/lib/Drupal/Core/ParamConverter/EntityConverter.php
@@ -3,8 +3,11 @@
 namespace Drupal\Core\ParamConverter;
 
 use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\ContentEntityStorageInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\TypedData\TranslatableInterface;
 use Symfony\Component\Routing\Route;
 
@@ -70,7 +73,7 @@ public function convert($value, $definition, $name, array $defaults) {
     // "load_latest_revision" flag, load the latest revision.
     if ($entity instanceof ContentEntityInterface && !empty($definition['load_latest_revision']) && $entity_definition->isRevisionable()) {
       /** @var \Drupal\Core\Entity\ContentEntityStorageInterface  $storage */
-      $latest_revision_id = $storage->getLatestRevisionId($value);
+      $latest_revision_id = $this->getLatestRevisionId($storage, $entity_definition, $value);
       // We explicitly perform a loose equality check, since a revision ID may
       // be returned as an integer or a string.
       if ($entity->getLoadedRevisionId() != $latest_revision_id) {
@@ -87,6 +90,53 @@ public function convert($value, $definition, $name, array $defaults) {
     return $entity;
   }
 
+  /**
+   * Get the latest revision ID.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityStorageInterface $storage
+   *   The entity storage.
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type definition.
+   * @param int $entity_id
+   *   The raw value.
+   *
+   * @return int
+   *   The latest (affected) revision ID for a given entity.
+   */
+  protected function getLatestRevisionId(ContentEntityStorageInterface $storage, EntityTypeInterface $entity_type, $entity_id) {
+    $latest_revision_id = NULL;
+    // TODO Inject this.
+    $language_manager = \Drupal::languageManager();
+
+    // @todo, replace this query with a standardized way of getting the
+    //   latest revision in https://www.drupal.org/node/2784201.
+    if ($entity_type->isTranslatable() && $language_manager->isMultilingual()) {
+      $langcode = $language_manager
+        ->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)
+        ->getId();
+      $latest_revision_id = $storage->getLatestTranslationAffectedRevisionId($entity_id, $langcode);
+
+      // If the latest affected revision was a default revision, it is fine to
+      // load the latest revision instead. This will allow to handle all cases
+      // that do not work with pending revisions and always expect the latest
+      // revision being loaded (and being the default revision).
+      /** @var \Drupal\Core\Entity\ContentEntityInterface|\Drupal\Core\Entity\RevisionLogInterface $revision */
+      $revision = $latest_revision_id ? $storage->loadRevision($latest_revision_id) : NULL;
+      if (!$revision || ($revision->wasDefaultRevision() && !$revision->isDefaultRevision())) {
+        $latest_revision_id = NULL;
+      }
+    }
+
+    // Fall back to the latest revisions if no affected revision for the current
+    // language could be found. This is acceptable as it means the entity is not
+    // translated. This is the correct logic also on monolingual sites.
+    if (!isset($latest_revision_id)) {
+      $latest_revision_id = $storage->getLatestRevisionId($entity_id);
+    }
+
+    return (string) $latest_revision_id;
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/content_moderation/content_moderation.module b/core/modules/content_moderation/content_moderation.module
index 2ae1e0da3a..8087b4caee 100644
--- a/core/modules/content_moderation/content_moderation.module
+++ b/core/modules/content_moderation/content_moderation.module
@@ -118,6 +118,15 @@ function content_moderation_entity_translation_delete(EntityInterface $translati
     ->entityTranslationDelete($translation);
 }
 
+/**
+ * Implements hook_entity_prepare_form().
+ */
+function content_moderation_entity_prepare_form(EntityInterface $entity, $operation, FormStateInterface $form_state) {
+  \Drupal::service('class_resolver')
+    ->getInstanceFromDefinition(EntityTypeInfo::class)
+    ->entityPrepareForm($entity, $operation, $form_state);
+}
+
 /**
  * Implements hook_form_alter().
  */
@@ -243,6 +252,7 @@ function content_moderation_action_info_alter(&$definitions) {
  * Implements hook_entity_bundle_info_alter().
  */
 function content_moderation_entity_bundle_info_alter(&$bundles) {
+  $translatable = FALSE;
   /** @var \Drupal\workflows\WorkflowInterface $workflow */
   foreach (Workflow::loadMultipleByType('content_moderation') as $workflow) {
     /** @var \Drupal\content_moderation\Plugin\WorkflowType\ContentModeration $plugin */
@@ -250,11 +260,19 @@ function content_moderation_entity_bundle_info_alter(&$bundles) {
     foreach ($plugin->getEntityTypes() as $entity_type_id) {
       foreach ($plugin->getBundlesForEntityType($entity_type_id) as $bundle_id) {
         if (isset($bundles[$entity_type_id][$bundle_id])) {
-          $bundles[$entity_type_id][$bundle_id]['workflow'] = $workflow->id();
+          $bundle_info = &$bundles[$entity_type_id][$bundle_id];
+          $bundle_info['workflow'] = $workflow->id();
+          $bundle_info['entity_validation_constraints.EntityChanged.changed_revision'] = TRUE;
+          // If we have even one translatable bundle for moderated entity types,
+          // we need to make the moderation state translatable as well.
+          if (!empty($bundle_info['translatable'])) {
+            $translatable = TRUE;
+          }
         }
       }
     }
   }
+  $bundles['content_moderation_state']['content_moderation_state']['translatable'] = $translatable;
 }
 
 /**
diff --git a/core/modules/content_moderation/src/Entity/ContentModerationState.php b/core/modules/content_moderation/src/Entity/ContentModerationState.php
index e54fbbe2a8..59344f2628 100644
--- a/core/modules/content_moderation/src/Entity/ContentModerationState.php
+++ b/core/modules/content_moderation/src/Entity/ContentModerationState.php
@@ -221,4 +221,19 @@ protected function realSave() {
     return parent::save();
   }
 
+  /**
+   * Returns an array of field names to skip in ::hasTranslationChanges.
+   *
+   * @return array
+   *   An array of field names.
+   */
+  protected function getFieldsToSkipFromTranslationChangesCheck() {
+    $field_names = $this->traitGetFieldsToSkipFromTranslationChangesCheck($this);
+    // We need to skip the parent entity revision ID, since that will always
+    // change on every save, otherwise every translation would be marked as
+    // affected regardless of actual changes.
+    $field_names[] = 'content_entity_revision_id';
+    return $field_names;
+  }
+
 }
diff --git a/core/modules/content_moderation/src/Entity/Handler/ModerationHandler.php b/core/modules/content_moderation/src/Entity/Handler/ModerationHandler.php
index f2c6917818..c44ab09841 100644
--- a/core/modules/content_moderation/src/Entity/Handler/ModerationHandler.php
+++ b/core/modules/content_moderation/src/Entity/Handler/ModerationHandler.php
@@ -35,11 +35,6 @@ public function onPresave(ContentEntityInterface $entity, $default_revision, $pu
     // This is probably not necessary if configuration is setup correctly.
     $entity->setNewRevision(TRUE);
     $entity->isDefaultRevision($default_revision);
-    if ($entity->hasField('revision_translation_affected')) {
-      // @todo remove this when revision and translation issues have been
-      // resolved. https://www.drupal.org/node/2860097
-      $entity->set('revision_translation_affected', TRUE);
-    }
 
     // Update publishing status if it can be updated and if it needs updating.
     if (($entity instanceof EntityPublishedInterface) && $entity->isPublished() !== $published_state) {
diff --git a/core/modules/content_moderation/src/Entity/Routing/EntityModerationRouteProvider.php b/core/modules/content_moderation/src/Entity/Routing/EntityModerationRouteProvider.php
index f72a47ede7..b7003eb1f8 100644
--- a/core/modules/content_moderation/src/Entity/Routing/EntityModerationRouteProvider.php
+++ b/core/modules/content_moderation/src/Entity/Routing/EntityModerationRouteProvider.php
@@ -88,7 +88,7 @@ protected function getLatestVersionRoute(EntityTypeInterface $entity_type) {
         ->setOption('parameters', [
           $entity_type_id => [
             'type' => 'entity:' . $entity_type_id,
-            'load_pending_revision' => 1,
+            'load_latest_revision' => 1,
           ],
         ]);
 
diff --git a/core/modules/content_moderation/src/EntityOperations.php b/core/modules/content_moderation/src/EntityOperations.php
index a7fcf7e8d9..a6099f760a 100644
--- a/core/modules/content_moderation/src/EntityOperations.php
+++ b/core/modules/content_moderation/src/EntityOperations.php
@@ -101,7 +101,6 @@ public function entityPresave(EntityInterface $entity) {
       // This entity is default if it is new, a new translation, the default
       // revision, or the default revision is not published.
       $update_default_revision = $entity->isNew()
-        || $entity->isNewTranslation()
         || $current_state->isDefaultRevisionState()
         || !$this->moderationInfo->isDefaultRevisionPublished($entity);
 
@@ -147,9 +146,10 @@ protected function updateOrCreateFromEntity(EntityInterface $entity) {
     $entity_revision_id = $entity->getRevisionId();
     $workflow = $this->moderationInfo->getWorkflowForEntity($entity);
     $content_moderation_state = ContentModerationStateEntity::loadFromModeratedEntity($entity);
+    /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
+    $storage = $this->entityTypeManager->getStorage('content_moderation_state');
 
     if (!($content_moderation_state instanceof ContentModerationStateInterface)) {
-      $storage = $this->entityTypeManager->getStorage('content_moderation_state');
       $content_moderation_state = $storage->create([
         'content_entity_type_id' => $entity->getEntityTypeId(),
         'content_entity_id' => $entity->id(),
@@ -159,11 +159,6 @@ protected function updateOrCreateFromEntity(EntityInterface $entity) {
       ]);
       $content_moderation_state->workflow->target_id = $workflow->id();
     }
-    elseif ($content_moderation_state->content_entity_revision_id->value != $entity_revision_id) {
-      // If a new revision of the content has been created, add a new content
-      // moderation state revision.
-      $content_moderation_state->setNewRevision(TRUE);
-    }
 
     // Sync translations.
     if ($entity->getEntityType()->hasKey('langcode')) {
@@ -176,6 +171,12 @@ protected function updateOrCreateFromEntity(EntityInterface $entity) {
       }
     }
 
+    // If a new revision of the content has been created, add a new content
+    // moderation state revision.
+    if (!$content_moderation_state->isNew() && $content_moderation_state->content_entity_revision_id->value != $entity_revision_id) {
+      $content_moderation_state = $storage->createRevision($content_moderation_state, $entity->isDefaultRevision());
+    }
+
     // Create the ContentModerationState entity for the inserted entity.
     $moderation_state = $entity->moderation_state->value;
     /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
@@ -245,23 +246,19 @@ public function entityTranslationDelete(EntityInterface $translation) {
    * @see EntityFieldManagerInterface::getExtraFields()
    */
   public function entityView(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
+    /** @var \Drupal\Core\Entity\ContentEntityInterface|\Drupal\Core\Entity\RevisionLogInterface $entity */
     if (!$this->moderationInfo->isModeratedEntity($entity)) {
       return;
     }
-    if (!$this->moderationInfo->isLatestRevision($entity)) {
-      return;
-    }
     if ($this->moderationInfo->isLiveRevision($entity)) {
       return;
     }
-    // Don't display the moderation form when when:
-    // - The revision is not translation affected.
-    // - There are more than one translation languages.
-    // - The entity has pending revisions.
-    if (!$this->moderationInfo->isPendingRevisionAllowed($entity)) {
+    // The moderation form should not be displayed when viewing old revisions.
+    // However it should be displayed if the latest affected revision is a
+    // pending revision.
+    if (!$entity->isDefaultRevision() && (($entity->wasDefaultRevision() && $this->moderationInfo->isDefaultRevisionPublished($entity)) || !$entity->isLatestTranslationAffectedRevision())) {
       return;
     }
-
     $component = $display->getComponent('content_moderation_control');
     if ($component) {
       $build['content_moderation_control'] = $this->formBuilder->getForm(EntityModerationForm::class, $entity);
diff --git a/core/modules/content_moderation/src/EntityTypeInfo.php b/core/modules/content_moderation/src/EntityTypeInfo.php
index 666ee2647f..e48157de2d 100644
--- a/core/modules/content_moderation/src/EntityTypeInfo.php
+++ b/core/modules/content_moderation/src/EntityTypeInfo.php
@@ -7,10 +7,12 @@
 use Drupal\Core\Entity\ContentEntityFormInterface;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Entity\ContentEntityTypeInterface;
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\Form\FormInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
@@ -268,6 +270,37 @@ public function entityBaseFieldInfo(EntityTypeInterface $entity_type) {
     return $fields;
   }
 
+  /**
+   * Replaces the entity form entity object with a proper revision object.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity being edited.
+   * @param string $operation
+   *   The entity form operation.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   *
+   * @see hook_entity_prepare_form()
+   */
+  public function entityPrepareForm(EntityInterface $entity, $operation, FormStateInterface $form_state) {
+    /** @var \Drupal\Core\Entity\EntityFormInterface $form_object */
+    $form_object = $form_state->getFormObject();
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+    if ($this->isModeratedEntityForm($form_object, $operation) && !$entity->isNew()) {
+      /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
+      $storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
+      // Generate a proper revision object for the current entity. This allows
+      // to correctly handle translatable entities having pending revisions.
+      $new_revision = $storage->createRevision($entity,FALSE);
+      // Restore the revision ID as other modules may expect to find it still
+      // populated. The entity object wil be marked as a new revision on
+      // presave.
+      $revision_key = $new_revision->getEntityType()->getKey('revision');
+      $new_revision->set($revision_key, $new_revision->getLoadedRevisionId());
+      $form_object->setEntity($new_revision);
+    }
+  }
+
   /**
    * Alters bundle forms to enforce revision handling.
    *
@@ -291,57 +324,15 @@ public function formAlter(array &$form, FormStateInterface $form_state, $form_id
         $this->entityTypeManager->getHandler($config_entity_type->getBundleOf(), 'moderation')->enforceRevisionsBundleFormAlter($form, $form_state, $form_id);
       }
     }
-    elseif ($form_object instanceof ContentEntityFormInterface && in_array($form_object->getOperation(), ['edit', 'default'])) {
+    elseif ($this->isModeratedEntityForm($form_object)) {
+      /** @var \Drupal\Core\Entity\ContentEntityFormInterface $form_object */
+      /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
       $entity = $form_object->getEntity();
       if ($this->moderationInfo->isModeratedEntity($entity)) {
         $this->entityTypeManager
           ->getHandler($entity->getEntityTypeId(), 'moderation')
           ->enforceRevisionsEntityFormAlter($form, $form_state, $form_id);
 
-        if (!$this->moderationInfo->isPendingRevisionAllowed($entity)) {
-          $latest_revision = $this->moderationInfo->getLatestRevision($entity->getEntityTypeId(), $entity->id());
-          if ($entity->bundle()) {
-            $bundle_type_id = $entity->getEntityType()->getBundleEntityType();
-            $bundle = $this->entityTypeManager->getStorage($bundle_type_id)->load($entity->bundle());
-            $type_label = $bundle->label();
-          }
-          else {
-            $type_label = $entity->getEntityType()->getLabel();
-          }
-
-          $translation = $this->moderationInfo->getAffectedRevisionTranslation($latest_revision);
-          $args = [
-            '@type_label' => $type_label,
-            '@latest_revision_edit_url' => $translation->toUrl('edit-form', ['language' => $translation->language()])->toString(),
-            '@latest_revision_delete_url' => $translation->toUrl('delete-form', ['language' => $translation->language()])->toString(),
-          ];
-          $label = $this->t('Unable to save this @type_label.', $args);
-          $message = $this->t('<a href="@latest_revision_edit_url">Publish</a> or <a href="@latest_revision_delete_url">delete</a> the latest revision to allow all workflow transitions.', $args);
-          $full_message = $this->t('Unable to save this @type_label. <a href="@latest_revision_edit_url">Publish</a> or <a href="@latest_revision_delete_url">delete</a> the latest revision to allow all workflow transitions.', $args);
-          drupal_set_message($full_message, 'error');
-
-          $form['moderation_state']['#access'] = FALSE;
-          $form['actions']['#access'] = FALSE;
-          $form['invalid_transitions'] = [
-            'label' => [
-              '#type' => 'item',
-              '#prefix' => '<strong class="label">',
-              '#markup' => $label,
-              '#suffix' => '</strong>',
-            ],
-            'message' => [
-              '#type' => 'item',
-              '#markup' => $message,
-            ],
-            '#weight' => 999,
-            '#no_valid_transitions' => TRUE,
-          ];
-
-          if ($form['footer']) {
-            $form['invalid_transitions']['#group'] = 'footer';
-          }
-        }
-
         // Submit handler to redirect to the latest version, if available.
         $form['actions']['submit']['#submit'][] = [EntityTypeInfo::class, 'bundleFormRedirect'];
 
@@ -360,6 +351,23 @@ public function formAlter(array &$form, FormStateInterface $form_state, $form_id
     }
   }
 
+  /**
+   * Checks whether the entity form should get content moderation.
+   *
+   * @param \Drupal\Core\Form\FormInterface $form_object
+   *   The form object.
+   * @param string $operation
+   *   The form operation.
+   *
+   * @return bool
+   *   TRUE if the form should get form moderation, FALSE otherwise.
+   */
+  protected function isModeratedEntityForm(FormInterface $form_object, $operation = NULL) {
+    return $form_object instanceof ContentEntityFormInterface &&
+      in_array($operation ?: $form_object->getOperation(), ['edit', 'default']) &&
+      $this->moderationInfo->isModeratedEntity($form_object->getEntity());
+  }
+
   /**
    * Redirect content entity edit forms on save, if there is a pending revision.
    *
diff --git a/core/modules/content_moderation/src/Form/EntityModerationForm.php b/core/modules/content_moderation/src/Form/EntityModerationForm.php
index 507ef54485..98f6fdf2b9 100644
--- a/core/modules/content_moderation/src/Form/EntityModerationForm.php
+++ b/core/modules/content_moderation/src/Form/EntityModerationForm.php
@@ -138,6 +138,9 @@ public function buildForm(array $form, FormStateInterface $form_state, ContentEn
   public function submitForm(array &$form, FormStateInterface $form_state) {
     /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
     $entity = $form_state->get('entity');
+    /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
+    $storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId());
+    $entity = $storage->createRevision($entity, $entity->isDefaultRevision());
 
     $new_state = $form_state->getValue('new_state');
 
diff --git a/core/modules/content_moderation/src/ModerationInformation.php b/core/modules/content_moderation/src/ModerationInformation.php
index 7e3e513307..4f60ab2e0a 100644
--- a/core/modules/content_moderation/src/ModerationInformation.php
+++ b/core/modules/content_moderation/src/ModerationInformation.php
@@ -127,13 +127,6 @@ public function getAffectedRevisionTranslation(ContentEntityInterface $entity) {
     }
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function isPendingRevisionAllowed(ContentEntityInterface $entity) {
-    return !(!$entity->isRevisionTranslationAffected() && count($entity->getTranslationLanguages()) > 1 && $this->hasPendingRevision($entity));
-  }
-
   /**
    * {@inheritdoc}
    */
@@ -145,8 +138,19 @@ public function isLatestRevision(ContentEntityInterface $entity) {
    * {@inheritdoc}
    */
   public function hasPendingRevision(ContentEntityInterface $entity) {
-    return $this->isModeratedEntity($entity)
-      && !($this->getLatestRevisionId($entity->getEntityTypeId(), $entity->id()) == $this->getDefaultRevisionId($entity->getEntityTypeId(), $entity->id()));
+    $result = FALSE;
+    if ($this->isModeratedEntity($entity)) {
+      /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
+      $storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
+      $latest_revision_id = $storage->getLatestTranslationAffectedRevisionId($entity->id(), $entity->language()->getId());
+      $default_revision_id = $this->getDefaultRevisionId($entity->getEntityTypeId(), $entity->id());
+      if ($latest_revision_id != $default_revision_id) {
+        /** @var \Drupal\Core\Entity\ContentEntityInterface|\Drupal\Core\Entity\RevisionLogInterface $latest_revision */
+        $latest_revision = $storage->loadRevision($latest_revision_id);
+        $result = !$latest_revision->wasDefaultRevision();
+      }
+    }
+    return $result;
   }
 
   /**
@@ -154,8 +158,7 @@ public function hasPendingRevision(ContentEntityInterface $entity) {
    */
   public function isLiveRevision(ContentEntityInterface $entity) {
     $workflow = $this->getWorkflowForEntity($entity);
-    return $this->isLatestRevision($entity)
-      && $entity->isDefaultRevision()
+    return $entity->isDefaultRevision()
       && $entity->moderation_state->value
       && $workflow->getTypePlugin()->getState($entity->moderation_state->value)->isPublishedState();
   }
@@ -165,7 +168,12 @@ public function isLiveRevision(ContentEntityInterface $entity) {
    */
   public function isDefaultRevisionPublished(ContentEntityInterface $entity) {
     $workflow = $this->getWorkflowForEntity($entity);
-    $default_revision = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId())->load($entity->id());
+
+    // Load the entity from the storage since the statically cached object may
+    // be polluted by local changes.
+    $default_revision = \Drupal::entityTypeManager()
+      ->getStorage($entity->getEntityTypeId())
+      ->loadUnchanged($entity->id());
 
     // Ensure we are checking all translations of the default revision.
     if ($default_revision instanceof TranslatableInterface && $default_revision->isTranslatable()) {
diff --git a/core/modules/content_moderation/src/ModerationInformationInterface.php b/core/modules/content_moderation/src/ModerationInformationInterface.php
index 1dafb3f71c..739c16b842 100644
--- a/core/modules/content_moderation/src/ModerationInformationInterface.php
+++ b/core/modules/content_moderation/src/ModerationInformationInterface.php
@@ -100,19 +100,6 @@ public function getDefaultRevisionId($entity_type_id, $entity_id);
    */
   public function getAffectedRevisionTranslation(ContentEntityInterface $entity);
 
-  /**
-   * Determines if pending revisions are allowed.
-   *
-   * @internal
-   *
-   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
-   *   The content entity.
-   *
-   * @return bool
-   *   If pending revisions are allowed.
-   */
-  public function isPendingRevisionAllowed(ContentEntityInterface $entity);
-
   /**
    * Determines if an entity is a latest revision.
    *
diff --git a/core/modules/content_moderation/src/ParamConverter/EntityRevisionConverter.php b/core/modules/content_moderation/src/ParamConverter/EntityRevisionConverter.php
index 8e7e3617a3..2421c4d6b8 100644
--- a/core/modules/content_moderation/src/ParamConverter/EntityRevisionConverter.php
+++ b/core/modules/content_moderation/src/ParamConverter/EntityRevisionConverter.php
@@ -2,11 +2,9 @@
 
 namespace Drupal\content_moderation\ParamConverter;
 
-use Drupal\Core\Entity\EntityInterface;
+use Drupal\content_moderation\ModerationInformationInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\ParamConverter\EntityConverter;
-use Drupal\Core\TypedData\TranslatableInterface;
-use Drupal\content_moderation\ModerationInformationInterface;
 use Symfony\Component\Routing\Route;
 
 /**
@@ -83,23 +81,8 @@ protected function isEditFormPage(Route $route) {
    * {@inheritdoc}
    */
   public function convert($value, $definition, $name, array $defaults) {
-    $entity = parent::convert($value, $definition, $name, $defaults);
-
-    if ($entity && $this->moderationInformation->isModeratedEntity($entity) && !$this->moderationInformation->isLatestRevision($entity)) {
-      $entity_type_id = $this->getEntityTypeFromDefaults($definition, $name, $defaults);
-      $latest_revision = $this->moderationInformation->getLatestRevision($entity_type_id, $value);
-
-      if ($latest_revision instanceof EntityInterface) {
-        // If the entity type is translatable, ensure we return the proper
-        // translation object for the current context.
-        if ($entity instanceof TranslatableInterface) {
-          $latest_revision = $this->entityManager->getTranslationFromContext($latest_revision, NULL, ['operation' => 'entity_upcast']);
-        }
-        $entity = $latest_revision;
-      }
-    }
-
-    return $entity;
+    // @todo Deprecate this in https://www.drupal.org/node/2924812.
+    return parent::convert($value, $definition, $name, $defaults);
   }
 
 }
diff --git a/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php b/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php
index b5263639e2..6b4a9c2117 100644
--- a/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php
+++ b/core/modules/content_moderation/src/Plugin/Field/ModerationStateFieldItemList.php
@@ -160,7 +160,6 @@ protected function updateModeratedEntity($moderation_state_id) {
       // This entity is default if it is new, a new translation, the default
       // revision state, or the default revision is not published.
       $update_default_revision = $entity->isNew()
-        || $entity->isNewTranslation()
         || $current_state->isDefaultRevisionState()
         || !$content_moderation_info->isDefaultRevisionPublished($entity);
 
diff --git a/core/modules/content_moderation/src/Plugin/views/filter/ModerationStateFilter.php b/core/modules/content_moderation/src/Plugin/views/filter/ModerationStateFilter.php
index 035ad3ea27..c658ef8759 100644
--- a/core/modules/content_moderation/src/Plugin/views/filter/ModerationStateFilter.php
+++ b/core/modules/content_moderation/src/Plugin/views/filter/ModerationStateFilter.php
@@ -176,8 +176,8 @@ protected function opSimple() {
         $entity_base_table_alias = $this->table;
 
         // The bundle field of an entity type is not revisionable so we need to
-        // join the data table.
-        $entity_base_table = $entity_type->isTranslatable() ? $entity_type->getDataTable() : $entity_type->getBaseTable();
+        // join the base table.
+        $entity_base_table = $entity_type->getBaseTable();
         $entity_revision_base_table = $entity_type->isTranslatable() ? $entity_type->getRevisionDataTable() : $entity_type->getRevisionTable();
         if ($this->table === $entity_revision_base_table) {
           $configuration = [
@@ -187,12 +187,6 @@ protected function opSimple() {
             'left_field' => $entity_type->getKey('id'),
             'type' => 'INNER',
           ];
-          if ($entity_type->isTranslatable()) {
-            $configuration['extra'][] = [
-              'field' => $entity_type->getKey('langcode'),
-              'left_field' => $entity_type->getKey('langcode'),
-            ];
-          }
 
           $join = Views::pluginManager('join')->createInstance('standard', $configuration);
           $entity_base_table_alias = $this->query->addRelationship($entity_base_table, $join, $entity_revision_base_table);
diff --git a/core/modules/content_moderation/tests/modules/content_moderation_test/content_moderation_test.info.yml b/core/modules/content_moderation/tests/modules/content_moderation_test/content_moderation_test.info.yml
new file mode 100644
index 0000000000..1416f147f9
--- /dev/null
+++ b/core/modules/content_moderation/tests/modules/content_moderation_test/content_moderation_test.info.yml
@@ -0,0 +1,9 @@
+name: 'Content moderation test'
+type: module
+description: 'Provides a testing module for Content Moderation.'
+package: Testing
+version: VERSION
+core: 8.x
+dependencies:
+  - content_moderation
+  - node
diff --git a/core/modules/content_moderation/tests/modules/content_moderation_test/content_moderation_test.module b/core/modules/content_moderation/tests/modules/content_moderation_test/content_moderation_test.module
new file mode 100644
index 0000000000..20853d9f4c
--- /dev/null
+++ b/core/modules/content_moderation/tests/modules/content_moderation_test/content_moderation_test.module
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @file Test module for Content Moderation.
+ */
+
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ *
+ * {@inheritdoc}
+ */
+function content_moderation_test_form_node_moderated_content_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) {
+  if (\Drupal::state()->get('content_moderation_test.concurrent_edit')) {
+    /** @var \Drupal\Core\Entity\ContentEntityForm $form_object */
+    $form_object = $form_state->getFormObject();
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+    $entity = $form_object->getEntity();
+    // We simulate a concurrent edit by setting the revision ID at the time the
+    // form is instantiated to the previous revision ID.
+    $form['original_latest_revision_id']['#default_value'] = $entity->getRevisionId() - 1;
+  }
+}
diff --git a/core/modules/content_moderation/tests/src/Functional/ModerationFormTest.php b/core/modules/content_moderation/tests/src/Functional/ModerationFormTest.php
index 13fd0c0571..b93ea622e7 100644
--- a/core/modules/content_moderation/tests/src/Functional/ModerationFormTest.php
+++ b/core/modules/content_moderation/tests/src/Functional/ModerationFormTest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Tests\content_moderation\Functional;
 
+use Drupal\Core\Entity\Plugin\Validation\Constraint\EntityChangedConstraint;
 use Drupal\workflows\Entity\Workflow;
 
 /**
@@ -21,6 +22,7 @@ class ModerationFormTest extends ModerationStateTestBase {
     'content_moderation',
     'locale',
     'content_translation',
+    'content_moderation_test',
   ];
 
   /**
@@ -295,14 +297,6 @@ public function testContentTranslationNodeForm() {
     $this->drupalGet($latest_version_path, ['language' => $french]);
     $this->assertTrue($this->xpath('//ul[@class="entity-moderation-form"]'));
 
-    // It should not be possible to add a new english revision.
-    $this->drupalGet($edit_path);
-    $this->assertSession()->fieldNotExists('moderation_state[0][state]');
-    $this->assertSession()->pageTextContains('Unable to save this Moderated content.');
-
-    $this->clickLink('Publish');
-    $this->assertSession()->fieldValueEquals('body[0][value]', 'Third version of the content.');
-
     $this->drupalGet($edit_path);
     $this->clickLink('Delete');
     $this->assertSession()->buttonExists('Delete');
@@ -323,7 +317,7 @@ public function testContentTranslationNodeForm() {
     $this->drupalGet($latest_version_path, ['language' => $french]);
     $this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
 
-    // Now we can publish the english (revision 5).
+    // Publish the english pending revision (revision 5).
     $this->drupalGet($edit_path);
     $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
     $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
@@ -336,13 +330,13 @@ public function testContentTranslationNodeForm() {
     $this->drupalGet($latest_version_path);
     $this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
 
-    // Make sure we're allowed to create a pending french revision.
+    // Make sure we are allowed to create a pending french revision.
     $this->drupalGet($edit_path, ['language' => $french]);
     $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
     $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
     $this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
 
-    // Add a english pending revision (revision 6).
+    // Add an english pending revision (revision 6).
     $this->drupalGet($edit_path);
     $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
     $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
@@ -354,16 +348,10 @@ public function testContentTranslationNodeForm() {
 
     $this->drupalGet($latest_version_path);
     $this->assertTrue($this->xpath('//ul[@class="entity-moderation-form"]'));
-
-    // Make sure we're not allowed to create a pending french revision.
-    $this->drupalGet($edit_path, ['language' => $french]);
-    $this->assertSession()->fieldNotExists('moderation_state[0][state]');
-    $this->assertSession()->pageTextContains('Unable to save this Moderated content.');
-
     $this->drupalGet($latest_version_path, ['language' => $french]);
     $this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
 
-    // We should be able to publish the english pending revision (revision 7)
+    // Publish the english pending revision (revision 7)
     $this->drupalGet($edit_path);
     $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
     $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
@@ -376,44 +364,17 @@ public function testContentTranslationNodeForm() {
     $this->drupalGet($latest_version_path);
     $this->assertFalse($this->xpath('//ul[@class="entity-moderation-form"]'));
 
-    // Make sure we're allowed to create a pending french revision.
+    // Make sure we are allowed to create a pending french revision.
     $this->drupalGet($edit_path, ['language' => $french]);
     $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
     $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
     $this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
 
-    // Make sure we're allowed to create a pending english revision.
-    $this->drupalGet($edit_path);
-    $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
-    $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
-    $this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
-
-    // Create new moderated content. (revision 1).
-    $this->drupalPostForm('node/add/moderated_content', [
-      'title[0][value]' => 'Second moderated content',
-      'body[0][value]' => 'First version of the content.',
-      'moderation_state[0][state]' => 'published',
-    ], t('Save'));
-
-    $node = $this->drupalGetNodeByTitle('Second moderated content');
-    $this->assertTrue($node->language(), 'en');
-    $edit_path = sprintf('node/%d/edit', $node->id());
-    $translate_path = sprintf('node/%d/translations/add/en/fr', $node->id());
-
-    // Add a pending revision (revision 2).
+    // Make sure we are allowed to create a pending english revision.
     $this->drupalGet($edit_path);
     $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
     $this->assertSession()->optionExists('moderation_state[0][state]', 'published');
     $this->assertSession()->optionExists('moderation_state[0][state]', 'archived');
-    $this->drupalPostForm(NULL, [
-      'body[0][value]' => 'Second version of the content.',
-      'moderation_state[0][state]' => 'draft',
-    ], t('Save'));
-
-    // It shouldn't be possible to translate as we have a pending revision.
-    $this->drupalGet($translate_path);
-    $this->assertSession()->fieldNotExists('moderation_state[0][state]');
-    $this->assertSession()->pageTextContains('Unable to save this Moderated content.');
 
     // Create new moderated content (revision 1).
     $this->drupalPostForm('node/add/moderated_content', [
@@ -444,11 +405,6 @@ public function testContentTranslationNodeForm() {
       'moderation_state[0][state]' => 'draft',
     ], t('Save (this translation)'));
 
-    // Editing the original translation should not be possible.
-    $this->drupalGet($edit_path);
-    $this->assertSession()->fieldNotExists('moderation_state[0][state]');
-    $this->assertSession()->pageTextContains('Unable to save this Moderated content.');
-
     // Updating and publishing the french translation is still possible.
     $this->drupalGet($edit_path, ['language' => $french]);
     $this->assertSession()->optionExists('moderation_state[0][state]', 'draft');
@@ -530,4 +486,43 @@ public function testWorkflowInUse() {
     }
   }
 
+  /**
+   * Tests that concurrent edits of the same draft are not allowed.
+   */
+  public function testConcurrentDraftEdit() {
+    // Create new moderated content in published status.
+    $title = 'Some moderated content';
+    $edit = [
+      'title[0][value]' => $title,
+      'body[0][value]' => 'First version of the content.',
+      'moderation_state[0][state]' => 'published',
+    ];
+    $this->drupalPostForm('node/add/moderated_content', $edit, t('Save'));
+
+    $node = $this->drupalGetNodeByTitle($title);
+    $edit_path = sprintf('node/%d/edit', $node->id());
+
+    // Create a draft.
+    $edit = [
+      'body[0][value]' => 'Second version of the content.',
+      'moderation_state[0][state]' => 'draft',
+    ];
+    $this->drupalPostForm($edit_path, $edit, t('Save'));
+    /** @var \Drupal\node\NodeStorageInterface $node_storage */
+    $node_storage = \Drupal::entityTypeManager()->getStorage('node');
+    $revision_id = $node_storage->getLatestRevisionId($node->id());
+
+    // Simulate a concurrent draft post: we expect to see a validation error in
+    // this case.
+    \Drupal::state()->set('content_moderation_test.concurrent_edit', 1);
+    $edit = [
+      'body[0][value]' => 'Third version of the content.',
+      'moderation_state[0][state]' => 'draft',
+    ];
+    $this->drupalPostForm($edit_path, $edit, t('Save'));
+    $this->assertEquals($revision_id, $node_storage->getLatestRevisionId($node->id()));
+    $this->assertSession()
+      ->responseContains((new EntityChangedConstraint())->message);
+  }
+
 }
diff --git a/core/modules/content_moderation/tests/src/Kernel/ViewsDataIntegrationTest.php b/core/modules/content_moderation/tests/src/Kernel/ViewsDataIntegrationTest.php
index a1b396fb45..00e107295a 100644
--- a/core/modules/content_moderation/tests/src/Kernel/ViewsDataIntegrationTest.php
+++ b/core/modules/content_moderation/tests/src/Kernel/ViewsDataIntegrationTest.php
@@ -108,14 +108,12 @@ public function testContentModerationStateBaseJoin() {
     $expected_result = [
       [
         'nid' => $node->id(),
-        // @todo I would have expected that the content_moderation_state default
-        //   revision is the same one as in the node, but it isn't.
         // Joins from the base table to the default revision of the
         // content_moderation.
-        'moderation_state' => 'draft',
+        'moderation_state' => 'published',
         // Joins from the revision table to the default revision of the
         // content_moderation.
-        'moderation_state_1' => 'draft',
+        'moderation_state_1' => 'published',
         // Joins from the revision table to the revision of the
         // content_moderation.
         'moderation_state_2' => 'published',
diff --git a/core/modules/content_moderation/tests/src/Kernel/ViewsModerationStateFilterTest.php b/core/modules/content_moderation/tests/src/Kernel/ViewsModerationStateFilterTest.php
index 637e70a99f..da7ac9299d 100644
--- a/core/modules/content_moderation/tests/src/Kernel/ViewsModerationStateFilterTest.php
+++ b/core/modules/content_moderation/tests/src/Kernel/ViewsModerationStateFilterTest.php
@@ -121,10 +121,10 @@ public function testStateFilterViewsRelationship() {
     $translated_forward_revision->moderation_state = 'translated_draft';
     $translated_forward_revision->save();
 
-    // Four revisions for the nodes when no filter.
-    $this->assertNodesWithFilters([$node, $second_node, $third_node, $third_node], []);
+    // The three default revisions are listed when no filter is specified.
+    $this->assertNodesWithFilters([$node, $second_node, $third_node], []);
 
-    // The default revision of node one and three is published.
+    // The default revision of node one and three are published.
     $this->assertNodesWithFilters([$node, $third_node], [
       'default_revision_state' => 'editorial-published',
     ]);
diff --git a/core/modules/content_translation/config/schema/content_translation.schema.yml b/core/modules/content_translation/config/schema/content_translation.schema.yml
index 808a6e73e8..73676724bd 100644
--- a/core/modules/content_translation/config/schema/content_translation.schema.yml
+++ b/core/modules/content_translation/config/schema/content_translation.schema.yml
@@ -18,3 +18,9 @@ language.content_settings.*.*.third_party.content_translation:
     enabled:
       type: boolean
       label: 'Content translation enabled'
+    bundle_settings:
+      type: sequence
+      label: 'Content translation bundle settings'
+      sequence:
+        type: string
+        label: 'Bundle settings values'
diff --git a/core/modules/content_translation/content_translation.admin.inc b/core/modules/content_translation/content_translation.admin.inc
index bf33ba0af0..f157749a90 100644
--- a/core/modules/content_translation/content_translation.admin.inc
+++ b/core/modules/content_translation/content_translation.admin.inc
@@ -5,6 +5,7 @@
  * The content translation administration forms.
  */
 
+use Drupal\content_translation\BundleTranslationSettingsInterface;
 use Drupal\Core\Config\Entity\ThirdPartySettingsInterface;
 use Drupal\Core\Entity\ContentEntityTypeInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
@@ -83,6 +84,7 @@ function _content_translation_form_language_content_settings_form_alter(array &$
     return;
   }
 
+  /** @var \Drupal\content_translation\ContentTranslationManagerInterface $content_translation_manager */
   $content_translation_manager = \Drupal::service('content_translation.manager');
   $default = $form['entity_types']['#default_value'];
   foreach ($default as $entity_type_id => $enabled) {
@@ -110,6 +112,23 @@ function _content_translation_form_language_content_settings_form_alter(array &$
         continue;
       }
 
+      // Displayed the "shared fields widgets" toggle.
+      if ($content_translation_manager instanceof BundleTranslationSettingsInterface) {
+        $settings = $content_translation_manager->getBundleTranslationSettings($entity_type_id, $bundle);
+        $form['settings'][$entity_type_id][$bundle]['settings']['content_translation']['untranslatable_fields_hide'] = [
+          '#type' => 'checkbox',
+          '#title' => t('Hide non translatable fields on translation forms'),
+          '#default_value' => !empty($settings['untranslatable_fields_hide']),
+          '#states' => [
+            'visible' => [
+              ':input[name="settings[' . $entity_type_id . '][' . $bundle . '][translatable]"]' => [
+                'checked' => TRUE,
+              ],
+            ],
+          ],
+        ];
+      }
+
       $fields = $entity_manager->getFieldDefinitions($entity_type_id, $bundle);
       if ($fields) {
         foreach ($fields as $field_name => $definition) {
@@ -317,6 +336,8 @@ function content_translation_form_language_content_settings_validate(array $form
  * @see content_translation_admin_settings_form_validate()
  */
 function content_translation_form_language_content_settings_submit(array $form, FormStateInterface $form_state) {
+  /** @var \Drupal\content_translation\ContentTranslationManagerInterface $content_translation_manager */
+  $content_translation_manager = \Drupal::service('content_translation.manager');
   $entity_types = $form_state->getValue('entity_types');
   $settings = &$form_state->getValue('settings');
 
@@ -347,7 +368,12 @@ function content_translation_form_language_content_settings_submit(array $form,
       }
       if (isset($bundle_settings['translatable'])) {
         // Store whether a bundle has translation enabled or not.
-        \Drupal::service('content_translation.manager')->setEnabled($entity_type_id, $bundle, $bundle_settings['translatable']);
+        $content_translation_manager->setEnabled($entity_type_id, $bundle, $bundle_settings['translatable']);
+
+        // Store any other bundle settings.
+        if ($content_translation_manager instanceof BundleTranslationSettingsInterface) {
+          $content_translation_manager->setBundleTranslationSettings($entity_type_id, $bundle, $bundle_settings['settings']['content_translation']);
+        }
 
         // Save translation_sync settings.
         if (!empty($bundle_settings['columns'])) {
@@ -367,8 +393,8 @@ function content_translation_form_language_content_settings_submit(array $form,
       }
     }
   }
+
   // Ensure entity and menu router information are correctly rebuilt.
   \Drupal::entityManager()->clearCachedDefinitions();
   \Drupal::service('router.builder')->setRebuildNeeded();
-
 }
diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module
index 6a0c229adb..9349824ca1 100644
--- a/core/modules/content_translation/content_translation.module
+++ b/core/modules/content_translation/content_translation.module
@@ -5,6 +5,7 @@
  * Allows entities to be translated into different languages.
  */
 
+use Drupal\content_translation\BundleTranslationSettingsInterface;
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\Entity\ContentEntityFormInterface;
 use Drupal\Core\Entity\ContentEntityInterface;
@@ -59,6 +60,13 @@ function content_translation_module_implements_alter(&$implementations, $hook) {
       unset($implementations['content_translation']);
       $implementations['content_translation'] = $group;
       break;
+
+    // Move our hook_entity_bundle_info_alter() implementation to the top of the
+    // list.
+    case 'entity_bundle_info_alter':
+      $group = $implementations['content_translation'];
+      $implementations = ['content_translation' => $group] + $implementations;
+      break;
   }
 }
 
@@ -161,9 +169,15 @@ function content_translation_entity_type_alter(array &$entity_types) {
  * Implements hook_entity_bundle_info_alter().
  */
 function content_translation_entity_bundle_info_alter(&$bundles) {
-  foreach ($bundles as $entity_type => &$info) {
+  /** @var \Drupal\content_translation\ContentTranslationManagerInterface $content_translation_manager */
+  $content_translation_manager = \Drupal::service('content_translation.manager');
+  foreach ($bundles as $entity_type_id => &$info) {
     foreach ($info as $bundle => &$bundle_info) {
-      $bundle_info['translatable'] = \Drupal::service('content_translation.manager')->isEnabled($entity_type, $bundle);
+      $bundle_info['translatable'] = $content_translation_manager->isEnabled($entity_type_id, $bundle);
+      if ($content_translation_manager instanceof BundleTranslationSettingsInterface) {
+        $settings = $content_translation_manager->getBundleTranslationSettings($entity_type_id, $bundle);
+        $bundle_info['untranslatable_fields.default_translation_affected'] = !empty($settings['untranslatable_fields_hide']);
+      }
     }
   }
 }
@@ -319,6 +333,11 @@ function content_translation_form_alter(array &$form, FormStateInterface $form_s
       }
     }
 
+    // The footer region, if defined, may contain multilingual widgets so we
+    // need to always display it.
+    if (isset($form['footer'])) {
+      $form['footer']['#multilingual'] = TRUE;
+    }
   }
 }
 
@@ -410,16 +429,15 @@ function content_translation_form_field_config_edit_form_alter(array &$form, For
  */
 function content_translation_entity_presave(EntityInterface $entity) {
   if ($entity instanceof ContentEntityInterface && $entity->isTranslatable() && !$entity->isNew()) {
+    /** @var \Drupal\content_translation\ContentTranslationManagerInterface $manager */
+    $manager = \Drupal::service('content_translation.manager');
+    if (!$manager->isEnabled($entity->getEntityTypeId(), $entity->bundle())) {
+      return;
+    }
     // If we are creating a new translation we need to use the source language
     // as original language, since source values are the only ones available to
     // compare against.
-    if (!isset($entity->original)) {
-      $entity->original = \Drupal::entityTypeManager()
-        ->getStorage($entity->entityType())->loadUnchanged($entity->id());
-    }
     $langcode = $entity->language()->getId();
-    /** @var \Drupal\content_translation\ContentTranslationManagerInterface $manager */
-    $manager = \Drupal::service('content_translation.manager');
     $source_langcode = !$entity->original->hasTranslation($langcode) ? $manager->getTranslationMetadata($entity)->getSource() : NULL;
     \Drupal::service('content_translation.synchronizer')->synchronizeFields($entity, $langcode, $source_langcode);
   }
diff --git a/core/modules/content_translation/src/BundleTranslationSettingsInterface.php b/core/modules/content_translation/src/BundleTranslationSettingsInterface.php
new file mode 100644
index 0000000000..b6bf89299e
--- /dev/null
+++ b/core/modules/content_translation/src/BundleTranslationSettingsInterface.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Drupal\content_translation;
+
+/**
+ * Interface providing support for content translation bundle settings.
+ */
+interface BundleTranslationSettingsInterface {
+
+  /**
+   * Returns translation settings for the specified bundle.
+   *
+   * @param string $entity_type_id
+   *   The entity type identifier.
+   * @param string $bundle
+   *   The bundle name.
+   *
+   * @return array
+   *   An associative array of values keyed by setting name.
+   */
+  public function getBundleTranslationSettings($entity_type_id, $bundle);
+
+  /**
+   * Sets translation settings for the specified bundle.
+   *
+   * @param array $settings
+   *   An associative array of values keyed by setting name.
+   * @param string $entity_type_id
+   *   The entity type identifier.
+   * @param string $bundle
+   *   The bundle name.
+   */
+  public function setBundleTranslationSettings($entity_type_id, $bundle, array $settings);
+
+}
diff --git a/core/modules/content_translation/src/ContentTranslationHandler.php b/core/modules/content_translation/src/ContentTranslationHandler.php
index 7e6a7ca3ea..cb6776f0e5 100644
--- a/core/modules/content_translation/src/ContentTranslationHandler.php
+++ b/core/modules/content_translation/src/ContentTranslationHandler.php
@@ -5,6 +5,7 @@
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
 use Drupal\Core\Entity\EntityChangedInterface;
+use Drupal\Core\Entity\EntityChangesDetectionTrait;
 use Drupal\Core\Entity\EntityHandlerInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
@@ -13,8 +14,10 @@
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Messenger\MessengerInterface;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\user\Entity\User;
 use Drupal\user\EntityOwnerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -25,7 +28,10 @@
  * @ingroup entity_api
  */
 class ContentTranslationHandler implements ContentTranslationHandlerInterface, EntityHandlerInterface {
+
+  use EntityChangesDetectionTrait;
   use DependencySerializationTrait;
+  use StringTranslationTrait;
 
   /**
    * The type of the entity being translated.
@@ -70,6 +76,13 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
    */
   protected $fieldStorageDefinitions;
 
+  /**
+   * The messenger service.
+   *
+   * @var \Drupal\Core\Messenger\MessengerInterface
+   */
+  protected $messenger;
+
   /**
    * Initializes an instance of the content translation controller.
    *
@@ -83,14 +96,17 @@ class ContentTranslationHandler implements ContentTranslationHandlerInterface, E
    *   The entity manager.
    * @param \Drupal\Core\Session\AccountInterface $current_user
    *   The current user.
+   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
+   *   The messenger service.
    */
-  public function __construct(EntityTypeInterface $entity_type, LanguageManagerInterface $language_manager, ContentTranslationManagerInterface $manager, EntityManagerInterface $entity_manager, AccountInterface $current_user) {
+  public function __construct(EntityTypeInterface $entity_type, LanguageManagerInterface $language_manager, ContentTranslationManagerInterface $manager, EntityManagerInterface $entity_manager, AccountInterface $current_user, MessengerInterface $messenger) {
     $this->entityTypeId = $entity_type->id();
     $this->entityType = $entity_type;
     $this->languageManager = $language_manager;
     $this->manager = $manager;
     $this->currentUser = $current_user;
     $this->fieldStorageDefinitions = $entity_manager->getLastInstalledFieldStorageDefinitions($this->entityTypeId);
+    $this->messenger = $messenger;
   }
 
   /**
@@ -102,7 +118,8 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
       $container->get('language_manager'),
       $container->get('content_translation.manager'),
       $container->get('entity.manager'),
-      $container->get('current_user')
+      $container->get('current_user'),
+      $container->get('messenger')
     );
   }
 
@@ -269,6 +286,8 @@ public function getSourceLangcode(FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function entityFormAlter(array &$form, FormStateInterface $form_state, EntityInterface $entity) {
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+
     $form_object = $form_state->getFormObject();
     $form_langcode = $form_object->getFormLangcode($form_state);
     $entity_langcode = $entity->getUntranslated()->language()->getId();
@@ -512,6 +531,20 @@ public function entityFormSharedElements($element, FormStateInterface $form_stat
       $ignored_types = array_flip(['actions', 'value', 'hidden', 'vertical_tabs', 'token', 'details']);
     }
 
+    /** @var \Drupal\Core\Entity\ContentEntityForm $form_object */
+    $form_object = $form_state->getFormObject();
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+    $entity = $form_object->getEntity();
+    $display_translatability_clue = !$entity->isDefaultTranslationAffectedOnly();
+    $hide_untranslatable_fields = $entity->isDefaultTranslationAffectedOnly() && !$entity->isDefaultTranslation();
+    $translation_form = $form_state->get(['content_translation', 'translation_form']);
+    $display_warning = FALSE;
+
+    // We use field definitions to identify untranslatable field widgets to be
+    // hidden. Fields that are not involved in translation changes checks should
+    // not be affected by this logic (the "revision_log" field, for instance).
+    $field_definitions = array_diff_key($entity->getFieldDefinitions(), array_flip($this->getFieldsToSkipFromTranslationChangesCheck($entity)));
+
     foreach (Element::children($element) as $key) {
       if (!isset($element[$key]['#type'])) {
         $this->entityFormSharedElements($element[$key], $form_state, $form);
@@ -524,10 +557,17 @@ public function entityFormSharedElements($element, FormStateInterface $form_stat
         // Elements are considered to be non multilingual by default.
         if (empty($element[$key]['#multilingual'])) {
           // If we are displaying a multilingual entity form we need to provide
-          // translatability clues, otherwise the shared form elements should be
-          // hidden.
-          if (!$form_state->get(['content_translation', 'translation_form'])) {
-            $this->addTranslatabilityClue($element[$key]);
+          // translatability clues, otherwise the non-multilingual form elements
+          // should be hidden.
+          if (!$translation_form) {
+            if ($display_translatability_clue) {
+              $this->addTranslatabilityClue($element[$key]);
+            }
+            // Hide widgets for untranslatable fields.
+            if ($hide_untranslatable_fields && isset($field_definitions[$key])) {
+              $element[$key]['#access'] = FALSE;
+              $display_warning = TRUE;
+            }
           }
           else {
             $element[$key]['#access'] = FALSE;
@@ -536,6 +576,11 @@ public function entityFormSharedElements($element, FormStateInterface $form_stat
       }
     }
 
+    if ($display_warning && !$form_state->isSubmitted() && !$form_state->isRebuilding()) {
+      $url = $entity->getUntranslated()->toUrl('edit-form')->toString();
+      $this->messenger->addWarning($this->t('Fields that apply to all languages are hidden to avoid conflicting changes. <a href=":url">Edit them on the original language form</a>.', [':url' => $url]));
+    }
+
     return $element;
   }
 
diff --git a/core/modules/content_translation/src/ContentTranslationManager.php b/core/modules/content_translation/src/ContentTranslationManager.php
index da8bd1c618..8b3831a251 100644
--- a/core/modules/content_translation/src/ContentTranslationManager.php
+++ b/core/modules/content_translation/src/ContentTranslationManager.php
@@ -8,7 +8,7 @@
 /**
  * Provides common functionality for content translation.
  */
-class ContentTranslationManager implements ContentTranslationManagerInterface {
+class ContentTranslationManager implements ContentTranslationManagerInterface, BundleTranslationSettingsInterface {
 
   /**
    * The entity type manager.
@@ -105,6 +105,23 @@ public function isEnabled($entity_type_id, $bundle = NULL) {
     return $enabled;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function setBundleTranslationSettings($entity_type_id, $bundle, array $settings) {
+    $config = $this->loadContentLanguageSettings($entity_type_id, $bundle);
+    $config->setThirdPartySetting('content_translation', 'bundle_settings', $settings)
+      ->save();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getBundleTranslationSettings($entity_type_id, $bundle) {
+    $config = $this->loadContentLanguageSettings($entity_type_id, $bundle);
+    return $config->getThirdPartySetting('content_translation', 'bundle_settings', []);
+  }
+
   /**
    * Loads a content language config entity based on the entity type and bundle.
    *
diff --git a/core/modules/content_translation/src/Controller/ContentTranslationController.php b/core/modules/content_translation/src/Controller/ContentTranslationController.php
index 190778d42c..8db63d7530 100644
--- a/core/modules/content_translation/src/Controller/ContentTranslationController.php
+++ b/core/modules/content_translation/src/Controller/ContentTranslationController.php
@@ -87,6 +87,7 @@ public function overview(RouteMatchInterface $route_match, $entity_type_id = NUL
     $handler = $this->entityManager()->getHandler($entity_type_id, 'translation');
     $manager = $this->manager;
     $entity_type = $entity->getEntityType();
+    $revisionable = $entity_type->isRevisionable();
 
     // Start collecting the cacheability metadata, starting with the entity and
     // later merge in the access result cacheability metadata.
@@ -99,6 +100,9 @@ public function overview(RouteMatchInterface $route_match, $entity_type_id = NUL
 
     $rows = [];
     $show_source_column = FALSE;
+    $default_revision = $entity;
+    /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
+    $storage = $this->entityTypeManager()->getStorage($entity_type_id);
 
     if ($this->languageManager()->isMultilingual()) {
       // Determine whether the current entity is translatable.
@@ -121,6 +125,16 @@ public function overview(RouteMatchInterface $route_match, $entity_type_id = NUL
         $language_name = $language->getName();
         $langcode = $language->getId();
 
+        // If the entity type is revisionable, we may have pending revisions
+        // with translations not available yet in the default revision. Thus we
+        // need to load the latest affected revision for the each language to be
+        // sure we are listing all available translations.
+        if ($revisionable) {
+          $latest_revision_id = $storage->getLatestTranslationAffectedRevisionId($entity->id(), $langcode);
+          $entity = $latest_revision_id ? $storage->loadRevision($latest_revision_id) : $default_revision;
+          $translations = $entity->getTranslationLanguages();
+        }
+
         $add_url = new Url(
           "entity.$entity_type_id.content_translation_add",
           [
@@ -330,8 +344,19 @@ public function overview(RouteMatchInterface $route_match, $entity_type_id = NUL
    *   A processed form array ready to be rendered.
    */
   public function add(LanguageInterface $source, LanguageInterface $target, RouteMatchInterface $route_match, $entity_type_id = NULL) {
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
     $entity = $route_match->getParameter($entity_type_id);
 
+    // In case of a pending revision, make sure we load the latest affected
+    // revision for the source language, otherwise the form initial values may
+    // not be up to date.
+    if (!$entity->isDefaultRevision()) {
+      /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
+      $storage = $this->entityTypeManager()->getStorage($entity->getEntityTypeId());
+      $revision_id = $storage->getLatestTranslationAffectedRevisionId($entity->id(), $source->getId());
+      $entity = $storage->loadRevision($revision_id);
+    }
+
     // @todo Exploit the upcoming hook_entity_prepare() when available.
     // See https://www.drupal.org/node/1810394.
     $this->prepareTranslation($entity, $source, $target);
diff --git a/core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php b/core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php
index 6aae426abe..d1107eb85f 100644
--- a/core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php
+++ b/core/modules/content_translation/src/Routing/ContentTranslationRouteSubscriber.php
@@ -70,6 +70,7 @@ protected function alterRoutes(RouteCollection $collection) {
           'parameters' => [
             $entity_type_id => [
               'type' => 'entity:' . $entity_type_id,
+              'load_latest_revision' => TRUE,
             ],
           ],
           '_admin_route' => $is_admin,
@@ -102,6 +103,7 @@ protected function alterRoutes(RouteCollection $collection) {
             ],
             $entity_type_id => [
               'type' => 'entity:' . $entity_type_id,
+              'load_latest_revision' => TRUE,
             ],
           ],
           '_admin_route' => $is_admin,
@@ -127,6 +129,7 @@ protected function alterRoutes(RouteCollection $collection) {
             ],
             $entity_type_id => [
               'type' => 'entity:' . $entity_type_id,
+              'load_latest_revision' => TRUE,
             ],
           ],
           '_admin_route' => $is_admin,
@@ -152,6 +155,7 @@ protected function alterRoutes(RouteCollection $collection) {
             ],
             $entity_type_id => [
               'type' => 'entity:' . $entity_type_id,
+              'load_latest_revision' => TRUE,
             ],
           ],
           '_admin_route' => $is_admin,
diff --git a/core/modules/content_translation/tests/src/Functional/ContentTranslationTestBase.php b/core/modules/content_translation/tests/src/Functional/ContentTranslationTestBase.php
index ec7f87dd1b..d3e81a6b4a 100644
--- a/core/modules/content_translation/tests/src/Functional/ContentTranslationTestBase.php
+++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationTestBase.php
@@ -138,7 +138,7 @@ protected function getEditorPermissions() {
    * Returns an array of permissions needed for the administrator.
    */
   protected function getAdministratorPermissions() {
-    return array_merge($this->getEditorPermissions(), $this->getTranslatorPermissions(), ['administer content translation']);
+    return array_merge($this->getEditorPermissions(), $this->getTranslatorPermissions(), ['administer languages', 'administer content translation']);
   }
 
   /**
diff --git a/core/modules/content_translation/tests/src/Functional/ContentTranslationUntranslatableFieldsTest.php b/core/modules/content_translation/tests/src/Functional/ContentTranslationUntranslatableFieldsTest.php
new file mode 100644
index 0000000000..75ed9255fe
--- /dev/null
+++ b/core/modules/content_translation/tests/src/Functional/ContentTranslationUntranslatableFieldsTest.php
@@ -0,0 +1,118 @@
+<?php
+
+namespace Drupal\Tests\content_translation\Functional;
+
+use Drupal\Core\Url;
+use Drupal\language\Entity\ConfigurableLanguage;
+
+/**
+ * Tests the untranslatable fields behaviors.
+ *
+ * @group content_translation
+ */
+class ContentTranslationUntranslatableFieldsTest extends ContentTranslationTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['language', 'content_translation', 'entity_test'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // Configure one field as untranslatable.
+    $this->drupalLogin($this->administrator);
+    $edit = [
+      'settings[' . $this->entityTypeId . '][' . $this->bundle . '][fields][' . $this->fieldName . ']' => 0,
+    ];
+    $this->drupalPostForm('admin/config/regional/content-language', $edit, 'Save configuration');
+
+    /** @var \Drupal\Core\Entity\EntityFieldManagerInterface $entity_field_manager */
+    $entity_field_manager = $this->container->get('entity_field.manager');
+    $entity_field_manager->clearCachedFieldDefinitions();
+    $definitions = $entity_field_manager->getFieldDefinitions($this->entityTypeId, $this->bundle);
+    $this->assertFalse($definitions[$this->fieldName]->isTranslatable());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditorPermissions() {
+    return array_merge(parent::getTranslatorPermissions(), ['administer entity_test content', 'view test entity']);
+  }
+
+  /**
+   * Tests that hiding untranslatable field widgets works correctly.
+   */
+  public function testHiddenWidgets() {
+    /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
+    $entity_type_manager = $this->container->get('entity_type.manager');
+    $id = $this->createEntity([], 'en');
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+    $entity = $entity_type_manager
+      ->getStorage($this->entityTypeId)
+      ->load($id);
+
+    // Check that the untranslatable field widget is displayed on the edit form
+    // and no translatability clue is displayed yet.
+    $this->drupalGet($entity->toUrl('edit-form'));
+    $field_xpath = '//input[@name="' . $this->fieldName . '[0][value]"]';
+    $this->assertNotEmpty($this->xpath($field_xpath));
+    $clue_xpath = '//label[@for="edit-' . strtr($this->fieldName, '_', '-') . '-0-value"]/span[text()="(all languages)"]';
+    $this->assertEmpty($this->xpath($clue_xpath));
+
+    // Add a translation and check that the untranslatable field widget is
+    // displayed on the translation and edit forms along with translatability
+    // clues.
+    $add_url = Url::fromRoute("entity.{$this->entityTypeId}.content_translation_add", [
+      $entity->getEntityTypeId() => $entity->id(),
+      'source' => 'en',
+      'target' => 'it'
+    ]);
+    $this->drupalGet($add_url);
+    $this->assertNotEmpty($this->xpath($field_xpath));
+    $this->assertNotEmpty($this->xpath($clue_xpath));
+    $this->drupalPostForm(NULL, [], 'Save');
+
+    // Check that the widget is displayed along with its clue in the edit form
+    // for both languages.
+    $this->drupalGet($entity->toUrl('edit-form'));
+    $this->assertNotEmpty($this->xpath($field_xpath));
+    $this->assertNotEmpty($this->xpath($clue_xpath));
+    $it_language = ConfigurableLanguage::load('it');
+    $this->drupalGet($entity->toUrl('edit-form', ['language' => $it_language]));
+    $this->assertNotEmpty($this->xpath($field_xpath));
+    $this->assertNotEmpty($this->xpath($clue_xpath));
+
+    // Configure untranslatable field widgets to be hidden on non-default
+    // language edit forms.
+    $edit = [
+      'settings[' . $this->entityTypeId . '][' . $this->bundle . '][settings][content_translation][untranslatable_fields_hide]' => 1,
+    ];
+    $this->drupalPostForm('admin/config/regional/content-language', $edit, 'Save configuration');
+
+    // Verify that the widget is displayed in the default language edit form,
+    // but no clue is displayed.
+    $this->drupalGet($entity->toUrl('edit-form'));
+    $field_xpath = '//input[@name="' . $this->fieldName . '[0][value]"]';
+    $this->assertNotEmpty($this->xpath($field_xpath));
+    $this->assertEmpty($this->xpath($clue_xpath));
+
+    // Verify no widget is displayed on the non-default language edit form.
+    $this->drupalGet($entity->toUrl('edit-form', ['language' => $it_language]));
+    $this->assertEmpty($this->xpath($field_xpath));
+    $this->assertEmpty($this->xpath($clue_xpath));
+
+    // Verify a warning is displayed.
+    $this->assertSession()->pageTextContains('Fields that apply to all languages are hidden to avoid conflicting changes.');
+    $edit_path = $entity->toUrl('edit-form')->toString();
+    $link_xpath = '//a[@href=:edit_path and text()="Edit them on the original language form"]';
+    $elements = $this->xpath($link_xpath, [':edit_path' => $edit_path]);
+    $this->assertNotEmpty($elements);
+  }
+}
diff --git a/core/modules/node/src/Controller/NodeController.php b/core/modules/node/src/Controller/NodeController.php
index 48401475fc..d91d3e1b05 100644
--- a/core/modules/node/src/Controller/NodeController.php
+++ b/core/modules/node/src/Controller/NodeController.php
@@ -173,6 +173,7 @@ public function revisionOverview(NodeInterface $node) {
 
     $rows = [];
     $default_revision = $node->getRevisionId();
+    $current_revision_displayed = FALSE;
 
     foreach ($this->getRevisionIds($node, $node_storage) as $vid) {
       /** @var \Drupal\node\NodeInterface $revision */
@@ -187,11 +188,17 @@ public function revisionOverview(NodeInterface $node) {
 
         // Use revision link to link to revisions that are not active.
         $date = $this->dateFormatter->format($revision->revision_timestamp->value, 'short');
-        if ($vid != $node->getRevisionId()) {
+
+        // We treat also the latest affected revision as current revision, if it
+        // was the default revision, as its values for the current language will
+        // be the same of the current default revision in this case.
+        $current_revision = $vid == $default_revision || (!$current_revision_displayed && $revision->wasDefaultRevision());
+        if (!$current_revision) {
           $link = $this->l($date, new Url('entity.node.revision', ['node' => $node->id(), 'node_revision' => $vid]));
         }
         else {
           $link = $node->link($date);
+          $current_revision_displayed = TRUE;
         }
 
         $row = [];
@@ -210,7 +217,7 @@ public function revisionOverview(NodeInterface $node) {
         $this->renderer->addCacheableDependency($column['data'], $username);
         $row[] = $column;
 
-        if ($vid == $default_revision) {
+        if ($current_revision) {
           $row[] = [
             'data' => [
               '#prefix' => '<em>',
diff --git a/core/modules/node/src/Form/NodeRevisionRevertForm.php b/core/modules/node/src/Form/NodeRevisionRevertForm.php
index fc6fe023f5..e18ad77610 100644
--- a/core/modules/node/src/Form/NodeRevisionRevertForm.php
+++ b/core/modules/node/src/Form/NodeRevisionRevertForm.php
@@ -28,7 +28,7 @@ class NodeRevisionRevertForm extends ConfirmFormBase {
   /**
    * The node storage.
    *
-   * @var \Drupal\Core\Entity\EntityStorageInterface
+   * @var \Drupal\node\NodeStorageInterface
    */
   protected $nodeStorage;
 
diff --git a/core/modules/node/src/Form/NodeRevisionRevertTranslationForm.php b/core/modules/node/src/Form/NodeRevisionRevertTranslationForm.php
index 0c68d8b254..a6a4d85d8a 100644
--- a/core/modules/node/src/Form/NodeRevisionRevertTranslationForm.php
+++ b/core/modules/node/src/Form/NodeRevisionRevertTranslationForm.php
@@ -88,10 +88,15 @@ public function buildForm(array $form, FormStateInterface $form_state, $node_rev
     $this->langcode = $langcode;
     $form = parent::buildForm($form, $form_state, $node_revision);
 
+    // Unless untranslatable fields are configured to affect only the default
+    // translation, we need to ask the user whether they should be included in
+    // the revert process.
+    $default_translation_affected = $this->revision->isDefaultTranslationAffectedOnly();
     $form['revert_untranslated_fields'] = [
       '#type' => 'checkbox',
       '#title' => $this->t('Revert content shared among translations'),
-      '#default_value' => FALSE,
+      '#default_value' => $default_translation_affected && $this->revision->getTranslation($this->langcode)->isDefaultTranslation(),
+      '#access' => !$default_translation_affected,
     ];
 
     return $form;
@@ -101,24 +106,9 @@ public function buildForm(array $form, FormStateInterface $form_state, $node_rev
    * {@inheritdoc}
    */
   protected function prepareRevertedRevision(NodeInterface $revision, FormStateInterface $form_state) {
-    $revert_untranslated_fields = $form_state->getValue('revert_untranslated_fields');
-
-    /** @var \Drupal\node\NodeInterface $default_revision */
-    $latest_revision = $this->nodeStorage->load($revision->id());
-    $latest_revision_translation = $latest_revision->getTranslation($this->langcode);
-
-    $revision_translation = $revision->getTranslation($this->langcode);
-
-    foreach ($latest_revision_translation->getFieldDefinitions() as $field_name => $definition) {
-      if ($definition->isTranslatable() || $revert_untranslated_fields) {
-        $latest_revision_translation->set($field_name, $revision_translation->get($field_name)->getValue());
-      }
-    }
-
-    $latest_revision_translation->setNewRevision();
-    $latest_revision_translation->isDefaultRevision(TRUE);
-
-    return $latest_revision_translation;
+    $revert_untranslated_fields = (bool) $form_state->getValue('revert_untranslated_fields');
+    $translation = $revision->getTranslation($this->langcode);
+    return $this->nodeStorage->createRevision($translation, TRUE, $revert_untranslated_fields);
   }
 
 }
diff --git a/core/modules/node/src/NodeViewBuilder.php b/core/modules/node/src/NodeViewBuilder.php
index 2bb7f4a2af..aec5d43089 100644
--- a/core/modules/node/src/NodeViewBuilder.php
+++ b/core/modules/node/src/NodeViewBuilder.php
@@ -89,7 +89,9 @@ public static function renderLinks($node_entity_id, $view_mode, $langcode, $is_i
     ];
 
     if (!$is_in_preview) {
-      $entity = Node::load($node_entity_id)->getTranslation($langcode);
+      /** @var \Drupal\node\NodeInterface $entity */
+      $entity = \Drupal::entityManager()
+        ->getTranslationFromContext(Node::load($node_entity_id), $langcode);
       $links['node'] = static::buildLinks($entity, $view_mode);
 
       // Allow other modules to alter the node links.
diff --git a/core/modules/node/src/Tests/NodeRevisionsTest.php b/core/modules/node/src/Tests/NodeRevisionsTest.php
index da5b260537..066193ddab 100644
--- a/core/modules/node/src/Tests/NodeRevisionsTest.php
+++ b/core/modules/node/src/Tests/NodeRevisionsTest.php
@@ -64,6 +64,9 @@ protected function setUp() {
     $field = FieldConfig::create($field_definition);
     $field->save();
 
+    // Enable translation for page nodes.
+    \Drupal::service('content_translation.manager')->setEnabled('node', 'page', TRUE);
+
     // Create and log in user.
     $web_user = $this->drupalCreateUser(
       [
diff --git a/core/modules/node/tests/src/Functional/NodeRevisionsUiTest.php b/core/modules/node/tests/src/Functional/NodeRevisionsUiTest.php
index a437e4aba8..3646ab3e01 100644
--- a/core/modules/node/tests/src/Functional/NodeRevisionsUiTest.php
+++ b/core/modules/node/tests/src/Functional/NodeRevisionsUiTest.php
@@ -160,10 +160,17 @@ public function testNodeRevisionsTabWithDefaultRevision() {
 
     $this->drupalGet('node/' . $node_id . '/revisions');
 
+    // Verify that the latest affected revision having been a default revision
+    // is displayed as the current one.
+    $this->assertNoLinkByHref('/node/' . $node_id . '/revisions/1/revert');
+    $elements = $this->xpath('//tr[contains(@class, "revision-current")]/td/a[1]');
+    // The site may be installed in a subdirectory, so check if the URL is
+    // contained in the retrieved one.
+    $this->assertContains('/node/1', current($elements)->getAttribute('href'));
+
     // Verify that the default revision can be an older revision than the latest
     // one.
-    // Assert that the revisions with translations changes are shown: 1 and 4.
-    $this->assertLinkByHref('/node/' . $node_id . '/revisions/1/revert');
+    // Assert that the revisions with translations changes are shown.
     $this->assertLinkByHref('/node/' . $node_id . '/revisions/4/revert');
 
     // Assert that the revisions without translations changes are filtered out:
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 7d4df70bae..7d3f393fec 100644
--- a/core/modules/system/tests/modules/entity_test/entity_test.module
+++ b/core/modules/system/tests/modules/entity_test/entity_test.module
@@ -210,13 +210,20 @@ function entity_test_entity_bundle_info() {
  * Implements hook_entity_bundle_info_alter().
  */
 function entity_test_entity_bundle_info_alter(&$bundles) {
-  $entity_info = \Drupal::entityTypeManager()->getDefinitions();
   $state = \Drupal::state();
+  $entity_test_bundle_info = $state->get('entity_test.bundle_info');
+  if ($entity_test_bundle_info) {
+    $bundles = array_merge_recursive($bundles, $entity_test_bundle_info);
+  }
+  $entity_info = \Drupal::entityTypeManager()->getDefinitions();
   foreach ($bundles as $entity_type_id => &$all_bundle_info) {
     if ($entity_info[$entity_type_id]->getProvider() == 'entity_test') {
       if ($state->get('entity_test.translation')) {
         foreach ($all_bundle_info as $bundle_name => &$bundle_info) {
           $bundle_info['translatable'] = TRUE;
+          if ($state->get('entity_test.untranslatable_fields.default_translation_affected')) {
+            $bundle_info['untranslatable_fields.default_translation_affected'] = TRUE;
+          }
         }
       }
     }
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
index 724385fd94..b4493e31f8 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRev.php
@@ -58,7 +58,8 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
 
     $fields['non_mul_field'] = BaseFieldDefinition::create('string')
       ->setLabel(t('Non translatable'))
-      ->setDescription(t('A non-translatable string field'));
+      ->setDescription(t('A non-translatable string field'))
+      ->setRevisionable(TRUE);
 
     return $fields;
   }
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChanged.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChanged.php
index 391307576e..13a169df49 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChanged.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestMulRevChanged.php
@@ -71,6 +71,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
     $fields['name']->setRevisionable(TRUE);
     $fields['user_id']->setRevisionable(TRUE);
     $fields['changed']->setRevisionable(TRUE);
+    $fields['not_translatable']->setRevisionable(TRUE);
 
     return $fields;
   }
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityConcurrentRevisionSaveTest.php b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityConcurrentRevisionSaveTest.php
new file mode 100644
index 0000000000..9feb106ce4
--- /dev/null
+++ b/core/tests/Drupal/KernelTests/Core/Entity/ContentEntityConcurrentRevisionSaveTest.php
@@ -0,0 +1,231 @@
+<?php
+
+namespace Drupal\KernelTests\Core\Entity;
+
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\entity_test\Entity\EntityTestMulRevChanged;
+use Drupal\language\Entity\ConfigurableLanguage;
+
+/**
+ * Tests concurrent entity saves and revisions.
+ *
+ * @group Entity
+ *
+ * @coversDefaultClass \Drupal\Core\Entity\Plugin\Validation\Constraint\EntityChangedConstraintValidator
+ */
+class ContentEntityConcurrentRevisionSaveTest extends EntityKernelTestBase {
+
+  const SAVE_NO_REVISION = 0x0;
+  const SAVE_NEW_REVISION = 0x1;
+  const SAVE_PENDING_REVISION = 0x3;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['user', 'system', 'field', 'text', 'filter', 'entity_test', 'language'];
+
+  /**
+   * The EntityTestMulRevChanged entity type storage.
+   *
+   * @var \Drupal\Core\Entity\ContentEntityStorageInterface
+   */
+  protected $storage;
+
+  /**
+   * Counter used to generate meaningful entity labels.
+   *
+   * @var int
+   */
+  protected $saveCounter;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installEntitySchema('entity_test_mulrev_changed');
+    $this->storage = $this->entityManager->getStorage('entity_test_mulrev_changed');
+
+    $this->installConfig(['language']);
+    ConfigurableLanguage::createFromLangcode('it')->save();
+  }
+
+  /**
+   * Data provider for testConcurrentSave().
+   */
+  public function dataTestConcurrentSave() {
+    return [
+      'pending revision - pending revision' => [[static::SAVE_PENDING_REVISION, static::SAVE_PENDING_REVISION]],
+      'no revision - pending revision' => [[static::SAVE_NO_REVISION, static::SAVE_PENDING_REVISION]],
+      'new revision - pending revision' => [[static::SAVE_NEW_REVISION, static::SAVE_PENDING_REVISION]],
+      'pending revision - no revision' => [[static::SAVE_PENDING_REVISION, static::SAVE_NO_REVISION]],
+      'no revision - no revision' => [[static::SAVE_NO_REVISION, static::SAVE_NO_REVISION]],
+      'new revision - no revision' => [[static::SAVE_NEW_REVISION, static::SAVE_NO_REVISION]],
+      'pending revision - new revision' => [[static::SAVE_PENDING_REVISION, static::SAVE_NEW_REVISION]],
+      'no revision - new revision' => [[static::SAVE_NO_REVISION, static::SAVE_NEW_REVISION]],
+      'new revision - new revision' => [[static::SAVE_NEW_REVISION, static::SAVE_NEW_REVISION]],
+    ];
+  }
+
+  /**
+   * Tests concurrent entity saves with various save types.
+   *
+   * @param int[] $type
+   *   An array of save types as defined by the related ::SAVE_* constants. One
+   *   for each of the two concurrent saves.
+   *
+   * @covers ::validate
+   *
+   * @dataProvider dataTestConcurrentSave
+   */
+  public function testConcurrentSave(array $type) {
+    $this->saveCounter = 1;
+
+    // When we do not create a new revision in the first of the two concurrent
+    // saves, only the changed timestamp can be used to detect concurrent edits.
+    // Validate this logic by applying only the related validation.
+    $changed_revision_validation = [TRUE];
+    if ($type[0] === static::SAVE_NO_REVISION) {
+      $changed_revision_validation[] = FALSE;
+    }
+
+    foreach ($changed_revision_validation as $enabled) {
+      // Set the "changed_revision" validation status.
+      $bundle_info = [
+        'entity_test_mulrev_changed' => [
+          'entity_test_mulrev_changed' => [
+            'entity_validation_constraints.EntityChanged.changed_revision' => $enabled,
+          ]
+        ],
+      ];
+      $this->state->set('entity_test.bundle_info', $bundle_info);
+      $this->bundleInfo->clearCachedBundles();
+
+      // Create a test entity.
+      $user = $this->createUser();
+      $entity = EntityTestMulRevChanged::create([
+        'not_translatable' => $this->randomString(),
+        'user_id' => $user->id(),
+        'language' => 'en',
+      ]);
+      $this->doEntitySave($entity, static::SAVE_NO_REVISION, 0);
+
+      // Perform two "concurrent" saves and verify the second one triggers a
+      // validation error.
+      // @todo Remove this line once the "latest_revision_id" field is introduced
+      //   in https://www.drupal.org/project/drupal/issues/2784201.
+      $entity->original_latest_revision_id = $this->storage->getLatestRevisionId($entity->id());
+      $entity_other_instance = clone $entity;
+      $this->doEntitySave($entity, $type[0], 0);
+      $this->doEntitySave($entity_other_instance, $type[1], 1);
+    }
+  }
+
+  /**
+   * Performs an entity save of the specified "type".
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface $entity
+   *   The entity to be saved.
+   * @param int $type
+   *   The save type, as defined by the ::SAVE_* constants.
+   * @param int $expected_violation_count
+   *   The number of validation expected violations.
+   */
+  protected function doEntitySave(ContentEntityInterface $entity, $type, $expected_violation_count) {
+    // Set a meaningful entity label, useful for debugging.
+    $entity->set('name', 'Title ' . ($this->saveCounter++));
+    if ($type & static::SAVE_NEW_REVISION) {
+      $entity->setNewRevision();
+    }
+    if ($type === static::SAVE_PENDING_REVISION) {
+      $entity->isDefaultRevision(FALSE);
+    }
+    $violations = $entity->validate();
+    $this->assertEquals($expected_violation_count, $violations->count(), 'Wrong violations count with constraints');
+    $entity->save();
+  }
+
+  /**
+   * Tests that multilingual edits do not trigger validation errors.
+   *
+   * @covers ::validate
+   */
+  public function testMultilingualRevisionEdit() {
+    // Enable validation of revision changes.
+    $bundle_info = [
+      'entity_test_mulrev_changed' => [
+        'entity_test_mulrev_changed' => [
+          'entity_validation_constraints.EntityChanged.changed_revision' => TRUE,
+        ]
+      ],
+    ];
+    $this->state->set('entity_test.bundle_info', $bundle_info);
+    $this->bundleInfo->clearCachedBundles();
+
+    // Create a new entity.
+    /** @var \Drupal\entity_test\Entity\EntityTestMulRevChanged $entity */
+    $entity = EntityTestMulRevChanged::create([
+      'user_id' => $this->createUser()->id(),
+      'name' => 'Test 1.1 EN',
+    ]);
+    $violations = $entity->validate();
+    $this->assertEmpty($violations->count());
+    $entity->save();
+
+    // Create a new Italian translation in a new pending revision.
+    $entity = $this->loadLatestTranslationAffectingRevision($entity->id(), 'en');
+    /** @var \Drupal\entity_test\Entity\EntityTestMulRevChanged $translation */
+    $translation = $entity->addTranslation('it');
+    $translation->setNewRevision(TRUE);
+    $translation->isDefaultRevision(FALSE);
+    $translation->setName('Test 1.2 IT');
+    $violations = $translation->validate();
+    $this->assertEmpty($violations->count());
+    $translation->save();
+
+    // Start from the latest translation-affecting revision and create an new
+    // default revision for the default language.
+    $entity = $this->loadLatestTranslationAffectingRevision($entity->id(), 'en');
+    $entity->setNewRevision(TRUE);
+    $entity->isDefaultRevision(TRUE);
+    $entity->setName('Test 1.3 EN');
+    $violations = $entity->validate();
+    $this->assertEmpty($violations->count());
+    $entity->save();
+
+    // Start from the latest translation-affecting revision and create an new
+    // pending revision for the Italian translation.
+    $entity = $this->loadLatestTranslationAffectingRevision($entity->id(), 'it');
+    /** @var \Drupal\entity_test\Entity\EntityTestMulRevChanged $translation */
+    $translation = $entity->getTranslation('it');
+    $translation->setNewRevision(TRUE);
+    $translation->isDefaultRevision(FALSE);
+    $translation->setName('Test 1.4 IT');
+    $violations = $translation->validate();
+    $this->assertEmpty($violations->count());
+    $translation->save();
+  }
+
+  /**
+   * Loads the latest translation-affecting revision for the specified language.
+   *
+   * @param int $entity_id
+   *   The entity ID.
+   * @param string $langcode
+   *   The language code.
+   *
+   * @return \Drupal\entity_test\Entity\EntityTestMulRevChanged
+   *   A revision entity object.
+   */
+  protected function loadLatestTranslationAffectingRevision($entity_id, $langcode) {
+    $revision_id = $this->storage->getLatestTranslationAffectedRevisionId($entity_id, $langcode);
+    /** @var \Drupal\entity_test\Entity\EntityTestMulRevChanged $entity */
+    $entity = $this->storage->loadRevision($revision_id);
+    // @todo Remove this line once the "latest_revision_id" field is introduced
+    //   in https://www.drupal.org/project/drupal/issues/2784201.
+    $entity->original_latest_revision_id = $this->storage->getLatestRevisionId($entity_id);
+    return $entity;
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityDecoupledTranslationRevisionsTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityDecoupledTranslationRevisionsTest.php
index 213d190cca..ab01d91529 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntityDecoupledTranslationRevisionsTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityDecoupledTranslationRevisionsTest.php
@@ -3,6 +3,7 @@
 namespace Drupal\KernelTests\Core\Entity;
 
 use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Language\LanguageInterface;
 use Drupal\entity_test\Entity\EntityTestMulRev;
 use Drupal\language\Entity\ConfigurableLanguage;
 use Drupal\user\Entity\User;
@@ -215,6 +216,9 @@ public function dataTestUntranslatableFields() {
 
     $sets['Default behavior - Untranslatable fields affect all revisions'] = [
       [
+        ['en', TRUE, TRUE],
+        ['it', FALSE, TRUE, FALSE],
+        ['en', FALSE, TRUE, FALSE],
         ['en', TRUE, TRUE],
         ['it', TRUE, TRUE],
         ['en', FALSE],
@@ -222,6 +226,23 @@ public function dataTestUntranslatableFields() {
         ['en', TRUE],
         ['it', TRUE],
       ],
+      FALSE,
+    ];
+
+    $sets['Alternative behavior - Untranslatable fields affect only default translation'] = [
+      [
+        ['en', TRUE, TRUE],
+        ['it', FALSE, TRUE, FALSE],
+        ['en', FALSE, TRUE],
+        ['it', FALSE],
+        ['it', TRUE],
+        ['en', TRUE, TRUE],
+        ['it', FALSE],
+        ['en', FALSE],
+        ['it', TRUE],
+        ['en', TRUE, TRUE],
+      ],
+      TRUE,
     ];
 
     return $sets;
@@ -234,11 +255,20 @@ public function dataTestUntranslatableFields() {
    *   An array with arrays of arguments for the ::doSaveNewRevision() method as
    *   values. Every child array corresponds to a method invocation.
    *
+   * @param bool $default_translation_affected
+   *   Whether untranslatable field changes affect all revisions or only the
+   *   default revision.
+   *
    * @covers ::createRevision
+   * @covers \Drupal\Core\Entity\Plugin\Validation\Constraint\EntityUntranslatableFieldsConstraintValidator::validate
    *
    * @dataProvider dataTestUntranslatableFields
    */
-  public function testUntranslatableFields($sequence) {
+  public function testUntranslatableFields($sequence, $default_translation_affected) {
+    // Configure the untranslatable fields edit mode.
+    $this->state->set('entity_test.untranslatable_fields.default_translation_affected', $default_translation_affected);
+    $this->bundleInfo->clearCachedBundles();
+
     // Test that a new entity is always valid.
     $entity = EntityTestMulRev::create();
     $entity->set('non_mul_field', 0);
@@ -289,14 +319,23 @@ protected function doTestEditSequence($sequence) {
   protected function doEditStep($active_langcode, $default_revision, $untranslatable_update = FALSE, $valid = TRUE) {
     $this->stepInfo = [$active_langcode, $default_revision, $untranslatable_update, $valid];
 
+    // If changes to untranslatable fields affect only the default translation,
+    // we can different values for untranslatable fields in the various
+    // revision translations, so we need to track their previous value per
+    // language.
+    $all_translations_affected = !$this->state->get('entity_test.untranslatable_fields.default_translation_affected');
+    $previous_untranslatable_field_langcode = $all_translations_affected ? LanguageInterface::LANGCODE_DEFAULT : $active_langcode;
+
     // Initialize previous data tracking.
     if (!isset($this->translations)) {
       $this->translations[$active_langcode] = EntityTestMulRev::create();
       $this->previousRevisionId[$active_langcode] = 0;
+      $this->previousUntranslatableFieldValue[$previous_untranslatable_field_langcode] = NULL;
     }
     if (!isset($this->translations[$active_langcode])) {
       $this->translations[$active_langcode] = reset($this->translations)->addTranslation($active_langcode);
       $this->previousRevisionId[$active_langcode] = 0;
+      $this->previousUntranslatableFieldValue[$active_langcode] = NULL;
     }
 
     // We want to update previous data only if we expect a valid result,
@@ -304,10 +343,12 @@ protected function doEditStep($active_langcode, $default_revision, $untranslatab
     if ($valid) {
       $entity = &$this->translations[$active_langcode];
       $previous_revision_id = &$this->previousRevisionId[$active_langcode];
+      $previous_untranslatable_field_value = &$this->previousUntranslatableFieldValue[$previous_untranslatable_field_langcode];
     }
     else {
       $entity = clone $this->translations[$active_langcode];
       $previous_revision_id = $this->previousRevisionId[$active_langcode];
+      $previous_untranslatable_field_value = $this->previousUntranslatableFieldValue[$previous_untranslatable_field_langcode];
     }
 
     // Check that after instantiating a new revision for the specified
@@ -332,10 +373,21 @@ protected function doEditStep($active_langcode, $default_revision, $untranslatab
       $this->assertEquals($previous_label, $entity->label(), $this->formatMessage('Loaded translatable field value does not match the previous one.'));
     }
 
+    // Check that the previous untranslatable field value is loaded in the new
+    // revision as expected. When we are dealing with a non default translation
+    // the expected value is always the one stored in the default revision, as
+    // untranslatable fields can only be changed in the default translation or
+    // in the default revision, depending on the configured mode.
     $value = $entity->get('non_mul_field')->value;
     if (isset($previous_untranslatable_field_value)) {
       $this->assertEquals($previous_untranslatable_field_value, $value, $this->formatMessage('Loaded untranslatable field value does not match the previous one.'));
     }
+    elseif (!$entity->isDefaultTranslation()) {
+      /** @var \Drupal\Core\Entity\ContentEntityInterface $default_revision */
+      $default_revision = $this->storage->loadUnchanged($entity->id());
+      $expected_value = $default_revision->get('non_mul_field')->value;
+      $this->assertEquals($expected_value, $value, $this->formatMessage('Loaded untranslatable field value does not match the previous one.'));
+    }
 
     // Perform a change and store it.
     $label = $this->generateNewEntityLabel($entity, $previous_revision_id, TRUE);
@@ -345,12 +397,13 @@ protected function doEditStep($active_langcode, $default_revision, $untranslatab
       // the new value, besides the upcoming revision ID. Useful to analyze test
       // failures.
       $prev = 0;
-      if (isset($value)) {
-        preg_match('/^\d+ -> (\d+)$/', $value, $matches);
+      if (isset($previous_untranslatable_field_value)) {
+        preg_match('/^\d+ -> (\d+)$/', $previous_untranslatable_field_value, $matches);
         $prev = $matches[1];
       }
       $value = $prev . ' -> ' . ($entity->getLoadedRevisionId() + 1);
       $entity->set('non_mul_field', $value);
+      $previous_untranslatable_field_value = $value;
     }
 
     $violations = $entity->validate();
@@ -378,7 +431,7 @@ protected function doEditStep($active_langcode, $default_revision, $untranslatab
       // translation was marked as affected.
       foreach ($entity->getTranslationLanguages() as $langcode => $language) {
         $translation = $entity->getTranslation($langcode);
-        $rta_expected = $langcode == $active_langcode || $untranslatable_update;
+        $rta_expected = $langcode == $active_langcode || ($untranslatable_update && $all_translations_affected);
         $this->assertEquals($rta_expected, $translation->isRevisionTranslationAffected(), $this->formatMessage("'$langcode' translation incorrectly affected"));
         $label_expected = $label;
         if ($langcode !== $active_langcode) {
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityKernelTestBase.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityKernelTestBase.php
index f7108d53b1..39ed42a4e4 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntityKernelTestBase.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityKernelTestBase.php
@@ -26,6 +26,13 @@
    */
   protected $entityManager;
 
+  /**
+   * The entity bundle info service.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface
+   */
+  protected $bundleInfo;
+
   /**
    * A list of generated identifiers.
    *
@@ -45,6 +52,7 @@ protected function setUp() {
 
     $this->entityManager = $this->container->get('entity.manager');
     $this->state = $this->container->get('state');
+    $this->bundleInfo = $this->container->get('entity_type.bundle.info');
 
     $this->installSchema('system', 'sequences');
 
diff --git a/core/tests/Drupal/KernelTests/Core/Entity/EntityTypeConstraintsTest.php b/core/tests/Drupal/KernelTests/Core/Entity/EntityTypeConstraintsTest.php
index a58b47c1ce..cdf4b96fb5 100644
--- a/core/tests/Drupal/KernelTests/Core/Entity/EntityTypeConstraintsTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Entity/EntityTypeConstraintsTest.php
@@ -24,7 +24,11 @@ public function testConstraintDefinition() {
     // Test reading the annotation. There should be two constraints, the defined
     // constraint and the automatically added EntityChanged constraint.
     $entity_type = $this->entityManager->getDefinition('entity_test_constraints');
-    $default_constraints = ['NotNull' => [], 'EntityChanged' => NULL];
+    $default_constraints = [
+      'NotNull' => [],
+      'EntityChanged' => NULL,
+      'EntityUntranslatableFields' => NULL,
+    ];
     $this->assertEqual($default_constraints, $entity_type->getConstraints());
 
     // Enable our test module and test extending constraints.
diff --git a/core/tests/Drupal/KernelTests/Core/ParamConverter/EntityConverterLatestRevisionTest.php b/core/tests/Drupal/KernelTests/Core/ParamConverter/EntityConverterLatestRevisionTest.php
index d2fd2f5241..f142d0c71c 100644
--- a/core/tests/Drupal/KernelTests/Core/ParamConverter/EntityConverterLatestRevisionTest.php
+++ b/core/tests/Drupal/KernelTests/Core/ParamConverter/EntityConverterLatestRevisionTest.php
@@ -80,8 +80,12 @@ public function testEntityWithPendingRevision() {
     $entity = EntityTestMulRev::create();
     $entity->save();
 
-    $entity->isDefaultRevision(FALSE);
-    $entity->setNewRevision(TRUE);
+    /** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
+    $storage = $this->container->get('entity_type.manager')
+      ->getStorage($entity->getEntityTypeId());
+
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+    $entity = $storage->createRevision($entity,FALSE);
     $entity->save();
 
     $converted = $this->converter->convert(1, [
