diff --git a/core/core.services.yml b/core/core.services.yml
index 4d178eb5f9396eadb3c26c102e975d658be240d0..3f76a3e37e663a100855e6712ab1db15776401d8 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1218,10 +1218,10 @@ services:
     arguments: ['@current_user']
   ajax_response.attachments_processor:
     class: Drupal\Core\Ajax\AjaxResponseAttachmentsProcessor
-    arguments: ['@asset.resolver', '@config.factory', '@asset.css.collection_renderer', '@asset.js.collection_renderer', '@request_stack', '@renderer', '@module_handler']
+    arguments: ['@asset.resolver', '@config.factory', '@asset.css.collection_renderer', '@asset.js.collection_renderer', '@request_stack', '@renderer', '@module_handler', '@language_manager']
   html_response.attachments_processor:
     class: Drupal\Core\Render\HtmlResponseAttachmentsProcessor
-    arguments: ['@asset.resolver', '@config.factory', '@asset.css.collection_renderer', '@asset.js.collection_renderer', '@request_stack', '@renderer', '@module_handler']
+    arguments: ['@asset.resolver', '@config.factory', '@asset.css.collection_renderer', '@asset.js.collection_renderer', '@request_stack', '@renderer', '@module_handler', '@language_manager']
   html_response.subscriber:
     class: Drupal\Core\EventSubscriber\HtmlResponseSubscriber
     tags:
@@ -1480,8 +1480,8 @@ services:
     class: Drupal\Core\Asset\CssCollectionRenderer
     arguments: [ '@state', '@file_url_generator' ]
   asset.css.collection_optimizer:
-    class: Drupal\Core\Asset\CssCollectionOptimizer
-    arguments: [ '@asset.css.collection_grouper', '@asset.css.optimizer', '@asset.css.dumper', '@state', '@file_system']
+    class: Drupal\Core\Asset\CssCollectionOptimizerLazy
+    arguments: [ '@asset.css.collection_grouper', '@asset.css.optimizer', '@theme.manager', '@library.dependency_resolver', '@request_stack', '@file_system', '@config.factory', '@file_url_generator', '@datetime.time', '@language_manager', '@state']
   asset.css.optimizer:
     class: Drupal\Core\Asset\CssOptimizer
     arguments: ['@file_url_generator']
@@ -1494,8 +1494,8 @@ services:
     class: Drupal\Core\Asset\JsCollectionRenderer
     arguments: [ '@state', '@file_url_generator' ]
   asset.js.collection_optimizer:
-    class: Drupal\Core\Asset\JsCollectionOptimizer
-    arguments: [ '@asset.js.collection_grouper', '@asset.js.optimizer', '@asset.js.dumper', '@state', '@file_system']
+    class: Drupal\Core\Asset\JsCollectionOptimizerLazy
+    arguments: [ '@asset.js.collection_grouper', '@asset.js.optimizer', '@theme.manager', '@library.dependency_resolver', '@request_stack', '@file_system', '@config.factory', '@file_url_generator', '@datetime.time', '@language_manager', '@state']
   asset.js.optimizer:
     class: Drupal\Core\Asset\JsOptimizer
   asset.js.collection_grouper:
diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php b/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php
index eaeed18ef2cac1b3992a92b97b4d6c8de3e23ef6..57228a0577cc70e955d2118cf2785588bcc42aa2 100644
--- a/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php
+++ b/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php
@@ -7,6 +7,7 @@
 use Drupal\Core\Asset\AttachedAssets;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Render\AttachmentsInterface;
 use Drupal\Core\Render\AttachmentsResponseProcessorInterface;
 use Drupal\Core\Render\RendererInterface;
@@ -87,8 +88,10 @@ class AjaxResponseAttachmentsProcessor implements AttachmentsResponseProcessorIn
    *   The renderer.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
+   *   The language manager.
    */
