diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index b3deb32..6e5e0ca 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -13,6 +13,7 @@
 use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Render\SafeString;
 use Drupal\Component\Utility\Xss;
 use Drupal\Core\Config\Config;
 use Drupal\Core\Config\StorageException;
@@ -1240,8 +1241,10 @@ function template_preprocess_html(&$variables) {
     $variables['page']['#title'] = (string) \Drupal::service('renderer')->render($variables['page']['#title']);
   }
   if (!empty($variables['page']['#title'])) {
+    // @todo fix this and check if we can really consider this safe, perhaps only mark as safe if it was safe previously?
+    // We needed this workaround because the title loses safeness when being cast to string, and it may have already been sanitized.
     $head_title = array(
-      'title' => trim(strip_tags($variables['page']['#title'])),
+      'title' => SafeString::create(trim(strip_tags($variables['page']['#title']))),
       'name' => $site_config->get('name'),
     );
   }
diff --git a/core/lib/Drupal/Component/Utility/FormattedString.php b/core/lib/Drupal/Component/Utility/FormattedString.php
new file mode 100644
index 0000000..c3c3feb
--- /dev/null
+++ b/core/lib/Drupal/Component/Utility/FormattedString.php
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Utility\FormattedString
+ */
+
+namespace Drupal\Component\Utility;
+
+/**
+ * @todo remove/rename
+ */
+final class FormattedString implements SafeStringInterface, \Countable {
+  use SafeStringTrait;
+}
+
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/Component/Utility/SafeMarkup.php b/core/lib/Drupal/Component/Utility/SafeMarkup.php
index b4e0989..40fa1f6 100644
--- a/core/lib/Drupal/Component/Utility/SafeMarkup.php
+++ b/core/lib/Drupal/Component/Utility/SafeMarkup.php
@@ -15,9 +15,9 @@
  * provides a store for known safe strings and methods to manage them
  * throughout the page request.
  *
- * Strings sanitized by self::checkPlain() and self::escape() are automatically
- * marked safe, as are markup strings created from @link theme_render render
- * arrays @endlink via drupal_render().
+ * Strings sanitized by self::checkPlain(), self::escape() and self::format()
+ * are automatically marked safe, as are markup strings created from
+ * @link theme_render render arrays @endlink via drupal_render().
  *
  * This class should be limited to internal use only. Module developers should
  * instead use the appropriate
