diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 3a2206305b5e2671467dc47a56b65b30791a538c..3c4c4d08e4e6841c39d9501e435b6f91f9a4ec41 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -9,7 +9,7 @@ include:
 variables:
   OPT_IN_TEST_NEXT_MINOR: 1
   OPT_IN_TEST_NEXT_MAJOR: 1
-  _CSPELL_WORDS: 'Csvg, Cpath'
+  _CSPELL_WORDS: 'Csvg, Cpath, purgeable'
 
 composer-lint:
   allow_failure: false
diff --git a/src/Access/TrashAccessCheck.php b/src/Access/TrashAccessCheck.php
new file mode 100644
index 0000000000000000000000000000000000000000..43e76e188b5ba142c371212611890b9703faf6f5
--- /dev/null
+++ b/src/Access/TrashAccessCheck.php
@@ -0,0 +1,140 @@
+<?php
+
+namespace Drupal\trash\Access;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Routing\Access\AccessInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\trash\TrashManagerInterface;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Access checker for routes with a '_trash_access' requirement.
+ *
+ * The basic concept is that translators are free to restore/purge translations
+ * when they have access to update translations. Restoring or purging the
+ * original language requires explicit permissions to do so.
+ *
+ * Translations can not be restored if the original language is deleted and the
+ * user does not also have access to also restore it.
+ *
+ * Expected operations for '_trash_access' and what they check access for:
+ * - 'restore': Restore the original language.
+ * - 'purge': Purge an entity, only allowed on original languages.
+ * - 'purge-translation': Purge a single translation.
+ * - 'restore-translation': Restore a single translation, and the original
+ *    language if it is also deleted.
+ */
+class TrashAccessCheck implements AccessInterface {
+
+  /**
+   * Constructs an access checker.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
+   *   The entity type manager.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
+   *   The language manager.
+   * @param \Drupal\trash\TrashManagerInterface $trashManager
+   *   The trash manager.
+   */
+  public function __construct(
+    protected EntityTypeManagerInterface $entityTypeManager,
+    protected LanguageManagerInterface $languageManager,
+    protected TrashManagerInterface $trashManager,
+  ) {}
+
+  /**
+   * Checks translation access for the entity and operation on the given route.
+   *
+   * @param \Symfony\Component\Routing\Route $route
+   *   The route to check against.
+   * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
+   *   The parametrized route.
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The currently logged in account.
+   * @param \Drupal\Core\Language\LanguageInterface|null $language
+   *   The language for the entity translation to check access for.
+   * @param string|null $entity_type_id
+   *   (optional) The entity type ID.
+   *
+   * @return \Drupal\Core\Access\AccessResultInterface
+   *   The access result.
+   */
+  public function access(Route $route, RouteMatchInterface $route_match, AccountInterface $account, ?LanguageInterface $language = NULL, ?string $entity_type_id = NULL) {
+    $entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
+    if (!$this->trashManager->isEntityTypeEnabled($entity_type)) {
+      return AccessResult::forbidden('Unsupported entity type')
+        ->addCacheableDependency($entity_type);
+    }
+    /** @var \Drupal\Core\Entity\ContentEntityInterface|null $entity */
+    $entity = $route_match->getParameter($entity_type_id);
+    if (NULL === $entity) {
+      return AccessResult::forbidden('No entity parameter')
+        ->addCacheableDependency($entity_type);
+    }
+    $operation = $route->getRequirement('_trash_access');
+    if ($language && $entity->hasTranslation($language->getId())) {
+      $entity = $entity->getTranslation($language->getId());
+    }
+    if (!trash_entity_is_deleted($entity)) {
+      return AccessResult::forbidden('Entity is not deleted')
+        ->addCacheableDependency($entity);
+    }
+
+    if ($entity->isDefaultTranslation() && in_array($operation, [
+      'restore-translation',
+      'purge-translation',
+    ])) {
+      return AccessResult::forbidden('Unsupported operation')
+        ->addCacheableDependency($entity);
+    }
+    if ($operation === 'restore-translation') {
+      // Restoring a translation with a deleted original language would also
+      // need to restore the original language.
+      if (trash_entity_is_deleted($entity->getUntranslated())) {
+        // This depends entirely on if the original language can be restored.
+        return AccessResult::allowedIfHasPermission($account, "restore {$entity->getEntityTypeId()} entities")
+          ->addCacheableDependency($entity);
+      }
+      try {
+        // If possible, fall back on normal translation access.
+        return $this->trashManager->executeInTrashContext('ignore', function () use ($entity) {
+          /** @var \Drupal\content_translation\ContentTranslationHandlerInterface $handler */
+          $handler = $this->entityTypeManager->getHandler($entity->getEntityTypeId(), 'translation');
+          return $handler->getTranslationAccess($entity, 'update');
+        });
+      }
+      catch (\Throwable $throwable) {
+        // No working translation handler, fall back on default access.
+      }
+      return $entity->getUntranslated()
+        ->access('update', $account, TRUE);
+    }
+    elseif ($operation === 'purge-translation') {
+      $result = AccessResult::allowedIfHasPermission($account, "purge {$entity->getEntityTypeId()} entities")
+        ->addCacheableDependency($entity);
+      if ($result->isAllowed()) {
+        return $result;
+      }
+      try {
+        return $this->trashManager->executeInTrashContext('ignore', function () use ($entity) {
+          /** @var \Drupal\content_translation\ContentTranslationHandlerInterface $handler */
+          $handler = $this->entityTypeManager->getHandler($entity->getEntityTypeId(), 'translation');
+          return $handler->getTranslationAccess($entity, 'delete');
+        });
+      }
+      catch (\Throwable $exception) {
+        // No working translation handler, fall back on default access.
+      }
+      return $entity->access('delete', $account, TRUE);
+    }
+
+    return AccessResult::allowedIfHasPermission($account, "$operation $entity_type_id entities")
+      ->addCacheableDependency($entity);
+  }
+
+}
diff --git a/src/Controller/TrashController.php b/src/Controller/TrashController.php
index efbd5e0b332f793f93a60b60131e3b9377a93835..2489cf042cf533c25484f4b38c2f24ed61394361 100644
--- a/src/Controller/TrashController.php
+++ b/src/Controller/TrashController.php
@@ -13,6 +13,8 @@ use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Entity\RevisionLogInterface;
+use Drupal\Core\Entity\TranslatableInterface;
+use Drupal\Core\Language\LanguageInterface;
 use Drupal\trash\TrashManagerInterface;
 use Drupal\user\EntityOwnerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -23,11 +25,18 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
  */
 class TrashController extends ControllerBase implements ContainerInjectionInterface {
 
+  /**
+   * Is the site multilingual?
+   */
+  protected bool $isMultilingual = FALSE;
+
   public function __construct(
     protected TrashManagerInterface $trashManager,
     protected EntityTypeBundleInfoInterface $bundleInfo,
     protected DateFormatterInterface $dateFormatter,
-  ) {}
+  ) {
+    $this->isMultilingual = $this->languageManager()->isMultilingual();
+  }
 
   /**
    * {@inheritdoc}
@@ -97,6 +106,19 @@ class TrashController extends ControllerBase implements ContainerInjectionInterf
     ];
 
     $entity_type = $this->entityTypeManager()->getDefinition($entity_type_id);
+    if ($this->isMultilingual && $entity_type->isTranslatable()) {
+      $build['intro'] = [
+        '#theme' => 'item_list',
+        '#title' => $this->t('Multilingual considerations'),
+        '#items' => [
+          $this->t('Restoring a translation always restores the original language if it was deleted.'),
+          $this->t('Purging the original language also purges all translations.'),
+          $this->t('Purging a translation does not purge the original language.'),
+          $this->t("Translations may also be restored or purged from each entity's translation overview."),
+        ],
+      ];
+    }
+
     $build['table'] = [
       '#type' => 'table',
       '#header' => $this->buildHeader($entity_type),
@@ -107,8 +129,24 @@ class TrashController extends ControllerBase implements ContainerInjectionInterf
         'tags' => $entity_type->getListCacheTags(),
       ],
     ];
-    foreach ($this->load($entity_type) as $entity) {
-      if ($row = $this->buildRow($entity)) {
+    /** @var \Drupal\Core\Entity\FieldableEntityInterface[] $entities */
+    $entities = $this->load($entity_type);
+    $url_options = [
+      'language' => $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_INTERFACE),
+      'query' => $this->getDestinationArray(),
+    ];
+    foreach ($entities as $entity) {
+      if ($entity instanceof TranslatableInterface) {
+        foreach (array_keys($entity->getTranslationLanguages()) as $langcode) {
+          if (($translation = $entity->getTranslation($langcode)) &&
+            $translation->get('deleted')->value &&
+            ($row = $this->buildRow($translation, $url_options))
+          ) {
+            $build['table']['#rows'][$entity->id() . '_' . $langcode]['data'] = $row;
+          }
+        }
+      }
+      elseif ($row = $this->buildRow($entity, $url_options)) {
         $build['table']['#rows'][$entity->id()] = $row;
       }
     }
