diff --git a/core/lib/Drupal/Component/Utility/Html.php b/core/lib/Drupal/Component/Utility/Html.php
index a775cdb..a88555d 100644
--- a/core/lib/Drupal/Component/Utility/Html.php
+++ b/core/lib/Drupal/Component/Utility/Html.php
@@ -54,10 +54,10 @@ class Html {
    *   The cleaned class name.
    */
   public static function getClass($class) {
-    if (!isset(static::$classes[$class])) {
-      static::$classes[$class] = static::cleanCssIdentifier(Unicode::strtolower($class));
+    if (!isset(static::$classes[(string) $class])) {
+      static::$classes[(string) $class] = static::cleanCssIdentifier(Unicode::strtolower($class));
     }
-    return static::$classes[$class];
+    return static::$classes[(string) $class];
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Cache/CacheCollector.php b/core/lib/Drupal/Core/Cache/CacheCollector.php
index a6d8ab5..17ac7fb 100644
--- a/core/lib/Drupal/Core/Cache/CacheCollector.php
+++ b/core/lib/Drupal/Core/Cache/CacheCollector.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Cache;
 
+use Drupal\Component\Utility\SafeStringInterface;
 use Drupal\Core\DestructableInterface;
 use Drupal\Core\Lock\LockBackendInterface;
 
@@ -145,6 +146,10 @@ public function has($key) {
    */
   public function get($key) {
     $this->lazyLoadCache();
+    // @todo fix this
+    if ($key instanceof SafeStringInterface) {
+      $key = (string) $key;
+    }
     if (isset($this->storage[$key]) || array_key_exists($key, $this->storage)) {
       return $this->storage[$key];
     }
diff --git a/core/lib/Drupal/Core/Config/StorableConfigBase.php b/core/lib/Drupal/Core/Config/StorableConfigBase.php
index a0ad7a2..6c3ca60 100644
--- a/core/lib/Drupal/Core/Config/StorableConfigBase.php
+++ b/core/lib/Drupal/Core/Config/StorableConfigBase.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Config;
 
+use Drupal\Component\Utility\SafeStringInterface;
 use Drupal\Core\Config\Schema\Ignore;
 use Drupal\Core\TypedData\PrimitiveInterface;
 use Drupal\Core\TypedData\Type\FloatInterface;
@@ -189,6 +190,10 @@ protected function castValue($key, $value) {
       $this->validateValue($key, $value);
       return $value;
     }
+    // @todo fix this
+    if ($value instanceof SafeStringInterface) {
+      $value = (string) $value;
+    }
     if (is_scalar($value) || $value === NULL) {
       if ($element && $element instanceof PrimitiveInterface) {
         // Special handling for integers and floats since the configuration
diff --git a/core/lib/Drupal/Core/Database/Install/Tasks.php b/core/lib/Drupal/Core/Database/Install/Tasks.php
index fed2c5e..c82a560 100644
--- a/core/lib/Drupal/Core/Database/Install/Tasks.php
+++ b/core/lib/Drupal/Core/Database/Install/Tasks.php
@@ -92,14 +92,16 @@ protected function hasPdoDriver() {
    * Assert test as failed.
    */
   protected function fail($message) {
-    $this->results[$message] = FALSE;
+    // @todo fix this
+    $this->results[(string) $message] = FALSE;
   }
 
   /**
    * Assert test as a pass.
    */
   protected function pass($message) {
-    $this->results[$message] = TRUE;
+    // @todo fix this
+    $this->results[(string) $message] = TRUE;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php
index 351361f..80f6fd3 100644
--- a/core/lib/Drupal/Core/Entity/EntityManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityManager.php
@@ -945,7 +945,7 @@ public function getEntityTypeLabels($group = FALSE) {
 
     foreach ($definitions as $entity_type_id => $definition) {
       if ($group) {
-        $options[$definition->getGroupLabel()][$entity_type_id] = $definition->getLabel();
+        $options[(string) $definition->getGroupLabel()][$entity_type_id] = $definition->getLabel();
       }
       else {
         $options[$entity_type_id] = $definition->getLabel();
@@ -959,7 +959,7 @@ 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'));
+      $content = (string) $this->t('Content', array(), array('context' => 'Entity type group'));
       $options = array($content => $options[$content]) + $options;
     }
 
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/BooleanItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/BooleanItem.php
index 7107436..cff8ab3 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/BooleanItem.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/BooleanItem.php
@@ -33,8 +33,8 @@ class BooleanItem extends FieldItemBase implements OptionsProviderInterface {
    */
   public static function defaultFieldSettings() {
     return array(
-      'on_label' => t('On'),
-      'off_label' => t('Off'),
+      'on_label' => 'On',
+      'off_label' => 'Off',
     ) + parent::defaultFieldSettings();
   }
 
@@ -72,13 +72,13 @@ public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
     $element['on_label'] = array(
       '#type' => 'textfield',
       '#title' => $this->t('"On" label'),
-      '#default_value' => $this->getSetting('on_label'),
+      '#default_value' => $this->t($this->getSetting('on_label')),
       '#required' => TRUE,
     );
     $element['off_label'] = array(
       '#type' => 'textfield',
       '#title' => $this->t('"Off" label'),
-      '#default_value' => $this->getSetting('off_label'),
+      '#default_value' => $this->t($this->getSetting('off_label')),
       '#required' => TRUE,
     );
 
@@ -97,8 +97,8 @@ public function getPossibleValues(AccountInterface $account = NULL) {
    */
   public function getPossibleOptions(AccountInterface $account = NULL) {
     return array(
-      0 => $this->getSetting('off_label'),
-      1 => $this->getSetting('on_label'),
+      0 => $this->t($this->getSetting('off_label')),
+      1 => $this->t($this->getSetting('on_label')),
     );
   }
 
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 6bf591a..1c1a77d 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;
 
 /**
@@ -18,7 +20,7 @@
  *
  * @see \Drupal\Core\Annotation\Translation
  */
-class TranslationWrapper implements SafeStringInterface {
+class TranslationWrapper implements SafeStringInterface, \JsonSerializable {
   use StringTranslationTrait;
 
   /**
@@ -98,7 +100,7 @@ public function getOptions() {
    * Implements the magic __toString() method.
    */
   public function __toString() {
-    return $this->render();
+    return (string) $this->render();
   }
 
   /**
@@ -108,7 +110,35 @@ public function __toString() {
    *   The translated string.
    */
   public function render() {
-    return $this->t($this->string, $this->arguments, $this->options);
+    $string = $this->getStringTranslation()->doTranslate($this->string, $this->options);
+    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;
   }
 
   /**
@@ -118,4 +148,8 @@ public function __sleep() {
     return array('string', 'arguments', 'options');
   }
 
+  public function jsonSerialize() {
+    return $this->render();
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Template/Attribute.php b/core/lib/Drupal/Core/Template/Attribute.php
index 8fe6788..687f026 100644
--- a/core/lib/Drupal/Core/Template/Attribute.php
+++ b/core/lib/Drupal/Core/Template/Attribute.php
@@ -109,7 +109,7 @@ protected function createAttributeValue($name, $value) {
     elseif (is_bool($value)) {
       $value = new AttributeBoolean($name, $value);
     }
-    elseif (!is_object($value)) {
+    elseif (!is_object($value) || $value instanceof SafeStringInterface) {
       $value = new AttributeString($name, $value);
     }
     return $value;
diff --git a/core/modules/ckeditor/src/Tests/CKEditorAdminTest.php b/core/modules/ckeditor/src/Tests/CKEditorAdminTest.php
index ca1954f..80598b0 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' => (string) t('Formatting'),
               'items' => array('Bold', 'Italic',),
             ),
             array(
-              'name' => t('Links'),
+              'name' => (string) t('Links'),
               'items' => array('DrupalLink', 'DrupalUnlink',),
             ),
             array(
-              'name' => t('Lists'),
+              'name' => (string) t('Lists'),
               'items' => array('BulletedList', 'NumberedList',),
             ),
             array(
-              'name' => t('Media'),
+              'name' => (string) t('Media'),
               'items' => array('Blockquote', 'DrupalImage',),
             ),
             array(
-              'name' => t('Tools'),
+              'name' => (string) t('Tools'),
               'items' => array('Source',),
             ),
           ),
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index 20ba4a3..614a7ff 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -299,7 +299,7 @@ function comment_form_field_ui_field_storage_add_form_alter(&$form, FormStateInt
   }
   if (!_comment_entity_uses_integer_id($form_state->get('entity_type_id'))) {
     // 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'][(string) t('General')]['comment']);
   }
 }
 
diff --git a/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php b/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php
index f220d1a..83adb6f 100644
--- a/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php
+++ b/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php
@@ -585,7 +585,7 @@ public function testViewsTranslationUI() {
     $response = $this->renderContextualLinks($ids, 'node');
     $this->assertResponse(200);
     $json = Json::decode($response);
-    $this->assertTrue(strpos($json[$ids[0]], t('Translate view')), 'Translate view contextual link added.');
+    $this->assertTrue(strpos($json[$ids[0]], 'Translate view', 'Translate view contextual link added.');
 
     $description = 'All content promoted to the front page.';
     $human_readable_name = 'Frontpage';
diff --git a/core/modules/contact/src/Tests/ContactPersonalTest.php b/core/modules/contact/src/Tests/ContactPersonalTest.php
index e522f09..031ee75 100644
--- a/core/modules/contact/src/Tests/ContactPersonalTest.php
+++ b/core/modules/contact/src/Tests/ContactPersonalTest.php
@@ -84,7 +84,7 @@ function testSendPersonalContactMessage() {
       '!recipient-name' => $this->contactUser->getUsername(),
     );
     $this->assertEqual($mail['subject'], t('[!site-name] !subject', $variables), 'Subject is in sent message.');
-    $this->assertTrue(strpos($mail['body'], t('Hello !recipient-name,', $variables)) !== FALSE, 'Recipient name is in sent message.');
+    $this->assertTrue(strpos($mail['body'], 'Hello ' . $variables['!recipient-name']) !== FALSE, 'Recipient name is in sent message.');
     $this->assertTrue(strpos($mail['body'], $this->webUser->getUsername()) !== FALSE, 'Sender name is in sent message.');
     $this->assertTrue(strpos($mail['body'], $message['message[0][value]']) !== FALSE, 'Message body is in sent message.');
 
diff --git a/core/modules/entity_reference/entity_reference.module b/core/modules/entity_reference/entity_reference.module
index e206dee..51baab3 100644
--- a/core/modules/entity_reference/entity_reference.module
+++ b/core/modules/entity_reference/entity_reference.module
@@ -128,8 +128,10 @@ function entity_reference_field_config_presave(FieldConfigInterface $field) {
 function entity_reference_form_field_ui_field_storage_add_form_alter(array &$form) {
   // 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…');
+  // @todo fix this
+  $reference = (string) t('Reference');
+  unset($form['add']['new_storage_type']['#options'][$reference]['entity_reference']);
+  $form['add']['new_storage_type']['#options'][$reference]['entity_reference'] = t('Other…');
 }
 
 /**
diff --git a/core/modules/language/src/Form/NegotiationBrowserForm.php b/core/modules/language/src/Form/NegotiationBrowserForm.php
index bf0ce4e..5fa186a 100644
--- a/core/modules/language/src/Form/NegotiationBrowserForm.php
+++ b/core/modules/language/src/Form/NegotiationBrowserForm.php
@@ -82,8 +82,8 @@ 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(),
+        (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..f3007f6 100644
--- a/core/modules/locale/src/Form/ImportForm.php
+++ b/core/modules/locale/src/Form/ImportForm.php
@@ -94,8 +94,8 @@ 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(),
+        (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..e12f0ba 100644
--- a/core/modules/locale/src/Tests/LocaleImportFunctionalTest.php
+++ b/core/modules/locale/src/Tests/LocaleImportFunctionalTest.php
@@ -238,8 +238,8 @@ 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.');
+    $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 +254,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/menu_ui/src/Tests/MenuNodeTest.php b/core/modules/menu_ui/src/Tests/MenuNodeTest.php
index 2647bc8..bfc3929 100644
--- a/core/modules/menu_ui/src/Tests/MenuNodeTest.php
+++ b/core/modules/menu_ui/src/Tests/MenuNodeTest.php
@@ -139,7 +139,7 @@ function testMenuNodeFormWidget() {
       'edit any page content',
     ]);
     $this->drupalLogin($admin_user);
-    foreach ([t('Save and unpublish') => FALSE, t('Save and keep unpublished') => FALSE, t('Save and publish') => TRUE, t('Save and keep published') => TRUE] as $submit => $visible) {
+    foreach ([(string) t('Save and unpublish') => FALSE, (string) t('Save and keep unpublished') => FALSE, (string) t('Save and publish') => TRUE, (string) t('Save and keep published') => TRUE] as $submit => $visible) {
       $edit = [
         'menu[enabled]' => 1,
         'menu[title]' => $node_title,
diff --git a/core/modules/quickedit/src/Tests/QuickEditLoadingTest.php b/core/modules/quickedit/src/Tests/QuickEditLoadingTest.php
index 5ddc5cf..8bf0e71 100644
--- a/core/modules/quickedit/src/Tests/QuickEditLoadingTest.php
+++ b/core/modules/quickedit/src/Tests/QuickEditLoadingTest.php
@@ -512,7 +512,8 @@ public function testConcurrentEdit() {
       $ajax_commands = Json::decode($response);
       $this->assertIdentical(2, count($ajax_commands), 'The field form HTTP request results in two AJAX commands.');
       $this->assertIdentical('quickeditFieldFormValidationErrors', $ajax_commands[1]['command'], 'The second AJAX command is a quickeditFieldFormValidationErrors command.');
-      $this->assertTrue(strpos($ajax_commands[1]['data'], t('The content has either been modified by another user, or you have already submitted modifications. As a result, your changes cannot be saved.')), 'Error message returned to user.');
+      // @todo fix this if needed
+      $this->assertTrue(strpos((string) $ajax_commands[1]['data'], 'The content has either been modified by another user, or you have already submitted modifications. As a result, your changes cannot be saved.'), 'Error message returned to user.');
     }
   }
 
diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module
index be83839..9bb0b46 100644
--- a/core/modules/shortcut/shortcut.module
+++ b/core/modules/shortcut/shortcut.module
@@ -311,7 +311,7 @@ function shortcut_preprocess_page(&$variables) {
 
     $query = array(
       'link' => $link,
-      'name' => $variables['title'],
+      'name' => (string) $variables['title'],
     );
 
     $shortcut_set = shortcut_current_displayed_set();
diff --git a/core/modules/simpletest/src/AssertContentTrait.php b/core/modules/simpletest/src/AssertContentTrait.php
index c601943..336e2c1 100644
--- a/core/modules/simpletest/src/AssertContentTrait.php
+++ b/core/modules/simpletest/src/AssertContentTrait.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Serialization\Json;
 use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Component\Utility\SafeStringInterface;
 use Drupal\Component\Utility\Xss;
 use Drupal\Core\Render\RenderContext;
 use Symfony\Component\CssSelector\CssSelector;
@@ -178,6 +179,10 @@ protected function getUrl() {
   protected function buildXPathQuery($xpath, array $args = array()) {
     // Replace placeholders.
     foreach ($args as $placeholder => $value) {
+      // @todo fix this
+      if ($value instanceof SafeStringInterface) {
+        $value = (string) $value;
+      }
       // XPath 1.0 doesn't support a way to escape single or double quotes in a
       // string literal. We split double quotes out of the string, and encode
       // them separately.
@@ -299,6 +304,8 @@ protected function getAllOptions(\SimpleXMLElement $element) {
    *   TRUE if the assertion succeeded, FALSE otherwise.
    */
   protected function assertLink($label, $index = 0, $message = '', $group = 'Other') {
+    // @todo fix this in the test
+    $label = (string) $label;
     $links = $this->xpath('//a[normalize-space(text())=:label]', array(':label' => $label));
     $message = ($message ? $message : strtr('Link with label %label found.', array('%label' => $label)));
     return $this->assert(isset($links[$index]), $message, $group);
@@ -323,6 +330,11 @@ protected function assertLink($label, $index = 0, $message = '', $group = 'Other
    *   TRUE if the assertion succeeded, FALSE otherwise.
    */
   protected function assertNoLink($label, $message = '', $group = 'Other') {
+    // @todo fix this
+    if ($label instanceof SafeStringInterface) {
+      $label = (string) $label;
+    }
+
     $links = $this->xpath('//a[normalize-space(text())=:label]', array(':label' => $label));
     $message = ($message ? $message : SafeMarkup::format('Link with label %label not found.', array('%label' => $label)));
     return $this->assert(empty($links), $message, $group);
@@ -671,10 +683,15 @@ protected function assertNoUniqueText($text, $message = '', $group = 'Other') {
    *   TRUE on pass, FALSE on fail.
    */
   protected function assertUniqueTextHelper($text, $message = '', $group = 'Other', $be_unique = FALSE) {
+    // @todo fix this
+    if ($text instanceof SafeStringInterface) {
+      $text = (string) $text;
+    }
     if (!$message) {
       $message = '"' . $text . '"' . ($be_unique ? ' found only once' : ' found more than once');
     }
-    $first_occurrence = strpos($this->getTextContent(), $text);
+    // @todo fix this in the test itself
+    $first_occurrence = strpos((string) $this->getTextContent(), $text);
     if ($first_occurrence === FALSE) {
       return $this->assert(FALSE, $message, $group);
     }
@@ -1094,6 +1111,13 @@ protected function assertNoFieldByName($name, $value = '', $message = '', $group
    *   TRUE on pass, FALSE on fail.
    */
   protected function assertFieldById($id, $value = '', $message = '', $group = 'Browser') {
+    // @todo fix this
+    if ($value instanceof SafeStringInterface) {
+      $value = (string) $value;
+    }
+    if ($message instanceof SafeStringInterface) {
+      $message = (string) $message;
+    }
     return $this->assertFieldByXPath($this->constructFieldXpath('id', $id), $value, $message ? $message : SafeMarkup::format('Found field by id @id', array('@id' => $id)), $group);
   }
 
diff --git a/core/modules/simpletest/src/TestBase.php b/core/modules/simpletest/src/TestBase.php
index 1e59712..77946f0 100644
--- a/core/modules/simpletest/src/TestBase.php
+++ b/core/modules/simpletest/src/TestBase.php
@@ -7,9 +7,11 @@
 
 namespace Drupal\simpletest;
 
+use Drupal\Component\Utility\SafeStringInterface;
 use Drupal\Component\Utility\Crypt;
 use Drupal\Component\Utility\Random;
 use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Annotation\Translation;
 use Drupal\Core\Database\Database;
 use Drupal\Core\Config\ConfigImporter;
 use Drupal\Core\Config\StorageComparer;
@@ -395,7 +397,7 @@ protected function assert($status, $message = '', $group = 'Other', array $calle
       'test_id' => $this->testId,
       'test_class' => get_class($this),
       'status' => $status,
-      'message' => $message,
+      'message' => (string) $message,
       'message_group' => $group,
       'function' => $caller['function'],
       'line' => $caller['line'],
@@ -654,6 +656,18 @@ protected function assertNotNull($value, $message = '', $group = 'Other') {
    *   TRUE if the assertion succeeded, FALSE otherwise.
    */
   protected function assertEqual($first, $second, $message = '', $group = 'Other') {
+    // @todo fix the actual tests
+    if ($first instanceof SafeStringInterface || $second instanceof SafeStringInterface) {
+      $first = (string) $first;
+      $second = (string) $second;
+    }
+    if (is_array($first)) {
+      array_walk_recursive($first, [$this, 'translationWrapperToString']);
+    }
+    if (is_array($second)) {
+      array_walk_recursive($second, [$this, 'translationWrapperToString']);
+    }
+
     return $this->assert($first == $second, $message ? $message : SafeMarkup::format('Value @first is equal to value @second.', array('@first' => var_export($first, TRUE), '@second' => var_export($second, TRUE))), $group);
   }
 
@@ -683,6 +697,15 @@ protected function assertNotEqual($first, $second, $message = '', $group = 'Othe
   }
 
   /**
+   * @todo remove this
+   */
+  public function translationWrapperToString(&$value) {
+    if ($value instanceof SafeStringInterface) {
+      $value = (string) $value;
+    }
+  }
+
+  /**
    * Check to see if two values are identical.
    *
    * @param $first
@@ -704,6 +727,17 @@ protected function assertNotEqual($first, $second, $message = '', $group = 'Othe
    *   TRUE if the assertion succeeded, FALSE otherwise.
    */
   protected function assertIdentical($first, $second, $message = '', $group = 'Other') {
+    // @todo fix the actual tests
+    if ($first instanceof SafeStringInterface || $second instanceof SafeStringInterface) {
+      $first = (string) $first;
+      $second = (string) $second;
+    }
+    if (is_array($first)) {
+      array_walk_recursive($first, [$this, 'translationWrapperToString']);
+    }
+    if (is_array($second)) {
+      array_walk_recursive($second, [$this, 'translationWrapperToString']);
+    }
     return $this->assert($first === $second, $message ? $message : SafeMarkup::format('Value @first is identical to value @second.', array('@first' => var_export($first, TRUE), '@second' => var_export($second, TRUE))), $group);
   }
 
diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php
index 5e86826..94e2f3b 100644
--- a/core/modules/simpletest/src/WebTestBase.php
+++ b/core/modules/simpletest/src/WebTestBase.php
@@ -13,6 +13,7 @@
 use Drupal\Component\Serialization\Yaml;
 use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\NestedArray;
+use Drupal\Component\Utility\SafeStringInterface;
 use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\Cache\Cache;
 use Drupal\Component\Utility\SafeMarkup;
@@ -1690,6 +1691,14 @@ protected function drupalGetXHR($path, array $options = array(), array $headers
    *   (e.g., "&extra_var1=hello+world&extra_var2=you%26me").
    */
   protected function drupalPostForm($path, $edit, $submit, array $options = array(), array $headers = array(), $form_html_id = NULL, $extra_post = NULL) {
+    // @todo fix this
+    if ($submit instanceof SafeStringInterface) {
+      $submit = (string) $submit;
+    }
+    if (is_array($edit)) {
+      array_walk_recursive($edit, [$this, 'translationWrapperToString']);
+    }
+
     $submit_matches = FALSE;
     $ajax = is_array($submit);
     if (isset($path)) {
@@ -2416,7 +2425,7 @@ protected function handleForm(&$post, &$edit, &$upload, $submit, $form) {
    *   Page contents on success, or FALSE on failure.
    */
   protected function clickLink($label, $index = 0) {
-    return $this->clickLinkHelper($label, $index, '//a[normalize-space()=:label]');
+    return $this->clickLinkHelper((string) $label, $index, '//a[normalize-space()=:label]');
   }
 
   /**
@@ -2436,7 +2445,7 @@ protected function clickLink($label, $index = 0) {
    * @see ::clickLink()
    */
   protected function clickLinkPartialName($label, $index = 0) {
-    return $this->clickLinkHelper($label, $index, '//a[starts-with(normalize-space(), :label)]');
+    return $this->clickLinkHelper((string) $label, $index, '//a[starts-with(normalize-space(), :label)]');
   }
 
   /**
@@ -2798,6 +2807,7 @@ protected function assertMail($name, $value = '', $message = '', $group = 'Email
   protected function assertMailString($field_name, $string, $email_depth, $message = '', $group = 'Other') {
     $mails = $this->drupalGetMails();
     $string_found = FALSE;
+    $string = (string) $string;
     for ($i = count($mails) -1; $i >= count($mails) - $email_depth && $i >= 0; $i--) {
       $mail = $mails[$i];
       // Normalize whitespace, as we don't know what the mail system might have
@@ -2986,4 +2996,10 @@ protected function assertNoCacheTag($cache_tag) {
     $this->assertFalse(in_array($cache_tag, $cache_tags), "'" . $cache_tag . "' is absent in the X-Drupal-Cache-Tags header.");
   }
 
+  // @todo remove this
+  public function translationWrapperToString(&$value) {
+    if ($value instanceof SafeStringInterface) {
+      $value = (string) $value;
+    }
+  }
 }
diff --git a/core/modules/system/src/Tests/Common/FormatDateTest.php b/core/modules/system/src/Tests/Common/FormatDateTest.php
index 0ece458..fda5048 100644
--- a/core/modules/system/src/Tests/Common/FormatDateTest.php
+++ b/core/modules/system/src/Tests/Common/FormatDateTest.php
@@ -87,12 +87,12 @@ 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->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.');
+    $this->assertIdentical((string) 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((string) 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((string) 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((string) 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((string) 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((string) 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.
     $this->config('system.site')->set('default_langcode', static::LANGCODE)->save();
diff --git a/core/modules/system/src/Tests/Common/XssUnitTest.php b/core/modules/system/src/Tests/Common/XssUnitTest.php
index f1358e0..5b8d6df 100644
--- a/core/modules/system/src/Tests/Common/XssUnitTest.php
+++ b/core/modules/system/src/Tests/Common/XssUnitTest.php
@@ -55,7 +55,7 @@ function testBadProtocolStripping() {
     $url = 'javascript:http://www.example.com/?x=1&y=2';
     $expected_plain = 'http://www.example.com/?x=1&y=2';
     $expected_html = 'http://www.example.com/?x=1&amp;y=2';
-    $this->assertIdentical(check_url($url), $expected_html, 'check_url() filters a URL and encodes it for HTML.');
+    $this->assertIdentical((string) check_url($url), $expected_html, 'check_url() filters a URL and encodes it for HTML.');
     $this->assertIdentical(UrlHelper::stripDangerousProtocols($url), $expected_plain, '\Drupal\Component\Utility\Url::stripDangerousProtocols() filters a URL and returns plain text.');
   }
 }
diff --git a/core/modules/system/src/Tests/Form/FormTest.php b/core/modules/system/src/Tests/Form/FormTest.php
index 4585ad1..bcda7d6 100644
--- a/core/modules/system/src/Tests/Form/FormTest.php
+++ b/core/modules/system/src/Tests/Form/FormTest.php
@@ -143,7 +143,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/tests/modules/plugin_test/src/Plugin/MockBlockManager.php b/core/modules/system/tests/modules/plugin_test/src/Plugin/MockBlockManager.php
index e435c0a..4106ce6 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' => (string) t('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' => (string) t('Layout'),
       'class' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockLayoutBlock',
       'deriver' => 'Drupal\plugin_test\Plugin\plugin_test\mock_block\MockLayoutBlockDeriver',
     ));
diff --git a/core/modules/user/src/Tests/UserCancelTest.php b/core/modules/user/src/Tests/UserCancelTest.php
index 5357013..0b2860f 100644
--- a/core/modules/user/src/Tests/UserCancelTest.php
+++ b/core/modules/user/src/Tests/UserCancelTest.php
@@ -535,7 +535,8 @@ 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);
+      // @todo fix the t()
+      $status = $status && (strpos($this->content, (string) t('%name has been deleted.', array('%name' => $account->getUsername()))) !== FALSE);
       $user_storage->resetCache(array($account->id()));
       $status = $status && !$user_storage->load($account->id());
     }
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 55473bb..02c3ac8 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -974,7 +974,8 @@ function user_user_role_insert(RoleInterface $role) {
     $action = entity_create('action', array(
       'id' => $add_id,
       'type' => 'user',
-      'label' => t('Add the @label role to the selected users', array('@label' => $role->label())),
+      // @todo investigate
+      'label' => (string) t('Add the @label role to the selected users', array('@label' => $role->label())),
       'configuration' => array(
         'rid' => $role->id(),
       ),
@@ -987,7 +988,8 @@ function user_user_role_insert(RoleInterface $role) {
     $action = entity_create('action', array(
       'id' => $remove_id,
       'type' => 'user',
-      'label' => t('Remove the @label role from the selected users', array('@label' => $role->label())),
+      // @todo investigate
+      'label' => (string) t('Remove the @label role from the selected users', array('@label' => $role->label())),
       'configuration' => array(
         'rid' => $role->id(),
       ),
diff --git a/core/modules/views/src/Plugin/views/area/TokenizeAreaPluginBase.php b/core/modules/views/src/Plugin/views/area/TokenizeAreaPluginBase.php
index 82bb51d..2a57e97 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();
+    // @todo fix this
+    $fields = (string) t('Fields');
     foreach ($this->view->display_handler->getHandlers('field') as $field => $handler) {
-      $options[t('Fields')]["[$field]"] = $handler->adminLabel();
+      $options[$fields]["[$field]"] = $handler->adminLabel();
     }
 
     $count = 0; // This lets us prepare the key as we want it printed.
+    // @todo fix this
+    $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[$arguments]['%' . ++$count] = $this->t('@argument title', array('@argument' => $handler->adminLabel()));
+      $options[$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 f9ae57a..0624fc5 100644
--- a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
+++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
@@ -1728,8 +1728,8 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
         $options = array();
         $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[(string) t('Arguments')]['%' . ++$count] = $this->t('@argument title', array('@argument' => $handler->adminLabel()));
+          $options[(string) t('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 310997c..ef9706f 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();
+      // @todo fix this
+      $fields = (string) t('Fields');
       foreach ($previous as $id => $label) {
-        $options[t('Fields')]["{{ $id }}"] = substr(strrchr($label, ":"), 2 );
+        $options[$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[$fields]["{{ {$this->options['id']} }}"] = substr(strrchr($this->adminLabel(), ":"), 2 );
 
       $count = 0; // This lets us prepare the key as we want it printed.
+      // @todo fix this
+      $arguments = (string) t('Arguments');
       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[$arguments]['%' . ++$count] = $this->t('@argument title', array('@argument' => $handler->adminLabel()));
+        $options[$arguments]['!' . $count] = $this->t('@argument input', array('@argument' => $handler->adminLabel()));
       }
 
-      $this->documentSelfTokens($options[t('Fields')]);
+      $this->documentSelfTokens($options[$fields]);
 
       // Default text.
 
diff --git a/core/modules/views_ui/admin.inc b/core/modules/views_ui/admin.inc
index 78be0fc..b6462ea 100644
--- a/core/modules/views_ui/admin.inc
+++ b/core/modules/views_ui/admin.inc
@@ -94,16 +94,18 @@ function views_ui_add_ajax_trigger(&$wrapping_element, $trigger_key, $refresh_pa
   // always give the button a unique #value, rather than playing around with
   // #name.
   $button_title = !empty($triggering_element['#title']) ? $triggering_element['#title'] : $trigger_key;
-  if (empty($seen_buttons[$button_title])) {
+  $button_title_string = (string) $button_title;
+  if (empty($seen_buttons[$button_title_string])) {
+    // This code relies on check_plain()'ing the string because of the quotes.
     $wrapping_element[$button_key]['#value'] = t('Update "@title" choice', array(
       '@title' => $button_title,
     ));
-    $seen_buttons[$button_title] = 1;
+    $seen_buttons[$button_title_string] = 1;
   }
   else {
     $wrapping_element[$button_key]['#value'] = t('Update "@title" choice (@number)', array(
       '@title' => $button_title,
-      '@number' => ++$seen_buttons[$button_title],
+      '@number' => ++$seen_buttons[$button_title_string],
     ));
   }
 
diff --git a/core/modules/views_ui/src/Tests/HandlerTest.php b/core/modules/views_ui/src/Tests/HandlerTest.php
index e7ce037..c53ddd7 100644
--- a/core/modules/views_ui/src/Tests/HandlerTest.php
+++ b/core/modules/views_ui/src/Tests/HandlerTest.php
@@ -150,7 +150,7 @@ public function testBrokenHandlers() {
       $result = $this->xpath('//a[contains(@href, :href)]', array(':href' => $href));
       $this->assertEqual(count($result), 1, SafeMarkup::format('Handler (%type) edit link found.', array('%type' => $type)));
 
-      $text = t('Broken/missing handler');
+      $text = 'Broken/missing handler';
 
       $this->assertIdentical((string) $result[0], $text, 'Ensure the broken handler text was found.');
 
