diff --git a/composer.json b/composer.json
index 062599973e1002a1bcb75e0361590fcde920f761..545b7bc6d727a50cd38d87e57754e2bc2dfe97d7 100644
--- a/composer.json
+++ b/composer.json
@@ -165,7 +165,7 @@
         "drupal/social_media_links": "^2.8",
         "drupal/superfish": "1.4",
         "drupal/svg_image": "1.15",
-        "drupal/token": "1.9",
+        "drupal/token": "1.11",
         "drupal/twig_tweak": "2.9",
         "drupal/twitter_block": "3.0-alpha1",
         "drupal/ultimate_cron": "^2.0@alpha",
diff --git a/composer.lock b/composer.lock
index c4e0a873cba92f1ff9464754d0e03c2401b16b46..012f7be0a77f37512160632d146f1705882b62d7 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "03a5824d6fa8642d5beb1999818d391f",
+    "content-hash": "cf5892a08916c24fe3a7342690decd98",
     "packages": [
         {
             "name": "alchemy/zippy",
@@ -7305,26 +7305,26 @@
         },
         {
             "name": "drupal/token",
-            "version": "1.9.0",
+            "version": "1.11.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/token.git",
-                "reference": "8.x-1.9"
+                "reference": "8.x-1.11"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/token-8.x-1.9.zip",
-                "reference": "8.x-1.9",
-                "shasum": "a5d234382a1a0e4ba61d4c7a2fa10671ca656be4"
+                "url": "https://ftp.drupal.org/files/projects/token-8.x-1.11.zip",
+                "reference": "8.x-1.11",
+                "shasum": "da264b36d1f88eb0c74bf84e18e8789854f98400"
             },
             "require": {
-                "drupal/core": "^8.8 || ^9"
+                "drupal/core": "^9.2 || ^10"
             },
             "type": "drupal-module",
             "extra": {
                 "drupal": {
-                    "version": "8.x-1.9",
-                    "datestamp": "1608284866",
+                    "version": "8.x-1.11",
+                    "datestamp": "1659471813",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index 6c7ddd963ef13679a00ff343f6e7629a40383a5c..786696c473d3706b3042791c6c9e007f157311c7 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -7576,27 +7576,27 @@
         },
         {
             "name": "drupal/token",
-            "version": "1.9.0",
-            "version_normalized": "1.9.0.0",
+            "version": "1.11.0",
+            "version_normalized": "1.11.0.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/token.git",
-                "reference": "8.x-1.9"
+                "reference": "8.x-1.11"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/token-8.x-1.9.zip",
-                "reference": "8.x-1.9",
-                "shasum": "a5d234382a1a0e4ba61d4c7a2fa10671ca656be4"
+                "url": "https://ftp.drupal.org/files/projects/token-8.x-1.11.zip",
+                "reference": "8.x-1.11",
+                "shasum": "da264b36d1f88eb0c74bf84e18e8789854f98400"
             },
             "require": {
-                "drupal/core": "^8.8 || ^9"
+                "drupal/core": "^9.2 || ^10"
             },
             "type": "drupal-module",
             "extra": {
                 "drupal": {
-                    "version": "8.x-1.9",
-                    "datestamp": "1608284866",
+                    "version": "8.x-1.11",
+                    "datestamp": "1659471813",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php
index 98f6596b91e438771c59bb80542fa4b81794eb48..7d3cf901d65a23ff833e400219e48800a7912f74 100644
--- a/vendor/composer/installed.php
+++ b/vendor/composer/installed.php
@@ -3,7 +3,7 @@
         'name' => 'osu-asc-webservices/d8-upstream',
         'pretty_version' => 'dev-master',
         'version' => 'dev-master',
-        'reference' => '980147862f8ed56447f54527d88a8e341a7ded83',
+        'reference' => '864332360465433168f0fa3c5657b2dacd15b33c',
         'type' => 'project',
         'install_path' => __DIR__ . '/../../',
         'aliases' => array(),
@@ -1235,9 +1235,9 @@
             'dev_requirement' => false,
         ),
         'drupal/token' => array(
-            'pretty_version' => '1.9.0',
-            'version' => '1.9.0.0',
-            'reference' => '8.x-1.9',
+            'pretty_version' => '1.11.0',
+            'version' => '1.11.0.0',
+            'reference' => '8.x-1.11',
             'type' => 'drupal-module',
             'install_path' => __DIR__ . '/../../web/modules/token',
             'aliases' => array(),
@@ -1594,7 +1594,7 @@
         'osu-asc-webservices/d8-upstream' => array(
             'pretty_version' => 'dev-master',
             'version' => 'dev-master',
-            'reference' => '980147862f8ed56447f54527d88a8e341a7ded83',
+            'reference' => '864332360465433168f0fa3c5657b2dacd15b33c',
             'type' => 'project',
             'install_path' => __DIR__ . '/../../',
             'aliases' => array(),
diff --git a/web/modules/token/js/token.js b/web/modules/token/js/token.js
index 3a41d6259a18efde7c4d706769b993e8192ed9b8..8dc6acfcd9cbd08d38bb6af3e7235e11be534346 100644
--- a/web/modules/token/js/token.js
+++ b/web/modules/token/js/token.js
@@ -5,9 +5,7 @@
 
   Drupal.behaviors.tokenTree = {
     attach: function (context, settings) {
-      $('table.token-tree', context).once('token-tree').each(function () {
-        $(this).treetable({ expandable: true });
-      });
+      $(once('token-tree', 'table.token-tree', context)).treetable({ expandable: true});
     }
   };
 
@@ -18,8 +16,8 @@
         drupalSettings.tokenFocusedField = this;
       });
 
-      $('.token-click-insert .token-key', context).once('token-click-insert').each(function () {
-        var newThis = $('<a href="javascript:void(0);" title="' + Drupal.t('Insert this token into your form') + '">' + $(this).html() + '</a>').click(function () {
+      once('token-click-insert', '.token-click-insert .token-key', context).forEach(function (token) {
+        var newThis = $('<a href="javascript:void(0);" title="' + Drupal.t('Insert this token into your form') + '">' + $(token).html() + '</a>').click(function () {
           var content = this.text;
 
           // Always work in normal text areas that currently have focus.
@@ -58,7 +56,7 @@
 
           return false;
         });
-        $(this).html(newThis);
+        $(token).html(newThis);
       });
 
       function insertAtCursor(editor, content) {
diff --git a/web/modules/token/migrations/state/token.migrate_drupal.yml b/web/modules/token/migrations/state/token.migrate_drupal.yml
new file mode 100644
index 0000000000000000000000000000000000000000..e0196c288aa55830f6eb8088a7c9a29b79bcf0c8
--- /dev/null
+++ b/web/modules/token/migrations/state/token.migrate_drupal.yml
@@ -0,0 +1,5 @@
+finished:
+  6:
+    token: token
+  7:
+    token: token
diff --git a/web/modules/token/src/Controller/TokenAutocompleteController.php b/web/modules/token/src/Controller/TokenAutocompleteController.php
deleted file mode 100644
index 979f832a48df8913524191c2f2af9560fd838886..0000000000000000000000000000000000000000
--- a/web/modules/token/src/Controller/TokenAutocompleteController.php
+++ /dev/null
@@ -1,77 +0,0 @@
-<?php
-
-namespace Drupal\token\Controller;
-
-use Drupal\Core\Controller\ControllerBase;
-use Drupal\token\TreeBuilderInterface;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-use Symfony\Component\HttpFoundation\JsonResponse;
-use Symfony\Component\HttpFoundation\Request;
-
-/**
- * Returns autocomplete responses for tokens.
- */
-class TokenAutocompleteController extends ControllerBase {
-
-  /**
-   * @var \Drupal\token\TreeBuilderInterface
-   */
-  protected $treeBuilder;
-
-  public function __construct(TreeBuilderInterface $tree_builder) {
-    $this->treeBuilder = $tree_builder;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static(
-      $container->get('token.tree_builder')
-    );
-  }
-
-  /**
-   * Retrieves suggestions for block category autocompletion.
-   *
-   * @param \Symfony\Component\HttpFoundation\Request $request
-   *   The current request.
-   * @param string $token_type
-   *   The token type.
-   * @param string $filter
-   *   The autocomplete filter.
-   *
-   * @return \Symfony\Component\HttpFoundation\JsonResponse
-   *   A JSON response containing autocomplete suggestions.
-   */
-  public function autocomplete($token_type, $filter, Request $request) {
-    $filter = substr($filter, strrpos($filter, '['));
-
-    $matches = [];
-
-    if (!mb_strlen($filter)) {
-      $matches["[{$token_type}:"] = 0;
-    }
-    else {
-      $depth = max(1, substr_count($filter, ':'));
-      $tree = $this->treeBuilder->buildTree($token_type, ['flat' => TRUE, 'depth' => $depth]);
-      foreach (array_keys($tree) as $token) {
-        if (strpos($token, $filter) === 0) {
-          $matches[$token] = levenshtein($token, $filter);
-          if (isset($tree[$token]['children'])) {
-            $token = rtrim($token, ':]') . ':';
-            $matches[$token] = levenshtein($token, $filter);
-          }
-        }
-      }
-    }
-
-    asort($matches);
-
-    $keys = array_keys($matches);
-    $matches = array_combine($keys, $keys);
-
-    return new JsonResponse($matches);
-  }
-
-}
diff --git a/web/modules/token/src/Element/TokenTreeTable.php b/web/modules/token/src/Element/TokenTreeTable.php
index 6de9c95d3c2e3d3a392133a980e21c1597ecedbb..cc607d5db39ff6be67fa6093981d7e04b9a06c28 100644
--- a/web/modules/token/src/Element/TokenTreeTable.php
+++ b/web/modules/token/src/Element/TokenTreeTable.php
@@ -81,10 +81,6 @@ public static function preRenderTokenTree($element) {
       }
     }
 
-    if (!empty($element['#rows'])) {
-      $element['#attached']['library'][] = 'token/jquery.treeTable';
-    }
-
     // Fill headers if one is not specified.
     if (empty($element['#header'])) {
       $column_map = [
diff --git a/web/modules/token/src/Routing/RouteSubscriber.php b/web/modules/token/src/Routing/RouteSubscriber.php
index b856f43e6f851f8ea4c86bc8f93c9d2fc1070604..65a232b6f4a223f94f15f097ea2457a38286729b 100644
--- a/web/modules/token/src/Routing/RouteSubscriber.php
+++ b/web/modules/token/src/Routing/RouteSubscriber.php
@@ -66,7 +66,7 @@ protected function alterRoutes(RouteCollection $collection) {
   /**
    * {@inheritdoc}
    */
-  public static function getSubscribedEvents() {
+  public static function getSubscribedEvents(): array {
     $events = parent::getSubscribedEvents();
     $events[RoutingEvents::ALTER] = array('onAlterRoutes', 100);
     return $events;
diff --git a/web/modules/token/tests/modules/token_module_test/token_module_test.info.yml b/web/modules/token/tests/modules/token_module_test/token_module_test.info.yml
index e58af02fce720ba1ca824352717485664677b4cd..6ceda27bc826e31d909e9c3d06c758a7b5e36506 100644
--- a/web/modules/token/tests/modules/token_module_test/token_module_test.info.yml
+++ b/web/modules/token/tests/modules/token_module_test/token_module_test.info.yml
@@ -2,10 +2,9 @@ type: module
 name: Token Module Test
 description: Testing module for token functionality.
 package: Testing
-core_version_requirement: ^8.7.7 || ^9
 hidden: TRUE
 
-# Information added by Drupal.org packaging script on 2020-12-18
-version: '8.x-1.9'
+# Information added by Drupal.org packaging script on 2022-08-02
+version: '8.x-1.11'
 project: 'token'
-datestamp: 1608284868
+datestamp: 1659471815
diff --git a/web/modules/token/tests/modules/token_module_test/token_module_test.module b/web/modules/token/tests/modules/token_module_test/token_module_test.module
index c022b6575c9efb5034e44452063d9400d692fd55..d76680c99d41b7009124cc772c5a1ab69375e18d 100644
--- a/web/modules/token/tests/modules/token_module_test/token_module_test.module
+++ b/web/modules/token/tests/modules/token_module_test/token_module_test.module
@@ -26,7 +26,7 @@ function token_module_test_page_attachments() {
 function token_module_test_node_presave(NodeInterface $node) {
   // Transform tokens in the body.
   // @see \Drupal\token\Tests\TokenMenuTest::testMenuTokens()
-  if ($node->hasField('body')) {
+  if ($node->hasField('body') && $node->get('body')->value) {
     $node->body->value = \Drupal::token()
       ->replace($node->body->value, ['node' => $node]);
   }
diff --git a/web/modules/token/tests/modules/token_user_picture/token_user_picture.info.yml b/web/modules/token/tests/modules/token_user_picture/token_user_picture.info.yml
index 601a0b85236e9e6e8337111e810468ed41bccab5..78b477996f5bf089006c67aadae8d569e737d99e 100644
--- a/web/modules/token/tests/modules/token_user_picture/token_user_picture.info.yml
+++ b/web/modules/token/tests/modules/token_user_picture/token_user_picture.info.yml
@@ -2,12 +2,11 @@ type: module
 name: Token User picture
 description: Testing module that provides user pictures field.
 package: Testing
-core_version_requirement: ^8.7.7 || ^9
 hidden: TRUE
 dependencies:
   - drupal:image
 
-# Information added by Drupal.org packaging script on 2020-12-18
-version: '8.x-1.9'
+# Information added by Drupal.org packaging script on 2022-08-02
+version: '8.x-1.11'
 project: 'token'
-datestamp: 1608284868
+datestamp: 1659471815
diff --git a/web/modules/token/tests/src/Functional/TokenBlockTest.php b/web/modules/token/tests/src/Functional/TokenBlockTest.php
index 4833df2ff3c0d0c089d2120bfd325a491d6c48f3..1fa9610b02a3c3e281426d44fd5919cfd7eb8077 100644
--- a/web/modules/token/tests/src/Functional/TokenBlockTest.php
+++ b/web/modules/token/tests/src/Functional/TokenBlockTest.php
@@ -13,16 +13,14 @@
 class TokenBlockTest extends TokenTestBase {
 
   /**
-   * Modules to enable.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  public static $modules = ['block', 'node', 'views', 'block_content'];
+  protected static $modules = ['block', 'node', 'views', 'block_content'];
 
   /**
    * {@inheritdoc}
    */
-  public function setUp($modules = []) {
+  public function setUp(): void {
     parent::setUp();
     $this->admin_user = $this->drupalCreateUser(['access content', 'administer blocks']);
     $this->drupalLogin($this->admin_user);
@@ -52,10 +50,10 @@ public function testBlockTitleTokens() {
     // Ensure that the link to available tokens is present and correctly
     // positioned.
     $this->assertSession()->linkExists('Browse available tokens.');
-    $this->assertText('This field supports tokens. Browse available tokens.');
-    $this->drupalPostForm(NULL, [], 'Save block');
+    $this->assertSession()->pageTextContains('This field supports tokens. Browse available tokens.');
+    $this->submitForm([], 'Save block');
     // Ensure token validation is working on the block.
-    $this->assertText('Title is using the following invalid tokens: [user:name].');
+    $this->assertSession()->pageTextContains('Title is using the following invalid tokens: [user:name].');
 
     // Create the block for real now with a valid title.
     $settings = $block->get('settings');
diff --git a/web/modules/token/tests/src/Functional/TokenCurrentPageTest.php b/web/modules/token/tests/src/Functional/TokenCurrentPageTest.php
index d3d5cebf40a16883db6164d8a806cad7b94c048b..f5170147b731b97c662d4f5f40304343d51737ea 100644
--- a/web/modules/token/tests/src/Functional/TokenCurrentPageTest.php
+++ b/web/modules/token/tests/src/Functional/TokenCurrentPageTest.php
@@ -12,11 +12,9 @@
 class TokenCurrentPageTest extends TokenTestBase {
 
   /**
-   * Modules to enable.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  public static $modules = ['node'];
+  protected static $modules = ['node'];
 
   function testCurrentPageTokens() {
     // Cache clear is necessary because the frontpage was already cached by an
diff --git a/web/modules/token/tests/src/Functional/TokenFieldUiTest.php b/web/modules/token/tests/src/Functional/TokenFieldUiTest.php
index 5da0416c44c7b211b883cfd1eda1c39c98571944..626046ee2535ae8d4ee9306fed5d16a6592c9082 100644
--- a/web/modules/token/tests/src/Functional/TokenFieldUiTest.php
+++ b/web/modules/token/tests/src/Functional/TokenFieldUiTest.php
@@ -22,18 +22,16 @@ class TokenFieldUiTest extends TokenTestBase {
   protected $adminUser;
 
   /**
-   * Modules to enable.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  public static $modules = ['field_ui', 'node', 'image'];
+  protected static $modules = ['field_ui', 'node', 'image'];
 
   /**
    * {@inheritdoc}
    */
-  public function setUp($modules = []) {
+  public function setUp(): void {
     parent::setUp();
-    $this->adminUser = $this->drupalCreateUser(['administer content types', 'administer node fields']);
+    $this->adminUser = $this->drupalCreateUser(['bypass node access', 'administer content types', 'administer node fields']);
     $this->drupalLogin($this->adminUser);
 
     $node_type = NodeType::create([
@@ -108,18 +106,19 @@ public function testFileFieldUi() {
     $this->assertSession()->linkByHrefExists('token/tree');
 
     // Ensure that the default file directory value validates correctly.
-    $this->drupalPostForm(NULL, [], 'Save settings');
-    $this->assertText(t('Saved Image configuration.'));
+    $this->submitForm([], 'Save settings');
+    $this->assertSession()->pageTextContains('Saved Image configuration.');
   }
 
   public function testFieldDescriptionTokens() {
     $edit = [
       'description' => 'The site is called [site:name].',
     ];
-    $this->drupalPostForm('admin/structure/types/manage/article/fields/node.article.field_body', $edit, 'Save settings');
+    $this->drupalGet('admin/structure/types/manage/article/fields/node.article.field_body');
+    $this->submitForm($edit, 'Save settings');
 
     $this->drupalGet('node/add/article');
-    $this->assertText('The site is called Drupal.');
+    $this->assertSession()->pageTextContains('The site is called Drupal.');
   }
 
   /**
diff --git a/web/modules/token/tests/src/Functional/TokenMenuTest.php b/web/modules/token/tests/src/Functional/TokenMenuTest.php
index 532f05971e98499118ff81569f3c17c71cbac3db..8b1c696acd9c9a5115e91d270f8c9c4a91645eaf 100644
--- a/web/modules/token/tests/src/Functional/TokenMenuTest.php
+++ b/web/modules/token/tests/src/Functional/TokenMenuTest.php
@@ -18,11 +18,9 @@
 class TokenMenuTest extends TokenTestBase {
 
   /**
-   * Modules to enable.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  public static $modules = [
+  protected static $modules = [
     'menu_ui',
     'node',
     'block',
@@ -153,11 +151,12 @@ function testMenuTokens() {
       'menu_options[main]' => 1,
       'menu_parent' => 'main-menu:',
     ];
-    $this->drupalPostForm('admin/structure/types/manage/page', $edit, 'Save content type');
+    $this->drupalGet('admin/structure/types/manage/page');
+    $this->submitForm($edit, 'Save content type');
 
     // Use a menu-link token in the body.
     $this->drupalGet('node/add/page');
-    $this->drupalPostForm(NULL, [
+    $this->submitForm([
       // This should get replaced on save.
       // @see token_module_test_node_presave()
       'title[0][value]' => 'Node menu title test',
@@ -171,9 +170,11 @@ function testMenuTokens() {
     // Disable the menu link, save the node and verify that the menu link is
     // no longer displayed.
     $link = menu_ui_get_menu_link_defaults($node);
-    $this->drupalPostForm('admin/structure/menu/manage/main-menu', ['links[menu_plugin_id:' . $link['id'] . '][enabled]' => FALSE], 'Save');
-    $this->assertText('Menu Main menu has been updated.');
-    $this->drupalPostForm('node/' . $node->id() . '/edit', [], 'Save');
+    $this->drupalGet('admin/structure/menu/manage/main-menu');
+    $this->submitForm(['links[menu_plugin_id:' . $link['id'] . '][enabled]' => FALSE], 'Save');
+    $this->assertSession()->pageTextContains('Menu Main menu has been updated.');
+    $this->drupalGet('node/' . $node->id() . '/edit');
+    $this->submitForm([], 'Save');
     $this->assertSession()->linkNotExists('Test preview');
 
     // Now test a parent link and token.
@@ -190,7 +191,7 @@ function testMenuTokens() {
       return strpos($element->getText(), 'Test preview') !== FALSE;
     });
     $this->assertCount(1, $options);
-    $this->drupalPostForm(NULL, [
+    $this->submitForm([
       'title[0][value]' => 'Node menu title parent path test',
       'body[0][value]' => 'This is a [node:menu-link:parent:url:path] token to the menu link parent',
       'menu[enabled]' => 1,
@@ -201,7 +202,8 @@ function testMenuTokens() {
     $this->assertEquals('This is a /admin/config token to the menu link parent', $node->body->value);
 
     // Now edit the node and update the parent and title.
-    $this->drupalPostForm('node/' . $node->id() . '/edit', [
+    $this->drupalGet('node/' . $node->id() . '/edit');
+    $this->submitForm([
       'menu[menu_parent]' => 'main-menu:' . $node_link->getPluginId(),
       'title[0][value]' => 'Node menu title edit parent path test',
       'body[0][value]' => 'This is a [node:menu-link:parent:url:path] token to the menu link parent',
@@ -225,7 +227,7 @@ function testMenuTokens() {
 
     // Now add a new node with no menu.
     $this->drupalGet('node/add/page');
-    $this->drupalPostForm(NULL, [
+    $this->submitForm([
       'title[0][value]' => 'Node menu adding menu later test',
       'body[0][value]' => 'Going to add a menu link on edit',
       'menu[enabled]' => 0,
@@ -233,7 +235,7 @@ function testMenuTokens() {
     $node = $this->drupalGetNodeByTitle('Node menu adding menu later test');
     // Now edit it and add a menu item.
     $this->drupalGet('node/' . $node->id() . '/edit');
-    $this->drupalPostForm(NULL, [
+    $this->submitForm([
       'title[0][value]' => 'Node menu adding menu later test',
       'body[0][value]' => 'This is a [node:menu-link:parent:url:path] token to the menu link parent',
       'menu[enabled]' => 1,
@@ -247,6 +249,7 @@ function testMenuTokens() {
     $this->assertNotEmpty($link['entity_id']);
     $query = \Drupal::entityQuery('menu_link_content')
       ->condition('link.uri', 'entity:node/' . $node->id())
+      ->accessCheck(TRUE)
       ->sort('id', 'ASC')
       ->range(0, 1);
     $result = $query->execute();
@@ -260,8 +263,9 @@ function testMenuTokens() {
       'menu[enabled]' => 1,
       'menu[title]' => 'menu link provided by node',
     ];
-    $this->drupalPostForm('node/add/page', $edit, 'Save');
-    $this->assertText('page ' . $node_title . ' has been created');
+    $this->drupalGet('node/add/page');
+    $this->submitForm($edit, 'Save');
+    $this->assertSession()->pageTextContains('page ' . $node_title . ' has been created');
     $node = $this->drupalGetNodeByTitle($node_title);
 
     $menu_ui_link1 = MenuLinkContent::create([
@@ -303,6 +307,7 @@ function testMultilingualMenu() {
     // Create the article content type.
     $node_type = NodeType::create([
       'type' => 'article',
+      'name' => 'Article',
     ]);
     $node_type->save();
 
@@ -328,8 +333,8 @@ function testMultilingualMenu() {
       'settings[node][article][fields][title]' => TRUE,
       'settings[menu_link_content][menu_link_content][translatable]' => TRUE,
     ];
-    $this->drupalPostForm(NULL, $edit, 'Save configuration');
-    $this->assertText('Settings successfully updated.');
+    $this->submitForm($edit, 'Save configuration');
+    $this->assertSession()->pageTextContains('Settings successfully updated.');
 
     // Create an english node with an english menu.
     $this->drupalGet('/node/add/article');
@@ -338,8 +343,9 @@ function testMultilingualMenu() {
       'menu[enabled]' => TRUE,
       'menu[title]' => 'English menu title',
     ];
-    $this->drupalPostForm('/node/add/article', $edit, 'Save');
-    $this->assertText('English test node with menu has been created.');
+    $this->drupalGet('/node/add/article');
+    $this->submitForm($edit, 'Save');
+    $this->assertSession()->pageTextContains('English test node with menu has been created.');
 
     // Add a german translation.
     $this->drupalGet('node/1/translations');
@@ -349,8 +355,8 @@ function testMultilingualMenu() {
       'menu[enabled]' => TRUE,
       'menu[title]' => 'German menu title',
     ];
-    $this->drupalPostForm(NULL, $edit, 'Save (this translation)');
-    $this->assertText('German test node with menu has been updated.');
+    $this->submitForm($edit, 'Save (this translation)');
+    $this->assertSession()->pageTextContains('German test node with menu has been updated.');
 
     // Verify that the menu links are correct.
     $this->drupalGet('node/1');
diff --git a/web/modules/token/tests/src/Functional/TokenMenuUiContentModerationTest.php b/web/modules/token/tests/src/Functional/TokenMenuUiContentModerationTest.php
index 0c8e269ea68e40e4fd3d30ed63e13ffe86a838f5..a9714c7c4be955f557ee0e2fa67422afd4984ef7 100644
--- a/web/modules/token/tests/src/Functional/TokenMenuUiContentModerationTest.php
+++ b/web/modules/token/tests/src/Functional/TokenMenuUiContentModerationTest.php
@@ -14,6 +14,6 @@ class TokenMenuUiContentModerationTest extends MenuUiContentModerationTest {
   /**
    * {@inheritdoc}
    */
-  public static $modules = ['token'];
+  protected static $modules = ['token'];
 
 }
diff --git a/web/modules/token/tests/src/Functional/TokenTestBase.php b/web/modules/token/tests/src/Functional/TokenTestBase.php
index fda4898b91b45d8b2b6a41485e69dea0fb8d85aa..20d559685f20dfcb3201dc63c7d1ce5049ca716a 100644
--- a/web/modules/token/tests/src/Functional/TokenTestBase.php
+++ b/web/modules/token/tests/src/Functional/TokenTestBase.php
@@ -12,11 +12,9 @@ abstract class TokenTestBase extends BrowserTestBase {
   use TokenTestTrait;
 
   /**
-   * Modules to enable.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  public static $modules = ['path', 'token', 'token_module_test'];
+  protected static $modules = ['path', 'token', 'token_module_test'];
 
   /**
    * {@inheritdoc}
diff --git a/web/modules/token/tests/src/Functional/TokenURLTest.php b/web/modules/token/tests/src/Functional/TokenURLTest.php
index a8d27f282dc6af3d3da881da46d6eb42dc6d2f6c..4668c7a7bbba619ca3a0ee3bba69343a70fc088e 100644
--- a/web/modules/token/tests/src/Functional/TokenURLTest.php
+++ b/web/modules/token/tests/src/Functional/TokenURLTest.php
@@ -12,16 +12,14 @@
 class TokenURLTest extends TokenTestBase {
 
   /**
-   * Modules to enable.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  public static $modules = ['node'];
+  protected static $modules = ['node'];
 
   /**
    * {@inheritdoc}
    */
-  public function setUp() {
+  public function setUp(): void {
     parent::setUp();
     $this->saveAlias('/node/1', '/first-node');
   }
diff --git a/web/modules/token/tests/src/Functional/TokenUserTest.php b/web/modules/token/tests/src/Functional/TokenUserTest.php
index 8ff3744bc4749c7f802d570cee14f7d6b0d68c0f..ff146cde74f77195c14d95e24f2a0a4097bd52bd 100644
--- a/web/modules/token/tests/src/Functional/TokenUserTest.php
+++ b/web/modules/token/tests/src/Functional/TokenUserTest.php
@@ -25,16 +25,14 @@ class TokenUserTest extends TokenTestBase {
   protected $account = NULL;
 
   /**
-   * Modules to enable.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  public static $modules = ['token_user_picture'];
+  protected static $modules = ['token_user_picture'];
 
   /**
    * {@inheritdoc}
    */
-  public function setUp() {
+  public function setUp(): void {
     parent::setUp();
 
     $this->account = $this->drupalCreateUser([
@@ -62,7 +60,8 @@ public function testUserTokens() {
     // Add a user picture to the account.
     $image = current($this->getTestFiles('image'));
     $edit = ['files[user_picture_0]' => \Drupal::service('file_system')->realpath($image->uri)];
-    $this->drupalPostForm('user/' . $this->account->id() . '/edit', $edit, 'Save');
+    $this->drupalGet('user/' . $this->account->id() . '/edit');
+    $this->submitForm($edit, 'Save');
 
     $storage = \Drupal::entityTypeManager()->getStorage('user');
 
@@ -124,7 +123,7 @@ public function testUserTokens() {
    */
   public function testUserAccountSettings() {
     $this->drupalGet('admin/config/people/accounts');
-    $this->assertText('The list of available tokens that can be used in e-mails is provided below.');
+    $this->assertSession()->pageTextContains('The list of available tokens that can be used in e-mails is provided below.');
     $this->assertSession()->linkExists('Browse available tokens.');
     $this->assertSession()->linkByHrefExists('token/tree');
   }
diff --git a/web/modules/token/tests/src/Functional/Tree/AutocompleteTest.php b/web/modules/token/tests/src/Functional/Tree/AutocompleteTest.php
deleted file mode 100644
index 1f6f4034ba40b9332af909299898f79a1ac3ac0d..0000000000000000000000000000000000000000
--- a/web/modules/token/tests/src/Functional/Tree/AutocompleteTest.php
+++ /dev/null
@@ -1,78 +0,0 @@
-<?php
-
-namespace Drupal\Tests\token\Functional\Tree;
-
-use Drupal\Component\Serialization\Json;
-use Drupal\Tests\token\Functional\TokenTestBase;
-
-/**
- * Test token autocomplete.
- *
- * @group token
- */
-class AutocompleteTest extends TokenTestBase {
-
-  /**
-   * Modules to enable.
-   *
-   * @var array
-   */
-  public static $modules = ['node'];
-
-  /**
-   * Tests autocomplete for node tokens.
-   */
-  public function testNodeAutocomplete() {
-    $url_prefix = "token/autocomplete/node/";
-
-    $url = $url_prefix . 'Title of [nod';
-    $response = Json::decode($this->drupalGet($url, [
-      'query' => [
-        '_format' => 'json',
-      ],
-    ]));
-
-    $this->assertArrayHasKey('[node:nid]', $response);
-    $this->assertArrayHasKey('[node:author]', $response);
-    $this->assertArrayHasKey('[node:url]', $response);
-    $this->assertArrayHasKey('[node:url:', $response);
-
-    $url = $url_prefix . 'Title of [node:url:';
-    $response = Json::decode($this->drupalGet($url, [
-      'query' => [
-        '_format' => 'json',
-      ],
-    ]));
-
-    $this->assertArrayHasKey('[node:url:path]', $response);
-    $this->assertArrayHasKey('[node:url:absolute]', $response);
-  }
-
-  /**
-   * Tests autocomplete for user tokens.
-   */
-  public function testUserAutocomplete() {
-    $url_prefix = "token/autocomplete/user/";
-
-    $url = $url_prefix . 'Name of the [us';
-    $response = Json::decode($this->drupalGet($url, [
-      'query' => [
-        '_format' => 'json',
-      ],
-    ]));
-
-    $this->assertArrayHasKey('[user:uid]', $response);
-    $this->assertArrayHasKey('[user:original]', $response);
-    $this->assertArrayHasKey('[user:url]', $response);
-    $this->assertArrayHasKey('[user:url:', $response);
-
-    $url = $url_prefix . 'Title of [user:original:';
-    $response = Json::decode($this->drupalGet($url, [
-      'query' => [
-        '_format' => 'json',
-      ],
-    ]));
-
-    $this->assertArrayHasKey('[user:original:uid]', $response);
-  }
-}
diff --git a/web/modules/token/tests/src/Functional/Tree/HelpPageTest.php b/web/modules/token/tests/src/Functional/Tree/HelpPageTest.php
index 719fb0f850f85452366884bf9b180ec1038a519f..f32081203df19c43d6c28096762da33a08258599 100644
--- a/web/modules/token/tests/src/Functional/Tree/HelpPageTest.php
+++ b/web/modules/token/tests/src/Functional/Tree/HelpPageTest.php
@@ -19,13 +19,11 @@ class HelpPageTest extends TokenTestBase {
   protected $account;
 
   /**
-   * Modules to enable.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  public static $modules = ['help'];
+  protected static $modules = ['help'];
 
-  public function setUp() {
+  public function setUp(): void {
     parent::setUp();
 
     $this->account = $this->drupalCreateUser(['access administration pages']);
@@ -37,7 +35,7 @@ public function setUp() {
    */
   public function testHelpPageTree() {
     $this->drupalGet('admin/help/token');
-    $this->assertText('The list of the currently available tokens on this site are shown below.');
+    $this->assertSession()->pageTextContains('The list of the currently available tokens on this site are shown below.');
 
     $this->assertTokenGroup('Current date');
     $this->assertTokenGroup('Site information');
diff --git a/web/modules/token/tests/src/Functional/Tree/TreeTest.php b/web/modules/token/tests/src/Functional/Tree/TreeTest.php
index c1c81104b7078f0f84ae7c9270a4937eccfbba6d..515295b129b74998207bd98fbfbf6b8323da6801 100644
--- a/web/modules/token/tests/src/Functional/Tree/TreeTest.php
+++ b/web/modules/token/tests/src/Functional/Tree/TreeTest.php
@@ -20,13 +20,11 @@ class TreeTest extends TokenTestBase {
   protected $account;
 
   /**
-   * Modules to enable.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  public static $modules = ['node'];
+  protected static $modules = ['node'];
 
-  public function setUp() {
+  public function setUp(): void {
     parent::setUp();
 
     $this->account = $this->drupalCreateUser(['administer account settings']);
@@ -96,7 +94,7 @@ public function testUserTokens() {
     // Request with show_restricted set to TRUE to show restricted tokens and
     // check for them.
     $this->drupalGet($this->getTokenTreeUrl(['token_types' => ['user'], 'show_restricted' => TRUE]));
-    $this->assertEquals('MISS', $this->drupalGetHeader('x-drupal-dynamic-cache'), 'Cache was not hit');
+    $this->assertEquals('MISS', $this->getSession()->getResponseHeader('x-drupal-dynamic-cache'), 'Cache was not hit');
     $this->assertTokenInTree('[user:one-time-login-url]', 'user');
     $this->assertTokenInTree('[user:original:cancel-url]', 'user--original');
   }
diff --git a/web/modules/token/tests/src/Functional/UrlTest.php b/web/modules/token/tests/src/Functional/UrlTest.php
index 107629bbe80338861d448ed3281b682b99b1a002..565dff89a9940ab1ceecc1f149c6f6dff1faafec 100644
--- a/web/modules/token/tests/src/Functional/UrlTest.php
+++ b/web/modules/token/tests/src/Functional/UrlTest.php
@@ -32,11 +32,9 @@ class UrlTest extends BrowserTestBase {
   protected $node2;
 
   /**
-   * Modules to install.
-   *
-   * @var string[]
+   * {@inheritdoc}
    */
-  public static $modules = ['node', 'token', 'block'];
+  protected static $modules = ['node', 'token', 'block'];
 
   /**
    * {@inheritdoc}
@@ -46,7 +44,7 @@ class UrlTest extends BrowserTestBase {
   /**
    * {@inheritdoc}
    */
-  protected function setUp() {
+  protected function setUp(): void {
     parent::setUp();
 
     $node_type = NodeType::create(['type' => 'article', 'name' => 'Article']);
diff --git a/web/modules/token/tests/src/Kernel/BookTest.php b/web/modules/token/tests/src/Kernel/BookTest.php
index 215fdb54e27f352d81fbc939c7cf4fd32b6308aa..75181f15769538ea6d89c2533e85fadceb53ddc1 100644
--- a/web/modules/token/tests/src/Kernel/BookTest.php
+++ b/web/modules/token/tests/src/Kernel/BookTest.php
@@ -13,16 +13,22 @@
 class BookTest extends KernelTestBase {
 
   /**
-   * Modules to enable.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  public static $modules = ['user', 'field', 'filter', 'text', 'node', 'book'];
+  protected static $modules = [
+    'user',
+    'field',
+    'filter',
+    'text',
+    'node',
+    'book',
+  ];
+
 
   /**
    * {@inheritdoc}
    */
-  protected function setUp() {
+  protected function setUp(): void {
     parent::setUp();
 
     $this->installEntitySchema('user');
diff --git a/web/modules/token/tests/src/Kernel/CommentTest.php b/web/modules/token/tests/src/Kernel/CommentTest.php
index 8054ed20f5d4b37b1f2d6f41faaee218310e8820..9c62e9a5bda30e3cec6a9e97bfc215634f16e3e5 100644
--- a/web/modules/token/tests/src/Kernel/CommentTest.php
+++ b/web/modules/token/tests/src/Kernel/CommentTest.php
@@ -18,16 +18,19 @@ class CommentTest extends KernelTestBase {
   use CommentTestTrait;
 
   /**
-   * Modules to enable.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  public static $modules = ['node', 'comment', 'field', 'text', 'entity_reference'];
+  protected static $modules = [
+    'node',
+    'comment',
+    'field',
+    'text',
+  ];
 
   /**
    * {@inheritdoc}
    */
-  protected function setUp() {
+  protected function setUp(): void {
     parent::setUp();
 
     $this->installEntitySchema('node');
diff --git a/web/modules/token/tests/src/Kernel/DateTest.php b/web/modules/token/tests/src/Kernel/DateTest.php
index c120e1109eb298725ec1335aa04a64489a273b46..47bf5be018f46dcabb2fe7d97d480031bbe6fb17 100644
--- a/web/modules/token/tests/src/Kernel/DateTest.php
+++ b/web/modules/token/tests/src/Kernel/DateTest.php
@@ -10,16 +10,14 @@
 class DateTest extends KernelTestBase {
 
   /**
-   * Modules to enable.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  public static $modules = [];
+  protected static $modules = [];
 
   /**
    * {@inheritdoc}
    */
-  protected function setUp() {
+  protected function setUp(): void {
     parent::setUp();
     $this->installConfig(['system', 'token_module_test']);
   }
diff --git a/web/modules/token/tests/src/Kernel/EntityTest.php b/web/modules/token/tests/src/Kernel/EntityTest.php
index 44501f7fd89adb433bc5cea698a3366211976d03..558750d55d054cb677c0e21dcef6faaafba61e6d 100644
--- a/web/modules/token/tests/src/Kernel/EntityTest.php
+++ b/web/modules/token/tests/src/Kernel/EntityTest.php
@@ -15,16 +15,14 @@
 class EntityTest extends KernelTestBase {
 
   /**
-   * Modules to enable.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  public static $modules = ['node', 'taxonomy', 'text'];
+  protected static $modules = ['node', 'taxonomy', 'text'];
 
   /**
    * {@inheritdoc}
    */
-  public function setUp() {
+  public function setUp(): void {
     parent::setUp();
 
     // Create the default tags vocabulary.
diff --git a/web/modules/token/tests/src/Kernel/FieldTest.php b/web/modules/token/tests/src/Kernel/FieldTest.php
index 345ae676d613a828ea0358557cb108680f06fb1c..d49038dc925b7730f69ce9b9ff486ded0d091e7c 100644
--- a/web/modules/token/tests/src/Kernel/FieldTest.php
+++ b/web/modules/token/tests/src/Kernel/FieldTest.php
@@ -42,16 +42,25 @@ class FieldTest extends KernelTestBase {
   protected $vocabulary;
 
   /**
-   * Modules to enable.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  public static $modules = ['node', 'text', 'field', 'filter', 'contact', 'options', 'taxonomy', 'language', 'datetime', 'datetime_range'];
+  protected static $modules = [
+    'node',
+    'text',
+    'field',
+    'filter',
+    'contact',
+    'options',
+    'taxonomy',
+    'language',
+    'datetime',
+    'datetime_range',
+  ];
 
   /**
    * {@inheritdoc}
    */
-  public function setUp() {
+  public function setUp(): void {
     parent::setUp();
 
     $this->installEntitySchema('user');
@@ -97,6 +106,7 @@ public function setUp() {
 
     $this->testFormat = FilterFormat::create([
       'format' => 'test',
+      'name' => 'Test format',
       'weight' => 1,
       'filters' => [
         'filter_html_escape' => ['status' => TRUE],
diff --git a/web/modules/token/tests/src/Kernel/FileTest.php b/web/modules/token/tests/src/Kernel/FileTest.php
index 6cfa98875b191c58256743225699b32958455484..b3d7a2e7c8525fdf458a43a04ac12e59104f8a5c 100644
--- a/web/modules/token/tests/src/Kernel/FileTest.php
+++ b/web/modules/token/tests/src/Kernel/FileTest.php
@@ -11,16 +11,14 @@
 class FileTest extends KernelTestBase {
 
   /**
-   * Modules to enable.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  public static $modules = ['file'];
+  protected static $modules = ['file'];
 
   /**
    * {@inheritdoc}
    */
-  public function setUp() {
+  public function setUp(): void {
     parent::setUp();
     $this->installEntitySchema('file');
   }
diff --git a/web/modules/token/tests/src/Kernel/KernelTestBase.php b/web/modules/token/tests/src/Kernel/KernelTestBase.php
index c272215ab203e144bdfd1f73125ff2a4006cf7e1..0ccaafb32c2779fe6a3cf6226d4d70dd6bd5ebb5 100644
--- a/web/modules/token/tests/src/Kernel/KernelTestBase.php
+++ b/web/modules/token/tests/src/Kernel/KernelTestBase.php
@@ -13,16 +13,21 @@ abstract class KernelTestBase extends BaseKernelTestBase {
   use TokenTestTrait;
 
   /**
-   * Modules to enable.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  public static $modules = ['path', 'token', 'token_module_test', 'system', 'user', 'path_alias'];
+  protected static $modules = [
+    'path',
+    'token',
+    'token_module_test',
+    'system',
+    'user',
+    'path_alias',
+  ];
 
   /**
    * {@inheritdoc}
    */
-  protected function setUp() {
+  protected function setUp(): void {
     parent::setUp();
 
     $this->installEntitySchema('path_alias');
diff --git a/web/modules/token/tests/src/Kernel/LanguageTest.php b/web/modules/token/tests/src/Kernel/LanguageTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..496b4015c08a5a52c2cbb9540986aab99c39ce22
--- /dev/null
+++ b/web/modules/token/tests/src/Kernel/LanguageTest.php
@@ -0,0 +1,367 @@
+<?php
+
+namespace Drupal\Tests\token\Kernel;
+
+use Drupal\Core\Language\Language;
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\language\Entity\ConfigurableLanguage;
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+
+/**
+ * Tests language tokens.
+ *
+ * @group token
+ */
+class LanguageTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'language',
+    'token',
+  ];
+
+  /**
+   * Language codes of languages to enable during the test.
+   *
+   * @var array
+   */
+  protected $langcodes = ['bg', 'hu', 'nl', 'pt-pt'];
+
+  /**
+   * An array of languages used during the test, keyed by language code.
+   *
+   * @var \Drupal\language\Entity\ConfigurableLanguage[]
+   */
+  protected $languages = [];
+
+  /**
+   * Language prefixes used during the test.
+   *
+   * @var array
+   */
+  protected $language_prefixes = [];
+
+  /**
+   * Language domains used during the test.
+   *
+   * @var array
+   */
+  protected $language_domains = [];
+
+  /**
+   * The token replacement service.
+   *
+   * @var \Drupal\Core\Utility\Token
+   */
+  protected $token;
+
+  /**
+   * The mock language manager service.
+   *
+   * @var \Drupal\Tests\token\Kernel\MockLanguageManager
+   */
+  protected $languageManager;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function register(ContainerBuilder $container) {
+    parent::register($container);
+
+    // Use Portuguese as the default language during the test. We're not using
+    // English so we can detect if the default language is correctly honored.
+    $language = Language::$defaultValues;
+    $language['id'] = 'pt-pt';
+    $language['name'] = 'Portuguese, Portugal';
+    $container->setParameter('language.default_values', $language);
+    $this->container
+      ->register('language.default', 'Drupal\Core\Language\LanguageDefault')
+      ->addArgument('%language.default_values%');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $this->token = $this->container->get('token');
+
+    // Use a version of the language manager in which the various languages can
+    // be easily overridden during the test. We need to do this here instead of
+    // in ::register() since the container is being altered by
+    // LanguageServiceProvider::alter() after the services have been registered.
+    $this->languageManager = new MockLanguageManager(
+      $this->container->get('language.default'),
+      $this->container->get('config.factory'),
+      $this->container->get('module_handler'),
+      $this->container->get('language.config_factory_override'),
+      $this->container->get('request_stack')
+    );
+    $this->container->set('language_manager', $this->languageManager);
+
+    foreach ($this->langcodes as $langcode) {
+      // Enable test languages.
+      $this->languages[$langcode] = ConfigurableLanguage::createFromLangcode($langcode);
+      $this->languages[$langcode]->save();
+
+      // Populate language prefixes and domains to use in the test.
+      $this->language_prefixes[$langcode] = "$langcode-prefix";
+      $this->language_domains[$langcode] = $langcode . '.example.com';
+    }
+
+    // Set language negotiation prefixes and domains to values that are uniquely
+    // identifiable in the test.
+    $language_negotiation_config = $this->config('language.negotiation');
+    $language_negotiation_config->set('url.prefixes', $this->language_prefixes);
+    $language_negotiation_config->set('url.domains', $this->language_domains);
+    $language_negotiation_config->save();
+  }
+
+  /**
+   * Tests the language tokens.
+   *
+   * @dataProvider languageTokenReplacementDataProvider
+   */
+  public function testLanguageTokenReplacement($token, $langcode, $expected_result) {
+    $bubbleable_metadata = new BubbleableMetadata();
+    $options = $langcode ? ['langcode' => $langcode] : [];
+    // The part of the token name between the last `:` and the closing bracket
+    // is the machine name of the token.
+    preg_match('/\[.+:(.+)\]/', $token, $matches);
+    $name = $matches[1];
+    $replacements = $this->token->generate('language', [$name => $token], [], $options, $bubbleable_metadata);
+    $this->assertEquals($expected_result, $replacements[$token]);
+  }
+
+  /**
+   * Tests retrieving the interface and content language from the current page.
+   *
+   * @dataProvider currentPageLanguageTokenReplacementDataProvider
+   */
+  public function testCurrentPageLanguageTokenReplacement($token, $langcode, $expected_result) {
+    // Set the interface language to Dutch.
+    $this->languageManager->setCurrentLanguage(LanguageInterface::TYPE_INTERFACE, $this->languages['nl']);
+    // Set the content language to Hungarian.
+    $this->languageManager->setCurrentLanguage(LanguageInterface::TYPE_CONTENT, $this->languages['hu']);
+
+    $options = $langcode ? ['langcode' => $langcode] : [];
+    $result = $this->token->replace($token, [], $options);
+    $this->assertEquals($expected_result, $result);
+  }
+
+  /**
+   * Provides test data for ::testLanguageTokenReplacement().
+   *
+   * @return array
+   *   An array of test cases. Each test case is an array with the following
+   *   values:
+   *   - The token to test.
+   *   - An optional language code to pass as an option.
+   *   - The expected result of the token replacement.
+   *
+   * @see testLanguageTokenReplacement()
+   */
+  public function languageTokenReplacementDataProvider() {
+    return [
+      [
+        // Test the replacement of the name of the site default language.
+        '[language:name]',
+        // We are not overriding the language by passing a language code as an
+        // option. This means that the default language should be used which has
+        // been set to Portuguese.
+        NULL,
+        // The expected result.
+        'Portuguese, Portugal',
+      ],
+      // Test the replacement of the other properties of the default language.
+      [
+        '[language:langcode]',
+        NULL,
+        'pt-pt',
+      ],
+      [
+        '[language:direction]',
+        NULL,
+        'ltr',
+      ],
+      [
+        '[language:domain]',
+        NULL,
+        'pt-pt.example.com',
+      ],
+      [
+        '[language:prefix]',
+        NULL,
+        'pt-pt-prefix',
+      ],
+      // Now repeat the entire test but override the language to use by passing
+      // Bulgarian as an option.
+      [
+        '[language:name]',
+        'bg',
+        'Bulgarian',
+      ],
+      [
+        '[language:langcode]',
+        'bg',
+        'bg',
+      ],
+      [
+        '[language:direction]',
+        'bg',
+        'ltr',
+      ],
+      [
+        '[language:domain]',
+        'bg',
+        'bg.example.com',
+      ],
+      [
+        '[language:prefix]',
+        'bg',
+        'bg-prefix',
+      ],
+    ];
+  }
+
+  /**
+   * Provides test data for ::testCurrentPageLanguageTokenReplacement().
+   *
+   * @return array
+   *   An array of test cases. Each test case is an array with the following
+   *   values:
+   *   - The token to test.
+   *   - An optional language code to pass as an option.
+   *   - The expected result of the token replacement.
+   *
+   * @see testCurrentPageLanguageTokenReplacement()
+   */
+  public function currentPageLanguageTokenReplacementDataProvider() {
+    return [
+      [
+        // Test the replacement of the language name token, taken from the
+        // interface language of the current page.
+        '[current-page:interface-language:name]',
+        // We are not overriding the language by passing a language code as an
+        // option. This means that the language should be taken from the
+        // interface language which has been set to Dutch.
+        NULL,
+        // The expected result.
+        'Dutch',
+      ],
+      // Test the token name in the content language.
+      [
+        '[current-page:content-language:name]',
+        NULL,
+        'Hungarian',
+      ],
+      // Test the other tokens both for the content and interface languages.
+      [
+        '[current-page:interface-language:langcode]',
+        NULL,
+        'nl',
+      ],
+      [
+        '[current-page:content-language:langcode]',
+        NULL,
+        'hu',
+      ],
+      [
+        '[current-page:interface-language:direction]',
+        NULL,
+        'ltr',
+      ],
+      [
+        '[current-page:content-language:direction]',
+        NULL,
+        'ltr',
+      ],
+      [
+        '[current-page:interface-language:domain]',
+        NULL,
+        'nl.example.com',
+      ],
+      [
+        '[current-page:content-language:domain]',
+        NULL,
+        'hu.example.com',
+      ],
+      [
+        '[current-page:interface-language:prefix]',
+        NULL,
+        'nl-prefix',
+      ],
+      [
+        '[current-page:content-language:prefix]',
+        NULL,
+        'hu-prefix',
+      ],
+      // Now repeat the entire test with Bulgarian passed as an option. This
+      // should not affect the results, the language should be sourced from the
+      // current page.
+      [
+        // Test the replacement of the language name token, taken from the
+        // interface language of the current page.
+        '[current-page:interface-language:name]',
+        // We are not overriding the language by passing a language code as an
+        // option. This means that the language should be taken from the
+        // interface language which has been set to Dutch.
+        'bg',
+        // The expected result.
+        'Dutch',
+      ],
+      // Test the token name in the content language.
+      [
+        '[current-page:content-language:name]',
+        'bg',
+        'Hungarian',
+      ],
+      // Test the other tokens both for the content and interface languages.
+      [
+        '[current-page:interface-language:langcode]',
+        'bg',
+        'nl',
+      ],
+      [
+        '[current-page:content-language:langcode]',
+        'bg',
+        'hu',
+      ],
+      [
+        '[current-page:interface-language:direction]',
+        'bg',
+        'ltr',
+      ],
+      [
+        '[current-page:content-language:direction]',
+        'bg',
+        'ltr',
+      ],
+      [
+        '[current-page:interface-language:domain]',
+        'bg',
+        'nl.example.com',
+      ],
+      [
+        '[current-page:content-language:domain]',
+        'bg',
+        'hu.example.com',
+      ],
+      [
+        '[current-page:interface-language:prefix]',
+        'bg',
+        'nl-prefix',
+      ],
+      [
+        '[current-page:content-language:prefix]',
+        'bg',
+        'hu-prefix',
+      ],
+    ];
+  }
+
+}
diff --git a/web/modules/token/tests/src/Kernel/MockLanguageManager.php b/web/modules/token/tests/src/Kernel/MockLanguageManager.php
new file mode 100644
index 0000000000000000000000000000000000000000..b2d7b72b0436711eb76f68ea6c195ab17f8fdb1f
--- /dev/null
+++ b/web/modules/token/tests/src/Kernel/MockLanguageManager.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Drupal\Tests\token\Kernel;
+
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\language\ConfigurableLanguageManager;
+
+/**
+ * A language manager that can be easily overridden for testing purposes.
+ */
+class MockLanguageManager extends ConfigurableLanguageManager {
+
+  /**
+   * List of current languages used in the test.
+   *
+   * @var \Drupal\Core\Language\LanguageInterface[]
+   */
+  protected $currentLanguages;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCurrentLanguage($type = LanguageInterface::TYPE_INTERFACE) {
+    if (isset($this->currentLanguages[$type])) {
+      return $this->currentLanguages[$type];
+    }
+    return parent::getCurrentLanguage($type);
+  }
+
+  /**
+   * Sets the current language of the given type to use during tests.
+   *
+   * @param string $type
+   *   The language type.
+   * @param \Drupal\Core\Language\LanguageInterface $language
+   *   The language.
+   */
+  public function setCurrentLanguage($type, LanguageInterface $language) {
+    $this->currentLanguages[$type] = $language;
+  }
+
+}
diff --git a/web/modules/token/tests/src/Kernel/NodeTest.php b/web/modules/token/tests/src/Kernel/NodeTest.php
index 846961a4db200b77ee80970c3bba8602fd15b04a..6e4116f94b7881c2b44f094bac53100809f6be44 100644
--- a/web/modules/token/tests/src/Kernel/NodeTest.php
+++ b/web/modules/token/tests/src/Kernel/NodeTest.php
@@ -14,16 +14,14 @@
 class NodeTest extends KernelTestBase {
 
   /**
-   * Modules to enable.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  public static $modules = ['node', 'field', 'text'];
+  protected static $modules = ['node', 'field', 'text'];
 
   /**
    * {@inheritdoc}
    */
-  protected function setUp() {
+  protected function setUp(): void {
     parent::setUp();
 
     $this->installEntitySchema('user');
@@ -68,6 +66,7 @@ function testNodeTokens() {
       'type' => 'page',
       'type-name' => 'Basic page',
       'url:alias' => '/content/source-node',
+      'language:name' => 'English'
     ];
     $this->assertTokens('node', ['node' => $page], $tokens);
 
@@ -93,6 +92,7 @@ function testNodeTokens() {
       'type' => 'article',
       'type-name' => 'Article',
       'url:alias' => "/node/{$article->id()}",
+      'language:name' => 'English'
     ];
     $this->assertTokens('node', ['node' => $article], $tokens);
   }
diff --git a/web/modules/token/tests/src/Kernel/TaxonomyTest.php b/web/modules/token/tests/src/Kernel/TaxonomyTest.php
index abc99ea4d4fbe381c60afe5cc51a06907a387c68..c7b5a0e98e27515d1e1f910d004e5ee288e0ee17 100644
--- a/web/modules/token/tests/src/Kernel/TaxonomyTest.php
+++ b/web/modules/token/tests/src/Kernel/TaxonomyTest.php
@@ -17,16 +17,14 @@ class TaxonomyTest extends KernelTestBase {
   protected $vocab;
 
   /**
-   * Modules to enable.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  public static $modules = ['taxonomy', 'text', 'language'];
+  protected static $modules = ['taxonomy', 'text', 'language'];
 
   /**
    * {@inheritdoc}
    */
-  public function setUp() {
+  public function setUp(): void {
     parent::setUp();
 
     $this->installEntitySchema('taxonomy_term');
diff --git a/web/modules/token/tests/src/Kernel/UnitTest.php b/web/modules/token/tests/src/Kernel/UnitTest.php
index ee0f72570696b6adafddefe684a7f90f7561bbbf..0cadcb7934ad1e076ab8bb3c291545016e0e5860 100644
--- a/web/modules/token/tests/src/Kernel/UnitTest.php
+++ b/web/modules/token/tests/src/Kernel/UnitTest.php
@@ -15,16 +15,14 @@ class UnitTest extends KernelTestBase {
   protected $tokenService;
 
   /**
-   * Modules to enable.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  public static $modules = ['file', 'node'];
+  protected static $modules = ['file', 'node'];
 
   /**
    * {@inheritdoc}
    */
-  public function setUp() {
+  public function setUp(): void {
     parent::setUp();
     $this->tokenService = \Drupal::token();
   }
diff --git a/web/modules/token/tests/src/Kernel/UrlTest.php b/web/modules/token/tests/src/Kernel/UrlTest.php
index 1464095d07bab8c2de08ac10eb8b8778c5d0f6ae..6de4a1cc8b59d72943cefa03719075ab0099f196 100644
--- a/web/modules/token/tests/src/Kernel/UrlTest.php
+++ b/web/modules/token/tests/src/Kernel/UrlTest.php
@@ -36,7 +36,7 @@ class UrlTest extends KernelTestBase {
   /**
    * {@inheritdoc}
    */
-  protected function setUp() {
+  protected function setUp(): void {
     parent::setUp();
     $this->token = $this->container->get('token');
     $this->requestStack = $this->container->get('request_stack');
diff --git a/web/modules/token/tests/src/Kernel/ValidateD6MigrationStateTest.php b/web/modules/token/tests/src/Kernel/ValidateD6MigrationStateTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..a2c52d279138aa2a202b8e6ff8d8e2ddc905ecb5
--- /dev/null
+++ b/web/modules/token/tests/src/Kernel/ValidateD6MigrationStateTest.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Drupal\Tests\token\Kernel;
+
+use Drupal\Tests\migrate_drupal\Kernel\d6\MigrateDrupal6TestBase;
+use Drupal\Tests\migrate_drupal\Traits\ValidateMigrationStateTestTrait;
+
+/**
+ * Tests that the token test has a declared D6 migration status.
+ *
+ * ValidateMigrationStateTestTrait::testMigrationState() will succeed if the
+ * modules enabled in \Drupal\Tests\KernelTestBase::bootKernel() have a valid
+ * migration status (i.e.: finished or not_finished); but will fail if they do
+ * not have a declared migration status.
+ *
+ * @group token
+ */
+class ValidateD6MigrationStateTest extends MigrateDrupal6TestBase {
+  use ValidateMigrationStateTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['token'];
+
+}
diff --git a/web/modules/token/tests/src/Kernel/ValidateD7MigrationStateTest.php b/web/modules/token/tests/src/Kernel/ValidateD7MigrationStateTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..118276817428b0f744e818486dfe20a49ff13176
--- /dev/null
+++ b/web/modules/token/tests/src/Kernel/ValidateD7MigrationStateTest.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Drupal\Tests\token\Kernel;
+
+use Drupal\Tests\migrate_drupal\Kernel\d7\MigrateDrupal7TestBase;
+use Drupal\Tests\migrate_drupal\Traits\ValidateMigrationStateTestTrait;
+
+/**
+ * Tests that the token test has a declared D7 migration status.
+ *
+ * ValidateMigrationStateTestTrait::testMigrationState() will succeed if the
+ * modules enabled in \Drupal\Tests\KernelTestBase::bootKernel() have a valid
+ * migration status (i.e.: finished or not_finished); but will fail if they do
+ * not have a declared migration status.
+ *
+ * @group token
+ */
+class ValidateD7MigrationStateTest extends MigrateDrupal7TestBase {
+  use ValidateMigrationStateTestTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['token'];
+
+}
diff --git a/web/modules/token/tests/src/Kernel/ViewsTest.php b/web/modules/token/tests/src/Kernel/ViewsTest.php
index 884e4fcb4673a947c40694062f8693308fc821df..a24bcdc00e28b63171b3ddb2c7a021c83ef78eb7 100644
--- a/web/modules/token/tests/src/Kernel/ViewsTest.php
+++ b/web/modules/token/tests/src/Kernel/ViewsTest.php
@@ -13,11 +13,9 @@
 class ViewsTest extends KernelTestBase {
 
   /**
-   * Modules to enable.
-   *
-   * @var array
+   * {@inheritdoc}
    */
-  public static $modules = ['views', 'block'];
+  protected static $modules = ['views', 'block'];
 
   /**
    * Views used by this test.
@@ -29,7 +27,7 @@ class ViewsTest extends KernelTestBase {
   /**
    * {@inheritdoc}
    */
-  protected function setUp() {
+  protected function setUp(): void {
     parent::setUp();
     $this->installEntitySchema('user');
     ViewTestData::createTestViews(get_class($this), ['token_module_test']);
diff --git a/web/modules/token/token.info.yml b/web/modules/token/token.info.yml
index 03fb438b7beced159cba16ba745b728a4dae73a4..f0339a0c03b14ff46a4c5b888429d115fe85cee3 100644
--- a/web/modules/token/token.info.yml
+++ b/web/modules/token/token.info.yml
@@ -1,9 +1,9 @@
 type: module
 name: Token
 description: Provides a user interface for the Token API and some missing core tokens.
-core_version_requirement: ^8.8 || ^9
+core_version_requirement: ^9.2 || ^10
 
-# Information added by Drupal.org packaging script on 2020-12-18
-version: '8.x-1.9'
+# Information added by Drupal.org packaging script on 2022-08-02
+version: '8.x-1.11'
 project: 'token'
-datestamp: 1608284868
+datestamp: 1659471815
diff --git a/web/modules/token/token.install b/web/modules/token/token.install
index 2360fb730d9855c1559fd36a02107293b4fa8e7e..8b32f74b717bdf931abaae506b6e1834a29bcd06 100644
--- a/web/modules/token/token.install
+++ b/web/modules/token/token.install
@@ -61,160 +61,6 @@ function token_install() {
   }
 }
 
-/**
- * Build a list of Drupal 6 tokens and their Drupal 7 token names.
- */
-function _token_upgrade_token_list() {
-  $tokens = [
-    // Global tokens
-    'user-name' => 'current-user:name',
-    'user-id' => 'current-user:id',
-    'user-mail' => 'current-user:mail',
-    'site-url' => 'site:url',
-    'site-name' => 'site:name',
-    'site-slogan' => 'site:slogan',
-    'site-mission' => 'site:mission',
-    'site-mail' => 'site:mail',
-    'site-date' => 'date:short',
-    //'site-date-' => '', // Date tokens expanded below
-    'current-page-path' => 'current-page:path',
-    'current-page-url' => 'current-page:url',
-    'page-number' => 'current-page:page-number',
-
-    // Comment tokens
-    'comment-cid' => 'comment:cid',
-    'comment-nid' => 'comment:node:nid',
-    'comment-title' => 'comment:title',
-    'comment-body' => 'comment:body',
-    'comment-author-name' => 'comment:author:name',
-    'comment-author-mail' => 'comment:author:mail',
-    //'comment-body-format' => '',
-    //'comment-' => '', // Date tokens expanded below
-    'comment-node-title' => 'comment:node',
-
-    // Node tokens
-    'nid' => 'node:nid',
-    'type' => 'node:type',
-    'type-name' => 'node:type-name',
-    'language' => 'node:language',
-    'title' => 'node:title',
-    'author-uid' => 'node:author:uid',
-    'author-name' => 'node:author:name',
-    'author-mail' => 'node:author:mail',
-    'node_comment_count' => 'node:comment-count',
-    'unread_comment_count' => 'node:comment-count-new',
-    'log' => 'node:log',
-    //'' => '', // Date tokens expanded below
-    //'mod-' => '', // Date tokens expanded below
-    'menupath' => 'node:menu-link:parent:path][node:menu-link',
-    'menu' => 'node:menu-link:menu-name',
-    'menu-link-title' => 'node:menu-link',
-    'menu-link-mlid' => 'node:menu-link:mlid',
-    'menu-link-plid' => 'node:menu-link:parent:mlid',
-    //'term' => 'node:term',
-    //'term-id' => 'node:term:tid',
-    //'vocab' => 'node:term:vocabulary',
-    //'vocab-id' => 'node:term:vocabulary:vid',
-
-    // Book tokens
-    //'book' => 'node:book',
-    //'book_id' => 'node:book:bid',
-    //'bookpath' => 'node:book:path',
-
-    // Taxonomy tokens
-    'tid' => 'term:tid',
-    'cat' => 'term:name',
-    'cat-description' => 'term:description',
-    'vid' => 'term:vocabulary:vid',
-    'vocab' => 'term:vocabulary',
-    'vocab-description' => 'term:vocabulary:description',
-
-    // User tokens
-    'user' => 'user:name',
-    'uid' => 'user:uid',
-    'mail' => 'user:mail',
-    'reg-date' => 'user:created',
-    'reg-since' => 'user:created:since',
-    //'user-created' => '', // Date tokens expanded below
-    'log-date' => 'user:last-login',
-    'log-since' => 'user:last-login:since',
-    //'user-last-login' => '', // Date tokens expanded below
-    //'date-in-tz' => '',
-    'account-url' => 'user:url',
-    'account-edit' => 'user:edit-url',
-  ];
-
-  // Account for date tokens which need to be expanded.
-  $tokens += _token_upgrade_token_date_list('site-', 'site:date');
-  $tokens += _token_upgrade_token_date_list('', 'node:created');
-  $tokens += _token_upgrade_token_date_list('mod-', 'node:changed');
-  //$tokens += _token_upgrade_token_date_list('node-revision-', 'node:changed');
-  $tokens += _token_upgrade_token_date_list('comment-', 'comment:created');
-  $tokens += _token_upgrade_token_date_list('user-register-', 'user:created');
-  $tokens += _token_upgrade_token_date_list('user-last-login-', 'user:last-login');
-
-  return $tokens;
-}
-
-/**
- * Build a list of Drupal 6 date tokens and their Drupal 7 token names.
- */
-function _token_upgrade_token_date_list($old_token, $new_token) {
-  $tokens = [];
-  $formats = [
-    'yyyy' => 'Y',
-    'yy' => 'y',
-    'month' => 'F',
-    'mon' => 'M',
-    'mm' => 'm',
-    'm' => 'n',
-    'ww' => 'W',
-    'date' => 'N',
-    'day' => 'l',
-    'ddd' => 'D',
-    'dd' => 'd',
-    'd' => 'j',
-  ];
-  foreach ($formats as $token_format => $date_format) {
-    $tokens[$old_token . $token_format] = "$new_token:custom:$date_format";
-  }
-  $tokens[$old_token . 'raw'] = "$new_token:raw";
-  $tokens[$old_token . 'since'] = "$new_token:since";
-  return $tokens;
-}
-
-/**
- * Update a string containing Drupal 6 style tokens to Drupal 7 style tokens.
- *
- * @param $text
- *   A string containing tokens.
- * @param $updates
- *   An optional array of Drupal 7 tokens keyed by their Drupal 6 token name.
- *   The default tokens will be merged into this array. Note neither the old
- *   or new token names should include the surrounding bracket ([ and ])
- *   characters.
- * @return
- *   A string with the tokens upgraded
- *
- * @see _token_upgrade_token_list()
- */
-function token_update_token_text($text, $updates = [], $leading = '[', $trailing = ']') {
-  $updates += _token_upgrade_token_list();
-  $regex = '/' . preg_quote($leading, '/') . '([^\s]*)' . preg_quote($trailing, '/') . '/';
-  preg_match_all($regex, $text, $matches);
-
-  foreach ($matches[1] as $old_token) {
-    if (isset($updates[$old_token])) {
-      $new_token = $updates[$old_token];
-      $text = str_replace("{$leading}{$old_token}{$trailing}", "[$new_token]", $text);
-      // Also replace any tokens that have a -raw suffix.
-      $text = str_replace("{$leading}{$old_token}-raw{$trailing}", "[$new_token]", $text);
-    }
-  }
-
-  return $text;
-}
-
 /**
  * Get token problems.
  */
diff --git a/web/modules/token/token.libraries.yml b/web/modules/token/token.libraries.yml
index 5c7ca14d1861e900f09d01947c0797bf33c05762..c5e390a0e771f910b5f554ac34ef08144a93869b 100644
--- a/web/modules/token/token.libraries.yml
+++ b/web/modules/token/token.libraries.yml
@@ -23,3 +23,5 @@ token:
   dependencies:
     - core/jquery
     - core/drupal
+    - core/once
+    - token/jquery.treeTable
diff --git a/web/modules/token/token.module b/web/modules/token/token.module
index 2299984ddef5132ea20d398b0703d619920dce3a..ff0c3dbf45b0d5753943e5ee376eff53aefff1e1 100644
--- a/web/modules/token/token.module
+++ b/web/modules/token/token.module
@@ -267,7 +267,7 @@ function _token_module($type, $name) {
 function token_element_validate($element, FormStateInterface $form_state) {
   $value = isset($element['#value']) ? $element['#value'] : $element['#default_value'];
 
-  if (!mb_strlen($value)) {
+  if (empty($value)) {
     // Empty value needs no further validation since the element should depend
     // on using the '#required' FAPI property.
     return $element;
diff --git a/web/modules/token/token.routing.yml b/web/modules/token/token.routing.yml
index f2105aeaf762366036ecb9399bf942601d6014d8..a3d062f12508cf04a4b850317239317c4c95d056 100644
--- a/web/modules/token/token.routing.yml
+++ b/web/modules/token/token.routing.yml
@@ -5,13 +5,6 @@ token.tree:
   requirements:
     _csrf_token: 'TRUE'
 
-token.autocomplete:
-  path: '/token/autocomplete/{token_type}/{filter}'
-  defaults:
-    _controller: '\Drupal\token\Controller\TokenAutocompleteController::autocomplete'
-  requirements:
-    _access: 'TRUE'
-
 token.flush_cache:
   path: '/token/flush-cache'
   defaults:
diff --git a/web/modules/token/token.tokens.inc b/web/modules/token/token.tokens.inc
index 1ac3f657645ecc5eea7ac3b1656762f60d58126f..7b2e2cfa3c34bda922bddcef5b877b91dcd8168d 100755
--- a/web/modules/token/token.tokens.inc
+++ b/web/modules/token/token.tokens.inc
@@ -9,10 +9,10 @@
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface;
+use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Render\BubbleableMetadata;
-use Drupal\Core\Render\Element;
-use Drupal\Component\Utility\Crypt;
 use Drupal\Component\Utility\Html;
+use Drupal\Core\Routing\RouteObjectInterface;
 use Drupal\Core\TypedData\DataReferenceDefinitionInterface;
 use Drupal\Core\Url;
 use Drupal\field\FieldStorageConfigInterface;
@@ -22,8 +22,6 @@
 use Drupal\node\NodeInterface;
 use Drupal\system\Entity\Menu;
 use Drupal\user\UserInterface;
-use Symfony\Cmf\Component\Routing\RouteObjectInterface;
-use Drupal\Core\TypedData\PrimitiveInterface;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\Core\Entity\ContentEntityTypeInterface;
 use Drupal\image\Entity\ImageStyle;
@@ -91,6 +89,16 @@ function token_token_info_alter(&$info) {
       ];
     }
 
+    // Add [entity:language] tokens if they do not already exist.
+    if (!isset($info['tokens'][$token_type]['language'])) {
+      $info['tokens'][$token_type]['language'] = [
+        'name' => t('Language'),
+        'description' => t('The language of the @entity.', ['@entity' => mb_strtolower($entity_info->getLabel())]),
+        'module' => 'token',
+        'type' => 'language',
+      ];
+    }
+
     // Add [entity:original] tokens if they do not already exist.
     if (!isset($info['tokens'][$token_type]['original'])) {
       $info['tokens'][$token_type]['original'] = [
@@ -271,6 +279,32 @@ function token_token_info() {
     'type' => 'menu-link',
   ];
 
+  // Language tokens.
+  $info['types']['language'] = [
+    'name' => t('Language'),
+    'description' => t('Tokens related to site language.'),
+  ];
+  $info['tokens']['language']['name'] = [
+    'name' => t('Language name'),
+    'description' => t('The language name.'),
+  ];
+  $info['tokens']['language']['langcode'] = [
+    'name' => t('Language code'),
+    'description' => t('The language code.'),
+  ];
+  $info['tokens']['language']['direction'] = [
+    'name' => t('Direction'),
+    'description' => t('Whether the language is written left-to-right (ltr) or right-to-left (rtl).'),
+  ];
+  $info['tokens']['language']['domain'] = [
+    'name' => t('Domain'),
+    'description' => t('The domain name to use for the language.'),
+  ];
+  $info['tokens']['language']['prefix'] = [
+    'name' => t('Path prefix'),
+    'description' => t('Path prefix for URLs in the language.'),
+  ];
+
   // Current page tokens.
   $info['types']['current-page'] = [
     'name' => t('Current page'),
@@ -294,6 +328,16 @@ function token_token_info() {
     'description' => t('The value of a specific query string field of the current page.'),
     'dynamic' => TRUE,
   ];
+  $info['tokens']['current-page']['interface-language'] = [
+    'name' => t('Interface language'),
+    'description' => t('The active user interface language.'),
+    'type' => 'language',
+  ];
+  $info['tokens']['current-page']['content-language'] = [
+    'name' => t('Content language'),
+    'description' => t('The active content language.'),
+    'type' => 'language',
+  ];
 
   // URL tokens.
   $info['types']['url'] = [
@@ -521,6 +565,7 @@ function token_tokens($type, array $tokens, array $data, array $options, Bubblea
           $count = \Drupal::entityQueryAggregate('node')
             ->aggregate('nid', 'COUNT')
             ->condition('type', $node_type->id())
+            ->accessCheck(TRUE)
             ->execute();
           $replacements[$original] = (int) $count;
           break;
@@ -751,6 +796,42 @@ function token_tokens($type, array $tokens, array $data, array $options, Bubblea
 
   }
 
+  // Language tokens.
+  if ($type == 'language' && !empty($langcode)) {
+    $language = $language_manager->getLanguage($langcode);
+    if ($language) {
+      foreach ($tokens as $name => $original) {
+        switch ($name) {
+          case 'name':
+            $replacements[$original] = $language->getName();
+            break;
+          case 'langcode':
+            $replacements[$original] = $langcode;
+            break;
+          case 'direction':
+            $replacements[$original] = $language->getDirection();
+            break;
+          case 'domain':
+            if (!isset($language_url_domains)) {
+              $language_url_domains = \Drupal::config('language.negotiation')->get('url.domains');
+            }
+            if (isset($language_url_domains[$langcode])) {
+              $replacements[$original] = $language_url_domains[$langcode];
+            }
+            break;
+          case 'prefix':
+            if (!isset($language_url_prefixes)) {
+              $language_url_prefixes = \Drupal::config('language.negotiation')->get('url.prefixes');
+            }
+            if (isset($language_url_prefixes[$langcode])) {
+              $replacements[$original] = $language_url_prefixes[$langcode];
+            }
+            break;
+        }
+      }
+    }
+  }
+
   // Current page tokens.
   if ($type == 'current-page') {
     $request = \Drupal::request();
@@ -795,6 +876,18 @@ function token_tokens($type, array $tokens, array $data, array $options, Bubblea
           $replacements[$original] = (int) $page + 1;
           break;
       }
+      // [current-page:interface-language:*] chained tokens.
+      if ($language_interface_tokens = \Drupal::token()->findWithPrefix($tokens, 'interface-language')) {
+        $language_interface = $language_manager->getCurrentLanguage(LanguageInterface::TYPE_INTERFACE);
+        $langcode = $language_interface->getId();
+        $replacements += \Drupal::token()->generate('language', $language_interface_tokens, $data, ['langcode' => $langcode] + $options, $bubbleable_metadata);
+      }
+      // [current-page:content-language:*] chained tokens.
+      if ($language_content_tokens = \Drupal::token()->findWithPrefix($tokens, 'content-language')) {
+        $language_content = $language_manager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT);
+        $langcode = $language_content->getId();
+        $replacements += \Drupal::token()->generate('language', $language_content_tokens, $data, ['langcode' => $langcode] + $options, $bubbleable_metadata);
+      }
     }
 
     // @deprecated
@@ -948,6 +1041,12 @@ function token_tokens($type, array $tokens, array $data, array $options, Bubblea
       $replacements += \Drupal::token()->generate($type, $original_tokens, [$type => $entity->original], $options, $bubbleable_metadata);
     }
 
+    // [entity:language:*] chained tokens.
+    if (($language_tokens = \Drupal::token()->findWithPrefix($tokens, 'language')) && _token_module($type, 'language') == 'token') {
+      $language_options = array_merge($options, ['langcode' => $entity->get('langcode')->value]);
+      $replacements += \Drupal::token()->generate('language', $language_tokens, [], $language_options, $bubbleable_metadata);
+    }
+
     // Pass through to an generic 'entity' token type generation.
     $entity_data = [
       'entity_type' => $entity_type,
@@ -1601,7 +1700,7 @@ function field_tokens($type, $tokens, array $data, array $options, BubbleableMet
       // For [entity:field_name:0], [entity:field_name:0:value] and
       // [entity:field_name:value] tokens.
       else {
-        list($field_name, $delta) = explode(':', $name, 2);
+        [$field_name, $delta] = explode(':', $name, 2);
         if (!is_numeric($delta)) {
           unset($delta);
         }