diff --git a/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php b/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php
index 1a1b6e46e9..0bacfe5b86 100644
--- a/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php
+++ b/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php
@@ -36,6 +36,7 @@ class ContentModerationStateTest extends KernelTestBase {
     'media_test_source',
     'image',
     'file',
+    'taxonomy',
     'field',
     'content_moderation',
     'user',
@@ -66,6 +67,7 @@ protected function setUp() {
     $this->installEntitySchema('block_content');
     $this->installEntitySchema('media');
     $this->installEntitySchema('file');
+    $this->installEntitySchema('taxonomy_term');
     $this->installEntitySchema('content_moderation_state');
     $this->installConfig('content_moderation');
     $this->installSchema('file', 'file_usage');
@@ -164,6 +166,9 @@ public function basicModerationTestCases() {
       'Media' => [
         'media',
       ],
+      'Taxonomy term' => [
+        'taxonomy_term',
+      ],
       'Test entity - revisions, data table, and published interface' => [
         'entity_test_mulrevpub',
       ],
@@ -252,7 +257,7 @@ public function testContentModerationStateTranslationDataRemoval($entity_type_id
       ConfigurableLanguage::createFromLangcode($langcode)
         ->save();
       $entity->save();
-      $translation = $entity->addTranslation($langcode, ['title' => 'Titolo test']);
+      $translation = $entity->addTranslation($langcode, [$entity->getEntityType()->getKey('label') => 'Titolo test']);
       // Make sure we add values for all of the required fields.
       if ($entity_type_id == 'block_content') {
         $translation->info = $this->randomString();
diff --git a/core/modules/taxonomy/src/Entity/Term.php b/core/modules/taxonomy/src/Entity/Term.php
index 8fc3cedb04..949518a56f 100644
--- a/core/modules/taxonomy/src/Entity/Term.php
+++ b/core/modules/taxonomy/src/Entity/Term.php
@@ -2,9 +2,7 @@
 
 namespace Drupal\taxonomy\Entity;
 
-use Drupal\Core\Entity\ContentEntityBase;
-use Drupal\Core\Entity\EntityChangedTrait;
-use Drupal\Core\Entity\EntityPublishedTrait;
+use Drupal\Core\Entity\EditorialContentEntityBase;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
@@ -40,16 +38,24 @@
  *   },
  *   base_table = "taxonomy_term_data",
  *   data_table = "taxonomy_term_field_data",
+ *   revision_table = "taxonomy_term_revision",
+ *   revision_data_table = "taxonomy_term_field_revision",
  *   uri_callback = "taxonomy_term_uri",
  *   translatable = TRUE,
  *   entity_keys = {
  *     "id" = "tid",
+ *     "revision" = "revision_id",
  *     "bundle" = "vid",
  *     "label" = "name",
  *     "langcode" = "langcode",
  *     "uuid" = "uuid",
  *     "published" = "status",
  *   },
+ *   revision_metadata_keys = {
+ *     "revision_user" = "revision_user",
+ *     "revision_created" = "revision_created",
+ *     "revision_log_message" = "revision_log_message",
+ *   },
  *   bundle_entity_type = "taxonomy_vocabulary",
  *   field_ui_base_route = "entity.taxonomy_vocabulary.overview_form",
  *   common_reference_target = TRUE,
@@ -59,13 +65,13 @@
  *     "edit-form" = "/taxonomy/term/{taxonomy_term}/edit",
  *     "create" = "/taxonomy/term",
  *   },
- *   permission_granularity = "bundle"
+ *   permission_granularity = "bundle",
+ *   constraints = {
+ *     "TaxonomyHierarchy" = {}
+ *   }
  * )
  */
-class Term extends ContentEntityBase implements TermInterface {
-
-  use EntityChangedTrait;
-  use EntityPublishedTrait;
+class Term extends EditorialContentEntityBase implements TermInterface {
 
   /**
    * {@inheritdoc}
@@ -120,8 +126,6 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
     /** @var \Drupal\Core\Field\BaseFieldDefinition[] $fields */
     $fields = parent::baseFieldDefinitions($entity_type);
 
-    // Add the published field.
-    $fields += static::publishedBaseFieldDefinitions($entity_type);
     // @todo Remove the usage of StatusItem in
     //   https://www.drupal.org/project/drupal/issues/2936864.
     $fields['status']->getItemDefinition()->setClass(StatusItem::class);
@@ -139,6 +143,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
     $fields['name'] = BaseFieldDefinition::create('string')
       ->setLabel(t('Name'))
       ->setTranslatable(TRUE)
+      ->setRevisionable(TRUE)
       ->setRequired(TRUE)
       ->setSetting('max_length', 255)
       ->setDisplayOptions('view', [
@@ -155,6 +160,7 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
     $fields['description'] = BaseFieldDefinition::create('text_long')
       ->setLabel(t('Description'))
       ->setTranslatable(TRUE)
+      ->setRevisionable(TRUE)
       ->setDisplayOptions('view', [
         'label' => 'hidden',
         'type' => 'text_default',
@@ -181,7 +187,14 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
     $fields['changed'] = BaseFieldDefinition::create('changed')
       ->setLabel(t('Changed'))
       ->setDescription(t('The time that the term was last edited.'))
-      ->setTranslatable(TRUE);
+      ->setTranslatable(TRUE)
+      ->setRevisionable(TRUE);
+
+    // @todo Keep this field hidden until we have a revision UI for terms.
+    // @see https://www.drupal.org/project/drupal/issues/2936995
+    $fields['revision_log_message']->setDisplayOptions('form', [
+      'region' => 'hidden',
+    ]);
 
     return $fields;
   }
diff --git a/core/modules/taxonomy/src/Form/OverviewTerms.php b/core/modules/taxonomy/src/Form/OverviewTerms.php
index 504816668d..73be385d72 100644
--- a/core/modules/taxonomy/src/Form/OverviewTerms.php
+++ b/core/modules/taxonomy/src/Form/OverviewTerms.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\taxonomy\Form;
 
+use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Access\AccessResult;
 use Drupal\Core\DependencyInjection\DeprecatedServicePropertyTrait;
 use Drupal\Core\Entity\EntityRepositoryInterface;
@@ -251,6 +252,56 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular
       }
     }
 
+    $args = [
+      '%capital_name' => Unicode::ucfirst($taxonomy_vocabulary->label()),
+      '%name' => $taxonomy_vocabulary->label(),
+    ];
+    if ($this->currentUser()->hasPermission('administer taxonomy') || $this->currentUser()->hasPermission('edit terms in ' . $taxonomy_vocabulary->id())) {
+      switch ($vocabulary_hierarchy) {
+        case VocabularyInterface::HIERARCHY_DISABLED:
+          $help_message = $this->t('You can reorganize the terms in %capital_name using their drag-and-drop handles, and group terms under a parent term by sliding them under and to the right of the parent.', $args);
+          break;
+        case VocabularyInterface::HIERARCHY_SINGLE:
+          $help_message = $this->t('%capital_name contains terms grouped under parent terms. You can reorganize the terms in %capital_name using their drag-and-drop handles.', $args);
+          break;
+        case VocabularyInterface::HIERARCHY_MULTIPLE:
+          $help_message = $this->t('%capital_name contains terms with multiple parents. Drag and drop of terms with multiple parents is not supported, but you can re-enable drag-and-drop support by editing each term to include only a single parent.', $args);
+          break;
+      }
+    }
+    else {
+      switch ($vocabulary_hierarchy) {
+        case VocabularyInterface::HIERARCHY_DISABLED:
+          $help_message = $this->t('%capital_name contains the following terms.', $args);
+          break;
+        case VocabularyInterface::HIERARCHY_SINGLE:
+          $help_message = $this->t('%capital_name contains terms grouped under parent terms', $args);
+          break;
+        case VocabularyInterface::HIERARCHY_MULTIPLE:
+          $help_message = $this->t('%capital_name contains terms with multiple parents.', $args);
+          break;
+      }
+    }
+
+    // Get IDs of terms which have pending revisions.
+    $pending_term_ids = $this->storageController->getTermIdsWithPendingRevisions();
+    if ($pending_term_ids) {
+      $help_message = $this->formatPlural(
+        count($pending_term_ids),
+        '%capital_name contains 1 term with pending revisions. Drag and drop of terms with pending revisions is not supported, but you can re-enable drag-and-drop support by getting each term to a published state.',
+        '%capital_name contains @count terms with pending revisions. Drag and drop of terms with pending revisions is not supported, but you can re-enable drag-and-drop support by getting each term to a published state.',
+        $args
+      );
+    }
+
+    $form['help'] = [
+      '#type' => 'container',
+      'message' => ['#markup' => $help_message],
+    ];
+    if ($pending_term_ids || $vocabulary_hierarchy === VocabularyInterface::HIERARCHY_MULTIPLE) {
+      $form['help']['#attributes']['class'] = ['messages', 'messages--warning'];
+    }
+
     $errors = $form_state->getErrors();
     $row_position = 0;
     // Build the actual form.
@@ -277,8 +328,8 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular
     $this->renderer->addCacheableDependency($form['terms'], $create_access);
 
     // Only allow access to changing weights if the user has update access for
-    // all terms.
-    $change_weight_access = AccessResult::allowed();
+    // all terms, and if there no pending revisions.
+    $change_weight_access = AccessResult::allowedIf(empty($pending_term_ids));
     foreach ($current_page as $key => $term) {
       $form['terms'][$key] = [
         'term' => [],
@@ -301,7 +352,16 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular
         '#title' => $term->getName(),
         '#url' => $term->toUrl(),
       ];
-      if ($vocabulary_hierarchy != VocabularyInterface::HIERARCHY_MULTIPLE && count($tree) > 1) {
+
+      // Add a special class for terms with pending revision so we can highlight
+      // them in the form.
+      $form['terms'][$key]['#attributes']['class'] = [];
+      if (in_array($term->id(), $pending_term_ids)) {
+        $form['terms'][$key]['#attributes']['class'][] = 'color-warning';
+        $form['terms'][$key]['#attributes']['class'][] = 'taxonomy-term-pending-revision';
+      }
+
+      if ($change_weight_access->isAllowed() && $vocabulary_hierarchy != VocabularyInterface::HIERARCHY_MULTIPLE && count($tree) > 1) {
         $parent_fields = TRUE;
         $form['terms'][$key]['term']['tid'] = [
           '#type' => 'hidden',
@@ -332,7 +392,7 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular
       $update_access = $term->access('update', NULL, TRUE);
       $change_weight_access = $change_weight_access->andIf($update_access);
 
-      if ($update_access->isAllowed()) {
+      if ($change_weight_access->isAllowed()) {
         $form['terms'][$key]['weight'] = [
           '#type' => 'weight',
           '#delta' => $delta,
@@ -350,7 +410,6 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular
         ];
       }
 
-      $form['terms'][$key]['#attributes']['class'] = [];
       if ($parent_fields) {
         $form['terms'][$key]['#attributes']['class'][] = 'draggable';
       }
@@ -505,12 +564,23 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
       }
     }
 
-    // Save all updated terms.
-    foreach ($changed_terms as $term) {
-      $term->save();
-    }
+    if (!empty($changed_terms)) {
+      $pending_term_ids = $this->storageController->getTermIdsWithPendingRevisions();
 
-    $this->messenger()->addStatus($this->t('The configuration options have been saved.'));
+      // Force a form rebuild if any of the changed terms has a pending
+      // revision.
+      if (array_intersect_key(array_flip($pending_term_ids), $changed_terms)) {
+        $form_state->setRebuild();
+      }
+      else {
+        // Save all updated terms.
+        foreach ($changed_terms as $term) {
+          $term->save();
+        }
+
+        $this->messenger()->addStatus($this->t('The configuration options have been saved.'));
+      }
+    }
   }
 
   /**
diff --git a/core/modules/taxonomy/src/Plugin/Validation/Constraint/TaxonomyTermHierarchyConstraint.php b/core/modules/taxonomy/src/Plugin/Validation/Constraint/TaxonomyTermHierarchyConstraint.php
new file mode 100644
index 0000000000..93abb07ac8
--- /dev/null
+++ b/core/modules/taxonomy/src/Plugin/Validation/Constraint/TaxonomyTermHierarchyConstraint.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace Drupal\taxonomy\Plugin\Validation\Constraint;
+
+use Drupal\Core\Entity\Plugin\Validation\Constraint\CompositeConstraintBase;
+
+/**
+ * Validation constraint for changing the term hierarchy in pending revisions.
+ *
+ * @Constraint(
+ *   id = "TaxonomyHierarchy",
+ *   label = @Translation("Taxonomy term hierarchy.", context = "Validation"),
+ * )
+ */
+class TaxonomyTermHierarchyConstraint extends CompositeConstraintBase {
+
+  /**
+   * The default violation message.
+   *
+   * @var string
+   */
+  public $message = 'You can only change the hierarchy for the <em>published</em> version of this term.';
+
+  /**
+   * {@inheritdoc}
+   */
+  public function coversFields() {
+    return ['parent', 'weight'];
+  }
+
+}
diff --git a/core/modules/taxonomy/src/Plugin/Validation/Constraint/TaxonomyTermHierarchyConstraintValidator.php b/core/modules/taxonomy/src/Plugin/Validation/Constraint/TaxonomyTermHierarchyConstraintValidator.php
new file mode 100644
index 0000000000..e98551cd13
--- /dev/null
+++ b/core/modules/taxonomy/src/Plugin/Validation/Constraint/TaxonomyTermHierarchyConstraintValidator.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Drupal\taxonomy\Plugin\Validation\Constraint;
+
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\taxonomy\TermStorageInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\Validator\Constraint;
+use Symfony\Component\Validator\ConstraintValidator;
+
+/**
+ * Constraint validator for changing term parents in pending revisions.
+ */
+class TaxonomyTermHierarchyConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  private $entityTypeManager;
+
+  /**
+   * Creates a new TaxonomyTermHierarchyConstraintValidator instance.
+   *
+   * @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) {
+    if ($entity && !$entity->isNew() && !$entity->isDefaultRevision()) {
+      $term_storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
+      assert($term_storage instanceof TermStorageInterface);
+
+      $new_parents = array_column($entity->parent->getValue(), 'target_id');
+      $original_parents = array_keys($term_storage->loadParents($entity->id())) ?: [0];
+      if ($new_parents != $original_parents) {
+        $this->context->buildViolation($constraint->message)
+          ->atPath('parent')
+          ->addViolation();
+      }
+
+      $original = $term_storage->loadUnchanged($entity->id());
+      if (!$entity->weight->equals($original->weight)) {
+        $this->context->buildViolation($constraint->message)
+          ->atPath('weight')
+          ->addViolation();
+      }
+    }
+  }
+
+}
diff --git a/core/modules/taxonomy/src/TermForm.php b/core/modules/taxonomy/src/TermForm.php
index 438b219a7f..3a724935bf 100644
--- a/core/modules/taxonomy/src/TermForm.php
+++ b/core/modules/taxonomy/src/TermForm.php
@@ -3,6 +3,7 @@
 namespace Drupal\taxonomy;
 
 use Drupal\Core\Entity\ContentEntityForm;
+use Drupal\Core\Entity\EntityConstraintViolationListInterface;
 use Drupal\Core\Form\FormStateInterface;
 
 /**
@@ -121,6 +122,31 @@ public function buildEntity(array $form, FormStateInterface $form_state) {
     return $term;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditedFieldNames(FormStateInterface $form_state) {
+    return array_merge(['parent', 'weight'], parent::getEditedFieldNames($form_state));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function flagViolations(EntityConstraintViolationListInterface $violations, array $form, FormStateInterface $form_state) {
+    // Manually flag violations of fields not handled by the form display. This
+    // is necessary as entity form displays only flag violations for fields
+    // contained in the display.
+    // @see ::form()
+    foreach ($violations->getByField('parent') as $violation) {
+      $form_state->setErrorByName('parent', $violation->getMessage());
+    }
+    foreach ($violations->getByField('weight') as $violation) {
+      $form_state->setErrorByName('weight', $violation->getMessage());
+    }
+
+    parent::flagViolations($violations, $form, $form_state);
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/taxonomy/src/TermInterface.php b/core/modules/taxonomy/src/TermInterface.php
index 877c27d205..687691d0b1 100644
--- a/core/modules/taxonomy/src/TermInterface.php
+++ b/core/modules/taxonomy/src/TermInterface.php
@@ -5,11 +5,12 @@
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\EntityChangedInterface;
 use Drupal\Core\Entity\EntityPublishedInterface;
+use Drupal\Core\Entity\RevisionLogInterface;
 
 /**
  * Provides an interface defining a taxonomy term entity.
  */
-interface TermInterface extends ContentEntityInterface, EntityChangedInterface, EntityPublishedInterface {
+interface TermInterface extends ContentEntityInterface, EntityChangedInterface, EntityPublishedInterface, RevisionLogInterface {
 
   /**
    * Gets the term description.
diff --git a/core/modules/taxonomy/src/TermStorage.php b/core/modules/taxonomy/src/TermStorage.php
index ef276d160d..8b5be2d1af 100644
--- a/core/modules/taxonomy/src/TermStorage.php
+++ b/core/modules/taxonomy/src/TermStorage.php
@@ -372,6 +372,38 @@ public function getNodeTerms(array $nids, array $vocabs = [], $langcode = NULL)
     return $terms;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getTermIdsWithPendingRevisions() {
+    $table_mapping = $this->getTableMapping();
+    $id_field = $table_mapping->getColumnNames($this->entityType->getKey('id'))['value'];
+    $revision_field = $table_mapping->getColumnNames($this->entityType->getKey('revision'))['value'];
+    $rta_field = $table_mapping->getColumnNames($this->entityType->getKey('revision_translation_affected'))['value'];
+    $langcode_field = $table_mapping->getColumnNames($this->entityType->getKey('langcode'))['value'];
+    $revision_default_field = $table_mapping->getColumnNames($this->entityType->getRevisionMetadataKey('revision_default'))['value'];
+
+    $query = $this->database->select($this->getRevisionDataTable(), 'tfr');
+    $query->fields('tfr', [$id_field]);
+    $query->addExpression("MAX(tfr.$revision_field)", $revision_field);
+
+    $query->join($this->getRevisionTable(), 'tr', "tfr.$revision_field = tr.$revision_field AND tr.$revision_default_field = 0");
+
+    $inner_select = $this->database->select($this->getRevisionDataTable(), 't');
+    $inner_select->condition("t.$rta_field", '1');
+    $inner_select->fields('t', [$id_field, $langcode_field]);
+    $inner_select->addExpression("MAX(t.$revision_field)", $revision_field);
+    $inner_select
+      ->groupBy("t.$id_field")
+      ->groupBy("t.$langcode_field");
+
+    $query->join($inner_select, 'mr', "tfr.$revision_field = mr.$revision_field AND tfr.$langcode_field = mr.$langcode_field");
+
+    $query->groupBy("tfr.$id_field");
+
+    return $query->execute()->fetchAllKeyed(1, 0);
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/core/modules/taxonomy/src/TermStorageInterface.php b/core/modules/taxonomy/src/TermStorageInterface.php
index fa0c36971a..4271805ac5 100644
--- a/core/modules/taxonomy/src/TermStorageInterface.php
+++ b/core/modules/taxonomy/src/TermStorageInterface.php
@@ -141,4 +141,15 @@ public function getNodeTerms(array $nids, array $vocabs = [], $langcode = NULL);
    */
   public function getVocabularyHierarchyType($vid);
 
+  /**
+   * Gets a list of term IDs with pending revisions.
+   *
+   * @return int[]
+   *   An array of term IDs which have pending revisions, keyed by their
+   *   revision IDs.
+   *
+   * @internal
+   */
+  public function getTermIdsWithPendingRevisions();
+
 }
diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
index 652ba32d07..2164848c7c 100644
--- a/core/modules/taxonomy/taxonomy.module
+++ b/core/modules/taxonomy/taxonomy.module
@@ -6,7 +6,6 @@
  */
 
 use Drupal\Component\Utility\Tags;
-use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
 use Drupal\Core\Render\Element;
@@ -78,30 +77,6 @@ function taxonomy_help($route_name, RouteMatchInterface $route_match) {
     case 'entity.taxonomy_vocabulary.collection':
       $output = '<p>' . t('Taxonomy is for categorizing content. Terms are grouped into vocabularies. For example, a vocabulary called "Fruit" would contain the terms "Apple" and "Banana".') . '</p>';
       return $output;
-
-    case 'entity.taxonomy_vocabulary.overview_form':
-      $vocabulary = $route_match->getParameter('taxonomy_vocabulary');
-      $vocabulary_hierarchy = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->getVocabularyHierarchyType($vocabulary->id());
-      if (\Drupal::currentUser()->hasPermission('administer taxonomy') || \Drupal::currentUser()->hasPermission('edit terms in ' . $vocabulary->id())) {
-        switch ($vocabulary_hierarchy) {
-          case VocabularyInterface::HIERARCHY_DISABLED:
-            return '<p>' . t('You can reorganize the terms in %capital_name using their drag-and-drop handles, and group terms under a parent term by sliding them under and to the right of the parent.', ['%capital_name' => Unicode::ucfirst($vocabulary->label()), '%name' => $vocabulary->label()]) . '</p>';
-          case VocabularyInterface::HIERARCHY_SINGLE:
-            return '<p>' . t('%capital_name contains terms grouped under parent terms. You can reorganize the terms in %capital_name using their drag-and-drop handles.', ['%capital_name' => Unicode::ucfirst($vocabulary->label()), '%name' => $vocabulary->label()]) . '</p>';
-          case VocabularyInterface::HIERARCHY_MULTIPLE:
-            return '<p>' . t('%capital_name contains terms with multiple parents. Drag and drop of terms with multiple parents is not supported, but you can re-enable drag-and-drop support by editing each term to include only a single parent.', ['%capital_name' => Unicode::ucfirst($vocabulary->label())]) . '</p>';
-        }
-      }
-      else {
-        switch ($vocabulary_hierarchy) {
-          case VocabularyInterface::HIERARCHY_DISABLED:
-            return '<p>' . t('%capital_name contains the following terms.', ['%capital_name' => Unicode::ucfirst($vocabulary->label())]) . '</p>';
-          case VocabularyInterface::HIERARCHY_SINGLE:
-            return '<p>' . t('%capital_name contains terms grouped under parent terms', ['%capital_name' => Unicode::ucfirst($vocabulary->label())]) . '</p>';
-          case VocabularyInterface::HIERARCHY_MULTIPLE:
-            return '<p>' . t('%capital_name contains terms with multiple parents.', ['%capital_name' => Unicode::ucfirst($vocabulary->label())]) . '</p>';
-        }
-      }
   }
 }
 
diff --git a/core/modules/taxonomy/taxonomy.post_update.php b/core/modules/taxonomy/taxonomy.post_update.php
index 6d84dfd69b..69fbb3d864 100644
--- a/core/modules/taxonomy/taxonomy.post_update.php
+++ b/core/modules/taxonomy/taxonomy.post_update.php
@@ -6,6 +6,8 @@
  */
 
 use Drupal\Core\Config\Entity\ConfigEntityUpdater;
+use Drupal\Core\Field\BaseFieldDefinition;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\views\ViewExecutable;
 
 /**
@@ -134,3 +136,94 @@ function taxonomy_post_update_remove_hierarchy_from_vocabularies(&$sandbox = NUL
     return TRUE;
   });
 }
+
+/**
+ * Update taxonomy terms to be revisionable.
+ */
+function taxonomy_post_update_make_taxonomy_term_revisionable(&$sandbox) {
+  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
+  /** @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $last_installed_schema_repository */
+  $last_installed_schema_repository = \Drupal::service('entity.last_installed_schema.repository');
+
+  $entity_type = $definition_update_manager->getEntityType('taxonomy_term');
+  $field_storage_definitions = $last_installed_schema_repository->getLastInstalledFieldStorageDefinitions('taxonomy_term');
+
+  // Update the entity type definition.
+  $entity_keys = $entity_type->getKeys();
+  $entity_keys['revision'] = 'revision_id';
+  $entity_keys['revision_translation_affected'] = 'revision_translation_affected';
+  $entity_type->set('entity_keys', $entity_keys);
+  $entity_type->set('revision_table', 'taxonomy_term_revision');
+  $entity_type->set('revision_data_table', 'taxonomy_term_field_revision');
+  $revision_metadata_keys = [
+    'revision_default' => 'revision_default',
+    'revision_user' => 'revision_user',
+    'revision_created' => 'revision_created',
+    'revision_log_message' => 'revision_log_message',
+  ];
+  $entity_type->set('revision_metadata_keys', $revision_metadata_keys);
+
+  // Update the field storage definitions and add the new ones required by a
+  // revisionable entity type.
+  $field_storage_definitions['langcode']->setRevisionable(TRUE);
+  $field_storage_definitions['name']->setRevisionable(TRUE);
+  $field_storage_definitions['description']->setRevisionable(TRUE);
+  $field_storage_definitions['changed']->setRevisionable(TRUE);
+
+  $field_storage_definitions['revision_id'] = BaseFieldDefinition::create('integer')
+    ->setName('revision_id')
+    ->setTargetEntityTypeId('taxonomy_term')
+    ->setTargetBundle(NULL)
+    ->setLabel(new TranslatableMarkup('Revision ID'))
+    ->setReadOnly(TRUE)
+    ->setSetting('unsigned', TRUE);
+
+  $field_storage_definitions['revision_default'] = BaseFieldDefinition::create('boolean')
+    ->setName('revision_default')
+    ->setTargetEntityTypeId('taxonomy_term')
+    ->setTargetBundle(NULL)
+    ->setLabel(new TranslatableMarkup('Default revision'))
+    ->setDescription(new TranslatableMarkup('A flag indicating whether this was a default revision when it was saved.'))
+    ->setStorageRequired(TRUE)
+    ->setInternal(TRUE)
+    ->setTranslatable(FALSE)
+    ->setRevisionable(TRUE);
+
+  $field_storage_definitions['revision_translation_affected'] = BaseFieldDefinition::create('boolean')
+    ->setName('revision_translation_affected')
+    ->setTargetEntityTypeId('taxonomy_term')
+    ->setTargetBundle(NULL)
+    ->setLabel(new TranslatableMarkup('Revision translation affected'))
+    ->setDescription(new TranslatableMarkup('Indicates if the last edit of a translation belongs to current revision.'))
+    ->setReadOnly(TRUE)
+    ->setRevisionable(TRUE)
+    ->setTranslatable(TRUE);
+
+  $field_storage_definitions['revision_created'] = BaseFieldDefinition::create('created')
+    ->setName('revision_created')
+    ->setTargetEntityTypeId('taxonomy_term')
+    ->setTargetBundle(NULL)
+    ->setLabel(new TranslatableMarkup('Revision create time'))
+    ->setDescription(new TranslatableMarkup('The time that the current revision was created.'))
+    ->setRevisionable(TRUE);
+  $field_storage_definitions['revision_user'] = BaseFieldDefinition::create('entity_reference')
+    ->setName('revision_user')
+    ->setTargetEntityTypeId('taxonomy_term')
+    ->setTargetBundle(NULL)
+    ->setLabel(new TranslatableMarkup('Revision user'))
+    ->setDescription(new TranslatableMarkup('The user ID of the author of the current revision.'))
+    ->setSetting('target_type', 'user')
+    ->setRevisionable(TRUE);
+  $field_storage_definitions['revision_log_message'] = BaseFieldDefinition::create('string_long')
+    ->setName('revision_log_message')
+    ->setTargetEntityTypeId('taxonomy_term')
+    ->setTargetBundle(NULL)
+    ->setLabel(new TranslatableMarkup('Revision log message'))
+    ->setDescription(new TranslatableMarkup('Briefly describe the changes you have made.'))
+    ->setRevisionable(TRUE)
+    ->setDefaultValue('');
+
+  $definition_update_manager->updateFieldableEntityType($entity_type, $field_storage_definitions, $sandbox);
+
+  return t('Taxonomy terms have been converted to be revisionable.');
+}
diff --git a/core/modules/taxonomy/tests/src/Functional/Rest/TermResourceTestBase.php b/core/modules/taxonomy/tests/src/Functional/Rest/TermResourceTestBase.php
index f303072a5f..91326511fe 100644
--- a/core/modules/taxonomy/tests/src/Functional/Rest/TermResourceTestBase.php
+++ b/core/modules/taxonomy/tests/src/Functional/Rest/TermResourceTestBase.php
@@ -156,6 +156,9 @@ protected function getExpectedNormalizedEntity() {
       'tid' => [
         ['value' => 1],
       ],
+      'revision_id' => [
+        ['value' => 1],
+      ],
       'uuid' => [
         ['value' => $this->entity->uuid()],
       ],
@@ -205,6 +208,16 @@ protected function getExpectedNormalizedEntity() {
           'value' => TRUE,
         ],
       ],
+      'revision_created' => [
+        $this->formatExpectedTimestampItemValues((int) $this->entity->getRevisionCreationTime()),
+      ],
+      'revision_user' => [],
+      'revision_log_message' => [],
+      'revision_translation_affected' => [
+        [
+          'value' => TRUE,
+        ],
+      ],
     ];
   }
 
diff --git a/core/modules/taxonomy/tests/src/Functional/TaxonomyTermContentModerationTest.php b/core/modules/taxonomy/tests/src/Functional/TaxonomyTermContentModerationTest.php
new file mode 100644
index 0000000000..97660fccce
--- /dev/null
+++ b/core/modules/taxonomy/tests/src/Functional/TaxonomyTermContentModerationTest.php
@@ -0,0 +1,201 @@
+<?php
+
+namespace Drupal\Tests\taxonomy\Functional;
+
+use Drupal\Tests\content_moderation\Traits\ContentModerationTestTrait;
+use Drupal\workflows\Entity\Workflow;
+
+/**
+ * Tests taxonomy terms with Content Moderation.
+ *
+ * @group content_moderation
+ * @group taxonomy
+ */
+class TaxonomyTermContentModerationTest extends TaxonomyTestBase {
+
+  use ContentModerationTestTrait;
+
+  /**
+   * The vocabulary used for creating terms.
+   *
+   * @var \Drupal\taxonomy\VocabularyInterface
+   */
+  protected $vocabulary;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['taxonomy', 'content_moderation'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->createEditorialWorkflow();
+
+    $this->drupalLogin($this->drupalCreateUser([
+      'administer taxonomy',
+      'use editorial transition create_new_draft',
+      'use editorial transition publish',
+      'view any unpublished content',
+      'view latest version',
+    ]));
+
+    $this->vocabulary = $this->createVocabulary();
+
+    // Set the vocabulary as moderated.
+    $workflow = Workflow::load('editorial');
+    $workflow->getTypePlugin()->addEntityTypeAndBundle('taxonomy_term', $this->vocabulary->id());
+    $workflow->save();
+  }
+
+  /**
+   * Tests taxonomy term parents on a moderated vocabulary.
+   */
+  public function testTaxonomyTermParents() {
+    // Create a simple hierarchy in the vocabulary, a root term and three parent
+    // terms.
+    $root = $this->createTerm($this->vocabulary, ['langcode' => 'en', 'moderation_state' => 'published']);
+    $parent_1 = $this->createTerm($this->vocabulary, ['langcode' => 'en', 'moderation_state' => 'published', 'parent' => $root->id()]);
+    $parent_2 = $this->createTerm($this->vocabulary, ['langcode' => 'en', 'moderation_state' => 'published', 'parent' => $root->id()]);
+    $parent_3 = $this->createTerm($this->vocabulary, ['langcode' => 'en', 'moderation_state' => 'published', 'parent' => $root->id()]);
+
+    // Create a child term and assign one of the parents above.
+    $child = $this->createTerm($this->vocabulary, ['langcode' => 'en', 'moderation_state' => 'published', 'parent' => $parent_1->id()]);
+
+    $taxonomy_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
+    $validation_message = 'You can only change the hierarchy for the published version of this term.';
+
+    // Add a pending revision without changing the term parent.
+    $this->drupalGet('taxonomy/term/' . $child->id() . '/edit');
+    $this->drupalPostForm(NULL, ['moderation_state[0][state]' => 'draft'], 'Save');
+
+    $this->assertSession()->pageTextNotContains($validation_message);
+
+    // Add a pending revision and change the parent.
+    $this->drupalGet('taxonomy/term/' . $child->id() . '/edit');
+    $this->drupalPostForm(NULL, [
+      'parent[]' => [$parent_2->id()],
+      'moderation_state[0][state]' => 'draft',
+    ], 'Save');
+
+    // Check that parents were not changed.
+    $this->assertSession()->pageTextContains($validation_message);
+    $taxonomy_storage->resetCache();
+    $this->assertEquals([$parent_1->id()], array_keys($taxonomy_storage->loadParents($child->id())));
+
+    // Add a pending revision and add a new parent.
+    $this->drupalGet('taxonomy/term/' . $child->id() . '/edit');
+    $this->drupalPostForm(NULL, [
+      'parent[]' => [$parent_1->id(), $parent_3->id()],
+      'moderation_state[0][state]' => 'draft',
+    ], 'Save');
+
+    // Check that parents were not changed.
+    $this->assertSession()->pageTextContains($validation_message);
+    $taxonomy_storage->resetCache();
+    $this->assertEquals([$parent_1->id()], array_keys($taxonomy_storage->loadParents($child->id())));
+
+    // Add a pending revision and use the root term as a parent.
+    $this->drupalGet('taxonomy/term/' . $child->id() . '/edit');
+    $this->drupalPostForm(NULL, [
+      'parent[]' => [$root->id()],
+      'moderation_state[0][state]' => 'draft',
+    ], 'Save');
+
+    // Check that parents were not changed.
+    $this->assertSession()->pageTextContains($validation_message);
+    $taxonomy_storage->resetCache();
+    $this->assertEquals([$parent_1->id()], array_keys($taxonomy_storage->loadParents($child->id())));
+
+    // Add a pending revision and remove the parent.
+    $this->drupalGet('taxonomy/term/' . $child->id() . '/edit');
+    $this->drupalPostForm(NULL, [
+      'parent[]' => [],
+      'moderation_state[0][state]' => 'draft',
+    ], 'Save');
+
+    // Check that parents were not changed.
+    $this->assertSession()->pageTextContains($validation_message);
+    $taxonomy_storage->resetCache();
+    $this->assertEquals([$parent_1->id()], array_keys($taxonomy_storage->loadParents($child->id())));
+
+    // Add a published revision and change the parent.
+    $this->drupalGet('taxonomy/term/' . $child->id() . '/edit');
+    $this->drupalPostForm(NULL, [
+      'parent[]' => [$parent_2->id()],
+      'moderation_state[0][state]' => 'published',
+    ], 'Save');
+
+    // Check that parents were changed.
+    $this->assertSession()->pageTextNotContains($validation_message);
+    $taxonomy_storage->resetCache();
+    $this->assertEquals([$parent_2->id()], array_keys($taxonomy_storage->loadParents($child->id())));
+
+    // Add a pending revision and change the weight.
+    $this->drupalGet('taxonomy/term/' . $child->id() . '/edit');
+    $this->drupalPostForm(NULL, [
+      'weight' => 10,
+      'moderation_state[0][state]' => 'draft',
+    ], 'Save');
+
+    // Check that weight was not changed.
+    $this->assertSession()->pageTextContains($validation_message);
+
+    // Add a new term without any parent and publish it.
+    $edit = [
+      'name[0][value]' => $this->randomMachineName(),
+      'moderation_state[0][state]' => 'published',
+    ];
+    $this->drupalPostForm("admin/structure/taxonomy/manage/{$this->vocabulary->id()}/add", $edit, 'Save');
+    // Add a pending revision without any changes.
+    $terms = taxonomy_term_load_multiple_by_name($edit['name[0][value]']);
+    $term = reset($terms);
+    $this->drupalPostForm('taxonomy/term/' . $term->id() . '/edit', ['moderation_state[0][state]' => 'draft'], 'Save');
+    $this->assertSession()->pageTextNotContains($validation_message);
+  }
+
+  /**
+   * Tests changing field values in pending revisions of taxonomy terms.
+   */
+  public function testTaxonomyTermPendingRevisions() {
+    $default_term_name = 'term - default revision';
+    $default_term_description = 'The default revision of a term.';
+    $term = $this->createTerm($this->vocabulary, [
+      'name' => $default_term_name,
+      'description' => $default_term_description,
+      'langcode' => 'en',
+      'moderation_state' => 'published',
+    ]);
+
+    // Add a pending revision without changing the term parent.
+    $this->drupalGet('taxonomy/term/' . $term->id() . '/edit');
+    $this->assertSession()->pageTextContains($default_term_name);
+    $this->assertSession()->pageTextContains($default_term_description);
+
+    // Check the revision log message field does not appear on the term edit
+    // page.
+    $this->drupalGet('taxonomy/term/' . $term->id() . '/edit');
+    $this->assertSession()->fieldNotExists('revision_log_message[0][value]');
+
+    $pending_term_name = 'term - pending revision';
+    $pending_term_description = 'The pending revision of a term.';
+    $this->drupalPostForm(NULL, [
+      'name[0][value]' => $pending_term_name,
+      'description[0][value]' => $pending_term_description,
+      'moderation_state[0][state]' => 'draft',
+    ], 'Save');
+
+    $this->assertSession()->pageTextContains($pending_term_name);
+    $this->assertSession()->pageTextContains($pending_term_description);
+    $this->assertSession()->pageTextNotContains($default_term_description);
+
+    // Check that the default revision of the term contains the correct values.
+    $this->drupalGet('taxonomy/term/' . $term->id());
+    $this->assertSession()->pageTextContains($default_term_name);
+    $this->assertSession()->pageTextContains($default_term_description);
+  }
+
+}
diff --git a/core/modules/taxonomy/tests/src/Functional/Update/TaxonomyTermUpdatePathTest.php b/core/modules/taxonomy/tests/src/Functional/Update/TaxonomyTermUpdatePathTest.php
index 55f14915a7..8dfcad7010 100644
--- a/core/modules/taxonomy/tests/src/Functional/Update/TaxonomyTermUpdatePathTest.php
+++ b/core/modules/taxonomy/tests/src/Functional/Update/TaxonomyTermUpdatePathTest.php
@@ -129,6 +129,54 @@ public function testPublishingStatusUpdateForTaxonomyTermViews() {
     }
   }
 
+  /**
+   * Tests the conversion of taxonomy terms to be revisionable.
+   *
+   * @see taxonomy_post_update_make_taxonomy_term_revisionable()
+   */
+  public function testConversionToRevisionable() {
+    $this->runUpdates();
+
+    // Log in as user 1.
+    $account = User::load(1);
+    $account->passRaw = 'drupal';
+    $this->drupalLogin($account);
+
+    // Make sure our vocabulary exists.
+    $this->drupalGet('admin/structure/taxonomy/manage/test_vocabulary/overview');
+
+    // Make sure our terms exist.
+    $assert_session = $this->assertSession();
+    $assert_session->pageTextContains('Test root term');
+    $assert_session->pageTextContains('Test child term');
+
+    $this->drupalGet('taxonomy/term/3');
+    $assert_session->statusCodeEquals('200');
+
+    // Make sure the terms are still translated.
+    $this->drupalGet('taxonomy/term/2/translations');
+    $assert_session->linkExists('Test root term - Spanish');
+
+    $storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
+
+    // Check that taxonomy terms can be created, saved and then loaded.
+    /** @var \Drupal\taxonomy\TermInterface $term */
+    $term = $storage->create([
+      'name' => 'Test term',
+      'vid' => 'article',
+      'revision_log_message' => 'Initial revision.',
+    ]);
+    $term->save();
+
+    $storage->resetCache();
+    $term = $storage->loadRevision($term->getRevisionId());
+
+    $this->assertEquals('Test term', $term->label());
+    $this->assertEquals('article', $term->bundle());
+    $this->assertEquals('Initial revision.', $term->getRevisionLogMessage());
+    $this->assertTrue($term->isPublished());
+  }
+
   /**
    * {@inheritdoc}
    */
