diff --git a/core/modules/toolbar/js/toolbar.js b/core/modules/toolbar/js/toolbar.js
index 344abe6..29b2195 100644
--- a/core/modules/toolbar/js/toolbar.js
+++ b/core/modules/toolbar/js/toolbar.js
@@ -67,11 +67,12 @@ Drupal.behaviors.toolbar = {
         strings: options.strings
       });
 
-      // Handle the resolution of Drupal.toolbar.setSubtrees().
+      // Handle the resolution of Drupal.toolbar.setSubtrees.
       // This is handled with a deferred so that the function may be invoked
       // asynchronously.
       Drupal.toolbar.setSubtrees.done(function (subtrees) {
         menuModel.set('subtrees', subtrees);
+        localStorage.setItem('Drupal.toolbar.subtrees', JSON.stringify(subtrees));
       });
 
       // Attach a listener to the configured media query breakpoints.
@@ -88,6 +89,14 @@ Drupal.behaviors.toolbar = {
         }
       }
 
+      // Trigger an initial attempt to load menu sub-items. This first attempt
+      // is made after the media query handlers have had an opportunity to
+      // process. The toolbar starts in the vertical position by default, unless
+      // the viewport is wide enough to accomodate a horizontal orientation.
+      // Thus we give the Toolbar a chance to determine if it should be set
+      // to horizontal before attempting to load menu subtrees.
+      Drupal.toolbar.views.toolbarVisualView.loadSubtrees();
+
       $(document)
         // Update the model when the viewport offset changes.
         .on('drupalViewportOffsetChange.toolbar', function (event, offsets) {
@@ -196,6 +205,9 @@ Drupal.toolbar = {
       // Indicates whether the toolbar is positioned absolute (false) or fixed
       // (true).
       isFixed: false,
+      // Menu subtrees are loaded through an AJAX request only when the Toolbar
+      // is set to a vertical orientation.
+      areSubtreesLoaded: false,
       // If the viewport overflow becomes constrained, such as when the overlay
       // is open, isFixed must be true so that elements in the trays aren't
       // lost offscreen and impossible to get to.
@@ -316,10 +328,16 @@ Drupal.toolbar = {
     /**
      * {@inheritdoc}
      */
-    render: function (model) {
+    render: function (model, value) {
       this.updateTabs();
       this.updateTrayOrientation();
       this.updateBarAttributes();
+      // Load the subtrees if the toolbar tray is in a vertical orientation.
+      // There is no other attribute in the model with the value of vertical
+      // that could be confused with the orientation attribute.
+      if (value && value === 'vertical') {
+        this.loadSubtrees();
+      }
       // Trigger a recalculation of viewport displacing elements. Use setTimeout
       // to ensure this recalculation happens after changes to visual elements
       // have processed.
@@ -485,6 +503,38 @@ Drupal.toolbar = {
         // the container for the trays.
         $trays.css('padding-top', this.$el.find('.toolbar-bar').outerHeight());
       }
+    },
+
+    /**
+     * Calls the endpoint URI that will return rendered subtrees with JSONP.
+     */
+    loadSubtrees: function () {
+      if (!this.model.get('areSubtreesLoaded')) {
+        var endpoint = drupalSettings.toolbar.subtreesPath;
+        var cachedEndpoint = localStorage.getItem('Drupal.toolbar.subtreesPath');
+        var cachedSubtrees = JSON.parse(localStorage.getItem('Drupal.toolbar.subtrees'));
+        var isVertical = this.model.get('orientation') === 'vertical';
+        // If we have the subtrees in localStorage and the endpoint url --
+        // including the hash of the subtrees -- has not changed, then use
+        // the cached data.
+        if (isVertical && endpoint === cachedEndpoint && cachedSubtrees) {
+          Drupal.toolbar.setSubtrees.resolve(cachedSubtrees);
+          this.model.set('areSubtreesLoaded', true);
+        }
+        // Only make the call to get the subtrees if the orientation of the
+        // toolbar is vertical.
+        else if (isVertical) {
+          // Remove the cached menu information.
+          localStorage.removeItem('Drupal.toolbar.subtreesPath');
+          localStorage.removeItem('Drupal.toolbar.subtrees');
+          // The response from the server will call the resolve method of the
+          // Drupal.toolbar.setSubtrees Promise.
+          $.ajax(endpoint);
+          // Cached the endpoint to the subtrees locally.
+          localStorage.setItem('Drupal.toolbar.subtreesPath', endpoint);
+          this.model.set('areSubtreesLoaded', true);
+        }
+      }
     }
   }),
 
diff --git a/core/modules/toolbar/lib/Drupal/toolbar/Tests/ToolbarAdminMenuTest.php b/core/modules/toolbar/lib/Drupal/toolbar/Tests/ToolbarAdminMenuTest.php
new file mode 100644
index 0000000..273c7c0
--- /dev/null
+++ b/core/modules/toolbar/lib/Drupal/toolbar/Tests/ToolbarAdminMenuTest.php
@@ -0,0 +1,384 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\toolbar\Tests\ToolbarAdminMenuTest.
+ */
+
+namespace Drupal\toolbar\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ *
+ * Tests the caching of the admin menu subtree items.
+ *
+ * The cache of the admin menu subtree items will be invalidated if the
+ * following hooks are invoked.
+ *
+ * toolbar_modules_enabled()
+ * toolbar_modules_disabled()
+ * toolbar_menu_link_update()
+ * toolbar_user_update()
+ * toolbar_user_role_update()
+ *
+ * Each hook invocation is simulated and then the previous hash of the admin
+ * menu subtrees is compared to the new hash.
+ */
+class ToolbarAdminMenuTest extends WebTestBase {
+
+  /**
+   * A user with permission to access the administrative toolbar.
+   *
+   * @var object
+   */
+  protected $admin_user;
+
+  /**
+   * A second user with permission to access the administrative toolbar.
+   *
+   * @var object
+   */
+  protected $admin_user_2;
+
+  /**
+   * The current admin menu subtrees hash for admin_user.
+   *
+   * @var string
+   */
+  protected $hash;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('block', 'menu', 'user', 'taxonomy', 'toolbar', 'language', 'test_page_test',);
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Toolbar admin menu',
+      'description' => 'Tests the caching of secondary admin menu items.',
+      'group' => 'Toolbar',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+
+    $perms = array(
+      'access toolbar',
+      'access administration pages',
+      'administer site configuration',
+      'bypass node access',
+      'administer themes',
+      'administer nodes',
+      'administer blocks',
+      'administer menu',
+      'administer modules',
+      'administer permissions',
+      'administer users',
+      'access user profiles',
+      'administer taxonomy',
+      'administer languages',
+    );
+
+    // Create an administrative user and log it in.
+    $this->admin_user = $this->drupalCreateUser($perms);
+    $this->admin_user_2 = $this->drupalCreateUser($perms);
+
+    $this->drupalLogin($this->admin_user);
+
+    $this->drupalGet('test-page');
+    $this->assertResponse(200);
+
+    // Assert that the toolbar is present in the HTML.
+    $this->assertRaw('id="toolbar-administration"');
+
+    // Store the admin_user admin menu subtrees hash for comparison later.
+    $this->hash = $this->getSubtreesHash();
+  }
+
+  /**
+   * Exercises the toolbar_modules_enabled() and toolbar_modules_disabled() hook
+   * implementations.
+   */
+  function testModuleStatusChangeSubtreesHashCacheClear() {
+    $edit = array();
+    $edit['modules[Core][taxonomy][enable]'] = FALSE;
+    $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+    $this->rebuildContainer();
+
+    // Assert that the subtrees hash has been altered because the subtrees
+    // structure changed.
+    $this->assertDifferentHash();
+
+    // Test enabling a module.
+    $edit = array();
+    $edit['modules[Core][taxonomy][enable]'] = TRUE;
+    $this->drupalPost('admin/modules', $edit, t('Save configuration'));
+    $this->rebuildContainer();
+
+    // Assert that the subtrees hash has been altered because the subtrees
+    // structure changed.
+    $this->assertDifferentHash();
+  }
+
+  /**
+   * Exercises toolbar_menu_link_update() hook implementation.
+   */
+  function testMenuLinkUpdateSubtreesHashCacheClear() {
+    // Get subtree items for the admin menu.
+    $query = \Drupal::entityQuery('menu_link');
+    for ($i = 1; $i <= 3; $i++) {
+      $query->sort('p' . $i, 'ASC');
+    }
+    $query->condition('menu_name', 'admin');
+    $query->condition('depth', '2', '>=');
+
+    // Build an ordered array of links using the query result object.
+    $links = array();
+    if ($result = $query->execute()) {
+      $links = menu_link_load_multiple($result);
+    }
+    // Get the first link in the set.
+    $links = array_values($links);
+    $link = array_shift($links);
+
+    // Disable the link.
+    $edit = array();
+    $edit['enabled'] = FALSE;
+    $this->drupalPost("admin/structure/menu/item/" . $link['mlid'] . "/edit", $edit, t('Save'));
+    $this->assertResponse(200);
+    $this->assertText('The menu link has been saved.');
+
+    // Assert that the subtrees hash has been altered because the subtrees
+    // structure changed.
+    $this->assertDifferentHash();
+  }
+
+  /**
+   * Exercises the toolbar_user_role_update() and toolbar_user_update() hook
+   * implementations.
+   */
+  function testUserRoleUpdateSubtreesHashCacheClear() {
+    // Find the new role ID.
+    $all_rids = $this->admin_user->getRoles();
+    unset($all_rids[array_search(DRUPAL_AUTHENTICATED_RID, $all_rids)]);
+    $rid = reset($all_rids);
+
+    $edit = array();
+    $edit[$rid . '[administer taxonomy]'] = FALSE;
+    $this->drupalPost('admin/people/permissions', $edit, t('Save permissions'));
+
+    // Assert that the subtrees hash has been altered because the subtrees
+    // structure changed.
+    $this->assertDifferentHash();
+
+    // Test that a user role change only affects a single user.
+    // Get the hash for a second user.
+    $this->drupalLogin($this->admin_user_2);
+    $this->drupalGet('test-page');
+    $this->assertResponse(200);
+
+    // Assert that the toolbar is present in the HTML.
+    $this->assertRaw('id="toolbar-administration"');
+
+    $admin_user_2_hash = $this->getSubtreesHash();
+
+    // Log in the first admin user again.
+    $this->drupalLogin($this->admin_user);
+    $this->drupalGet('test-page');
+    $this->assertResponse(200);
+
+    // Assert that the toolbar is present in the HTML.
+    $this->assertRaw('id="toolbar-administration"');
+
+    $this->hash = $this->getSubtreesHash();
+
+    $rid = $this->drupalCreateRole(array('administer content types',));
+
+    // Assign the role to the user.
+    $this->drupalPost('user/' . $this->admin_user->id() . '/edit', array("roles[$rid]" => $rid), t('Save'));
+    $this->assertText(t('The changes have been saved.'));
+
+    // Assert that the subtrees hash has been altered because the subtrees
+    // structure changed.
+    $this->assertDifferentHash();
+
+    // Log in the second user again and assert that their subtrees hash did not
+    // change.
+    $this->drupalLogin($this->admin_user_2);
+
+    // Request a new page to refresh the drupalSettings object.
+    $this->drupalGet('test-page');
+    $this->assertResponse(200);
+    $new_subtree_hash = $this->getSubtreesHash();
+
+    // Assert that the old admin menu subtree hash and the new admin menu
+    // subtree hash are the same.
+    $this->assertTrue($new_subtree_hash, 'A valid hash value for the admin menu subtrees was created.');
+    $this->assertEqual($admin_user_2_hash, $new_subtree_hash, 'The user-specific subtree menu hash has not been updated.');
+  }
+
+  /**
+   * Tests that the toolbar user cache is cleared with a cache tag for all
+   * languages that have cached admin menu subtrees.
+   */
+  function testCacheClearByCacheTag() {
+    // Test that the toolbar admin menu subtrees cache is invalidated for a user
+    // across multiple languages.
+    $this->drupalLogin($this->admin_user);
+    $toolbarCache = $this->container->get('cache.toolbar');
+    $admin_user_id = $this->admin_user->id();
+    $admin_user_2_id = $this->admin_user_2->id();
+
+    // Assert that a cache tag in the toolbar cache under the key "user" exists
+    // for admin_user against the language "en".
+    $cache = $toolbarCache->get($admin_user_id . ':' . 'en');
+    $this->assertEqual($cache->tags[0], 'user:' . $admin_user_id, 'A cache tag in the toolbar cache under the key "user" exists for admin_user against the language "en".');
+
+    // Asert that no toolbar cache exists for admin_user against the
+    // language "fr".
+    $cache = $toolbarCache->get($admin_user_id . ':' . 'fr');
+    $this->assertFalse($cache, 'No toolbar cache exists for admin_user against the language "fr".');
+
+    // Install a second language.
+    $edit = array(
+      'predefined_langcode' => 'fr',
+    );
+    $this->drupalPost('admin/config/regional/language/add', $edit, 'Add language');
+
+    // Request a page in 'fr' to update the cache.
+    $this->drupalGet('fr/test-page');
+    $this->assertResponse(200);
+
+    // Asert that a cache tag in the toolbar cache under the key "user" exists
+    // for admin_user against the language "fr".
+    $cache = $toolbarCache->get($admin_user_id . ':' . 'fr');
+    $this->assertEqual($cache->tags[0], 'user:' . $admin_user_id, 'A cache tag in the toolbar cache under the key "user" exists for admin_user against the language "fr".');
+
+    // Log in the admin_user_2 user. We will use this user as a control to
+    // verify that clearing a cache tag for admin_user does not clear the cache
+    // for admin_user_2.
+    $this->drupalLogin($this->admin_user_2);
+
+    // Request a page in 'en' to create the cache.
+    $this->drupalGet('test-page');
+    $this->assertResponse(200);
+    // Asert that a cache tag in the toolbar cache under the key "user" exists
+    // for admin_user against the language "en".
+    $cache = $toolbarCache->get($admin_user_2_id . ':' . 'en');
+    $this->assertEqual($cache->tags[0], 'user:' . $admin_user_2_id, 'A cache tag in the toolbar cache under the key "user" exists for admin_user_2 against the language "en".');
+
+    // Request a page in 'en' to create the cache.
+    $this->drupalGet('fr/test-page');
+    $this->assertResponse(200);
+    // Asert that a cache tag in the toolbar cache under the key "user" exists
+    // for admin_user against the language "fr".
+    $cache = $toolbarCache->get($admin_user_2_id . ':' . 'fr');
+    $this->assertEqual($cache->tags[0], 'user:' . $admin_user_2_id, 'A cache tag in the toolbar cache under the key "user" exists for admin_user_2 against the language "fr".');
+
+    // Log in admin_user and clear the caches for this user using a tag.
+    $this->drupalLogin($this->admin_user);
+
+    // Delete the toolbar cache for the admin_user based on a tag.
+    $toolbarCache->deleteTags(array('user' => array($admin_user_id)));
+
+    // Asert that no toolbar cache exists for admin_user against the
+    // language "en".
+    $cache = $toolbarCache->get($admin_user_id . ':' . 'en');
+    $this->assertFalse($cache, 'No toolbar cache exists for admin_user against the language "en".');
+
+    // Asert that no toolbar cache exists for admin_user against the
+    // language "fr".
+    $cache = $toolbarCache->get($admin_user_id . ':' . 'fr');
+    $this->assertFalse($cache, 'No toolbar cache exists for admin_user against the language "fr".');
+
+    // Log in admin_user_2 and verify that this user's caches still exist.
+    $this->drupalLogin($this->admin_user_2);
+
+    // Asert that a cache tag in the toolbar cache under the key "user" exists
+    // for admin_user_2 against the language "en".
+    $cache = $toolbarCache->get($admin_user_2_id . ':' . 'en');
+    $this->assertEqual($cache->tags[0], 'user:' . $admin_user_2_id, 'A cache tag in the toolbar cache under the key "user" exists for admin_user_2 against the language "en".');
+
+    // Asert that a cache tag in the toolbar cache under the key "user" exists
+    // for admin_user_2 against the language "fr".
+    $cache = $toolbarCache->get($admin_user_2_id . ':' . 'fr');
+    $this->assertEqual($cache->tags[0], 'user:' . $admin_user_2_id, 'A cache tag in the toolbar cache under the key "user" exists for admin_user_2 against the language "fr".');
+  }
+
+  /**
+   * Tests that changes to a user account by another user clears the changed
+   * account's toolbar cached, not the user's who took the action.
+   */
+  function testNonCurrentUserAccountUpdates() {
+    $toolbarCache = $this->container->get('cache.toolbar');
+    $admin_user_id = $this->admin_user->id();
+    $admin_user_2_id = $this->admin_user_2->id();
+    $this->hash = $this->getSubtreesHash();
+
+    // admin_user_2 will add a role to admin_user.
+    $this->drupalLogin($this->admin_user_2);
+    $rid = $this->drupalCreateRole(array('administer content types',));
+
+    // Get the subtree hash for admin_user_2 to check later that it has not
+    // changed. Request a new page to refresh the drupalSettings object.
+    $this->drupalGet('test-page');
+    $this->assertResponse(200);
+    $admin_user_2_hash = $this->getSubtreesHash();
+
+    // Assign the role to the user.
+    $this->drupalPost('user/' . $admin_user_id . '/edit', array("roles[$rid]" => $rid), t('Save'));
+    $this->assertText(t('The changes have been saved.'));
+
+    // Log in admin_user and assert that the subtrees hash has changed.
+    $this->drupalLogin($this->admin_user);
+    $this->assertDifferentHash();
+
+    // Log in admin_user_2 to check that its subtrees hash has not changed.
+    $this->drupalLogin($this->admin_user_2);
+    $new_subtree_hash = $this->getSubtreesHash();
+
+    // Assert that the old admin_user subtree hash and the new admin_user
+    // subtree hash are the same.
+    $this->assertTrue($new_subtree_hash, 'A valid hash value for the admin menu subtrees was created.');
+    $this->assertEqual($admin_user_2_hash, $new_subtree_hash, 'The user-specific subtree menu hash has not been updated.');
+  }
+
+  /**
+   * Get the hash value from the admin menu subtrees route path.
+   *
+   * @return String
+   *   The hash value from the admin menu subtrees route path.
+   */
+  private function getSubtreesHash() {
+    $settings = $this->drupalGetSettings();
+    // The toolbar module defines a route '/toolbar/subtrees/{hash}' that
+    // returns JSON for the rendered subtrees. This path is provided to the
+    // client in drupalSettings. To get just the hash value, remove the
+    // '/toolbar/subtrees/' portion of the path.
+    return substr($settings['toolbar']['subtreesPath'], 18);
+  }
+
+  /**
+   * Asserts the subtrees hash on a fresh page GET is different from the hash
+   * from the previous page GET.
+   */
+  private function assertDifferentHash() {
+    // Request a new page to refresh the drupalSettings object.
+    $this->drupalGet('test-page');
+    $this->assertResponse(200);
+    $new_subtree_hash = $this->getSubtreesHash();
+
+    // Assert that the old admin menu subtree hash and the new admin menu
+    // subtree hash are different.
+    $this->assertTrue($new_subtree_hash, 'A valid hash value for the admin menu subtrees was created.');
+    $this->assertNotEqual($this->hash, $new_subtree_hash, 'The user-specific subtree menu hash has been updated.');
+
+    // Save the new subtree hash as the original.
+    $this->hash = $new_subtree_hash;
+  }
+
+}
diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module
index 43ad735..11a4962 100644
--- a/core/modules/toolbar/toolbar.module
+++ b/core/modules/toolbar/toolbar.module
@@ -5,11 +5,14 @@
  * Administration toolbar for quick access to top level administration items.
  */
 
+use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Language\Language;
-use Symfony\Component\HttpFoundation\JsonResponse;
 use Drupal\Core\Template\Attribute;
 use Drupal\Component\Utility\Crypt;
 use Symfony\Component\HttpFoundation\Response;
+use Drupal\menu_link\MenuLinkInterface;
+use Drupal\user\RoleInterface;
+use Drupal\user\UserInterface;
 
 /**
  * Implements hook_help().
@@ -437,11 +440,16 @@ function toolbar_toolbar() {
   );
 
   // To conserve bandwidth, we only include the top-level links in the HTML.
-  // The subtrees are included in a JSONP script, cached by the browser. Here we
-  // add that JSONP script. We add it as an external script, because it's a
-  // Drupal path, not a file available via a stream wrapper.
+  // The subtrees are fetched through a JSONP script. Here we
+  // add that JSONP script URL. We add it as a path because it is not a file
+  // available via a stream wrapper.
   // @see toolbar_subtrees_jsonp()
-  $menu['toolbar_administration']['#attached']['js'][url('toolbar/subtrees/' . _toolbar_get_subtree_hash())] = array('type' => 'external');
+  $menu['toolbar_administration']['#attached']['js'][] = array(
+    'type' => 'setting',
+    'data' => array('toolbar' => array(
+      'subtreesPath' => url('toolbar/subtrees/' . _toolbar_get_subtree_hash()),
+    )),
+  );
 
   // The administration element has a link that is themed to correspond to
   // a toolbar tray. The tray contains the full administrative menu of the site.
@@ -552,34 +560,6 @@ function toolbar_get_rendered_subtrees() {
 }
 
 /**
- * Checks whether an item is in the active trail.
- *
- * Useful when using a menu generated by menu_tree_all_data() which does
- * not set the 'in_active_trail' flag on items.
- *
- * @return
- *   TRUE when path is in the active trail, FALSE if not.
- *
- * @todo
- *   Look at migrating to a menu system level function.
- */
-function toolbar_in_active_trail($path) {
-  $active_paths = &drupal_static(__FUNCTION__);
-
-  // Gather active paths.
-  if (!isset($active_paths)) {
-    $active_paths = array();
-    $trail = menu_get_active_trail();
-    foreach ($trail as $item) {
-      if (!empty($item['href'])) {
-        $active_paths[] = $item['href'];
-      }
-    }
-  }
-  return in_array($path, $active_paths);
-}
-
-/**
  * Implements hook_library_info().
  */
 function toolbar_library_info() {
@@ -599,6 +579,7 @@ function toolbar_library_info() {
       array('system', 'jquery'),
       array('system', 'drupal'),
       array('system', 'drupalSettings'),
+      array('system', 'drupal.announce'),
       array('system', 'backbone'),
       array('system', 'matchmedia'),
       array('system', 'jquery.once'),
@@ -628,17 +609,91 @@ function toolbar_library_info() {
 
 /**
  * Returns the hash of the per-user rendered toolbar subtrees.
+ *
+ * @return string
+ *   The hash of the admin_menu subtrees.
  */
 function _toolbar_get_subtree_hash() {
-  $user = Drupal::currentUser();
-  $cid = $user->id() . ':' . language(Language::TYPE_INTERFACE)->id;
+  $uid = \Drupal::currentUser()->id();
+  $cid = _toolbar_get_user_cid($uid);
   if ($cache = cache('toolbar')->get($cid)) {
     $hash = $cache->data;
   }
   else {
     $subtrees = toolbar_get_rendered_subtrees();
     $hash = Crypt::hashBase64(serialize($subtrees));
-    cache('toolbar')->set($cid, $hash);
+    // Cache using a tag 'user' so that we can invalidate all
+    // user-specific caches later based on the uer's ID regardless of language.
+    cache('toolbar')->set($cid, $hash, CacheBackendInterface::CACHE_PERMANENT, array('user' => array($uid)));
   }
   return $hash;
 }
+
+/**
+ * Implements hook_modules_enabled().
+ */
+function toolbar_modules_enabled($modules) {
+  _toolbar_clear_user_cache();
+}
+
+/**
+ * Implements hook_modules_disabled().
+ */
+function toolbar_modules_disabled($modules) {
+  _toolbar_clear_user_cache();
+}
+
+/**
+ * Implements hook_ENTITY_TYPE_update().
+ */
+function toolbar_menu_link_update(MenuLinkInterface $menu_link) {
+  if ($menu_link->get('menu_name') === 'admin') {
+    _toolbar_clear_user_cache();
+  }
+}
+
+/**
+ * Implements hook_ENTITY_TYPE_update().
+ */
+function toolbar_user_update(UserInterface $user) {
+  _toolbar_clear_user_cache($user->id());
+}
+
+/**
+ * Implements hook_ENTITY_TYPE_update().
+ */
+function toolbar_user_role_update(RoleInterface $role) {
+  _toolbar_clear_user_cache();
+}
+
+/**
+ * Returns a cache ID from the user and language IDs.
+ *
+ * @param string $uid
+ *   A user ID.
+ *
+ * @return string
+ *   A unique cache ID for the user.
+ */
+function _toolbar_get_user_cid($uid) {
+  return $uid . ':' . \Drupal::languageManager()->getLanguage(Language::TYPE_INTERFACE)->id;
+}
+
+/**
+ * Clears the Toolbar user cache.
+ *
+ * @param string $uid
+ *   (optional) The user ID to clear.
+ */
+function _toolbar_clear_user_cache($uid = NULL) {
+  $cache = cache('toolbar');
+  if (!$cache->isEmpty()) {
+    // Clear by the 'user' tag in order to delete all caches, in any language,
+    // associated with this user.
+    if (isset($uid)) {
+      $cache->deleteTags(array('user' => array($uid)));
+    } else {
+      $cache->deleteAll();
+    }
+  }
+}
