diff --git a/core/lib/Drupal/Core/Utility/Token.php b/core/lib/Drupal/Core/Utility/Token.php
index 8d98232..cf2d3a3 100644
--- a/core/lib/Drupal/Core/Utility/Token.php
+++ b/core/lib/Drupal/Core/Utility/Token.php
@@ -206,7 +206,7 @@ public function replace($text, array $data = array(), array $options = array(),
 
     // Escape the tokens, unless they are explicitly markup.
     foreach ($replacements as $token => $value) {
-      $replacements[$token] = $value instanceof MarkupInterface ? $value : new HtmlEscapedText($value);
+      $replacements[$token] = ($value instanceof MarkupInterface) ? $value : new HtmlEscapedText($value);
     }
 
     // Optionally alter the list of replacement values.
diff --git a/core/modules/node/node.tokens.inc b/core/modules/node/node.tokens.inc
index 81fa3f0..03c7e21 100644
--- a/core/modules/node/node.tokens.inc
+++ b/core/modules/node/node.tokens.inc
@@ -6,9 +6,9 @@
  */
 
 use Drupal\Core\Datetime\Entity\DateFormat;
-use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Render\BubbleableMetadata;
 use Drupal\user\Entity\User;
+use Drupal\Component\Utility\Html;
 
 /**
  * Implements hook_token_info().
@@ -93,13 +93,13 @@ function node_tokens($type, $tokens, array $data, array $options, BubbleableMeta
     $langcode = $options['langcode'];
   }
   else {
-    $langcode = LanguageInterface::LANGCODE_DEFAULT;
+    $langcode = NULL;
   }
   $replacements = array();
 
   if ($type == 'node' && !empty($data['node'])) {
     /** @var \Drupal\node\NodeInterface $node */
-    $node = $data['node'];
+    $node = \Drupal::entityManager()->getTranslationFromContext($data['node'], $langcode, ['operation' => 'node_tokens']);
 
     foreach ($tokens as $name => $original) {
       switch ($name) {
@@ -127,8 +127,7 @@ function node_tokens($type, $tokens, array $data, array $options, BubbleableMeta
 
         case 'body':
         case 'summary':
-          $translation = \Drupal::entityManager()->getTranslationFromContext($node, $langcode, array('operation' => 'node_tokens'));
-          if ($translation->hasField('body') && ($items = $translation->get('body')) && !$items->isEmpty()) {
+          if ($node->hasField('body') && ($items = $node->get('body')) && !$items->isEmpty()) {
             $item = $items[0];
             // If the summary was requested and is not empty, use it.
             if ($name == 'summary' && !empty($item->summary)) {
@@ -181,15 +180,13 @@ function node_tokens($type, $tokens, array $data, array $options, BubbleableMeta
           break;
 
         case 'created':
-          $date_format = DateFormat::load('medium');
-          $bubbleable_metadata->addCacheableDependency($date_format);
-          $replacements[$original] = format_date($node->getCreatedTime(), 'medium', '', NULL, $langcode);
-          break;
-
         case 'changed':
+          /** @var \Drupal\Core\Datetime\Entity\DateFormat $date_format */
           $date_format = DateFormat::load('medium');
+          $date_raw = ($name === 'created' ? $node->getCreatedTime() : $node->getChangedTime());
+          $date_formatted = \Drupal::service('date.formatter')->format($date_raw, $date_format->id(), '', NULL, $langcode);
           $bubbleable_metadata->addCacheableDependency($date_format);
-          $replacements[$original] = format_date($node->getChangedTime(), 'medium', '', NULL, $langcode);
+          $replacements[$original] = $date_formatted;
           break;
       }
     }
diff --git a/core/modules/node/src/Tests/NodeTokenLanguageTest.php b/core/modules/node/src/Tests/NodeTokenLanguageTest.php
new file mode 100644
index 0000000..0ec44fe
--- /dev/null
+++ b/core/modules/node/src/Tests/NodeTokenLanguageTest.php
@@ -0,0 +1,149 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\Tests\NodeTokenLanguageTest.
+ */
+
+namespace Drupal\node\Tests;
+
+use Drupal\simpletest\WebTestBase;
+use Drupal\language\Entity\ConfigurableLanguage;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\node\Entity\Node;
+use Drupal\system\Tests\Utility\AssertTokenReplacementTrait;
+
+/**
+ * Check if node tokens are multilingual.
+ *
+ * @group node
+ */
+class NodeTokenLanguageTest extends WebTestBase {
+
+  use AssertTokenReplacementTrait;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['node', 'language', 'content_translation'];
+
+  /**
+   * Users for testing different languages.
+   *
+   * @var null
+   */
+  private $users = NULL;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // Create Basic page node type.
+    $this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
+
+    // Setup user.
+    $admin_user = $this->drupalCreateUser([
+      'administer languages',
+      'administer content types',
+      'access administration pages',
+      'create page content',
+      'edit own page content'
+    ]);
+    $this->drupalLogin($admin_user);
+
+    // Add a new language.
+    ConfigurableLanguage::createFromLangcode('it')->save();
+
+    // Enable URL language detection and selection.
+    $edit = ['language_interface[enabled][language-url]' => '1'];
+    $this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));
+
+    // Set "Basic page" content type to use multilingual support.
+    $edit = ['language_configuration[language_alterable]' => TRUE];
+    $this->drupalPostForm('admin/structure/types/manage/page', $edit, t('Save content type'));
+    $this->assertRaw(t('The content type %type has been updated.', ['%type' => 'Basic page']), 'Basic page content type has been updated.');
+
+    // Make node body translatable.
+    $field_storage = FieldStorageConfig::loadByName('node', 'body');
+    $field_storage->setTranslatable(TRUE);
+    $field_storage->save();
+  }
+
+  /**
+   * Test if node tokes are multilingual.
+   */
+  public function testMultilingualNodeTokens() {
+    // Setup users.
+    foreach (['en', 'it'] as $langcode) {
+      $this->users[$langcode] = $this->drupalCreateUser([
+        'create page content',
+        'edit own page content'
+      ]);
+    }
+
+    // Create English "Basic page" node.
+    $langcode = 'en';
+    $this->drupalLogin($this->users[$langcode]);
+    $node = Node::create($this->getFieldsArray($langcode));
+    $node->save();
+
+    $this->executeLanguageSensitiveTokenReplacementAssertions($node, 'English node token %token replaced with %output which is equal to %expected');
+
+    // Create italian translation with new field values.
+    $langcode = 'it';
+    $this->drupalLogin($this->users[$langcode]);
+    $node->addTranslation($langcode, $this->getFieldsArray($langcode));
+    $translation = $node->getTranslation($langcode);
+
+    $this->executeLanguageSensitiveTokenReplacementAssertions($translation, 'Italian node token %token replaced with %output which is equal to %expected');
+  }
+
+  /**
+   * Asserts language sensitive token replacement for general node tokens.
+   *
+   * @param \Drupal\node\Entity\Node $node
+   *  The node to assert.
+   * @param $message
+   *  A message to describe the test results.
+   *  @see AssertTokenReplacementTrait::assertTokenReplacement()
+   */
+  protected function executeLanguageSensitiveTokenReplacementAssertions(Node $node, $message) {
+    $tests['[node:title]'] = $node->getTitle();
+    $tests['[node:body]'] = $node->body->processed;
+    $tests['[node:summary]'] = !empty($node->body->summary) ? $node->body->summary_processed : $node->body->processed;
+    $tests['[node:author]'] = $node->getOwner()->getAccountName();
+    $tests['[node:created]'] = \Drupal::service('date.formatter')->format($node->getCreatedTime(), 'medium', '', NULL, $node->language()->getId());
+    $tests['[node:changed]'] = \Drupal::service('date.formatter')->format($node->getChangedTime(), 'medium', '', NULL, $node->language()->getId());
+
+    $data = ['node' => $node];
+    $options = ['langcode' => $node->language()->getId()];
+    $this->assertTokenReplacement($tests, $data, $options, $message);
+  }
+
+  /**
+   * Populates an array with field values for a given language.
+   *
+   * @param string $langcode
+   *   The language code.
+   *
+   * @return array
+   *   The fields of node entity.
+   */
+  protected function getFieldsArray($langcode) {
+    $time = REQUEST_TIME - rand(0, 1000);
+    return [
+      'title' => $langcode . $this->randomString(8),
+      'body' => $langcode . $this->randomString(16),
+      'type' => 'page',
+      'language' => $langcode,
+      'uid' => $this->users[$langcode]->id(),
+      'created' => $time,
+      'changed' => $time + 100,
+    ];
+  }
+
+}
diff --git a/core/modules/node/src/Tests/NodeTokenReplaceTest.php b/core/modules/node/src/Tests/NodeTokenReplaceTest.php
index bc6a049..e9d4911 100644
--- a/core/modules/node/src/Tests/NodeTokenReplaceTest.php
+++ b/core/modules/node/src/Tests/NodeTokenReplaceTest.php
@@ -13,6 +13,8 @@
 use Drupal\system\Tests\System\TokenReplaceUnitTestBase;
 
 /**
+ * Generates text using placeholders.
+ *
  * Generates text using placeholders for dummy content to check node token
  * replacement.
  *
@@ -42,7 +44,7 @@ protected function setUp() {
   /**
    * Creates a node, then tests the tokens generated from it.
    */
-  function testNodeTokenReplacement() {
+  public function testNodeTokenReplacement() {
     $url_options = array(
       'absolute' => TRUE,
       'language' => $this->interfaceLanguage,
diff --git a/core/modules/system/src/Tests/Utility/AssertTokenReplacementTrait.php b/core/modules/system/src/Tests/Utility/AssertTokenReplacementTrait.php
new file mode 100644
index 0000000..4c9f4fc
--- /dev/null
+++ b/core/modules/system/src/Tests/Utility/AssertTokenReplacementTrait.php
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Utility\AssertTokenReplacementTrait.
+ */
+
+namespace Drupal\system\Tests\Utility;
+use Drupal\Component\Render\FormattableMarkup;
+use Drupal\Component\Render\HtmlEscapedText;
+use Drupal\Component\Render\MarkupInterface;
+use Drupal\Core\Render\BubbleableMetadata;
+
+/**
+ * Provides test assertions for testing token replacements.
+ *
+ * Can be used by test classes that extend \Drupal\simpletest\WebTestBase.
+ */
+trait AssertTokenReplacementTrait {
+
+  /**
+   * Checks token replacement and metadata.
+   *
+   * Asserts if tokens are correctly replaced and verifies that the correct
+   * metadata has been set.
+   *
+   * @param array $tests
+   *   Expected results keyed by their respective tokens.
+   * @param array $data
+   *   The data to perform the replacement on. @see Token::replace().
+   * @param array $options
+   *   Additional replacement options. @see Token::replace().
+   * @param $message
+   *   The message to display with the assertion. Can contain replacement
+   *   tokens:
+   *   - %token for the token name
+   *   - %output for the value returned by the token replacement service
+   *   - %expected for the expected result
+   * @param array $metadata_tests
+   *   The metadata to verify. Keyed by the relevant token.
+   */
+  protected function assertTokenReplacementAndCheckMetadata(array $tests, array $data, array $options, $message, array $metadata_tests) {
+    foreach ($tests as $token => $expected) {
+      $bubbleable_metadata = new BubbleableMetadata();
+      $output = \Drupal::token()->replace($token, $data, $options, $bubbleable_metadata);
+      $this->assertEqual($output, new HtmlEscapedText($expected), new FormattableMarkup($message, [
+        '%token' => $token,
+        '%output' => $output,
+        '%expected' => $expected,
+      ]));
+
+      $this->assertEqual($bubbleable_metadata, $metadata_tests[$token]);
+    }
+  }
+
+  /**
+   * Asserts if tokens are correctly replaced.
+   *
+   * @param array $tests
+   *   Expected results keyed by their respective tokens.
+   * @param array $data
+   *   The data to perform the replacement on. @see Token::replace().
+   * @param array $options
+   *   Additional replacement options. @see Token::replace().
+   * @param $message
+   *   The message to display with the assertion. Can contain replacement
+   *   tokens:
+   *   - %token for the token name
+   *   - %output for the value returned by the token replacement service
+   *   - %expected for the expected result
+   */
+  protected function assertTokenReplacement(array $tests, array $data, array $options, $message) {
+    foreach ($tests as $token => $expected) {
+      $output = \Drupal::token()->replace($token, $data, $options);
+      $expected = ($expected instanceof MarkupInterface) ? $expected : new HtmlEscapedText($expected);
+      $this->assertEqual($output, $expected, new FormattableMarkup($message, [
+        '%token' => $token,
+        '%output' => $output,
+        '%expected' => $expected,
+      ]));
+    }
+  }
+
+}
diff --git a/core/modules/taxonomy/src/Tests/TaxonomyTestTrait.php b/core/modules/taxonomy/src/Tests/TaxonomyTestTrait.php
index 34971db..9027c5c 100644
--- a/core/modules/taxonomy/src/Tests/TaxonomyTestTrait.php
+++ b/core/modules/taxonomy/src/Tests/TaxonomyTestTrait.php
@@ -18,10 +18,16 @@
 
   /**
    * Returns a new vocabulary with random properties.
+   *
+   * @param array $values
+   *   (optional) An array of values to set, keyed by property name.
+   *
+   * @return \Drupal\taxonomy\VocabularyInterface
+   *   The new taxonomy vocabulary object.
    */
-  function createVocabulary() {
+  function createVocabulary(array $values = []) {
     // Create a vocabulary.
-    $vocabulary = entity_create('taxonomy_vocabulary', array(
+    $vocabulary = entity_create('taxonomy_vocabulary', $values + array(
       'name' => $this->randomMachineName(),
       'description' => $this->randomMachineName(),
       'vid' => Unicode::strtolower($this->randomMachineName()),
diff --git a/core/modules/taxonomy/src/Tests/TaxonomyTokenLanguageTest.php b/core/modules/taxonomy/src/Tests/TaxonomyTokenLanguageTest.php
new file mode 100644
index 0000000..29a5386
--- /dev/null
+++ b/core/modules/taxonomy/src/Tests/TaxonomyTokenLanguageTest.php
@@ -0,0 +1,111 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\taxonomy\Tests\TaxonomyTokenLanguageTest.
+ */
+
+namespace Drupal\taxonomy\Tests;
+
+use Drupal\language\Entity\ConfigurableLanguage;
+use Drupal\system\Tests\Utility\AssertTokenReplacementTrait;
+use Drupal\taxonomy\Entity\Term;
+
+/**
+ * Check if taxonomy tokens are multilingual.
+ *
+ * @group taxonomy
+ */
+class TaxonomyTokenLanguageTest extends TaxonomyTestBase {
+
+  use AssertTokenReplacementTrait;
+
+  /**
+   * Vocabulary for testing.
+   *
+   * @var \Drupal\taxonomy\VocabularyInterface
+   */
+  protected $vocabulary;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = ['taxonomy', 'language', 'content_translation'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // Create an administrative user.
+    $this->drupalLogin($this->drupalCreateUser(['administer taxonomy']));
+
+    // Create a vocabulary to which the terms will be assigned.
+    $this->vocabulary = $this->createVocabulary();
+
+    // Add a new language.
+    ConfigurableLanguage::createFromLangcode('it')->save();
+  }
+
+  /**
+   * Test if taxonomy token are multilingual.
+   */
+  public function testMultilingualTaxonomyTokens() {
+    // Configure the vocabulary to not hide the language selector.
+    $edit = [
+      'default_language[language_alterable]' => TRUE,
+    ];
+    $this->drupalPostForm('admin/structure/taxonomy/manage/' . $this->vocabulary->id(), $edit, t('Save'));
+
+    // Create Term.
+    $langcode = 'en';
+    $fields = $this->getFieldsArray($langcode);
+    $term = $this->createTerm($this->vocabulary, $fields);
+    $this->executeLanguageSensitiveTokenReplacementAssertions($term, "English taxonomy term token %token replaced with %output which is equal to %expected");
+
+    $langcode = 'it';
+    $fields = $this->getFieldsArray($langcode);
+    $term = $term->addTranslation($langcode, $fields);
+    $this->executeLanguageSensitiveTokenReplacementAssertions($term, "Italian taxonomy term token %token replaced with %output which is equal to %expected");
+  }
+
+  /**
+   * Populates an array with field values for a given language.
+   *
+   * @param string $langcode
+   *   The language code.
+   *
+   * @return array
+   *   The fields of a term.
+   */
+  protected function getFieldsArray($langcode) {
+    return [
+      'name' => $langcode . $this->randomString(8),
+      'description' => $langcode . $this->randomString(16),
+      'langcode' => $langcode,
+    ];
+  }
+
+  /**
+   * Asserts language sensitive token replacement for general taxonomy tokens.
+   *
+   * @param \Drupal\taxonomy\Entity\Term $term
+   *  The term to assert.
+   * @param $message
+   *  A message to describe the test results.
+   *  @see AssertTokenReplacementTrait::assertTokenReplacement()
+   */
+  protected function executeLanguageSensitiveTokenReplacementAssertions(Term $term, $message) {
+    $tests['[term:name]'] = $term->getName();
+    $tests['[term:description]'] = $term->getDescription();
+    $tests['[term:url]'] = $term->url('canonical', ['absolute' => TRUE]);
+
+    $data = ['term' => $term];
+    $options = ['langcode' => $term->language()->getId()];
+    $this->assertTokenReplacement($tests, $data, $options, $message);
+  }
+
+}
diff --git a/core/modules/taxonomy/src/Tests/TokenReplaceTest.php b/core/modules/taxonomy/src/Tests/TokenReplaceTest.php
index 1e00a8e..494a3d9 100644
--- a/core/modules/taxonomy/src/Tests/TokenReplaceTest.php
+++ b/core/modules/taxonomy/src/Tests/TokenReplaceTest.php
@@ -9,8 +9,11 @@
 
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\system\Tests\Utility\AssertTokenReplacementTrait;
 
 /**
+ * Generates text using placeholders.
+ *
  * Generates text using placeholders for dummy content to check taxonomy token
  * replacement.
  *
@@ -18,10 +21,12 @@
  */
 class TokenReplaceTest extends TaxonomyTestBase {
 
+  use AssertTokenReplacementTrait;
+
   /**
    * The vocabulary used for creating terms.
    *
-   * @var \Drupal\taxonomy\VocabularyInterface
+   * @var \Drupal\taxonomy\entity\Vocabulary
    */
   protected $vocabulary;
 
@@ -32,10 +37,21 @@ class TokenReplaceTest extends TaxonomyTestBase {
    */
   protected $fieldName;
 
+  /**
+   * Token service.
+   *
+   * @var \Drupal\Core\Utility\Token
+   */
+  protected $tokenService;
+
+  /**
+   * {@inheritdoc}
+   */
   protected function setUp() {
     parent::setUp();
+
     $this->drupalLogin($this->drupalCreateUser(['administer taxonomy', 'bypass node access']));
-    $this->vocabulary = $this->createVocabulary();
+    $this->vocabulary = $this->createVocabulary(['name' => 'V1 <strong>"&lt;name&gt;"</strong>']);
     $this->fieldName = 'taxonomy_' . $this->vocabulary->id();
 
     $handler_settings = array(
@@ -61,13 +77,12 @@ protected function setUp() {
   /**
    * Creates some terms and a node, then tests the tokens generated from them.
    */
-  function testTaxonomyTokenReplacement() {
-    $token_service = \Drupal::token();
+  public function testTaxonomyTokenReplacement() {
     $language_interface = \Drupal::languageManager()->getCurrentLanguage();
 
-    // Create two taxonomy terms.
-    $term1 = $this->createTerm($this->vocabulary);
-    $term2 = $this->createTerm($this->vocabulary);
+    // Create two taxonomy terms with unsafe names.
+    $term1 = $this->createTerm($this->vocabulary, ['name' => 'T1 <script>"&lt;name&gt;"</script>',]);
+    $term2 = $this->createTerm($this->vocabulary, ['name' => 'T2 <strong>"&lt;name&gt;"</strong>',]);
 
     // Edit $term2, setting $term1 as parent.
     $edit = array();
@@ -85,13 +100,18 @@ function testTaxonomyTokenReplacement() {
     $tests = array();
     $tests['[term:tid]'] = $term1->id();
     $tests['[term:name]'] = $term1->getName();
-    $tests['[term:description]'] = $term1->description->processed;
+    $tests['[term:description]'] = $term1->getDescription();
     $tests['[term:url]'] = $term1->url('canonical', array('absolute' => TRUE));
     $tests['[term:node-count]'] = 0;
+    $tests['[term:parent]'] = '[term:parent]';
     $tests['[term:parent:name]'] = '[term:parent:name]';
+    $tests['[term:parent:url]'] = '[term:parent:url]';
     $tests['[term:vocabulary:name]'] = $this->vocabulary->label();
     $tests['[term:vocabulary]'] = $this->vocabulary->label();
 
+    // Test to make sure that we generated something for each token.
+    $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
+
     $base_bubbleable_metadata = BubbleableMetadata::createFromObject($term1);
 
     $metadata_tests = array();
@@ -100,37 +120,67 @@ function testTaxonomyTokenReplacement() {
     $metadata_tests['[term:description]'] = $base_bubbleable_metadata;
     $metadata_tests['[term:url]'] = $base_bubbleable_metadata;
     $metadata_tests['[term:node-count]'] = $base_bubbleable_metadata;
+    $metadata_tests['[term:parent]'] = $base_bubbleable_metadata;
     $metadata_tests['[term:parent:name]'] = $base_bubbleable_metadata;
+    $metadata_tests['[term:parent:url]'] = $base_bubbleable_metadata;
     $bubbleable_metadata = clone $base_bubbleable_metadata;
     $metadata_tests['[term:vocabulary:name]'] = $bubbleable_metadata->addCacheTags($this->vocabulary->getCacheTags());
     $metadata_tests['[term:vocabulary]'] = $bubbleable_metadata->addCacheTags($this->vocabulary->getCacheTags());
 
-    foreach ($tests as $input => $expected) {
-      $bubbleable_metadata = new BubbleableMetadata();
-      $output = $token_service->replace($input, array('term' => $term1), array('langcode' => $language_interface->getId()), $bubbleable_metadata);
-      $this->assertEqual($output, $expected, format_string('Sanitized taxonomy term token %token replaced.', array('%token' => $input)));
-      $this->assertEqual($bubbleable_metadata, $metadata_tests[$input]);
-    }
+    $data = ['term' => $term1];
+    $options = ['langcode' => $language_interface->getId()];
+    $msg = 'Taxonomy term 1 token %token replaced with %output which is equal to %expected';
+    $this->assertTokenReplacementAndCheckMetadata($tests, $data, $options, $msg, $metadata_tests);
 
     // Generate and test sanitized tokens for term2.
     $tests = array();
     $tests['[term:tid]'] = $term2->id();
     $tests['[term:name]'] = $term2->getName();
-    $tests['[term:description]'] = $term2->description->processed;
+    $tests['[term:description]'] = $term2->getDescription();
     $tests['[term:url]'] = $term2->url('canonical', array('absolute' => TRUE));
     $tests['[term:node-count]'] = 1;
+    $tests['[term:vocabulary]'] = $this->vocabulary->label();
+    $tests['[term:parent]'] = $term1->getName();
+    $tests['[term:parent:tid]'] = $term1->id();
     $tests['[term:parent:name]'] = $term1->getName();
+    $tests['[term:parent:description]'] = $term1->getDescription();
     $tests['[term:parent:url]'] = $term1->url('canonical', array('absolute' => TRUE));
+    $tests['[term:parent:node-count]'] = 0;
     $tests['[term:parent:parent:name]'] = '[term:parent:parent:name]';
     $tests['[term:vocabulary:name]'] = $this->vocabulary->label();
+    $tests['[term:parent:vocabulary]'] = $this->vocabulary->label();
+    $tests['[term:parent:vocabulary:name]'] = $this->vocabulary->label();
 
     // Test to make sure that we generated something for each token.
     $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
 
-    foreach ($tests as $input => $expected) {
-      $output = $token_service->replace($input, array('term' => $term2), array('langcode' => $language_interface->getId()));
-      $this->assertEqual($output, $expected, format_string('Sanitized taxonomy term token %token replaced.', array('%token' => $input)));
-    }
+    $base_bubbleable_metadata = BubbleableMetadata::createFromObject($term2);
+    $metadata_tests = array();
+    $metadata_tests['[term:tid]'] = $base_bubbleable_metadata;
+    $metadata_tests['[term:name]'] = $base_bubbleable_metadata;
+    $metadata_tests['[term:description]'] = $base_bubbleable_metadata;
+    $metadata_tests['[term:url]'] = $base_bubbleable_metadata;
+    $metadata_tests['[term:node-count]'] = $base_bubbleable_metadata;
+    $bubbleable_metadata = clone $base_bubbleable_metadata;
+    $bubbleable_metadata = $bubbleable_metadata->addCacheTags($this->vocabulary->getCacheTags());
+    $metadata_tests['[term:vocabulary]'] = $bubbleable_metadata;
+    $metadata_tests['[term:vocabulary:name]'] = $bubbleable_metadata;
+    $base_bubbleable_metadata = BubbleableMetadata::createFromObject($term1)->addCacheTags($term2->getCacheTags());
+    $metadata_tests['[term:parent]'] = $base_bubbleable_metadata;
+    $metadata_tests['[term:parent:tid]'] = $base_bubbleable_metadata;
+    $metadata_tests['[term:parent:name]'] = $base_bubbleable_metadata;
+    $metadata_tests['[term:parent:description]'] = $base_bubbleable_metadata;
+    $metadata_tests['[term:parent:url]'] = $base_bubbleable_metadata;
+    $metadata_tests['[term:parent:node-count]'] = $base_bubbleable_metadata;
+    $metadata_tests['[term:parent:parent:name]'] = $base_bubbleable_metadata;
+    $bubbleable_metadata = clone $base_bubbleable_metadata;
+    $bubbleable_metadata = $bubbleable_metadata->addCacheTags($this->vocabulary->getCacheTags());
+    $metadata_tests['[term:parent:vocabulary:name]'] = $bubbleable_metadata;
+    $metadata_tests['[term:parent:vocabulary]'] = $bubbleable_metadata;
+
+    $data = ['term' => $term2];
+    $msg = 'Taxonomy term 2 token %token replaced with %output which is equal to %expected';
+    $this->assertTokenReplacementAndCheckMetadata($tests, $data, $options, $msg, $metadata_tests);
 
     // Generate and test sanitized tokens.
     $tests = array();
@@ -143,10 +193,18 @@ function testTaxonomyTokenReplacement() {
     // Test to make sure that we generated something for each token.
     $this->assertFalse(in_array(0, array_map('strlen', $tests)), 'No empty tokens generated.');
 
-    foreach ($tests as $input => $expected) {
-      $output = $token_service->replace($input, array('vocabulary' => $this->vocabulary), array('langcode' => $language_interface->getId()));
-      $this->assertEqual($output, $expected, format_string('Sanitized taxonomy vocabulary token %token replaced.', array('%token' => $input)));
-    }
+    $base_bubbleable_metadata = BubbleableMetadata::createFromObject($this->vocabulary);
+
+    $metadata_tests = array();
+    $metadata_tests['[vocabulary:vid]'] = $base_bubbleable_metadata;
+    $metadata_tests['[vocabulary:name]'] = $base_bubbleable_metadata;
+    $metadata_tests['[vocabulary:description]'] = $base_bubbleable_metadata;
+    $metadata_tests['[vocabulary:node-count]'] = $base_bubbleable_metadata;
+    $metadata_tests['[vocabulary:term-count]'] = $base_bubbleable_metadata;
+
+    $data = ['vocabulary' => $this->vocabulary];
+    $msg = 'Taxonomy vocabulary token %token replaced with %output which is equal to %expected';
+    $this->assertTokenReplacementAndCheckMetadata($tests, $data, $options, $msg, $metadata_tests);
   }
-}
 
+}
diff --git a/core/modules/taxonomy/taxonomy.tokens.inc b/core/modules/taxonomy/taxonomy.tokens.inc
index 20251f2..5e5cb7f 100644
--- a/core/modules/taxonomy/taxonomy.tokens.inc
+++ b/core/modules/taxonomy/taxonomy.tokens.inc
@@ -95,10 +95,15 @@ function taxonomy_tokens($type, $tokens, array $data, array $options, Bubbleable
   $token_service = \Drupal::token();
 
   $replacements = array();
+
+  $langcode = isset($options['langcode']) ? $options['langcode'] : $langcode = NULL;
+
   $taxonomy_storage = \Drupal::entityManager()->getStorage('taxonomy_term');
   if ($type == 'term' && !empty($data['term'])) {
-    $term = $data['term'];
+    /** @var \Drupal\taxonomy\TermInterface $term */
+    $term = \Drupal::entityManager()->getTranslationFromContext($data['term'], $langcode, ['operation' => 'term_tokens']);
 
+    /** @var \Drupal\taxonomy\TermStorageInterface $taxonomy_storage */
     foreach ($tokens as $name => $original) {
       switch ($name) {
         case 'tid':
@@ -110,9 +115,7 @@ function taxonomy_tokens($type, $tokens, array $data, array $options, Bubbleable
           break;
 
         case 'description':
-          // "processed" returns a \Drupal\Component\Render\MarkupInterface via
-          // check_markup().
-          $replacements[$original] = $term->description->processed;
+          $replacements[$original] = $term->getDescription();
           break;
 
         case 'url':
@@ -128,6 +131,7 @@ function taxonomy_tokens($type, $tokens, array $data, array $options, Bubbleable
           break;
 
         case 'vocabulary':
+          /** @var \Drupal\taxonomy\VocabularyInterface $vocabulary */
           $vocabulary = Vocabulary::load($term->bundle());
           $bubbleable_metadata->addCacheableDependency($vocabulary);
           $replacements[$original] = $vocabulary->label();
@@ -135,7 +139,7 @@ function taxonomy_tokens($type, $tokens, array $data, array $options, Bubbleable
 
         case 'parent':
           if ($parents = $taxonomy_storage->loadParents($term->id())) {
-            $parent = array_pop($parents);
+            $parent = \Drupal::entityManager()->getTranslationFromContext(array_pop($parents), $langcode, ['operation' => 'term_tokens']);
             $bubbleable_metadata->addCacheableDependency($parent);
             $replacements[$original] = $parent->getName();
           }
@@ -148,14 +152,14 @@ function taxonomy_tokens($type, $tokens, array $data, array $options, Bubbleable
       $replacements += $token_service->generate('vocabulary', $vocabulary_tokens, array('vocabulary' => $vocabulary), $options, $bubbleable_metadata);
     }
 
-    if (($vocabulary_tokens = $token_service->findWithPrefix($tokens, 'parent')) && $parents = $taxonomy_storage->loadParents($term->id())) {
+    if (($parent_tokens = $token_service->findWithPrefix($tokens, 'parent')) && $parents = $taxonomy_storage->loadParents($term->id())) {
       $parent = array_pop($parents);
-      $replacements += $token_service->generate('term', $vocabulary_tokens, array('term' => $parent), $options, $bubbleable_metadata);
+      $replacements += $token_service->generate('term', $parent_tokens, ['term' => $parent], $options, $bubbleable_metadata);
     }
   }
-
   elseif ($type == 'vocabulary' && !empty($data['vocabulary'])) {
-    $vocabulary = $data['vocabulary'];
+    /** @var \Drupal\taxonomy\VocabularyInterface $vocabulary */
+    $vocabulary = \Drupal::entityManager()->getTranslationFromContext($data['vocabulary'], $langcode, ['operation' => 'vocabulary_tokens']);
 
     foreach ($tokens as $name => $original) {
       switch ($name) {
