diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php
index 351361f..dd3c8f0 100644
--- a/core/lib/Drupal/Core/Entity/EntityManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityManager.php
@@ -945,7 +945,10 @@ public function getEntityTypeLabels($group = FALSE) {
 
     foreach ($definitions as $entity_type_id => $definition) {
       if ($group) {
-        $options[$definition->getGroupLabel()][$entity_type_id] = $definition->getLabel();
+        // We cast the optgroup label to string as array keys must not be
+        // objects and t() may return a TranslationWrapper once issue #2557113
+        // lands.
+        $options[(string) $definition->getGroupLabel()][$entity_type_id] = $definition->getLabel();
       }
       else {
         $options[$entity_type_id] = $definition->getLabel();
@@ -960,7 +963,9 @@ public function getEntityTypeLabels($group = FALSE) {
 
       // Make sure that the 'Content' group is situated at the top.
       $content = $this->t('Content', array(), array('context' => 'Entity type group'));
-      $options = array($content => $options[$content]) + $options;
+      // We cast the optgroup label to string as array keys must not be objects
+      // and t() may return a TranslationWrapper once issue #2557113 lands.
+      $options = array((string) $content => $options[(string) $content]) + $options;
     }
 
     return $options;
diff --git a/core/lib/Drupal/Core/StringTranslation/TranslationInterface.php b/core/lib/Drupal/Core/StringTranslation/TranslationInterface.php
index c3e2f68..f7d311f 100644
--- a/core/lib/Drupal/Core/StringTranslation/TranslationInterface.php
+++ b/core/lib/Drupal/Core/StringTranslation/TranslationInterface.php
@@ -41,6 +41,24 @@
   public function translate($string, array $args = array(), array $options = array());
 
   /**
+   * Translates a string to the current language or to a given language.
+   *
+   * @param string $string
+   *   A string containing the English string to translate.
+   * @param array $options
+   *   An associative array of additional options, with the following elements:
+   *   - 'langcode': The language code to translate to a language other than
+   *      what is used to display the page.
+   *   - 'context': The context the source string belongs to.
+   *
+   * @return string
+   *   The translated string.
+   *
+   * @internal
+   */
+  public function doTranslate($string, array $options = array());
+
+  /**
    * Formats a string containing a count of items.
    *
    * This function ensures that the string is pluralized correctly. Since t() is
diff --git a/core/lib/Drupal/Core/StringTranslation/TranslationManager.php b/core/lib/Drupal/Core/StringTranslation/TranslationManager.php
index af6aa2b..c22c762 100644
--- a/core/lib/Drupal/Core/StringTranslation/TranslationManager.php
+++ b/core/lib/Drupal/Core/StringTranslation/TranslationManager.php
@@ -140,38 +140,13 @@ public function getStringTranslation($langcode, $string, $context) {
    * {@inheritdoc}
    */
   public function translate($string, array $args = array(), array $options = array()) {
-    $string = $this->doTranslate($string, $options);
-    if (empty($args)) {
-      // We add the string to the safe list as opposed to making it an object
-      // implementing SafeStringInterface as we may need to call __toString()
-      // on the object before render time, at which point the string ceases to
-      // be safe, and working around this would require significant rework.
-      // Adding this string to the safe list is assumed to be safe because
-      // translate() should only be called with strings defined in code.
-      // @see \Drupal\Core\StringTranslation\TranslationInterface::translate()
-      SafeMarkup::setMultiple([$string => ['html' => TRUE]]);
-      return $string;
-    }
-    else {
-      return SafeMarkup::format($string, $args);
-    }
+    return new TranslationWrapper($string, $args, $options);
   }
 
   /**
-   * Translates a string to the current language or to a given language.
-   *
-   * @param string $string
-   *   A string containing the English string to translate.
-   * @param array $options
-   *   An associative array of additional options, with the following elements:
-   *   - 'langcode': The language code to translate to a language other than
-   *      what is used to display the page.
-   *   - 'context': The context the source string belongs to.
-   *
-   * @return string
-   *   The translated string.
+   * {@inheritdoc}
    */
-  protected function doTranslate($string, array $options = array()) {
+  public function doTranslate($string, array $options = array()) {
     // Merge in defaults.
     if (empty($options['langcode'])) {
       $options['langcode'] = $this->defaultLangcode;
diff --git a/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php b/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php
index 10e9b12..162b267 100644
--- a/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php
+++ b/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\Core\StringTranslation;
 
+use Drupal\Component\Utility\Html;
+use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Component\Utility\SafeStringInterface;
 use Drupal\Component\Utility\ToStringTrait;
 
@@ -103,7 +105,36 @@ public function getOptions() {
    *   The translated string.
    */
   public function render() {
-    return $this->t($this->string, $this->arguments, $this->options);
+    $string = $this->getStringTranslation()->doTranslate($this->string, $this->options);
+    // @todo fix this pending a decision on https://www.drupal.org/node/2506427
+    if (!empty($this->arguments)) {
+      // Transform arguments before inserting them.
+      $args = $this->arguments;
+      foreach ($args as $key => $value) {
+        switch ($key[0]) {
+          case '@':
+            if (!SafeMarkup::isSafe($value)) {
+              // Escaped only.
+              $args[$key] = Html::escape($value);
+            }
+            break;
+
+          case '%':
+          default:
+            // Escaped and placeholder.
+            if (!SafeMarkup::isSafe($value)) {
+              $value = Html::escape($value);
+            }
+            $args[$key] = '<em class="placeholder">' . $value . '</em>';
+            break;
+
+          case '!':
+            // Pass-through. This should be safe.
+        }
+      }
+      $string = strtr($string, $args);
+    }
+    return $string;
   }
 
   /**
@@ -123,5 +154,4 @@ public function jsonSerialize() {
     return $this->__toString();
   }
 
-
 }
diff --git a/core/lib/Drupal/Core/Template/Attribute.php b/core/lib/Drupal/Core/Template/Attribute.php
index cc0d591..76d7c6a 100644
--- a/core/lib/Drupal/Core/Template/Attribute.php
+++ b/core/lib/Drupal/Core/Template/Attribute.php
@@ -109,7 +109,8 @@ protected function createAttributeValue($name, $value) {
     elseif (is_bool($value)) {
       $value = new AttributeBoolean($name, $value);
     }
-    elseif (!is_object($value)) {
+    // As a development aid, we allow the value to be a safe string object.
+    elseif (!is_object($value) || $value instanceof SafeStringInterface) {
       $value = new AttributeString($name, $value);
     }
     return $value;
diff --git a/core/lib/Drupal/Core/Validation/DrupalTranslator.php b/core/lib/Drupal/Core/Validation/DrupalTranslator.php
index a2bbf5b..dfbef68 100644
--- a/core/lib/Drupal/Core/Validation/DrupalTranslator.php
+++ b/core/lib/Drupal/Core/Validation/DrupalTranslator.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\Core\Validation;
 
+use Drupal\Component\Utility\SafeStringInterface;
+
 /**
  * Translates strings using Drupal's translation system.
  *
@@ -73,8 +75,13 @@ public function getLocale() {
   protected function processParameters(array $parameters) {
     $return = array();
     foreach ($parameters as $key => $value) {
+      // We allow the values in the parameters to be safe string objects. This can be
+      // useful when we want to use parameter values that are TranslationWrappers.
+      if ($value instanceof SafeStringInterface) {
+        $value = (string) $value;
+      }
       if (is_object($value)) {
-        // t() does not work will objects being passed as replacement strings.
+        // t() does not work with objects being passed as replacement strings.
       }
       // Check for symfony replacement patterns in the form "{{ name }}".
       elseif (strpos($key, '{{ ') === 0 && strrpos($key, ' }}') == strlen($key) - 3) {
diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php
index d873820..6fa8771 100644
--- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php
+++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidator.php
@@ -16,6 +16,7 @@
 use Drupal\Core\TypedData\Type\StringInterface;
 use Drupal\Core\TypedData\Type\UriInterface;
 use Drupal\Core\TypedData\Validation\TypedDataAwareValidatorTrait;
+use Drupal\Component\Utility\SafeStringInterface;
 use Symfony\Component\Validator\Constraint;
 use Symfony\Component\Validator\ConstraintValidator;
 
@@ -49,7 +50,7 @@ public function validate($value, Constraint $constraint) {
     if ($typed_data instanceof IntegerInterface && filter_var($value, FILTER_VALIDATE_INT) === FALSE) {
       $valid = FALSE;
     }
-    if ($typed_data instanceof StringInterface && !is_scalar($value)) {
+    if ($typed_data instanceof StringInterface && !is_scalar($value) && !($value instanceof SafeStringInterface)) {
       $valid = FALSE;
     }
     // Ensure that URIs comply with http://tools.ietf.org/html/rfc3986, which
diff --git a/core/modules/block/src/Tests/BlockInterfaceTest.php b/core/modules/block/src/Tests/BlockInterfaceTest.php
index 7215765..1464e1b 100644
--- a/core/modules/block/src/Tests/BlockInterfaceTest.php
+++ b/core/modules/block/src/Tests/BlockInterfaceTest.php
@@ -109,16 +109,16 @@ public function testBlockInterface() {
     $actual_form = $display_block->buildConfigurationForm(array(), $form_state);
     // Remove the visibility sections, as that just tests condition plugins.
     unset($actual_form['visibility'], $actual_form['visibility_tabs']);
-    $this->assertIdentical($actual_form, $expected_form, 'Only the expected form elements were present.');
+    $this->assertIdenticalWithSafeStrings($actual_form, $expected_form, 'Only the expected form elements were present.');
 
     $expected_build = array(
       '#children' => 'My custom display message.',
     );
     // Ensure the build array is proper.
-    $this->assertIdentical($display_block->build(), $expected_build, 'The plugin returned the appropriate build array.');
+    $this->assertIdenticalWithSafeStrings($display_block->build(), $expected_build, 'The plugin returned the appropriate build array.');
 
     // Ensure the machine name suggestion is correct. In truth, this is actually
     // testing BlockBase's implementation, not the interface itself.
-    $this->assertIdentical($display_block->getMachineNameSuggestion(), 'displaymessage', 'The plugin returned the expected machine name suggestion.');
+    $this->assertIdenticalWithSafeStrings($display_block->getMachineNameSuggestion(), 'displaymessage', 'The plugin returned the expected machine name suggestion.');
   }
 }
diff --git a/core/modules/block_content/src/Tests/BlockContentListTest.php b/core/modules/block_content/src/Tests/BlockContentListTest.php
index aeed4dc..b3feb7e 100644
--- a/core/modules/block_content/src/Tests/BlockContentListTest.php
+++ b/core/modules/block_content/src/Tests/BlockContentListTest.php
@@ -46,7 +46,7 @@ public function testListing() {
     // Test the contents of each th cell.
     $expected_items = array(t('Block description'), t('Operations'));
     foreach ($elements as $key => $element) {
-      $this->assertIdentical((string) $element[0], $expected_items[$key]);
+      $this->assertIdentical((string) $element[0], (string) $expected_items[$key]);
     }
 
     $label = 'Antelope';
diff --git a/core/modules/block_content/src/Tests/BlockContentListViewsTest.php b/core/modules/block_content/src/Tests/BlockContentListViewsTest.php
index ba84f2b..0267913 100644
--- a/core/modules/block_content/src/Tests/BlockContentListViewsTest.php
+++ b/core/modules/block_content/src/Tests/BlockContentListViewsTest.php
@@ -45,10 +45,10 @@ public function testListing() {
     $expected_items = [t('Block description'), t('Block type'), t('Updated'), t('Operations')];
     foreach ($elements as $key => $element) {
       if ($element->xpath('a')) {
-        $this->assertIdentical(trim((string) $element->xpath('a')[0]), $expected_items[$key]);
+        $this->assertIdenticalWithSafeStrings(trim((string) $element->xpath('a')[0]), $expected_items[$key]);
       }
       else {
-        $this->assertIdentical(trim((string) $element[0]), $expected_items[$key]);
+        $this->assertIdenticalWithSafeStrings(trim((string) $element[0]), $expected_items[$key]);
       }
     }
 
diff --git a/core/modules/breakpoint/src/Tests/BreakpointDiscoveryTest.php b/core/modules/breakpoint/src/Tests/BreakpointDiscoveryTest.php
index 0668c86..386d552 100644
--- a/core/modules/breakpoint/src/Tests/BreakpointDiscoveryTest.php
+++ b/core/modules/breakpoint/src/Tests/BreakpointDiscoveryTest.php
@@ -193,7 +193,7 @@ public function testBreakpointGroups() {
     );
     $breakpoint_groups = \Drupal::service('breakpoint.manager')->getGroups();
     // Ensure the order is as expected. Should be sorted by label.
-    $this->assertIdentical($expected, $breakpoint_groups);
+    $this->assertIdenticalWithSafeStrings($expected, $breakpoint_groups);
 
     $expected = array(
       'breakpoint_theme_test' => 'theme',
diff --git a/core/modules/ckeditor/ckeditor.admin.inc b/core/modules/ckeditor/ckeditor.admin.inc
index 21be21d..d5bb690 100644
--- a/core/modules/ckeditor/ckeditor.admin.inc
+++ b/core/modules/ckeditor/ckeditor.admin.inc
@@ -18,6 +18,10 @@
  *   An associative array containing:
  *   - editor: An editor object.
  *   - plugins: A list of plugins.
+ *   - active_buttons: A list of disabled buttons.
+ *   - disabled_buttons: A list of disabled buttons.
+ *   - multiple_buttons: A list of multiple buttons that may be added multiple
+ *     times.
  */
 function template_preprocess_ckeditor_settings_toolbar(&$variables) {
   $language_interface = \Drupal::languageManager()->getCurrentLanguage();
@@ -113,6 +117,9 @@ function template_preprocess_ckeditor_settings_toolbar(&$variables) {
   $variables['active_buttons'] = array();
   foreach ($active_buttons as $row_number => $button_row) {
     foreach ($button_groups[$row_number] as $group_name) {
+      // We cast the group name to string as array keys must not be objects
+      // and t() may return a TranslationWrapper once issue #2557113 lands.
+      $group_name = (string) $group_name;
       $variables['active_buttons'][$row_number][$group_name] = array(
         'group_name_class' => Html::getClass($group_name),
         'buttons' => array(),
diff --git a/core/modules/ckeditor/src/Tests/CKEditorAdminTest.php b/core/modules/ckeditor/src/Tests/CKEditorAdminTest.php
index ca1954f..babd92c 100644
--- a/core/modules/ckeditor/src/Tests/CKEditorAdminTest.php
+++ b/core/modules/ckeditor/src/Tests/CKEditorAdminTest.php
@@ -87,23 +87,23 @@ function testExistingFormat() {
           // Button groups
           array(
             array(
-              'name' => t('Formatting'),
+              'name' => 'Formatting',
               'items' => array('Bold', 'Italic',),
             ),
             array(
-              'name' => t('Links'),
+              'name' => 'Links',
               'items' => array('DrupalLink', 'DrupalUnlink',),
             ),
             array(
-              'name' => t('Lists'),
+              'name' => 'Lists',
               'items' => array('BulletedList', 'NumberedList',),
             ),
             array(
-              'name' => t('Media'),
+              'name' => 'Media',
               'items' => array('Blockquote', 'DrupalImage',),
             ),
             array(
-              'name' => t('Tools'),
+              'name' => 'Tools',
               'items' => array('Source',),
             ),
           ),
@@ -111,7 +111,7 @@ function testExistingFormat() {
       ),
       'plugins' => array(),
     );
-    $this->assertIdentical($ckeditor->getDefaultSettings(), $expected_default_settings);
+    $this->assertIdenticalWithSafeStrings($ckeditor->getDefaultSettings(), $expected_default_settings);
 
     // Keep the "CKEditor" editor selected and click the "Configure" button.
     $this->drupalPostAjaxForm(NULL, $edit, 'editor_configure');
@@ -279,7 +279,7 @@ function testNewFormat() {
     $expected_settings['plugins']['stylescombo']['styles'] = '';
     $editor = entity_load('editor', 'amazing_format');
     $this->assertTrue($editor instanceof Editor, 'An Editor config entity exists now.');
-    $this->assertIdentical($expected_settings, $editor->getSettings(), 'The Editor config entity has the correct settings.');
+    $this->assertIdenticalWithSafeStrings($expected_settings, $editor->getSettings(), 'The Editor config entity has the correct settings.');
   }
 
 }
diff --git a/core/modules/ckeditor/src/Tests/CKEditorLoadingTest.php b/core/modules/ckeditor/src/Tests/CKEditorLoadingTest.php
index 01b9081..5bd8ed4 100644
--- a/core/modules/ckeditor/src/Tests/CKEditorLoadingTest.php
+++ b/core/modules/ckeditor/src/Tests/CKEditorLoadingTest.php
@@ -112,7 +112,7 @@ function testLoading() {
       'isXssSafe' => FALSE,
     )));
     $this->assertTrue($editor_settings_present, "Text Editor module's JavaScript settings are on the page.");
-    $this->assertIdentical($expected, $settings['editor'], "Text Editor module's JavaScript settings on the page are correct.");
+    $this->assertIdenticalWithSafeStrings($expected, $settings['editor'], "Text Editor module's JavaScript settings on the page are correct.");
     $this->assertTrue($editor_js_present, 'Text Editor JavaScript is present.');
     $this->assertTrue(count($body) === 1, 'A body field exists.');
     $this->assertTrue(count($format_selector) === 1, 'A single text format selector exists on the page.');
@@ -143,7 +143,7 @@ function testLoading() {
           'isXssSafe' => FALSE,
     )));
     $this->assertTrue($editor_settings_present, "Text Editor module's JavaScript settings are on the page.");
-    $this->assertIdentical($expected, $settings['editor'], "Text Editor module's JavaScript settings on the page are correct.");
+    $this->assertIdenticalWithSafeStrings($expected, $settings['editor'], "Text Editor module's JavaScript settings on the page are correct.");
     $this->assertTrue($editor_js_present, 'Text Editor JavaScript is present.');
     $this->assertTrue(in_array('ckeditor/drupal.ckeditor', explode(',', $settings['ajaxPageState']['libraries'])), 'CKEditor glue library is present.');
   }
diff --git a/core/modules/ckeditor/src/Tests/CKEditorTest.php b/core/modules/ckeditor/src/Tests/CKEditorTest.php
index d2a59bc..e7ed49f 100644
--- a/core/modules/ckeditor/src/Tests/CKEditorTest.php
+++ b/core/modules/ckeditor/src/Tests/CKEditorTest.php
@@ -95,7 +95,7 @@ function testGetJSSettings() {
       ),
     );
     ksort($expected_config);
-    $this->assertIdentical($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for default configuration.');
+    $this->assertIdenticalWithSafeStrings($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for default configuration.');
 
     // Customize the configuration: add button, have two contextually enabled
     // buttons, and configure a CKEditor plugin setting.
@@ -116,7 +116,7 @@ function testGetJSSettings() {
     $expected_config['drupalExternalPlugins']['llama_contextual_and_button'] = file_create_url('core/modules/ckeditor/tests/modules/js/llama_contextual_and_button.js');
     $expected_config['contentsCss'][] = file_create_url('core/modules/ckeditor/tests/modules/ckeditor_test.css');
     ksort($expected_config);
-    $this->assertIdentical($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.');
+    $this->assertIdenticalWithSafeStrings($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.');
 
     // Change the allowed HTML tags; the "allowedContent" and "format_tags"
     // settings for CKEditor should automatically be updated as well.
@@ -127,7 +127,7 @@ function testGetJSSettings() {
     $expected_config['allowedContent']['pre'] = array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE);
     $expected_config['allowedContent']['h3'] = array('attributes' => TRUE, 'styles' => FALSE, 'classes' => TRUE);
     $expected_config['format_tags'] = 'p;h2;h3;h4;h5;h6;pre';
-    $this->assertIdentical($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.');
+    $this->assertIdenticalWithSafeStrings($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.');
 
     // Disable the filter_html filter: allow *all *tags.
     $format->setFilterConfig('filter_html', array('status' => 0));
@@ -136,7 +136,7 @@ function testGetJSSettings() {
     $expected_config['allowedContent'] = TRUE;
     $expected_config['disallowedContent'] = FALSE;
     $expected_config['format_tags'] = 'p;h1;h2;h3;h4;h5;h6;pre';
-    $this->assertIdentical($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.');
+    $this->assertIdenticalWithSafeStrings($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.');
 
     // Enable the filter_test_restrict_tags_and_attributes filter.
     $format->setFilterConfig('filter_test_restrict_tags_and_attributes', array(
@@ -205,7 +205,7 @@ function testGetJSSettings() {
     );
     $expected_config['format_tags'] = 'p';
     ksort($expected_config);
-    $this->assertIdentical($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.');
+    $this->assertIdenticalWithSafeStrings($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.');
   }
 
   /**
@@ -216,7 +216,7 @@ function testBuildToolbarJSSetting() {
 
     // Default toolbar.
     $expected = $this->getDefaultToolbarConfig();
-    $this->assertIdentical($expected, $this->ckeditor->buildToolbarJSSetting($editor), '"toolbar" configuration part of JS settings built correctly for default toolbar.');
+    $this->assertIdenticalWithSafeStrings($expected, $this->ckeditor->buildToolbarJSSetting($editor), '"toolbar" configuration part of JS settings built correctly for default toolbar.');
 
     // Customize the configuration.
     $settings = $editor->getSettings();
@@ -224,7 +224,7 @@ function testBuildToolbarJSSetting() {
     $editor->setSettings($settings);
     $editor->save();
     $expected[0]['items'][] = 'Strike';
-    $this->assertIdentical($expected, $this->ckeditor->buildToolbarJSSetting($editor), '"toolbar" configuration part of JS settings built correctly for customized toolbar.');
+    $this->assertIdenticalWithSafeStrings($expected, $this->ckeditor->buildToolbarJSSetting($editor), '"toolbar" configuration part of JS settings built correctly for customized toolbar.');
 
     // Enable the editor_test module, customize further.
     $this->enableModules(array('ckeditor_test'));
@@ -236,7 +236,7 @@ function testBuildToolbarJSSetting() {
     $editor->save();
     $expected[0]['name'] = 'JunkScience';
     $expected[0]['items'][] = 'Llama';
-    $this->assertIdentical($expected, $this->ckeditor->buildToolbarJSSetting($editor), '"toolbar" configuration part of JS settings built correctly for customized toolbar with contrib module-provided CKEditor plugin.');
+    $this->assertIdenticalWithSafeStrings($expected, $this->ckeditor->buildToolbarJSSetting($editor), '"toolbar" configuration part of JS settings built correctly for customized toolbar with contrib module-provided CKEditor plugin.');
   }
 
   /**
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index 4945fe0..596542f 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -299,8 +299,11 @@ function comment_form_field_ui_field_storage_add_form_alter(&$form, FormStateInt
     $form['#title'] = \Drupal::service('comment.manager')->getFieldUIPageTitle($route_match->getParameter('commented_entity_type'), $route_match->getParameter('field_name'));
   }
   if (!_comment_entity_uses_integer_id($form_state->get('entity_type_id'))) {
+    // We cast the optgroup label to string as array keys must not be objects
+    // and t() will return a TranslationWrapper once issue #2557113 lands.
+    $optgroup = (string) t('General');
     // You cannot use comment fields on entity types with non-integer IDs.
-    unset($form['add']['new_storage_type']['#options'][t('General')]['comment']);
+    unset($form['add']['new_storage_type']['#options'][$optgroup]['comment']);
   }
 }
 
diff --git a/core/modules/config/src/Tests/ConfigImportRenameValidationTest.php b/core/modules/config/src/Tests/ConfigImportRenameValidationTest.php
index 7f0d72e..9cc8c65 100644
--- a/core/modules/config/src/Tests/ConfigImportRenameValidationTest.php
+++ b/core/modules/config/src/Tests/ConfigImportRenameValidationTest.php
@@ -112,7 +112,7 @@ public function testRenameValidation() {
       $expected = array(
         SafeMarkup::format('Entity type mismatch on rename. @old_type not equal to @new_type for existing configuration @old_name and staged configuration @new_name.', array('@old_type' => 'node_type', '@new_type' => 'config_test', '@old_name' => 'node.type.' . $content_type->id(), '@new_name' => 'config_test.dynamic.' . $test_entity_id))
       );
-      $this->assertIdentical($expected, $this->configImporter->getErrors());
+      $this->assertIdenticalWithSafeStrings($expected, $this->configImporter->getErrors());
     }
   }
 
@@ -155,7 +155,7 @@ public function testRenameSimpleConfigValidation() {
       $expected = array(
         SafeMarkup::format('Rename operation for simple configuration. Existing configuration @old_name and staged configuration @new_name.', array('@old_name' => 'config_test.old', '@new_name' => 'config_test.new'))
       );
-      $this->assertIdentical($expected, $this->configImporter->getErrors());
+      $this->assertIdenticalWithSafeStrings($expected, $this->configImporter->getErrors());
     }
   }
 
diff --git a/core/modules/entity_reference/entity_reference.module b/core/modules/entity_reference/entity_reference.module
index e206dee..908e0e5 100644
--- a/core/modules/entity_reference/entity_reference.module
+++ b/core/modules/entity_reference/entity_reference.module
@@ -126,10 +126,13 @@ function entity_reference_field_config_presave(FieldConfigInterface $field) {
  * Implements hook_form_FORM_ID_alter() for 'field_ui_field_storage_add_form'.
  */
 function entity_reference_form_field_ui_field_storage_add_form_alter(array &$form) {
+  // We cast the optgroup label to string as array keys must not be objects
+  // and t() may return a TranslationWrapper once issue #2557113 lands.
+  $optgroup = (string) t('Reference');
   // Move the "Entity reference" option to the end of the list and rename it to
   // "Other".
-  unset($form['add']['new_storage_type']['#options'][t('Reference')]['entity_reference']);
-  $form['add']['new_storage_type']['#options'][t('Reference')]['entity_reference'] = t('Other…');
+  unset($form['add']['new_storage_type']['#options'][$optgroup]['entity_reference']);
+  $form['add']['new_storage_type']['#options'][$optgroup]['entity_reference'] = t('Other…');
 }
 
 /**
diff --git a/core/modules/language/src/Form/NegotiationBrowserForm.php b/core/modules/language/src/Form/NegotiationBrowserForm.php
index bf0ce4e..111f35a 100644
--- a/core/modules/language/src/Form/NegotiationBrowserForm.php
+++ b/core/modules/language/src/Form/NegotiationBrowserForm.php
@@ -82,8 +82,10 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     }
     else {
       $language_options = array(
-        $this->t('Existing languages') => $existing_languages,
-        $this->t('Languages not yet added') => $this->languageManager->getStandardLanguageListWithoutConfigured(),
+        // We cast the optgroup labels to string as array keys must not be objects
+        // and t() may return a TranslationWrapper once issue #2557113 lands.
+        (string) $this->t('Existing languages') => $existing_languages,
+        (string) $this->t('Languages not yet added') => $this->languageManager->getStandardLanguageListWithoutConfigured(),
       );
     }
 
diff --git a/core/modules/locale/src/Form/ImportForm.php b/core/modules/locale/src/Form/ImportForm.php
index 1728879..86aecc3 100644
--- a/core/modules/locale/src/Form/ImportForm.php
+++ b/core/modules/locale/src/Form/ImportForm.php
@@ -94,8 +94,10 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     else {
       $default = key($existing_languages);
       $language_options = array(
-        $this->t('Existing languages') => $existing_languages,
-        $this->t('Languages not yet added') => $this->languageManager->getStandardLanguageListWithoutConfigured(),
+        // We cast the optgroup labels to string as array keys must not be objects
+        // and t() may return a TranslationWrapper once issue #2557113 lands.
+        (string) $this->t('Existing languages') => $existing_languages,
+        (string) $this->t('Languages not yet added') => $this->languageManager->getStandardLanguageListWithoutConfigured(),
       );
     }
 
diff --git a/core/modules/locale/src/Tests/LocaleImportFunctionalTest.php b/core/modules/locale/src/Tests/LocaleImportFunctionalTest.php
index 05af294..02e364b 100644
--- a/core/modules/locale/src/Tests/LocaleImportFunctionalTest.php
+++ b/core/modules/locale/src/Tests/LocaleImportFunctionalTest.php
@@ -238,8 +238,10 @@ public function testLanguageContext() {
       'langcode' => 'hr',
     ));
 
-    $this->assertIdentical(t('May', array(), array('langcode' => 'hr', 'context' => 'Long month name')), 'Svibanj', 'Long month name context is working.');
-    $this->assertIdentical(t('May', array(), array('langcode' => 'hr')), 'Svi.', 'Default context is working.');
+    // We cast the return value of t() to string so as to retrieve the translated
+    // value, rendered as a string.
+    $this->assertIdentical((string) t('May', array(), array('langcode' => 'hr', 'context' => 'Long month name')), 'Svibanj', 'Long month name context is working.');
+    $this->assertIdentical((string) t('May', array(), array('langcode' => 'hr')), 'Svi.', 'Default context is working.');
   }
 
   /**
@@ -254,7 +256,7 @@ public function testEmptyMsgstr() {
     ));
 
     $this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), 'The translation file was successfully imported.');
-    $this->assertIdentical(t('Operations', array(), array('langcode' => $langcode)), 'Műveletek', 'String imported and translated.');
+    $this->assertIdentical((string) t('Operations', array(), array('langcode' => $langcode)), 'Műveletek', 'String imported and translated.');
 
     // Try importing a .po file.
     $this->importPoFile($this->getPoFileWithEmptyMsgstr(), array(
diff --git a/core/modules/locale/src/Tests/LocaleTranslationUiTest.php b/core/modules/locale/src/Tests/LocaleTranslationUiTest.php
index 6c35b18..a708311 100644
--- a/core/modules/locale/src/Tests/LocaleTranslationUiTest.php
+++ b/core/modules/locale/src/Tests/LocaleTranslationUiTest.php
@@ -64,7 +64,7 @@ public function testStringTranslation() {
     );
     $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
     // Add string.
-    t($name, array(), array('langcode' => $langcode));
+    (string) t($name, array(), array('langcode' => $langcode));
     // Reset locale cache.
     $this->container->get('string_translation')->reset();
     $this->assertRaw('"edit-languages-' . $langcode . '-weight"', 'Language code found.');
@@ -302,7 +302,7 @@ public function testStringValidation() {
     );
     $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
     // Add string.
-    t($name, array(), array('langcode' => $langcode));
+    (string) t($name, array(), array('langcode' => $langcode));
     // Reset locale cache.
     $search = array(
       'string' => $name,
@@ -361,7 +361,7 @@ public function testStringSearch() {
     $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add custom language'));
 
     // Add string.
-    t($name, array(), array('langcode' => $langcode));
+    (string) t($name, array(), array('langcode' => $langcode));
     // Reset locale cache.
     $this->container->get('string_translation')->reset();
     $this->drupalLogout();
diff --git a/core/modules/migrate_drupal/src/Tests/dependencies/MigrateDependenciesTest.php b/core/modules/migrate_drupal/src/Tests/dependencies/MigrateDependenciesTest.php
index 59caa80..f2ab5f6 100644
--- a/core/modules/migrate_drupal/src/Tests/dependencies/MigrateDependenciesTest.php
+++ b/core/modules/migrate_drupal/src/Tests/dependencies/MigrateDependenciesTest.php
@@ -69,7 +69,7 @@ public function testAggregatorMigrateDependencies() {
     $executable = new MigrateExecutable($migration, $this);
     $this->startCollectingMessages();
     $executable->import();
-    $this->assertIdentical($this->migrateMessages['error'], array(SafeMarkup::format('Migration @id did not meet the requirements. Missing migrations d6_aggregator_feed. requirements: d6_aggregator_feed.', array('@id' => $migration->id()))));
+    $this->assertIdenticalWithSafeStrings($this->migrateMessages['error'], array(SafeMarkup::format('Migration @id did not meet the requirements. Missing migrations d6_aggregator_feed. requirements: d6_aggregator_feed.', array('@id' => $migration->id()))));
     $this->collectMessages = FALSE;
   }
 
diff --git a/core/modules/node/src/Tests/Migrate/d6/MigrateNodeBuilderTest.php b/core/modules/node/src/Tests/Migrate/d6/MigrateNodeBuilderTest.php
index 4a5483a..9e08361 100644
--- a/core/modules/node/src/Tests/Migrate/d6/MigrateNodeBuilderTest.php
+++ b/core/modules/node/src/Tests/Migrate/d6/MigrateNodeBuilderTest.php
@@ -34,8 +34,8 @@ class MigrateNodeBuilderTest extends MigrateDrupal6TestBase {
   protected function assertEntity($id, $label) {
     $migration = $this->builtMigrations[$id];
     $this->assertTrue($migration instanceof Migration);
-    $this->assertIdentical($id, $migration->Id());
-    $this->assertIdentical($label, $migration->label());
+    $this->assertIdenticalWithSafeStrings($id, $migration->id());
+    $this->assertIdenticalWithSafeStrings($label, $migration->label());
   }
 
   /**
diff --git a/core/modules/shortcut/src/Tests/ShortcutSetsTest.php b/core/modules/shortcut/src/Tests/ShortcutSetsTest.php
index bc3c5f9..b9c684c 100644
--- a/core/modules/shortcut/src/Tests/ShortcutSetsTest.php
+++ b/core/modules/shortcut/src/Tests/ShortcutSetsTest.php
@@ -73,7 +73,7 @@ function testShortcutSetEdit() {
     // Test the contents of each th cell.
     $expected_items = array(t('Name'), t('Weight'), t('Operations'));
     foreach ($elements as $key => $element) {
-      $this->assertIdentical((string) $element[0], $expected_items[$key]);
+      $this->assertIdenticalWithSafeStrings((string) $element[0], $expected_items[$key]);
     }
 
     // Look for test shortcuts in the table.
diff --git a/core/modules/simpletest/src/TestBase.php b/core/modules/simpletest/src/TestBase.php
index 62e4c9f..e72f4ca 100644
--- a/core/modules/simpletest/src/TestBase.php
+++ b/core/modules/simpletest/src/TestBase.php
@@ -750,6 +750,38 @@ protected function assertIdentical($first, $second, $message = '', $group = 'Oth
   }
 
   /**
+   * Runs $this->assertIdentical() after casting safe string objects to string.
+   *
+   * This helper function assists with comparison of two values where the actual
+   * value may include objects implementing SafeStringInterface such as
+   * TranslationWrappers, and we want to refer to these objects as strings in
+   * the expected value.
+   *
+   * @param $first
+   *   The first value to check.
+   * @param $second
+   *   The second value to check.
+   * @param $message
+   *   (optional) A message to display with the assertion. Do not translate
+   *   messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
+   *   variables in the message text, not t(). If left blank, a default message
+   *   will be displayed.
+   * @param $group
+   *   (optional) The group this message is in, which is displayed in a column
+   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
+   *   translate this string. Defaults to 'Other'; most tests do not override
+   *   this default.
+   *
+   * @return
+   *   TRUE if the assertion succeeded, FALSE otherwise.
+   *
+   */
+  protected function assertIdenticalWithSafeStrings($first, $second, $message = '', $group = 'Other') {
+    $this->castSafeStrings($first, $second);
+    $this->assertIdentical($first, $second, $message, $group);
+  }
+
+  /**
    * Check to see if two values are not identical.
    *
    * @param $first
diff --git a/core/modules/system/src/Tests/Common/FormatDateTest.php b/core/modules/system/src/Tests/Common/FormatDateTest.php
index 0ece458..aa357dd 100644
--- a/core/modules/system/src/Tests/Common/FormatDateTest.php
+++ b/core/modules/system/src/Tests/Common/FormatDateTest.php
@@ -88,10 +88,10 @@ function testAdminDefinedFormatDate() {
   function testFormatDate() {
     $timestamp = strtotime('2007-03-26T00:00:00+00:00');
     $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', 'en'), 'Sunday, 25-Mar-07 17:00:00 PDT', 'Test all parameters.');
-    $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), 'domingo, 25-Mar-07 17:00:00 PDT', 'Test translated format.');
-    $this->assertIdentical(format_date($timestamp, 'custom', '\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), 'l, 25-Mar-07 17:00:00 PDT', 'Test an escaped format string.');
-    $this->assertIdentical(format_date($timestamp, 'custom', '\\\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), '\\domingo, 25-Mar-07 17:00:00 PDT', 'Test format containing backslash character.');
-    $this->assertIdentical(format_date($timestamp, 'custom', '\\\\\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), '\\l, 25-Mar-07 17:00:00 PDT', 'Test format containing backslash followed by escaped format string.');
+    $this->assertIdenticalWithSafeStrings(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), 'domingo, 25-Mar-07 17:00:00 PDT', 'Test translated format.');
+    $this->assertIdenticalWithSafeStrings(format_date($timestamp, 'custom', '\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), 'l, 25-Mar-07 17:00:00 PDT', 'Test an escaped format string.');
+    $this->assertIdenticalWithSafeStrings(format_date($timestamp, 'custom', '\\\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), '\\domingo, 25-Mar-07 17:00:00 PDT', 'Test format containing backslash character.');
+    $this->assertIdenticalWithSafeStrings(format_date($timestamp, 'custom', '\\\\\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), '\\l, 25-Mar-07 17:00:00 PDT', 'Test format containing backslash followed by escaped format string.');
     $this->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'Europe/London', 'en'), 'Monday, 26-Mar-07 01:00:00 BST', 'Test a different time zone.');
 
     // Change the default language and timezone.
diff --git a/core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php b/core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php
index 1010b03..7ad44eb 100644
--- a/core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php
@@ -107,7 +107,7 @@ public function testEntityTypeUpdateWithoutData() {
         t('Update the %entity_type entity type.', array('%entity_type' => $this->entityManager->getDefinition('entity_test_update')->getLabel())),
       ),
     );
-    $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected); //, 'EntityDefinitionUpdateManager reports the expected change summary.');
+    $this->assertIdenticalWithSafeStrings($this->entityDefinitionUpdateManager->getChangeSummary(), $expected); //, 'EntityDefinitionUpdateManager reports the expected change summary.');
 
     // Run the update and ensure the revision table is created.
     $this->entityDefinitionUpdateManager->applyUpdates();
@@ -146,7 +146,7 @@ public function testBaseFieldCreateUpdateDeleteWithoutData() {
         t('Create the %field_name field.', array('%field_name' => t('A new base field'))),
       ),
     );
-    $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
+    $this->assertIdenticalWithSafeStrings($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
     $this->entityDefinitionUpdateManager->applyUpdates();
     $this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field'), 'Column created in shared table for new_base_field.');
 
@@ -159,7 +159,7 @@ public function testBaseFieldCreateUpdateDeleteWithoutData() {
         t('Update the %field_name field.', array('%field_name' => t('A new base field'))),
       ),
     );
-    $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
+    $this->assertIdenticalWithSafeStrings($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
     $this->entityDefinitionUpdateManager->applyUpdates();
     $this->assertTrue($this->database->schema()->indexExists('entity_test_update', 'entity_test_update_field__new_base_field'), 'Index created.');
 
@@ -172,7 +172,7 @@ public function testBaseFieldCreateUpdateDeleteWithoutData() {
         t('Update the %field_name field.', array('%field_name' => t('A new base field'))),
       ),
     );
-    $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
+    $this->assertIdenticalWithSafeStrings($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
     $this->entityDefinitionUpdateManager->applyUpdates();
     $this->assertFalse($this->database->schema()->indexExists('entity_test_update', 'entity_test_update_field__new_base_field'), 'Index deleted.');
 
@@ -186,7 +186,7 @@ public function testBaseFieldCreateUpdateDeleteWithoutData() {
         t('Update the %field_name field.', array('%field_name' => t('A new base field'))),
       ),
     );
-    $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
+    $this->assertIdenticalWithSafeStrings($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
     $this->entityDefinitionUpdateManager->applyUpdates();
     $this->assertFalse($this->database->schema()->fieldExists('entity_test_update', 'new_base_field'), 'Original column deleted in shared table for new_base_field.');
     $this->assertTrue($this->database->schema()->fieldExists('entity_test_update', 'new_base_field__value'), 'Value column created in shared table for new_base_field.');
@@ -201,7 +201,7 @@ public function testBaseFieldCreateUpdateDeleteWithoutData() {
         t('Delete the %field_name field.', array('%field_name' => t('A new base field'))),
       ),
     );
-    $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
+    $this->assertIdenticalWithSafeStrings($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
     $this->entityDefinitionUpdateManager->applyUpdates();
     $this->assertFalse($this->database->schema()->fieldExists('entity_test_update', 'new_base_field_value'), 'Value column deleted from shared table for new_base_field.');
     $this->assertFalse($this->database->schema()->fieldExists('entity_test_update', 'new_base_field_format'), 'Format column deleted from shared table for new_base_field.');
@@ -220,7 +220,7 @@ public function testBundleFieldCreateUpdateDeleteWithoutData() {
         t('Create the %field_name field.', array('%field_name' => t('A new bundle field'))),
       ),
     );
-    $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
+    $this->assertIdenticalWithSafeStrings($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
     $this->entityDefinitionUpdateManager->applyUpdates();
     $this->assertTrue($this->database->schema()->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table created for new_bundle_field.');
 
@@ -234,7 +234,7 @@ public function testBundleFieldCreateUpdateDeleteWithoutData() {
         t('Update the %field_name field.', array('%field_name' => t('A new bundle field'))),
       ),
     );
-    $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
+    $this->assertIdenticalWithSafeStrings($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
     $this->entityDefinitionUpdateManager->applyUpdates();
     $this->assertTrue($this->database->schema()->fieldExists('entity_test_update__new_bundle_field', 'new_bundle_field_format'), 'Format column created in dedicated table for new_base_field.');
 
@@ -247,7 +247,7 @@ public function testBundleFieldCreateUpdateDeleteWithoutData() {
         t('Delete the %field_name field.', array('%field_name' => t('A new bundle field'))),
       ),
     );
-    $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
+    $this->assertIdenticalWithSafeStrings($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
     $this->entityDefinitionUpdateManager->applyUpdates();
     $this->assertFalse($this->database->schema()->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table deleted for new_bundle_field.');
   }
@@ -274,7 +274,7 @@ public function testBaseFieldCreateDeleteWithExistingEntities() {
     $schema_handler = $this->database->schema();
     $this->assertTrue($schema_handler->fieldExists('entity_test_update', 'new_base_field'), 'Column created in shared table for new_base_field.');
     $entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id());
-    $this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field creation.');
+    $this->assertIdenticalWithSafeStrings($entity->name->value, $name, 'Entity data preserved during field creation.');
 
     // Remove the base field and run the update. Ensure the base field's column
     // is deleted and the prior saved entity data is still there.
@@ -282,7 +282,7 @@ public function testBaseFieldCreateDeleteWithExistingEntities() {
     $this->entityDefinitionUpdateManager->applyUpdates();
     $this->assertFalse($schema_handler->fieldExists('entity_test_update', 'new_base_field'), 'Column deleted from shared table for new_base_field.');
     $entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id());
-    $this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field deletion.');
+    $this->assertIdenticalWithSafeStrings($entity->name->value, $name, 'Entity data preserved during field deletion.');
 
     // Add a base field with a required property and run the update. Ensure
     // 'not null' is not applied and thus no exception is thrown.
@@ -331,7 +331,7 @@ public function testBundleFieldCreateDeleteWithExistingEntities() {
     $schema_handler = $this->database->schema();
     $this->assertTrue($schema_handler->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table created for new_bundle_field.');
     $entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id());
-    $this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field creation.');
+    $this->assertIdenticalWithSafeStrings($entity->name->value, $name, 'Entity data preserved during field creation.');
 
     // Remove the base field and run the update. Ensure the bundle field's
     // table is deleted and the prior saved entity data is still there.
@@ -339,7 +339,7 @@ public function testBundleFieldCreateDeleteWithExistingEntities() {
     $this->entityDefinitionUpdateManager->applyUpdates();
     $this->assertFalse($schema_handler->tableExists('entity_test_update__new_bundle_field'), 'Dedicated table deleted for new_bundle_field.');
     $entity = $this->entityManager->getStorage('entity_test_update')->load($entity->id());
-    $this->assertIdentical($entity->name->value, $name, 'Entity data preserved during field deletion.');
+    $this->assertIdenticalWithSafeStrings($entity->name->value, $name, 'Entity data preserved during field deletion.');
 
     // Test that required columns are created as 'not null'.
     $this->addBundleField('shape_required');
@@ -489,7 +489,7 @@ public function testEntityIndexCreateDeleteWithoutData() {
         t('Update the %entity_type entity type.', array('%entity_type' => $this->entityManager->getDefinition('entity_test_update')->getLabel())),
       ),
     );
-    $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
+    $this->assertIdenticalWithSafeStrings($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
 
     // Run the update and ensure the new index is created.
     $this->entityDefinitionUpdateManager->applyUpdates();
@@ -504,7 +504,7 @@ public function testEntityIndexCreateDeleteWithoutData() {
         t('Update the %entity_type entity type.', array('%entity_type' => $this->entityManager->getDefinition('entity_test_update')->getLabel())),
       ),
     );
-    $this->assertIdentical($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
+    $this->assertIdenticalWithSafeStrings($this->entityDefinitionUpdateManager->getChangeSummary(), $expected, 'EntityDefinitionUpdateManager reports the expected change summary.');
 
     // Run the update and ensure the index is deleted.
     $this->entityDefinitionUpdateManager->applyUpdates();
diff --git a/core/modules/system/src/Tests/Form/FormTest.php b/core/modules/system/src/Tests/Form/FormTest.php
index e89b78f..b02cbdc 100644
--- a/core/modules/system/src/Tests/Form/FormTest.php
+++ b/core/modules/system/src/Tests/Form/FormTest.php
@@ -144,7 +144,7 @@ function testRequiredFields() {
               // Select elements are going to have validation errors with empty
               // input, since those are illegal choices. Just make sure the
               // error is not "field is required".
-              $this->assertTrue((empty($errors[$element]) || strpos('field is required', $errors[$element]) === FALSE), "Optional '$type' field '$element' is not treated as a required element");
+              $this->assertTrue((empty($errors[$element]) || strpos('field is required', (string) $errors[$element]) === FALSE), "Optional '$type' field '$element' is not treated as a required element");
             }
             else {
               // Make sure there is *no* form error for this element.
diff --git a/core/modules/system/src/Tests/Form/ValidationTest.php b/core/modules/system/src/Tests/Form/ValidationTest.php
index 693d0af..2271d94 100644
--- a/core/modules/system/src/Tests/Form/ValidationTest.php
+++ b/core/modules/system/src/Tests/Form/ValidationTest.php
@@ -293,7 +293,7 @@ protected function assertErrorMessages($messages) {
         $this->fail(format_string('The error message for the "@title" element with key "@key" was not found.', ['@title' => $message['title'], '@key' => $message['key']]));
       }
       else {
-        $this->assertIdentical($message['message'], (string) $element[$delta]);
+        $this->assertIdenticalWithSafeStrings($message['message'], (string) $element[$delta]);
       }
 
       // Gather the element for checking the jump link section.
diff --git a/core/modules/system/tests/modules/plugin_test/src/Plugin/MockBlockManager.php b/core/modules/system/tests/modules/plugin_test/src/Plugin/MockBlockManager.php
index e435c0a..d4aeb4f 100644
--- a/core/modules/system/tests/modules/plugin_test/src/Plugin/MockBlockManager.php
+++ b/core/modules/system/tests/modules/plugin_test/src/Plugin/MockBlockManager.php
@@ -40,7 +40,7 @@ public function __construct() {
 
     // A simple plugin: the user login block.
     $this->discovery->setDefinition('user_login', array(
-      'label' => t('User login'),
+      'label' => 'User login',
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserLoginBlock',
     ));
 
@@ -67,7 +67,7 @@ public function __construct() {
     // MockLayoutBlockDeriver class ensures that both the base plugin and the
     // derivatives are available to the system.
     $this->discovery->setDefinition('layout', array(
-      'label' => t('Layout'),
+      'label' => 'Layout',
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockLayoutBlock',
       'deriver' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockLayoutBlockDeriver',
     ));
@@ -78,7 +78,7 @@ public function __construct() {
       'label' => t('User name'),
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserNameBlock',
       'context' => array(
-        'user' => new ContextDefinition('entity:user', t('User')),
+        'user' => new ContextDefinition('entity:user', 'User'),
       ),
     ));
 
@@ -87,7 +87,7 @@ public function __construct() {
       'label' => t('User name optional'),
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockUserNameBlock',
       'context' => array(
-        'user' => new ContextDefinition('entity:user', t('User'), FALSE),
+        'user' => new ContextDefinition('entity:user', 'User', FALSE),
       ),
     ));
 
@@ -102,8 +102,8 @@ public function __construct() {
       'label' => t('Complex context'),
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockComplexContextBlock',
       'context' => array(
-        'user' => new ContextDefinition('entity:user', t('User')),
-        'node' => new ContextDefinition('entity:node', t('Node')),
+        'user' => new ContextDefinition('entity:user', 'User'),
+        'node' => new ContextDefinition('entity:node', 'Node'),
       ),
     ));
 
diff --git a/core/modules/user/src/Tests/UserCancelTest.php b/core/modules/user/src/Tests/UserCancelTest.php
index 5357013..648f56f 100644
--- a/core/modules/user/src/Tests/UserCancelTest.php
+++ b/core/modules/user/src/Tests/UserCancelTest.php
@@ -535,7 +535,7 @@ function testMassUserCancelByAdmin() {
     $this->drupalPostForm(NULL, NULL, t('Cancel accounts'));
     $status = TRUE;
     foreach ($users as $account) {
-      $status = $status && (strpos($this->content, t('%name has been deleted.', array('%name' => $account->getUsername()))) !== FALSE);
+      $status = $status && (strpos($this->content,  $account->getUsername() . '</em> has been deleted.') !== FALSE);
       $user_storage->resetCache(array($account->id()));
       $status = $status && !$user_storage->load($account->id());
     }
diff --git a/core/modules/views/src/Plugin/Derivative/ViewsBlock.php b/core/modules/views/src/Plugin/Derivative/ViewsBlock.php
index 3ebfc5f..5a8714f 100644
--- a/core/modules/views/src/Plugin/Derivative/ViewsBlock.php
+++ b/core/modules/views/src/Plugin/Derivative/ViewsBlock.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Plugin\Discovery\ContainerDeriverInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Component\Utility\Xss;
 
 /**
  * Provides block plugin definitions for all Views block displays.
@@ -93,7 +94,8 @@ public function getDerivativeDefinitions($base_plugin_definition) {
 
           if (empty($desc)) {
             if ($display->display['display_title'] == $display->definition['title']) {
-              $desc = t('!view', array('!view' => $view->label()));
+              // @todo fix this, we should sanitize during rendering instead.
+              $desc = t('!view', array('!view' => Xss::filterAdmin($view->label())));
             }
             else {
               $desc = t('!view: !display', array('!view' => $view->label(), '!display' => $display->display['display_title']));
diff --git a/core/modules/views/src/Plugin/views/PluginBase.php b/core/modules/views/src/Plugin/views/PluginBase.php
index 49b7f1d..66661df 100644
--- a/core/modules/views/src/Plugin/views/PluginBase.php
+++ b/core/modules/views/src/Plugin/views/PluginBase.php
@@ -15,6 +15,7 @@
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\Plugin\PluginBase as ComponentPluginBase;
 use Drupal\Core\Render\Element;
+use Drupal\Core\StringTranslation\TranslationWrapper;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\ViewExecutable;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -552,7 +553,14 @@ protected function listLanguages($flags = LanguageInterface::STATE_ALL, array $c
     // Since this is not a real language, surround it by '***LANGUAGE_...***',
     // like the negotiated languages below.
     if ($flags & LanguageInterface::STATE_SITE_DEFAULT) {
-      $list[PluginBase::VIEWS_QUERY_LANGUAGE_SITE_DEFAULT] = $this->t($languages[LanguageInterface::LANGCODE_SITE_DEFAULT]->getName());
+      $name = $languages[LanguageInterface::LANGCODE_SITE_DEFAULT]->getName();
+      // The language name may have already been translated, no need to
+      // translate it again.
+      // @see Drupal\Core\Language::filterLanguages().
+      if (!$name instanceof TranslationWrapper) {
+        $name = $this->t($name);
+      }
+      $list[PluginBase::VIEWS_QUERY_LANGUAGE_SITE_DEFAULT] = $name;
       // Remove site default language from $languages so it's not added
       // twice with the real languages below.
       unset($languages[LanguageInterface::LANGCODE_SITE_DEFAULT]);
diff --git a/core/modules/views/src/Plugin/views/area/TokenizeAreaPluginBase.php b/core/modules/views/src/Plugin/views/area/TokenizeAreaPluginBase.php
index 82bb51d..7c26932 100644
--- a/core/modules/views/src/Plugin/views/area/TokenizeAreaPluginBase.php
+++ b/core/modules/views/src/Plugin/views/area/TokenizeAreaPluginBase.php
@@ -52,14 +52,18 @@ public function tokenForm(&$form, FormStateInterface $form_state) {
 
     // Get a list of the available fields and arguments for token replacement.
     $options = array();
+    // We cast the optgroup labels to string as array keys must not be objects
+    // and t() may return a TranslationWrapper once issue #2557113 lands.
+    $optgroup_arguments = (string) t('Arguments');
+    $optgroup_fields = (string) t('Fields');
     foreach ($this->view->display_handler->getHandlers('field') as $field => $handler) {
-      $options[t('Fields')]["[$field]"] = $handler->adminLabel();
+      $options[$optgroup_fields]["[$field]"] = $handler->adminLabel();
     }
 
     $count = 0; // This lets us prepare the key as we want it printed.
     foreach ($this->view->display_handler->getHandlers('argument') as $handler) {
-      $options[t('Arguments')]['%' . ++$count] = $this->t('@argument title', array('@argument' => $handler->adminLabel()));
-      $options[t('Arguments')]['!' . $count] = $this->t('@argument input', array('@argument' => $handler->adminLabel()));
+      $options[$optgroup_arguments]['%' . ++$count] = $this->t('@argument title', array('@argument' => $handler->adminLabel()));
+      $options[$optgroup_arguments]['!' . $count] = $this->t('@argument input', array('@argument' => $handler->adminLabel()));
     }
 
     if (!empty($options)) {
diff --git a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
index 35e9b92..4ed8f55 100644
--- a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
+++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
@@ -1726,9 +1726,13 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
 
         $options = array();
         $count = 0; // This lets us prepare the key as we want it printed.
+        // We cast the optgroup label to string as array keys must not be
+        // objects and t() may return a TranslationWrapper once issue #2557113
+        // lands.
+        $optgroup_arguments = (string) t('Arguments');
         foreach ($this->view->display_handler->getHandlers('argument') as $handler) {
-          $options[t('Arguments')]['%' . ++$count] = $this->t('@argument title', array('@argument' => $handler->adminLabel()));
-          $options[t('Arguments')]['!' . $count] = $this->t('@argument input', array('@argument' => $handler->adminLabel()));
+          $options[$optgroup_arguments]['%' . ++$count] = $this->t('@argument title', array('@argument' => $handler->adminLabel()));
+          $options[$optgroup_arguments]['!' . $count] = $this->t('@argument input', array('@argument' => $handler->adminLabel()));
         }
 
         // Default text.
diff --git a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
index 152d4e9..baa11ab 100644
--- a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
+++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
@@ -862,19 +862,23 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
 
       // Setup the tokens for fields.
       $previous = $this->getPreviousFieldLabels();
+      // We cast the optgroup labels to string as array keys must not be objects
+      // and t() may return a TranslationWrapper once issue #2557113 lands.
+      $optgroup_arguments = (string) t('Arguments');
+      $optgroup_fields = (string) t('Fields');
       foreach ($previous as $id => $label) {
-        $options[t('Fields')]["{{ $id }}"] = substr(strrchr($label, ":"), 2 );
+        $options[$optgroup_fields]["{{ $id }}"] = substr(strrchr($label, ":"), 2 );
       }
       // Add the field to the list of options.
-      $options[t('Fields')]["{{ {$this->options['id']} }}"] = substr(strrchr($this->adminLabel(), ":"), 2 );
+      $options[$optgroup_fields]["{{ {$this->options['id']} }}"] = substr(strrchr($this->adminLabel(), ":"), 2 );
 
       $count = 0; // This lets us prepare the key as we want it printed.
       foreach ($this->view->display_handler->getHandlers('argument') as $arg => $handler) {
-        $options[t('Arguments')]['%' . ++$count] = $this->t('@argument title', array('@argument' => $handler->adminLabel()));
-        $options[t('Arguments')]['!' . $count] = $this->t('@argument input', array('@argument' => $handler->adminLabel()));
+        $options[$optgroup_arguments]['%' . ++$count] = $this->t('@argument title', array('@argument' => $handler->adminLabel()));
+        $options[$optgroup_arguments]['!' . $count] = $this->t('@argument input', array('@argument' => $handler->adminLabel()));
       }
 
-      $this->documentSelfTokens($options[t('Fields')]);
+      $this->documentSelfTokens($options[$optgroup_fields]);
 
       // Default text.
 
diff --git a/core/modules/views/src/Tests/ModuleTest.php b/core/modules/views/src/Tests/ModuleTest.php
index 7d5a31b..7adb05f 100644
--- a/core/modules/views/src/Tests/ModuleTest.php
+++ b/core/modules/views/src/Tests/ModuleTest.php
@@ -157,40 +157,40 @@ public function testLoadFunctions() {
     $all_views = $storage->loadMultiple();
 
     // Test Views::getAllViews().
-    $this->assertIdentical(array_keys($all_views), array_keys(Views::getAllViews()), 'Views::getAllViews works as expected.');
+    $this->assertIdenticalWithSafeStrings(array_keys($all_views), array_keys(Views::getAllViews()), 'Views::getAllViews works as expected.');
 
     // Test Views::getEnabledViews().
     $expected_enabled = array_filter($all_views, function($view) {
       return views_view_is_enabled($view);
     });
-    $this->assertIdentical(array_keys($expected_enabled), array_keys(Views::getEnabledViews()), 'Expected enabled views returned.');
+    $this->assertIdenticalWithSafeStrings(array_keys($expected_enabled), array_keys(Views::getEnabledViews()), 'Expected enabled views returned.');
 
     // Test Views::getDisabledViews().
     $expected_disabled = array_filter($all_views, function($view) {
       return views_view_is_disabled($view);
     });
-    $this->assertIdentical(array_keys($expected_disabled), array_keys(Views::getDisabledViews()), 'Expected disabled views returned.');
+    $this->assertIdenticalWithSafeStrings(array_keys($expected_disabled), array_keys(Views::getDisabledViews()), 'Expected disabled views returned.');
 
     // Test Views::getViewsAsOptions().
     // Test the $views_only parameter.
-    $this->assertIdentical(array_keys($all_views), array_keys(Views::getViewsAsOptions(TRUE)), 'Expected option keys for all views were returned.');
+    $this->assertIdenticalWithSafeStrings(array_keys($all_views), array_keys(Views::getViewsAsOptions(TRUE)), 'Expected option keys for all views were returned.');
     $expected_options = array();
     foreach ($all_views as $id => $view) {
       $expected_options[$id] = $view->label();
     }
-    $this->assertIdentical($expected_options, Views::getViewsAsOptions(TRUE), 'Expected options array was returned.');
+    $this->assertIdenticalWithSafeStrings($expected_options, Views::getViewsAsOptions(TRUE), 'Expected options array was returned.');
 
     // Test the default.
-    $this->assertIdentical($this->formatViewOptions($all_views), Views::getViewsAsOptions(), 'Expected options array for all views was returned.');
+    $this->assertIdenticalWithSafeStrings($this->formatViewOptions($all_views), Views::getViewsAsOptions(), 'Expected options array for all views was returned.');
     // Test enabled views.
-    $this->assertIdentical($this->formatViewOptions($expected_enabled), Views::getViewsAsOptions(FALSE, 'enabled'), 'Expected enabled options array was returned.');
+    $this->assertIdenticalWithSafeStrings($this->formatViewOptions($expected_enabled), Views::getViewsAsOptions(FALSE, 'enabled'), 'Expected enabled options array was returned.');
     // Test disabled views.
-    $this->assertIdentical($this->formatViewOptions($expected_disabled), Views::getViewsAsOptions(FALSE, 'disabled'), 'Expected disabled options array was returned.');
+    $this->assertIdenticalWithSafeStrings($this->formatViewOptions($expected_disabled), Views::getViewsAsOptions(FALSE, 'disabled'), 'Expected disabled options array was returned.');
 
     // Test the sort parameter.
     $all_views_sorted = $all_views;
     ksort($all_views_sorted);
-    $this->assertIdentical(array_keys($all_views_sorted), array_keys(Views::getViewsAsOptions(TRUE, 'all', NULL, FALSE, TRUE)), 'All view id keys returned in expected sort order');
+    $this->assertIdenticalWithSafeStrings(array_keys($all_views_sorted), array_keys(Views::getViewsAsOptions(TRUE, 'all', NULL, FALSE, TRUE)), 'All view id keys returned in expected sort order');
 
     // Test $exclude_view parameter.
     $this->assertFalse(array_key_exists('archive', Views::getViewsAsOptions(TRUE, 'all', 'archive')), 'View excluded from options based on name');
@@ -204,7 +204,7 @@ public function testLoadFunctions() {
           $expected_opt_groups[$view->id()][$view->id() . ':' . $display['id']] = t('@view : @display', array('@view' => $view->id(), '@display' => $display['id']));
       }
     }
-    $this->assertIdentical($expected_opt_groups, Views::getViewsAsOptions(FALSE, 'all', NULL, TRUE), 'Expected option array for an option group returned.');
+    $this->assertIdenticalWithSafeStrings($expected_opt_groups, Views::getViewsAsOptions(FALSE, 'all', NULL, TRUE), 'Expected option array for an option group returned.');
   }
 
   /**
@@ -241,7 +241,7 @@ public function testViewsFetchPluginNames() {
     // Test using the 'test' style plugin type only returns the test_style and
     // mapping_test plugins.
     $plugins = Views::fetchPluginNames('style', 'test');
-    $this->assertIdentical(array_keys($plugins), array('mapping_test', 'test_style', 'test_template_style'));
+    $this->assertIdenticalWithSafeStrings(array_keys($plugins), array('mapping_test', 'test_style', 'test_template_style'));
 
     // Test a non existent style plugin type returns no plugins.
     $plugins = Views::fetchPluginNames('style', $this->randomString());
diff --git a/core/modules/views_ui/admin.inc b/core/modules/views_ui/admin.inc
index 78be0fc..cbb0782 100644
--- a/core/modules/views_ui/admin.inc
+++ b/core/modules/views_ui/admin.inc
@@ -92,8 +92,9 @@ function views_ui_add_ajax_trigger(&$wrapping_element, $trigger_key, $refresh_pa
   }
   // For easiest integration with the form API and the testing framework, we
   // always give the button a unique #value, rather than playing around with
-  // #name.
-  $button_title = !empty($triggering_element['#title']) ? $triggering_element['#title'] : $trigger_key;
+  // #name. We also cast the #title to string as we will use it as an array
+  // key and it may be a TranslationWrapper.
+  $button_title = !empty($triggering_element['#title']) ? (string) $triggering_element['#title'] : $trigger_key;
   if (empty($seen_buttons[$button_title])) {
     $wrapping_element[$button_key]['#value'] = t('Update "@title" choice', array(
       '@title' => $button_title,
diff --git a/core/tests/Drupal/Tests/Core/Annotation/TranslationTest.php b/core/tests/Drupal/Tests/Core/Annotation/TranslationTest.php
index be8cca3..df3ff1a 100644
--- a/core/tests/Drupal/Tests/Core/Annotation/TranslationTest.php
+++ b/core/tests/Drupal/Tests/Core/Annotation/TranslationTest.php
@@ -46,8 +46,8 @@ public function testGet(array $values, $expected) {
       'context' => $values['context'],
     ) : array();
     $this->translationManager->expects($this->once())
-      ->method('translate')
-      ->with($values['value'], $arguments, $options);
+      ->method('doTranslate')
+      ->with($values['value'], $options);
 
     $annotation = new Translation($values);
 
@@ -61,9 +61,9 @@ public function providerTestGet() {
     $data = array();
     $data[] = array(
       array(
-        'value' => 'Foo',
+        'value' => 'Foo'
       ),
-      'Foo'
+      'Foo',
     );
     $random = $this->randomMachineName();
     $random_html_entity = '&' . $random;
diff --git a/core/tests/Drupal/Tests/Core/Menu/ContextualLinkDefaultTest.php b/core/tests/Drupal/Tests/Core/Menu/ContextualLinkDefaultTest.php
index ac92088..14758e1 100644
--- a/core/tests/Drupal/Tests/Core/Menu/ContextualLinkDefaultTest.php
+++ b/core/tests/Drupal/Tests/Core/Menu/ContextualLinkDefaultTest.php
@@ -74,8 +74,8 @@ public function testGetTitle() {
     $this->pluginDefinition['title'] = (new TranslationWrapper($title))
       ->setStringTranslation($this->stringTranslation);
     $this->stringTranslation->expects($this->once())
-      ->method('translate')
-      ->with($title, array(), array())
+      ->method('doTranslate')
+      ->with($title, array())
       ->will($this->returnValue('Example translated'));
 
     $this->setupContextualLinkDefault();
@@ -90,8 +90,8 @@ public function testGetTitleWithContext() {
     $this->pluginDefinition['title'] = (new TranslationWrapper($title, array(), array('context' => 'context')))
       ->setStringTranslation($this->stringTranslation);
     $this->stringTranslation->expects($this->once())
-      ->method('translate')
-      ->with($title, array(), array('context' => 'context'))
+      ->method('doTranslate')
+      ->with($title, array('context' => 'context'))
       ->will($this->returnValue('Example translated with context'));
 
     $this->setupContextualLinkDefault();
@@ -106,8 +106,8 @@ public function testGetTitleWithTitleArguments() {
     $this->pluginDefinition['title'] = (new TranslationWrapper($title, array('@test' => 'value')))
       ->setStringTranslation($this->stringTranslation);
     $this->stringTranslation->expects($this->once())
-      ->method('translate')
-      ->with($title, array('@test' => 'value'), array())
+      ->method('doTranslate')
+      ->with($title, array())
       ->will($this->returnValue('Example value'));
 
     $this->setupContextualLinkDefault();
diff --git a/core/tests/Drupal/Tests/Core/Menu/LocalActionDefaultTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalActionDefaultTest.php
index 02d4578..105ae5e 100644
--- a/core/tests/Drupal/Tests/Core/Menu/LocalActionDefaultTest.php
+++ b/core/tests/Drupal/Tests/Core/Menu/LocalActionDefaultTest.php
@@ -86,8 +86,8 @@ public function testGetTitle() {
     $this->pluginDefinition['title'] = (new TranslationWrapper('Example'))
       ->setStringTranslation($this->stringTranslation);
     $this->stringTranslation->expects($this->once())
-      ->method('translate')
-      ->with('Example', array(), array())
+      ->method('doTranslate')
+      ->with('Example', array())
       ->will($this->returnValue('Example translated'));
 
     $this->setupLocalActionDefault();
@@ -103,8 +103,8 @@ public function testGetTitleWithContext() {
     $this->pluginDefinition['title'] = (new TranslationWrapper('Example', array(), array('context' => 'context')))
       ->setStringTranslation($this->stringTranslation);
     $this->stringTranslation->expects($this->once())
-      ->method('translate')
-      ->with('Example', array(), array('context' => 'context'))
+      ->method('doTranslate')
+      ->with('Example', array('context' => 'context'))
       ->will($this->returnValue('Example translated with context'));
 
     $this->setupLocalActionDefault();
@@ -118,8 +118,8 @@ public function testGetTitleWithTitleArguments() {
     $this->pluginDefinition['title'] = (new TranslationWrapper('Example @test', array('@test' => 'value')))
       ->setStringTranslation($this->stringTranslation);
     $this->stringTranslation->expects($this->once())
-      ->method('translate')
-      ->with('Example @test', array('@test' => 'value'), array())
+      ->method('doTranslate')
+      ->with('Example @test', array())
       ->will($this->returnValue('Example value'));
 
     $this->setupLocalActionDefault();
diff --git a/core/tests/Drupal/Tests/Core/Menu/LocalTaskDefaultTest.php b/core/tests/Drupal/Tests/Core/Menu/LocalTaskDefaultTest.php
index 6e393a8..2420b37 100644
--- a/core/tests/Drupal/Tests/Core/Menu/LocalTaskDefaultTest.php
+++ b/core/tests/Drupal/Tests/Core/Menu/LocalTaskDefaultTest.php
@@ -236,8 +236,8 @@ public function testGetTitle() {
     $this->pluginDefinition['title'] = (new TranslationWrapper('Example'))
       ->setStringTranslation($this->stringTranslation);
     $this->stringTranslation->expects($this->once())
-      ->method('translate')
-      ->with('Example', array(), array())
+      ->method('doTranslate')
+      ->with('Example', array())
       ->will($this->returnValue('Example translated'));
 
     $this->setupLocalTaskDefault();
@@ -252,8 +252,8 @@ public function testGetTitleWithContext() {
     $this->pluginDefinition['title'] = (new TranslationWrapper($title, array(), array('context' => 'context')))
       ->setStringTranslation($this->stringTranslation);
     $this->stringTranslation->expects($this->once())
-      ->method('translate')
-      ->with($title, array(), array('context' => 'context'))
+      ->method('doTranslate')
+      ->with($title, array('context' => 'context'))
       ->will($this->returnValue('Example translated with context'));
 
     $this->setupLocalTaskDefault();
@@ -268,8 +268,8 @@ public function testGetTitleWithTitleArguments() {
     $this->pluginDefinition['title'] = (new TranslationWrapper('Example @test', array('@test' => 'value')))
       ->setStringTranslation($this->stringTranslation);
     $this->stringTranslation->expects($this->once())
-      ->method('translate')
-      ->with($title, array('@test' => 'value'), array())
+      ->method('doTranslate')
+      ->with($title, array())
       ->will($this->returnValue('Example value'));
 
     $this->setupLocalTaskDefault();
diff --git a/core/tests/Drupal/Tests/Core/StringTranslation/TranslationWrapperTest.php b/core/tests/Drupal/Tests/Core/StringTranslation/TranslationWrapperTest.php
index 3facf38..b6cdc09 100644
--- a/core/tests/Drupal/Tests/Core/StringTranslation/TranslationWrapperTest.php
+++ b/core/tests/Drupal/Tests/Core/StringTranslation/TranslationWrapperTest.php
@@ -66,7 +66,7 @@ public function testToString() {
       ->willReturn('');
 
     $translation = $this->prophesize(TranslationInterface::class);
-    $translation->translate($string, [], [])->will(function () {
+    $translation->doTranslate($string, [])->will(function () {
       throw new \Exception('Yes you may.');
     });
     $text->setStringTranslation($translation->reveal());
diff --git a/core/tests/Drupal/Tests/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidatorTest.php b/core/tests/Drupal/Tests/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidatorTest.php
index 2bcb318..0707fa3 100644
--- a/core/tests/Drupal/Tests/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidatorTest.php
+++ b/core/tests/Drupal/Tests/Core/Validation/Plugin/Validation/Constraint/PrimitiveTypeConstraintValidatorTest.php
@@ -16,6 +16,7 @@
 use Drupal\Core\TypedData\PrimitiveInterface;
 use Drupal\Core\Validation\Plugin\Validation\Constraint\PrimitiveTypeConstraint;
 use Drupal\Core\Validation\Plugin\Validation\Constraint\PrimitiveTypeConstraintValidator;
+use Drupal\Core\StringTranslation\TranslationWrapper;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -63,6 +64,7 @@ public function provideTestValidate() {
     $data[] = [new IntegerData(DataDefinition::create('integer')), 1.5, FALSE];
     $data[] = [new IntegerData(DataDefinition::create('integer')), 'test', FALSE];
     $data[] = [new StringData(DataDefinition::create('string')), 'test', TRUE];
+    $data[] = [new StringData(DataDefinition::create('string')), new TranslationWrapper('test'), TRUE];
     // It is odd that 1 is a valid string.
     // $data[] = [$this->getMock('Drupal\Core\TypedData\Type\StringInterface'), 1, FALSE];
     $data[] = [new StringData(DataDefinition::create('string')), [], FALSE];
diff --git a/core/tests/Drupal/Tests/UnitTestCase.php b/core/tests/Drupal/Tests/UnitTestCase.php
index 3433c94..5819c39 100644
--- a/core/tests/Drupal/Tests/UnitTestCase.php
+++ b/core/tests/Drupal/Tests/UnitTestCase.php
@@ -220,6 +220,11 @@ public function getStringTranslationStub() {
       ->willReturnCallback(function ($count, $singular, $plural, array $args = [], array $options = []) {
         return $count === 1 ? SafeMarkup::format($singular, $args) : SafeMarkup::format($plural, $args + ['@count' => $count]);
       });
+    $translation->expects($this->any())
+      ->method('doTranslate')
+      ->willReturnCallback(function ($string, array $options = []) {
+        return $string;
+      });
     return $translation;
   }
 
