diff --git a/core/modules/taxonomy/src/Entity/Term.php b/core/modules/taxonomy/src/Entity/Term.php
index 8fc3cedb0466a5609d46089d52505d01df94578c..949518a56f2f795bfa263b844a394614823763be 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 504816668d126ea90b1f23150b1c7b5aa8e32dd9..3656a751ae332498c5ef8b027c4da27dcd39d496 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,64 @@ 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 the IDs of the terms edited on the current page which have pending
+    // revisions.
+    $edited_term_ids = array_map(function ($item) {
+      return $item->id();
+    }, $current_page);
+    $pending_term_ids = array_intersect($this->storageController->getTermIdsWithPendingRevisions(), $edited_term_ids);
+    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
+      );
+    }
+
+    // Only allow access to change parents and reorder the tree if there are no
+    // pending revisions and there are no terms with multiple parents.
+    $update_tree_access = AccessResult::allowedIf(empty($pending_term_ids) && $vocabulary_hierarchy !== VocabularyInterface::HIERARCHY_MULTIPLE);
+
+    $form['help'] = [
+      '#type' => 'container',
+      'message' => ['#markup' => $help_message],
+    ];
+    if (!$update_tree_access->isAllowed()) {
+      $form['help']['#attributes']['class'] = ['messages', 'messages--warning'];
+    }
+
     $errors = $form_state->getErrors();
     $row_position = 0;
     // Build the actual form.
