diff --git a/core/lib/Drupal/Core/Path/AliasStorage.php b/core/lib/Drupal/Core/Path/AliasStorage.php
index 7de344a781e0856b491a6908a423bd2855c0e7d0..5781359732c783630c6b4cb810c39e7823ff7d96 100644
--- a/core/lib/Drupal/Core/Path/AliasStorage.php
+++ b/core/lib/Drupal/Core/Path/AliasStorage.php
@@ -171,17 +171,30 @@ public function delete($conditions) {
     $storage->delete($storage->loadMultiple($result));
   }
 
+  /**
+   * Returns a SELECT query for the path_alias base table.
+   *
+   * @return \Drupal\Core\Database\Query\SelectInterface
+   *   A Select query object.
+   */
+  protected function getBaseQuery() {
+    $query = $this->connection->select(static::TABLE, 'base_table');
+    $query->condition('base_table.status', 1);
+
+    return $query;
+  }
+
   /**
    * {@inheritdoc}
    */
   public function preloadPathAlias($preloaded, $langcode) {
-    $select = $this->connection->select(static::TABLE)
-      ->fields(static::TABLE, ['path', 'alias']);
+    $select = $this->getBaseQuery()
+      ->fields('base_table', ['path', 'alias']);
 
     if (!empty($preloaded)) {
       $conditions = new Condition('OR');
       foreach ($preloaded as $preloaded_item) {
-        $conditions->condition('path', $this->connection->escapeLike($preloaded_item), 'LIKE');
+        $conditions->condition('base_table.path', $this->connection->escapeLike($preloaded_item), 'LIKE');
       }
       $select->condition($conditions);
     }
@@ -191,7 +204,7 @@ public function preloadPathAlias($preloaded, $langcode) {
     // We order by ID ASC so that fetchAllKeyed() returns the most recently
     // created alias for each source. Subsequent queries using fetchField() must
     // use ID DESC to have the same effect.
-    $select->orderBy('id', 'ASC');
+    $select->orderBy('base_table.id', 'ASC');
 
     return $select->execute()->fetchAllKeyed();
   }
@@ -201,13 +214,13 @@ public function preloadPathAlias($preloaded, $langcode) {
    */
   public function lookupPathAlias($path, $langcode) {
     // See the queries above. Use LIKE for case-insensitive matching.
-    $select = $this->connection->select(static::TABLE)
-      ->fields(static::TABLE, ['alias'])
-      ->condition('path', $this->connection->escapeLike($path), 'LIKE');
+    $select = $this->getBaseQuery()
+      ->fields('base_table', ['alias'])
+      ->condition('base_table.path', $this->connection->escapeLike($path), 'LIKE');
 
     $this->addLanguageFallback($select, $langcode);
 
-    $select->orderBy('id', 'DESC');
+    $select->orderBy('base_table.id', 'DESC');
 
     return $select->execute()->fetchField();
   }
@@ -217,13 +230,13 @@ public function lookupPathAlias($path, $langcode) {
    */
   public function lookupPathSource($alias, $langcode) {
     // See the queries above. Use LIKE for case-insensitive matching.
-    $select = $this->connection->select(static::TABLE)
-      ->fields(static::TABLE, ['path'])
-      ->condition('alias', $this->connection->escapeLike($alias), 'LIKE');
+    $select = $this->getBaseQuery()
+      ->fields('base_table', ['path'])
+      ->condition('base_table.alias', $this->connection->escapeLike($alias), 'LIKE');
 
     $this->addLanguageFallback($select, $langcode);
 
-    $select->orderBy('id', 'DESC');
+    $select->orderBy('base_table.id', 'DESC');
 
     return $select->execute()->fetchField();
   }
@@ -246,12 +259,12 @@ protected function addLanguageFallback(SelectInterface $query, $langcode) {
       array_pop($langcode_list);
     }
     elseif ($langcode > LanguageInterface::LANGCODE_NOT_SPECIFIED) {
-      $query->orderBy('langcode', 'DESC');
+      $query->orderBy('base_table.langcode', 'DESC');
     }
     else {
-      $query->orderBy('langcode', 'ASC');
+      $query->orderBy('base_table.langcode', 'ASC');
     }
-    $query->condition('langcode', $langcode_list, 'IN');
+    $query->condition('base_table.langcode', $langcode_list, 'IN');
   }
 
   /**
@@ -304,11 +317,11 @@ public function getAliasesForAdminListing($header, $keys = NULL) {
    * {@inheritdoc}
    */
   public function pathHasMatchingAlias($initial_substring) {
-    $query = $this->connection->select(static::TABLE);
+    $query = $this->getBaseQuery();
     $query->addExpression(1);
 
     return (bool) $query
-      ->condition('path', $this->connection->escapeLike($initial_substring) . '%', 'LIKE')
+      ->condition('base_table.path', $this->connection->escapeLike($initial_substring) . '%', 'LIKE')
       ->range(0, 1)
       ->execute()
       ->fetchField();
diff --git a/core/lib/Drupal/Core/Path/Entity/PathAlias.php b/core/lib/Drupal/Core/Path/Entity/PathAlias.php
index ce96d61a0a9b71866bb833fd92cb7b753202acd1..89dd0510526271eb47fa9f956728881b54e68e26 100644
--- a/core/lib/Drupal/Core/Path/Entity/PathAlias.php
+++ b/core/lib/Drupal/Core/Path/Entity/PathAlias.php
@@ -3,6 +3,7 @@
 namespace Drupal\Core\Path\Entity;
 
 use Drupal\Core\Entity\ContentEntityBase;
+use Drupal\Core\Entity\EntityPublishedTrait;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Field\BaseFieldDefinition;
@@ -34,6 +35,7 @@
  *     "revision" = "revision_id",
  *     "langcode" = "langcode",
  *     "uuid" = "uuid",
+ *     "published" = "status",
  *   },
  *   admin_permission = "administer url aliases",
  *   list_cache_tags = { "route_match" },
@@ -41,6 +43,8 @@
  */
 class PathAlias extends ContentEntityBase implements PathAliasInterface {
 
+  use EntityPublishedTrait;
+
   /**
    * {@inheritdoc}
    */
@@ -61,6 +65,10 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
 
     $fields['langcode']->setDefaultValue(LanguageInterface::LANGCODE_NOT_SPECIFIED);
 
+    // Add the published field.
+    $fields += static::publishedBaseFieldDefinitions($entity_type);
+    $fields['status']->setTranslatable(FALSE);
+
     return $fields;
   }
 
diff --git a/core/lib/Drupal/Core/Path/PathAliasInterface.php b/core/lib/Drupal/Core/Path/PathAliasInterface.php
index 73d2b4569f69eb6528e73762e3cdedac64d0af13..b6f5c3b0f7768cdbc3fba615f6b04001cf61056f 100644
--- a/core/lib/Drupal/Core/Path/PathAliasInterface.php
+++ b/core/lib/Drupal/Core/Path/PathAliasInterface.php
@@ -3,11 +3,12 @@
 namespace Drupal\Core\Path;
 
 use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityPublishedInterface;
 
 /**
  * Provides an interface defining a path_alias entity.
  */
-interface PathAliasInterface extends ContentEntityInterface {
+interface PathAliasInterface extends ContentEntityInterface, EntityPublishedInterface {
 
   /**
    * Gets the source path of the alias.
diff --git a/core/lib/Drupal/Core/Path/PathAliasStorageSchema.php b/core/lib/Drupal/Core/Path/PathAliasStorageSchema.php
index 2d23c85e4fda518565d5a016e29ed9fc641613c5..c79e2c77f0f196a37bb207eff3bc4b0824d51825 100644
--- a/core/lib/Drupal/Core/Path/PathAliasStorageSchema.php
+++ b/core/lib/Drupal/Core/Path/PathAliasStorageSchema.php
@@ -17,8 +17,8 @@ protected function getEntitySchema(ContentEntityTypeInterface $entity_type, $res
     $schema = parent::getEntitySchema($entity_type, $reset);
 
     $schema[$this->storage->getBaseTable()]['indexes'] += [
-      'path_alias__alias_langcode_id' => ['alias', 'langcode', 'id'],
-      'path_alias__path_langcode_id' => ['path', 'langcode', 'id'],
+      'path_alias__alias_langcode_id_status' => ['alias', 'langcode', 'id', 'status'],
+      'path_alias__path_langcode_id_status' => ['path', 'langcode', 'id', 'status'],
     ];
 
     return $schema;
diff --git a/core/lib/Drupal/Core/Routing/CacheableRouteProviderInterface.php b/core/lib/Drupal/Core/Routing/CacheableRouteProviderInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..1a5f5b0b45e35fb96fbfab8574d457cba770ac33
--- /dev/null
+++ b/core/lib/Drupal/Core/Routing/CacheableRouteProviderInterface.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Drupal\Core\Routing;
+
+/**
+ * Extends the router provider interface to provide caching support.
+ */
+interface CacheableRouteProviderInterface extends RouteProviderInterface {
+
+  /**
+   * Adds a cache key part to be used in the cache ID of the route collection.
+   *
+   * @param string $cache_key_provider
+   *   The provider of the cache key part.
+   * @param string $cache_key_part
+   *   A string to be used as a cache key part.
+   */
+  public function addExtraCacheKeyPart($cache_key_provider, $cache_key_part);
+
+}
diff --git a/core/lib/Drupal/Core/Routing/RouteProvider.php b/core/lib/Drupal/Core/Routing/RouteProvider.php
index d31f293e18c411c660c4c96989e0fef7625f66c9..c6355508124e35344f710907a129c7760830cadf 100644
--- a/core/lib/Drupal/Core/Routing/RouteProvider.php
+++ b/core/lib/Drupal/Core/Routing/RouteProvider.php
@@ -21,7 +21,7 @@
 /**
  * A Route Provider front-end for all Drupal-stored routes.
  */
-class RouteProvider implements PreloadableRouteProviderInterface, PagedRouteProviderInterface, EventSubscriberInterface {
+class RouteProvider implements CacheableRouteProviderInterface, PreloadableRouteProviderInterface, PagedRouteProviderInterface, EventSubscriberInterface {
 
   /**
    * The database connection from which to read route information.
@@ -98,6 +98,13 @@ class RouteProvider implements PreloadableRouteProviderInterface, PagedRouteProv
    */
   const ROUTE_LOAD_CID_PREFIX = 'route_provider.route_load:';
 
+  /**
+   * An array of cache key parts to be used for the route match cache.
+   *
+   * @var string[]
+   */
+  protected $extraCacheKeyParts = [];
+
   /**
    * Constructs a new PathMatcher.
    *
@@ -442,6 +449,13 @@ public function getRoutesCount() {
     return $this->connection->query("SELECT COUNT(*) FROM {" . $this->connection->escapeTable($this->tableName) . "}")->fetchField();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function addExtraCacheKeyPart($cache_key_provider, $cache_key_part) {
+    $this->extraCacheKeyParts[$cache_key_provider] = $cache_key_part;
+  }
+
   /**
    * Returns the cache ID for the route collection cache.
    *
@@ -455,8 +469,17 @@ protected function getRouteCollectionCacheId(Request $request) {
     // Include the current language code in the cache identifier as
     // the language information can be elsewhere than in the path, for example
     // based on the domain.
-    $language_part = $this->getCurrentLanguageCacheIdPart();
-    return 'route:' . $language_part . ':' . $request->getPathInfo() . ':' . $request->getQueryString();
+    $this->addExtraCacheKeyPart('language', $this->getCurrentLanguageCacheIdPart());
+
+    // Sort the cache key parts by their provider in order to have predictable
+    // cache keys.
+    ksort($this->extraCacheKeyParts);
+    $key_parts = [];
+    foreach ($this->extraCacheKeyParts as $provider => $key_part) {
+      $key_parts[] = '[' . $provider . ']=' . $key_part;
+    }
+
+    return 'route:' . implode(':', $key_parts) . ':' . $request->getPathInfo() . ':' . $request->getQueryString();
   }
 
   /**
diff --git a/core/modules/jsonapi/tests/src/Functional/PathAliasTest.php b/core/modules/jsonapi/tests/src/Functional/PathAliasTest.php
index 873cb2abadde8259040212cad4ebcdab879bd9ea..745f85f840847bb17c0c02b123f81eb9648e2d4c 100644
--- a/core/modules/jsonapi/tests/src/Functional/PathAliasTest.php
+++ b/core/modules/jsonapi/tests/src/Functional/PathAliasTest.php
@@ -9,6 +9,7 @@
  * JSON:API integration test for the "PathAlias" content entity type.
  *
  * @group jsonapi
+ * @group path
  */
 class PathAliasTest extends ResourceTestBase {
 
@@ -86,6 +87,7 @@ protected function getExpectedDocument() {
           'alias' => '/frontpage1',
           'path' => '/<front>',
           'langcode' => 'en',
+          'status' => TRUE,
           'drupal_internal__id' => 1,
           'drupal_internal__revision_id' => 1,
         ],
diff --git a/core/modules/path/src/Plugin/Field/FieldType/PathItem.php b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php
index 0fb913b9a3954886a1f2be526ba4892b88308567..ba57f2467c9574c6f073ed27099572e3e049fb79 100644
--- a/core/modules/path/src/Plugin/Field/FieldType/PathItem.php
+++ b/core/modules/path/src/Plugin/Field/FieldType/PathItem.php
@@ -63,28 +63,42 @@ public function preSave() {
    * {@inheritdoc}
    */
   public function postSave($update) {
+    $path_alias_storage = \Drupal::entityTypeManager()->getStorage('path_alias');
+    $entity = $this->getEntity();
+
     // If specified, rely on the langcode property for the language, so that the
     // existing language of an alias can be kept. That could for example be
     // unspecified even if the field/entity has a specific langcode.
     $alias_langcode = ($this->langcode && $this->pid) ? $this->langcode : $this->getLangcode();
 
-    if (!$update) {
-      if ($this->alias) {
-        $entity = $this->getEntity();
-        if ($path = \Drupal::service('path.alias_storage')->save('/' . $entity->toUrl()->getInternalPath(), $this->alias, $alias_langcode)) {
-          $this->pid = $path['pid'];
+    // If we have an alias, we need to create or update a path alias entity.
+    if ($this->alias) {
+      if (!$update || !$this->pid) {
+        $path_alias = $path_alias_storage->create([
+          'path' => '/' . $entity->toUrl()->getInternalPath(),
+          'alias' => $this->alias,
+          'langcode' => $alias_langcode,
+        ]);
+        $path_alias->save();
+        $this->pid = $path_alias->id();
+      }
+      elseif ($this->pid) {
+        $path_alias = $path_alias_storage->load($this->pid);
+
+        if ($this->alias != $path_alias->getAlias()) {
+          $path_alias->setAlias($this->alias);
+          $path_alias->save();
         }
       }
     }
-    else {
-      // Delete old alias if user erased it.
-      if ($this->pid && !$this->alias) {
-        \Drupal::service('path.alias_storage')->delete(['pid' => $this->pid]);
+    elseif ($this->pid && !$this->alias) {
+      // Otherwise, delete the old alias if the user erased it.
+      $path_alias = $path_alias_storage->load($this->pid);
+      if ($entity->isDefaultRevision()) {
+        $path_alias_storage->delete([$path_alias]);
       }
-      // Only save a non-empty alias.
-      elseif ($this->alias) {
-        $entity = $this->getEntity();
-        \Drupal::service('path.alias_storage')->save('/' . $entity->toUrl()->getInternalPath(), $this->alias, $alias_langcode, $this->pid);
+      else {
+        $path_alias_storage->deleteRevision($path_alias->getRevisionID());
       }
     }
   }
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index cafd176ffa7acb8db5293c6d8b3f137b81325953..c7d4aa4bfc6b7a45b8c1beafd861ef6cf1d72dcf 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -2395,6 +2395,7 @@ function system_update_8803() {
         'revision' => 'revision_id',
         'langcode' => 'langcode',
         'uuid' => 'uuid',
+        'published' => 'status',
       ],
     ]);
 
@@ -2423,6 +2424,11 @@ function system_update_8803() {
       ->setInternal(TRUE)
       ->setRevisionable(TRUE);
 
+    $field_storage_definitions['status'] = BaseFieldDefinition::create('boolean')
+      ->setLabel(new TranslatableMarkup('Published'))
+      ->setRevisionable(TRUE)
+      ->setDefaultValue(TRUE);
+
     $field_storage_definitions['path'] = BaseFieldDefinition::create('string')
       ->setLabel(new TranslatableMarkup('System path'))
       ->setDescription(new TranslatableMarkup('The path that this alias belongs to.'))
@@ -2472,9 +2478,9 @@ function system_update_8804(&$sandbox = NULL) {
     $uuid = \Drupal::service('uuid');
 
     $base_table_insert = $database->insert('path_alias');
-    $base_table_insert->fields(['id', 'revision_id', 'uuid', 'path', 'alias', 'langcode']);
+    $base_table_insert->fields(['id', 'revision_id', 'uuid', 'path', 'alias', 'langcode', 'status']);
     $revision_table_insert = $database->insert('path_alias_revision');
-    $revision_table_insert->fields(['id', 'revision_id', 'path', 'alias', 'langcode', 'revision_default']);
+    $revision_table_insert->fields(['id', 'revision_id', 'path', 'alias', 'langcode', 'status', 'revision_default']);
     foreach ($url_aliases as $url_alias) {
       $values = [
         'id' => $url_alias->pid,
@@ -2483,6 +2489,7 @@ function system_update_8804(&$sandbox = NULL) {
         'path' => $url_alias->source,
         'alias' => $url_alias->alias,
         'langcode' => $url_alias->langcode,
+        'status' => 1,
       ];
       $base_table_insert->values($values);
 
diff --git a/core/modules/system/tests/src/Functional/Update/PathAliasToEntityUpdateTest.php b/core/modules/system/tests/src/Functional/Update/PathAliasToEntityUpdateTest.php
index 0677f061f942dfd4b5060bc6ff26cdf278015ec3..e688faab80d323596552e0057fc9ad2ef1b7708f 100644
--- a/core/modules/system/tests/src/Functional/Update/PathAliasToEntityUpdateTest.php
+++ b/core/modules/system/tests/src/Functional/Update/PathAliasToEntityUpdateTest.php
@@ -38,6 +38,11 @@ public function testConversionToEntities() {
     $query->addField('url_alias', 'source', 'path');
     $query->addField('url_alias', 'alias');
     $query->addField('url_alias', 'langcode');
+
+    // Path aliases did not have a 'status' value before the conversion to
+    // entities, but we're adding it here to ensure that the field was installed
+    // and populated correctly.
+    $query->addExpression('1', 'status');
     $original_records = $query->execute()->fetchAllAssoc('id');
 
     // drupal-8.filled.standard.php.gz contains one URL alias and
@@ -90,12 +95,12 @@ public function testConversionToEntities() {
     // Check that correct data was written in both the base and the revision
     // tables.
     $base_table_records = $database->select('path_alias')
-      ->fields('path_alias', ['id', 'path', 'alias', 'langcode'])
+      ->fields('path_alias', ['id', 'path', 'alias', 'langcode', 'status'])
       ->execute()->fetchAllAssoc('id');
     $this->assertEquals($original_records, $base_table_records);
 
     $revision_table_records = $database->select('path_alias_revision')
-      ->fields('path_alias_revision', ['id', 'path', 'alias', 'langcode'])
+      ->fields('path_alias_revision', ['id', 'path', 'alias', 'langcode', 'status'])
       ->execute()->fetchAllAssoc('id');
     $this->assertEquals($original_records, $revision_table_records);
   }
diff --git a/core/modules/workspaces/src/AliasStorage.php b/core/modules/workspaces/src/AliasStorage.php
new file mode 100644
index 0000000000000000000000000000000000000000..219eb7f4160fa4b7f7e932025fa518800caf9795
--- /dev/null
+++ b/core/modules/workspaces/src/AliasStorage.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace Drupal\workspaces;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Path\AliasStorage as CoreAliasStorage;
+
+/**
+ * Provides workspace-specific path alias lookup queries.
+ */
+class AliasStorage extends CoreAliasStorage {
+
+  /**
+   * The workspace manager.
+   *
+   * @var \Drupal\workspaces\WorkspaceManagerInterface
+   */
+  protected $workspaceManager;
+
+  /**
+   * AliasStorage constructor.
+   *
+   * @param \Drupal\Core\Database\Connection $connection
+   *   A database connection for reading and writing path aliases.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   * @param \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager
+   *   The workspace manager service.
+   */
+  public function __construct(Connection $connection, ModuleHandlerInterface $module_handler, EntityTypeManagerInterface $entity_type_manager, WorkspaceManagerInterface $workspace_manager) {
+    parent::__construct($connection, $module_handler, $entity_type_manager);
+    $this->workspaceManager = $workspace_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getBaseQuery() {
+    // Don't alter any queries if we're not in a workspace context.
+    if (!$this->workspaceManager->hasActiveWorkspace()) {
+      return parent::getBaseQuery();
+    }
+
+    $active_workspace = $this->workspaceManager->getActiveWorkspace();
+
+    $query = $this->connection->select('path_alias', 'base_table_2');
+    $wa_join = $query->leftJoin('workspace_association', NULL, "%alias.target_entity_type_id = 'path_alias' AND %alias.target_entity_id = base_table_2.id AND %alias.workspace = :active_workspace_id", [
+      ':active_workspace_id' => $active_workspace->id(),
+    ]);
+    $query->innerJoin('path_alias_revision', 'base_table', "%alias.revision_id = COALESCE($wa_join.target_entity_revision_id, base_table_2.revision_id)");
+
+    return $query;
+  }
+
+}
diff --git a/core/modules/workspaces/src/EntityTypeInfo.php b/core/modules/workspaces/src/EntityTypeInfo.php
index d8fca35733709c6779d9d54fd31e9c545826c93c..4f0ba1a7d4673c461c69dc3a9b77d3219316217c 100644
--- a/core/modules/workspaces/src/EntityTypeInfo.php
+++ b/core/modules/workspaces/src/EntityTypeInfo.php
@@ -107,6 +107,11 @@ public function fieldInfoAlter(&$definitions) {
     if (isset($definitions['entity_reference'])) {
       $definitions['entity_reference']['constraints']['EntityReferenceSupportedNewEntities'] = [];
     }
+
+    // Allow path aliases to be changed in workspace-specific pending revisions.
+    if (isset($definitions['path'])) {
+      unset($definitions['path']['constraints']['PathAlias']);
+    }
   }
 
   /**
diff --git a/core/modules/workspaces/src/EventSubscriber/WorkspaceRequestSubscriber.php b/core/modules/workspaces/src/EventSubscriber/WorkspaceRequestSubscriber.php
new file mode 100644
index 0000000000000000000000000000000000000000..e44d36b02ad45364afa7b7b642672b4c730107b7
--- /dev/null
+++ b/core/modules/workspaces/src/EventSubscriber/WorkspaceRequestSubscriber.php
@@ -0,0 +1,110 @@
+<?php
+
+namespace Drupal\workspaces\EventSubscriber;
+
+use Drupal\Core\Path\AliasManagerInterface;
+use Drupal\Core\Path\CurrentPathStack;
+use Drupal\Core\Routing\CacheableRouteProviderInterface;
+use Drupal\Core\Routing\RouteProviderInterface;
+use Drupal\workspaces\WorkspaceManagerInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\KernelEvents;
+
+/**
+ * Provides a event subscriber for setting workspace-specific cache keys.
+ */
+class WorkspaceRequestSubscriber implements EventSubscriberInterface {
+
+  /**
+   * The alias manager that caches alias lookups based on the request.
+   *
+   * @var \Drupal\Core\Path\AliasManagerInterface
+   */
+  protected $aliasManager;
+
+  /**
+   * The current path.
+   *
+   * @var \Drupal\Core\Path\CurrentPathStack
+   */
+  protected $currentPath;
+
+  /**
+   * The route provider to load routes by name.
+   *
+   * @var \Drupal\Core\Routing\RouteProviderInterface
+   */
+  protected $routeProvider;
+
+  /**
+   * The workspace manager.
+   *
+   * @var \Drupal\workspaces\WorkspaceManagerInterface
+   */
+  protected $workspaceManager;
+
+  /**
+   * Constructs a new WorkspaceRequestSubscriber instance.
+   *
+   * @param \Drupal\Core\Path\AliasManagerInterface $alias_manager
+   *   The alias manager.
+   * @param \Drupal\Core\Path\CurrentPathStack $current_path
+   *   The current path.
+   * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
+   *   The route provider.
+   * @param \Drupal\workspaces\WorkspaceManagerInterface $workspace_manager
+   *   The workspace manager.
+   */
+  public function __construct(AliasManagerInterface $alias_manager, CurrentPathStack $current_path, RouteProviderInterface $route_provider, WorkspaceManagerInterface $workspace_manager) {
+    $this->aliasManager = $alias_manager;
+    $this->currentPath = $current_path;
+    $this->routeProvider = $route_provider;
+    $this->workspaceManager = $workspace_manager;
+  }
+
+  /**
+   * Sets the cache key on the alias manager cache decorator.
+   *
+   * KernelEvents::CONTROLLER is used in order to be executed after routing.
+   *
+   * @param \Symfony\Component\HttpKernel\Event\FilterControllerEvent $event
+   *   The Event to process.
+   */
+  public function onKernelController(FilterControllerEvent $event) {
+    // Set the cache key on the alias manager cache decorator.
+    if ($event->isMasterRequest() && $this->workspaceManager->hasActiveWorkspace()) {
+      $cache_key = $this->workspaceManager->getActiveWorkspace()->id() . ':' . rtrim($this->currentPath->getPath($event->getRequest()), '/');
+      $this->aliasManager->setCacheKey($cache_key);
+    }
+  }
+
+  /**
+   * Adds the active workspace as a cache key part to the route provider.
+   *
+   * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
+   *   An event object.
+   */
+  public function onKernelRequest(GetResponseEvent $event) {
+    if ($this->workspaceManager->hasActiveWorkspace() && $this->routeProvider instanceof CacheableRouteProviderInterface) {
+      $this->routeProvider->addExtraCacheKeyPart('workspace', $this->workspaceManager->getActiveWorkspace()->id());
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public static function getSubscribedEvents() {
+    // Use a priority of 190 in order to run after the generic core subscriber.
+    // @see \Drupal\Core\EventSubscriber\PathSubscriber::getSubscribedEvents()
+    $events[KernelEvents::CONTROLLER][] = ['onKernelController', 190];
+
+    // Use a priority of 33 in order to run before Symfony's router listener.
+    // @see \Symfony\Component\HttpKernel\EventListener\RouterListener::getSubscribedEvents()
+    $events[KernelEvents::REQUEST][] = ['onKernelRequest', 33];
+
+    return $events;
+  }
+
+}
diff --git a/core/modules/workspaces/src/WorkspaceManager.php b/core/modules/workspaces/src/WorkspaceManager.php
index b53be3613025a3e12416a50da5fea8a57ea35164..499c5cd786a45c45fd4b8afa419e505255e4c407 100644
--- a/core/modules/workspaces/src/WorkspaceManager.php
+++ b/core/modules/workspaces/src/WorkspaceManager.php
@@ -262,6 +262,10 @@ protected function doSwitchWorkspace($workspace) {
       return 'entity.memory_cache:' . $entity_type_id;
     }, array_keys($this->getSupportedEntityTypes()));
     $this->entityMemoryCache->invalidateTags($cache_tags_to_invalidate);
+
+    // Clear the static cache for path aliases. We can't inject the path alias
+    // manager service because it would create a circular dependency.
+    \Drupal::service('path.alias_manager')->cacheClear();
   }
 
   /**
diff --git a/core/modules/workspaces/src/WorkspacesServiceProvider.php b/core/modules/workspaces/src/WorkspacesServiceProvider.php
index 685575f2eff1476e9e5d623b8cb0858f5e34f4c1..598ae742d7c3d756f28d2984abcfe0f2250e3630 100644
--- a/core/modules/workspaces/src/WorkspacesServiceProvider.php
+++ b/core/modules/workspaces/src/WorkspacesServiceProvider.php
@@ -4,6 +4,7 @@
 
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\DependencyInjection\ServiceProviderBase;
+use Symfony\Component\DependencyInjection\Reference;
 
 /**
  * Defines a service provider for the Workspaces module.
@@ -18,6 +19,11 @@ public function alter(ContainerBuilder $container) {
     $renderer_config = $container->getParameter('renderer.config');
     $renderer_config['required_cache_contexts'][] = 'workspace';
     $container->setParameter('renderer.config', $renderer_config);
+
+    // Replace the class of the 'path.alias_storage' service.
+    $container->getDefinition('path.alias_storage')
+      ->setClass(AliasStorage::class)
+      ->addArgument(new Reference('workspaces.manager'));
   }
 
 }
diff --git a/core/modules/workspaces/tests/src/Functional/PathWorkspacesTest.php b/core/modules/workspaces/tests/src/Functional/PathWorkspacesTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..78ae786c52768b8b8b40de4b5a785d3421683ecd
--- /dev/null
+++ b/core/modules/workspaces/tests/src/Functional/PathWorkspacesTest.php
@@ -0,0 +1,309 @@
+<?php
+
+namespace Drupal\Tests\workspaces\Functional;
+
+use Drupal\language\Entity\ConfigurableLanguage;
+use Drupal\Tests\BrowserTestBase;
+use Drupal\workspaces\Entity\Workspace;
+
+/**
+ * Tests path aliases with workspaces.
+ *
+ * @group path
+ * @group workspaces
+ */
+class PathWorkspacesTest extends BrowserTestBase {
+
+  use WorkspaceTestUtilities;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['block', 'content_translation', 'node', 'path', 'workspaces'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    ConfigurableLanguage::createFromLangcode('ro')->save();
+    $this->rebuildContainer();
+
+    // Create a content type.
+    $this->drupalCreateContentType([
+      'name' => 'article',
+      'type' => 'article',
+    ]);
+
+    $this->drupalLogin($this->rootUser);
+
+    // Enable URL language detection and selection.
+    $edit = ['language_interface[enabled][language-url]' => 1];
+    $this->drupalPostForm('admin/config/regional/language/detection', $edit, 'Save settings');
+
+    // Enable translation for article node.
+    $edit = [
+      'entity_types[node]' => 1,
+      'settings[node][article][translatable]' => 1,
+      'settings[node][article][fields][path]' => 1,
+      'settings[node][article][fields][body]' => 1,
+      'settings[node][article][settings][language][language_alterable]' => 1,
+    ];
+    $this->drupalPostForm('admin/config/regional/content-language', $edit, 'Save configuration');
+    \Drupal::entityTypeManager()->clearCachedDefinitions();
+
+    $this->setupWorkspaceSwitcherBlock();
+  }
+
+  /**
+   * Tests path aliases with workspaces.
+   */
+  public function testPathAliases() {
+    // Create a published node in Live, without an alias.
+    $node = $this->drupalCreateNode([
+      'type' => 'article',
+      'status' => TRUE,
+    ]);
+
+    // Switch to Stage and create an alias for the node.
+    $stage = Workspace::load('stage');
+    $this->switchToWorkspace($stage);
+
+    $edit = [
+      'path[0][alias]' => '/' . $this->randomMachineName(),
+    ];
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+
+    // Check that the node can be accessed in Stage with the given alias.
+    $path = $edit['path[0][alias]'];
+    $this->assertAccessiblePaths([$path]);
+
+    // Check that the 'preload-paths' cache includes the active workspace ID in
+    // the cache key.
+    $this->assertNotEmpty(\Drupal::cache('data')->get('preload-paths:stage:/node/1'));
+    $this->assertFalse(\Drupal::cache('data')->get('preload-paths:/node/1'));
+
+    // Check that the alias can not be accessed in Live.
+    $this->switchToLive();
+    $this->assertNotAccessiblePaths([$path]);
+    $this->assertFalse(\Drupal::cache('data')->get('preload-paths:/node/1'));
+
+    // Publish the workspace and check that the alias can be accessed in Live.
+    $stage->publish();
+    $this->assertAccessiblePaths([$path]);
+    $this->assertNotEmpty(\Drupal::cache('data')->get('preload-paths:/node/1'));
+  }
+
+  /**
+   * Tests path aliases with workspaces and user switching.
+   */
+  public function testPathAliasesUserSwitch() {
+    // Create a published node in Live, without an alias.
+    $node = $this->drupalCreateNode([
+      'type' => 'article',
+      'status' => TRUE,
+    ]);
+
+    // Switch to Stage and create an alias for the node.
+    $stage = Workspace::load('stage');
+    $this->switchToWorkspace($stage);
+
+    $edit = [
+      'path[0][alias]' => '/' . $this->randomMachineName(),
+    ];
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, 'Save');
+
+    // Check that the node can be accessed in Stage with the given alias.
+    $path = $edit['path[0][alias]'];
+    $this->assertAccessiblePaths([$path]);
+
+    // Check that the 'preload-paths' cache includes the active workspace ID in
+    // the cache key.
+    $this->assertNotEmpty(\Drupal::cache('data')->get('preload-paths:stage:/node/1'));
+    $this->assertFalse(\Drupal::cache('data')->get('preload-paths:/node/1'));
+
+    // Check that the alias can not be accessed in Live, by logging out without
+    // an explicit switch.
+    $this->drupalLogout();
+    $this->assertNotAccessiblePaths([$path]);
+    $this->assertFalse(\Drupal::cache('data')->get('preload-paths:/node/1'));
+
+    // Publish the workspace and check that the alias can be accessed in Live.
+    $this->drupalLogin($this->rootUser);
+    $stage->publish();
+    $this->drupalLogout();
+    $this->assertAccessiblePaths([$path]);
+    $this->assertNotEmpty(\Drupal::cache('data')->get('preload-paths:/node/1'));
+  }
+
+  /**
+   * Tests path aliases with workspaces for translatable nodes.
+   */
+  public function testPathAliasesWithTranslation() {
+    $stage = Workspace::load('stage');
+
+    // Create one node with a random alias.
+    $default_node = $this->drupalCreateNode([
+      'type' => 'article',
+      'langcode' => 'en',
+      'status' => TRUE,
+      'path' => '/' . $this->randomMachineName(),
+    ]);
+
+    // Add published translation with another alias.
+    $this->drupalGet('node/' . $default_node->id());
+    $this->drupalGet('node/' . $default_node->id() . '/translations');
+    $this->clickLink('Add');
+    $edit_translation = [
+      'body[0][value]' => $this->randomMachineName(),
+      'status[value]' => TRUE,
+      'path[0][alias]' => '/' . $this->randomMachineName(),
+    ];
+    $this->drupalPostForm(NULL, $edit_translation, 'Save (this translation)');
+    // Confirm that the alias works.
+    $this->drupalGet('ro' . $edit_translation['path[0][alias]']);
+    $this->assertSession()->pageTextContains($edit_translation['body[0][value]']);
+
+    $default_path = $default_node->path->alias;
+    $translation_path = 'ro' . $edit_translation['path[0][alias]'];
+
+    $this->assertAccessiblePaths([$default_path, $translation_path]);
+
+    $this->switchToWorkspace($stage);
+
+    $this->assertAccessiblePaths([$default_path, $translation_path]);
+
+    // Create a workspace-specific revision for the translation with a new path
+    // alias.
+    $edit_new_translation_draft_with_alias = [
+      'path[0][alias]' => '/' . $this->randomMachineName(),
+    ];
+    $this->drupalPostForm('ro/node/' . $default_node->id() . '/edit', $edit_new_translation_draft_with_alias, 'Save (this translation)');
+    $stage_translation_path = 'ro' . $edit_new_translation_draft_with_alias['path[0][alias]'];
+
+    // The new alias of the translation should be available in Stage, but not
+    // available in Live.
+    $this->assertAccessiblePaths([$default_path, $stage_translation_path]);
+
+    // Check that the previous (Live) path alias no longer works.
+    $this->assertNotAccessiblePaths([$translation_path]);
+
+    // Switch out of Stage and check that the initial path aliases still work.
+    $this->switchToLive();
+    $this->assertAccessiblePaths([$default_path, $translation_path]);
+    $this->assertNotAccessiblePaths([$stage_translation_path]);
+
+    // Switch back to Stage.
+    $this->switchToWorkspace($stage);
+
+    // Create new workspace-specific revision for translation without changing
+    // the path alias.
+    $edit_new_translation_draft = [
+      'body[0][value]' => $this->randomMachineName(),
+    ];
+    $this->drupalPostForm('ro/node/' . $default_node->id() . '/edit', $edit_new_translation_draft, t('Save (this translation)'));
+    // Confirm that the new draft revision was created.
+    $this->assertSession()->pageTextContains($edit_new_translation_draft['body[0][value]']);
+
+    // Switch out of Stage and check that the initial path aliases still work.
+    $this->switchToLive();
+    $this->assertAccessiblePaths([$default_path, $translation_path]);
+    $this->assertNotAccessiblePaths([$stage_translation_path]);
+
+    // Switch back to Stage.
+    $this->switchToWorkspace($stage);
+    $this->assertAccessiblePaths([$default_path, $stage_translation_path]);
+    $this->assertNotAccessiblePaths([$translation_path]);
+
+    // Create a new workspace-specific revision for translation with path alias
+    // from the original language's default revision.
+    $edit_new_translation_draft_with_defaults_alias = [
+      'path[0][alias]' => $default_node->path->alias,
+    ];
+    $this->drupalPostForm('ro/node/' . $default_node->id() . '/edit', $edit_new_translation_draft_with_defaults_alias, 'Save (this translation)');
+
+    // Switch out of Stage and check that the initial path aliases still work.
+    $this->switchToLive();
+    $this->assertAccessiblePaths([$default_path, $translation_path]);
+    $this->assertNotAccessiblePaths([$stage_translation_path]);
+
+    // Check that only one path alias (the original one) is available in Stage.
+    $this->switchToWorkspace($stage);
+    $this->assertAccessiblePaths([$default_path]);
+    $this->assertNotAccessiblePaths([$translation_path, $stage_translation_path]);
+
+    // Create new workspace-specific revision for translation with a deleted
+    // (empty) path alias.
+    $edit_new_translation_draft_empty_alias = [
+      'body[0][value]' => $this->randomMachineName(),
+      'path[0][alias]' => '',
+    ];
+    $this->drupalPostForm('ro/node/' . $default_node->id() . '/edit', $edit_new_translation_draft_empty_alias, 'Save (this translation)');
+
+    // Check that only one path alias (the original one) is available now.
+    $this->switchToLive();
+    $this->assertAccessiblePaths([$default_path, $translation_path]);
+    $this->assertNotAccessiblePaths([$stage_translation_path]);
+
+    $this->switchToWorkspace($stage);
+    $this->assertAccessiblePaths([$default_path]);
+    $this->assertNotAccessiblePaths([$translation_path, $stage_translation_path]);
+
+    // Create a new workspace-specific revision for the translation with a new
+    // path alias.
+    $edit_new_translation = [
+      'body[0][value]' => $this->randomMachineName(),
+      'path[0][alias]' => '/' . $this->randomMachineName(),
+    ];
+    $this->drupalPostForm('ro/node/' . $default_node->id() . '/edit', $edit_new_translation, 'Save (this translation)');
+
+    // Confirm that the new revision was created.
+    $this->assertSession()->pageTextContains($edit_new_translation['body[0][value]']);
+    $this->assertSession()->addressEquals('ro' . $edit_new_translation['path[0][alias]']);
+
+    // Check that only the new path alias of the translation can be accessed.
+    $new_stage_translation_path = 'ro' . $edit_new_translation['path[0][alias]'];
+    $this->assertAccessiblePaths([$default_path, $new_stage_translation_path]);
+    $this->assertNotAccessiblePaths([$stage_translation_path]);
+
+    // Switch out of Stage and check that none of the workspace-specific path
+    // aliases can be accessed.
+    $this->switchToLive();
+    $this->assertAccessiblePaths([$default_path, $translation_path]);
+    $this->assertNotAccessiblePaths([$stage_translation_path, $new_stage_translation_path]);
+
+    // Publish Stage and check that its path alias for the translation can be
+    // accessed.
+    $stage->publish();
+    $this->assertAccessiblePaths([$default_path, $new_stage_translation_path]);
+    $this->assertNotAccessiblePaths([$stage_translation_path]);
+  }
+
+  /**
+   * Helper callback to verify paths are responding with status 200.
+   *
+   * @param string[] $paths
+   *   An array of paths to check for.
+   */
+  protected function assertAccessiblePaths(array $paths) {
+    foreach ($paths as $path) {
+      $this->drupalGet($path);
+      $this->assertSession()->statusCodeEquals(200);
+    }
+  }
+
+  /**
+   * Helper callback to verify paths are responding with status 404.
+   *
+   * @param string[] $paths
+   *   An array of paths to check for.
+   */
+  protected function assertNotAccessiblePaths(array $paths) {
+    foreach ($paths as $path) {
+      $this->drupalGet($path);
+      $this->assertSession()->statusCodeEquals(404);
+    }
+  }
+
+}
diff --git a/core/modules/workspaces/tests/src/Unit/WorkspaceRequestSubscriberTest.php b/core/modules/workspaces/tests/src/Unit/WorkspaceRequestSubscriberTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e5f109832d0cae7bc227ff03335a0aba78474f2e
--- /dev/null
+++ b/core/modules/workspaces/tests/src/Unit/WorkspaceRequestSubscriberTest.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Drupal\Tests\workspaces\Unit;
+
+use Drupal\Core\Path\AliasManagerInterface;
+use Drupal\Core\Path\CurrentPathStack;
+use Drupal\Core\Routing\CacheableRouteProviderInterface;
+use Drupal\Core\Routing\RouteProviderInterface;
+use Drupal\Tests\UnitTestCase;
+use Drupal\workspaces\EventSubscriber\WorkspaceRequestSubscriber;
+use Drupal\workspaces\WorkspaceInterface;
+use Drupal\workspaces\WorkspaceManagerInterface;
+use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+
+/**
+ * @coversDefaultClass \Drupal\workspaces\EventSubscriber\WorkspaceRequestSubscriber
+ *
+ * @group workspace
+ */
+class WorkspaceRequestSubscriberTest extends UnitTestCase {
+
+  /**
+   * @var \Drupal\Core\Path\AliasManagerInterface
+   */
+  protected $aliasManager;
+
+  /**
+   * @var \Drupal\Core\Path\CurrentPathStack
+   */
+  protected $currentPath;
+
+  /**
+   * @var \Drupal\workspaces\WorkspaceManagerInterface
+   */
+  protected $workspaceManager;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->aliasManager = $this->prophesize(AliasManagerInterface::class)->reveal();
+    $this->currentPath = $this->prophesize(CurrentPathStack::class)->reveal();
+    $this->workspaceManager = $this->prophesize(WorkspaceManagerInterface::class);
+
+    $active_workspace = $this->prophesize(WorkspaceInterface::class);
+    $active_workspace->id()->willReturn('test');
+    $this->workspaceManager->getActiveWorkspace()->willReturn($active_workspace->reveal());
+    $this->workspaceManager->hasActiveWorkspace()->willReturn(TRUE);
+  }
+
+  /**
+   * @covers ::onKernelRequest
+   */
+  public function testOnKernelRequestWithCacheableRouteProvider() {
+    $route_provider = $this->prophesize(CacheableRouteProviderInterface::class);
+    $route_provider->addExtraCacheKeyPart('workspace', 'test')->shouldBeCalled();
+
+    // Check that WorkspaceRequestSubscriber::onKernelRequest() calls
+    // addExtraCacheKeyPart() on a route provider that implements
+    // CacheableRouteProviderInterface.
+    $workspace_request_subscriber = new WorkspaceRequestSubscriber($this->aliasManager, $this->currentPath, $route_provider->reveal(), $this->workspaceManager->reveal());
+    $event = $this->prophesize(GetResponseEvent::class)->reveal();
+    $this->assertNull($workspace_request_subscriber->onKernelRequest($event));
+  }
+
+  /**
+   * @covers ::onKernelRequest
+   */
+  public function testOnKernelRequestWithoutCacheableRouteProvider() {
+    $route_provider = $this->prophesize(RouteProviderInterface::class);
+
+    // Check that WorkspaceRequestSubscriber::onKernelRequest() doesn't call
+    // addExtraCacheKeyPart() on a route provider that does not implement
+    // CacheableRouteProviderInterface.
+    $workspace_request_subscriber = new WorkspaceRequestSubscriber($this->aliasManager, $this->currentPath, $route_provider->reveal(), $this->workspaceManager->reveal());
+    $event = $this->prophesize(GetResponseEvent::class)->reveal();
+    $this->assertNull($workspace_request_subscriber->onKernelRequest($event));
+  }
+
+}
diff --git a/core/modules/workspaces/workspaces.services.yml b/core/modules/workspaces/workspaces.services.yml
index 45f1d50746141b382da9faf3884bdcdf826716a1..358cfc23a7c7a62bc80a0f63656fe8091e8274c4 100644
--- a/core/modules/workspaces/workspaces.services.yml
+++ b/core/modules/workspaces/workspaces.services.yml
@@ -35,6 +35,11 @@ services:
     arguments: ['@entity.definition_update_manager', '@entity.last_installed_schema.repository', '@workspaces.manager']
     tags:
       - { name: 'event_subscriber' }
+  workspaces.workspace_subscriber:
+    class: Drupal\workspaces\EventSubscriber\WorkspaceRequestSubscriber
+    arguments: ['@path.alias_manager', '@path.current', '@router.route_provider', '@workspaces.manager']
+    tags:
+      - { name: event_subscriber }
 
   cache_context.workspace:
     class: Drupal\workspaces\WorkspaceCacheContext
diff --git a/core/tests/Drupal/FunctionalTests/Rest/PathAliasResourceTestBase.php b/core/tests/Drupal/FunctionalTests/Rest/PathAliasResourceTestBase.php
index 29adab111dc162fc444017081d1eb7d5197688e3..784a0230ea93bb0424f79074cfd3b35be491ca11 100644
--- a/core/tests/Drupal/FunctionalTests/Rest/PathAliasResourceTestBase.php
+++ b/core/tests/Drupal/FunctionalTests/Rest/PathAliasResourceTestBase.php
@@ -83,6 +83,11 @@ protected function getExpectedNormalizedEntity() {
           'value' => '/frontpage1',
         ],
       ],
+      'status' => [
+        [
+          'value' => TRUE,
+        ],
+      ],
       'uuid' => [
         [
           'value' => $this->entity->uuid(),
diff --git a/core/tests/Drupal/KernelTests/Core/Routing/RouteProviderTest.php b/core/tests/Drupal/KernelTests/Core/Routing/RouteProviderTest.php
index 171968792287b0ab38c57e410692a2f0f58e668d..f439164c4e257b91844e7cb17dd11508dd119e28 100644
--- a/core/tests/Drupal/KernelTests/Core/Routing/RouteProviderTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Routing/RouteProviderTest.php
@@ -546,7 +546,7 @@ public function testRouteCaching() {
     $request = Request::create($path, 'GET');
     $provider->getRouteCollectionForRequest($request);
 
-    $cache = $this->cache->get('route:en:/path/add/one:');
+    $cache = $this->cache->get('route:[language]=en:/path/add/one:');
     $this->assertEqual('/path/add/one', $cache->data['path']);
     $this->assertEqual([], $cache->data['query']);
     $this->assertEqual(3, count($cache->data['routes']));
@@ -556,7 +556,7 @@ public function testRouteCaching() {
     $request = Request::create($path, 'GET');
     $provider->getRouteCollectionForRequest($request);
 
-    $cache = $this->cache->get('route:en:/path/add/one:foo=bar');
+    $cache = $this->cache->get('route:[language]=en:/path/add/one:foo=bar');
     $this->assertEqual('/path/add/one', $cache->data['path']);
     $this->assertEqual(['foo' => 'bar'], $cache->data['query']);
     $this->assertEqual(3, count($cache->data['routes']));
@@ -566,7 +566,7 @@ public function testRouteCaching() {
     $request = Request::create($path, 'GET');
     $provider->getRouteCollectionForRequest($request);
 
-    $cache = $this->cache->get('route:en:/path/1/one:');
+    $cache = $this->cache->get('route:[language]=en:/path/1/one:');
     $this->assertEqual('/path/1/one', $cache->data['path']);
     $this->assertEqual([], $cache->data['query']);
     $this->assertEqual(2, count($cache->data['routes']));
@@ -583,7 +583,7 @@ public function testRouteCaching() {
     $request = Request::create($path, 'GET');
     $provider->getRouteCollectionForRequest($request);
 
-    $cache = $this->cache->get('route:en:/path/add-one:');
+    $cache = $this->cache->get('route:[language]=en:/path/add-one:');
     $this->assertEqual('/path/add/one', $cache->data['path']);
     $this->assertEqual([], $cache->data['query']);
     $this->assertEqual(3, count($cache->data['routes']));
@@ -598,7 +598,7 @@ public function testRouteCaching() {
     $request = Request::create($path, 'GET');
     $provider->getRouteCollectionForRequest($request);
 
-    $cache = $this->cache->get('route:gsw-berne:/path/add-one:');
+    $cache = $this->cache->get('route:[language]=gsw-berne:/path/add-one:');
     $this->assertEquals('/path/add/one', $cache->data['path']);
     $this->assertEquals([], $cache->data['query']);
     $this->assertEquals(3, count($cache->data['routes']));