diff --git a/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php b/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php
index ef63472..9de5722 100644
--- a/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php
+++ b/core/modules/content_moderation/tests/src/Kernel/ContentModerationStateTest.php
@@ -33,6 +33,7 @@ class ContentModerationStateTest extends KernelTestBase {
     'media_test_source',
     'image',
     'file',
+    'taxonomy',
     'field',
     'content_moderation',
     'user',
@@ -63,6 +64,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');
@@ -161,6 +163,9 @@ public function basicModerationTestCases() {
       'Media' => [
         'media',
       ],
+      'Taxonomy term' => [
+        'taxonomy_term',
+      ],
       'Test entity - revisions, data table, and published interface' => [
         'entity_test_mulrevpub',
       ],
@@ -249,7 +254,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/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php
index ce60904..d5c4a64 100644
--- a/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php
+++ b/core/modules/rest/tests/src/Functional/EntityResource/Term/TermResourceTestBase.php
@@ -156,6 +156,9 @@ protected function getExpectedNormalizedEntity() {
       'tid' => [
         ['value' => 1],
       ],
+      'revision_id' => [
+        ['value' => 1],
+      ],
       'uuid' => [
         ['value' => $this->entity->uuid()],
       ],
@@ -200,6 +203,21 @@ protected function getExpectedNormalizedEntity() {
           'langcode' => 'en',
         ],
       ],
+      'status' => [
+        [
+          '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/src/Entity/Term.php b/core/modules/taxonomy/src/Entity/Term.php
index 51e295a..10d729f 100644
--- a/core/modules/taxonomy/src/Entity/Term.php
+++ b/core/modules/taxonomy/src/Entity/Term.php
@@ -2,8 +2,7 @@
 
 namespace Drupal\taxonomy\Entity;
 
-use Drupal\Core\Entity\ContentEntityBase;
-use Drupal\Core\Entity\EntityChangedTrait;
+use Drupal\Core\Entity\EditorialContentEntityBase;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
@@ -38,14 +37,23 @@
  *   },
  *   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"
+ *     "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",
@@ -56,12 +64,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;
+class Term extends EditorialContentEntityBase implements TermInterface {
 
   /**
    * {@inheritdoc}
@@ -129,6 +138,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', [
@@ -145,6 +155,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',
@@ -171,7 +182,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 8c0f390..08dd447 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\Entity\EntityManagerInterface;
 use Drupal\Core\Form\FormBase;
@@ -227,6 +228,60 @@ 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 ($taxonomy_vocabulary->getHierarchy()) {
+        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 ($taxonomy_vocabulary->getHierarchy()) {
+        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.
+    $current_page_tids = array_map(function ($key) {
+      list(, $tid) = explode(':', $key);
+      return $tid;
+    }, array_keys($current_page));
+    $pending_term_ids = $this->getTermsWithPendingRevisions($current_page_tids);
+    if (!empty($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.',
+        ['%capital_name' => Unicode::ucfirst($taxonomy_vocabulary->label())]
+      );
+    }
+
+    $form['help'] = [
+      '#type' => 'container',
+      'message' => ['#markup' => $help_message],
+    ];
+    if (!empty($pending_term_ids) || $taxonomy_vocabulary->getHierarchy() === VocabularyInterface::HIERARCHY_MULTIPLE) {
+      $form['help']['#attributes']['class'] = ['messages', 'messages--warning'];
+    }
+
     $errors = $form_state->getErrors();
     $row_position = 0;
     // Build the actual form.
@@ -277,6 +332,13 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular
         '#title' => $term->getName(),
         '#url' => $term->urlInfo(),
       ];
+
+      // Add a special class for terms with pending revision so we can highlight
+      // them in the form.
+      if (in_array($term->id(), $pending_term_ids)) {
+        $form['terms'][$key]['#attributes']['class'][] = 'color-warning';
+      }
+
       if ($taxonomy_vocabulary->getHierarchy() != VocabularyInterface::HIERARCHY_MULTIPLE && count($tree) > 1) {
         $parent_fields = TRUE;
         $form['terms'][$key]['term']['tid'] = [
@@ -326,7 +388,6 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular
         ];
       }
 
-      $form['terms'][$key]['#attributes']['class'] = [];
       if ($parent_fields) {
         $form['terms'][$key]['#attributes']['class'][] = 'draggable';
       }
@@ -355,7 +416,7 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular
     }
 
     $this->renderer->addCacheableDependency($form['terms'], $change_weight_access);
-    if ($change_weight_access->isAllowed()) {
+    if ($change_weight_access->isAllowed() && empty($pending_term_ids)) {
       if ($parent_fields) {
         $form['terms']['#tabledrag'][] = [
           'action' => 'match',
@@ -384,7 +445,7 @@ public function buildForm(array $form, FormStateInterface $form_state, Vocabular
       ];
     }
 
-    if (($taxonomy_vocabulary->getHierarchy() !== VocabularyInterface::HIERARCHY_MULTIPLE && count($tree) > 1) && $change_weight_access->isAllowed()) {
+    if (($taxonomy_vocabulary->getHierarchy() !== VocabularyInterface::HIERARCHY_MULTIPLE && count($tree) > 1) && $change_weight_access->isAllowed() && empty($pending_term_ids)) {
       $form['actions'] = ['#type' => 'actions', '#tree' => FALSE];
       $form['actions']['submit'] = [
         '#type' => 'submit',
@@ -509,4 +570,33 @@ public function submitReset(array &$form, FormStateInterface $form_state) {
     $form_state->setRedirectUrl($vocabulary->urlInfo('reset-form'));
   }
 
+  /**
+   * Gets the terms with pending revisions from a list of term IDs.
+   *
+   * @param int[] $tids
+   *   An array of term IDs.
+   *
+   * @return int[]
+   *   An array of term IDs which have pending revisions, keyed by revision IDs.
+   */
+  protected function getTermsWithPendingRevisions(array $tids) {
+    if (empty($tids)) {
+      return [];
+    }
+
+    $entity_type = $this->entityManager->getDefinition('taxonomy_term');
+
+    $query = $this->storageController->getQuery()
+      ->condition($entity_type->getKey('id'), $tids, 'IN')
+      ->sort($entity_type->getKey('revision'), 'DESC')
+      ->accessCheck(FALSE);
+    $default_revisions = $query->execute();
+
+    $latest_revisions_query = clone $query;
+    $latest_revisions = $latest_revisions_query->allRevisions()->execute();
+    $latest_revisions = array_unique($latest_revisions);
+
+    return array_diff_key($latest_revisions, $default_revisions);
+  }
+
 }
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 0000000..93abb07
--- /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 0000000..804ddb8
--- /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 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()) {
+      /** @var \Drupal\taxonomy\TermStorageInterface $term_storage */
+      $term_storage = $this->entityTypeManager->getStorage($entity->getEntityTypeId());
+
+      $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();
+      }
+
+      /** @var \Drupal\taxonomy\TermInterface $original */
+      $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 6de48e0..d909649 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;
 
 /**
@@ -123,6 +124,29 @@ public function buildEntity(array $form, FormStateInterface $form_state) {
   /**
    * {@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.
+    // @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}
+   */
   public function save(array $form, FormStateInterface $form_state) {
     $term = $this->entity;
 
diff --git a/core/modules/taxonomy/src/TermInterface.php b/core/modules/taxonomy/src/TermInterface.php
index 4cde8f4..bbab26c 100644
--- a/core/modules/taxonomy/src/TermInterface.php
+++ b/core/modules/taxonomy/src/TermInterface.php
@@ -4,11 +4,13 @@
 
 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 {
+interface TermInterface extends ContentEntityInterface, EntityChangedInterface, EntityPublishedInterface, RevisionLogInterface {
 
   /**
    * Gets the term's description.
diff --git a/core/modules/taxonomy/src/TermStorageSchema.php b/core/modules/taxonomy/src/TermStorageSchema.php
index 2b49ee2..aa5e1e6 100644
--- a/core/modules/taxonomy/src/TermStorageSchema.php
+++ b/core/modules/taxonomy/src/TermStorageSchema.php
@@ -17,7 +17,7 @@ class TermStorageSchema extends SqlContentEntityStorageSchema {
   protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $reset = FALSE) {
     $schema = parent::getEntitySchema($entity_type, $reset = FALSE);
 
-    $schema['taxonomy_term_field_data']['indexes'] += [
+    $schema[$entity_type->getDataTable()]['indexes'] += [
       'taxonomy_term__tree' => ['vid', 'weight', 'name'],
       'taxonomy_term__vid_name' => ['vid', 'name'],
     ];
diff --git a/core/modules/taxonomy/taxonomy.install b/core/modules/taxonomy/taxonomy.install
index c1a18bc..7a974cd 100644
--- a/core/modules/taxonomy/taxonomy.install
+++ b/core/modules/taxonomy/taxonomy.install
@@ -5,6 +5,24 @@
  * Install, update and uninstall functions for the taxonomy module.
  */
 
+use Drupal\Core\Field\BaseFieldDefinition;
+
+/**
+ * Implements hook_update_dependencies().
+ */
+function taxonomy_update_dependencies() {
+  // The update function that adds the status field must run after
+  // content_translation_update_8400() which fixes NULL values for the
+  // 'content_translation_status' field.
+  if (\Drupal::moduleHandler()->moduleExists('content_translation')) {
+    $dependencies['taxonomy'][8600] = [
+      'content_translation' => 8400,
+    ];
+
+    return $dependencies;
+  }
+}
+
 /**
  * Convert the custom taxonomy term hierarchy storage to a default storage.
  */
@@ -126,3 +144,93 @@ function taxonomy_update_8503() {
     }
   }
 }
+
+/**
+ * Add the 'published' and revisionable metadata fields to taxonomy terms.
+ */
+function taxonomy_update_8600() {
+  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
+
+  // Add the published entity key and revisionable metadata fields to the
+  // taxonomy_term entity type.
+  $entity_type = $definition_update_manager->getEntityType('taxonomy_term');
+
+  $entity_keys = $entity_type->getKeys();
+  $entity_keys['published'] = 'status';
+  $entity_type->set('entity_keys', $entity_keys);
+
+  $revision_metadata_keys = [
+    'revision_user' => 'revision_user',
+    'revision_created' => 'revision_created',
+    'revision_log_message' => 'revision_log_message'
+  ];
+  $entity_type->set('revision_metadata_keys', $revision_metadata_keys);
+
+  $definition_update_manager->updateEntityType($entity_type);
+
+  // Add the status field.
+  $status = BaseFieldDefinition::create('boolean')
+    ->setLabel(t('Publishing status'))
+    ->setDescription(t('A boolean indicating the published state.'))
+    ->setRevisionable(TRUE)
+    ->setTranslatable(TRUE)
+    ->setDefaultValue(TRUE);
+
+  $has_content_translation_status_field = \Drupal::moduleHandler()->moduleExists('content_translation') && $definition_update_manager->getFieldStorageDefinition('content_translation_status', 'taxonomy_term');
+  if ($has_content_translation_status_field) {
+    $status->setInitialValueFromField('content_translation_status', TRUE);
+  }
+  else {
+    $status->setInitialValue(TRUE);
+  }
+  $definition_update_manager->installFieldStorageDefinition('status', 'taxonomy_term', 'taxonomy_term', $status);
+
+  // Add the revision metadata fields.
+  $revision_created = BaseFieldDefinition::create('created')
+    ->setLabel(t('Revision create time'))
+    ->setDescription(t('The time that the current revision was created.'))
+    ->setRevisionable(TRUE);
+  $definition_update_manager->installFieldStorageDefinition('revision_created', 'taxonomy_term', 'taxonomy_term', $revision_created);
+
+  $revision_user = BaseFieldDefinition::create('entity_reference')
+    ->setLabel(t('Revision user'))
+    ->setDescription(t('The user ID of the author of the current revision.'))
+    ->setSetting('target_type', 'user')
+    ->setRevisionable(TRUE);
+  $definition_update_manager->installFieldStorageDefinition('revision_user', 'taxonomy_term', 'taxonomy_term', $revision_user);
+
+  $revision_log_message = BaseFieldDefinition::create('string_long')
+    ->setLabel(t('Revision log message'))
+    ->setDescription(t('Briefly describe the changes you have made.'))
+    ->setRevisionable(TRUE)
+    ->setDefaultValue('')
+    ->setDisplayOptions('form', [
+      'type' => 'string_textarea',
+      'weight' => 25,
+      'settings' => [
+        'rows' => 4,
+      ],
+    ]);
+  $definition_update_manager->installFieldStorageDefinition('revision_log_message', 'taxonomy_term', 'taxonomy_term', $revision_log_message);
+
+  // Uninstall the 'content_translation_status' field if needed.
+  $database = \Drupal::database();
+  if ($has_content_translation_status_field) {
+    // First we have to remove the field data.
+    $database->update($entity_type->getDataTable())
+      ->fields(['content_translation_status' => NULL])
+      ->execute();
+
+    // A site may have disabled revisionability for this entity type.
+    if ($entity_type->isRevisionable()) {
+      $database->update($entity_type->getRevisionDataTable())
+        ->fields(['content_translation_status' => NULL])
+        ->execute();
+    }
+
+    $content_translation_status = $definition_update_manager->getFieldStorageDefinition('content_translation_status', 'taxonomy_term');
+    $definition_update_manager->uninstallFieldStorageDefinition($content_translation_status);
+  }
+
+  return t('Taxonomy terms have been converted to revisionable and publishable.');
+}
diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
index be8532a..bd3bafa 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,29 +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');
-      if (\Drupal::currentUser()->hasPermission('administer taxonomy') || \Drupal::currentUser()->hasPermission('edit terms in ' . $vocabulary->id())) {
-        switch ($vocabulary->getHierarchy()) {
-          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->getHierarchy()) {
-          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
new file mode 100644
index 0000000..8ecda9a
--- /dev/null
+++ b/core/modules/taxonomy/taxonomy.post_update.php
@@ -0,0 +1,106 @@
+<?php
+
+/**
+ * @file
+ * Post update functions for the Taxonomy module.
+ */
+
+use Drupal\Core\Config\Entity\ConfigEntityUpdater;
+use Drupal\Core\Entity\Sql\SqlContentEntityStorageSchemaConverter;
+use Drupal\views\ViewExecutable;
+
+/**
+ * Add a 'published' = TRUE filter for all Taxonomy term views.
+ */
+function taxonomy_post_update_add_status_filter_to_taxonomy_views(&$sandbox = NULL) {
+  $definition_update_manager = \Drupal::entityDefinitionUpdateManager();
+  $entity_type = $definition_update_manager->getEntityType('taxonomy_term');
+  $published_key = $entity_type->getKey('published');
+
+  $status_filter = [
+    'id' => 'status',
+    'table' => 'taxonomy_term_field_data',
+    'field' => $published_key,
+    'relationship' => 'none',
+    'group_type' => 'group',
+    'admin_label' => '',
+    'operator' => '=',
+    'value' => '1',
+    'group' => 1,
+    'exposed' => FALSE,
+    'expose' => [
+      'operator_id' => '',
+      'label' => '',
+      'description' => '',
+      'use_operator' => FALSE,
+      'operator' => '',
+      'identifier' => '',
+      'required' => FALSE,
+      'remember' => FALSE,
+      'multiple' => FALSE,
+      'remember_roles' => [
+        'authenticated' => 'authenticated',
+        'anonymous' => '0',
+        'administrator' => '0',
+      ],
+    ],
+    'is_grouped' => FALSE,
+    'group_info' => [
+      'label' => '',
+      'description' => '',
+      'identifier' => '',
+      'optional' => TRUE,
+      'widget' => 'select',
+      'multiple' => FALSE,
+      'remember' => FALSE,
+      'default_group' => 'All',
+      'default_group_multiple' => [],
+      'group_items' => [],
+    ],
+    'entity_type' => 'taxonomy_term',
+    'entity_field' => $published_key,
+    'plugin_id' => 'boolean',
+  ];
+
+  \Drupal::classResolver(ConfigEntityUpdater::class)->update($sandbox, 'view', function ($view) use ($published_key, $status_filter) {
+    /** @var \Drupal\views\ViewEntityInterface $view */
+    // Only alter taxonomy term views.
+    if ($view->get('base_table') !== 'taxonomy_term_field_data') {
+      return FALSE;
+    }
+
+    $displays = $view->get('display');
+    foreach ($displays as $display_name => &$display) {
+      // Add a filter on the published field for the default and all the
+      // overridden displays.
+      $filters = isset($display['display_options']['filters']) ? $display['display_options']['filters'] : [];
+      $status_filter['id'] = ViewExecutable::generateHandlerId($published_key, $filters);
+
+      $display['display_options']['filters'][$status_filter['id']] = $status_filter;
+    }
+    return TRUE;
+  });
+}
+
+/**
+ * Update taxonomy terms to be revisionable.
+ */
+function taxonomy_post_update_make_taxonomy_term_revisionable(&$sandbox) {
+  $schema_converter = new SqlContentEntityStorageSchemaConverter(
+    'taxonomy_term',
+    \Drupal::entityTypeManager(),
+    \Drupal::entityDefinitionUpdateManager(),
+    \Drupal::service('entity.last_installed_schema.repository'),
+    \Drupal::keyValue('entity.storage_schema.sql'),
+    \Drupal::database()
+  );
+
+  $schema_converter->convertToRevisionable(
+    $sandbox,
+    [
+      'name',
+      'description',
+      'changed',
+    ]
+  );
+}
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 0000000..ce5e885
--- /dev/null
+++ b/core/modules/taxonomy/tests/src/Functional/TaxonomyTermContentModerationTest.php
@@ -0,0 +1,152 @@
+<?php
+
+namespace Drupal\Tests\taxonomy\Functional;
+
+use Drupal\workflows\Entity\Workflow;
+
+/**
+ * Tests taxonomy terms with Content Moderation.
+ *
+ * @group content_moderation
+ * @group taxonomy
+ */
+class TaxonomyTermContentModerationTest extends TaxonomyTestBase {
+
+  /**
+   * The vocabulary used for creating terms.
+   *
+   * @var \Drupal\taxonomy\VocabularyInterface
+   */
+  protected $vocabulary;
+
+  /**
+   * Modules to install.
+   *
+   * @var array
+   */
+  public static $modules = ['taxonomy', 'content_moderation'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->drupalLogin($this->drupalCreateUser(['administer taxonomy', 'use editorial transition create_new_draft', 'use editorial transition publish']));
+
+    // Create a vocabulary.
+    $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);
+  }
+
+}
diff --git a/core/modules/taxonomy/tests/src/Functional/Update/TermRevisionablePublishableUpdateTest.php b/core/modules/taxonomy/tests/src/Functional/Update/TermRevisionablePublishableUpdateTest.php
new file mode 100644
index 0000000..ef472fd
--- /dev/null
+++ b/core/modules/taxonomy/tests/src/Functional/Update/TermRevisionablePublishableUpdateTest.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace Drupal\Tests\taxonomy\Functional\Update;
+
+use Drupal\FunctionalTests\Update\UpdatePathTestBase;
+use Drupal\user\Entity\User;
+
+/**
+ * Tests the upgrade path for taxonomy terms.
+ *
+ * @group taxonomy
+ * @group Update
+ */
+class TermRevisionablePublishableUpdateTest extends UpdatePathTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setDatabaseDumpFiles() {
+    $this->databaseDumpFiles = [
+      __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.filled.standard.php.gz',
+    ];
+  }
+
+  /**
+   * Tests the conversion of taxonomy terms to be revisionable and publishable.
+   *
+   * @see taxonomy_update_8600()
+   * @see taxonomy_post_update_make_taxonomy_term_revisionable()
+   */
+  public function testConversionToRevisionableAndPublishable() {
+    $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');
+
+    // Check that taxonomy terms can be created, saved and then loaded.
+    $storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
+    /** @var \Drupal\taxonomy\TermInterface $term */
+    $term = $storage->create([
+      'name' => 'Test term',
+      'vid' => 'article',
+    ]);
+    $term->save();
+
+    $storage->resetCache();
+    $term = $storage->loadRevision($term->getRevisionId());
+
+    $this->assertEquals('Test term', $term->label());
+    $this->assertEquals('article', $term->bundle());
+    $this->assertTrue($term->isPublished());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function replaceUser1() {
+    // Do not replace the user from our dump.
+  }
+
+}
diff --git a/core/modules/taxonomy/tests/src/Functional/Update/TermRevisionablePublishableViewsUpdateTest.php b/core/modules/taxonomy/tests/src/Functional/Update/TermRevisionablePublishableViewsUpdateTest.php
new file mode 100644
index 0000000..ec76903
--- /dev/null
+++ b/core/modules/taxonomy/tests/src/Functional/Update/TermRevisionablePublishableViewsUpdateTest.php
@@ -0,0 +1,56 @@
+<?php
+
+namespace Drupal\Tests\taxonomy\Functional\Update;
+
+use Drupal\FunctionalTests\Update\UpdatePathTestBase;
+use Drupal\views\Views;
+
+/**
+ * Tests the upgrade path for taxonomy_term_field_data based views.
+ *
+ * @group taxonomy
+ * @group Update
+ */
+class TermRevisionablePublishableViewsUpdateTest extends UpdatePathTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'taxonomy_test_views'
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setDatabaseDumpFiles() {
+    $this->databaseDumpFiles = [
+      __DIR__ . '/../../../../../system/tests/fixtures/update/drupal-8.bare.standard.php.gz',
+    ];
+  }
+
+  /**
+   * Tests the conversion of taxonomy terms to be revisionable and publishable.
+   *
+   * @see taxonomy_post_update_add_status_filter_to_taxonomy_views()
+   */
+  public function testAddPublishedFilterToTaxonomyTermFieldDataViews() {
+    $this->runUpdates();
+
+    $views = Views::getAllViews();
+    foreach ($views as $view) {
+      // Only check taxonomy term views.
+      if ($view->get('base_table') !== 'taxonomy_term_field_data') {
+        continue;
+      }
+
+      foreach ($view->display as $display) {
+        // Assert that a published filter was added.
+        if (!empty($display['display_options']['filters'])) {
+          $this->assertArrayHasKey('status', $display['display_options']['filters']);
+        }
+      }
+    }
+  }
+
+}