@@ -152,6 +190,7 @@ class TrashController extends ControllerBase implements ContainerInjectionInterf
    */
   protected function buildHeader(EntityTypeInterface $entity_type): array {
     $row['label'] = $this->t('Title');
+    $row['entity_id'] = $this->t('Id');
     $row['bundle'] = $entity_type->getBundleLabel();
     if ($entity_type->entityClassImplements(EntityOwnerInterface::class)) {
       $row['owner'] = $this->t('Author');
@@ -162,6 +201,9 @@ class TrashController extends ControllerBase implements ContainerInjectionInterf
     if ($entity_type->entityClassImplements(RevisionLogInterface::class)) {
       $row['revision_user'] = $this->t('Deleted by');
     }
+    if ($this->isMultilingual && $entity_type->isTranslatable()) {
+      $row['language'] = $this->t('Language');
+    }
     $row['deleted'] = $this->t('Deleted on');
     $row['operations'] = $this->t('Operations');
     return $row;
@@ -172,25 +214,31 @@ class TrashController extends ControllerBase implements ContainerInjectionInterf
    *
    * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
    *   The entity for this row of the list.
+   * @param array $url_options
+   *   URL options for operation links.
    *
    * @return array
    *   A render array structure of fields for this entity.
    */
-  protected function buildRow(FieldableEntityInterface $entity): array {
+  protected function buildRow(FieldableEntityInterface $entity, array $url_options): array {
     $entity_type = $entity->getEntityType();
     if ($entity_type->getLinkTemplate('canonical') != $entity_type->getLinkTemplate('edit-form') && $entity->access('view')) {
       $row['label']['data'] = [
         '#type' => 'link',
-        '#title' => "{$entity->label()} ({$entity->id()})",
+        '#title' => $entity->label(),
         '#url' => $entity->toUrl('canonical', ['query' => ['in_trash' => TRUE]]),
       ];
     }
     else {
       $row['label']['data'] = [
-        '#markup' => "{$entity->label()} ({$entity->id()})",
+        '#markup' => $entity->label(),
       ];
     }
 
+    $row['entity_id']['data'] = [
+      '#markup' => $entity->id(),
+    ];
+
     $row['bundle'] = $this->bundleInfo->getBundleInfo($entity->getEntityTypeId())[$entity->bundle()]['label'];
 
     if ($entity_type->entityClassImplements(EntityOwnerInterface::class)) {
@@ -216,6 +264,18 @@ class TrashController extends ControllerBase implements ContainerInjectionInterf
 
     $row['deleted'] = $this->dateFormatter->format($entity->get('deleted')->value, 'short');
 
+    if ($this->isMultilingual && $entity->getEntityType()->isTranslatable()) {
+      if ($entity instanceof TranslatableInterface && $entity->isDefaultTranslation()) {
+        $row['language'] = $this->t('<strong>@language_name (Original language)</strong>', [
+          '@language_name' => $entity->language()
+            ->getName(),
+        ]);
+      }
+      else {
+        $row['language'] = $entity->language()->getName();
+      }
+    }
+
     $list_builder = $this->entityTypeManager->hasHandler($entity_type->id(), 'list_builder')
       ? $this->entityTypeManager->getListBuilder($entity_type->id())
       : $this->entityTypeManager->createHandlerInstance(EntityListBuilder::class, $entity_type);
diff --git a/src/EventSubscriber/TrashLanguageOverviewSubscriber.php b/src/EventSubscriber/TrashLanguageOverviewSubscriber.php
new file mode 100644
index 0000000000000000000000000000000000000000..33ccab68f31c2400b197eb1f3e7a5d25ce99fc77
--- /dev/null
+++ b/src/EventSubscriber/TrashLanguageOverviewSubscriber.php
@@ -0,0 +1,92 @@
+<?php
+
+namespace Drupal\trash\EventSubscriber;
+
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Routing\RedirectDestinationTrait;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\trash\Controller\TrashController;
+use Drupal\trash\TrashManagerInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpKernel\Event\ViewEvent;
+use Symfony\Component\HttpKernel\KernelEvents;
+
+/**
+ * Listens to the Kernel View event to alter the translation overview.
+ */
+class TrashLanguageOverviewSubscriber implements EventSubscriberInterface {
+
+  use StringTranslationTrait;
+  use RedirectDestinationTrait;
+
+  /**
+   * Construct the event subscriber.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
+   *   The entity type manager.
+   * @param \Drupal\trash\TrashManagerInterface $trashManager
+   *   The trash manager.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
+   *   The language manager.
+   */
+  public function __construct(
+    protected EntityTypeManagerInterface $entityTypeManager,
+    protected TrashManagerInterface $trashManager,
+    protected LanguageManagerInterface $languageManager,
+  ) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events[KernelEvents::VIEW] = ['onView', 1];
+    return $events;
+  }
+
+  /**
+   * Adds "deleted" status to the translation overview page.
+   *
+   * Most of the logic from ContentTranslationController is done again here to
+   * the correct revision and status for each language. It would have been
+   * easier if content_translation had provided this data in the render array.
+   *
+   * @param \Symfony\Component\HttpKernel\Event\ViewEvent $event
+   *   The Kernel View event.
+   */
+  public function onView(ViewEvent $event) {
+    if (is_array($event->getControllerResult()) && isset($event->getControllerResult()['content_translation_overview'])) {
+      $build = $event->getControllerResult();
+      /** @var \Drupal\Core\Entity\EntityInterface $entity */
+      $entity = $build['#entity'];
+      $entity_type = $entity->getEntityType();
+      if (!$entity instanceof ContentEntityInterface || !$this->trashManager->isEntityTypeEnabled($entity_type)) {
+        return;
+      }
+      $operations_column = count($build['content_translation_overview']['#header']) - 1;
+      $status_column = $operations_column - 1;
+      $url_options = [
+        'language' => $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_INTERFACE),
+        'query' => $this->getDestinationArray(),
+      ];
+      foreach (array_keys($this->languageManager->getLanguages()) as $row_index => $langcode) {
+        $row = &$build['content_translation_overview']['#rows'][$row_index];
+        if (!$entity->hasTranslation($langcode) || !$entity->hasLinkTemplate('restore-translation') || !$entity->hasLinkTemplate('purge-translation')) {
+          continue;
+        }
+        /** @var \Drupal\Core\Entity\ContentEntityInterface $translation */
+        $translation = $entity->getTranslation($langcode);
+        if ($translation && !$translation->get('deleted')->isEmpty()) {
+          $row[$status_column] = $this->t('Deleted');
+          $links = TrashController::getOperations($translation, $url_options);
+          $row[$operations_column]['data']['#links'] = $links;
+        }
+      }
+      $event->setControllerResult($build);
+    }
+  }
+
+}
diff --git a/src/Form/EntityPurgeForm.php b/src/Form/EntityPurgeForm.php
index bd23be70ed01450f8379c88cdeb6bff6c2050e8a..fa0ba78dab3d6d298a882c481098816986fcb585 100644
--- a/src/Form/EntityPurgeForm.php
+++ b/src/Form/EntityPurgeForm.php
@@ -6,6 +6,7 @@ namespace Drupal\trash\Form;
 
 use Drupal\Core\Entity\ContentEntityConfirmFormBase;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Form\WorkspaceSafeFormInterface;
 use Drupal\Core\Url;
 use Drupal\trash\TrashManager;
@@ -34,6 +35,21 @@ class EntityPurgeForm extends ContentEntityConfirmFormBase implements WorkspaceS
    * {@inheritdoc}
    */
   public function getQuestion() {
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+    $entity = $this->getEntity();
+    if (!$entity->isDefaultTranslation()) {
+      return $this->t('Are you sure you want to permanently delete the @language translation of the @entity-type %label?', [
+        '@language' => $entity->language()->getName(),
+        '@entity-type' => $this->getEntity()->getEntityType()->getSingularLabel(),
+        '%label' => $this->getEntity()->label(),
+      ]);
+    }
+    if (count($entity->getTranslationLanguages()) > 1) {
+      return $this->t('Are you sure you want to permanently delete the @entity-type %label and all translations?', [
+        '@entity-type' => $this->getEntity()->getEntityType()->getSingularLabel(),
+        '%label' => $this->getEntity()->label() ?? $this->getEntity()->id(),
+      ]);
+    }
     return $this->t('Are you sure you want to permanently delete the @entity-type %label?', [
       '@entity-type' => $this->getEntity()->getEntityType()->getSingularLabel(),
       '%label' => $this->getEntity()->label() ?? $this->getEntity()->id(),
@@ -50,9 +66,43 @@ class EntityPurgeForm extends ContentEntityConfirmFormBase implements WorkspaceS
   /**
    * {@inheritdoc}
    */
-  public function buildForm(array $form, FormStateInterface $form_state) {
+  public function buildForm(array $form, FormStateInterface $form_state, ?LanguageInterface $language = NULL) {
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+    $entity = $this->getEntity();
+    if ($language === NULL) {
+      $entity = $entity->getUntranslated();
+      $language = $entity->language();
+    }
+    if ($entity->hasTranslation($language->getId())) {
+      $form_state->set('langcode', $language->getId());
+    }
     $form = parent::buildForm($form, $form_state);
-    $this->trashManager->getHandler($this->getEntity()->getEntityTypeId())?->restoreFormAlter($form, $form_state);
+
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+    $entity = $this->getEntity();
+    if ($entity->isDefaultTranslation()) {
+      if (count($entity->getTranslationLanguages()) > 1) {
+        $languages = [];
+        foreach ($entity->getTranslationLanguages() as $language) {
+          $languages[] = $language->getName();
+        }
+
+        $form['deleted_translations'] = [
+          '#theme' => 'item_list',
+          '#title' => $this->t('The following @entity-type translations will be permanently deleted:', [
+            '@entity-type' => $entity->getEntityType()->getSingularLabel(),
+          ]),
+          '#items' => $languages,
+        ];
+
+        $form['actions']['submit']['#value'] = $this->t('Delete all translations');
+      }
+    }
+    else {
+      $form['actions']['submit']['#value'] = $this->t('Delete @language translation', ['@language' => $entity->language()->getName()]);
+    }
+
+    $this->trashManager->getHandler($this->getEntity()->getEntityTypeId())?->purgeFormAlter($form, $form_state);
 
     return $form;
   }
@@ -63,15 +113,29 @@ class EntityPurgeForm extends ContentEntityConfirmFormBase implements WorkspaceS
   public function submitForm(array &$form, FormStateInterface $form_state) {
     /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
     $entity = $this->getEntity();
-    $message = $this->t('The @entity-type %label has been permanently deleted.', [
-      '@entity-type' => $entity->getEntityType()->getSingularLabel(),
-      '%label' => $entity->label() ?? $entity->id(),
-    ]);
 
-    $entity->delete();
+    if (!$entity->isDefaultTranslation()) {
+      $message = $this->t('The @entity-type %label @language translation has been permanently deleted.', [
+        '@entity-type' => $entity->getEntityType()->getSingularLabel(),
+        '%label' => $entity->label() ?? $entity->id(),
+        '@language' => $entity->language()->getName(),
+      ]);
+      $untranslated_entity = $entity->getUntranslated();
+      $untranslated_entity->removeTranslation($entity->language()->getId());
+      $untranslated_entity->save();
+    }
+    else {
+      $message = $this->t('The @entity-type %label has been permanently deleted.', [
+        '@entity-type' => $entity->getEntityType()->getSingularLabel(),
+        '%label' => $entity->label() ?? $entity->id(),
+      ]);
+      $entity->delete();
+    }
+
     $form_state->setRedirectUrl($this->getRedirectUrl());
 
     $this->messenger()->addStatus($message);
+    // @todo Change log message if only a translation was purged.
     $this->getLogger('trash')->info('@entity-type (@bundle): permanently deleted %label.', [
       '@entity-type' => $entity->getEntityType()->getLabel(),
       '@bundle' => $this->entityTypeBundleInfo->getBundleInfo($entity->getEntityTypeId())[$entity->bundle()]['label'],
diff --git a/src/Form/EntityRestoreForm.php b/src/Form/EntityRestoreForm.php
index a2c751623ef6a8bb63b62fb8f64d0fbaec7300e3..c722556631ea684f515bb49d49d98e7dc8e08bc5 100644
--- a/src/Form/EntityRestoreForm.php
+++ b/src/Form/EntityRestoreForm.php
@@ -6,6 +6,7 @@ namespace Drupal\trash\Form;
 
 use Drupal\Core\Entity\ContentEntityConfirmFormBase;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Form\WorkspaceSafeFormInterface;
 use Drupal\Core\Url;
 use Drupal\trash\TrashManager;
@@ -53,14 +54,61 @@ class EntityRestoreForm extends ContentEntityConfirmFormBase implements Workspac
    * {@inheritdoc}
    */
   public function getDescription() {
-    return NULL;
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+    $entity = $this->getEntity();
+    $untranslated = $entity->getUntranslated();
+    if (!$entity->isDefaultTranslation() && trash_entity_is_deleted($untranslated)) {
+      return $this->t('Restoring the @language translation will also restore the @original_language original language.', [
+        '@language' => $entity->language()->getName(),
+        '@original_language' => $untranslated->language()->getName(),
+      ]);
+    }
+
+    return '';
   }
 
   /**
    * {@inheritdoc}
    */
-  public function buildForm(array $form, FormStateInterface $form_state) {
-    $form = parent::buildForm($form, $form_state);
+  public function buildForm(array $form, FormStateInterface $form_state, ?LanguageInterface $language = NULL) {
+    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
+    $entity = $this->getEntity();
+    if ($language === NULL) {
+      $entity = $entity->getUntranslated();
+      $language = $entity->language();
+    }
+    if ($entity->hasTranslation($language->getId())) {
+      $form_state->set('langcode', $language->getId());
+    }
+    else {
+      $language = $entity->language();
+    }
+
+    $translation_languages = $entity->getTranslationLanguages(FALSE);
+    if (count($translation_languages) > 0) {
+      $translation_options = array_map(function (LanguageInterface $translation) use ($entity) {
+        return $this->t('@translation_label: %entity_label', [
+          '@translation_label' => $translation->getName(),
+          '%entity_label' => $entity->getTranslation($translation->getId())->label(),
+        ]);
+      }, array_filter($translation_languages,
+        function (LanguageInterface $translation) use ($entity, $language) {
+          return $translation->getId() !== $entity->language()->getId() &&
+            $translation->getId() !== $language->getId() &&
+          trash_entity_is_deleted($entity->getTranslation($translation->getId()));
+        }
+      ));
+      if (!empty($translation_options)) {
+        $form['translations'] = [
+          '#type' => 'checkboxes',
+          '#title' => $this->t('Translations'),
+          '#description' => $this->t('Other translations to also restore.'),
+          '#options' => $translation_options,
+          '#default_value' => $entity->getTranslation($language->getId())->isDefaultTranslation() ? array_keys($translation_languages) : [],
+        ];
+      }
+    }
+
     $this->trashManager->getHandler($this->getEntity()->getEntityTypeId())?->restoreFormAlter($form, $form_state);
 
     return $form;
@@ -72,15 +120,38 @@ class EntityRestoreForm extends ContentEntityConfirmFormBase implements Workspac
   public function submitForm(array &$form, FormStateInterface $form_state) {
     /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
     $entity = $this->getEntity();
-    $message = $this->t('The @entity-type %label has been restored from trash.', [
+    $message = $this->t('The @entity-type <a href=":url">%label</a> has been restored from trash.', [
       '@entity-type' => $entity->getEntityType()->getSingularLabel(),
       '%label' => $entity->label() ?? $entity->id(),
+      ':url' => $entity->toUrl()->toString(),
     ]);
 
-    trash_restore_entity($entity);
-    $form_state->setRedirectUrl($this->getRedirectUrl());
+    $langcodes = [$entity->language()->getId()];
+    $langcodes = array_merge($langcodes, array_keys(array_filter($form_state->getValue('translations', []))));
+    // Restoring the original language ensures we don't end up in a weird
+    // situation where a translated entity's source translation looks purgeable
+    // in the trash but doing so unexpectedly also purges active translations.
+    if (!$entity->isDefaultTranslation()) {
+      $untranslated = $entity->getUntranslated();
+      if (trash_entity_is_deleted($untranslated)) {
+        $langcodes[] = $untranslated->language()->getId();
+        $message = $this->t('The @entity-type <a href=":entity_url">%label</a> (@language) and the <a href=":original_url">%original_label</a> (@original_language) original language have been restored from trash.', [
+          '@entity-type' => $entity->getEntityType()->getSingularLabel(),
+          '%label' => $entity->label() ?? $entity->id(),
+          ':entity_url' => $entity->toUrl()->toString(),
+          '@language' => $entity->language()->getName(),
+          '%original_label' => $untranslated->label(),
+          '@original_language' => $untranslated->language()->getName(),
+          ':original_url' => $untranslated->toUrl()->toString(),
+        ]);
+      }
+    }
 
+    trash_restore_entity($entity, $langcodes);
+    $form_state->setRedirectUrl($this->getRedirectUrl());
     $this->messenger()->addStatus($message);
+
+    // @todo Change log message if only a translation was restored.
     $this->getLogger('trash')->info('@entity-type (@bundle): restored %label.', [
       '@entity-type' => $entity->getEntityType()->getLabel(),
       '@bundle' => $this->entityTypeBundleInfo->getBundleInfo($entity->getEntityTypeId())[$entity->bundle()]['label'],
diff --git a/src/Routing/RouteSubscriber.php b/src/Routing/RouteSubscriber.php
index f2906126745c66ecd76e89e513783b334591a510..fc7c9aeea8d99d6c6051fb48ba9d2f43b105af29 100644
--- a/src/Routing/RouteSubscriber.php
+++ b/src/Routing/RouteSubscriber.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
 namespace Drupal\trash\Routing;
 
 use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Routing\RouteSubscriberBase;
 use Drupal\Core\Routing\RoutingEvents;
 use Drupal\trash\TrashManagerInterface;
@@ -19,6 +20,7 @@ class RouteSubscriber extends RouteSubscriberBase {
   public function __construct(
     protected EntityTypeManagerInterface $entityTypeManager,
     protected TrashManagerInterface $trashManager,
+    protected LanguageManagerInterface $languageManager,
   ) {}
 
   /**
@@ -27,13 +29,6 @@ class RouteSubscriber extends RouteSubscriberBase {
   protected function alterRoutes(RouteCollection $collection): void {
     foreach ($this->entityTypeManager->getDefinitions() as $entity_type_id => $entity_type) {
       if ($this->trashManager->isEntityTypeEnabled($entity_type)) {
-        if ($entity_type->hasLinkTemplate('canonical')) {
-          $base_path = $entity_type->getLinkTemplate('canonical');
-        }
-        else {
-          $base_path = "/admin/content/trash/$entity_type_id/{" . $entity_type_id . '}';
-        }
-
         $parameters = [
           $entity_type_id => [
             'type' => "entity:$entity_type_id",
@@ -41,30 +36,76 @@ class RouteSubscriber extends RouteSubscriberBase {
         ];
 
         // Add a route for the restore form.
-        $route = new Route($base_path . '/restore');
+        $route = new Route($entity_type->getLinkTemplate('restore'));
         $route
           ->addDefaults([
             '_entity_form' => "{$entity_type_id}.restore",
             'entity_type_id' => $entity_type_id,
           ])
-          ->setRequirement('_entity_access', "{$entity_type_id}.restore")
+          ->setRequirements([
+            '_trash_access' => 'restore',
+          ])
           ->setOption('parameters', $parameters)
           ->setOption('_admin_route', TRUE)
           ->setOption('_trash_route', TRUE);
         $collection->add("entity.$entity_type_id.restore", $route);
 
         // Add a route for the purge form.
-        $route = new Route($base_path . '/purge');
+        $route = new Route($entity_type->getLinkTemplate('purge'));
         $route
           ->addDefaults([
             '_entity_form' => "{$entity_type_id}.purge",
             'entity_type_id' => $entity_type_id,
           ])
-          ->setRequirement('_entity_access', "{$entity_type_id}.purge")
+          ->setRequirements([
+            '_trash_access' => 'purge',
+          ])
           ->setOption('parameters', $parameters)
           ->setOption('_admin_route', TRUE)
           ->setOption('_trash_route', TRUE);
         $collection->add("entity.$entity_type_id.purge", $route);
+
+        $translation_parameters = [
+          'language' => [
+            'type' => 'language',
+          ],
+        ] + $parameters;
+
+        if ($entity_type->hasLinkTemplate('restore-translation')) {
+          // Add a route for the restore form.
+          $route = new Route($entity_type->getLinkTemplate('restore-translation'));
+          $route
+            ->addDefaults([
+              '_entity_form' => "{$entity_type_id}.restore",
+              'entity_type_id' => $entity_type_id,
+              'language' => NULL,
+            ])
+            ->setRequirements([
+              '_trash_access' => 'restore-translation',
+            ])
+            ->setOption('parameters', $translation_parameters)
+            ->setOption('_admin_route', TRUE)
+            ->setOption('_trash_route', TRUE);
+          $collection->add("entity.$entity_type_id.restore_translation", $route);
+        }
+
+        if ($entity_type->hasLinkTemplate('purge-translation')) {
+          // Add a route for the purge form.
+          $route = new Route($entity_type->getLinkTemplate('purge-translation'));
+          $route
+            ->addDefaults([
+              '_entity_form' => "{$entity_type_id}.purge",
+              'entity_type_id' => $entity_type_id,
+              'language' => NULL,
+            ])
+            ->setRequirements([
+              '_trash_access' => 'purge-translation',
+            ])
+            ->setOption('parameters', $translation_parameters)
+            ->setOption('_admin_route', TRUE)
+            ->setOption('_trash_route', TRUE);
+          $collection->add("entity.$entity_type_id.purge_translation", $route);
+        }
       }
     }
   }
diff --git a/src/TrashManager.php b/src/TrashManager.php
index dba305f1ddfe11df0d4a5cfce2fc9f9ba3db1989..9535cbeb412413c618df0c28ccafd4aca585926a 100644
--- a/src/TrashManager.php
+++ b/src/TrashManager.php
@@ -89,7 +89,7 @@ class TrashManager implements TrashManagerInterface {
       ->setLabel(t('Deleted'))
       ->setDescription(t('Time when the item got deleted'))
       ->setInternal(TRUE)
-      ->setTranslatable(FALSE)
+      ->setTranslatable(TRUE)
       ->setRevisionable(TRUE);
 
     $this->entityDefinitionUpdateManager->installFieldStorageDefinition('deleted', $entity_type->id(), 'trash', $storage_definition);
diff --git a/src/TrashStorageTrait.php b/src/TrashStorageTrait.php
index 680bf2062809e1f4918b62a872cc21f29428356c..9774c1f5faf8fc945acadbfd3545d115808ad03a 100644
--- a/src/TrashStorageTrait.php
+++ b/src/TrashStorageTrait.php
@@ -2,7 +2,13 @@
 
 namespace Drupal\trash;
 
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityStorageException;
 use Drupal\Core\Entity\RevisionLogInterface;
+use Drupal\Core\Entity\TranslatableInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\TypedData\TranslationStatusInterface;
+use Drupal\Core\Utility\Error;
 use Drupal\workspaces\WorkspaceInformationInterface;
 use Drupal\workspaces\WorkspaceManagerInterface;
 
@@ -15,7 +21,8 @@ trait TrashStorageTrait {
    * {@inheritdoc}
    */
   public function delete(array $entities) {
-    if ($this->getTrashManager()->getTrashContext() !== 'active') {
+    /** @var \Drupal\Core\Entity\ContentEntityInterface[] $entities */
+    if (!$entities || $this->getTrashManager()->getTrashContext() !== 'active') {
       parent::delete($entities);
       return;
     }
@@ -35,69 +42,127 @@ trait TrashStorageTrait {
     parent::delete($to_delete);
 
     $field_name = 'deleted';
+    $request_time = \Drupal::time()->getRequestTime();
     $revisionable = $this->getEntityType()->isRevisionable();
 
-    foreach ($to_trash as $entity) {
-      // Allow code to run before soft-deleting.
-      $this->getTrashManager()->getHandler($this->entityTypeId)->preTrashDelete($entity);
-      $this->invokeHook('pre_trash_delete', $entity);
+    try {
+      $transaction = $this->database->startTransaction();
+
+      foreach ($to_trash as $entity) {
+        // Allow code to run before soft-deleting.
+        $this->getTrashManager()->getHandler($this->entityTypeId)->preTrashDelete($entity);
+        $this->invokeHook('pre_trash_delete', $entity);
 
-      $entity->set($field_name, \Drupal::time()->getRequestTime());
+        foreach (array_keys($entity->getTranslationLanguages()) as $langcode) {
+          $entity->getTranslation($langcode)->set($field_name, $request_time);
+        }
 
-      // Always create a new revision if the entity type is revisionable.
-      if ($revisionable) {
-        /** @var \Drupal\Core\Entity\RevisionableInterface $entity */
-        $entity->setNewRevision(TRUE);
+        // Always create a new revision if the entity type is revisionable.
+        if ($revisionable) {
+          /** @var \Drupal\Core\Entity\RevisionableInterface $entity */
+          $entity->setNewRevision(TRUE);
 
-        if ($entity instanceof RevisionLogInterface) {
-          $entity->setRevisionUserId(\Drupal::currentUser()->id());
-          $entity->setRevisionCreationTime(\Drupal::time()->getRequestTime());
+          if ($entity instanceof RevisionLogInterface) {
+            $entity->setRevisionUserId(\Drupal::currentUser()->id());
+            $entity->setRevisionCreationTime(\Drupal::time()->getRequestTime());
+          }
         }
-      }
-      $entity->save();
 
-      // Allow code to run after soft-deleting.
-      $this->getTrashManager()->getHandler($this->entityTypeId)->postTrashDelete($entity);
-      $this->invokeHook('trash_delete', $entity);
+        $entity->setSyncing(TRUE)->save();
+
+        // Allow code to run after soft-deleting.
+        $this->getTrashManager()->getHandler($this->entityTypeId)->postTrashDelete($entity);
+        $this->invokeHook('trash_delete', $entity);
+      }
+    }
+    catch (\Exception $e) {
+      if (isset($transaction)) {
+        $transaction->rollBack();
+      }
+      Error::logException(\Drupal::logger($this->entityTypeId), $e);
+      throw new EntityStorageException($e->getMessage(), $e->getCode(), $e);
     }
   }
 
   /**
    * Restores soft-deleted entities.
    *
-   * @param array $entities
+   * @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
    *   An array of entity objects to restore.
+   * @param string[] $langcodes
+   *   The translations to restore, defaults to all.
    *
    * @throws \Drupal\Core\Entity\EntityStorageException
    *   In case of failures, an exception is thrown.
    */
-  public function restoreFromTrash(array $entities) {
-    $field_name = 'deleted';
-    $revisionable = $this->getEntityType()->isRevisionable();
-
-    foreach ($entities as $entity) {
-      // Allow code to run before restoring from trash.
-      $this->getTrashManager()->getHandler($this->entityTypeId)->preTrashRestore($entity);
-      $this->invokeHook('pre_trash_restore', $entity);
-
-      $entity->set($field_name, NULL);
-
-      // Always create a new revision if the entity type is revisionable.
-      if ($revisionable) {
-        /** @var \Drupal\Core\Entity\RevisionableInterface $entity */
-        $entity->setNewRevision(TRUE);
-
-        if ($entity instanceof RevisionLogInterface) {
-          $entity->setRevisionUserId(\Drupal::currentUser()->id());
-          $entity->setRevisionCreationTime(\Drupal::time()->getRequestTime());
-        }
+  public function restoreFromTrash(array $entities, array $langcodes = []) {
+    try {
+      $transaction = $this->database->startTransaction();
+      // Need to be able to load trashed entities or loadUnchanged() will fail.
+      $this->getTrashManager()
+        ->executeInTrashContext('ignore', function () use ($entities, $langcodes) {
+          $field_name = 'deleted';
+          foreach ($entities as $entity) {
+            // Allow code to run before restoring from trash.
+            $this->getTrashManager()->getHandler($this->entityTypeId)->preTrashRestore($entity);
+            $this->invokeHook('pre_trash_restore', $entity);
+
+            $translation_langcodes = $langcodes ?: array_keys($entity->getTranslationLanguages());
+            foreach ($translation_langcodes as $langcode) {
+              if ($entity->hasTranslation($langcode)) {
+                $entity->getTranslation($langcode)->set($field_name, NULL);
+              }
+
+              if ($entity instanceof RevisionLogInterface) {
+                $entity->setRevisionUserId(\Drupal::currentUser()->id());
+                $entity->setRevisionCreationTime(\Drupal::time()->getRequestTime());
+              }
+            }
+            $entity->setSyncing(TRUE)->save();
+
+            // Allow code to run after restoring from trash.
+            $this->getTrashManager()->getHandler($this->entityTypeId)->postTrashRestore($entity);
+            $this->invokeHook('trash_restore', $entity);
+          }
+        });
+    }
+    catch (\Exception $e) {
+      if (isset($transaction)) {
+        $transaction->rollBack();
       }
-      $entity->save();
+      Error::logException(\Drupal::logger($this->entityTypeId), $e);
+      throw new EntityStorageException($e->getMessage(), $e->getCode(), $e);
+    }
+  }
 
-      // Allow code to run after restoring from trash.
-      $this->getTrashManager()->getHandler($this->entityTypeId)->postTrashRestore($entity);
-      $this->invokeHook('trash_restore', $entity);
+  /**
+   * {@inheritdoc}
+   */
+  protected function doPreSave(EntityInterface $entity) {
+    if ($entity->isNew() || $this->getTrashManager()->getTrashContext() !== 'active') {
+      return parent::doPreSave($entity);
     }
+    // Need to be able to load trashed entities or loadUnchanged() will fail.
+    $this->getTrashManager()->executeInTrashContext('ignore', function () use ($entity) {
+      if ($entity instanceof TranslatableInterface && $entity instanceof TranslationStatusInterface) {
+        if (!isset($entity->original)) {
+          $entity->original = $this->loadUnchanged($entity->id());
+        }
+        $field_name = 'deleted';
+        // Check if any translation was removed before saving.
+        foreach (array_keys($this->getLanguageManager()
+          ->getLanguages()) as $langcode) {
+          if ($entity->getTranslationStatus($langcode) === TranslationStatusInterface::TRANSLATION_REMOVED) {
+            // Restore the removed translation and mark it deleted.
+            $entity->addTranslation($langcode, $entity->original->getTranslation($langcode)
+              ->toArray());
+            $entity->getTranslation($langcode)->set($field_name, \Drupal::time()
+              ->getRequestTime());
+          }
+        }
+      }
+    });
+    return parent::doPreSave($entity);
   }
 
   /**
@@ -196,6 +261,13 @@ trait TrashStorageTrait {
     return \Drupal::service('trash.manager');
   }
 
+  /**
+   * Gets the language manager.
+   */
+  private function getLanguageManager(): LanguageManagerInterface {
+    return \Drupal::service('language_manager');
+  }
+
   /**
    * Gets the workspace manager service.
    */
diff --git a/tests/modules/trash_test/config/optional/views.view.trash_test_view.yml b/tests/modules/trash_test/config/optional/views.view.trash_test_view.yml
index 4e3cc3a37b7eddbab454cb9efbf5c2526359a839..1b19efb31aa0949a4b5b7c2f4fb5a89a6ac3f08e 100644
--- a/tests/modules/trash_test/config/optional/views.view.trash_test_view.yml
+++ b/tests/modules/trash_test/config/optional/views.view.trash_test_view.yml
@@ -8,7 +8,7 @@ label: trash_test_view
 module: views
 description: 'Various test views'
 tag: ''
-base_table: trash_test
+base_table: trash_test_field_data
 base_field: id
 core: 8.x
 display:
@@ -67,7 +67,7 @@ display:
       fields:
         id:
           id: id
-          table: trash_test
+          table: trash_test_field_data
           field: id
           relationship: none
           group_type: group
@@ -135,7 +135,7 @@ display:
       sorts:
         id:
           id: id
-          table: trash_test
+          table: trash_test_field_data
           field: id
           relationship: none
           group_type: group
@@ -192,15 +192,17 @@ display:
       filters:
         Deleted:
           id: Deleted
-          table: trash_test
+          table: trash_test_field_data
           field: Deleted
           relationship: none
           group_type: group
           admin_label: ''
-          operator: '>'
+          operator: '<='
           value:
-            value: '0'
-            type: 'date'
+            min: ''
+            max: ''
+            value: now
+            type: offset
           group: 1
           exposed: false
           expose:
@@ -209,12 +211,17 @@ display:
             description: ''
             use_operator: false
             operator: ''
+            operator_limit_selection: false
+            operator_list: {  }
             identifier: ''
             required: false
             remember: false
             multiple: false
             remember_roles:
               authenticated: authenticated
+            min_placeholder: ''
+            max_placeholder: ''
+            placeholder: ''
           is_grouped: false
           group_info:
             label: ''
diff --git a/tests/modules/trash_test/src/Entity/TrashTestEntity.php b/tests/modules/trash_test/src/Entity/TrashTestEntity.php
index fbb75bc68be6931f8a4c60301d91aa15887464f3..9e9a11d5b564549b84ffab141fd9e128f452e252 100644
--- a/tests/modules/trash_test/src/Entity/TrashTestEntity.php
+++ b/tests/modules/trash_test/src/Entity/TrashTestEntity.php
@@ -3,6 +3,8 @@
 namespace Drupal\trash_test\Entity;
 
 use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Field\BaseFieldDefinition;
 
 /**
  * Provides a trash test entity.
@@ -25,11 +27,16 @@ use Drupal\Core\Entity\ContentEntityBase;
  *   },
  *   base_table = "trash_test",
  *   revision_table = "trash_test_revision",
+ *   data_table = "trash_test_field_data",
+ *   revision_table = "trash_test_revision",
+ *   revision_data_table = "trash_test_field_revision",
+ *   translatable = TRUE,
  *   entity_keys = {
  *     "id" = "id",
  *     "revision" = "revision",
  *     "label" = "label",
  *     "uuid" = "uuid",
+ *     "langcode" = "langcode",
  *   },
  *   links = {
  *     "canonical" = "/trash_test/{trash_test}",
@@ -40,4 +47,18 @@ use Drupal\Core\Entity\ContentEntityBase;
  */
 class TrashTestEntity extends ContentEntityBase {
 
+  /**
+   * {@inheritdoc}
+   */
+  public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
+    $fields = parent::baseFieldDefinitions($entity_type);
+    $fields['label'] = BaseFieldDefinition::create('string')
+      ->setLabel('Label')
+      ->setRequired(TRUE)
+      ->setTranslatable(TRUE)
+      ->setRevisionable(TRUE)
+      ->setSetting('max_length', 255);
+    return $fields;
+  }
+
 }
diff --git a/tests/src/Functional/TrashMultilingualNodeTest.php b/tests/src/Functional/TrashMultilingualNodeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..44cae8cebf9f7e0f3fc5ef899e22eee188cbe806
--- /dev/null
+++ b/tests/src/Functional/TrashMultilingualNodeTest.php
@@ -0,0 +1,550 @@
+<?php
+
+namespace Drupal\Tests\trash\Functional;
+
+use Drupal\Component\Render\FormattableMarkup;
+use Drupal\Tests\BrowserTestBase;
+use Drupal\language\Entity\ContentLanguageSettings;
+use Drupal\node\NodeInterface;
+
+/**
+ * Tests the basic trash functionality on multilingual nodes.
+ *
+ * @group trash
+ */
+class TrashMultilingualNodeTest extends BrowserTestBase {
+
+  /**
+   * A user with permission to trash content but not restoring.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $webUser;
+
+  /**
+   * A user with permission to trash, restore and purge the trash bin.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $adminUser;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['block', 'node', 'trash', 'language'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    // Create Basic page node type.
+    if ($this->profile != 'standard') {
+      $this->drupalCreateContentType([
+        'type' => 'page',
+        'name' => 'Basic Page',
+        'display_submitted' => FALSE,
+      ]);
+    }
+
+    $this->webUser = $this->drupalCreateUser([
+      'access content',
+      'create page content',
+      'edit own page content',
+      'delete own page content',
+      'access trash',
+    ]);
+    $this->adminUser = $this->drupalCreateUser([
+      'access content',
+      'create page content',
+      'edit own page content',
+      'delete own page content',
+      'edit any page content',
+      'delete any page content',
+      'administer nodes',
+      'bypass node access',
+      'access trash',
+      'view deleted entities',
+      'purge node entities',
+      'restore node entities',
+      'administer content types',
+      'administer languages',
+      'delete any page content',
+    ]);
+    $this->drupalLogin($this->adminUser);
+    $this->addLanguage('sv');
+    $this->addLanguage('de');
+    $this->drupalPlaceBlock('local_tasks_block', ['id' => 'page_tabs_block']);
+    $this->drupalPlaceBlock('local_actions_block', ['id' => 'page_actions_block']);
+  }
+
+  /**
+   * Test moving a node to the trash bin and restoring it.
+   *
+   * @dataProvider languageProvider
+   */
+  public function testTrashAndRestoreNode(string $langcode) {
+    // Login as a regular user.
+    $this->drupalLogin($this->webUser);
+
+    // Create "Basic page" content with title.
+    $settings = [
+      'title' => $this->randomMachineName(8),
+      'langcode' => $langcode,
+    ];
+    $node = $this->drupalCreateNode($settings);
+
+    // Load the node edit form.
+    $this->drupalGet('node/' . $node->id() . '/edit');
+
+    // Make sure the task is there.
+    $this->assertSession()->linkExists('Delete');
+
+    // Now edit the same node as an admin user.
+    $this->drupalLogin($this->adminUser);
+    $this->drupalGet('node/' . $node->id() . '/edit');
+
+    // Make sure we can move to the trash bin.
+    $this->assertSession()->linkExists('Delete');
+
+    // Make sure the link works as expected.
+    $this->clickLink('Delete');
+    $this->assertSession()->addressEquals('node/1/delete');
+
+    $this->assertSession()->pageTextContains('Deleting this content item will move it to the trash. You can restore it from the trash at a later date if necessary.');
+    $this->submitForm([], 'Delete');
+
+    // The content has been moved to the trash.
+    $this->assertSession()->statusMessageContains('The Basic Page ' . $node->getTitle() . ' has been deleted', 'status');
+    // I can see it in the trash context.
+    $this->drupalGet('node/1', ['query' => ['in_trash' => 1]]);
+
+    $this->assertSession()->elementExists('css', 'article.is-deleted');
+
+    // I can't see the node anymore with a regular editor.
+    $this->drupalLogin($this->webUser);
+    $this->drupalGet('node/' . $node->id());
+    $this->assertSession()->statusCodeEquals(404);
+
+    // I can restore the content.
+    $this->drupalLogin($this->adminUser);
+    $this->drupalGet('/admin/content/trash');
+    $this->assertSession()->linkExists('Restore');
+    $this->clickLink('Restore');
+    $this->assertSession()->addressEquals('/node/' . $node->id() . '/restore');
+    $this->submitForm([], 'Confirm');
+    $this->assertSession()->statusMessageContains('The content item ' . $node->getTitle() . ' has been restored from trash.', 'status');
+  }
+
+  /**
+   * Test moving a node to the trash bin and purging it.
+   *
+   * @dataProvider languageProvider
+   */
+  public function testPurgingNode(string $langcode) {
+    // Login as a privileged user.
+    $this->drupalLogin($this->adminUser);
+
+    // Create "Basic page" content with title.
+    $settings = [
+      'title' => $this->randomMachineName(8),
+      'langcode' => $langcode,
+    ];
+    $node = $this->drupalCreateNode($settings);
+
+    // Load the node edit form.
+    $this->drupalGet('node/' . $node->id() . '/edit');
+    $this->clickLink('Delete');
+    $this->submitForm([], 'Delete');
+
+    // The content has been moved to the trash.
+    $this->assertSession()->statusMessageContains('The Basic Page ' . $node->getTitle() . ' has been deleted', 'status');
+
+    // Make sure we can Purge.
+    $this->drupalGet('/admin/content/trash');
+    $this->assertSession()->linkExists('Purge');
+    $this->clickLink('Purge');
+    $this->assertSession()->addressEquals('/node/' . $node->id() . '/purge');
+    $this->assertSession()->pageTextContains('This action cannot be undone.');
+    $this->submitForm([], 'Confirm');
+    $this->assertSession()->statusMessageContains('The content item ' . $node->getTitle() . ' has been permanently deleted.', 'status');
+  }
+
+  /**
+   * Data provider for Content Translation module status.
+   *
+   *  Content Translation really only adds a GUI to perform translation tasks,
+   *  as entities may have translations as soon as Language module is enabled.
+   *  The difference is whether interactive translation is presented to the user
+   *  or not, and Trash should function even if translations were added
+   *  programmatically, or Content Translation was disabled after adding some.
+   *
+   * @return array[]
+   *   A data array keyed by language names holding arrays of the corresponding
+   *   language code argument.
+   */
+  public static function contentTranslationEnabledProvider(): array {
+    return [
+      'With Content Translation' => [TRUE],
+      'Without Content Translation' => [FALSE],
+    ];
+  }
+
+  /**
+   * Test moving a node and translations to the trash bin and restoring them.
+   *
+   * @dataProvider contentTranslationEnabledProvider
+   */
+  public function testTrashAndRestoreMultiNode(bool $enable_content_translation) {
+    if ($enable_content_translation) {
+      $this->enableContentTranslation();
+    }
+    // Login as a regular editor.
+    $this->drupalLogin($this->webUser);
+
+    // Create "Basic page" content with translated titles.
+    $english_title = $this->randomMachineName(8);
+    $swedish_title = $this->randomMachineName(8);
+    $german_title = $this->randomMachineName(8);
+    $settings = [
+      'title' => $english_title,
+      'langcode' => 'en',
+    ];
+    $node = $this->drupalCreateNode($settings);
+
+    // Load the Swedish node add translation form.
+    $nid = $node->id();
+    $this->addTranslation($node, 'en', 'sv', $swedish_title);
+
+    // Load the German node add translation form.
+    $this->addTranslation($node, 'en', 'de', $german_title);
+
+    // Load the English form.
+    $this->drupalGet("/node/$nid/edit");
+    // Make sure we can move all translations to the trash bin in one go.
+    $this->clickLink('Delete');
+    $this->assertSession()->addressEquals("/node/$nid/delete");
+    $this->assertSession()->pageTextContains('Deleting this content item will move it to the trash.');
+    $this->assertSession()->pageTextContains("The following content item translations will be deleted:EnglishSwedishGerman");
+    $this->submitForm([], 'Delete all translations');
+    // The content has been moved to the trash.
+    $this->assertSession()->statusMessageContains('The Basic Page ' . $node->getTitle() . ' has been deleted', 'status');
+
+    // The regular editor is no longer able to access any translation.
+    $this->drupalGet("/node/$nid");
+    // Access is 404 because the node "does not exist".
+    $this->assertSession()->statusCodeEquals(404);
+    $this->drupalGet("/sv/node/$nid");
+    $this->assertSession()->statusCodeEquals(404);
+    $this->drupalGet("/de/node/$nid");
+    $this->assertSession()->statusCodeEquals(404);
+
+    // The editor cannot restore any translations because the source is deleted,
+    // and they do not have access to restore it.
+    $this->drupalGet('/admin/content/trash');
+    // Make some positive assertions before checking links do not exist.
+    $this->assertSession()->pageTextContains($english_title);
+    $this->assertSession()->pageTextContains($swedish_title);
+    $this->assertSession()->pageTextContains($german_title);
+    // This is a "contains()" check so covers all translations.
+    $this->assertSession()->linkByHrefNotExists("/node/$nid/restore");
+
+    // The admin can see all translations in the trash context.
+    $this->drupalLogin($this->adminUser);
+    $this->drupalGet("/node/$nid", ['query' => ['in_trash' => 1]]);
+    $this->assertSession()->pageTextContains($english_title);
+    $this->assertSession()->elementExists('css', 'article.is-deleted');
+    $this->drupalGet("/sv/node/$nid", ['query' => ['in_trash' => 1]]);
+    $this->assertSession()->pageTextContains($swedish_title);
+    $this->assertSession()->elementExists('css', 'article.is-deleted');
+    $this->drupalGet("/de/node/$nid", ['query' => ['in_trash' => 1]]);
+    $this->assertSession()->pageTextContains($german_title);
+    $this->assertSession()->elementExists('css', 'article.is-deleted');
+
+    // The admin can restore the main node and translations.
+    $this->drupalGet('/admin/content/trash');
+    $base_path = base_path();
+    $this->assertSession()->linkByHrefExistsExact("{$base_path}node/$nid/restore?destination={$base_path}admin/content/trash");
+    $this->assertSession()->linkByHrefExists("/node/$nid/restore/sv");
+    $this->assertSession()->linkByHrefExists("/node/$nid/restore/de");
+
+    // Check that attempting to restore Swedish will also restore English.
+    $this->drupalGet("/node/$nid/restore/sv");
+    $this->assertSession()->pageTextContains('Restoring the Swedish translation will also restore the English original language.');
+    // Same for German.
+    $this->drupalGet("/node/$nid/restore/de");
+    $this->assertSession()->pageTextContains('Restoring the German translation will also restore the English original language.');
+    // Restore only the English original language.
+    $this->drupalGet("/node/$nid/restore");
+    $this->submitForm([
+      'translations[sv]' => FALSE,
+      'translations[de]' => FALSE,
+    ], 'Confirm');
+    $this->assertSession()->statusMessageContains("The content item $english_title has been restored from trash.", 'status');
+
+    // The regular editor can now restore translations from the trashcan page.
+    $this->drupalLogin($this->webUser);
+    $this->drupalGet('/admin/content/trash');
+    $this->assertSession()->linkByHrefExists("/node/$nid/restore/sv");
+    $this->assertSession()->linkByHrefExists("/node/$nid/restore/de");
+
+    if ($enable_content_translation) {
+      // Also verify they can do it from the translation overview page.
+      $this->drupalGet("/node/$nid/translations");
+      $this->assertSession()->linkByHrefExists("/node/$nid/restore/sv");
+      $this->assertSession()->linkByHrefExists("/node/$nid/restore/de");
+      $this->assertSession()->pageTextContains("English (Original language) $english_title Published");
+      $this->assertSession()->pageTextContains("Swedish $swedish_title Deleted");
+      $this->assertSession()->pageTextContains("German $german_title Deleted");
+    }
+
+    // Restore the German translation.
+    $this->drupalGet("/node/$nid/restore/de");
+    $this->submitForm([], 'Confirm');
+    $this->assertSession()->statusMessageContains("The content item $german_title has been restored from trash.", 'status');
+
+    // The regular editor can again see the German translation.
+    $this->drupalGet("/de/node/$nid");
+    $this->assertSession()->statusCodeEquals(200);
+
+    // Delete the English and German translations again.
+    $this->drupalGet("node/$nid/delete");
+    $this->submitForm([], 'Delete all translations');
+    $this->assertSession()->statusMessageContains("The Basic Page $english_title has been deleted.", 'status');
+
+    // The admin can restore a translation and the original language at once.
+    $this->drupalLogin($this->adminUser);
+    $this->drupalGet("/node/$nid/restore/sv");
+    $this->assertSession()->pageTextContains('Restoring the Swedish translation will also restore the English original language.');
+    $this->submitForm([], 'Confirm');
+    $this->assertSession()->statusMessageContains("The content item $swedish_title (Swedish) and the $english_title (English) original language have been restored from trash.", 'status');
+
+    // Switch back to the editor.
+    $this->drupalLogin($this->webUser);
+    // Verify English and Swedish are visible and German is still inaccessible.
+    $this->drupalGet("/node/$nid");
+    $this->assertSession()->statusCodeEquals(200);
+    $this->drupalGet("/sv/node/$nid");
+    $this->assertSession()->statusCodeEquals(200);
+    $this->drupalGet("/de/node/$nid");
+    $this->assertSession()->statusCodeEquals(403);
+
+    // Switch back to the Admin and delete all translations.
+    $this->drupalLogin($this->adminUser);
+    $this->drupalGet("/node/$nid/delete");
+    $this->submitForm([], 'Delete all translations');
+    // Restore the English original language and only the German translation.
+    $this->drupalGet("/node/$nid/restore");
+    $this->submitForm([
+      'translations[sv]' => FALSE,
+      'translations[de]' => TRUE,
+    ], 'Confirm');
+
+    // Verify the Swedish translation is still deleted.
+    $this->drupalGet('/admin/content/trash');
+    $this->assertSession()->linkByHrefExists("/node/$nid/restore/sv");
+  }
+
+  /**
+   * Test moving a node and translations to the trash bin and purging them.
+   *
+   * @dataProvider contentTranslationEnabledProvider
+   */
+  public function testPurgingMultiNode(bool $enable_content_translation) {
+    if ($enable_content_translation) {
+      $this->enableContentTranslation();
+    }
+    // Login as a regular editor.
+    $this->drupalLogin($this->webUser);
+
+    // Create "Basic page" content with translated titles.
+    $english_title = $this->randomMachineName(8);
+    $swedish_title = $this->randomMachineName(8);
+    $german_title = $this->randomMachineName(8);
+    $settings = [
+      'title' => $english_title,
+      'langcode' => 'en',
+    ];
+    $node = $this->drupalCreateNode($settings);
+
+    // Load the Swedish node add translation form.
+    $nid = $node->id();
+    $this->addTranslation($node, 'en', 'sv', $swedish_title);
+
+    // Load the German node add translation form.
+    $this->addTranslation($node, 'en', 'de', $german_title);
+
+    // Delete the node.
+    $this->drupalGet("/node/$nid/delete");
+    $this->submitForm([], 'Delete all translations');
+
+    // The editor may purge translations but not the original.
+    $this->drupalGet('/admin/content/trash');
+    $this->assertSession()->pageTextContains($english_title);
+    $this->assertSession()->pageTextContains($swedish_title);
+    $this->assertSession()->pageTextContains($german_title);
+    $base_path = base_path();
+    $this->assertSession()->linkByHrefNotExistsExact("{$base_path}node/$nid/purge?destination={$base_path}admin/content/trash");
+    $this->assertSession()->linkByHrefExists("/node/$nid/purge/sv");
+    $this->assertSession()->linkByHrefExists("/node/$nid/purge/de");
+    if ($enable_content_translation) {
+      // The translations overview is disabled because the original is deleted.
+      $this->drupalGet("/node/$nid/translations");
+      $this->assertSession()->statusCodeEquals(404);
+    }
+
+    // The admin may purge all the translations.
+    $this->drupalLogin($this->adminUser);
+    $this->drupalGet('/admin/content/trash');
+    $this->assertSession()->linkByHrefExists("/node/$nid/purge");
+    $this->assertSession()->linkByHrefExists("/node/$nid/purge/sv");
+    $this->assertSession()->linkByHrefExists("/node/$nid/purge/de");
+
+    // Restore the English original language.
+    $this->assertSession()->linkByHrefExists("/node/$nid/restore");
+    $this->drupalGet("/node/$nid/restore");
+    $this->submitForm([
+      'translations[sv]' => FALSE,
+      'translations[de]' => FALSE,
+    ], 'Confirm');
+
+    // The editor may now purge the translations.
+    $this->drupalLogin($this->webUser);
+    $this->drupalGet('/admin/content/trash');
+    $this->assertSession()->linkByHrefExists("/node/$nid/purge/sv");
+    $this->assertSession()->linkByHrefExists("/node/$nid/purge/de");
+    if ($enable_content_translation) {
+      // Also verify they can do it from the translation overview page.
+      $this->drupalGet("/node/$nid/translations");
+      $this->assertSession()->linkByHrefExists("/node/$nid/purge/sv");
+      $this->assertSession()->linkByHrefExists("/node/$nid/purge/de");
+      $this->assertSession()->pageTextContains("Swedish $swedish_title Deleted");
+      $this->assertSession()->pageTextContains("German $german_title Deleted");
+    }
+
+    // Purge the Swedish translation.
+    $this->drupalGet("/node/$nid/purge/sv");
+    $this->assertSession()->pageTextContains("Are you sure you want to permanently delete the Swedish translation of the content item $swedish_title?");
+    $this->submitForm([], 'Delete Swedish translation');
+    // Check that the German translation was not removed.
+    $this->assertSession()->linkByHrefExists("/node/$nid/purge/de");
+    // Check that the Swedish translation can be re-added.
+    if ($enable_content_translation) {
+      // Also verify they can do it from the translation overview page.
+      $this->drupalGet("/node/$nid/translations");
+      $this->assertSession()->linkByHrefExists("/node/$nid/purge/de");
+      $this->assertSession()->pageTextContains("Swedish N/A Not translated");
+      $this->assertSession()->pageTextContains("German $german_title Deleted");
+    }
+    $this->addTranslation($node, 'en', 'sv', $swedish_title);
+  }
+
+  /**
+   * Data provider for language codes.
+   *
+   * @return array[]
+   *   A data array keyed by language names holding arrays of the corresponding
+   *   language code argument.
+   */
+  public static function languageProvider(): array {
+    return [
+      'English' => ['en'],
+      'Swedish' => ['sv'],
+    ];
+  }
+
+  /**
+   * Adds a language.
+   *
+   * @param string $langcode
+   *   The language code of the language to add.
+   */
+  protected function addLanguage($langcode) {
+    $edit = ['predefined_langcode' => $langcode];
+    $this->drupalGet('admin/config/regional/language/add');
+    $this->submitForm($edit, 'Add language');
+    $this->container->get('language_manager')->reset();
+    $this->assertNotEmpty(\Drupal::languageManager()->getLanguage($langcode), new FormattableMarkup('Language %langcode added.', ['%langcode' => $langcode]));
+  }
+
+  /**
+   * Add a translation to a node.
+   *
+   * @param \Drupal\node\NodeInterface $node
+   *   The node to add a translation to.
+   * @param string $source_langcode
+   *   The source langcode.
+   * @param string $langcode
+   *   The new translation langcode.
+   * @param string $title
+   *   The new translation title.
+   */
+  protected function addTranslation(NodeInterface $node, string $source_langcode, string $langcode, string $title) {
+    if ($this->container->get('module_handler')->moduleExists('content_translation')) {
+      $nid = $node->id();
+      $this->drupalGet("/node/$nid/translations/add/$source_langcode/$langcode");
+      $edit = [
+        'Title' => $title,
+      ];
+      $this->submitForm($edit, 'Save (this translation)');
+      $this->assertSession()->pageTextContains("$title has been updated");
+      return;
+    }
+    $node = $this->drupalGetNodeByTitle($node->getUntranslated()->getTitle());
+    $this->assertFalse($node->hasTranslation($langcode), 'Translation does not already exist');
+    $translation = $node->addTranslation($langcode, [
+      'title' => $title,
+    ]);
+    $translation->save();
+    $this->assertSame($title, $node->getTranslation($langcode)
+      ->getTitle(), 'Translation exists');
+  }
+
+  /**
+   * Enable and configure Content Translation module.
+   */
+  protected function enableContentTranslation() {
+    $success = $this->container->get('module_installer')
+      ->install(['content_translation'], TRUE);
+    $this->assertTrue($success, 'Enabled Content Translation');
+
+    ContentLanguageSettings::loadByEntityTypeBundle('node', 'page')
+      ->setDefaultLangcode('en')
+      ->setLanguageAlterable(TRUE)
+      ->setThirdPartySetting('content_translation', 'enabled', TRUE)
+      ->save();
+
+    $editor_roles = $this->webUser->getRoles(TRUE);
+    /** @var \Drupal\user\RoleInterface $role */
+    $role = \Drupal::entityTypeManager()
+      ->getStorage('user_role')
+      ->load(reset($editor_roles));
+    $role->grantPermission('translate editable entities');
+    $role->grantPermission('update content translations');
+    $role->save();
+
+    $admin_roles = $this->adminUser->getRoles(TRUE);
+    /** @var \Drupal\user\RoleInterface $role */
+    $role = \Drupal::entityTypeManager()
+      ->getStorage('user_role')
+      ->load(reset($admin_roles));
+    $role->grantPermission('administer content translation');
+    $role->grantPermission('delete content translations');
+    $role->grantPermission('translate editable entities');
+    $role->save();
+
+    /** @var \Drupal\user\UserStorageInterface $user_storage */
+    $user_storage = $this->container->get('entity_type.manager')
+      ->getStorage('user');
+    $user_storage->resetCache([$this->adminUser->id(), $this->webUser->id()]);
+
+    $this->rebuildAll();
+  }
+
+}
diff --git a/tests/src/Kernel/TrashAccessCheckTest.php b/tests/src/Kernel/TrashAccessCheckTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..5aac5e50426830304828aede263942e774f88d81
--- /dev/null
+++ b/tests/src/Kernel/TrashAccessCheckTest.php
@@ -0,0 +1,350 @@
+<?php
+
+namespace Drupal\Tests\trash\Kernel;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Access\AccessResultForbidden;
+use Drupal\Core\Access\AccessResultInterface;
+use Drupal\Core\Access\AccessResultReasonInterface;
+use Drupal\Core\Routing\RouteMatch;
+use Drupal\KernelTests\KernelTestBase;
+use Drupal\Tests\user\Traits\UserCreationTrait;
+use Drupal\entity_test\Entity\EntityTestMulBundle;
+use Drupal\entity_test\Entity\EntityTestMulWithBundle;
+use Drupal\language\Entity\ConfigurableLanguage;
+use Drupal\language\Entity\ContentLanguageSettings;
+use Drupal\trash\Access\TrashAccessCheck;
+use Drupal\trash\TrashManagerInterface;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Tests TrashAccessCheck.
+ *
+ * @coversDefaultClass \Drupal\trash\Access\TrashAccessCheck
+ * @group trash
+ */
+class TrashAccessCheckTest extends KernelTestBase {
+
+  use UserCreationTrait;
+
+  /**
+   * The trash manager.
+   */
+  protected TrashManagerInterface $trashManager;
+
+  /**
+   * The access checker being tested.
+   */
+  protected TrashAccessCheck $accessCheck;
+
+  /**
+   * The entity type used for the tests.
+   */
+  protected static string $entityTypeId = 'entity_test_mul_with_bundle';
+
+  /**
+   * The entity used for the tests.
+   */
+  protected EntityTestMulWithBundle $entity;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'trash',
+    'entity_test',
+    'text',
+    'language',
+    'user',
+    'system',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $this->installConfig(['system', 'user']);
+    $this->installEntitySchema('user');
+    $this->installEntitySchema(static::$entityTypeId);
+    $this->installEntitySchema('entity_test_mul');
+
+    $this->trashManager = $this->container->get('trash.manager');
+    $this->accessCheck = new TrashAccessCheck($this->container->get('entity_type.manager'), $this->container->get('language_manager'), $this->trashManager);
+
+    ConfigurableLanguage::createFromLangcode('en')->save();
+    ConfigurableLanguage::createFromLangcode('sv')->save();
+
+    EntityTestMulBundle::create([
+      'id' => 'default',
+      'label' => 'Default',
+    ])->save();
+  }
+
+  /**
+   * Provides data for access check tests.
+   */
+  public static function accessArgumentProvider(): array {
+    $entity_type_id = static::$entityTypeId;
+    // The default state has a single deleted entity with one translation.
+    // The default operation checked is "purge" and the current user has no
+    // permissions other than 'access content'.
+    return [
+      'Rejects disabled entity type' => [
+        'expected' => AccessResult::forbidden('Unsupported entity type'),
+        'entity_values' => [
+          'type_enabled' => FALSE,
+        ],
+      ],
+      'Rejects mismatched entity type' => [
+        'expected' => AccessResult::forbidden('Unsupported entity type'),
+        'entity_values' => [
+          'route_entity_type_id' => 'entity_test_mul',
+        ],
+      ],
+      'Rejects missing entity parameter' => [
+        'expected' => AccessResult::forbidden('No entity parameter'),
+        'test_parameters' => [
+          'route_match_entity_exists' => FALSE,
+        ],
+      ],
+      'Cannot restore the original language if not deleted' => [
+        'expected' => AccessResult::forbidden('Entity is not deleted'),
+        'test_parameters' => [
+          'is_deleted' => FALSE,
+          'operation' => 'restore',
+        ],
+      ],
+      'Cannot purge the original language if not deleted' => [
+        'expected' => AccessResult::forbidden('Entity is not deleted'),
+        'test_parameters' => [
+          'is_deleted' => FALSE,
+          'operation' => 'purge',
+        ],
+      ],
+      'Cannot restore a translation which is not deleted' => [
+        'expected' => AccessResult::forbidden('Entity is not deleted'),
+        'test_parameters' => [
+          'is_deleted' => FALSE,
+          'operation' => 'restore-translation',
+          'language_parameter' => 'sv',
+        ],
+      ],
+      'Cannot purge a translation which is not deleted' => [
+        'expected' => AccessResult::forbidden('Entity is not deleted'),
+        'test_parameters' => [
+          'is_deleted' => FALSE,
+          'operation' => 'purge-translation',
+          'language_parameter' => 'sv',
+        ],
+      ],
+      'Cannot purge-translation the original language' => [
+        'expected' => AccessResult::forbidden('Unsupported operation'),
+        'test_parameters' => [
+          'operation' => 'purge-translation',
+        ],
+      ],
+      'Cannot restore-translation the original language' => [
+        'expected' => AccessResult::forbidden('Unsupported operation'),
+        'test_parameters' => [
+          'operation' => 'restore-translation',
+        ],
+      ],
+      'Cannot purge an entity without permission' => [
+        'expected' => AccessResult::neutral("The 'purge {$entity_type_id} entities' permission is required."),
+      ],
+      'Cannot restore an entity without permission' => [
+        'expected' => AccessResult::neutral("The 'purge {$entity_type_id} entities' permission is required."),
+        'test_parameters' => [
+          'operation' => 'restore',
+        ],
+      ],
+      'Can purge an entity with permission' => [
+        'expected' => AccessResult::allowed(),
+        'test_parameters' => [
+          'user_permissions' => [
+            "purge {$entity_type_id} entities",
+          ],
+        ],
+      ],
+      'Can restore an entity with permission' => [
+        'expected' => AccessResult::allowed(),
+        'test_parameters' => [
+          'operation' => 'restore',
+          'user_permissions' => [
+            "restore {$entity_type_id} entities",
+          ],
+        ],
+      ],
+      'Can purge an entity translation with permission' => [
+        'expected' => AccessResult::allowed(),
+        'test_parameters' => [
+          'is_deleted' => FALSE,
+          'language_parameter' => 'sv',
+          'translation_deleted' => TRUE,
+          'operation' => 'purge-translation',
+          'user_permissions' => [
+            "purge {$entity_type_id} entities",
+          ],
+        ],
+      ],
+      'Cannot restore an entity translation without editing permission' => [
+        'expected' => AccessResult::neutral("The 'administer entity_test content' permission is required."),
+        'test_parameters' => [
+          'is_deleted' => FALSE,
+          'language_parameter' => 'sv',
+          'translation_deleted' => TRUE,
+          'operation' => 'restore-translation',
+          'user_permissions' => [
+            // Only give restore access without editing access.
+            "restore {$entity_type_id} entities",
+          ],
+        ],
+      ],
+      'Can restore an entity translation with editing permissions' => [
+        'expected' => AccessResult::allowed(),
+        'test_parameters' => [
+          'is_deleted' => FALSE,
+          'language_parameter' => 'sv',
+          'translation_deleted' => TRUE,
+          'operation' => 'restore-translation',
+          'user_permissions' => [
+            "restore {$entity_type_id} entities",
+            "administer entity_test content",
+          ],
+        ],
+      ],
+      'Can purge an entity translation with translation permission, CT enabled' => [
+        'expected' => AccessResult::allowed(),
+        'test_parameters' => [
+          'is_deleted' => FALSE,
+          'language_parameter' => 'sv',
+          'translation_deleted' => TRUE,
+          'operation' => 'purge-translation',
+          'enable_content_translation' => TRUE,
+          'user_permissions' => [
+            "purge {$entity_type_id} entities",
+          ],
+        ],
+      ],
+      'Can restore an entity translation with translation permission, CT enabled' => [
+        'expected' => AccessResult::allowed(),
+        'test_parameters' => [
+          'is_deleted' => FALSE,
+          'language_parameter' => 'sv',
+          'translation_deleted' => TRUE,
+          'operation' => 'restore-translation',
+          'enable_content_translation' => TRUE,
+          'user_permissions' => [
+            // Content Translation permissions.
+            'translate any entity',
+            'update content translations',
+            "restore {$entity_type_id} entities",
+          ],
+        ],
+      ],
+    ];
+  }
+
+  /**
+   * Tests the access checker.
+   *
+   * @dataProvider accessArgumentProvider
+   */
+  public function testAccess(
+    AccessResultInterface $expected,
+    array $test_parameters = [],
+  ) {
+
+    // Default values most tests will use.
+    $test_parameters += [
+      // If the entity type is enabled in the TrashManager.
+      'type_enabled' => TRUE,
+      // The language parameter in the URL.
+      'language_parameter' => NULL,
+      // If the original language is deleted.
+      'is_deleted' => TRUE,
+      // If the translation is deleted, is_deleted takes priority.
+      'translation_deleted' => FALSE,
+      // The entity_type_id parameter passed in from the URL.
+      'route_entity_type_id' => static::$entityTypeId,
+      // The operation set in the route's _trash_access requirement.
+      'operation' => 'purge',
+      // Any permissions on the current user. Keys permissions, values toggle.
+      'user_permissions' => [],
+      // If the entity was actually found by the entity_type_id route parameter.
+      'route_match_entity_exists' => TRUE,
+      // If to enable content_translation module.
+      'enable_content_translation' => FALSE,
+    ];
+    // Add at least one permission so the user does not become an admin.
+    $test_parameters['user_permissions'][] = 'access content';
+
+    $route = new Route('/test_entity/{' . static::$entityTypeId . '}/{language}', [
+      'language' => NULL,
+      'entity_type_id' => NULL,
+    ], [
+      '_trash_access' => $test_parameters['operation'],
+      static::$entityTypeId => '\d+',
+    ]);
+
+    if ($test_parameters['type_enabled']) {
+      $this->config('trash.settings')->set('enabled_entity_types', [static::$entityTypeId => []])->save();
+    }
+
+    if ($test_parameters['enable_content_translation']) {
+      $this->enableModules(['content_translation']);
+      ContentLanguageSettings::loadByEntityTypeBundle(static::$entityTypeId, 'default')
+        ->setDefaultLangcode('en')
+        ->setLanguageAlterable(TRUE)
+        ->setThirdPartySetting('content_translation', 'enabled', TRUE)
+        ->save();
+      // Reload references from the new container.
+      $this->trashManager = $this->container->get('trash.manager');
+      $this->accessCheck = new TrashAccessCheck($this->container->get('entity_type.manager'), $this->container->get('language_manager'), $this->trashManager);
+    }
+
+    $this->entity = EntityTestMulWithBundle::create([
+      'name' => 'English',
+      'type' => 'default',
+      'language' => 'en',
+    ]);
+    $this->entity->addTranslation('sv', ['name' => 'Swedish translation']);
+    $this->entity->save();
+
+    if ($test_parameters['is_deleted']) {
+      $this->entity->delete();
+    }
+    elseif ($test_parameters['translation_deleted']) {
+      $this->entity->removeTranslation('sv');
+      $this->entity->save();
+    }
+
+    $user = $this->createUser($test_parameters['user_permissions'], 'Editor', FALSE, ['uid' => 2]);
+    $this->setCurrentUser($user);
+
+    // Build arguments for a simulated request.
+    $language_parameter = $test_parameters['language_parameter'] ? $this->container->get('language_manager')->getLanguage($test_parameters['language_parameter']) : NULL;
+    $route_match_raw_parameters = [
+      'language' => $test_parameters['language_parameter'],
+    ];
+    $route_match_parameters = [
+      'language' => $language_parameter,
+    ];
+    if ($test_parameters['route_match_entity_exists']) {
+      $route_match_raw_parameters[static::$entityTypeId] = 1;
+      $route_match_parameters[static::$entityTypeId] = $this->entity;
+    }
+    $entity_type_id = static::$entityTypeId;
+    $route_match = new RouteMatch("entity.{$entity_type_id}.canonical", $route, $route_match_parameters, $route_match_raw_parameters);
+
+    // Execute and verify expectations.
+    $result = $this->accessCheck->access($route, $route_match, $user, $language_parameter, $test_parameters['route_entity_type_id']);
+    $this->assertInstanceOf($expected::class, $result, $result instanceof AccessResultReasonInterface ? $result->getReason() : '');
+    if ($expected instanceof AccessResultForbidden && $result instanceof AccessResultForbidden) {
+      $this->assertEquals($expected->getReason(), $result->getReason());
+    }
+  }
+
+}
diff --git a/tests/src/Kernel/TrashKernelTest.php b/tests/src/Kernel/TrashKernelTest.php
index e9e25ac8b49065f8369e16d5d812dd9a06512574..bcb82072c1ac95ee8dd8709d0ef3a41d178d3eae 100644
--- a/tests/src/Kernel/TrashKernelTest.php
+++ b/tests/src/Kernel/TrashKernelTest.php
@@ -3,6 +3,7 @@
 namespace Drupal\Tests\trash\Kernel;
 
 use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\language\Entity\ConfigurableLanguage;
 use Drupal\node\Entity\Node;
 use Drupal\trash_test\Entity\TrashTestEntity;
 use Drupal\user\Entity\User;
@@ -113,4 +114,111 @@ class TrashKernelTest extends TrashKernelTestBase {
     ];
   }
 
+  /**
+   * Tests deleting and explicitly removing translations.
+   */
+  public function testTranslationDeletion() {
+    $this->enableModules(['user', 'language']);
+    $this->installConfig(['user', 'language']);
+    $this->installEntitySchema('user');
+
+    ConfigurableLanguage::createFromLangcode('sv')->save();
+    ConfigurableLanguage::createFromLangcode('de')->save();
+
+    $english = TrashTestEntity::create([
+      'label' => 'Test 1 EN',
+    ]);
+    $english->addTranslation('sv', [
+      'label' => 'Test 1 SV',
+    ]);
+    $english->addTranslation('de', [
+      'label' => 'Test 1 DE',
+    ]);
+    $english->save();
+    $swedish = $english->getTranslation('sv');
+    $german = $english->getTranslation('de');
+    $this->assertEquals(0, $english->get('deleted')->value);
+    $this->assertEquals(0, $swedish->get('deleted')->value);
+    $this->assertEquals(0, $german->get('deleted')->value);
+    $this->assertEquals('Test 1 EN', $english->label());
+    $this->assertEquals('Test 1 SV', $swedish->label());
+    $this->assertEquals('Test 1 DE', $german->label());
+    $request_time = \Drupal::time()->getRequestTime();
+
+    // German deleted. English and Swedish automatically deleted.
+    $german->delete();
+    $this->assertEquals($request_time, $english->get('deleted')->value);
+    $this->assertEquals($request_time, $german->get('deleted')->value);
+    $this->assertEquals($request_time, $swedish->get('deleted')->value);
+    $this->assertEquals('Test 1 EN', $english->label());
+    $this->assertEquals('Test 1 DE', $german->label());
+    $this->assertEquals('Test 1 SV', $swedish->label());
+    trash_restore_entity($english);
+
+    // All restored.
+    $this->assertEquals(0, $english->get('deleted')->value);
+    $this->assertEquals(0, $german->get('deleted')->value);
+    $this->assertEquals(0, $swedish->get('deleted')->value);
+    $this->assertEquals('Test 1 EN', $english->label());
+    $this->assertEquals('Test 1 DE', $german->label());
+    $this->assertEquals('Test 1 SV', $swedish->label());
+
+    // Swedish deleted. English and German automatically deleted.
+    $swedish->delete();
+    $this->assertEquals($request_time, $english->get('deleted')->value);
+    $this->assertEquals($request_time, $german->get('deleted')->value);
+    $this->assertEquals($request_time, $swedish->get('deleted')->value);
+    $this->assertEquals('Test 1 EN', $english->label());
+    $this->assertEquals('Test 1 DE', $german->label());
+    $this->assertEquals('Test 1 SV', $swedish->label());
+    trash_restore_entity($english);
+
+    // All restored.
+    $english->save();
+    $this->assertEquals(0, $english->get('deleted')->value);
+    $this->assertEquals(0, $german->get('deleted')->value);
+    $this->assertEquals(0, $swedish->get('deleted')->value);
+    $this->assertEquals('Test 1 EN', $english->label());
+    $this->assertEquals('Test 1 DE', $german->label());
+    $this->assertEquals('Test 1 SV', $swedish->label());
+
+    // Swedish removed. German and English available.
+    $english->removeTranslation('sv');
+    $english->save();
+    $this->assertEquals(0, $english->get('deleted')->value);
+    $this->assertEquals(0, $german->get('deleted')->value);
+    $this->assertEquals($request_time, $swedish->get('deleted')->value);
+    $this->assertEquals('Test 1 EN', $english->label());
+    $this->assertEquals('Test 1 DE', $german->label());
+    $this->assertEquals('Test 1 SV', $swedish->label());
+
+    // German and Swedish removed. English available.
+    $english->removeTranslation('de');
+    $english->save();
+    $this->assertEquals(0, $english->get('deleted')->value);
+    $this->assertEquals($request_time, $german->get('deleted')->value);
+    $this->assertEquals($request_time, $swedish->get('deleted')->value);
+    $this->assertEquals('Test 1 EN', $english->label());
+    $this->assertEquals('Test 1 DE', $german->label());
+    $this->assertEquals('Test 1 SV', $swedish->label());
+
+    // All deleted.
+    $english->delete();
+    $this->assertEquals($request_time, $english->get('deleted')->value);
+    $this->assertEquals($request_time, $german->get('deleted')->value);
+    $this->assertEquals($request_time, $swedish->get('deleted')->value);
+    $this->assertEquals('Test 1 EN', $english->label());
+    $this->assertEquals('Test 1 DE', $german->label());
+    $this->assertEquals('Test 1 SV', $swedish->label());
+
+    // English restored. German and Swedish deleted.
+    trash_restore_entity($english, ['en']);
+    $this->assertEquals(0, $english->get('deleted')->value);
+    $this->assertEquals($request_time, $german->get('deleted')->value);
+    $this->assertEquals($request_time, $swedish->get('deleted')->value);
+    $this->assertEquals('Test 1 EN', $english->label());
+    $this->assertEquals('Test 1 DE', $german->label());
+    $this->assertEquals('Test 1 SV', $swedish->label());
+  }
+
 }
diff --git a/trash.install b/trash.install
index 60a03703d08e7003dff331567d2a6bb57e44f2b5..592149de3c41c7eb305c9fef9a71525aa87baa9d 100644
--- a/trash.install
+++ b/trash.install
@@ -31,3 +31,16 @@ function trash_update_10301(): void {
   $config = \Drupal::configFactory()->getEditable('trash.settings');
   $config->set('compact_overview', FALSE)->save();
 }
+
+/**
+ * Add translation support to the Trash module.
+ */
+function trash_update_10302(): void {
+  $update_manager = \Drupal::entityDefinitionUpdateManager();
+  $enabled_entity_types = \Drupal::service('trash.manager')->getEnabledEntityTypes();
+  foreach ($enabled_entity_types as $entity_type_id) {
+    $field_storage_definition = $update_manager->getFieldStorageDefinition('deleted', $entity_type_id);
+    $field_storage_definition->setTranslatable(TRUE);
+    $update_manager->updateFieldStorageDefinition($field_storage_definition);
+  }
+}
diff --git a/trash.module b/trash.module
index ac6770cf537274bc5d51fde0ea583e8af2f4e8bb..5267d9d260b5743a770fa712b9916dbf76614953 100644
--- a/trash.module
+++ b/trash.module
@@ -57,18 +57,20 @@ function trash_entity_is_deleted(EntityInterface $entity): bool {
  *
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   An entity object.
+ * @param string[] $langcodes
+ *   The translations to restore, defaults to all.
  *
  * @throws \Drupal\Core\Entity\EntityStorageException
  *   In case of failures, an exception is thrown.
  */
-function trash_restore_entity(EntityInterface $entity): void {
+function trash_restore_entity(EntityInterface $entity, array $langcodes = []): void {
   if (!\Drupal::service('trash.manager')->isEntityTypeEnabled($entity->getEntityType(), $entity->bundle())) {
     return;
   }
 
   $storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId());
   // @phpstan-ignore-next-line
-  $storage->restoreFromTrash([$entity]);
+  $storage->restoreFromTrash([$entity], $langcodes);
 }
 
 /**
@@ -81,7 +83,7 @@ function trash_entity_base_field_info(EntityTypeInterface $entity_type) {
       ->setLabel(t('Deleted'))
       ->setDescription(t('Time when the item got deleted'))
       ->setInternal(TRUE)
-      ->setTranslatable(FALSE)
+      ->setTranslatable(TRUE)
       ->setRevisionable(TRUE);
 
     return $base_field_definitions;
@@ -92,6 +94,12 @@ function trash_entity_base_field_info(EntityTypeInterface $entity_type) {
  * Implements hook_entity_access().
  */
 function trash_entity_access(EntityInterface $entity, $operation, AccountInterface $account) {
+  /** @var \Drupal\trash\TrashManagerInterface $trash_manager */
+  $trash_manager = \Drupal::service('trash.manager');
+  if ($trash_manager->getTrashContext() === 'ignore') {
+    return AccessResult::neutral();
+  }
+
   $cacheability = new CacheableMetadata();
   $cacheability->addCacheContexts(['user.permissions']);
   $cacheability->addCacheableDependency($entity);
@@ -239,6 +247,8 @@ function _trash_generate_storage_class($original_class, $type = 'storage') {
  * Implements hook_entity_type_alter().
  */
 function trash_entity_type_alter(array &$entity_types) {
+  $is_multilingual = \Drupal::languageManager()->isMultilingual();
+
   /** @var \Drupal\Core\Entity\EntityTypeInterface[] $entity_types */
   foreach ($entity_types as $entity_type_id => $entity_type) {
     if (\Drupal::service('trash.manager')->isEntityTypeEnabled($entity_type)) {
@@ -258,12 +268,17 @@ function trash_entity_type_alter(array &$entity_types) {
       }
       $entity_type->setLinkTemplate('restore', $base_path . '/restore');
       $entity_type->setLinkTemplate('purge', $base_path . '/purge');
-    }
 
-    // Override node's access control handler, so we can skip the
-    // 'bypass node access' permission check if the node is deleted.
-    if ($entity_type->id() === 'node') {
-      $entity_type->setHandlerClass('access', TrashNodeAccessControlHandler::class);
+      if ($is_multilingual && $entity_type->isTranslatable()) {
+        $entity_type->setLinkTemplate('restore-translation', $base_path . '/restore/{language}');
+        $entity_type->setLinkTemplate('purge-translation', $base_path . '/purge/{language}');
+      }
+
+      // Override node's access control handler, so we can skip the
+      // 'bypass node access' permission check if the node is deleted.
+      if ($entity_type->id() === 'node') {
+        $entity_type->setHandlerClass('access', TrashNodeAccessControlHandler::class);
+      }
     }
   }
 }
@@ -345,41 +360,6 @@ function trash_form_alter(&$form, FormStateInterface $form_state, $form_id) {
     $entity_multiple_delete_label = t('Deleting these @label will move them to the trash.', $params);
   }
 
-  // Prevent deleting individual translations.
-  // @todo Remove this after https://www.drupal.org/i/3376216 is fixed.
-  if ($is_entity_delete_form && $entity instanceof TranslatableInterface && $entity->isTranslatable() && !$entity->isDefaultTranslation()) {
-    $entity_delete_label = t('Deleting a translation of a @label is currently not supported by Trash. Unpublish the translation instead.', $params);
-    $form['actions']['submit']['#access'] = FALSE;
-  }
-  elseif ($is_entity_multiple_delete_form && $entity_type->isTranslatable()) {
-    $storage = \Drupal::entityTypeManager()->getStorage($entity_type->id());
-
-    $can_delete = TRUE;
-    foreach (array_keys($form['entities']['#items'] ?? []) as $item) {
-      [$id, $langcode] = explode(':', $item, 2);
-
-      // All entities have been loaded already in the static cache by the
-      // delete multiple form, so it's ok to single-load them again.
-      $entity = $storage->load($id);
-      assert($entity instanceof TranslatableInterface);
-
-      // Deleting the default translation is considered the same as deleting
-      // the entire entity. When all translations are selected, only the
-      // default langcode will show up in the selections.
-      if (!$entity->getTranslation($langcode)->isDefaultTranslation()) {
-        // If any of the selected translations are not the default translation
-        // of the entity, the multiple deletion can not proceed.
-        $can_delete = FALSE;
-        break;
-      }
-    }
-
-    if (!$can_delete) {
-      $entity_multiple_delete_label = t('Deleting translations of @label is currently not supported by Trash. Unpublish the translations instead.', $params);
-      $form['actions']['submit']['#access'] = FALSE;
-    }
-  }
-
   $trash_handler = \Drupal::service('trash.manager')->getHandler($entity_type->id());
   assert($trash_handler instanceof TrashHandlerInterface);
   if (isset($form['description']['#markup']) && $form['description']['#markup'] instanceof TranslatableMarkup) {
@@ -412,6 +392,10 @@ function trash_entity_operation_alter(array &$operations, EntityInterface $entit
     return;
   }
 
+  $url_options = [
+    'language' => \Drupal::languageManager()->getCurrentLanguage(),
+  ];
+
   // Remove all other operations for deleted entities.
   $operations = [];
   if ($entity->access('restore')) {
@@ -419,9 +403,18 @@ function trash_entity_operation_alter(array &$operations, EntityInterface $entit
       '@label' => $entity->label() ?? $entity->id(),
     ]);
 
+    if ($entity instanceof TranslatableInterface && !$entity->isDefaultTranslation()) {
+      $restore_url = $entity->toUrl('restore-translation')
+        ->mergeOptions($url_options)
+        ->setRouteParameter('language', $entity->language()->getId());
+    }
+    else {
+      $restore_url = $entity->toUrl('restore')->mergeOptions($url_options);
+    }
+
     $operations['restore'] = [
       'title' => t('Restore'),
-      'url' => $entity->toUrl('restore')->mergeOptions($url_options),
+      'url' => $restore_url,
       'weight' => 0,
       'attributes' => [
         'class' => ['use-ajax'],
@@ -437,9 +430,18 @@ function trash_entity_operation_alter(array &$operations, EntityInterface $entit
       '@label' => $entity->label() ?? $entity->id(),
     ]);
 
+    if ($entity instanceof TranslatableInterface && !$entity->isDefaultTranslation()) {
+      $purge_url = $entity->toUrl('purge-translation')
+        ->mergeOptions($url_options)
+        ->setRouteParameter('language', $entity->language()->getId());
+    }
+    else {
+      $purge_url = $entity->toUrl('purge')->mergeOptions($url_options);
+    }
+
     $operations['purge'] = [
       'title' => t('Purge'),
-      'url' => $entity->toUrl('purge')->mergeOptions($url_options),
+      'url' => $purge_url,
       'weight' => 5,
       'attributes' => [
         'class' => ['use-ajax'],
@@ -512,6 +514,22 @@ function trash_page_top(array &$page_top): void {
   }
 }
 
+/**
+ * Implements hook_ENTITY_TYPE_insert().
+ */
+function trash_configurable_language_insert(EntityInterface $entity) {
+  if (count(\Drupal::languageManager()->getLanguages()) === 2) {
+    // Need to add translation link templates.
+    /** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
+    $entity_type_manager = \Drupal::service('entity_type.manager');
+    $entity_type_manager->clearCachedDefinitions();
+    // Need to add the translation restore and purge routes.
+    /** @var \Drupal\Core\Routing\RouteBuilderInterface $router_builder */
+    $router_builder = \Drupal::service('router.builder');
+    $router_builder->setRebuildNeeded();
+  }
+}
+
 /**
  * Implements hook_modules_installed().
  */
diff --git a/trash.services.yml b/trash.services.yml
index 79a2fff223f3da6be7687418846bf82565d5e0ad..6594f4d46c5fe44a65d5032e6288bf9072924d78 100644
--- a/trash.services.yml
+++ b/trash.services.yml
@@ -20,6 +20,12 @@ services:
     tags:
       - { name: event_subscriber }
 
+  trash.language_overview_subscriber:
+    class: Drupal\trash\EventSubscriber\TrashLanguageOverviewSubscriber
+    arguments: ['@entity_type.manager', '@trash.manager', '@language_manager']
+    tags:
+      - { name: event_subscriber }
+
   trash.ignore_subscriber:
     class: Drupal\trash\EventSubscriber\TrashIgnoreSubscriber
     arguments: ['@trash.manager', '@current_route_match']
@@ -28,10 +34,16 @@ services:
 
   trash.route_subscriber:
     class: Drupal\trash\Routing\RouteSubscriber
-    arguments: ['@entity_type.manager', '@trash.manager']
+    arguments: ['@entity_type.manager', '@trash.manager', '@language_manager']
     tags:
       - { name: event_subscriber }
 
+  trash.access_check:
+    class: Drupal\trash\Access\TrashAccessCheck
+    arguments: ['@entity_type.manager', '@language_manager', '@trash.manager']
+    tags:
+      - { name: access_check, applies_to: _trash_access }
+
   trash.route_processor:
     class: Drupal\trash\RouteProcessor\TrashRouteProcessor
     arguments: ['@request_stack', '@current_route_match']