-  public function __construct(AssetResolverInterface $asset_resolver, ConfigFactoryInterface $config_factory, AssetCollectionRendererInterface $css_collection_renderer, AssetCollectionRendererInterface $js_collection_renderer, RequestStack $request_stack, RendererInterface $renderer, ModuleHandlerInterface $module_handler) {
+  public function __construct(AssetResolverInterface $asset_resolver, ConfigFactoryInterface $config_factory, AssetCollectionRendererInterface $css_collection_renderer, AssetCollectionRendererInterface $js_collection_renderer, RequestStack $request_stack, RendererInterface $renderer, ModuleHandlerInterface $module_handler, protected LanguageManagerInterface $languageManager) {
     $this->assetResolver = $asset_resolver;
     $this->config = $config_factory->get('system.performance');
     $this->cssCollectionRenderer = $css_collection_renderer;
@@ -96,6 +99,10 @@ public function __construct(AssetResolverInterface $asset_resolver, ConfigFactor
     $this->requestStack = $request_stack;
     $this->renderer = $renderer;
     $this->moduleHandler = $module_handler;
+    if (!isset($languageManager)) {
+      @trigger_error('Calling ' . __METHOD__ . '() without the $language_manager argument is deprecated in drupal:10.1.0 and will be required in drupal:11.0.0', E_USER_DEPRECATED);
+      $this->languageManager = \Drupal::languageManager();
+    }
   }
 
   /**
@@ -141,8 +148,8 @@ protected function buildAttachmentsCommands(AjaxResponse $response, Request $req
     $assets->setLibraries($attachments['library'] ?? [])
       ->setAlreadyLoadedLibraries(isset($ajax_page_state['libraries']) ? explode(',', $ajax_page_state['libraries']) : [])
       ->setSettings($attachments['drupalSettings'] ?? []);
-    $css_assets = $this->assetResolver->getCssAssets($assets, $optimize_css);
-    [$js_assets_header, $js_assets_footer] = $this->assetResolver->getJsAssets($assets, $optimize_js);
+    $css_assets = $this->assetResolver->getCssAssets($assets, $optimize_css, $this->languageManager->getCurrentLanguage());
+    [$js_assets_header, $js_assets_footer] = $this->assetResolver->getJsAssets($assets, $optimize_js, $this->languageManager->getCurrentLanguage());
 
     // First, AttachedAssets::setLibraries() ensures duplicate libraries are
     // removed: it converts it to a set of libraries if necessary. Second,
diff --git a/core/lib/Drupal/Core/Asset/AssetCollectionGroupOptimizerInterface.php b/core/lib/Drupal/Core/Asset/AssetCollectionGroupOptimizerInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..244013fc581482cc3e6f6cc1da67098cecf83377
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/AssetCollectionGroupOptimizerInterface.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Drupal\Core\Asset;
+
+/**
+ * Interface defining a service that optimizes a collection of assets.
+ *
+ * Contains an additional method to allow for optimizing an asset group.
+ */
+interface AssetCollectionGroupOptimizerInterface extends AssetCollectionOptimizerInterface {
+
+  /**
+   * Optimizes a specific group of assets.
+   *
+   * @param array $group
+   *   An asset group.
+   *
+   * @return string
+   *   The optimized string for the group.
+   */
+  public function optimizeGroup(array $group): string;
+
+}
diff --git a/core/lib/Drupal/Core/Asset/AssetCollectionOptimizerInterface.php b/core/lib/Drupal/Core/Asset/AssetCollectionOptimizerInterface.php
index 526acf50325120327333dc2b995c83d92799cfcb..80557a713feaaf52b43e9c41882977a620992a52 100644
--- a/core/lib/Drupal/Core/Asset/AssetCollectionOptimizerInterface.php
+++ b/core/lib/Drupal/Core/Asset/AssetCollectionOptimizerInterface.php
@@ -12,11 +12,13 @@ interface AssetCollectionOptimizerInterface {
    *
    * @param array $assets
    *   An asset collection.
+   * @param array $libraries
+   *   An array of library names.
    *
    * @return array
    *   An optimized asset collection.
    */
-  public function optimize(array $assets);
+  public function optimize(array $assets, array $libraries);
 
   /**
    * Returns all optimized asset collections assets.
diff --git a/core/lib/Drupal/Core/Asset/AssetDumper.php b/core/lib/Drupal/Core/Asset/AssetDumper.php
index 6f90f8f5560f29331bbc8bff6f01c7722319db57..91360e5bc4642a132ab14afbcafda0aac1484507 100644
--- a/core/lib/Drupal/Core/Asset/AssetDumper.php
+++ b/core/lib/Drupal/Core/Asset/AssetDumper.php
@@ -9,7 +9,7 @@
 /**
  * Dumps a CSS or JavaScript asset.
  */
-class AssetDumper implements AssetDumperInterface {
+class AssetDumper implements AssetDumperUriInterface {
 
   /**
    * The file system service.
@@ -36,12 +36,19 @@ public function __construct(FileSystemInterface $file_system) {
    * browsers to download new CSS when the CSS changes.
    */
   public function dump($data, $file_extension) {
+    $path = 'public://' . $file_extension;
     // Prefix filename to prevent blocking by firewalls which reject files
     // starting with "ad*".
     $filename = $file_extension . '_' . Crypt::hashBase64($data) . '.' . $file_extension;
-    // Create the css/ or js/ path within the files folder.
-    $path = 'public://' . $file_extension;
     $uri = $path . '/' . $filename;
+    return $this->dumpToUri($data, $file_extension, $uri);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function dumpToUri(string $data, string $file_extension, string $uri): string {
+    $path = 'public://' . $file_extension;
     // Create the CSS or JS file.
     $this->fileSystem->prepareDirectory($path, FileSystemInterface::CREATE_DIRECTORY);
     try {
diff --git a/core/lib/Drupal/Core/Asset/AssetDumperUriInterface.php b/core/lib/Drupal/Core/Asset/AssetDumperUriInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..cf7085fef76fd3efc144515fc58c647bd6820201
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/AssetDumperUriInterface.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Drupal\Core\Asset;
+
+/**
+ * Interface defining a service that dumps an asset to a specified location.
+ */
+interface AssetDumperUriInterface extends AssetDumperInterface {
+
+  /**
+   * Dumps an (optimized) asset to persistent storage.
+   *
+   * @param string $data
+   *   The asset's contents.
+   * @param string $file_extension
+   *   The file extension of this asset.
+   * @param string $uri
+   *   The URI to dump to.
+   *
+   * @return string
+   *   An URI to access the dumped asset.
+   */
+  public function dumpToUri(string $data, string $file_extension, string $uri): string;
+
+}
diff --git a/core/lib/Drupal/Core/Asset/AssetGroupSetHashTrait.php b/core/lib/Drupal/Core/Asset/AssetGroupSetHashTrait.php
new file mode 100644
index 0000000000000000000000000000000000000000..1d805b4c329be72f4bbae7f78b5ba87cd9494db8
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/AssetGroupSetHashTrait.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Drupal\Core\Asset;
+
+use Drupal\Component\Utility\Crypt;
+use Drupal\Core\Site\Settings;
+
+/**
+ * Provides a method to generate a normalized hash of a given asset group set.
+ */
+trait AssetGroupSetHashTrait {
+
+  /**
+   * Generates a hash for an array of asset groups.
+   *
+   * @param array $group
+   *   An asset group.
+   *
+   * @return string
+   *   A hash to uniquely identify the groups.
+   */
+  protected function generateHash(array $group): string {
+    $normalized = [];
+    $group_keys = [
+      'type' => NULL,
+      'group' => NULL,
+      'media' => NULL,
+      'browsers' => NULL,
+    ];
+
+    $normalized['asset_group'] = array_intersect_key($group, $group_keys);
+    $normalized['asset_group']['items'] = [];
+    // Remove some keys to make the hash more stable.
+    $omit_keys = [
+      'weight' => NULL,
+    ];
+    foreach ($group['items'] as $key => $asset) {
+      $normalized['asset_group']['items'][$key] = array_diff_key($asset, $group_keys, $omit_keys);
+    }
+    // The asset array ensures that a valid hash can only be generated via the
+    // same code base. Additionally use the hash salt to ensure that hashes are
+    // not re-usable between different installations.
+    return Crypt::hmacBase64(serialize($normalized), Settings::getHashSalt());
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Asset/AssetResolver.php b/core/lib/Drupal/Core/Asset/AssetResolver.php
index 6f477d19f68eb9728c38da6be6c0f440f5266831..f4ca58bb3fb6ad5c7add95515998e4a45e6dd735 100644
--- a/core/lib/Drupal/Core/Asset/AssetResolver.php
+++ b/core/lib/Drupal/Core/Asset/AssetResolver.php
@@ -7,6 +7,7 @@
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Theme\ThemeManagerInterface;
 
 /**
@@ -109,12 +110,15 @@ protected function getLibrariesToLoad(AttachedAssetsInterface $assets) {
   /**
    * {@inheritdoc}
    */
-  public function getCssAssets(AttachedAssetsInterface $assets, $optimize) {
+  public function getCssAssets(AttachedAssetsInterface $assets, $optimize, LanguageInterface $language = NULL) {
+    if (!isset($language)) {
+      $language = $this->languageManager->getCurrentLanguage();
+    }
     $theme_info = $this->themeManager->getActiveTheme();
     // Add the theme name to the cache key since themes may implement
     // hook_library_info_alter().
     $libraries_to_load = $this->getLibrariesToLoad($assets);
-    $cid = 'css:' . $theme_info->getName() . ':' . Crypt::hashBase64(serialize($libraries_to_load)) . (int) $optimize;
+    $cid = 'css:' . $theme_info->getName() . ':' . $language->getId() . Crypt::hashBase64(serialize($libraries_to_load)) . (int) $optimize;
     if ($cached = $this->cache->get($cid)) {
       return $cached->data;
     }
@@ -151,14 +155,14 @@ public function getCssAssets(AttachedAssetsInterface $assets, $optimize) {
     }
 
     // Allow modules and themes to alter the CSS assets.
-    $this->moduleHandler->alter('css', $css, $assets);
-    $this->themeManager->alter('css', $css, $assets);
+    $this->moduleHandler->alter('css', $css, $assets, $language);
+    $this->themeManager->alter('css', $css, $assets, $language);
 
     // Sort CSS items, so that they appear in the correct order.
     uasort($css, [static::class, 'sort']);
 
     if ($optimize) {
-      $css = \Drupal::service('asset.css.collection_optimizer')->optimize($css);
+      $css = \Drupal::service('asset.css.collection_optimizer')->optimize($css, $libraries_to_load, $language);
     }
     $this->cache->set($cid, $css, CacheBackendInterface::CACHE_PERMANENT, ['library_info']);
 
@@ -194,13 +198,16 @@ protected function getJsSettingsAssets(AttachedAssetsInterface $assets) {
   /**
    * {@inheritdoc}
    */
-  public function getJsAssets(AttachedAssetsInterface $assets, $optimize) {
+  public function getJsAssets(AttachedAssetsInterface $assets, $optimize, LanguageInterface $language = NULL) {
+    if (!isset($language)) {
+      $language = $this->languageManager->getCurrentLanguage();
+    }
     $theme_info = $this->themeManager->getActiveTheme();
     // Add the theme name to the cache key since themes may implement
     // hook_library_info_alter(). Additionally add the current language to
     // support translation of JavaScript files via hook_js_alter().
     $libraries_to_load = $this->getLibrariesToLoad($assets);
-    $cid = 'js:' . $theme_info->getName() . ':' . $this->languageManager->getCurrentLanguage()->getId() . ':' . Crypt::hashBase64(serialize($libraries_to_load)) . (int) (count($assets->getSettings()) > 0) . (int) $optimize;
+    $cid = 'js:' . $theme_info->getName() . ':' . $language->getId() . ':' . Crypt::hashBase64(serialize($libraries_to_load)) . (int) (count($assets->getSettings()) > 0) . (int) $optimize;
 
     if ($cached = $this->cache->get($cid)) {
       [$js_assets_header, $js_assets_footer, $settings, $settings_in_header] = $cached->data;
@@ -258,8 +265,8 @@ public function getJsAssets(AttachedAssetsInterface $assets, $optimize) {
       }
 
       // Allow modules and themes to alter the JavaScript assets.
-      $this->moduleHandler->alter('js', $javascript, $assets);
-      $this->themeManager->alter('js', $javascript, $assets);
+      $this->moduleHandler->alter('js', $javascript, $assets, $language);
+      $this->themeManager->alter('js', $javascript, $assets, $language);
 
       // Sort JavaScript assets, so that they appear in the correct order.
       uasort($javascript, [static::class, 'sort']);
@@ -278,8 +285,8 @@ public function getJsAssets(AttachedAssetsInterface $assets, $optimize) {
 
       if ($optimize) {
         $collection_optimizer = \Drupal::service('asset.js.collection_optimizer');
-        $js_assets_header = $collection_optimizer->optimize($js_assets_header);
-        $js_assets_footer = $collection_optimizer->optimize($js_assets_footer);
+        $js_assets_header = $collection_optimizer->optimize($js_assets_header, $libraries_to_load);
+        $js_assets_footer = $collection_optimizer->optimize($js_assets_footer, $libraries_to_load);
       }
 
       // If the core/drupalSettings library is being loaded or is already
diff --git a/core/lib/Drupal/Core/Asset/AssetResolverInterface.php b/core/lib/Drupal/Core/Asset/AssetResolverInterface.php
index 66f6ec095ab770b3953438dd1dfdd4c721d9d486..9100ebd57c75afe32c1143fe17be672bba58f5a9 100644
--- a/core/lib/Drupal/Core/Asset/AssetResolverInterface.php
+++ b/core/lib/Drupal/Core/Asset/AssetResolverInterface.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Core\Asset;
 
+use Drupal\Core\Language\LanguageInterface;
+
 /**
  * Resolves asset libraries into concrete CSS and JavaScript assets.
  *
@@ -43,11 +45,13 @@ interface AssetResolverInterface {
    * @param bool $optimize
    *   Whether to apply the CSS asset collection optimizer, to return an
    *   optimized CSS asset collection rather than an unoptimized one.
+   * @param \Drupal\Core\Language\LanguageInterface $language
+   *   (optional) The interface language the assets will be rendered with.
    *
    * @return array
    *   A (possibly optimized) collection of CSS assets.
    */
-  public function getCssAssets(AttachedAssetsInterface $assets, $optimize);
+  public function getCssAssets(AttachedAssetsInterface $assets, $optimize, LanguageInterface $language = NULL);
 
   /**
    * Returns the JavaScript assets for the current response's libraries.
@@ -69,6 +73,8 @@ public function getCssAssets(AttachedAssetsInterface $assets, $optimize);
    * @param bool $optimize
    *   Whether to apply the JavaScript asset collection optimizer, to return
    *   optimized JavaScript asset collections rather than an unoptimized ones.
+   * @param \Drupal\Core\Language\LanguageInterface $language
+   *   (optional) The interface language for the assets will be rendered with.
    *
    * @return array
    *   A nested array containing 2 values:
@@ -77,6 +83,6 @@ public function getCssAssets(AttachedAssetsInterface $assets, $optimize);
    *   - at index one: the (possibly optimized) collection of JavaScript assets
    *     for the bottom of the page
    */
-  public function getJsAssets(AttachedAssetsInterface $assets, $optimize);
+  public function getJsAssets(AttachedAssetsInterface $assets, $optimize, LanguageInterface $language = NULL);
 
 }
diff --git a/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php b/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php
index 7d47dad86e6ce688188f5f9a24647c06f9733524..8841866ff0975a6ae592484438b74f91ee0230ea 100644
--- a/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php
+++ b/core/lib/Drupal/Core/Asset/CssCollectionOptimizer.php
@@ -2,11 +2,18 @@
 
 namespace Drupal\Core\Asset;
 
+@trigger_error('The ' . __NAMESPACE__ . '\CssCollectionOptimizer is deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Instead, use ' . __NAMESPACE__ . '\CssCollectionOptimizerLazy. See https://www.drupal.org/node/2888767', E_USER_DEPRECATED);
+
 use Drupal\Core\File\FileSystemInterface;
 use Drupal\Core\State\StateInterface;
 
 /**
  * Optimizes CSS assets.
+ *
+ *  @deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Instead, use
+ *    \Drupal\Core\Asset\CssCollectionOptimizerLazy.
+ *
+ * @see https://www.drupal.org/node/2888767
  */
 class CssCollectionOptimizer implements AssetCollectionOptimizerInterface {
 
@@ -81,7 +88,7 @@ public function __construct(AssetCollectionGrouperInterface $grouper, AssetOptim
    * configurable period (@code system.performance.stale_file_threshold @endcode)
    * to ensure that files referenced by a cached page will still be available.
    */
-  public function optimize(array $css_assets) {
+  public function optimize(array $css_assets, array $libraries) {
     // Group the assets.
     $css_groups = $this->grouper->group($css_assets);
 
diff --git a/core/lib/Drupal/Core/Asset/CssCollectionOptimizerLazy.php b/core/lib/Drupal/Core/Asset/CssCollectionOptimizerLazy.php
new file mode 100644
index 0000000000000000000000000000000000000000..1c273438ca8684ca6d82cc9f1ec4a966b5f9a518
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/CssCollectionOptimizerLazy.php
@@ -0,0 +1,180 @@
+<?php
+
+namespace Drupal\Core\Asset;
+
+use Drupal\Component\Datetime\TimeInterface;
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\File\FileSystemInterface;
+use Drupal\Core\File\FileUrlGeneratorInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\State\StateInterface;
+use Drupal\Core\Theme\ThemeManagerInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Optimizes CSS assets.
+ */
+class CssCollectionOptimizerLazy implements AssetCollectionGroupOptimizerInterface {
+
+  use AssetGroupSetHashTrait;
+
+  /**
+   * Constructs a CssCollectionOptimizerLazy.
+   *
+   * @param \Drupal\Core\Asset\AssetCollectionGrouperInterface $grouper
+   *   The grouper for CSS assets.
+   * @param \Drupal\Core\Asset\AssetOptimizerInterface $optimizer
+   *   The asset optimizer.
+   * @param \Drupal\Core\Theme\ThemeManagerInterface $themeManager
+   *   The theme manager.
+   * @param \Drupal\Core\Asset\LibraryDependencyResolverInterface $dependencyResolver
+   *   The library dependency resolver.
+   * @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
+   *   The request stack.
+   * @param \Drupal\Core\File\FileSystemInterface $fileSystem
+   *   The file system service.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
+   *   The config factory.
+   * @param \Drupal\Core\File\FileUrlGeneratorInterface $fileUrlGenerator
+   *   The file URL generator.
+   * @param \Drupal\Component\Datetime\TimeInterface $time
+   *   The time service.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
+   *   The language manager.
+   * @param \Drupal\Core\State\StateInterface $state
+   *   The state key/value store.
+   */
+  public function __construct(
+    protected readonly AssetCollectionGrouperInterface $grouper,
+    protected readonly AssetOptimizerInterface $optimizer,
+    protected readonly ThemeManagerInterface $themeManager,
+    protected readonly LibraryDependencyResolverInterface $dependencyResolver,
+    protected readonly RequestStack $requestStack,
+    protected readonly FileSystemInterface $fileSystem,
+    protected readonly ConfigFactoryInterface $configFactory,
+    protected readonly FileUrlGeneratorInterface $fileUrlGenerator,
+    protected readonly TimeInterface $time,
+    protected readonly LanguageManagerInterface $languageManager,
+    protected readonly StateInterface $state
+  ) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function optimize(array $css_assets, array $libraries) {
+    // File names are generated based on library/asset definitions. This
+    // includes a hash of the assets and the group index. Additionally, the full
+    // set of libraries, already loaded libraries and theme are sent as query
+    // parameters to allow a PHP controller to generate a valid file with
+    // sufficient information. Files are not generated by this method since
+    // they're assumed to be successfully returned from the URL created whether
+    // on disk or not.
+
+    // Group the assets.
+    $css_groups = $this->grouper->group($css_assets);
+
+    $css_assets = [];
+    foreach ($css_groups as $order => $css_group) {
+      // We have to return a single asset, not a group of assets. It is now up
+      // to one of the pieces of code in the switch statement below to set the
+      // 'data' property to the appropriate value.
+      $css_assets[$order] = $css_group;
+
+      if ($css_group['type'] === 'file') {
+        // No preprocessing, single CSS asset: just use the existing URI.
+        if (!$css_group['preprocess']) {
+          $uri = $css_group['items'][0]['data'];
+          $css_assets[$order]['data'] = $uri;
+        }
+        else {
+          // To reproduce the full context of assets outside of the request,
+          // we must know the entire set of libraries used to generate all CSS
+          // groups, whether or not files in a group are from a particular
+          // library or not.
+          $css_assets[$order]['preprocessed'] = TRUE;
+        }
+      }
+      if ($css_group['type'] === 'external') {
+        // We don't do any aggregation and hence also no caching for external
+        // CSS assets.
+        $uri = $css_group['items'][0]['data'];
+        $css_assets[$order]['data'] = $uri;
+      }
+    }
+    // Generate a URL for each group of assets, but do not process them inline,
+    // this is done using optimizeGroup() when the asset path is requested.
+    $ajax_page_state = $this->requestStack->getCurrentRequest()->get('ajax_page_state');
+    $already_loaded = isset($ajax_page_state) ? explode(',', $ajax_page_state['libraries']) : [];
+    $query_args = [
+      'language' => $this->languageManager->getCurrentLanguage()->getId(),
+      'theme' => $this->themeManager->getActiveTheme()->getName(),
+      'include' => implode(',', $this->dependencyResolver->getMinimalRepresentativeSubset($libraries)),
+    ];
+    if ($already_loaded) {
+      $query_args['exclude'] = implode(',', $this->dependencyResolver->getMinimalRepresentativeSubset($already_loaded));
+    }
+    foreach ($css_assets as $order => $css_asset) {
+      if (!empty($css_asset['preprocessed'])) {
+        $query = ['delta' => "$order"] + $query_args;
+        $filename = 'css_' . $this->generateHash($css_asset) . '.css';
+        $uri = 'public://css/' . $filename;
+        $css_assets[$order]['data'] = $this->fileUrlGenerator->generateAbsoluteString($uri) . '?' . UrlHelper::buildQuery($query);
+      }
+      unset($css_assets[$order]['items']);
+    }
+
+    return $css_assets;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAll() {
+    return $this->state->get('drupal_css_cache_files', []);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteAll() {
+    $this->state->delete('drupal_css_cache_files');
+
+    $delete_stale = function ($uri) {
+      $threshold = $this->configFactory
+        ->get('system.performance')
+        ->get('stale_file_threshold');
+      // Default stale file threshold is 30 days.
+      if ($this->time->getRequestTime() - filemtime($uri) > $threshold) {
+        $this->fileSystem->delete($uri);
+      }
+    };
+    if (is_dir('public://css')) {
+      $this->fileSystem->scanDirectory('public://css', '/.*/', ['callback' => $delete_stale]);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function optimizeGroup(array $group): string {
+    // Optimize each asset within the group.
+    $data = '';
+    foreach ($group['items'] as $css_asset) {
+      $data .= $this->optimizer->optimize($css_asset);
+    }
+    // Per the W3C specification at
+    // http://www.w3.org/TR/REC-CSS2/cascade.html#at-import, @import rules must
+    // precede any other style, so we move those to the top. The regular
+    // expression is expressed in NOWDOC since it is detecting backslashes as
+    // well as single and double quotes. It is difficult to read when
+    // represented as a quoted string.
+    $regexp = <<<'REGEXP'
+/@import\s*(?:'(?:\\'|.)*'|"(?:\\"|.)*"|url\(\s*(?:\\[\)\'\"]|[^'")])*\s*\)|url\(\s*'(?:\'|.)*'\s*\)|url\(\s*"(?:\"|.)*"\s*\)).*;/iU
+REGEXP;
+    preg_match_all($regexp, $data, $matches);
+    $data = preg_replace($regexp, '', $data);
+    return implode('', $matches[0]) . $data;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php b/core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php
index 99c8ac64879870201954788f2653549a080fb816..ceb8505c438fe490df7a960f3fb980edc9beefbb 100644
--- a/core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php
+++ b/core/lib/Drupal/Core/Asset/JsCollectionOptimizer.php
@@ -2,11 +2,18 @@
 
 namespace Drupal\Core\Asset;
 
+@trigger_error('The ' . __NAMESPACE__ . '\JsCollectionOptimizer is deprecated in drupal:10.0.0 and is removed from drupal:11.0.0. Instead, use ' . __NAMESPACE__ . '\JsCollectionOptimizerLazy. See https://www.drupal.org/node/2888767', E_USER_DEPRECATED);
+
 use Drupal\Core\File\FileSystemInterface;
 use Drupal\Core\State\StateInterface;
 
 /**
  * Optimizes JavaScript assets.
+ *
+ * @deprecated in drupal:10.1.0 and is removed from drupal:11.0.0. Instead use
+ *   \Drupal\Core\Asset\JsCollectionOptimizerLazy.
+ *
+ * @see https://www.drupal.org/node/2888767
  */
 class JsCollectionOptimizer implements AssetCollectionOptimizerInterface {
 
@@ -81,7 +88,7 @@ public function __construct(AssetCollectionGrouperInterface $grouper, AssetOptim
    * configurable period (@code system.performance.stale_file_threshold @endcode)
    * to ensure that files referenced by a cached page will still be available.
    */
-  public function optimize(array $js_assets) {
+  public function optimize(array $js_assets, array $libraries) {
     // Group the assets.
     $js_groups = $this->grouper->group($js_assets);
 
diff --git a/core/lib/Drupal/Core/Asset/JsCollectionOptimizerLazy.php b/core/lib/Drupal/Core/Asset/JsCollectionOptimizerLazy.php
new file mode 100644
index 0000000000000000000000000000000000000000..3a2a7137dc914c2262e434b3c85e058d1d9e5809
--- /dev/null
+++ b/core/lib/Drupal/Core/Asset/JsCollectionOptimizerLazy.php
@@ -0,0 +1,191 @@
+<?php
+
+namespace Drupal\Core\Asset;
+
+use Drupal\Component\Datetime\TimeInterface;
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\File\FileSystemInterface;
+use Drupal\Core\File\FileUrlGeneratorInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\State\StateInterface;
+use Drupal\Core\Theme\ThemeManagerInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Optimizes JavaScript assets.
+ */
+class JsCollectionOptimizerLazy implements AssetCollectionGroupOptimizerInterface {
+
+  use AssetGroupSetHashTrait;
+
+  /**
+   * Constructs a JsCollectionOptimizerLazy.
+   *
+   * @param \Drupal\Core\Asset\AssetCollectionGrouperInterface $grouper
+   *   The grouper for JS assets.
+   * @param \Drupal\Core\Asset\AssetOptimizerInterface $optimizer
+   *   The asset optimizer.
+   * @param \Drupal\Core\Theme\ThemeManagerInterface $themeManager
+   *   The theme manager.
+   * @param \Drupal\Core\Asset\LibraryDependencyResolverInterface $dependencyResolver
+   *   The library dependency resolver.
+   * @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
+   *   The request stack.
+   * @param \Drupal\Core\File\FileSystemInterface $fileSystem
+   *   The file system service.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
+   *   The config factory.
+   * @param \Drupal\Core\File\FileUrlGeneratorInterface $fileUrlGenerator
+   *   The file URL generator.
+   * @param \Drupal\Component\Datetime\TimeInterface $time
+   *   The time service.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
+   *   The language manager.
+   * @param \Drupal\Core\State\StateInterface $state
+   *   The state key/value store.
+   */
+  public function __construct(
+    protected readonly AssetCollectionGrouperInterface $grouper,
+    protected readonly AssetOptimizerInterface $optimizer,
+    protected readonly ThemeManagerInterface $themeManager,
+    protected readonly LibraryDependencyResolverInterface $dependencyResolver,
+    protected readonly RequestStack $requestStack,
+    protected readonly FileSystemInterface $fileSystem,
+    protected readonly ConfigFactoryInterface $configFactory,
+    protected readonly FileUrlGeneratorInterface $fileUrlGenerator,
+    protected readonly TimeInterface $time,
+    protected readonly LanguageManagerInterface $languageManager,
+    protected readonly StateInterface $state
+  ) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function optimize(array $js_assets, array $libraries) {
+    // File names are generated based on library/asset definitions. This
+    // includes a hash of the assets and the group index. Additionally, the full
+    // set of libraries, already loaded libraries and theme are sent as query
+    // parameters to allow a PHP controller to generate a valid file with
+    // sufficient information. Files are not generated by this method since
+    // they're assumed to be successfully returned from the URL created whether
+    // on disk or not.
+
+    // Group the assets.
+    $js_groups = $this->grouper->group($js_assets);
+
+    $js_assets = [];
+    foreach ($js_groups as $order => $js_group) {
+      // We have to return a single asset, not a group of assets. It is now up
+      // to one of the pieces of code in the switch statement below to set the
+      // 'data' property to the appropriate value.
+      $js_assets[$order] = $js_group;
+
+      switch ($js_group['type']) {
+        case 'file':
+          // No preprocessing, single JS asset: just use the existing URI.
+          if (!$js_group['preprocess']) {
+            $uri = $js_group['items'][0]['data'];
+            $js_assets[$order]['data'] = $uri;
+          }
+          else {
+            // To reproduce the full context of assets outside of the request,
+            // we must know the entire set of libraries used to generate all CSS
+            // groups, whether or not files in a group are from a particular
+            // library or not.
+            $js_assets[$order]['preprocessed'] = TRUE;
+          }
+          break;
+
+        case 'external':
+          // We don't do any aggregation and hence also no caching for external
+          // JS assets.
+          $uri = $js_group['items'][0]['data'];
+          $js_assets[$order]['data'] = $uri;
+          break;
+
+        case 'setting':
+          $js_assets[$order]['data'] = $js_group['data'];
+          break;
+      }
+    }
+    if ($libraries) {
+      // Generate a URL for the group, but do not process it inline, this is
+      // done by \Drupal\system\controller\JsAssetController.
+      $ajax_page_state = $this->requestStack->getCurrentRequest()
+        ->get('ajax_page_state');
+      $already_loaded = isset($ajax_page_state) ? explode(',', $ajax_page_state['libraries']) : [];
+      $language = $this->languageManager->getCurrentLanguage()->getId();
+      $query_args = [
+        'language' => $language,
+        'theme' => $this->themeManager->getActiveTheme()->getName(),
+        'include' => implode(',', $this->dependencyResolver->getMinimalRepresentativeSubset($libraries)),
+      ];
+      if ($already_loaded) {
+        $query_args['exclude'] = implode(',', $this->dependencyResolver->getMinimalRepresentativeSubset($already_loaded));
+      }
+      foreach ($js_assets as $order => $js_asset) {
+        if (!empty($js_asset['preprocessed'])) {
+          $query = [
+            'scope' => $js_asset['scope'] === 'header' ? 'header' : 'footer',
+            'delta' => "$order",
+          ] + $query_args;
+          $filename = 'js_' . $this->generateHash($js_asset) . '.js';
+          $uri = 'public://js/' . $filename;
+          $js_assets[$order]['data'] = $this->fileUrlGenerator->generateAbsoluteString($uri) . '?' . UrlHelper::buildQuery($query);
+        }
+        unset($js_assets[$order]['items']);
+      }
+    }
+
+    return $js_assets;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAll() {
+    return $this->state->get('system.js_cache_files', []);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteAll() {
+    $this->state->delete('system.js_cache_files');
+    $delete_stale = function ($uri) {
+      $threshold = $this->configFactory
+        ->get('system.performance')
+        ->get('stale_file_threshold');
+      // Default stale file threshold is 30 days.
+      if ($this->time->getRequestTime() - filemtime($uri) > $threshold) {
+        $this->fileSystem->delete($uri);
+      }
+    };
+    if (is_dir('public://js')) {
+      $this->fileSystem->scanDirectory('public://js', '/.*/', ['callback' => $delete_stale]);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function optimizeGroup(array $group): string {
+    $data = '';
+    foreach ($group['items'] as $js_asset) {
+      // Optimize this JS file, but only if it's not yet minified.
+      if (isset($js_asset['minified']) && $js_asset['minified']) {
+        $data .= file_get_contents($js_asset['data']);
+      }
+      else {
+        $data .= $this->optimizer->optimize($js_asset);
+      }
+      // Append a ';' and a newline after each JS file to prevent them from
+      // running together.
+      $data .= ";\n";
+    }
+    // Remove unwanted JS code that causes issues.
+    return $this->optimizer->clean($data);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php b/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php
index 7df92bdb6db6436bcfb47151741eb14c0a6b7b29..c932f84181097bcc8fb79b95ae530a35ff3ba9b4 100644
--- a/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php
+++ b/core/lib/Drupal/Core/Render/HtmlResponseAttachmentsProcessor.php
@@ -9,6 +9,7 @@
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Form\EnforcedResponseException;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Component\Utility\Html;
 use Symfony\Component\HttpFoundation\RequestStack;
 
@@ -97,8 +98,10 @@ class HtmlResponseAttachmentsProcessor implements AttachmentsResponseProcessorIn
    *   The renderer.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler service.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $languageManager
+   *   The language manager.
    */
-  public function __construct(AssetResolverInterface $asset_resolver, ConfigFactoryInterface $config_factory, AssetCollectionRendererInterface $css_collection_renderer, AssetCollectionRendererInterface $js_collection_renderer, RequestStack $request_stack, RendererInterface $renderer, ModuleHandlerInterface $module_handler) {
+  public function __construct(AssetResolverInterface $asset_resolver, ConfigFactoryInterface $config_factory, AssetCollectionRendererInterface $css_collection_renderer, AssetCollectionRendererInterface $js_collection_renderer, RequestStack $request_stack, RendererInterface $renderer, ModuleHandlerInterface $module_handler, protected LanguageManagerInterface $languageManager) {
     $this->assetResolver = $asset_resolver;
     $this->config = $config_factory->get('system.performance');
     $this->cssCollectionRenderer = $css_collection_renderer;
@@ -106,6 +109,10 @@ public function __construct(AssetResolverInterface $asset_resolver, ConfigFactor
     $this->requestStack = $request_stack;
     $this->renderer = $renderer;
     $this->moduleHandler = $module_handler;
+    if (!isset($languageManager)) {
+      @trigger_error('Calling ' . __METHOD__ . '() without the $languageManager argument is deprecated in drupal:10.1.0 and will be required in drupal:11.0.0', E_USER_DEPRECATED);
+      $this->languageManager = \Drupal::languageManager();
+    }
   }
 
   /**
@@ -309,14 +316,14 @@ protected function processAssetLibraries(AttachedAssetsInterface $assets, array
     if (isset($placeholders['styles'])) {
       // Optimize CSS if necessary, but only during normal site operation.
       $optimize_css = !defined('MAINTENANCE_MODE') && $this->config->get('css.preprocess');
-      $variables['styles'] = $this->cssCollectionRenderer->render($this->assetResolver->getCssAssets($assets, $optimize_css));
+      $variables['styles'] = $this->cssCollectionRenderer->render($this->assetResolver->getCssAssets($assets, $optimize_css, $this->languageManager->getCurrentLanguage()));
     }
 
     // Print scripts - if any are present.
     if (isset($placeholders['scripts']) || isset($placeholders['scripts_bottom'])) {
       // Optimize JS if necessary, but only during normal site operation.
       $optimize_js = !defined('MAINTENANCE_MODE') && !\Drupal::state()->get('system.maintenance_mode') && $this->config->get('js.preprocess');
-      [$js_assets_header, $js_assets_footer] = $this->assetResolver->getJsAssets($assets, $optimize_js);
+      [$js_assets_header, $js_assets_footer] = $this->assetResolver->getJsAssets($assets, $optimize_js, $this->languageManager->getCurrentLanguage());
       $variables['scripts'] = $this->jsCollectionRenderer->render($js_assets_header);
       $variables['scripts_bottom'] = $this->jsCollectionRenderer->render($js_assets_footer);
     }
diff --git a/core/lib/Drupal/Core/Render/theme.api.php b/core/lib/Drupal/Core/Render/theme.api.php
index 3ea1f6c44d6d6222f1fe6eee08671aa8b7302538..dc329fd6ef9013c497e8b3e4a76be18607f4d0fb 100644
--- a/core/lib/Drupal/Core/Render/theme.api.php
+++ b/core/lib/Drupal/Core/Render/theme.api.php
@@ -824,10 +824,12 @@ function hook_element_plugin_alter(array &$definitions) {
  *   An array of all JavaScript being presented on the page.
  * @param \Drupal\Core\Asset\AttachedAssetsInterface $assets
  *   The assets attached to the current response.
+ * @param \Drupal\Core\Language\LanguageInterface $language
+ *   The language for the page request that the assets will be rendered for.
  *
  * @see \Drupal\Core\Asset\AssetResolver
  */
-function hook_js_alter(&$javascript, \Drupal\Core\Asset\AttachedAssetsInterface $assets) {
+function hook_js_alter(&$javascript, \Drupal\Core\Asset\AttachedAssetsInterface $assets, \Drupal\Core\Language\LanguageInterface $language) {
   // Swap out jQuery to use an updated version of the library.
   $javascript['core/assets/vendor/jquery/jquery.min.js']['data'] = \Drupal::service('extension.list.module')->getPath('jquery_update') . '/jquery.js';
 }
@@ -1000,10 +1002,12 @@ function hook_library_info_alter(&$libraries, $extension) {
  *   An array of all CSS items (files and inline CSS) being requested on the page.
  * @param \Drupal\Core\Asset\AttachedAssetsInterface $assets
  *   The assets attached to the current response.
+ * @param \Drupal\Core\Language\LanguageInterface $language
+ *   The language of the request that the assets will be rendered for.
  *
  * @see Drupal\Core\Asset\LibraryResolverInterface::getCssAssets()
  */
-function hook_css_alter(&$css, \Drupal\Core\Asset\AttachedAssetsInterface $assets) {
+function hook_css_alter(&$css, \Drupal\Core\Asset\AttachedAssetsInterface $assets, \Drupal\Core\Language\LanguageInterface $language) {
   // Remove defaults.css file.
   $file_path = \Drupal::service('extension.list.module')->getPath('system') . '/defaults.css';
   unset($css[$file_path]);
diff --git a/core/modules/big_pipe/big_pipe.services.yml b/core/modules/big_pipe/big_pipe.services.yml
index ff21df3367487a8d66a04bcec0b0d4d979c4eb34..09747e887fb8b47f1ff812bf6ab7b321d0f61db0 100644
--- a/core/modules/big_pipe/big_pipe.services.yml
+++ b/core/modules/big_pipe/big_pipe.services.yml
@@ -16,7 +16,7 @@ services:
     public: false
     class: \Drupal\big_pipe\Render\BigPipeResponseAttachmentsProcessor
     decorates: html_response.attachments_processor
-    arguments: ['@html_response.attachments_processor.big_pipe.inner', '@asset.resolver', '@config.factory', '@asset.css.collection_renderer', '@asset.js.collection_renderer', '@request_stack', '@renderer', '@module_handler']
+    arguments: ['@html_response.attachments_processor.big_pipe.inner', '@asset.resolver', '@config.factory', '@asset.css.collection_renderer', '@asset.js.collection_renderer', '@request_stack', '@renderer', '@module_handler', '@language_manager']
 
   route_subscriber.no_big_pipe:
     class: Drupal\big_pipe\EventSubscriber\NoBigPipeRouteAlterSubscriber
diff --git a/core/modules/big_pipe/src/Render/BigPipeResponseAttachmentsProcessor.php b/core/modules/big_pipe/src/Render/BigPipeResponseAttachmentsProcessor.php
index 66ed363b6c0f1d0840ca6da1b8495c1440bd8e85..fe42a3e564684dacf888530ec28931d1574f8f90 100644
--- a/core/modules/big_pipe/src/Render/BigPipeResponseAttachmentsProcessor.php
+++ b/core/modules/big_pipe/src/Render/BigPipeResponseAttachmentsProcessor.php
@@ -7,6 +7,7 @@
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Form\EnforcedResponseException;
+use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Render\AttachmentsInterface;
 use Drupal\Core\Render\AttachmentsResponseProcessorInterface;
 use Drupal\Core\Render\HtmlResponse;
@@ -48,10 +49,12 @@ class BigPipeResponseAttachmentsProcessor extends HtmlResponseAttachmentsProcess
    *   The renderer.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   The module handler service.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
    */
-  public function __construct(AttachmentsResponseProcessorInterface $html_response_attachments_processor, AssetResolverInterface $asset_resolver, ConfigFactoryInterface $config_factory, AssetCollectionRendererInterface $css_collection_renderer, AssetCollectionRendererInterface $js_collection_renderer, RequestStack $request_stack, RendererInterface $renderer, ModuleHandlerInterface $module_handler) {
+  public function __construct(AttachmentsResponseProcessorInterface $html_response_attachments_processor, AssetResolverInterface $asset_resolver, ConfigFactoryInterface $config_factory, AssetCollectionRendererInterface $css_collection_renderer, AssetCollectionRendererInterface $js_collection_renderer, RequestStack $request_stack, RendererInterface $renderer, ModuleHandlerInterface $module_handler, LanguageManagerInterface $language_manager) {
     $this->htmlResponseAttachmentsProcessor = $html_response_attachments_processor;
-    parent::__construct($asset_resolver, $config_factory, $css_collection_renderer, $js_collection_renderer, $request_stack, $renderer, $module_handler);
+    parent::__construct($asset_resolver, $config_factory, $css_collection_renderer, $js_collection_renderer, $request_stack, $renderer, $module_handler, $language_manager);
   }
 
   /**
diff --git a/core/modules/big_pipe/tests/src/Unit/Render/BigPipeResponseAttachmentsProcessorTest.php b/core/modules/big_pipe/tests/src/Unit/Render/BigPipeResponseAttachmentsProcessorTest.php
index 2f77684e554459f628a403a121207fb5926600eb..f91a8bbeb26757f42f0f9fb2dfc3fe5a777329d9 100644
--- a/core/modules/big_pipe/tests/src/Unit/Render/BigPipeResponseAttachmentsProcessorTest.php
+++ b/core/modules/big_pipe/tests/src/Unit/Render/BigPipeResponseAttachmentsProcessorTest.php
@@ -9,6 +9,7 @@
 use Drupal\Core\Asset\AssetResolverInterface;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\Core\Render\AttachmentsInterface;
 use Drupal\Core\Render\AttachmentsResponseProcessorInterface;
 use Drupal\Core\Render\HtmlResponse;
@@ -135,7 +136,8 @@ protected function createBigPipeResponseAttachmentsProcessor(ObjectProphecy $dec
       $this->prophesize(AssetCollectionRendererInterface::class)->reveal(),
       $this->prophesize(RequestStack::class)->reveal(),
       $this->prophesize(RendererInterface::class)->reveal(),
-      $this->prophesize(ModuleHandlerInterface::class)->reveal()
+      $this->prophesize(ModuleHandlerInterface::class)->reveal(),
+      $this->prophesize(LanguageManagerInterface::class)->reveal()
     );
   }
 
diff --git a/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php b/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php
index 0e43f969e499ff35af18cd0e04ee0f417cf9f4e6..eca98b75fed6ef1901f8818648c03df4f234252e 100644
--- a/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php
+++ b/core/modules/ckeditor/src/Plugin/Editor/CKEditor.php
@@ -355,7 +355,7 @@ public function getJSSettings(Editor $editor) {
 
     // Parse all CKEditor plugin JavaScript files for translations.
     if ($this->moduleHandler->moduleExists('locale')) {
-      locale_js_translate(array_values($external_plugin_files));
+      locale_js_translate(array_values($external_plugin_files), $language_interface);
     }
 
     ksort($settings);
diff --git a/core/modules/ckeditor5/ckeditor5.module b/core/modules/ckeditor5/ckeditor5.module
index 5be20aab8a7b374716e7a8a6f91b9912f183e3fd..1e0ed1de95cec193e070574000fa4a0b5d48764e 100644
--- a/core/modules/ckeditor5/ckeditor5.module
+++ b/core/modules/ckeditor5/ckeditor5.module
@@ -18,6 +18,7 @@
 use Drupal\Core\Ajax\RemoveCommand;
 use Drupal\Core\Asset\AttachedAssetsInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Url;
@@ -368,7 +369,7 @@ function _add_attachments_to_editor_update_response(array $form, AjaxResponse &$
 /**
  * Returns a list of language codes supported by CKEditor 5.
  *
- * @param $lang
+ * @param string|bool $lang
  *   The Drupal langcode to match.
  *
  * @return array|mixed|string
@@ -413,7 +414,6 @@ function _ckeditor5_get_langcode_mapping($lang = FALSE) {
       unset($langcodes[$langcode]);
     }
   }
-
   if ($lang) {
     return $langcodes[$lang] ?? 'en';
   }
@@ -537,7 +537,7 @@ function ckeditor5_library_info_alter(&$libraries, $extension) {
 /**
  * Implements hook_js_alter().
  */
-function ckeditor5_js_alter(&$javascript, AttachedAssetsInterface $assets) {
+function ckeditor5_js_alter(&$javascript, AttachedAssetsInterface $assets, LanguageInterface $language) {
   // This file means CKEditor 5 translations are in use on the page.
   // @see locale_js_alter()
   $placeholder_file = 'core/assets/vendor/ckeditor5/translation.js';
@@ -559,8 +559,7 @@ function ckeditor5_js_alter(&$javascript, AttachedAssetsInterface $assets) {
       return;
     }
 
-    $language_interface = \Drupal::languageManager()->getCurrentLanguage()->getId();
-    $ckeditor5_language = _ckeditor5_get_langcode_mapping($language_interface);
+    $ckeditor5_language = _ckeditor5_get_langcode_mapping($language->getId());
 
     // Remove all CKEditor 5 translations files that are not in the current
     // language.
diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module
index 1a30d0bfc18456e252741cafcac91ba44abade75..7fbf1698d65e419d7dc0ecf06f08ca88d93ec09f 100644
--- a/core/modules/locale/locale.module
+++ b/core/modules/locale/locale.module
@@ -490,7 +490,7 @@ function locale_cache_flush() {
 /**
  * Implements hook_js_alter().
  */
-function locale_js_alter(&$javascript, AttachedAssetsInterface $assets) {
+function locale_js_alter(&$javascript, AttachedAssetsInterface $assets, LanguageInterface $language) {
   // @todo Remove this in https://www.drupal.org/node/2421323.
   $files = [];
   foreach ($javascript as $item) {
@@ -506,7 +506,7 @@ function locale_js_alter(&$javascript, AttachedAssetsInterface $assets) {
   // Replace the placeholder file with the actual JS translation file.
   $placeholder_file = 'core/modules/locale/locale.translation.js';
   if (isset($javascript[$placeholder_file])) {
-    if ($translation_file = locale_js_translate($files)) {
+    if ($translation_file = locale_js_translate($files, $language)) {
       $js_translation_asset = &$javascript[$placeholder_file];
       $js_translation_asset['data'] = $translation_file;
       // @todo Remove this when https://www.drupal.org/node/1945262 lands.
@@ -530,13 +530,17 @@ function locale_js_alter(&$javascript, AttachedAssetsInterface $assets) {
  *
  * @param array $files
  *   An array of local file paths.
+ * @param \Drupal\Core\Language\LanguageInterface $language_interface
+ *   The interface language the files should be translated into.
  *
  * @return string|null
  *   The filepath to the translation file or NULL if no translation is
  *   applicable.
  */
-function locale_js_translate(array $files = []) {
-  $language_interface = \Drupal::languageManager()->getCurrentLanguage();
+function locale_js_translate(array $files = [], $language_interface = NULL) {
+  if (!isset($language_interface)) {
+    $language_interface = \Drupal::languageManager()->getCurrentLanguage();
+  }
 
   $dir = 'public://' . \Drupal::config('locale.settings')->get('javascript.directory');
   $parsed = \Drupal::state()->get('system.javascript_parsed', []);
diff --git a/core/modules/settings_tray/settings_tray.module b/core/modules/settings_tray/settings_tray.module
index e16c213f267f8674fce597c6c472ce058633199d..0f3c3a262d97dd9de13df80e6610383262323add 100644
--- a/core/modules/settings_tray/settings_tray.module
+++ b/core/modules/settings_tray/settings_tray.module
@@ -8,6 +8,7 @@
 use Drupal\Core\Url;
 use Drupal\Core\Asset\AttachedAssetsInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Language\LanguageInterface;
 use Drupal\block\entity\Block;
 use Drupal\block\BlockInterface;
 use Drupal\settings_tray\Block\BlockEntitySettingTrayForm;
@@ -177,7 +178,7 @@ function settings_tray_block_alter(&$definitions) {
 /**
  * Implements hook_css_alter().
  */
-function settings_tray_css_alter(&$css, AttachedAssetsInterface $assets) {
+function settings_tray_css_alter(&$css, AttachedAssetsInterface $assets, LanguageInterface $language) {
   // @todo Remove once conditional ordering is introduced in
   //   https://www.drupal.org/node/1945262.
   $path = \Drupal::service('extension.list.module')->getPath('settings_tray') . '/css/settings_tray.theme.css';
diff --git a/core/modules/system/src/Controller/AssetControllerBase.php b/core/modules/system/src/Controller/AssetControllerBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..cb83318b391796e66fd9be16de59bc3b569c04d1
--- /dev/null
+++ b/core/modules/system/src/Controller/AssetControllerBase.php
@@ -0,0 +1,224 @@
+<?php
+
+namespace Drupal\system\Controller;
+
+use Drupal\Core\Asset\AssetCollectionGrouperInterface;
+use Drupal\Core\Asset\AssetCollectionOptimizerInterface;
+use Drupal\Core\Asset\AssetDumperUriInterface;
+use Drupal\Core\Asset\AssetGroupSetHashTrait;
+use Drupal\Core\Asset\AssetResolverInterface;
+use Drupal\Core\Asset\AttachedAssets;
+use Drupal\Core\Asset\AttachedAssetsInterface;
+use Drupal\Core\Asset\LibraryDependencyResolverInterface;
+use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
+use Drupal\Core\Theme\ThemeInitializationInterface;
+use Drupal\Core\Theme\ThemeManagerInterface;
+use Drupal\system\FileDownloadController;
+use Symfony\Component\HttpFoundation\BinaryFileResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
+
+/**
+ * Defines a controller to serve asset aggregates.
+ */
+abstract class AssetControllerBase extends FileDownloadController {
+
+  use AssetGroupSetHashTrait;
+
+  /**
+   * The asset type.
+   *
+   * @var string
+   */
+  protected string $assetType;
+
+  /**
+   * The aggregate file extension.
+   *
+   * @var string
+   */
+  protected string $fileExtension;
+
+  /**
+   * The asset aggregate content type to send as Content-Type header.
+   *
+   * @var string
+   */
+  protected string $contentType;
+
+  /**
+   * The cache control header to use.
+   *
+   * Headers sent from PHP can never perfectly match those sent when the
+   * file is served by the filesystem, so ensure this request does not get
+   * cached in either the browser or reverse proxies. Subsequent requests
+   * for the file will be served from disk and be cached. This is done to
+   * avoid situations such as where one CDN endpoint is serving a version
+   * cached from PHP, while another is serving a version cached from disk.
+   * Should there be any discrepancy in behaviour between those files, this
+   * can make debugging very difficult.
+   */
+  protected const CACHE_CONTROL = 'private, no-store';
+
+  /**
+   * Constructs an object derived from AssetControllerBase.
+   *
+   * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $streamWrapperManager
+   *   The stream wrapper manager.
+   * @param \Drupal\Core\Asset\LibraryDependencyResolverInterface $libraryDependencyResolver
+   *   The library dependency resolver.
+   * @param \Drupal\Core\Asset\AssetResolverInterface $assetResolver
+   *   The asset resolver.
+   * @param \Drupal\Core\Theme\ThemeInitializationInterface $themeInitialization
+   *   The theme initializer.
+   * @param \Drupal\Core\Theme\ThemeManagerInterface $themeManager
+   *   The theme manager.
+   * @param \Drupal\Core\Asset\AssetCollectionGrouperInterface $grouper
+   *   The asset grouper.
+   * @param \Drupal\Core\Asset\AssetCollectionOptimizerInterface $optimizer
+   *   The asset collection optimizer.
+   * @param \Drupal\Core\Asset\AssetDumperUriInterface $dumper
+   *   The asset dumper.
+   */
+  public function __construct(
+    StreamWrapperManagerInterface $streamWrapperManager,
+    protected readonly LibraryDependencyResolverInterface $libraryDependencyResolver,
+    protected readonly AssetResolverInterface $assetResolver,
+    protected readonly ThemeInitializationInterface $themeInitialization,
+    protected readonly ThemeManagerInterface $themeManager,
+    protected readonly AssetCollectionGrouperInterface $grouper,
+    protected readonly AssetCollectionOptimizerInterface $optimizer,
+    protected readonly AssetDumperUriInterface $dumper,
+  ) {
+    parent::__construct($streamWrapperManager);
+    $this->fileExtension = $this->assetType;
+  }
+
+  /**
+   * Generates an aggregate, given a filename.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request object.
+   * @param string $file_name
+   *   The file to deliver.
+   *
+   * @return \Symfony\Component\HttpFoundation\BinaryFileResponse|\Symfony\Component\HttpFoundation\Response
+   *   The transferred file as response.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
+   *   Thrown when the filename is invalid or an invalid query argument is
+   *   supplied.
+   */
+  public function deliver(Request $request, string $file_name) {
+    $uri = 'public://' . $this->assetType . '/' . $file_name;
+
+    // Check to see whether a file matching the $uri already exists, this can
+    // happen if it was created while this request was in progress.
+    if (file_exists($uri)) {
+      return new BinaryFileResponse($uri, 200, ['Cache-control' => static::CACHE_CONTROL]);
+    }
+
+    // First validate that the request is valid enough to produce an asset group
+    // aggregate. The theme must be passed as a query parameter, since assets
+    // always depend on the current theme.
+    if (!$request->query->has('theme')) {
+      throw new BadRequestHttpException('The theme must be passed as a query argument');
+    }
+    if (!$request->query->has('delta') || !is_numeric($request->query->get('delta'))) {
+      throw new BadRequestHttpException('The numeric delta must be passed as a query argument');
+    }
+    if (!$request->query->has('language')) {
+      throw new BadRequestHttpException('The language must be passed as a query argument');
+    }
+    $file_parts = explode('_', basename($file_name, '.' . $this->fileExtension), 2);
+
+    // The hash is the second segment of the filename.
+    if (!isset($file_parts[1])) {
+      throw new BadRequestHttpException('Invalid filename');
+    }
+    $received_hash = $file_parts[1];
+
+    // Now build the asset groups based on the libraries.  It requires the full
+    // set of asset groups to extract and build the aggregate for the group we
+    // want, since libraries may be split across different asset groups.
+    $theme = $request->query->get('theme');
+    $active_theme = $this->themeInitialization->initTheme($theme);
+    $this->themeManager->setActiveTheme($active_theme);
+
+    $attached_assets = new AttachedAssets();
+    $attached_assets->setLibraries(explode(',', $request->query->get('include')));
+    if ($request->query->has('exclude')) {
+      $attached_assets->setAlreadyLoadedLibraries(explode(',', $request->query->get('exclude')));
+    }
+    $groups = $this->getGroups($attached_assets, $request);
+
+    $group = $this->getGroup($groups, $request->query->get('delta'));
+    // Generate a hash based on the asset group, this uses the same method as
+    // the collection optimizer does to create the filename, so it should match.
+    $generated_hash = $this->generateHash($group);
+    $data = $this->optimizer->optimizeGroup($group);
+
+    // However, the hash from the library definitions in code may not match the
+    // hash from the URL. This can be for three reasons:
+    // 1. Someone has requested an outdated URL, i.e. from a cached page, which
+    // matches a different version of the code base.
+    // 2. Someone has requested an outdated URL during a deployment. This is
+    // the same case as #1 but a much shorter window.
+    // 3. Someone is attempting to craft an invalid URL in order to conduct a
+    // denial of service attack on the site.
+    // Dump the optimized group into an aggregate file, but only if the
+    // received hash and generated hash match. This prevents invalid filenames
+    // from filling the disk, while still serving aggregates that may be
+    // referenced in cached HTML.
+    if (hash_equals($generated_hash, $received_hash)) {
+      $uri = $this->dumper->dumpToUri($data, $this->assetType, $uri);
+      $state_key = 'drupal_' . $this->assetType . '_cache_files';
+      $files = $this->state()->get($state_key, []);
+      $files[] = $uri;
+      $this->state()->set($state_key, $files);
+    }
+    return new Response($data, 200, [
+      'Cache-control' => static::CACHE_CONTROL,
+      'Content-Type' => $this->contentType,
+    ]);
+  }
+
+  /**
+   * Gets a group.
+   *
+   * @param array $groups
+   *   An array of asset groups.
+   * @param int $group_delta
+   *   The group delta.
+   *
+   * @return array
+   *   The correct asset group matching $group_delta.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
+   *   Thrown when the filename is invalid.
+   */
+  protected function getGroup(array $groups, int $group_delta): array {
+    if (isset($groups[$group_delta])) {
+      return $groups[$group_delta];
+    }
+    throw new BadRequestHttpException('Invalid filename.');
+  }
+
+  /**
+   * Get grouped assets.
+   *
+   * @param \Drupal\Core\Asset\AttachedAssetsInterface $attached_assets
+   *   The attached assets.
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The current request.
+   *
+   * @return array
+   *   The grouped assets.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
+   *   Thrown when the query argument is omitted.
+   */
+  abstract protected function getGroups(AttachedAssetsInterface $attached_assets, Request $request): array;
+
+}
diff --git a/core/modules/system/src/Controller/CssAssetController.php b/core/modules/system/src/Controller/CssAssetController.php
new file mode 100644
index 0000000000000000000000000000000000000000..76d1e8a8b7114a4d826e11f6dfc779a2dcfb8855
--- /dev/null
+++ b/core/modules/system/src/Controller/CssAssetController.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Drupal\system\Controller;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+use Drupal\Core\Asset\AttachedAssetsInterface;
+use Drupal\Core\Asset\AssetGroupSetHashTrait;
+
+/**
+ * Defines a controller to serve CSS aggregates.
+ */
+class CssAssetController extends AssetControllerBase {
+
+  use AssetGroupSetHashTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected string $contentType = 'text/css';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected string $assetType = 'css';
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('stream_wrapper_manager'),
+      $container->get('library.dependency_resolver'),
+      $container->get('asset.resolver'),
+      $container->get('theme.initialization'),
+      $container->get('theme.manager'),
+      $container->get('asset.css.collection_grouper'),
+      $container->get('asset.css.collection_optimizer'),
+      $container->get('asset.css.dumper'),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getGroups(AttachedAssetsInterface $attached_assets, Request $request): array {
+    $language = $this->languageManager()->getLanguage($request->get('language'));
+    $assets = $this->assetResolver->getCssAssets($attached_assets, FALSE, $language);
+    return $this->grouper->group($assets);
+  }
+
+}
diff --git a/core/modules/system/src/Controller/JsAssetController.php b/core/modules/system/src/Controller/JsAssetController.php
new file mode 100644
index 0000000000000000000000000000000000000000..7900286496b168ccadc4cf18a4b0fe9b3afd9805
--- /dev/null
+++ b/core/modules/system/src/Controller/JsAssetController.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Drupal\system\Controller;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
+use Drupal\Core\Asset\AttachedAssetsInterface;
+use Drupal\Core\Asset\AssetGroupSetHashTrait;
+
+/**
+ * Defines a controller to serve Javascript aggregates.
+ */
+class JsAssetController extends AssetControllerBase {
+
+  use AssetGroupSetHashTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected string $contentType = 'application/javascript';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected string $assetType = 'js';
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('stream_wrapper_manager'),
+      $container->get('library.dependency_resolver'),
+      $container->get('asset.resolver'),
+      $container->get('theme.initialization'),
+      $container->get('theme.manager'),
+      $container->get('asset.js.collection_grouper'),
+      $container->get('asset.js.collection_optimizer'),
+      $container->get('asset.js.dumper'),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getGroups(AttachedAssetsInterface $attached_assets, Request $request): array {
+    // The header and footer scripts are two distinct sets of asset groups. The
+    // $group_key is not sufficient to find the group, we also need to locate it
+    // within either the header or footer set.
+    $language = $this->languageManager()->getLanguage($request->get('language'));
+    [$js_assets_header, $js_assets_footer] = $this->assetResolver->getJsAssets($attached_assets, FALSE, $language);
+    $scope = $request->get('scope');
+    if (!isset($scope)) {
+      throw new BadRequestHttpException('The URL must have a scope query argument.');
+    }
+    $assets = $scope === 'header' ? $js_assets_header : $js_assets_footer;
+    // While the asset resolver will find settings, these are never aggregated,
+    // so filter them out.
+    unset($assets['drupalSettings']);
+    return $this->grouper->group($assets);
+  }
+
+}
diff --git a/core/modules/system/src/Routing/AssetRoutes.php b/core/modules/system/src/Routing/AssetRoutes.php
new file mode 100644
index 0000000000000000000000000000000000000000..b569824ec532b801c1891445c97e6e9282978eb5
--- /dev/null
+++ b/core/modules/system/src/Routing/AssetRoutes.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Drupal\system\Routing;
+
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\Routing\Route;
+
+/**
+ * Defines a routes' callback to register an url for serving assets.
+ */
+class AssetRoutes implements ContainerInjectionInterface {
+
+  /**
+   * Constructs an asset routes object.
+   *
+   * @param \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface $streamWrapperManager
+   *   The stream wrapper manager service.
+   */
+  public function __construct(
+    protected readonly StreamWrapperManagerInterface $streamWrapperManager
+  ) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('stream_wrapper_manager')
+    );
+  }
+
+  /**
+   * Returns an array of route objects.
+   *
+   * @return \Symfony\Component\Routing\Route[]
+   *   An array of route objects.
+   */
+  public function routes(): array {
+    $routes = [];
+    // Generate assets. If clean URLs are disabled image derivatives will always
+    // be served through the routing system. If clean URLs are enabled and the
+    // image derivative already exists, PHP will be bypassed.
+    $directory_path = $this->streamWrapperManager->getViaScheme('public')->getDirectoryPath();
+
+    $routes['system.css_asset'] = new Route(
+      '/' . $directory_path . '/css/{file_name}',
+      [
+        '_controller' => 'Drupal\system\Controller\CssAssetController::deliver',
+      ],
+      [
+        '_access' => 'TRUE',
+      ]
+    );
+    $routes['system.js_asset'] = new Route(
+      '/' . $directory_path . '/js/{file_name}',
+      [
+        '_controller' => 'Drupal\system\Controller\JsAssetController::deliver',
+      ],
+      [
+        '_access' => 'TRUE',
+      ]
+    );
+    return $routes;
+  }
+
+}
diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml
index 00eddce6b5502a5e06dc5754bea2ffa47cb0d80e..362763fa18236877a5bed03ed5a292d906883afe 100644
--- a/core/modules/system/system.routing.yml
+++ b/core/modules/system/system.routing.yml
@@ -520,3 +520,6 @@ system.csrftoken:
     _controller: '\Drupal\system\Controller\CsrfTokenController::csrfToken'
   requirements:
     _access: 'TRUE'
+
+route_callbacks:
+  - '\Drupal\system\Routing\AssetRoutes::routes'
diff --git a/core/modules/system/tests/modules/common_test/common_test.module b/core/modules/system/tests/modules/common_test/common_test.module
index b38d5f7bbe9b1c76ef4610135d776649e3fcdc85..211147ef5b65536676c329880af4375f63c5ba01 100644
--- a/core/modules/system/tests/modules/common_test/common_test.module
+++ b/core/modules/system/tests/modules/common_test/common_test.module
@@ -6,6 +6,7 @@
  */
 
 use Drupal\Core\Asset\AttachedAssetsInterface;
+use Drupal\Core\Language\LanguageInterface;
 
 /**
  * Applies #printed to an element to help test #pre_render.
@@ -250,7 +251,7 @@ function common_test_page_attachments_alter(array &$page) {
  *
  * @see \Drupal\KernelTests\Core\Asset\AttachedAssetsTest::testAlter()
  */
-function common_test_js_alter(&$javascript, AttachedAssetsInterface $assets) {
+function common_test_js_alter(&$javascript, AttachedAssetsInterface $assets, LanguageInterface $language) {
   // Attach alter.js above tableselect.js.
   $alterjs = \Drupal::service('extension.list.module')->getPath('common_test') . '/alter.js';
   if (array_key_exists($alterjs, $javascript) && array_key_exists('core/misc/tableselect.js', $javascript)) {
diff --git a/core/modules/system/tests/src/Functional/Ajax/FrameworkTest.php b/core/modules/system/tests/src/Functional/Ajax/FrameworkTest.php
index 669037f82d05f0c86c0f6ad2228cd1311e183aa8..5f44d545a514c969652ce51c477b2d44dcaa0aa7 100644
--- a/core/modules/system/tests/src/Functional/Ajax/FrameworkTest.php
+++ b/core/modules/system/tests/src/Functional/Ajax/FrameworkTest.php
@@ -52,12 +52,12 @@ public function testOrder() {
     $renderer = \Drupal::service('renderer');
     $build['#attached']['library'][] = 'ajax_test/order-css-command';
     $assets = AttachedAssets::createFromRenderArray($build);
-    $css_render_array = $css_collection_renderer->render($asset_resolver->getCssAssets($assets, FALSE));
+    $css_render_array = $css_collection_renderer->render($asset_resolver->getCssAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage()));
     $expected_commands[1] = new AddCssCommand($renderer->renderRoot($css_render_array));
     $build['#attached']['library'][] = 'ajax_test/order-header-js-command';
     $build['#attached']['library'][] = 'ajax_test/order-footer-js-command';
     $assets = AttachedAssets::createFromRenderArray($build);
-    [$js_assets_header, $js_assets_footer] = $asset_resolver->getJsAssets($assets, FALSE);
+    [$js_assets_header, $js_assets_footer] = $asset_resolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage());
     $js_header_render_array = $js_collection_renderer->render($js_assets_header);
     $js_footer_render_array = $js_collection_renderer->render($js_assets_footer);
     $expected_commands[2] = new AddJsCommand(array_column($js_header_render_array, '#attributes'), 'head');
diff --git a/core/phpstan-baseline.neon b/core/phpstan-baseline.neon
index 50c770ed6213eac5c286174d234ebecf7f8dc1b1..3fe6bec6b1bd724037807f17dccf104bdd2f39aa 100644
--- a/core/phpstan-baseline.neon
+++ b/core/phpstan-baseline.neon
@@ -100,16 +100,6 @@ parameters:
 			count: 1
 			path: lib/Drupal/Core/Archiver/ArchiverManager.php
 
-		-
-			message: "#^Call to deprecated constant REQUEST_TIME\\: Deprecated in drupal\\:8\\.3\\.0 and is removed from drupal\\:10\\.0\\.0\\. Use \\\\Drupal\\:\\:time\\(\\)\\-\\>getRequestTime\\(\\); $#"
-			count: 1
-			path: lib/Drupal/Core/Asset/CssCollectionOptimizer.php
-
-		-
-			message: "#^Call to deprecated constant REQUEST_TIME\\: Deprecated in drupal\\:8\\.3\\.0 and is removed from drupal\\:10\\.0\\.0\\. Use \\\\Drupal\\:\\:time\\(\\)\\-\\>getRequestTime\\(\\); $#"
-			count: 1
-			path: lib/Drupal/Core/Asset/JsCollectionOptimizer.php
-
 		-
 			message: "#^Call to deprecated constant REQUEST_TIME\\: Deprecated in drupal\\:8\\.3\\.0 and is removed from drupal\\:10\\.0\\.0\\. Use \\\\Drupal\\:\\:time\\(\\)\\-\\>getRequestTime\\(\\); $#"
 			count: 1
diff --git a/core/tests/Drupal/FunctionalTests/Asset/AssetOptimizationTest.php b/core/tests/Drupal/FunctionalTests/Asset/AssetOptimizationTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c7b71cd84dcd4e96cfab1844ef060be72f4925b6
--- /dev/null
+++ b/core/tests/Drupal/FunctionalTests/Asset/AssetOptimizationTest.php
@@ -0,0 +1,203 @@
+<?php
+
+namespace Drupal\FunctionalTests\Asset;
+
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\Tests\BrowserTestBase;
+
+// cspell:ignore abcdefghijklmnop
+
+/**
+ * Tests asset aggregation.
+ *
+ * @group asset
+ */
+class AssetOptimizationTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['system'];
+
+  /**
+   * Tests that asset aggregates are rendered and created on disk.
+   */
+  public function testAssetAggregation(): void {
+    $this->config('system.performance')->set('css', [
+      'preprocess' => TRUE,
+      'gzip' => TRUE,
+    ])->save();
+    $this->config('system.performance')->set('js', [
+      'preprocess' => TRUE,
+      'gzip' => TRUE,
+    ])->save();
+    $user = $this->createUser();
+    $this->drupalLogin($user);
+    $this->drupalGet('');
+    $session = $this->getSession();
+    $page = $session->getPage();
+
+    $elements = $page->findAll('xpath', '//link[@rel="stylesheet"]');
+    $urls = [];
+    foreach ($elements as $element) {
+      if ($element->hasAttribute('href')) {
+        $urls[] = $element->getAttribute('href');
+      }
+    }
+    foreach ($urls as $url) {
+      $this->assertAggregate($url);
+    }
+    foreach ($urls as $url) {
+      $this->assertAggregate($url, FALSE);
+    }
+
+    foreach ($urls as $url) {
+      $this->assertInvalidAggregates($url);
+    }
+
+    $elements = $page->findAll('xpath', '//script');
+    $urls = [];
+    foreach ($elements as $element) {
+      if ($element->hasAttribute('src')) {
+        $urls[] = $element->getAttribute('src');
+      }
+    }
+    foreach ($urls as $url) {
+      $this->assertAggregate($url);
+    }
+    foreach ($urls as $url) {
+      $this->assertAggregate($url, FALSE);
+    }
+    foreach ($urls as $url) {
+      $this->assertInvalidAggregates($url);
+    }
+  }
+
+  /**
+   * Asserts the aggregate header.
+   *
+   * @param string $url
+   *   The source URL.
+   * @param bool $from_php
+   *   (optional) Is the result from PHP or disk? Defaults to TRUE (PHP).
+   */
+  protected function assertAggregate(string $url, bool $from_php = TRUE): void {
+    $url = $this->getAbsoluteUrl($url);
+    $session = $this->getSession();
+    $session->visit($url);
+    $this->assertSession()->statusCodeEquals(200);
+    $headers = $session->getResponseHeaders();
+    if ($from_php) {
+      $this->assertEquals(['no-store, private'], $headers['Cache-Control']);
+    }
+    else {
+      $this->assertArrayNotHasKey('Cache-Control', $headers);
+    }
+  }
+
+  /**
+   * Asserts the aggregate when it is invalid.
+   *
+   * @param string $url
+   *   The source URL.
+   *
+   * @throws \Behat\Mink\Exception\ExpectationException
+   */
+  protected function assertInvalidAggregates(string $url): void {
+    $session = $this->getSession();
+    $session->visit($this->replaceGroupDelta($url));
+    $this->assertSession()->statusCodeEquals(200);
+
+    $session->visit($this->omitTheme($url));
+    $this->assertSession()->statusCodeEquals(400);
+
+    $session->visit($this->setInvalidLibrary($url));
+    $this->assertSession()->statusCodeEquals(200);
+
+    $session->visit($this->replaceGroupHash($url));
+    $this->assertSession()->statusCodeEquals(200);
+    $headers = $session->getResponseHeaders();
+    $this->assertEquals(['no-store, private'], $headers['Cache-Control']);
+
+    // And again to confirm it's not cached on disk.
+    $session->visit($this->replaceGroupHash($url));
+    $this->assertSession()->statusCodeEquals(200);
+    $headers = $session->getResponseHeaders();
+    $this->assertEquals(['no-store, private'], $headers['Cache-Control']);
+  }
+
+  /**
+   * Replaces the delta in the given URL.
+   *
+   * @param string $url
+   *   The source URL.
+   *
+   * @return string
+   *   The URL with the delta replaced.
+   */
+  protected function replaceGroupDelta(string $url): string {
+    $parts = UrlHelper::parse($url);
+    $parts['query']['delta'] = 100;
+    $query = UrlHelper::buildQuery($parts['query']);
+    return $this->getAbsoluteUrl($parts['path'] . '?' . $query . '#' . $parts['fragment']);
+  }
+
+  /**
+   * Replaces the group hash in the given URL.
+   *
+   * @param string $url
+   *   The source URL.
+   *
+   * @return string
+   *   The URL with the group hash replaced.
+   */
+  protected function replaceGroupHash(string $url): string {
+    $parts = explode('_', $url, 2);
+    $hash = strtok($parts[1], '.');
+    $parts[1] = str_replace($hash, 'abcdefghijklmnop', $parts[1]);
+    return $this->getAbsoluteUrl(implode('_', $parts));
+  }
+
+  /**
+   * Replaces the 'libraries' entry in the given URL with an invalid value.
+   *
+   * @param string $url
+   *   The source URL.
+   *
+   * @return string
+   *   The URL with the 'library' query set to an invalid value.
+   */
+  protected function setInvalidLibrary(string $url): string {
+    // First replace the hash, so we don't get served the actual file on disk.
+    $url = $this->replaceGroupHash($url);
+    $parts = UrlHelper::parse($url);
+    $parts['query']['libraries'] = ['system/llama'];
+
+    $query = UrlHelper::buildQuery($parts['query']);
+    return $this->getAbsoluteUrl($parts['path'] . '?' . $query . '#' . $parts['fragment']);
+  }
+
+  /**
+   * Removes the 'theme' query parameter from the given URL.
+   *
+   * @param string $url
+   *   The source URL.
+   *
+   * @return string
+   *   The URL with the 'theme' omitted.
+   */
+  protected function omitTheme(string $url): string {
+    // First replace the hash, so we don't get served the actual file on disk.
+    $url = $this->replaceGroupHash($url);
+    $parts = UrlHelper::parse($url);
+    unset($parts['query']['theme']);
+    $query = UrlHelper::buildQuery($parts['query']);
+    return $this->getAbsoluteUrl($parts['path'] . '?' . $query . '#' . $parts['fragment']);
+  }
+
+}
diff --git a/core/tests/Drupal/KernelTests/Core/Asset/AttachedAssetsTest.php b/core/tests/Drupal/KernelTests/Core/Asset/AttachedAssetsTest.php
index 0a98220c141949313983057df80b15db33b11d29..26185c6a2c1af036153b3c16ee827a716ad84926 100644
--- a/core/tests/Drupal/KernelTests/Core/Asset/AttachedAssetsTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Asset/AttachedAssetsTest.php
@@ -63,8 +63,8 @@ protected function setUp(): void {
    */
   public function testDefault() {
     $assets = new AttachedAssets();
-    $this->assertEquals([], $this->assetResolver->getCssAssets($assets, FALSE), 'Default CSS is empty.');
-    [$js_assets_header, $js_assets_footer] = $this->assetResolver->getJsAssets($assets, FALSE);
+    $this->assertEquals([], $this->assetResolver->getCssAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage()), 'Default CSS is empty.');
+    [$js_assets_header, $js_assets_footer] = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage());
     $this->assertEquals([], $js_assets_header, 'Default header JavaScript is empty.');
     $this->assertEquals([], $js_assets_footer, 'Default footer JavaScript is empty.');
   }
@@ -76,7 +76,7 @@ public function testLibraryUnknown() {
     $build['#attached']['library'][] = 'core/unknown';
     $assets = AttachedAssets::createFromRenderArray($build);
 
-    $this->assertSame([], $this->assetResolver->getJsAssets($assets, FALSE)[0], 'Unknown library was not added to the page.');
+    $this->assertSame([], $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[0], 'Unknown library was not added to the page.');
   }
 
   /**
@@ -86,8 +86,8 @@ public function testAddFiles() {
     $build['#attached']['library'][] = 'common_test/files';
     $assets = AttachedAssets::createFromRenderArray($build);
 
-    $css = $this->assetResolver->getCssAssets($assets, FALSE);
-    $js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
+    $css = $this->assetResolver->getCssAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage());
+    $js = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
     $this->assertArrayHasKey('core/modules/system/tests/modules/common_test/bar.css', $css);
     $this->assertArrayHasKey('core/modules/system/tests/modules/common_test/foo.js', $js);
 
@@ -109,12 +109,12 @@ public function testAddJsSettings() {
     $assets = AttachedAssets::createFromRenderArray($build);
 
     $this->assertEquals([], $assets->getSettings(), 'JavaScript settings on $assets are empty.');
-    $javascript = $this->assetResolver->getJsAssets($assets, FALSE)[1];
+    $javascript = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
     $this->assertArrayHasKey('currentPath', $javascript['drupalSettings']['data']['path']);
     $this->assertArrayHasKey('currentPath', $assets->getSettings()['path']);
 
     $assets->setSettings(['drupal' => 'rocks', 'dries' => 280342800]);
-    $javascript = $this->assetResolver->getJsAssets($assets, FALSE)[1];
+    $javascript = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
     $this->assertEquals(280342800, $javascript['drupalSettings']['data']['dries'], 'JavaScript setting is set correctly.');
     $this->assertEquals('rocks', $javascript['drupalSettings']['data']['drupal'], 'The other JavaScript setting is set correctly.');
   }
@@ -126,8 +126,8 @@ public function testAddExternalFiles() {
     $build['#attached']['library'][] = 'common_test/external';
     $assets = AttachedAssets::createFromRenderArray($build);
 
-    $css = $this->assetResolver->getCssAssets($assets, FALSE);
-    $js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
+    $css = $this->assetResolver->getCssAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage());
+    $js = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
     $this->assertArrayHasKey('http://example.com/stylesheet.css', $css);
     $this->assertArrayHasKey('http://example.com/script.js', $js);
 
@@ -146,7 +146,7 @@ public function testAttributes() {
     $build['#attached']['library'][] = 'common_test/js-attributes';
     $assets = AttachedAssets::createFromRenderArray($build);
 
-    $js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
+    $js = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
     $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
     $rendered_js = $this->renderer->renderPlain($js_render_array);
     $expected_1 = '<script src="http://example.com/deferred-external.js" foo="bar" defer></script>';
@@ -162,7 +162,7 @@ public function testAggregatedAttributes() {
     $build['#attached']['library'][] = 'common_test/js-attributes';
     $assets = AttachedAssets::createFromRenderArray($build);
 
-    $js = $this->assetResolver->getJsAssets($assets, TRUE)[1];
+    $js = $this->assetResolver->getJsAssets($assets, TRUE, \Drupal::languageManager()->getCurrentLanguage())[1];
     $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
     $rendered_js = $this->renderer->renderPlain($js_render_array);
     $expected_1 = '<script src="http://example.com/deferred-external.js" foo="bar" defer></script>';
@@ -179,9 +179,9 @@ public function testAggregation() {
     $build['#attached']['library'][] = 'core/drupal.vertical-tabs';
     $assets = AttachedAssets::createFromRenderArray($build);
 
-    $this->assertCount(1, $this->assetResolver->getCssAssets($assets, TRUE), 'There is a sole aggregated CSS asset.');
+    $this->assertCount(1, $this->assetResolver->getCssAssets($assets, TRUE, \Drupal::languageManager()->getCurrentLanguage()), 'There is a sole aggregated CSS asset.');
 
-    [$header_js, $footer_js] = $this->assetResolver->getJsAssets($assets, TRUE);
+    [$header_js, $footer_js] = $this->assetResolver->getJsAssets($assets, TRUE, \Drupal::languageManager()->getCurrentLanguage());
     $this->assertEquals([], \Drupal::service('asset.js.collection_renderer')->render($header_js), 'There are 0 JavaScript assets in the header.');
     $rendered_footer_js = \Drupal::service('asset.js.collection_renderer')->render($footer_js);
     $this->assertCount(2, $rendered_footer_js, 'There are 2 JavaScript assets in the footer.');
@@ -199,7 +199,7 @@ public function testSettings() {
     $build['#attached']['drupalSettings']['path']['pathPrefix'] = 'yarhar';
     $assets = AttachedAssets::createFromRenderArray($build);
 
-    $js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
+    $js = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
     $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
     // Cast to string since this returns a \Drupal\Core\Render\Markup object.
     $rendered_js = (string) $this->renderer->renderPlain($js_render_array);
@@ -236,7 +236,7 @@ public function testHeaderHTML() {
     $build['#attached']['library'][] = 'common_test/js-header';
     $assets = AttachedAssets::createFromRenderArray($build);
 
-    $js = $this->assetResolver->getJsAssets($assets, FALSE)[0];
+    $js = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[0];
     $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
     $rendered_js = $this->renderer->renderPlain($js_render_array);
     $query_string = $this->container->get('state')->get('system.css_js_query_string') ?: '0';
@@ -252,7 +252,7 @@ public function testNoCache() {
     $build['#attached']['library'][] = 'common_test/no-cache';
     $assets = AttachedAssets::createFromRenderArray($build);
 
-    $js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
+    $js = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
     $this->assertFalse($js['core/modules/system/tests/modules/common_test/nocache.js']['preprocess'], 'Setting cache to FALSE sets preprocess to FALSE when adding JavaScript.');
   }
 
@@ -263,7 +263,7 @@ public function testVersionQueryString() {
     $build['#attached']['library'][] = 'core/once';
     $assets = AttachedAssets::createFromRenderArray($build);
 
-    $js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
+    $js = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
     $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
 
     $rendered_js = $this->renderer->renderPlain($js_render_array);
@@ -293,7 +293,7 @@ public function testRenderOrder() {
     ];
 
     // Retrieve the rendered JavaScript and test against the regex.
-    $js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
+    $js = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
     $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
     $rendered_js = $this->renderer->renderPlain($js_render_array);
     $matches = [];
@@ -335,7 +335,7 @@ public function testRenderOrder() {
     ];
 
     // Retrieve the rendered CSS and test against the regex.
-    $css = $this->assetResolver->getCssAssets($assets, FALSE);
+    $css = $this->assetResolver->getCssAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage());
     $css_render_array = \Drupal::service('asset.css.collection_renderer')->render($css);
     $rendered_css = $this->renderer->renderPlain($css_render_array);
     $matches = [];
@@ -358,7 +358,7 @@ public function testRenderDifferentWeight() {
     $build['#attached']['library'][] = 'common_test/weight';
     $assets = AttachedAssets::createFromRenderArray($build);
 
-    $js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
+    $js = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
     $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
     $rendered_js = $this->renderer->renderPlain($js_render_array);
     // Verify that lighter CSS assets are rendered first.
@@ -383,7 +383,7 @@ public function testAlter() {
     // Render the JavaScript, testing if alter.js was altered to be before
     // tableselect.js. See common_test_js_alter() to see where this alteration
     // takes place.
-    $js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
+    $js = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
     $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
     $rendered_js = $this->renderer->renderPlain($js_render_array);
     // Verify that JavaScript weight is correctly altered by the alter hook.
@@ -405,7 +405,7 @@ public function testLibraryAlter() {
     // common_test_library_info_alter() also added a dependency on jQuery Form.
     $build['#attached']['library'][] = 'core/jquery.farbtastic';
     $assets = AttachedAssets::createFromRenderArray($build);
-    $js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
+    $js = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
     $js_render_array = \Drupal::service('asset.js.collection_renderer')->render($js);
     $rendered_js = $this->renderer->renderPlain($js_render_array);
     $this->assertStringContainsString('core/assets/vendor/jquery-form/jquery.form.min.js', (string) $rendered_js, 'Altered library dependencies are added to the page.');
@@ -450,8 +450,8 @@ public function testAddJsFileWithQueryString() {
     $build['#attached']['library'][] = 'common_test/querystring';
     $assets = AttachedAssets::createFromRenderArray($build);
 
-    $css = $this->assetResolver->getCssAssets($assets, FALSE);
-    $js = $this->assetResolver->getJsAssets($assets, FALSE)[1];
+    $css = $this->assetResolver->getCssAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage());
+    $js = $this->assetResolver->getJsAssets($assets, FALSE, \Drupal::languageManager()->getCurrentLanguage())[1];
     $this->assertArrayHasKey('core/modules/system/tests/modules/common_test/querystring.css?arg1=value1&arg2=value2', $css);
     $this->assertArrayHasKey('core/modules/system/tests/modules/common_test/querystring.js?arg1=value1&arg2=value2', $js);
 
diff --git a/core/tests/Drupal/Tests/Core/Asset/AssetResolverTest.php b/core/tests/Drupal/Tests/Core/Asset/AssetResolverTest.php
index f9f58764db8bcf28be9c000c160a0187f94a81f9..b2589385abfa6e5dfa41eb3bea0c30fec30d2691 100644
--- a/core/tests/Drupal/Tests/Core/Asset/AssetResolverTest.php
+++ b/core/tests/Drupal/Tests/Core/Asset/AssetResolverTest.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Asset\AttachedAssets;
 use Drupal\Core\Asset\AttachedAssetsInterface;
 use Drupal\Core\Cache\MemoryBackend;
+use Drupal\Core\Language\LanguageInterface;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -68,6 +69,16 @@ class AssetResolverTest extends UnitTestCase {
    */
   protected $cache;
 
+  /**
+   * A mocked English language object.
+   */
+  protected LanguageInterface $english;
+
+  /**
+   * A mocked Japanese language object.
+   */
+  protected LanguageInterface $japanese;
+
   /**
    * {@inheritdoc}
    */
@@ -95,10 +106,12 @@ protected function setUp(): void {
     $english->expects($this->any())
       ->method('getId')
       ->willReturn('en');
+    $this->english = $english;
     $japanese = $this->createMock('\Drupal\Core\Language\LanguageInterface');
     $japanese->expects($this->any())
       ->method('getId')
       ->willReturn('jp');
+    $this->japanese = $japanese;
     $this->languageManager = $this->createMock('\Drupal\Core\Language\LanguageManagerInterface');
     $this->languageManager->expects($this->any())
       ->method('getCurrentLanguage')
@@ -113,8 +126,8 @@ protected function setUp(): void {
    * @dataProvider providerAttachedAssets
    */
   public function testGetCssAssets(AttachedAssetsInterface $assets_a, AttachedAssetsInterface $assets_b, $expected_cache_item_count) {
-    $this->assetResolver->getCssAssets($assets_a, FALSE);
-    $this->assetResolver->getCssAssets($assets_b, FALSE);
+    $this->assetResolver->getCssAssets($assets_a, FALSE, $this->english);
+    $this->assetResolver->getCssAssets($assets_b, FALSE, $this->english);
     $this->assertCount($expected_cache_item_count, $this->cache->getAllCids());
   }
 
@@ -123,12 +136,12 @@ public function testGetCssAssets(AttachedAssetsInterface $assets_a, AttachedAsse
    * @dataProvider providerAttachedAssets
    */
   public function testGetJsAssets(AttachedAssetsInterface $assets_a, AttachedAssetsInterface $assets_b, $expected_cache_item_count) {
-    $this->assetResolver->getJsAssets($assets_a, FALSE);
-    $this->assetResolver->getJsAssets($assets_b, FALSE);
+    $this->assetResolver->getJsAssets($assets_a, FALSE, $this->english);
+    $this->assetResolver->getJsAssets($assets_b, FALSE, $this->english);
     $this->assertCount($expected_cache_item_count, $this->cache->getAllCids());
 
-    $this->assetResolver->getJsAssets($assets_a, FALSE);
-    $this->assetResolver->getJsAssets($assets_b, FALSE);
+    $this->assetResolver->getJsAssets($assets_a, FALSE, $this->japanese);
+    $this->assetResolver->getJsAssets($assets_b, FALSE, $this->japanese);
     $this->assertCount($expected_cache_item_count * 2, $this->cache->getAllCids());
   }
 
diff --git a/core/tests/Drupal/Tests/Core/Asset/CssCollectionOptimizerLazyUnitTest.php b/core/tests/Drupal/Tests/Core/Asset/CssCollectionOptimizerLazyUnitTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..8e58467ea885b7262589acc26ad41a049e5eea2d
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Asset/CssCollectionOptimizerLazyUnitTest.php
@@ -0,0 +1,75 @@
+<?php
+
+namespace Drupal\Tests\Core\Asset;
+
+use Drupal\Component\Datetime\TimeInterface;
+use Drupal\Core\Asset\AssetCollectionGrouperInterface;
+use Drupal\Core\Asset\AssetOptimizerInterface;
+use Drupal\Core\Asset\LibraryDependencyResolverInterface;
+use Drupal\Core\Asset\CssCollectionOptimizerLazy;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\File\FileUrlGeneratorInterface;
+use Drupal\Core\File\FileSystemInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\State\StateInterface;
+use Drupal\Core\Theme\ThemeManagerInterface;
+use Drupal\Tests\UnitTestCase;
+use Symfony\Component\HttpFoundation\RequestStack;
+
+/**
+ * Tests the CSS asset optimizer.
+ *
+ * @group Asset
+ */
+class CssCollectionOptimizerLazyUnitTest extends UnitTestCase {
+
+  /**
+   * Tests that CSS imports with strange letters do not destroy the CSS output.
+   */
+  public function testCssImport(): void {
+    $mock_grouper = $this->createMock(AssetCollectionGrouperInterface::class);
+    $mock_grouper->method('group')
+      ->willReturnCallback(function ($assets) {
+        return [
+          [
+            'items' => $assets,
+            'type' => 'file',
+            'preprocess' => TRUE,
+          ],
+        ];
+      });
+    $mock_optimizer = $this->createMock(AssetOptimizerInterface::class);
+    $mock_optimizer->method('optimize')
+      ->willReturn(
+        file_get_contents(__DIR__ . '/css_test_files/css_input_with_import.css.optimized.css'),
+        file_get_contents(__DIR__ . '/css_test_files/css_subfolder/css_input_with_import.css.optimized.css')
+      );
+    $mock_theme_manager = $this->createMock(ThemeManagerInterface::class);
+    $mock_dependency_resolver = $this->createMock(LibraryDependencyResolverInterface::class);
+    $mock_state = $this->createMock(StateInterface::class);
+    $mock_file_system = $this->createMock(FileSystemInterface::class);
+    $mock_config_factory = $this->createMock(ConfigFactoryInterface::class);
+    $mock_file_url_generator = $this->createMock(FileUrlGeneratorInterface::class);
+    $mock_time = $this->createMock(TimeInterface::class);
+    $mock_language = $this->createMock(LanguageManagerInterface::class);
+    $optimizer = new CssCollectionOptimizerLazy($mock_grouper, $mock_optimizer, $mock_theme_manager, $mock_dependency_resolver, new RequestStack(), $mock_file_system, $mock_config_factory, $mock_file_url_generator, $mock_time, $mock_language, $mock_state);
+    $aggregate = $optimizer->optimizeGroup(
+      [
+        'items' => [
+          'core/modules/system/tests/modules/common_test/common_test_css_import.css' => [
+            'type' => 'file',
+            'data' => 'core/modules/system/tests/modules/common_test/common_test_css_import.css',
+            'preprocess' => TRUE,
+          ],
+          'core/modules/system/tests/modules/common_test/common_test_css_import_not_preprocessed.css' => [
+            'type' => 'file',
+            'data' => 'core/modules/system/tests/modules/common_test/common_test_css_import.css',
+            'preprocess' => TRUE,
+          ],
+        ],
+      ],
+    );
+    self::assertStringEqualsFile(__DIR__ . '/css_test_files/css_input_with_import.css.optimized.aggregated.css', $aggregate);
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Asset/CssCollectionOptimizerUnitTest.php b/core/tests/Drupal/Tests/Core/Asset/CssCollectionOptimizerUnitTest.php
index a27ce061978067d4a6ceb8541a321f4b7a6dc43a..f08dd9e1e310c65f77e85ed5f918665d539ef588 100644
--- a/core/tests/Drupal/Tests/Core/Asset/CssCollectionOptimizerUnitTest.php
+++ b/core/tests/Drupal/Tests/Core/Asset/CssCollectionOptimizerUnitTest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Tests\Core\Asset;
 
+use Drupal\Component\Datetime\TimeInterface;
 use Drupal\Core\Asset\AssetCollectionGrouperInterface;
 use Drupal\Core\Asset\AssetDumperInterface;
 use Drupal\Core\Asset\AssetOptimizerInterface;
@@ -31,8 +32,12 @@ class CssCollectionOptimizerUnitTest extends UnitTestCase {
    */
   protected $optimizer;
 
-  protected function setUp(): void {
-    parent::setUp();
+  /**
+   * Tests that CSS imports with strange letters do not destroy the CSS output.
+   *
+   * @group legacy
+   */
+  public function testCssImport() {
     $mock_grouper = $this->createMock(AssetCollectionGrouperInterface::class);
     $mock_grouper->method('group')
       ->willReturnCallback(function ($assets) {
@@ -57,13 +62,8 @@ protected function setUp(): void {
       });
     $mock_state = $this->createMock(StateInterface::class);
     $mock_file_system = $this->createMock(FileSystemInterface::class);
-    $this->optimizer = new CssCollectionOptimizer($mock_grouper, $mock_optimizer, $mock_dumper, $mock_state, $mock_file_system);
-  }
-
-  /**
-   * Test that css imports with strange letters do not destroy the css output.
-   */
-  public function testCssImport() {
+    $mock_time = $this->createMock(TimeInterface::class);
+    $this->optimizer = new CssCollectionOptimizer($mock_grouper, $mock_optimizer, $mock_dumper, $mock_state, $mock_file_system, $mock_time);
     $this->optimizer->optimize([
       'core/modules/system/tests/modules/common_test/common_test_css_import.css' => [
         'type' => 'file',
@@ -75,7 +75,8 @@ public function testCssImport() {
         'data' => 'core/modules/system/tests/modules/common_test/common_test_css_import.css',
         'preprocess' => TRUE,
       ],
-    ]);
+    ],
+    []);
     self::assertEquals(file_get_contents(__DIR__ . '/css_test_files/css_input_with_import.css.optimized.aggregated.css'), $this->dumperData);
   }