@@ -205,7 +205,9 @@ public static function checkPlain($text) {
    *     @code
    *       <em class="placeholder">text output here.</em>
    *     @endcode
-   *   - !variable: Inserted as is, with no sanitization or formatting. Only
+   *     If the string is safe, it will be inserted as is, with no
+   *     sanitization or formatting. If the string is unsafe, a very permissive
+   *     XSS/HTML filter for admin-only use will be applied. Only
    *     use this when the resulting string is being generated for one of:
    *     - Non-HTML usage, such as a plain-text email.
    *     - Non-direct HTML output, such as a plain-text variable that will be
@@ -213,17 +215,14 @@ public static function checkPlain($text) {
    *       self::checkPlain() as part of that.
    *     - Some other special reason for suppressing sanitization.
    *
-   * @return string
-   *   The formatted string, which is marked as safe unless sanitization of an
-   *   unsafe argument was suppressed (see above).
+   * @return \Drupal\Component\Utility\SafeStringInterface
+   *   A safe string object containing the formatted string.
    *
    * @ingroup sanitization
    *
    * @see t()
    */
   public static function format($string, array $args) {
-    $safe = TRUE;
-
     // Transform arguments before inserting them.
     foreach ($args as $key => $value) {
       switch ($key[0]) {
@@ -239,19 +238,19 @@ public static function format($string, array $args) {
           break;
 
         case '!':
-          // Pass-through.
+          // Try to preserve the value by only admin filtering if not marked
+          // safe.
           if (!static::isSafe($value)) {
-            $safe = FALSE;
+            // XSS admin filtered.
+            $args[$key] = Xss::filterAdmin($value);
           }
       }
     }
 
+    // @todo explore if we want to leave the placeholder replacement until __toString() time.
     $output = strtr($string, $args);
-    if ($safe) {
-      static::$safeStrings[$output]['html'] = TRUE;
-    }
 
-    return $output;
+    return FormattedString::create($output);
   }
 
 }
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..9879998 100644
--- a/core/lib/Drupal/Core/Database/Install/Tasks.php
+++ b/core/lib/Drupal/Core/Database/Install/Tasks.php
@@ -79,7 +79,10 @@
    *
    * @var array
    */
-  protected $results = array();
+  protected $results = array(
+    'fail' => array(),
+    'pass' => array(),
+  );
 
   /**
    * Ensure the PDO driver is supported by the version of PHP in use.
@@ -92,14 +95,14 @@ protected function hasPdoDriver() {
    * Assert test as failed.
    */
   protected function fail($message) {
-    $this->results[$message] = FALSE;
+    $this->results['fail'][] = $message;
   }
 
   /**
    * Assert test as a pass.
    */
   protected function pass($message) {
-    $this->results[$message] = TRUE;
+    $this->results['pass'][] = $message;
   }
 
   /**
@@ -149,11 +152,7 @@ public function runTasks() {
         }
       }
     }
-    // Filter out the success messages from results.
-    $errors = array_filter($this->results, function ($value) {
-      return !$value;
-    });
-    return array_keys($errors);
+    return $this->results['fail'];
   }
 
   /**
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..6bf3d4f 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('!on_label', ['!on_label' => $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('!off_label', ['!off_label' => $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('!off_label', ['!off_label' => $this->getSetting('off_label')]),
+      1 => $this->t('!on_label', ['!on_label' => $this->getSetting('on_label')]),
     );
   }
 
diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index bb8f1ff..a0cccd2 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -714,7 +714,8 @@ protected function createPlaceholder(array $element) {
     // Build the placeholder element to return.
     $placeholder_element = [];
     $placeholder_element['#markup'] = $placeholder_markup;
-    $placeholder_element['#attached']['placeholders'][$placeholder_markup] = $placeholder_render_array;
+    // @todo fix this
+    $placeholder_element['#attached']['placeholders'][(string) $placeholder_markup] = $placeholder_render_array;
     return $placeholder_element;
   }
 
diff --git a/core/lib/Drupal/Core/StringTranslation/TranslationInterface.php b/core/lib/Drupal/Core/StringTranslation/TranslationInterface.php
index c3e2f68..28b06d2 100644
--- a/core/lib/Drupal/Core/StringTranslation/TranslationInterface.php
+++ b/core/lib/Drupal/Core/StringTranslation/TranslationInterface.php
@@ -25,7 +25,8 @@
    *   A string containing the English string to translate.
    * @param array $args
    *   An associative array of replacements to make after translation. Based
-   *   on the first character of the key, the value is escaped and/or themed.
+   *   on the first character of the key, the value is escaped and/or themed
+   *   (%, @), or XSS admin filtered if unsafe (!).
    *   See \Drupal\Component\Utility\SafeMarkup::format() for details.
    * @param array $options
    *   An associative array of additional options, with the following elements:
@@ -34,13 +35,31 @@
    *   - 'context': The context the source string belongs to.
    *
    * @return string
-   *   The translated string.
+   *   The translated string, which is marked as safe.
    *
    * @see \Drupal\Component\Utility\SafeMarkup::format()
    */
   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
@@ -74,14 +93,15 @@ public function translate($string, array $args = array(), array $options = array
    *   An associative array of replacements to make after translation. Instances
    *   of any key in this array are replaced with the corresponding value.
    *   Based on the first character of the key, the value is escaped and/or
-   *   themed. See \Drupal\Component\Utility\SafeMarkup::format(). Note that you do
+   *   themed (%, @), or XSS admin filtered if unsafe (!).
+   *   See \Drupal\Component\Utility\SafeMarkup::format(). Note that you do
    *   not need to include @count in this array; this replacement is done
    *   automatically for the plural cases.
    * @param array $options
    *   An associative array of additional options. See t() for allowed keys.
    *
    * @return string
-   *   A translated string.
+   *   A translated string, which is marked as safe.
    *
    * @see self::translate()
    * @see t()
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..0a21e17 100644
--- a/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php
+++ b/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php
@@ -7,7 +7,10 @@
 
 namespace Drupal\Core\StringTranslation;
 
+use Drupal\Component\Utility\Html;
+use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Component\Utility\SafeStringInterface;
+use Drupal\Component\Utility\Xss;
 
 /**
  * Provides a class to wrap a translatable string.
@@ -18,7 +21,7 @@
  *
  * @see \Drupal\Core\Annotation\Translation
  */
-class TranslationWrapper implements SafeStringInterface {
+class TranslationWrapper implements SafeStringInterface, \JsonSerializable {
   use StringTranslationTrait;
 
   /**
@@ -98,7 +101,7 @@ public function getOptions() {
    * Implements the magic __toString() method.
    */
   public function __toString() {
-    return $this->render();
+    return (string) $this->render();
   }
 
   /**
@@ -108,7 +111,40 @@ 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 '!':
+            // Try to preserve the value by only admin filtering if not marked
+            // safe.
+            if (!SafeMarkup::isSafe($value)) {
+              // XSS admin filtered.
+              $args[$key] = Xss::filterAdmin($value);
+            }
+        }
+      }
+      $string = strtr($string, $args);
+    }
+    return $string;
   }
 
   /**
@@ -118,4 +154,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/lib/Drupal/Core/Template/TwigExtension.php b/core/lib/Drupal/Core/Template/TwigExtension.php
index 443cf73..44134d8 100644
--- a/core/lib/Drupal/Core/Template/TwigExtension.php
+++ b/core/lib/Drupal/Core/Template/TwigExtension.php
@@ -15,6 +15,7 @@
 use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Component\Utility\SafeStringInterface;
+use Drupal\Component\Utility\Xss;
 use Drupal\Core\Render\RendererInterface;
 use Drupal\Core\Routing\UrlGeneratorInterface;
 use Drupal\Core\Theme\ThemeManagerInterface;
@@ -128,12 +129,11 @@ public function getFilters() {
       // Translation filters.
       new \Twig_SimpleFilter('t', 't', array('is_safe' => array('html'))),
       new \Twig_SimpleFilter('trans', 't', array('is_safe' => array('html'))),
-      // The "raw" filter is not detectable when parsing "trans" tags. To detect
-      // which prefix must be used for translation (@, !, %), we must clone the
-      // "raw" filter and give it identifiable names. These filters should only
-      // be used in "trans" tags.
+      // To detect which prefix must be used for translation (@, !, %), we
+      // create filters and give them identifiable names. These filters should
+      // only be used in "trans" tags.
       // @see TwigNodeTrans::compileString()
-      new \Twig_SimpleFilter('passthrough', 'twig_raw_filter', array('is_safe' => array('html'))),
+      new \Twig_SimpleFilter('passthrough', [$this, 'passthrough'], array('is_safe' => array('html'))),
       new \Twig_SimpleFilter('placeholder', [$this, 'escapePlaceholder'], array('is_safe' => array('html'), 'needs_environment' => TRUE)),
 
       // Replace twig's escape filter with our own.
@@ -371,6 +371,22 @@ public function escapePlaceholder($env, $string) {
   }
 
   /**
+   * Provides a passthrough filter for trans.
+   *
+   * @param mixed $string
+   *   The value.
+   *
+   * @return string|null
+   *   The string, XSS admin filtered if not safe.
+   */
+  public function passthrough($string) {
+    if (!SafeMarkup::isSafe($string)) {
+      $string = Xss::filterAdmin($string);
+    }
+    return $string;
+  }
+
+  /**
    * Overrides twig_escape_filter().
    *
    * Replacement function for Twig's escape filter.
diff --git a/core/lib/Drupal/Core/Validation/DrupalTranslator.php b/core/lib/Drupal/Core/Validation/DrupalTranslator.php
index b14c7d2..e042682 100644
--- a/core/lib/Drupal/Core/Validation/DrupalTranslator.php
+++ b/core/lib/Drupal/Core/Validation/DrupalTranslator.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Validation;
 
 use Symfony\Component\Translation\TranslatorInterface;
+use Drupal\Component\Utility\SafeStringInterface;
 
 /**
  * Translates strings using Drupal's translation system.
@@ -64,8 +65,11 @@ public function getLocale() {
   protected function processParameters(array $parameters) {
     $return = array();
     foreach ($parameters as $key => $value) {
+      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/modules/book/tests/src/Unit/BookUninstallValidatorTest.php b/core/modules/book/tests/src/Unit/BookUninstallValidatorTest.php
index d3748c2..8baaf61 100644
--- a/core/modules/book/tests/src/Unit/BookUninstallValidatorTest.php
+++ b/core/modules/book/tests/src/Unit/BookUninstallValidatorTest.php
@@ -78,6 +78,7 @@ public function testValidateEntityQueryWithResults() {
     $module = 'book';
     $expected = ['To uninstall Book, delete all content that has the Book content type'];
     $reasons = $this->bookUninstallValidator->validate($module);
+    $reasons[0] = (string) $reasons[0];
     $this->assertSame($expected, $reasons);
   }
 
@@ -94,6 +95,7 @@ public function testValidateOutlineStorage() {
     $module = 'book';
     $expected = ['To uninstall Book, delete all content that is part of a book'];
     $reasons = $this->bookUninstallValidator->validate($module);
+    $reasons[0] = (string) $reasons[0];
     $this->assertSame($expected, $reasons);
   }
 
diff --git a/core/modules/ckeditor/ckeditor.admin.inc b/core/modules/ckeditor/ckeditor.admin.inc
index 21be21d..6c42d72 100644
--- a/core/modules/ckeditor/ckeditor.admin.inc
+++ b/core/modules/ckeditor/ckeditor.admin.inc
@@ -113,6 +113,8 @@ 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) {
+      // @todo fix this
+      $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..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/comment/src/CommentForm.php b/core/modules/comment/src/CommentForm.php
index c96202b..3980690 100644
--- a/core/modules/comment/src/CommentForm.php
+++ b/core/modules/comment/src/CommentForm.php
@@ -294,7 +294,9 @@ public function buildEntity(array $form, FormStateInterface $form_state) {
       // Edge cases where the comment body is populated only by HTML tags will
       // require a default subject.
       if ($comment->getSubject() == '') {
-        $comment->setSubject($this->t('(No subject)'));
+        // @todo fix this. We cast to string so this will pass the
+        // primitive type constraint validation.
+        $comment->setSubject((string) $this->t('(No subject)'));
       }
     }
     return $comment;
diff --git a/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php b/core/modules/config_translation/src/Tests/ConfigTranslationUiTest.php
index f220d1a..eae7325 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/config_translation/tests/src/Unit/ConfigNamesMapperTest.php b/core/modules/config_translation/tests/src/Unit/ConfigNamesMapperTest.php
index 796012f..fce0d67 100644
--- a/core/modules/config_translation/tests/src/Unit/ConfigNamesMapperTest.php
+++ b/core/modules/config_translation/tests/src/Unit/ConfigNamesMapperTest.php
@@ -140,7 +140,7 @@ protected function setUp() {
    */
   public function testGetTitle() {
     $result = $this->configNamesMapper->getTitle();
-    $this->assertSame($this->pluginDefinition['title'], $result);
+    $this->assertSame($this->pluginDefinition['title'], (string) $result);
   }
 
   /**
@@ -397,7 +397,7 @@ public function testPopulateFromRequest() {
    */
   public function testGetTypeLabel() {
     $result = $this->configNamesMapper->getTypeLabel();
-    $this->assertSame($this->pluginDefinition['title'], $result);
+    $this->assertSame($this->pluginDefinition['title'], (string) $result);
   }
 
   /**
@@ -624,7 +624,7 @@ public function providerTestHasTranslation() {
    */
   public function testGetTypeName() {
     $result = $this->configNamesMapper->getTypeName();
-    $this->assertSame('Settings', $result);
+    $this->assertSame('Settings', (string) $result);
   }
 
   /**
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/field/tests/src/Unit/FieldUninstallValidatorTest.php b/core/modules/field/tests/src/Unit/FieldUninstallValidatorTest.php
index ea9e0c8..86ac574 100644
--- a/core/modules/field/tests/src/Unit/FieldUninstallValidatorTest.php
+++ b/core/modules/field/tests/src/Unit/FieldUninstallValidatorTest.php
@@ -63,6 +63,7 @@ public function testValidateDeleted() {
     $module = $this->randomMachineName();
     $expected = ['Fields pending deletion'];
     $reasons = $this->fieldUninstallValidator->validate($module);
+    $reasons[0] = (string) $reasons[0];
     $this->assertSame($expected, $reasons);
   }
 
@@ -83,6 +84,7 @@ public function testValidateNoDeleted() {
     $module = $this->randomMachineName();
     $expected = ['Fields type(s) in use'];
     $reasons = $this->fieldUninstallValidator->validate($module);
+    $reasons[0] = (string) $reasons[0];
     $this->assertSame($expected, $reasons);
   }
 
diff --git a/core/modules/filter/tests/src/Unit/FilterUninstallValidatorTest.php b/core/modules/filter/tests/src/Unit/FilterUninstallValidatorTest.php
index 4a4f7ba..e999f88 100644
--- a/core/modules/filter/tests/src/Unit/FilterUninstallValidatorTest.php
+++ b/core/modules/filter/tests/src/Unit/FilterUninstallValidatorTest.php
@@ -162,6 +162,7 @@ public function testValidateNoMatchingFormats() {
       'Provides a filter plugin that is in use in the following filter formats: <em class="placeholder">Filter Format 1 Label, Filter Format 2 Label</em>'
     ];
     $reasons = $this->filterUninstallValidator->validate($this->randomMachineName());
+    $reasons[0] = (string) $reasons[0];
     $this->assertSame($expected, $reasons);
   }
 
diff --git a/core/modules/forum/tests/src/Unit/ForumUninstallValidatorTest.php b/core/modules/forum/tests/src/Unit/ForumUninstallValidatorTest.php
index 560a872..4f8773f 100644
--- a/core/modules/forum/tests/src/Unit/ForumUninstallValidatorTest.php
+++ b/core/modules/forum/tests/src/Unit/ForumUninstallValidatorTest.php
@@ -94,6 +94,7 @@ public function testValidateHasForumNodes() {
       'To uninstall Forum, first delete all <em>Forum</em> content',
     ];
     $reasons = $this->forumUninstallValidator->validate($module);
+    $reasons[0] = (string) $reasons[0];
     $this->assertSame($expected, $reasons);
   }
 
@@ -129,6 +130,8 @@ public function testValidateHasTermsForVocabularyWithNodesAccess() {
       'To uninstall Forum, first delete all <a href="/path/to/vocabulary/overview"><em class="placeholder">Vocabulary label</em></a> terms',
     ];
     $reasons = $this->forumUninstallValidator->validate($module);
+    $reasons[0] = (string) $reasons[0];
+    $reasons[1] = (string) $reasons[1];
     $this->assertSame($expected, $reasons);
   }
 
@@ -163,6 +166,8 @@ public function testValidateHasTermsForVocabularyWithNodesNoAccess() {
       'To uninstall Forum, first delete all <em class="placeholder">Vocabulary label</em> terms',
     ];
     $reasons = $this->forumUninstallValidator->validate($module);
+    $reasons[0] = (string) $reasons[0];
+    $reasons[1] = (string) $reasons[1];
     $this->assertSame($expected, $reasons);
   }
 
@@ -197,6 +202,7 @@ public function testValidateHasTermsForVocabularyAccess() {
       'To uninstall Forum, first delete all <a href="/path/to/vocabulary/overview"><em class="placeholder">Vocabulary label</em></a> terms',
     ];
     $reasons = $this->forumUninstallValidator->validate($module);
+    $reasons[0] = (string) $reasons[0];
     $this->assertSame($expected, $reasons);
   }
 
@@ -230,6 +236,8 @@ public function testValidateHasTermsForVocabularyNoAccess() {
       'To uninstall Forum, first delete all <em class="placeholder">Vocabulary label</em> terms',
     ];
     $reasons = $this->forumUninstallValidator->validate($module);
+    // @todo see if this needs fixing
+    $reasons[0] = (string) $reasons[0];
     $this->assertSame($expected, $reasons);
   }
 
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/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/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/migrate/src/MigrateExecutable.php b/core/modules/migrate/src/MigrateExecutable.php
index 6148ef9..4d8bc84 100644
--- a/core/modules/migrate/src/MigrateExecutable.php
+++ b/core/modules/migrate/src/MigrateExecutable.php
@@ -237,7 +237,7 @@ public function import() {
     }
     catch (\Exception $e) {
       $this->message->display(
-        $this->t('Migration failed with source plugin exception: !e', array('!e' => $e->getMessage())), 'error');
+        $this->t('Migration failed with source plugin exception: @e', array('@e' => $e->getMessage())), 'error');
       $this->migration->setStatus(MigrationInterface::STATUS_IDLE);
       return MigrationInterface::RESULT_FAILED;
     }
diff --git a/core/modules/migrate/tests/src/Unit/MigrateExecutableTest.php b/core/modules/migrate/tests/src/Unit/MigrateExecutableTest.php
index eb1097c..e7c041a 100644
--- a/core/modules/migrate/tests/src/Unit/MigrateExecutableTest.php
+++ b/core/modules/migrate/tests/src/Unit/MigrateExecutableTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\migrate\Unit;
 
+use Drupal\Component\Utility\Html;
 use Drupal\migrate\Entity\MigrationInterface;
 use Drupal\migrate\Plugin\MigrateIdMapInterface;
 use Drupal\migrate\MigrateException;
@@ -72,7 +73,7 @@ public function testImportWithFailingRewind() {
     // Ensure that a message with the proper message was added.
     $this->message->expects($this->once())
       ->method('display')
-      ->with("Migration failed with source plugin exception: $exception_message");
+      ->with('Migration failed with source plugin exception: ' . Html::escape($exception_message));
 
     $result = $this->executable->import();
     $this->assertEquals(MigrationInterface::RESULT_FAILED, $result);
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/search/src/Plugin/SearchPluginBase.php b/core/modules/search/src/Plugin/SearchPluginBase.php
index ee24ea7..26aa720 100644
--- a/core/modules/search/src/Plugin/SearchPluginBase.php
+++ b/core/modules/search/src/Plugin/SearchPluginBase.php
@@ -124,7 +124,9 @@ public function suggestedTitle() {
     // If the user entered a search string, truncate it and append it to the
     // title.
     if (!empty($this->keywords)) {
-      return $this->t('Search for @keywords', array('@keywords' => Unicode::truncate($this->keywords, 60, TRUE, TRUE)));
+      // This will already be auto-escaped on output, so we use a !placeholder to prevent double escaping.
+      // @todo see if there is no better solution here, if we use @placeholder the head_title will be double-escaped in template_preprocess_html().
+      return $this->t('Search for !keywords', array('!keywords' => Unicode::truncate($this->keywords, 60, TRUE, TRUE)));
     }
     // Use the default 'Search' title.
     return $this->t('Search');
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..48c975a 100644
--- a/core/modules/simpletest/src/TestBase.php
+++ b/core/modules/simpletest/src/TestBase.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\simpletest;
 
+use Drupal\Component\Utility\SafeStringInterface;
 use Drupal\Component\Utility\Crypt;
 use Drupal\Component\Utility\Random;
 use Drupal\Component\Utility\SafeMarkup;
@@ -395,7 +396,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 +655,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 +696,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 +726,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..82a8869 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;
@@ -245,6 +246,9 @@ function drupalGetNodeByTitle($title, $reset = FALSE) {
     if ($reset) {
       \Drupal::entityManager()->getStorage('node')->resetCache();
     }
+    if ($title instanceof SafeStringInterface) {
+      $title = (string) $title;
+    }
     $nodes = entity_load_multiple_by_properties('node', array('title' => $title));
     // Load the first node returned from the database.
     $returned_node = reset($nodes);
@@ -1690,6 +1694,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 +2428,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 +2448,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 +2810,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 +2999,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..f0fb710 100644
--- a/core/modules/system/src/Tests/Common/XssUnitTest.php
+++ b/core/modules/system/src/Tests/Common/XssUnitTest.php
@@ -41,7 +41,7 @@ function testT() {
     $text = t('Placeholder text: %value', array('%value' => '<script>'));
     $this->assertEqual($text, 'Placeholder text: <em class="placeholder">&lt;script&gt;</em>', 't replaces, escapes and themes string.');
     $text = t('Verbatim text: !value', array('!value' => '<script>'));
-    $this->assertEqual($text, 'Verbatim text: <script>', 't replaces verbatim string as-is.');
+    $this->assertEqual($text, 'Verbatim text: ', 't XSS admin filters string.');
   }
 
   /**
@@ -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/src/Tests/Theme/TwigExtensionTest.php b/core/modules/system/src/Tests/Theme/TwigExtensionTest.php
index 7e25ae5..e67fe61 100644
--- a/core/modules/system/src/Tests/Theme/TwigExtensionTest.php
+++ b/core/modules/system/src/Tests/Theme/TwigExtensionTest.php
@@ -47,6 +47,9 @@ function testTwigExtensionFilter() {
 
     $this->drupalGet('twig-extension-test/filter');
     $this->assertText('Every plant is not a mineral.', 'Success: String filtered.');
+    $this->assertRaw('<em class="placeholder">&lt;strong&gt;In a placeholder&lt;/strong&gt;</em>');
+    $this->assertRaw('<strong>Passed through</strong>');
+    $this->assertRaw('<strong>translated strings are marked safe<script></script></strong>');
   }
 
   /**
diff --git a/core/modules/system/src/Tests/Theme/TwigTransTest.php b/core/modules/system/src/Tests/Theme/TwigTransTest.php
index 21aef18..52c751e 100644
--- a/core/modules/system/src/Tests/Theme/TwigTransTest.php
+++ b/core/modules/system/src/Tests/Theme/TwigTransTest.php
@@ -134,22 +134,28 @@ protected function assertTwigTransTags() {
     );
 
     $this->assertRaw(
-      'ESCAPEE: &amp;&quot;&lt;&gt;',
+      'ESCAPEE: &lt;script&gt;&amp;&quot;&lt;&gt;&lt;/script&gt;',
       '{{ token }} was successfully translated and prefixed with "@".'
     );
 
+    // @see \Drupal\Core\Template\TwigNodeTrans::compileString()
     $this->assertRaw(
-      'PAS-THRU: &"<>',
-      '{{ token|passthrough }} was successfully translated and prefixed with "!".'
+      'Y U NO LET ME XSS: &amp;"&lt;&gt;',
+      '{{ token|passthrough }} (unsafe) was successfully translated and prefixed with "!".'
     );
 
     $this->assertRaw(
-      'PLAYSHOLDR: <em class="placeholder">&amp;&quot;&lt;&gt;</em>',
+      'YAY XSS: <script></script>',
+      '{{ token|passthrough }} (safe) was successfully translated and prefixed with "!".'
+    );
+
+    $this->assertRaw(
+      'PLAYSHOLDR: <em class="placeholder">&lt;script&gt;&amp;&quot;&lt;&gt;&lt;/script&gt;</em>',
       '{{ token|placeholder }} was successfully translated and prefixed with "%".'
     );
 
     $this->assertRaw(
-      'DIS complex token HAZ LENGTH OV: 3. IT CONTAYNZ: <em class="placeholder">12345</em> AN &amp;&quot;&lt;&gt;. LETS PAS TEH BAD TEXT THRU: &"<>.',
+      'DIS complex token HAZ LENGTH OV: 3. IT CONTAYNZ: <em class="placeholder">12345</em> AN &lt;script&gt;&amp;&quot;&lt;&gt;&lt;/script&gt;. LETS FIRRTER TEH BAD TXT: &amp;"&lt;&gt;',
       '{{ complex.tokens }} were successfully translated with appropriate prefixes.'
     );
 
@@ -253,14 +259,17 @@ protected function poFileContents($langcode) {
 msgid "Escaped: @string"
 msgstr "ESCAPEE: @string"
 
-msgid "Pass-through: !string"
-msgstr "PAS-THRU: !string"
+msgid "Admin filtered: !string"
+msgstr "Y U NO LET ME XSS: !string"
+
+msgid "Safe: !safe_string"
+msgstr "YAY XSS: !safe_string"
 
 msgid "Placeholder: %string"
 msgstr "PLAYSHOLDR: %string"
 
-msgid "This @token.name has a length of: @count. It contains: %token.numbers and @token.bad_text. Lets pass the bad text through: !token.bad_text."
-msgstr "DIS @token.name HAZ LENGTH OV: @count. IT CONTAYNZ: %token.numbers AN @token.bad_text. LETS PAS TEH BAD TEXT THRU: !token.bad_text."
+msgid "This @token.name has a length of: @count. It contains: %token.numbers and @token.bad_text. Let's filter the bad text: !token.bad_text."
+msgstr "DIS @token.name HAZ LENGTH OV: @count. IT CONTAYNZ: %token.numbers AN @token.bad_text. LETS FIRRTER TEH BAD TXT: !token.bad_text."
 
 msgctxt "Lolspeak"
 msgid "I have context."
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..67143ca 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',
     ));
@@ -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/system/tests/modules/twig_extension_test/src/TwigExtensionTestController.php b/core/modules/system/tests/modules/twig_extension_test/src/TwigExtensionTestController.php
index 4ff849a..f45264b 100644
--- a/core/modules/system/tests/modules/twig_extension_test/src/TwigExtensionTestController.php
+++ b/core/modules/system/tests/modules/twig_extension_test/src/TwigExtensionTestController.php
@@ -6,11 +6,13 @@
  */
 
 namespace Drupal\twig_extension_test;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
 
 /**
  * Controller routines for Twig extension test routes.
  */
 class TwigExtensionTestController {
+  use StringTranslationTrait;
 
   /**
    * Menu callback for testing Twig filters in a Twig template.
@@ -19,6 +21,9 @@ public function testFilterRender() {
     return array(
       '#theme' => 'twig_extension_test_filter',
       '#message' => 'Every animal is not a mineral.',
+      '#placeholder_text' => '<strong>In a placeholder</strong>',
+      '#passthrough_text' => '<strong>Passed through<script></script></strong>',
+      '#passthrough_safe_text' => $this->t('<strong>translated strings are marked safe<script></script></strong>'),
     );
   }
 
diff --git a/core/modules/system/tests/modules/twig_extension_test/templates/twig_extension_test.filter.html.twig b/core/modules/system/tests/modules/twig_extension_test/templates/twig_extension_test.filter.html.twig
index 1e224d0..2e99d2b 100644
--- a/core/modules/system/tests/modules/twig_extension_test/templates/twig_extension_test.filter.html.twig
+++ b/core/modules/system/tests/modules/twig_extension_test/templates/twig_extension_test.filter.html.twig
@@ -1,3 +1,16 @@
 <div class="testfilter">
   {{ message|testfilter }}
 </div>
+<!--
+ The placeholder and passthrough filters should only be used in trans tags but
+ their effects need to be tested outside trans tags.
+-->
+<div>
+  {{ placeholder_text|placeholder }}
+</div>
+<div>
+  {{ passthrough_text|passthrough }}
+</div>
+<div>
+  {{ passthrough_safe_text|passthrough }}
+</div>
\ No newline at end of file
diff --git a/core/modules/system/tests/modules/twig_extension_test/twig_extension_test.module b/core/modules/system/tests/modules/twig_extension_test/twig_extension_test.module
index 7e89470..899193c 100644
--- a/core/modules/system/tests/modules/twig_extension_test/twig_extension_test.module
+++ b/core/modules/system/tests/modules/twig_extension_test/twig_extension_test.module
@@ -11,7 +11,7 @@
 function twig_extension_test_theme($existing, $type, $theme, $path) {
   return array(
     'twig_extension_test_filter' => array(
-      'variables' => array('message' => NULL),
+      'variables' => array('message' => NULL, 'placeholder_text' => NULL, 'passthrough_text' => NULL, 'passthrough_safe_text' => NULL),
       'template' => 'twig_extension_test.filter',
     ),
     'twig_extension_test_function' => array(
diff --git a/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.trans.html.twig b/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.trans.html.twig
index 9623ba5..9106d7f 100644
--- a/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.trans.html.twig
+++ b/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.trans.html.twig
@@ -41,7 +41,7 @@
 </div>
 
 {# Test trans tag with different filters applied to tokens. #}
-{% set string = '&"<>' %}
+{% set string = '<script>&"<></script>' %}
 <div>
   {% trans %}
     Escaped: {{ string }}
@@ -49,7 +49,13 @@
 </div>
 <div>
   {% trans %}
-    Pass-through: {{ string|passthrough }}
+    Admin filtered: {{ string|passthrough }}
+  {% endtrans %}
+</div>
+<div>
+  {% trans %}
+    {# Note this should never be necessary: we can just print the safe string in the template directly. #}
+    Safe: {{ safe_string|passthrough }}
   {% endtrans %}
 </div>
 <div>
@@ -59,11 +65,11 @@
 </div>
 
 {# Test trans tag with complex tokens. #}
-{% set token = {'name': 'complex token', 'numbers': '12345', 'bad_text': '&"<>' } %}
+{% set token = {'name': 'complex token', 'numbers': '12345', 'bad_text': '<script>&"<></script>' } %}
 {% set count = token|length %}
 <div>
   {% trans %}
-    This {{ token.name }} has a length of: {{ count }}. It contains: {{ token.numbers|placeholder }} and {{ token.bad_text }}. Lets pass the bad text through: {{ token.bad_text|passthrough }}.
+    This {{ token.name }} has a length of: {{ count }}. It contains: {{ token.numbers|placeholder }} and {{ token.bad_text }}. Let's filter the bad text: {{ token.bad_text|passthrough }}.
   {% endtrans %}
 </div>
 
diff --git a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module
index 52c0ef4..71e551a 100644
--- a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module
+++ b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module
@@ -1,5 +1,7 @@
 <?php
 
+use Drupal\Core\Render\SafeString;
+
 /**
  * Implements hook_theme().
  */
@@ -12,7 +14,7 @@ function twig_theme_test_theme($existing, $type, $theme, $path) {
     'template' => 'twig_theme_test.php_variables',
   );
   $items['twig_theme_test_trans'] = array(
-    'variables' => array(),
+    'variables' => array('safe_string' => SafeString::create('<script></script>')),
     'template' => 'twig_theme_test.trans',
   );
   $items['twig_theme_test_placeholder_outside_trans'] = array(
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/Derivative/ViewsBlock.php b/core/modules/views/src/Plugin/Derivative/ViewsBlock.php
index 3ebfc5f..490ec99 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,9 @@ 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 hack, it should not be needed -- 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/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/exposed_form/InputRequired.php b/core/modules/views/src/Plugin/views/exposed_form/InputRequired.php
index 110115a..b8525cf 100644
--- a/core/modules/views/src/Plugin/views/exposed_form/InputRequired.php
+++ b/core/modules/views/src/Plugin/views/exposed_form/InputRequired.php
@@ -81,8 +81,11 @@ public function preRender($values) {
         'label' => '',
         'relationship' => 'none',
         'group_type' => 'group',
-        'content' => $this->options['text_input_required'],
-        'format' => $this->options['text_input_required_format'],
+        'content' => [
+          // @todo check if this is correct
+          'value' => $this->options['text_input_required'],
+          'format' => $this->options['text_input_required_format'],
+        ],
       );
       $handler = Views::handlerManager('area')->getHandler($options);
       $handler->init($this->view, $this->displayHandler, $options);
diff --git a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
index 152d4e9..fae542b 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.');
 
diff --git a/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php b/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php
index 1278a6d..5aaea8d 100644
--- a/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php
+++ b/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php
@@ -166,13 +166,11 @@ function providerCheckPlain() {
    *   The expected result from calling the function.
    * @param string $message
    *   The message to display as output to the test.
-   * @param bool $expected_is_safe
-   *   Whether the result is expected to be safe for HTML display.
    */
-  function testFormat($string, $args, $expected, $message, $expected_is_safe) {
+  function testFormat($string, $args, $expected, $message) {
     $result = SafeMarkup::format($string, $args);
     $this->assertEquals($expected, $result, $message);
-    $this->assertEquals($expected_is_safe, SafeMarkup::isSafe($result), 'SafeMarkup::format correctly sets the result as safe or not safe.');
+    $this->assertTrue(SafeMarkup::isSafe($result), 'SafeMarkup::format correctly sets the result as safe for HTML display.');
   }
 
   /**
@@ -181,13 +179,13 @@ function testFormat($string, $args, $expected, $message, $expected_is_safe) {
    * @see testFormat()
    */
   function providerFormat() {
-    $tests[] = array('Simple text', array(), 'Simple text', 'SafeMarkup::format leaves simple text alone.', TRUE);
-    $tests[] = array('Escaped text: @value', array('@value' => '<script>'), 'Escaped text: &lt;script&gt;', 'SafeMarkup::format replaces and escapes string.', TRUE);
-    $tests[] = array('Escaped text: @value', array('@value' => SafeMarkupTestSafeString::create('<span>Safe HTML</span>')), 'Escaped text: <span>Safe HTML</span>', 'SafeMarkup::format does not escape an already safe string.', TRUE);
-    $tests[] = array('Placeholder text: %value', array('%value' => '<script>'), 'Placeholder text: <em class="placeholder">&lt;script&gt;</em>', 'SafeMarkup::format replaces, escapes and themes string.', TRUE);
-    $tests[] = array('Placeholder text: %value', array('%value' => SafeMarkupTestSafeString::create('<span>Safe HTML</span>')), 'Placeholder text: <em class="placeholder"><span>Safe HTML</span></em>', 'SafeMarkup::format does not escape an already safe string themed as a placeholder.', TRUE);
-    $tests[] = array('Verbatim text: !value', array('!value' => '<script>'), 'Verbatim text: <script>', 'SafeMarkup::format replaces verbatim string as-is.', FALSE);
-    $tests[] = array('Verbatim text: !value', array('!value' => SafeMarkupTestSafeString::create('<span>Safe HTML</span>')), 'Verbatim text: <span>Safe HTML</span>', 'SafeMarkup::format replaces verbatim string as-is.', TRUE);
+    $tests[] = array('Simple text', array(), 'Simple text', 'SafeMarkup::format leaves simple text alone.');
+    $tests[] = array('Escaped text: @value', array('@value' => '<script>'), 'Escaped text: &lt;script&gt;', 'SafeMarkup::format replaces and escapes string.');
+    $tests[] = array('Escaped text: @value', array('@value' => SafeMarkupTestSafeString::create('<span>Safe HTML</span>')), 'Escaped text: <span>Safe HTML</span>', 'SafeMarkup::format does not escape an already safe string.');
+    $tests[] = array('Placeholder text: %value', array('%value' => '<script>'), 'Placeholder text: <em class="placeholder">&lt;script&gt;</em>', 'SafeMarkup::format replaces, escapes and themes string.');
+    $tests[] = array('Placeholder text: %value', array('%value' => SafeMarkupTestSafeString::create('<span>Safe HTML</span>')), 'Placeholder text: <em class="placeholder"><span>Safe HTML</span></em>', 'SafeMarkup::format does not escape an already safe string themed as a placeholder.');
+    $tests[] = array('Verbatim text: !value', array('!value' => '<script>'), 'Verbatim text: ', 'SafeMarkup::format XSS admin filters an unsafe !replacement.');
+    $tests[] = array('Verbatim text: !value', array('!value' => SafeMarkupTestSafeString::create('<script>')), 'Verbatim text: <script>', 'SafeMarkup::format does not XSS admin filter a safe !replacement.');
 
     return $tests;
   }
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/Extension/RequiredModuleUninstallValidatorTest.php b/core/tests/Drupal/Tests/Core/Extension/RequiredModuleUninstallValidatorTest.php
index a5f6fbb..64962c7 100644
--- a/core/tests/Drupal/Tests/Core/Extension/RequiredModuleUninstallValidatorTest.php
+++ b/core/tests/Drupal/Tests/Core/Extension/RequiredModuleUninstallValidatorTest.php
@@ -73,6 +73,7 @@ public function testValidateRequired() {
 
     $expected = ["The $module module is required"];
     $reasons = $this->uninstallValidator->validate($module);
+    $reasons[0] = (string) $reasons[0];
     $this->assertSame($expected, $reasons);
   }
 
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/TranslationManagerTest.php b/core/tests/Drupal/Tests/Core/StringTranslation/TranslationManagerTest.php
index 53165c9..61b4682 100644
--- a/core/tests/Drupal/Tests/Core/StringTranslation/TranslationManagerTest.php
+++ b/core/tests/Drupal/Tests/Core/StringTranslation/TranslationManagerTest.php
@@ -34,19 +34,19 @@ protected function setUp() {
    */
   public function providerTestFormatPlural() {
     return array(
-      array(1, 'Singular', '@count plural', array(), array(), 'Singular', TRUE),
-      array(2, 'Singular', '@count plural', array(), array(), '2 plural', TRUE),
+      array(1, 'Singular', '@count plural', array(), array(), 'Singular'),
+      array(2, 'Singular', '@count plural', array(), array(), '2 plural'),
       // @todo support locale_get_plural
-      array(2, 'Singular', '@count @arg', array('@arg' => '<script>'), array(), '2 &lt;script&gt;', TRUE),
-      array(2, 'Singular', '@count %arg', array('%arg' => '<script>'), array(), '2 <em class="placeholder">&lt;script&gt;</em>', TRUE),
-      array(2, 'Singular', '@count !arg', array('!arg' => '<script>'), array(), '2 <script>', FALSE),
+      array(2, 'Singular', '@count @arg', array('@arg' => '<script>'), array(), '2 &lt;script&gt;'),
+      array(2, 'Singular', '@count %arg', array('%arg' => '<script>'), array(), '2 <em class="placeholder">&lt;script&gt;</em>'),
+      array(2, 'Singular', '@count !arg', array('!arg' => '<script>'), array(), '2 '),
     );
   }
 
   /**
    * @dataProvider providerTestFormatPlural
    */
-  public function testFormatPlural($count, $singular, $plural, array $args = array(), array $options = array(), $expected, $safe) {
+  public function testFormatPlural($count, $singular, $plural, array $args = array(), array $options = array(), $expected) {
     $translator = $this->getMock('\Drupal\Core\StringTranslation\Translator\TranslatorInterface');
     $translator->expects($this->once())
       ->method('getStringTranslation')
@@ -56,7 +56,7 @@ public function testFormatPlural($count, $singular, $plural, array $args = array
     $this->translationManager->addTranslator($translator);
     $result = $this->translationManager->formatPlural($count, $singular, $plural, $args, $options);
     $this->assertEquals($expected, $result);
-    $this->assertEquals(SafeMarkup::isSafe($result), $safe);
+    $this->assertTrue(SafeMarkup::isSafe($result));
   }
 
 }
diff --git a/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php
index 6659945..08bb2c8 100644
--- a/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php
+++ b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php
@@ -488,16 +488,16 @@ public function testGenerateBubbleableMetadata() {
     $expected_link_markup = '<a href="/test-route-1">Test</a>';
 
     // Test ::generate().
-    $this->assertSame($expected_link_markup, $this->linkGenerator->generate('Test', $url));
+    $this->assertSame($expected_link_markup, (string) $this->linkGenerator->generate('Test', $url));
     $generated_link = $this->linkGenerator->generate('Test', $url, TRUE);
-    $this->assertSame($expected_link_markup, $generated_link->getGeneratedLink());
+    $this->assertSame($expected_link_markup, (string) $generated_link->getGeneratedLink());
     $this->assertInstanceOf('\Drupal\Core\Render\BubbleableMetadata', $generated_link);
 
     // Test ::generateFromLink().
     $link = new Link('Test', $url);
-    $this->assertSame($expected_link_markup, $this->linkGenerator->generateFromLink($link));
+    $this->assertSame($expected_link_markup, (string) $this->linkGenerator->generateFromLink($link));
     $generated_link = $this->linkGenerator->generateFromLink($link, TRUE);
-    $this->assertSame($expected_link_markup, $generated_link->getGeneratedLink());
+    $this->assertSame($expected_link_markup, (string) $generated_link->getGeneratedLink());
     $this->assertInstanceOf('\Drupal\Core\Render\BubbleableMetadata', $generated_link);
   }
 
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;
   }
 
