diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index eac2f97..5ca24f4 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -1487,7 +1487,6 @@ function bootstrap_hooks() {
  * @ingroup sanitization
  */
 function t($string, array $args = array(), array $options = array()) {
-  static $custom_strings;
 
   // Merge in default.
   if (empty($options['langcode'])) {
@@ -1497,21 +1496,10 @@ function t($string, array $args = array(), array $options = array()) {
     $options['context'] = '';
   }
 
-  // First, check for an array of customized strings. If present, use the array
-  // *instead of* database lookups. This is a high performance way to provide a
-  // handful of string replacements. See settings.php for examples.
-  // Cache the $custom_strings variable to improve performance.
-  if (!isset($custom_strings[$options['langcode']])) {
-    $custom_strings[$options['langcode']] = variable_get('locale_custom_strings_' . $options['langcode'], array());
-  }
-  // Custom strings work for English too, even if locale module is disabled.
-  if (isset($custom_strings[$options['langcode']][$options['context']][$string])) {
-    $string = $custom_strings[$options['langcode']][$options['context']][$string];
-  }
-  // Translate with locale module if enabled.
-  elseif ($options['langcode'] != LANGUAGE_SYSTEM && ($options['langcode'] != 'en' || variable_get('locale_translate_english', FALSE)) && function_exists('locale')) {
-    $string = locale($string, $options['context'], $options['langcode']);
-  }
+  $translation = drupal_container()->get('locale.translation')
+    ->translate($options['langcode'], $string, $options['context']);
+  $string = $translation === FALSE ? $string : $translation;
+
   if (empty($args)) {
     return $string;
   }
@@ -2470,6 +2458,7 @@ function drupal_container(Container $new_container = NULL, $rebuild = FALSE) {
       ->setFactoryClass('Drupal\Core\Database\Database')
       ->setFactoryMethod('getConnection')
       ->addArgument('default');
+
     // Register the KeyValueStore factory.
     $container
       ->register('keyvalue', 'Drupal\Core\KeyValueStore\KeyValueFactory')
@@ -2478,6 +2467,14 @@ function drupal_container(Container $new_container = NULL, $rebuild = FALSE) {
       ->register('keyvalue.database', 'Drupal\Core\KeyValueStore\KeyValueDatabaseFactory')
       ->addArgument(new Reference('database'));
 
+    // Register a minimum translation service just in case a translation is
+    // attempted before language initialization.
+    $container
+      ->register('locale.translation.custom', 'Drupal\Core\Translation\CustomStrings');
+    $container
+      ->register('locale.translation', 'Drupal\Core\Translation\TranslationFallback')
+      ->addArgument(new Reference('locale.translation.custom'));
+
     // Register the EntityManager.
     $container->register('plugin.manager.entity', 'Drupal\Core\Entity\EntityManager');
   }
@@ -2654,13 +2651,9 @@ function drupal_installation_attempted() {
  * @ingroup sanitization
  */
 function get_t() {
-  static $t;
-  // This is not converted to drupal_static because there is no point in
-  // resetting this as it can not change in the course of a request.
-  if (!isset($t)) {
-    $t = drupal_installation_attempted() ? 'st' : 't';
-  }
-  return $t;
+  // Use always t() which will work nicely on any situation.
+  // @todo Remove all this logic, maybe keep st() to mark installer strings.
+  return 't';
 }
 
 /**
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 3968682..1a50ac2 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -322,11 +322,22 @@ function install_begin_request(&$install_state) {
     $container->register('config.factory', 'Drupal\Core\Config\ConfigFactory')
       ->addArgument(new Reference('config.storage'))
       ->addArgument(new Reference('dispatcher'));
+    $container->register('locale.translation.custom', 'Drupal\Core\Translation\CustomStrings');
     drupal_container($container);
   }
 
   // Set up $language, so t() caller functions will still work.
   drupal_language_initialize();
+  // Set up file based translation as fall back for localization.
+  drupal_container()
+    ->register('locale.translation.file', 'Drupal\Core\Translation\FileTranslation')
+    ->addArgument(variable_get('locale_translate_file_directory', conf_path() . '/files/translations'));
+  drupal_container()
+     ->register('locale.translation.custom', 'Drupal\Core\Translation\CustomStrings');
+  drupal_container()
+    ->register('locale.translation', 'Drupal\Core\Translation\TranslationFallback')
+    ->addArgument(new Reference('locale.translation.custom'))
+    ->addArgument(new Reference('locale.translation.file'));
 
   require_once DRUPAL_ROOT . '/core/includes/ajax.inc';
   // Override the module list with a minimal set of modules.
@@ -1274,7 +1285,7 @@ function install_select_profile_form($form, &$form_state, $install_state) {
  * @see file_scan_directory()
  */
 function install_find_translations() {
-  $files = install_find_translation_files();
+  $files = drupal_container()->get('locale.translation.file')->findTranslationFiles();
   // English does not need a translation file.
   array_unshift($files, (object) array('name' => 'en'));
   foreach ($files as $key => $file) {
@@ -1290,26 +1301,6 @@ function install_find_translations() {
 }
 
 /**
- * Finds installer translations either for a specific langcode or all languages.
- *
- * @param $langcode
- *   (optional) The language code corresponding to the language for which we
- *   want to find translation files. If omitted, information on all available
- *   files will be returned.
- *
- * @return
- *   An associative array of file information objects keyed by file URIs as
- *   returned by file_scan_directory().
- *
- * @see file_scan_directory()
- */
-function install_find_translation_files($langcode = NULL) {
-  $directory = variable_get('locale_translate_file_directory', conf_path() . '/files/translations');
-  $files = file_scan_directory($directory, '!drupal-\d+\.\d+\.' . (!empty($langcode) ? preg_quote($langcode, '!') : '[^\.]+') . '\.po$!', array('recurse' => FALSE));
-  return $files;
-}
-
-/**
  * Selects which language to use during installation.
  *
  * @param $install_state
diff --git a/core/includes/install.inc b/core/includes/install.inc
index 023e204..e0582f3 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -6,7 +6,7 @@
  */
 
 use Drupal\Core\Database\Database;
-use Drupal\locale\Gettext;
+use Drupal\Core\Translation\FileTranslation;
 
 /**
  * Requirement severity -- Informational message only.
@@ -725,53 +725,23 @@ function drupal_requirements_url($severity) {
  * Use st() if your code will only run during installation and never any other
  * time. Use get_t() if your code could run in either circumstance.
  *
+ * @todo Remove or keep as a t wrapper to mark config strings..?
+ *
  * @see t()
  * @see get_t()
  * @ingroup sanitization
  */
 function st($string, array $args = array(), array $options = array()) {
-  static $strings = NULL;
+  static $langcode;
   global $install_state;
 
-  if (empty($options['context'])) {
-    $options['context'] = '';
-  }
-
-  if (!isset($strings)) {
-    $strings = array();
-    if (isset($install_state['parameters']['langcode'])) {
-      // If the given langcode was selected, there should be at least one .po
-      // file with its name in the pattern drupal-$version.$langcode.po.
-      // This might or might not be the entire filename. It is also possible
-      // that multiple files end with the same suffix, even if unlikely.
-      $files = install_find_translation_files($install_state['parameters']['langcode']);
-      if (!empty($files)) {
-        // Register locale classes with the classloader. Locale module is not
-        // yet enabled at this stage, so this is not happening automatically.
-        drupal_classloader_register('locale', drupal_get_path('module', 'locale'));
-        $strings = Gettext::filesToArray($install_state['parameters']['langcode'], $files);
-      }
-    }
+  // If not set, initialize installation language if possible.
+  if (!isset($langcode) && isset($install_state['parameters']['langcode'])) {
+    $langcode = $install_state['parameters']['langcode'];
   }
+  $options += array('langcode' => $langcode);
 
-  require_once DRUPAL_ROOT . '/core/includes/theme.inc';
-  // Transform arguments before inserting them
-  foreach ($args as $key => $value) {
-    switch ($key[0]) {
-      // Escaped only
-      case '@':
-        $args[$key] = check_plain($value);
-        break;
-      // Escaped and placeholder
-      case '%':
-      default:
-        $args[$key] = '<em>' . check_plain($value) . '</em>';
-        break;
-      // Pass-through
-      case '!':
-    }
-  }
-  return strtr((!empty($strings[$options['context']][$string]) ? $strings[$options['context']][$string] : $string), $args);
+  return t($string, $args, $options);
 }
 
 /**
diff --git a/core/modules/locale/lib/Drupal/locale/Gettext.php b/core/modules/locale/lib/Drupal/locale/Gettext.php
index a7c37ca..a078e77 100644
--- a/core/modules/locale/lib/Drupal/locale/Gettext.php
+++ b/core/modules/locale/lib/Drupal/locale/Gettext.php
@@ -21,33 +21,6 @@
  * - File public://*.po to database
  */
 class Gettext {
-
-  /**
-   * Reads the given Gettext PO files into a data structure.
-   *
-   * @param string $langcode
-   *   Language code string.
-   * @param array $files
-   *   List of file objects with URI properties pointing to read.
-   *
-   * @return array
-   *   Structured array as produced by a PoMemoryWriter.
-   *
-   * @see Drupal\Component\Gettext\PoMemoryWriter
-   */
-  static function filesToArray($langcode, array $files) {
-    $writer = new PoMemoryWriter();
-    $writer->setLangcode($langcode);
-    foreach ($files as $file) {
-      $reader = new PoStreamReader();
-      $reader->setURI($file->uri);
-      $reader->setLangcode($langcode);
-      $reader->open();
-      $writer->writeItems($reader, -1);
-    }
-    return $writer->getData();
-  }
-
   /**
    * Reads the given PO files into the database.
    *
diff --git a/core/modules/locale/lib/Drupal/locale/LocaleBundle.php b/core/modules/locale/lib/Drupal/locale/LocaleBundle.php
new file mode 100644
index 0000000..6fad4f1
--- /dev/null
+++ b/core/modules/locale/lib/Drupal/locale/LocaleBundle.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\locale\LocaleBundle.
+ */
+
+namespace Drupal\locale;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\HttpKernel\Bundle\Bundle;
+
+use Drupal\Core\Database\Database;
+
+/**
+ * Bundle class for locale services.
+ *
+ * Ths is where we register locale services that should run when the module is
+ * enabled. For now only the locale storage that will be used for translations.
+ */
+class LocaleBundle extends Bundle
+{
+  public function build(ContainerBuilder $container) {
+    // Register storage and lookup translation service.
+    $container->register('locale.storage', 'Drupal\locale\StringDatabaseStorage')
+      ->addArgument(new Reference('database'))
+      ->addArgument(array('target' => 'default'));
+    $container->register('locale.translation.lookup', 'Drupal\locale\LocaleTranslation')
+      ->addArgument(new Reference('locale.storage'));
+    // Replace the default translation service, using lookup as the fallback.
+    $container->register('locale.translation', 'Drupal\Core\Translation\TranslationFallback')
+      ->addArgument(new Reference('locale.translation.custom'))
+      ->addArgument(new Reference('locale.translation.lookup'));
+  }
+}
diff --git a/core/modules/locale/lib/Drupal/locale/LocaleLookup.php b/core/modules/locale/lib/Drupal/locale/LocaleLookup.php
index 9dd04f6..2838524 100644
--- a/core/modules/locale/lib/Drupal/locale/LocaleLookup.php
+++ b/core/modules/locale/lib/Drupal/locale/LocaleLookup.php
@@ -29,7 +29,7 @@ class LocaleLookup extends CacheArray {
   protected $context;
 
   /**
-   * The locale storage
+   * The locale storage.
    *
    * @var Drupal\locale\StringStorageInterface
    */
@@ -74,13 +74,7 @@ protected function resolveCacheMiss($offset) {
       $value = TRUE;
     }
     $this->storage[$offset] = $value;
-    // Disabling the usage of string caching allows a module to watch for
-    // the exact list of strings used on a page. From a performance
-    // perspective that is a really bad idea, so we have no user
-    // interface for this. Be careful when turning this option off!
-    if (variable_get('locale_cache_strings', 1)) {
-      $this->persist($offset);
-    }
+    $this->persist($offset);
     return $value;
   }
 }
diff --git a/core/modules/locale/lib/Drupal/locale/LocaleTranslation.php b/core/modules/locale/lib/Drupal/locale/LocaleTranslation.php
new file mode 100644
index 0000000..a7ba30b
--- /dev/null
+++ b/core/modules/locale/lib/Drupal/locale/LocaleTranslation.php
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Language\LocaleTranslation.
+ */
+
+namespace Drupal\locale;
+
+use Drupal\Core\Translation\TranslationInterface;
+use Drupal\locale\StringStorageInterface;
+use Drupal\locale\LocaleLookup;
+
+
+/**
+ * String translator using the locale module.
+ *
+ * Full featured translation system using locale's string storage and
+ * database caching.
+ */
+class LocaleTranslation implements TranslationInterface {
+
+  /**
+   * Storage for strings.
+   *
+   * @var Drupal\locale\StringStorageInterface
+   */
+  protected $storage;
+
+  /**
+   * Cached translations
+   *
+   * @var array
+   *   Array of Drupal\locale\LocaleLookup objects indexed by language code
+   *   and context.
+   */
+  protected $translations;
+
+  /**
+   * Constructs a translator using a string storage.
+   *
+   * @param Drupal\locale\StringStorageInterface $storage
+   *   Storage to use when looking for new translations.
+   */
+  public function __construct($storage) {
+    $this->storage = $storage;
+  }
+
+  /**
+   * Implements Drupal\Core\Translation\TranslationInterface::translate()
+   */
+  public function translate($langcode, $string, $context) {
+    // If the language is not suitable for locale module, just return.
+    if ($langcode == LANGUAGE_SYSTEM || $langcode == 'en' && !variable_get('locale_translate_english', FALSE)) {
+      return FALSE;
+    }
+    // Strings are cached by langcode, context and roles, using instances of the
+    // LocaleLookup class to handle string lookup and caching.
+    if (!isset($this->translations[$langcode][$context])) {
+      $this->translations[$langcode][$context] = new LocaleLookup($langcode, $context, $this->storage);
+    }
+    $translation = $this->translations[$langcode][$context][$string];
+    return $translation === TRUE ? FALSE : $translation;
+  }
+
+  /**
+   * Implements Drupal\Core\Language\TranslationInterface::reset()
+   */
+  public function reset() {
+    $this->translations = array();
+  }
+
+}
diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleExportTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleExportTest.php
index 7ae1bf7..816b726 100644
--- a/core/modules/locale/lib/Drupal/locale/Tests/LocaleExportTest.php
+++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleExportTest.php
@@ -79,9 +79,11 @@ function testExportTranslation() {
     ), t('Import'));
     drupal_unlink($name);
 
-    // We can't import a string with an empty translation, but calling
-    // locale() for an new string creates an entry in the locales_source table.
-    locale('February', NULL, 'fr');
+    // Create string without translation in the locales_source table.
+    locale_storage()
+      ->createString()
+      ->setString('February')
+      ->save();
 
     // Export only customized French translations.
     $this->drupalPost('admin/config/regional/translate/export', array(
diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module
index 3ebe97d..c9f03ce 100644
--- a/core/modules/locale/locale.module
+++ b/core/modules/locale/locale.module
@@ -16,6 +16,7 @@
 use Drupal\locale\StringDatabaseStorage;
 use Drupal\locale\TranslationsStream;
 use Drupal\Core\Database\Database;
+use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
 
 /**
  * Regular expression pattern used to localize JavaScript strings.
@@ -291,76 +292,21 @@ function locale_translatable_language_list() {
 }
 
 /**
- * Provides interface translation services.
- *
- * This function is called from t() to translate a string if needed.
- *
- * @param $string
- *   A string to look up translation for. If omitted, all the
- *   cached strings will be returned in all languages already
- *   used on the page.
- * @param $context
- *   The context of this string.
- * @param $langcode
- *   Language code to use for the lookup.
- */
-function locale($string = NULL, $context = NULL, $langcode = NULL) {
-  $language_interface = language(LANGUAGE_TYPE_INTERFACE);
-
-  // Use the advanced drupal_static() pattern, since this is called very often.
-  static $drupal_static_fast;
-  if (!isset($drupal_static_fast)) {
-    $drupal_static_fast['locale'] = &drupal_static(__FUNCTION__, array('cache' => array(), 'exists' => NULL));
-  }
-  $locale_t = &$drupal_static_fast['locale']['cache'];
-  $locale_exists = &$drupal_static_fast['locale']['exists'];
-
-  // Check whether Locale module is actually installed and operational.
-  // The mere existence of locale() does not imply that Locale module is
-  // actually enabled and its database tables are installed. Since PHP code
-  // cannot be unloaded, this is typically the case in the environment that
-  // is executing a test.
-  if (!isset($locale_exists)) {
-    $locale_exists = function_exists('module_exists') && module_exists('locale');
-  }
-  if (!$locale_exists) {
-    return $string;
-  }
-
-  if (!isset($string)) {
-    // Return all cached strings if no string was specified
-    return $locale_t;
-  }
-
-  $langcode = isset($langcode) ? $langcode : $language_interface->langcode;
-
-  // Strings are cached by langcode, context and roles, using instances of the
-  // LocaleLookup class to handle string lookup and caching.
-  if (!isset($locale_t[$langcode][$context]) && isset($language_interface)) {
-    $locale_t[$langcode][$context] = new LocaleLookup($langcode, $context, locale_storage());
-  }
-  return ($locale_t[$langcode][$context][$string] === TRUE ? $string : $locale_t[$langcode][$context][$string]);
-}
-
-/**
- * Reset static variables used by locale().
- */
-function locale_reset() {
-  drupal_static_reset('locale');
-}
-
-/**
- * Gets the locale storage controller class .
+ * Gets the locale storage controller.
  *
  * @return Drupal\locale\StringStorageInterface
  */
 function locale_storage() {
-  $storage = &drupal_static(__FUNCTION__);
-  if (!isset($storage)) {
-    $options = array('target' => 'default');
-    $storage = new StringDatabaseStorage(Database::getConnection($options['target']), $options);
+  try {
+    return drupal_container()->get('locale.storage');
+  }
+  catch (InvalidArgumentException $e) {
+    // The locale storage is not initialized yet, which happens when
+    // regenerating JavaScript strings during updates.
+    $storage = new StringDatabaseStorage(drupal_container()->get('database'), array('target' => 'default'));
+    drupal_container()->set('locale.storage', $storage);
+    return $storage;
   }
-  return $storage;
 }
 
 /**
@@ -1058,6 +1004,7 @@ function _locale_rebuild_js($langcode = NULL) {
  * Implements hook_language_init().
  */
 function locale_language_init() {
+  //new Drupal\locale\LocaleTranslation();
   // Add locale helper to configuration subscribers.
   drupal_container()->get('dispatcher')->addSubscriber(new LocaleConfigSubscriber());
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/InstallerLanguageTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/InstallerLanguageTest.php
index 9960d4c..19d78cf 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Common/InstallerLanguageTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Common/InstallerLanguageTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\system\Tests\Common;
 
 use Drupal\simpletest\WebTestBase;
+use Drupal\Core\Translation\FileTranslation;
 
 /**
  * Tests installer language detection.
@@ -41,9 +42,9 @@ function testInstallerTranslationFiles() {
       'hu' => array('drupal-8.0.hu.po'),
       'it' => array(),
     );
-
+    $file_translation = new FileTranslation(variable_get('locale_translate_file_directory'));
     foreach ($expected_translation_files as $langcode => $files_expected) {
-      $files_found = install_find_translation_files($langcode);
+      $files_found = $file_translation->findTranslationFiles($langcode);
       $this->assertTrue(count($files_found) == count($files_expected), format_string('@count installer languages found.', array('@count' => count($files_expected))));
       foreach ($files_found as $file) {
         $this->assertTrue(in_array($file->filename, $files_expected), format_string('@file found.', array('@file' => $file->filename)));