@@ -268,7 +327,7 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular
       '#header' => [
         'term' => $this->t('Name'),
         'operations' => $this->t('Operations'),
-        'weight' => $this->t('Weight'),
+        'weight' => $update_tree_access->isAllowed() ? $this->t('Weight') : NULL,
       ],
       '#attributes' => [
         'id' => 'taxonomy',
@@ -276,14 +335,11 @@ 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();
     foreach ($current_page as $key => $term) {
       $form['terms'][$key] = [
         'term' => [],
         'operations' => [],
-        'weight' => [],
+        'weight' => $update_tree_access->isAllowed() ? [] : NULL,
       ];
       /** @var $term \Drupal\Core\Entity\EntityInterface */
       $term = $this->entityRepository->getTranslationFromContext($term);
@@ -301,7 +357,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 ($update_tree_access->isAllowed() && count($tree) > 1) {
         $parent_fields = TRUE;
         $form['terms'][$key]['term']['tid'] = [
           '#type' => 'hidden',
@@ -330,9 +395,9 @@ 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);
+      $update_tree_access = $update_tree_access->andIf($update_access);
 
-      if ($update_access->isAllowed()) {
+      if ($update_tree_access->isAllowed()) {
         $form['terms'][$key]['weight'] = [
           '#type' => 'weight',
           '#delta' => $delta,
@@ -350,7 +415,6 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular
         ];
       }
 
-      $form['terms'][$key]['#attributes']['class'] = [];
       if ($parent_fields) {
         $form['terms'][$key]['#attributes']['class'][] = 'draggable';
       }
@@ -378,8 +442,8 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular
       $row_position++;
     }
 
-    $this->renderer->addCacheableDependency($form['terms'], $change_weight_access);
-    if ($change_weight_access->isAllowed()) {
+    $this->renderer->addCacheableDependency($form['terms'], $update_tree_access);
+    if ($update_tree_access->isAllowed()) {
       if ($parent_fields) {
         $form['terms']['#tabledrag'][] = [
           'action' => 'match',
@@ -408,7 +472,7 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular
       ];
     }
 
-    if (($vocabulary_hierarchy !== VocabularyInterface::HIERARCHY_MULTIPLE && count($tree) > 1) && $change_weight_access->isAllowed()) {
+    if ($update_tree_access->isAllowed() && count($tree) > 1) {
       $form['actions'] = ['#type' => 'actions', '#tree' => FALSE];
       $form['actions']['submit'] = [
         '#type' => 'submit',
@@ -505,12 +569,25 @@ 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)) {
+        $this->messenger()->addError($this->t('The terms with updated parents have been modified by another user, the changes could not be saved.'));
+        $form_state->setRebuild();
+
+        return;
+      }
+
+      // 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 0000000000000000000000000000000000000000..93abb07ac8338952bae459268d9be749db9044f5
--- /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 0000000000000000000000000000000000000000..4eeb80961bc102d6d9b7f1b328902faf1f5ff27c
--- /dev/null
+++ b/core/modules/taxonomy/src/Plugin/Validation/Constraint/TaxonomyTermHierarchyConstraintValidator.php
@@ -0,0 +1,77 @@
+<?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) {
+    $term_storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
+    assert($term_storage instanceof TermStorageInterface);
+
+    // Newly created entities should be able to specify a parent.
+    if ($entity && $entity->isNew()) {
+      return;
+    }
+
+    $is_pending_revision = !$entity->isDefaultRevision();
+    $pending_term_ids = $term_storage->getTermIdsWithPendingRevisions();
+    $ancestors = $term_storage->loadAllParents($entity->id());
+    $ancestor_is_pending_revision = (bool) array_intersect_key($ancestors, array_flip($pending_term_ids));
+
+    $new_parents = array_column($entity->parent->getValue(), 'target_id');
+    $original_parents = array_keys($term_storage->loadParents($entity->id())) ?: [0];
+    if (($is_pending_revision || $ancestor_is_pending_revision) && $new_parents != $original_parents) {
+      $a = 1;
+      $this->context->buildViolation($constraint->message)
+        ->atPath('parent')
+        ->addViolation();
+    }
+
+    $original = $term_storage->loadUnchanged($entity->id());
+    if (($is_pending_revision || $ancestor_is_pending_revision) && !$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 baca720fde77ddcc847ea052fed8ee41b5e8f172..865b12dec666bf7d5af38b51b51739aab0bb37db 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 877c27d205d814419335130f5a0e0399b2221e1e..687691d0b1f07d7126697886eb61bd091c1025c3 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 4b74ba8342416314b8b42e2651ae52e8e39455c3..118bdd107a5af39b1b18c7e14ea40673674a4490 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 fa0c36971a32c3d36ce4017a1e70996b30663ce9..4271805ac574d4456fb6025ff99b72759145f51c 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 652ba32d0790bb6907b990b961e7397dd7f44e31..1d724c2535aa6dae5f60fe7a4cc361ad03a4c6bd 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,33 +77,19 @@ 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>';
-        }
-      }
   }
 }
 
+/**
+ * Implements hook_entity_type_alter().
+ */
+function taxonomy_entity_type_alter(array &$entity_types) {
+  // @todo Moderation is disabled for taxonomy terms until when we have an UI
+  //   for them.
+  //   @see https://www.drupal.org/project/drupal/issues/2899923
+  $entity_types['taxonomy_term']->setHandlerClass('moderation', '');
+}
+
 /**
  * Entity URI callback.
  */
diff --git a/core/modules/taxonomy/taxonomy.post_update.php b/core/modules/taxonomy/taxonomy.post_update.php
index 6d84dfd69b08eb592ee49c26ee7bbcd467ea92e8..69fbb3d8648e68e1a9cf6b112ad1d5d4ce435f96 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 f303072a5f8192961f9fabcbc84d8ec254c03299..91326511fe59bbe39a40d8d710fa7bc3cb7983f9 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/Update/TaxonomyTermUpdatePathTest.php b/core/modules/taxonomy/tests/src/Functional/Update/TaxonomyTermUpdatePathTest.php
index 55f14915a7725deddd504ab66b8b150d9a7565bf..45e7e56a886d1f3cab7a7eb911cbedc4266b2227 100644
--- a/core/modules/taxonomy/tests/src/Functional/Update/TaxonomyTermUpdatePathTest.php
+++ b/core/modules/taxonomy/tests/src/Functional/Update/TaxonomyTermUpdatePathTest.php
@@ -129,6 +129,67 @@ 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();
+
+    // Check the database tables and the field storage definitions.
+    $schema = \Drupal::database()->schema();
+    $this->assertTrue($schema->tableExists('taxonomy_term_data'));
+    $this->assertTrue($schema->tableExists('taxonomy_term_field_data'));
+    $this->assertTrue($schema->tableExists('taxonomy_term_revision'));
+    $this->assertTrue($schema->tableExists('taxonomy_term_field_revision'));
+
+    $field_storage_definitions = \Drupal::service('entity.last_installed_schema.repository')->getLastInstalledFieldStorageDefinitions('taxonomy_term');
+    $this->assertTrue($field_storage_definitions['langcode']->isRevisionable());
+    $this->assertTrue($field_storage_definitions['name']->isRevisionable());
+    $this->assertTrue($field_storage_definitions['description']->isRevisionable());
+    $this->assertTrue($field_storage_definitions['changed']->isRevisionable());
+
+    // 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}
    */
diff --git a/core/modules/taxonomy/tests/src/Kernel/TermHierarchyValidationTest.php b/core/modules/taxonomy/tests/src/Kernel/TermHierarchyValidationTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..95e14f96b44507a75c40979527091304b70f6b62
--- /dev/null
+++ b/core/modules/taxonomy/tests/src/Kernel/TermHierarchyValidationTest.php
@@ -0,0 +1,194 @@
+<?php
+
+namespace Drupal\Tests\taxonomy\Kernel;
+
+use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
+use Drupal\taxonomy\Entity\Vocabulary;
+
+/**
+ * Tests handling of pending revisions.
+ *
+ * @coversDefaultClass \Drupal\taxonomy\Plugin\Validation\Constraint\TaxonomyTermHierarchyConstraintValidator
+ *
+ * @group taxonomy
+ */
+class TermHierarchyValidationTest extends EntityKernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['taxonomy'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installEntitySchema('taxonomy_term');
+  }
+
+  /**
+   * Tests the term hierarchy validation with re-parenting in pending revisions.
+   */
+  public function testTermHierarchyValidation() {
+    $vocabulary_id = mb_strtolower($this->randomMachineName());
+    $vocabulary = Vocabulary::create([
+      'name' => $vocabulary_id,
+      'vid' => $vocabulary_id,
+    ]);
+    $vocabulary->save();
+
+    // Create a simple hierarchy in the vocabulary, a root term and three parent
+    // terms.
+    /** @var \Drupal\Core\Entity\RevisionableStorageInterface $term_storage */
+    $term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
+    $root = $term_storage->create([
+      'name' => $this->randomMachineName(),
+      'vid' => $vocabulary_id,
+    ]);
+    $root->save();
+    $parent1 = $term_storage->create([
+      'name' => $this->randomMachineName(),
+      'vid' => $vocabulary_id,
+      'parent' => $root->id(),
+    ]);
+    $parent1->save();
+    $parent2 = $term_storage->create([
+      'name' => $this->randomMachineName(),
+      'vid' => $vocabulary_id,
+      'parent' => $root->id(),
+    ]);
+    $parent2->save();
+    $parent3 = $term_storage->create([
+      'name' => $this->randomMachineName(),
+      'vid' => $vocabulary_id,
+      'parent' => $root->id(),
+    ]);
+    $parent3->save();
+
+    // Create a child term and assign one of the parents above.
+    $child1 = $term_storage->create([
+      'name' => $this->randomMachineName(),
+      'vid' => $vocabulary_id,
+      'parent' => $parent1->id(),
+    ]);
+    $violations = $child1->validate();
+    $this->assertEmpty($violations);
+    $child1->save();
+
+    $validation_message = 'You can only change the hierarchy for the <em>published</em> version of this term.';
+
+    // Add a pending revision without changing the term parent.
+    $pending_name = $this->randomMachineName();
+    $child_pending = $term_storage->createRevision($child1, FALSE);
+    $child_pending->name = $pending_name;
+    $violations = $child_pending->validate();
+    $this->assertEmpty($violations);
+
+    // Add a pending revision and change the parent.
+    $child_pending = $term_storage->createRevision($child1, FALSE);
+    $child_pending->parent = $parent2;
+    $violations = $child_pending->validate();
+    $this->assertCount(1, $violations);
+    $this->assertEquals($validation_message, $violations[0]->getMessage());
+    $this->assertEquals('parent', $violations[0]->getPropertyPath());
+
+    // Add a pending revision and add a new parent.
+    $child_pending = $term_storage->createRevision($child1, FALSE);
+    $child_pending->parent[0] = $parent1;
+    $child_pending->parent[1] = $parent3;
+    $violations = $child_pending->validate();
+    $this->assertCount(1, $violations);
+    $this->assertEquals($validation_message, $violations[0]->getMessage());
+    $this->assertEquals('parent', $violations[0]->getPropertyPath());
+
+    // Add a pending revision and use the root term as a parent.
+    $child_pending = $term_storage->createRevision($child1, FALSE);
+    $child_pending->parent[0] = $root;
+    $violations = $child_pending->validate();
+    $this->assertCount(1, $violations);
+    $this->assertEquals($validation_message, $violations[0]->getMessage());
+    $this->assertEquals('parent', $violations[0]->getPropertyPath());
+
+    // Add a pending revision and remove the parent.
+    $child_pending = $term_storage->createRevision($child1, FALSE);
+    $child_pending->parent[0] = NULL;
+    $violations = $child_pending->validate();
+    $this->assertCount(1, $violations);
+    $this->assertEquals($validation_message, $violations[0]->getMessage());
+    $this->assertEquals('parent', $violations[0]->getPropertyPath());
+
+    // Add a pending revision and change the weight.
+    $child_pending = $term_storage->createRevision($child1, FALSE);
+    $child_pending->weight = 10;
+    $violations = $child_pending->validate();
+    $this->assertCount(1, $violations);
+    $this->assertEquals($validation_message, $violations[0]->getMessage());
+    $this->assertEquals('weight', $violations[0]->getPropertyPath());
+
+    // Add a pending revision and change both the parent and the weight.
+    $child_pending = $term_storage->createRevision($child1, FALSE);
+    $child_pending->parent = $parent2;
+    $child_pending->weight = 10;
+    $violations = $child_pending->validate();
+    $this->assertCount(2, $violations);
+    $this->assertEquals($validation_message, $violations[0]->getMessage());
+    $this->assertEquals($validation_message, $violations[1]->getMessage());
+    $this->assertEquals('parent', $violations[0]->getPropertyPath());
+    $this->assertEquals('weight', $violations[1]->getPropertyPath());
+
+    // Add a published revision and change the parent.
+    $child_pending = $term_storage->createRevision($child1, TRUE);
+    $child_pending->parent[0] = $parent2;
+    $violations = $child_pending->validate();
+    $this->assertEmpty($violations);
+
+    // Add a new term as a third-level child.
+    // The taxonomy tree structure ends up as follows:
+    // root
+    // - parent1
+    // - parent2
+    // -- child1 <- this will be a term with a pending revision
+    // --- child2
+    // - parent3
+    $child2 = $term_storage->create([
+      'name' => $this->randomMachineName(),
+      'vid' => $vocabulary_id,
+      'parent' => $child1->id(),
+    ]);
+    $child2->save();
+
+    // Change 'child1' to be a pending revision.
+    $child1 = $term_storage->createRevision($child1, FALSE);
+    $child1->save();
+
+    // Check that a child of a pending term can not be re-parented.
+    $child2_pending = $term_storage->createRevision($child2, FALSE);
+    $child2_pending->parent = $parent3;
+    $violations = $child2_pending->validate();
+    $this->assertCount(1, $violations);
+    $this->assertEquals($validation_message, $violations[0]->getMessage());
+    $this->assertEquals('parent', $violations[0]->getPropertyPath());
+
+    // Check that another term which has a pending revision can not moved under
+    // another term which has pending revision.
+    $parent3_pending = $term_storage->createRevision($parent3, FALSE);
+    $parent3_pending->parent = $child1;
+    $violations = $parent3_pending->validate();
+    $this->assertCount(1, $violations);
+    $this->assertEquals($validation_message, $violations[0]->getMessage());
+    $this->assertEquals('parent', $violations[0]->getPropertyPath());
+
+    // Check that a new term can be created under a term that has a pending
+    // revision.
+    $child3 = $term_storage->create([
+      'name' => $this->randomMachineName(),
+      'vid' => $vocabulary_id,
+      'parent' => $child1->id(),
+    ]);
+    $violations = $child3->validate();
+    $this->assertEmpty($violations);
+  }
+
+}