diff --git a/core/includes/form.inc b/core/includes/form.inc
index 42206a444369e5da83c51a94047c8dc2ef2d6665..91d2d6df08394e50d6f7f7d088167de01217572a 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -983,7 +983,7 @@ function form_select_options($element, $choices = NULL) {
       $options .= form_select_options($element, $choice);
       $options .= '</optgroup>';
     }
-    elseif (is_object($choice)) {
+    elseif (is_object($choice) && isset($choice->option)) {
       $options .= form_select_options($element, $choice->option);
     }
     else {
diff --git a/core/lib/Drupal/Core/Annotation/Translation.php b/core/lib/Drupal/Core/Annotation/Translation.php
index ad32b5e196c0871b3fa70047666575c0ebe4396e..bb754b6a66ea864cb14cf6906d23d4f0676c7bab 100644
--- a/core/lib/Drupal/Core/Annotation/Translation.php
+++ b/core/lib/Drupal/Core/Annotation/Translation.php
@@ -8,7 +8,7 @@
 namespace Drupal\Core\Annotation;
 
 use Drupal\Component\Annotation\AnnotationBase;
-use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslationWrapper;
 
 /**
  * @defgroup plugin_translatable Translatable plugin metadata
@@ -52,12 +52,11 @@
  * @ingroup plugin_translatable
  */
 class Translation extends AnnotationBase {
-  use StringTranslationTrait;
 
   /**
-   * The translation of the value passed to the constructor of the class.
+   * The string translation object.
    *
-   * @var string
+   * @var \Drupal\Core\StringTranslation\TranslationWrapper
    */
   protected $translation;
 
@@ -83,11 +82,11 @@ public function __construct(array $values) {
         'context' => $values['context'],
       );
     }
-    $this->translation = $this->t($string, $arguments, $options);
+    $this->translation = new TranslationWrapper($string, $arguments, $options);
   }
 
   /**
-   * Implements Drupal\Core\Annotation\AnnotationInterface::get().
+   * {@inheritdoc}
    */
   public function get() {
     return $this->translation;
diff --git a/core/lib/Drupal/Core/Config/StorableConfigBase.php b/core/lib/Drupal/Core/Config/StorableConfigBase.php
index 4a5fd1d7bcbc5a9d326b9b78a1e4282fd355663b..60a1d023e2995f143e5e56a6b37bbe32741b1566 100644
--- a/core/lib/Drupal/Core/Config/StorableConfigBase.php
+++ b/core/lib/Drupal/Core/Config/StorableConfigBase.php
@@ -183,7 +183,7 @@ protected function castValue($key, $value) {
     if ($element && ($element instanceof Undefined || $element instanceof Ignore)) {
       return $value;
     }
-    if ((is_scalar($value) || $value === NULL)) {
+    if (is_scalar($value) || $value === NULL) {
       if ($element && $element instanceof PrimitiveInterface) {
         // Special handling for integers and floats since the configuration
         // system is primarily concerned with saving values from the Form API
diff --git a/core/lib/Drupal/Core/Form/OptGroup.php b/core/lib/Drupal/Core/Form/OptGroup.php
index f94d62dc43f61442497e6338e1fbe17cbde5a52b..21e9855962d51c791de4d9f70a827d24ec8cfae4 100644
--- a/core/lib/Drupal/Core/Form/OptGroup.php
+++ b/core/lib/Drupal/Core/Form/OptGroup.php
@@ -43,7 +43,7 @@ public static function flattenOptions(array $array) {
    */
   protected static function doFlattenOptions(array $array, array &$options) {
     foreach ($array as $key => $value) {
-      if (is_object($value)) {
+      if (is_object($value) && isset($value->option)) {
         static::doFlattenOptions($value->option, $options);
       }
       elseif (is_array($value)) {
diff --git a/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php b/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php
new file mode 100644
index 0000000000000000000000000000000000000000..1285cb783f07e277d0cedc73e11069b70f22d908
--- /dev/null
+++ b/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\StringTranslation\TranslationWrapper.
+ */
+
+namespace Drupal\Core\StringTranslation;
+
+/**
+ * Provides a class to wrap a translatable string.
+ *
+ * This class can be used to delay translating strings until the translation
+ * system is ready. This is useful for using translation in very low level
+ * subsystems like entity definition and stream wrappers.
+ *
+ * @see \Drupal\Core\Annotation\Translation
+ */
+class TranslationWrapper {
+  use StringTranslationTrait;
+
+  /**
+   * The string to be translated.
+   *
+   * @var string
+   */
+  protected $string;
+
+  /**
+   * The translation arguments.
+   *
+   * @var array
+   */
+  protected $arguments;
+
+  /**
+   * The translation options.
+   *
+   * @var array
+   */
+  protected $options;
+
+  /**
+   * Constructs a new class instance.
+   *
+   * Parses values passed into this class through the t() function in Drupal and
+   * handles an optional context for the string.
+   *
+   * @param string $string
+   *   The string that is to be translated.
+   * @param array $arguments
+   *   (optional) An array with placeholder replacements, keyed by placeholder.
+   * @param array $options
+   *   (optional) An array of additional options.
+   */
+  public function __construct($string, array $arguments = array(), array $options = array()) {
+    $this->string = $string;
+    $this->arguments = $arguments;
+    $this->options = $options;
+  }
+
+  /**
+   * Implements the magic __toString() method.
+   */
+  public function __toString() {
+    return $this->render();
+  }
+
+  /**
+   * Renders the object as a string.
+   *
+   * @return string
+   *   The translated string.
+   */
+  public function render() {
+    return $this->t($this->string, $this->arguments, $this->options);
+  }
+
+  /**
+   * Magic __sleep() method to avoid serializing the string translator.
+   */
+  public function __sleep() {
+    return array('string', 'arguments', 'options');
+  }
+
+}
diff --git a/core/modules/block/src/BlockBase.php b/core/modules/block/src/BlockBase.php
index 68f5f36189149058c106a611d8ff502eae3fa9c9..df3507c487a74be65001458165251277dfec904d 100644
--- a/core/modules/block/src/BlockBase.php
+++ b/core/modules/block/src/BlockBase.php
@@ -36,7 +36,9 @@ public function label() {
     }
 
     $definition = $this->getPluginDefinition();
-    return $definition['admin_label'];
+    // Cast the admin label to a string since it is an object.
+    // @see \Drupal\Core\StringTranslation\TranslationWrapper
+    return (string) $definition['admin_label'];
   }
 
   /**
diff --git a/core/modules/editor/src/Tests/EditorManagerTest.php b/core/modules/editor/src/Tests/EditorManagerTest.php
index 997d861f7cbce9a3d899e39c7873832281a0d8a7..51cd2854b92f7c9276c5b623404abaa119188345 100644
--- a/core/modules/editor/src/Tests/EditorManagerTest.php
+++ b/core/modules/editor/src/Tests/EditorManagerTest.php
@@ -82,7 +82,7 @@ public function testManager() {
     $this->editorManager->clearCachedDefinitions();
 
     // Case 2: a text editor available.
-    $this->assertIdentical(array('unicorn' => 'Unicorn Editor'), $this->editorManager->listOptions(), 'When some text editor is enabled, the manager works correctly.');
+    $this->assertIdentical('Unicorn Editor', (string) $this->editorManager->listOptions()['unicorn'], 'When some text editor is enabled, the manager works correctly.');
 
     // Case 3: a text editor available & associated (but associated only with
     // the 'Full HTML' text format).
diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module
index f626203b8de0ce7cdeb7b5f46164f8a251fbc108..73c6db63d508af844d180caa861f92c815ea8738 100644
--- a/core/modules/locale/locale.module
+++ b/core/modules/locale/locale.module
@@ -15,6 +15,7 @@
 use Drupal\Component\Utility\Xss;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Language\Language;
+use Drupal\Core\StringTranslation\TranslationWrapper;
 use Drupal\language\Entity\Language as LanguageEntity;
 use Drupal\Component\Utility\Crypt;
 use Symfony\Component\HttpFoundation\Request;
@@ -209,9 +210,9 @@ function locale_theme() {
 function locale_stream_wrappers() {
   $wrappers = array(
     'translations' => array(
-      'name' => t('Translation files'),
+      'name' => new TranslationWrapper('Translation files'),
       'class' => 'Drupal\locale\TranslationsStream',
-      'description' => t('Translation files'),
+      'description' => new TranslationWrapper('Translation files'),
       'type' => STREAM_WRAPPERS_LOCAL_HIDDEN,
     ),
   );
diff --git a/core/modules/locale/src/Tests/LocaleLocaleLookupTest.php b/core/modules/locale/src/Tests/LocaleLocaleLookupTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..bf126c7caabec7a7cd1ead510cde1d414b8e4c98
--- /dev/null
+++ b/core/modules/locale/src/Tests/LocaleLocaleLookupTest.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\locale\Tests\LocaleLocaleLookupTest.
+ */
+
+namespace Drupal\locale\Tests;
+
+use Drupal\Core\Language\Language;
+use Drupal\simpletest\WebTestBase;
+
+class LocaleLocaleLookupTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('locale');
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Test LocaleLookup',
+      'description' => 'Tests LocaleLookup does not cause circular references.',
+      'group' => 'Locale',
+    );
+  }
+
+  /**
+   * Tests hasTranslation().
+   */
+  public function testCircularDependency() {
+    // Change the language default object to different values.
+    $new_language_default = new Language(array(
+      'id' => 'fr',
+      'name' => 'French',
+      'direction' => 0,
+      'weight' => 0,
+      'method_id' => 'language-default',
+      'default' => TRUE,
+    ));
+    language_save($new_language_default);
+    $this->drupalLogin($this->root_user);
+    // Ensure that we can enable early_translation_test on a non-english site.
+    $this->drupalPostForm('admin/modules', array('modules[Testing][early_translation_test][enable]' => TRUE), t('Save configuration'));
+    $this->assertResponse(200);
+  }
+
+}
diff --git a/core/modules/locale/tests/modules/early_translation_test/early_translation_test.info.yml b/core/modules/locale/tests/modules/early_translation_test/early_translation_test.info.yml
new file mode 100644
index 0000000000000000000000000000000000000000..cfc6f7ea5be61f6a1d4bbb827405d61c96c03463
--- /dev/null
+++ b/core/modules/locale/tests/modules/early_translation_test/early_translation_test.info.yml
@@ -0,0 +1,7 @@
+name: 'Early translation test'
+type: module
+description: 'Support module for testing early bootstrap getting of annotations with translations.'
+core: 8.x
+package: Testing
+version: VERSION
+
diff --git a/core/modules/locale/tests/modules/early_translation_test/early_translation_test.services.yml b/core/modules/locale/tests/modules/early_translation_test/early_translation_test.services.yml
new file mode 100644
index 0000000000000000000000000000000000000000..13718c287b8fb53eedca558a80f9323b57301eb7
--- /dev/null
+++ b/core/modules/locale/tests/modules/early_translation_test/early_translation_test.services.yml
@@ -0,0 +1,6 @@
+services:
+  authentication.early_translation_test:
+    class: Drupal\early_translation_test\Auth
+    arguments: ['@entity.manager']
+    tags:
+      - { name: authentication_provider, priority: 100 }
diff --git a/core/modules/locale/tests/modules/early_translation_test/src/Auth.php b/core/modules/locale/tests/modules/early_translation_test/src/Auth.php
new file mode 100644
index 0000000000000000000000000000000000000000..afb5ea1685f554b2484a1f0d3244604e0f97b612
--- /dev/null
+++ b/core/modules/locale/tests/modules/early_translation_test/src/Auth.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\early_translation_test\Auth.
+ */
+
+namespace Drupal\early_translation_test;
+
+use Drupal\Core\Authentication\AuthenticationProviderInterface;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
+
+/**
+ * Test authentication provider.
+ */
+class Auth implements AuthenticationProviderInterface {
+
+  /**
+   * The user storage.
+   *
+   * @var \Drupal\user\UserStorageInterface
+   */
+  protected $userStorage;
+
+  /**
+   * Constructs an authentication provider object.
+   *
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager service.
+   */
+  public function __construct(EntityManagerInterface $entity_manager) {
+    // Authentication providers are called early during in the bootstrap.
+    // Getting the user storage used to result in a circular reference since
+    // translation involves a call to \Drupal\locale\LocaleLookup that tries to
+    // get the user roles.
+    // @see https://drupal.org/node/2241461
+    $this->userStorage = $entity_manager->getStorage('user');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function applies(Request $request) {
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function authenticate(Request $request) {
+    return NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function cleanup(Request $request) {}
+
+  /**
+   * {@inheritdoc}
+   */
+  public function handleException(GetResponseForExceptionEvent $event) {
+    return FALSE;
+  }
+
+}
diff --git a/core/modules/system/src/Tests/Plugin/Discovery/DiscoveryTestBase.php b/core/modules/system/src/Tests/Plugin/Discovery/DiscoveryTestBase.php
index 9d0be5469665fe32637da613805b97b3bd1328c9..d58cf03770bc274658df2cc2357c756203e2bfd1 100644
--- a/core/modules/system/src/Tests/Plugin/Discovery/DiscoveryTestBase.php
+++ b/core/modules/system/src/Tests/Plugin/Discovery/DiscoveryTestBase.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Tests\Plugin\Discovery;
 
+use Drupal\Core\StringTranslation\TranslationWrapper;
 use Drupal\simpletest\UnitTestBase;
 
 /**
@@ -49,7 +50,7 @@ function testDiscoveryInterface() {
 
     // Ensure that getDefinition() returns the expected definition.
     foreach ($this->expectedDefinitions as $id => $definition) {
-      $this->assertIdentical($this->discovery->getDefinition($id), $definition);
+      $this->assertDefinitionIdentical($this->discovery->getDefinition($id), $definition);
     }
 
     // Ensure that an empty array is returned if no plugin definitions are found.
@@ -58,5 +59,30 @@ function testDiscoveryInterface() {
     // Ensure that NULL is returned as the definition of a non-existing plugin.
     $this->assertIdentical($this->emptyDiscovery->getDefinition('non_existing', FALSE), NULL, 'NULL returned as the definition of a non-existing plugin.');
   }
+
+  /**
+   * Asserts a definition against an expected definition.
+   *
+   * Converts any instances of \Drupal\Core\Annotation\Translation to a string.
+   *
+   * @param array $definition
+   *   The definition to test.
+   * @param array $expected_definition
+   *   The expected definition to test against.
+   *
+   * @return bool
+   *   TRUE if the assertion succeeded, FALSE otherwise.
+   */
+  protected function assertDefinitionIdentical(array $definition, array $expected_definition) {
+    $func = function (&$item){
+      if ($item instanceof TranslationWrapper) {
+        $item = (string) $item;
+      }
+    };
+    array_walk_recursive($definition, $func);
+    array_walk_recursive($expected_definition, $func);
+    return $this->assertIdentical($definition, $expected_definition);
+  }
+
 }
 
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index ad3a014b53674ce63fbd29a3a2ceb3652a18a44a..6aea5b886cb855a3c326e48a77ede5d15c369560 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -9,6 +9,7 @@
 use Drupal\Core\Language\Language;
 use Drupal\Core\Extension\Extension;
 use Drupal\Core\Extension\ExtensionDiscovery;
+use Drupal\Core\StringTranslation\TranslationWrapper;
 use Drupal\block\BlockPluginInterface;
 use Drupal\user\UserInterface;
 use Symfony\Component\HttpFoundation\RedirectResponse;
@@ -719,15 +720,15 @@ function system_theme_suggestions_field(array $variables) {
 function system_stream_wrappers() {
   $wrappers = array(
     'public' => array(
-      'name' => t('Public files'),
+      'name' => new TranslationWrapper('Public files'),
       'class' => 'Drupal\Core\StreamWrapper\PublicStream',
-      'description' => t('Public local files served by the webserver.'),
+      'description' => new TranslationWrapper('Public local files served by the webserver.'),
       'type' => STREAM_WRAPPERS_LOCAL_NORMAL,
     ),
     'temporary' => array(
-      'name' => t('Temporary files'),
+      'name' => new TranslationWrapper('Temporary files'),
       'class' => 'Drupal\Core\StreamWrapper\TemporaryStream',
-      'description' => t('Temporary local files for upload and previews.'),
+      'description' => new TranslationWrapper('Temporary local files for upload and previews.'),
       'type' => STREAM_WRAPPERS_LOCAL_HIDDEN,
     ),
   );
@@ -735,9 +736,9 @@ function system_stream_wrappers() {
   // Only register the private file stream wrapper if a file path has been set.
   if (\Drupal::config('system.file')->get('path.private')) {
     $wrappers['private'] = array(
-      'name' => t('Private files'),
+      'name' => new TranslationWrapper('Private files'),
       'class' => 'Drupal\Core\StreamWrapper\PrivateStream',
-      'description' => t('Private local files served by Drupal.'),
+      'description' => new TranslationWrapper('Private local files served by Drupal.'),
       'type' => STREAM_WRAPPERS_LOCAL_NORMAL,
     );
   }
diff --git a/core/modules/views/src/Entity/View.php b/core/modules/views/src/Entity/View.php
index 87a56745062f90a22e4378d7d1f31387f0c4ec48..db5ba76104541eb2bcbd537bf10db80fe75c8154 100644
--- a/core/modules/views/src/Entity/View.php
+++ b/core/modules/views/src/Entity/View.php
@@ -195,7 +195,9 @@ public function addDisplay($plugin_id = 'page', $title = NULL, $id = NULL) {
     $display_options = array(
       'display_plugin' => $plugin_id,
       'id' => $id,
-      'display_title' => $title,
+      // Cast the display title to a string since it is an object.
+      // @see \Drupal\Core\StringTranslation\TranslationWrapper
+      'display_title' => (string) $title,
       'position' => $id === 'default' ? 0 : count($this->display),
       'provider' => $plugin['provider'],
       'display_options' => array(),
diff --git a/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php b/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php
index 902b5438a226f3e948665d26e9beb147a5db511d..562ed680e5b14a7a8276cfb214bb403467706551 100644
--- a/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php
+++ b/core/modules/views/src/Plugin/views/filter/FilterPluginBase.php
@@ -1160,11 +1160,13 @@ protected function prepareFilterSelectOptions(&$options) {
       }
       // FAPI has some special value to allow hierarchy.
       // @see _form_options_flatten
-      elseif (is_object($label)) {
+      elseif (is_object($label) && isset($label->option)) {
         $this->prepareFilterSelectOptions($options[$value]->option);
       }
       else {
-        $options[$value] = strip_tags(decode_entities($label));
+        // Cast the label to a string since it can be an object.
+        // @see \Drupal\Core\StringTranslation\TranslationWrapper
+        $options[$value] = strip_tags(decode_entities((string) $label));
       }
     }
   }
diff --git a/core/modules/views/src/Plugin/views/relationship/RelationshipPluginBase.php b/core/modules/views/src/Plugin/views/relationship/RelationshipPluginBase.php
index f1b7b586a8fbf9527ab4575d7f278583b8f03db0..ec40410a8a54da3af8650d6b3bd6b920abc290b1 100644
--- a/core/modules/views/src/Plugin/views/relationship/RelationshipPluginBase.php
+++ b/core/modules/views/src/Plugin/views/relationship/RelationshipPluginBase.php
@@ -79,7 +79,9 @@ protected function defineOptions() {
 
     // Relationships definitions should define a default label, but if they aren't get another default value.
     if (!empty($this->definition['label'])) {
-      $label = $this->definition['label'];
+      // Cast the label to a string since it is an object.
+      // @see \Drupal\Core\StringTranslation\TranslationWrapper
+      $label = (string) $this->definition['label'];
     }
     else {
       $label = !empty($this->definition['field']) ? $this->definition['field'] : $this->definition['base field'];
diff --git a/core/modules/views_ui/src/ViewListBuilder.php b/core/modules/views_ui/src/ViewListBuilder.php
index 68ad5b07081a2ba61bd2efbc678da8d58de9dfd4..a8d3b81118d1a2dfbf79cf854ce4ed5a363ddcd1 100644
--- a/core/modules/views_ui/src/ViewListBuilder.php
+++ b/core/modules/views_ui/src/ViewListBuilder.php
@@ -229,13 +229,15 @@ protected function getDisplaysList(EntityInterface $view) {
     $displays = array();
     foreach ($view->get('display') as $display) {
       $definition = $this->displayManager->getDefinition($display['display_plugin']);
-      if (!empty($definition['admin'])) {
-        $displays[$definition['admin']] = TRUE;
+      if (isset($definition['admin'])) {
+        // Cast the admin label to a string since it is an object.
+        // @see \Drupal\Core\StringTranslation\TranslationWrapper
+        $displays[] = (string) $definition['admin'];
       }
     }
 
-    ksort($displays);
-    return array_keys($displays);
+    sort($displays);
+    return $displays;
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/Annotation/TranslationTest.php b/core/tests/Drupal/Tests/Core/Annotation/TranslationTest.php
index 071df3e8039c2d405283513154c84a559dcec167..bcfe00df1e1b1655cf9fa3c178caaa40ffab1358 100644
--- a/core/tests/Drupal/Tests/Core/Annotation/TranslationTest.php
+++ b/core/tests/Drupal/Tests/Core/Annotation/TranslationTest.php
@@ -62,7 +62,7 @@ public function testGet(array $values, $expected) {
 
     $annotation = new Translation($values);
 
-    $this->assertSame($expected, $annotation->get());
+    $this->assertSame($expected, (string) $annotation->get());
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/Form/OptGroupTest.php b/core/tests/Drupal/Tests/Core/Form/OptGroupTest.php
index 0807448e93de45f950ccae202c055bd8d15d069a..febc6c68ba49c656bf9349b615a20149b2a4f0f7 100644
--- a/core/tests/Drupal/Tests/Core/Form/OptGroupTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/OptGroupTest.php
@@ -50,11 +50,15 @@ public function providerTestFlattenOptions() {
     $object1->option = array('foo' => 'foo');
     $object2 = new \stdClass();
     $object2->option = array(array('foo' => 'foo'), array('foo' => 'foo'));
+    $object3 = new \stdClass();
     return array(
       array(array('foo' => 'foo')),
       array(array(array('foo' => 'foo'))),
       array(array($object1)),
       array(array($object2)),
+      array(array($object1, $object2)),
+      array(array('foo' => $object3)),
+      array(array('foo' => $object3, $object1, array('foo' => 'foo'))),
     );
   }