diff --git a/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php b/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php
index 1285cb7..c69b705 100644
--- a/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php
+++ b/core/lib/Drupal/Core/StringTranslation/TranslationWrapper.php
@@ -60,6 +60,29 @@ public function __construct($string, array $arguments = array(), array $options
   }
 
   /**
+   * Get the string value stored in this translation wrapper.
+   *
+   * @return string
+   *   The string stored in this wrapper.
+   */
+  public function getString() {
+    return $this->string;
+  }
+
+  /**
+   * Get a specific option from this translation wrapper.
+   *
+   * @param $name
+   *   Option name.
+   *
+   * @return mixed
+   *   The value of this option or empty string of option is not set.
+   */
+  public function getOption($name) {
+    return isset($this->options[$name]) ? $this->options[$name] : '';
+  }
+
+  /**
    * Implements the magic __toString() method.
    */
   public function __toString() {
diff --git a/core/modules/config/src/Tests/ConfigSchemaTest.php b/core/modules/config/src/Tests/ConfigSchemaTest.php
index 2bbfee5..4a65e62 100644
--- a/core/modules/config/src/Tests/ConfigSchemaTest.php
+++ b/core/modules/config/src/Tests/ConfigSchemaTest.php
@@ -26,7 +26,7 @@ class ConfigSchemaTest extends KernelTestBase {
    *
    * @var array
    */
-  public static $modules = array('system', 'language', 'locale', 'field', 'image', 'config_test', 'config_schema_test');
+  public static $modules = array('system', 'language', 'field', 'image', 'config_test', 'config_schema_test');
 
   /**
    * {@inheritdoc}
diff --git a/core/modules/config_translation/src/ConfigEntityMapper.php b/core/modules/config_translation/src/ConfigEntityMapper.php
index 08ce8a6..fe3da3c 100644
--- a/core/modules/config_translation/src/ConfigEntityMapper.php
+++ b/core/modules/config_translation/src/ConfigEntityMapper.php
@@ -98,7 +98,7 @@ public static function create(ContainerInterface $container, array $configuratio
       $plugin_definition,
       $container->get('config.factory'),
       $container->get('config.typed'),
-      $container->get('locale.config.typed'),
+      $container->get('locale.config_manager'),
       $container->get('plugin.manager.config_translation.mapper'),
       $container->get('router.route_provider'),
       $container->get('string_translation'),
diff --git a/core/modules/config_translation/src/ConfigNamesMapper.php b/core/modules/config_translation/src/ConfigNamesMapper.php
index b681b33..ee1d0ac 100644
--- a/core/modules/config_translation/src/ConfigNamesMapper.php
+++ b/core/modules/config_translation/src/ConfigNamesMapper.php
@@ -150,7 +150,7 @@ public static function create(ContainerInterface $container, array $configuratio
       $plugin_definition,
       $container->get('config.factory'),
       $container->get('config.typed'),
-      $container->get('locale.config.typed'),
+      $container->get('locale.config_manager'),
       $container->get('plugin.manager.config_translation.mapper'),
       $container->get('router.route_provider'),
       $container->get('string_translation'),
@@ -465,7 +465,7 @@ public function hasTranslatable() {
    */
   public function hasTranslation(LanguageInterface $language) {
     foreach ($this->getConfigNames() as $name) {
-      if ($this->localeConfigManager->hasTranslation($name, $language)) {
+      if ($this->localeConfigManager->hasTranslation($name, $language->getId())) {
         return TRUE;
       }
     }
diff --git a/core/modules/config_translation/tests/src/Unit/ConfigNamesMapperTest.php b/core/modules/config_translation/tests/src/Unit/ConfigNamesMapperTest.php
index 59c1469..3a602e1 100644
--- a/core/modules/config_translation/tests/src/Unit/ConfigNamesMapperTest.php
+++ b/core/modules/config_translation/tests/src/Unit/ConfigNamesMapperTest.php
@@ -593,7 +593,7 @@ public function testHasTranslation(array $mock_return_values, $expected) {
 
     $map = array();
     foreach ($config_names as $i => $config_name) {
-      $map[] = array($config_name, $language, $mock_return_values[$i]);
+      $map[] = array($config_name, $language->getId(), $mock_return_values[$i]);
     }
     $this->localeConfigManager
       ->expects($this->any())
diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc
index 357aaf8..a4677ca 100644
--- a/core/modules/locale/locale.bulk.inc
+++ b/core/modules/locale/locale.bulk.inc
@@ -324,7 +324,7 @@ function locale_translate_batch_refresh(array &$context) {
   }
   elseif ($name = array_shift($context['sandbox']['refresh']['names'])) {
     // Refresh all languages for one object at a time.
-    $count = locale_config_update_multiple(array($name), $context['sandbox']['refresh']['languages']);
+    $count = Locale::config()->updateConfigTranslations(array($name), $context['sandbox']['refresh']['languages']);
     $context['results']['stats']['config'] += $count;
     // Inherit finished information from the "parent" string lookup step so
     // visual display of status will make sense.
@@ -532,7 +532,7 @@ function locale_translate_delete_translation_files(array $projects = array(), ar
  *   The batch definition.
  */
 function locale_config_batch_update_components(array $options, array $langcodes = array(), array $components = array()) {
-  $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list());
+  $langcodes = $langcodes ? $langcodes : array_keys(\Drupal::languageManager()->getLanguages());
   if ($langcodes && $names = \Drupal\locale\Locale::config()->getComponentNames($components)) {
     return locale_config_batch_build($names, $langcodes, $options);
   }
@@ -606,7 +606,7 @@ function locale_config_batch_refresh_name(array $names, array $langcodes, array
   if (!isset($context['result']['stats']['config'])) {
     $context['result']['stats']['config'] = 0;
   }
-  $context['result']['stats']['config'] += locale_config_update_multiple($names, $langcodes);
+  $context['result']['stats']['config'] += Locale::config()->updateConfigTranslations($names, $langcodes);
   foreach ($names as $name) {
     $context['result']['names'][] = $name;
   }
@@ -639,41 +639,3 @@ function locale_config_batch_finished($success, array $results) {
     }
   }
 }
-
-/**
- * Updates all configuration for names / languages.
- *
- * @param array $names
- *   Array of names of configuration objects to update.
- * @param array $langcodes
- *   (optional) Array of language codes to update. Defaults to all languages.
- *
- * @return int
- *   Number of configuration objects retranslated.
- */
-function locale_config_update_multiple(array $names, array $langcodes = array()) {
-  /** @var \Drupal\language\ConfigurableLanguageManagerInterface $language_manager */
-  $language_manager = \Drupal::languageManager();
-  $locale_config_manager = Locale::config();
-
-  $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list());
-  $count = 0;
-  foreach ($names as $name) {
-    $wrapper = $locale_config_manager->get($name);
-    foreach ($langcodes as $langcode) {
-      $translation = $wrapper->getValue() ? $wrapper->getTranslation($langcode)->getValue() : NULL;
-      if ($translation) {
-        $locale_config_manager->saveTranslationData($name, $langcode, $translation);
-        $count++;
-      }
-      else {
-        // Do not bother deleting language overrides which do not exist in the
-        // first place.
-        if (!$language_manager->getLanguageConfigOverride($langcode, $name)->isNew()) {
-          $locale_config_manager->deleteTranslationData($name, $langcode);
-        }
-      }
-    }
-  }
-  return $count;
-}
diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module
index 971b47d..66557f3 100644
--- a/core/modules/locale/locale.module
+++ b/core/modules/locale/locale.module
@@ -24,6 +24,7 @@
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\language\ConfigurableLanguageInterface;
 use Drupal\Component\Utility\Crypt;
+use Drupal\locale\Locale;
 
 /**
  * Regular expression pattern used to localize JavaScript strings.
@@ -226,7 +227,7 @@ function locale_configurable_language_delete(ConfigurableLanguageInterface $lang
   locale_translate_delete_translation_files(array(), array($language->id()));
 
   // Remove translated configuration objects.
-  \Drupal\locale\Locale::config()->deleteLanguageTranslations($language->id());
+  Locale::config()->deleteLanguageTranslations($language->id());
 
   // Changing the language settings impacts the interface:
   _locale_invalidate_js($language->id());
@@ -246,7 +247,7 @@ function locale_configurable_language_delete(ConfigurableLanguageInterface $lang
  */
 function locale_translatable_language_list() {
   $languages = \Drupal::languageManager()->getLanguages();
-  if (!locale_translate_english()) {
+  if (!locale_is_translatable('en')) {
     unset($languages['en']);
   }
   return $languages;
@@ -313,6 +314,7 @@ function locale_get_plural($count, $langcode = NULL) {
  */
 function locale_modules_installed($modules) {
   $components['module'] = $modules;
+  locale_system_set_config_langcodes($components);
   locale_system_update($components);
 }
 
@@ -329,6 +331,7 @@ function locale_module_preuninstall($module) {
  */
 function locale_themes_installed($themes) {
   $components['theme'] = $themes;
+  locale_system_set_config_langcodes($components);
   locale_system_update($components);
 }
 
@@ -356,6 +359,41 @@ function locale_cron() {
 }
 
 /**
+ * Update default configuration when new modules or themes are installed.
+ *
+ * @param array $components
+ *   An array of arrays of component (theme and/or module) names to import
+ *   translations for, indexed by type.
+ */
+function locale_system_set_config_langcodes(array $components) {
+  // Need to rewrite some default configuration language codes if the default
+  // site language is not English.
+  $default_langcode = \Drupal::languageManager()->getDefaultLanguage()->getId();
+  if ($default_langcode != 'en') {
+    // If just installed the locale module, we need to update all prior
+    // shipped configuration to the foreign site language. Otherwise keep the
+    // components list received to just update the shipped configuration just
+    // imported.
+    if (isset($components['module']) && in_array('locale', $components['module'])) {
+      $components = array();
+    }
+    $names = Locale::config()->getComponentNames($components);
+
+    foreach ($names as $name) {
+      $config = \Drupal::configFactory()->getEditable($name);
+      // Should only update if still exists in active configuration. If locale
+      // module is enabled later, then some configuration may not exist anymore.
+      if (!$config->isNew()) {
+        $langcode = $config->get('langcode');
+        if (empty($langcode) || $langcode == 'en') {
+          $config->set('langcode', $default_langcode)->save();
+        }
+      }
+    }
+  }
+}
+
+/**
  * Imports translations when new modules or themes are installed.
  *
  * This function will start a batch to import translations for the added
@@ -411,8 +449,6 @@ function locale_system_remove($components) {
   if ($language_list = locale_translatable_language_list()) {
     module_load_include('compare.inc', 'locale');
     \Drupal::moduleHandler()->loadInclude('locale', 'bulk.inc');
-    // Delete configuration translations.
-    \Drupal\locale\Locale::config()->deleteComponentTranslations($components, array_keys($language_list));
 
     // Only when projects are removed, the translation files and records will be
     // deleted. Not each disabled module will remove a project, e.g., sub
@@ -606,7 +642,7 @@ function locale_form_language_admin_overview_form_alter(&$form, FormStateInterfa
       'translated' => 0,
       'ratio' => 0,
     );
-    if (!$language->isLocked() && ($langcode != 'en' || locale_translate_english())) {
+    if (!$language->isLocked() && locale_is_translatable($langcode)) {
       $form['languages'][$langcode]['locale_statistics'] = array(
         '#markup' => \Drupal::l(
           t('@translated/@total (@ratio%)', array(
@@ -689,13 +725,15 @@ function locale_form_language_admin_edit_form_alter_submit($form, FormStateInter
 }
 
 /**
- * Checks whether locale translates to English.
+ * Checks whether $langcode is a language supported as a locale target.
  *
+ * @param string $langcode
+ *   The language code.
  * @return bool
- *   Returns TRUE if content should be translated to English, FALSE otherwise.
+ *   Whether $langcode can be translated to in locale.
  */
-function locale_translate_english() {
-  return \Drupal::config('locale.settings')->get('translate_english');
+function locale_is_translatable($langcode) {
+  return $langcode != 'en' || \Drupal::config('locale.settings')->get('translate_english');
 }
 
 /**
@@ -1020,19 +1058,14 @@ function _locale_refresh_translations($langcodes, $lids = array()) {
 /**
  * Refreshes configuration after string translations have been updated.
  *
- * The information that will be refreshed includes:
- * - JavaScript translations.
- * - Locale cache.
- *
  * @param array $langcodes
  *   Language codes for updated translations.
  * @param array $lids
  *   List of string identifiers that have been updated / created.
  */
 function _locale_refresh_configuration(array $langcodes, array $lids) {
-  if ($lids && $langcodes && $names = \Drupal\locale\Locale::config()->getStringNames($lids)) {
-    \Drupal::moduleHandler()->loadInclude('locale', 'bulk.inc');
-    locale_config_update_multiple($names, $langcodes);
+  if ($lids && $langcodes && $names = Locale::config()->getStringNames($lids)) {
+    Locale::config()->updateConfigTranslations($names, $langcodes);
   }
 }
 
diff --git a/core/modules/locale/locale.services.yml b/core/modules/locale/locale.services.yml
index 27cef2c..da3cf77 100644
--- a/core/modules/locale/locale.services.yml
+++ b/core/modules/locale/locale.services.yml
@@ -1,5 +1,5 @@
 services:
-  locale.config.typed:
+  locale.config_manager:
     class: Drupal\locale\LocaleConfigManager
     arguments: ['@config.storage', '@config.storage.installer', '@locale.storage', '@config.factory', '@config.typed', '@language_manager']
   locale.storage:
@@ -22,6 +22,6 @@ services:
       - { name: stream_wrapper, scheme: translations }
   locale.config_subscriber:
     class: Drupal\locale\LocaleConfigSubscriber
-    arguments: ['@locale.storage', '@config.factory', '@locale.config.typed']
+    arguments: ['@config.factory', '@locale.config_manager']
     tags:
       - { name: event_subscriber }
diff --git a/core/modules/locale/src/Form/ExportForm.php b/core/modules/locale/src/Form/ExportForm.php
index f9b6361..035b2e8 100644
--- a/core/modules/locale/src/Form/ExportForm.php
+++ b/core/modules/locale/src/Form/ExportForm.php
@@ -61,7 +61,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     $languages = $this->languageManager->getLanguages();
     $language_options = array();
     foreach ($languages as $langcode => $language) {
-      if ($langcode != 'en' || locale_translate_english()) {
+      if (locale_is_translatable($langcode)) {
         $language_options[$langcode] = $language->getName();
       }
     }
diff --git a/core/modules/locale/src/Form/ImportForm.php b/core/modules/locale/src/Form/ImportForm.php
index e3448b3..ca706ea 100644
--- a/core/modules/locale/src/Form/ImportForm.php
+++ b/core/modules/locale/src/Form/ImportForm.php
@@ -79,7 +79,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     // are to translate Drupal to English as well.
     $existing_languages = array();
     foreach ($languages as $langcode => $language) {
-      if ($langcode != 'en' || locale_translate_english()) {
+      if (locale_is_translatable($langcode)) {
         $existing_languages[$langcode] = $language->getName();
       }
     }
diff --git a/core/modules/locale/src/Form/TranslateFormBase.php b/core/modules/locale/src/Form/TranslateFormBase.php
index b894f17..59af72b 100644
--- a/core/modules/locale/src/Form/TranslateFormBase.php
+++ b/core/modules/locale/src/Form/TranslateFormBase.php
@@ -164,7 +164,7 @@ protected function translateFilters() {
     $languages = $this->languageManager->getLanguages();
     $language_options = array();
     foreach ($languages as $langcode => $language) {
-      if ($langcode != 'en' || locale_translate_english()) {
+      if (locale_is_translatable($langcode)) {
         $language_options[$langcode] = $language->getName();
       }
     }
diff --git a/core/modules/locale/src/Locale.php b/core/modules/locale/src/Locale.php
index f4afaed..9235a9c 100644
--- a/core/modules/locale/src/Locale.php
+++ b/core/modules/locale/src/Locale.php
@@ -23,6 +23,6 @@ class Locale {
    * @return \Drupal\locale\LocaleConfigManager
    */
   public static function config() {
-    return \Drupal::service('locale.config.typed');
+    return \Drupal::service('locale.config_manager');
   }
 }
diff --git a/core/modules/locale/src/LocaleConfigManager.php b/core/modules/locale/src/LocaleConfigManager.php
index aee2d59..16bb970 100644
--- a/core/modules/locale/src/LocaleConfigManager.php
+++ b/core/modules/locale/src/LocaleConfigManager.php
@@ -7,33 +7,53 @@
 
 namespace Drupal\locale;
 
-use Drupal\Core\Config\TypedConfigManagerInterface;
-use Drupal\Core\Config\StorageInterface;
+use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Config\ConfigFactoryInterface;
-use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Config\StorageInterface;
+use Drupal\Core\Config\TypedConfigManagerInterface;
+use Drupal\Core\StringTranslation\TranslationWrapper;
+use Drupal\Core\TypedData\TraversableTypedDataInterface;
+use Drupal\Core\TypedData\TypedDataInterface;
 use Drupal\language\ConfigurableLanguageManagerInterface;
 
 /**
- * Manages localized configuration type plugins.
+ * Manages configuration supported in part by interface translation.
+ *
+ * This manager is responsible to update configuration overrides and active
+ * translations when interface translation data changes. This allows Drupal to
+ * translate user roles, views, blocks, etc. after Drupal has been installed
+ * using the locale module's storage. When translations change in locale,
+ * LocaleConfigManager::updateConfigTranslations() is invoked to update the
+ * corresponding storage of the translation (in active storage or overrides).
+ *
+ * In turn when translated configuration or configuration language overrides are
+ * changed, it is the responsibility of LocaleConfigSubscriber to update locale
+ * storage.
+ *
+ * By design locale module only deals with sources in English.
+ *
+ * @see \Drupal\locale\LocaleConfigSubscriber
  */
 class LocaleConfigManager {
 
   /**
-   * A storage instance for reading configuration data.
+   * The storage instance for reading configuration data.
    *
    * @var \Drupal\Core\Config\StorageInterface
    */
   protected $configStorage;
 
   /**
-   * A storage instance for reading default configuration data.
+   * The storage instance for reading default configuration data.
    *
    * @var \Drupal\Core\Config\StorageInterface
    */
   protected $installStorage;
 
   /**
-   * A string storage for reading and writing translations.
+   * The string storage for reading and writing translations.
+   *
+   * @var \Drupal\locale\StringStorageInterface;
    */
   protected $localeStorage;
 
@@ -66,11 +86,13 @@ class LocaleConfigManager {
   protected $typedConfigManager;
 
   /**
-   * Whether or not configuration translations are currently being updated.
+   * Whether or not configuration translations are being updated from locale.
+   *
+   * @see self::isUpdatingFromLocale()
    *
    * @var bool
    */
-  protected $isUpdating = FALSE;
+  protected $isUpdatingFromLocale = FALSE;
 
   /**
    * Creates a new typed configuration manager.
@@ -99,61 +121,119 @@ public function __construct(StorageInterface $config_storage, StorageInterface $
   }
 
   /**
-   * Gets locale wrapper with typed configuration data.
+   * Gets array of translation wrappers for translatable configuration.
    *
    * @param string $name
    *   Configuration object name.
    *
-   * @return \Drupal\locale\LocaleTypedConfig
-   *   Locale-wrapped configuration element.
-   */
-  public function get($name) {
-    // Read default and current configuration data.
-    $default = $this->installStorageRead($name);
-    $updated = $this->configStorage->read($name);
-    // We get only the data that didn't change from default.
-    $data = $this->compareConfigData($default, $updated);
-    $definition = $this->typedConfigManager->getDefinition($name);
-    $data_definition = $this->typedConfigManager->buildDataDefinition($definition, $data);
-    // Unless the configuration has a explicit language code we assume English.
-    $langcode = isset($default['langcode']) ? $default['langcode'] : 'en';
-    $wrapper = new LocaleTypedConfig($data_definition, $name, $langcode, $this, $this->typedConfigManager, $this->languageManager);
-    $wrapper->setValue($data);
-    return $wrapper;
+   * @return array
+   *   Array of translatable elements of the default configuration in $name.
+   */
+  public function getTranslatableDefaultConfig($name) {
+    if ($this->isSupported($name)) {
+      // Create typed configuration wrapper based on install storage data.
+      $data = $this->installStorageRead($name);
+      $type_definition = $this->typedConfigManager->getDefinition($name);
+      $data_definition = $this->typedConfigManager->buildDataDefinition($type_definition, $data);
+      $typed_config = $this->typedConfigManager->create($data_definition, $data);
+      if ($typed_config instanceof TraversableTypedDataInterface) {
+        return $this->getTranslatableData($typed_config);
+      }
+    }
+    return array();
   }
 
   /**
-   * Compares default configuration with updated data.
+   * Gets translatable configuration data for a typed configuration element.
    *
-   * @param array $default
-   *   Default configuration data.
-   * @param array|false $updated
-   *   Current configuration data, or FALSE if no configuration data existed.
+   * @param \Drupal\Core\TypedData\TypedDataInterface $element
+   *   Typed configuration element.
    *
-   * @return array
-   *   The elements of default configuration that haven't changed.
+   * @return array|\Drupal\Core\StringTranslation\TranslationWrapper
+   *   A nested array matching the exact structure under $element with only the
+   *   elements that are translatable wrapped into a TranslationWrapper. If the
+   *   provided $element is not traversable, the return value is a single
+   *   TranslationWrapper.
    */
-  protected function compareConfigData(array $default, $updated) {
-    // Speed up comparison, specially for install operations.
-    if ($default === $updated) {
-      return $default;
+  protected function getTranslatableData(TypedDataInterface $element) {
+    $translatable = array();
+    if ($element instanceof TraversableTypedDataInterface) {
+      foreach ($element as $key => $property) {
+        $value = $this->getTranslatableData($property);
+        if (!empty($value)) {
+          $translatable[$key] = $value;
+        }
+      }
     }
-    $result = array();
-    foreach ($default as $key => $value) {
-      if (isset($updated[$key])) {
-        if (is_array($value)) {
-          $result[$key] = $this->compareConfigData($value, $updated[$key]);
+    else {
+      $definition = $element->getDataDefinition();
+      if (!empty($definition['translatable'])) {
+        $options = array();
+        if (isset($definition['translation context'])) {
+          $options['context'] = $definition['translation context'];
+        }
+        return new TranslationWrapper($element->getValue(), array(), $options);
+      }
+    }
+    return $translatable;
+  }
+
+  /**
+   * Process the translatable data array with a given language.
+   *
+   * If the given language is translatable, will return the translated copy
+   * which will only contain strings that had translations. If the given
+   * language is English and is not translatable, will return a simplified
+   * array of the English source strings only.
+   *
+   * @param string $name
+   *   The configuration name.
+   * @param array $active
+   *   The active configuration data.
+   * @param array $translatable
+   *   The translatable array structure. A nested array matching the exact
+   *   structure under of the default configuration for $name with only the
+   *   elements that are translatable wrapped into a TranslationWrapper.
+   *   @see self::getTranslatableData().
+   * @param string $langcode
+   *   The language code to process the array with
+   *
+   * @return array
+   *   Processed translatable data array. Will only contain translations
+   *   different from source strings or in case of untranslatable English, the
+   *   source strings themselves.
+   */
+  protected function processTranslatableData($name, array $active, array $translatable, $langcode) {
+    $translated = array();
+    foreach ($translatable as $key => $item) {
+      if (!isset($active[$key])) {
+        continue;
+      }
+      if (is_array($item)) {
+        // Only add this key if there was a translated value underneath.
+        $value = $this->processTranslatableData($name, $active[$key], $item, $langcode);
+        if (!empty($value)) {
+          $translated[$key] = $value;
+        }
+      }
+      else {
+        /** @var \Drupal\Core\StringTranslation\TranslationWrapper $item */
+        if (locale_is_translatable($langcode)) {
+          $value = $this->translateString($name, $langcode, $item->getString(), $item->getOption('context'));
+        }
+        else {
+          $value = $item->getString();
         }
-        elseif ($value === $updated[$key]) {
-          $result[$key] = $value;
+        if (!empty($value)) {
+          $translated[$key] = $value;
         }
       }
     }
-    return $result;
+    return $translated;
   }
 
   /**
-   * Saves translated configuration data.
+   * Saves translated configuration override.
    *
    * @param string $name
    *   Configuration object name.
@@ -162,10 +242,24 @@ protected function compareConfigData(array $default, $updated) {
    * @param array $data
    *   Configuration data to be saved, that will be only the translated values.
    */
-  public function saveTranslationData($name, $langcode, array $data) {
-    $this->isUpdating = TRUE;
+  public function saveTranslationOverride($name, $langcode, array $data) {
+    $this->isUpdatingFromLocale = TRUE;
     $this->languageManager->getLanguageConfigOverride($langcode, $name)->setData($data)->save();
-    $this->isUpdating = FALSE;
+    $this->isUpdatingFromLocale = FALSE;
+  }
+
+  /**
+   * Saves translated configuration data.
+   *
+   * @param string $name
+   *   Configuration object name.
+   * @param array $data
+   *   Configuration data to be saved with translations merged in.
+   */
+  public function saveTranslationActive($name, array $data) {
+    $this->isUpdatingFromLocale = TRUE;
+    $this->configFactory->getEditable($name)->setData($data)->save();
+    $this->isUpdatingFromLocale = FALSE;
   }
 
   /**
@@ -176,10 +270,10 @@ public function saveTranslationData($name, $langcode, array $data) {
    * @param string $langcode
    *   Language code.
    */
-  public function deleteTranslationData($name, $langcode) {
-    $this->isUpdating = TRUE;
+  public function deleteTranslationOverride($name, $langcode) {
+    $this->isUpdatingFromLocale = TRUE;
     $this->languageManager->getLanguageConfigOverride($langcode, $name)->delete();
-    $this->isUpdating = FALSE;
+    $this->isUpdatingFromLocale = FALSE;
   }
 
   /**
@@ -192,7 +286,7 @@ public function deleteTranslationData($name, $langcode) {
    * @return array
    *   Array of configuration object names.
    */
-  public function getComponentNames(array $components) {
+  public function getComponentNames(array $components = array()) {
     $components = array_filter($components);
     if ($components) {
       $names = array();
@@ -209,27 +303,6 @@ public function getComponentNames(array $components) {
   }
 
   /**
-   * Deletes configuration translations for uninstalled components.
-   *
-   * @param array $components
-   *   Array with string identifiers.
-   * @param array $langcodes
-   *   Array of language codes.
-   */
-  public function deleteComponentTranslations(array $components, array $langcodes) {
-    $this->isUpdating = TRUE;
-    $names = $this->getComponentNames($components);
-    if ($names && $langcodes) {
-      foreach ($names as $name) {
-        foreach ($langcodes as $langcode) {
-          $this->deleteTranslationData($name, $langcode);
-        }
-      }
-    }
-    $this->isUpdating = FALSE;
-  }
-
-  /**
    * Gets configuration names associated with strings.
    *
    * @param array $lids
@@ -254,12 +327,12 @@ public function getStringNames(array $lids) {
    *   Language code to delete.
    */
   public function deleteLanguageTranslations($langcode) {
-    $this->isUpdating = TRUE;
+    $this->isUpdatingFromLocale = TRUE;
     $storage = $this->languageManager->getLanguageConfigOverrideStorage($langcode);
     foreach ($storage->listAll() as $name) {
       $this->languageManager->getLanguageConfigOverride($langcode, $name)->delete();
     }
-    $this->isUpdating = FALSE;
+    $this->isUpdatingFromLocale = FALSE;
   }
 
   /**
@@ -330,29 +403,234 @@ public function translateString($name, $langcode, $source, $context) {
   }
 
   /**
+   * Reset static cache of configuration string translations.
+   *
+   * @return $this
+   */
+  public function reset() {
+    $this->translations = array();
+    return $this;
+  }
+
+  /**
+   * Get the translation object for the given source/context and language.
+   *
+   * @param string $name
+   *   Name of the configuration location.
+   * @param string $langcode
+   *   Language code to translate to.
+   * @param string $source
+   *   The source string, should be English.
+   * @param string $context
+   *   The string context.
+   *
+   * @return \Drupal\locale\TranslationString|FALSE
+   *   The translation object if the string was not empty or FALSE otherwise.
+   */
+  public function getStringTranslation($name, $langcode, $source, $context) {
+    if ($source) {
+      $this->translateString($name, $langcode, $source, $context);
+      if ($string = $this->translations[$name][$langcode][$context][$source]) {
+        if (!$string->isTranslation()) {
+          $conditions = array('lid' => $string->lid, 'language' => $langcode);
+          return $this->localeStorage->createTranslation($conditions);
+        }
+        else {
+          return $string;
+        }
+      }
+    }
+    return FALSE;
+  }
+
+  /**
    * Checks whether a language has configuration translation.
    *
    * @param string $name
    *   Configuration name.
-   * @param \Drupal\Core\Language\LanguageInterface $language
-   *   A language object.
+   * @param string $langcode
+   *   A language code.
    *
    * @return bool
    *   A boolean indicating if a language has configuration translations.
    */
-  public function hasTranslation($name, LanguageInterface $language) {
-    $translation = $this->languageManager->getLanguageConfigOverride($language->getId(), $name);
+  public function hasTranslation($name, $langcode) {
+    $translation = $this->languageManager->getLanguageConfigOverride($langcode, $name);
     return !$translation->isNew();
   }
 
   /**
-   * Indicates whether configuration translations are currently being updated.
+   * Returns the original language code for this shipped configuration.
+   *
+   * @param $name
+   *   The configuration name.
+   *
+   * @return null|string
+   *   Language code of the default configuration for $name. If the default
+   *   configuration data for $name did not contain a language code, it is
+   *   assumed to be English. The return value is NULL if no such default
+   *   configuration exists.
+   */
+  public function getDefaultConfigLangcode($name) {
+    $shipped = $this->installStorageRead($name);
+    if (!empty($shipped)) {
+      return !empty($shipped['langcode']) ? $shipped['langcode'] : 'en';
+    }
+  }
+
+  /**
+   * Returns the current language code for this active configuration.
+   *
+   * @param $name
+   *   The configuration name.
+   *
+   * @return null|string
+   *   Language code of the current active configuration for $name. If the
+   *   configuration data for $name did not contain a language code, it is
+   *   assumed to be English. The return value is NULL if no such active
+   *   configuration exists.
+   */
+  public function getActiveConfigLangcode($name) {
+    $active = $this->configStorage->read($name);
+    if (!empty($active)) {
+      return !empty($active['langcode']) ? $active['langcode'] : 'en';
+    }
+  }
+
+  /**
+   * Whether the given configuration is supported for interface translation.
+   *
+   * @param $name
+   *   The configuration name.
+   *
+   * @return bool
+   *   TRUE if interface translation is supported.
+   */
+  public function isSupported($name) {
+    return $this->getDefaultConfigLangcode($name) == 'en' && $this->configStorage->read($name);
+  }
+
+  /**
+   * Indicates whether configuration translations are being updated from locale.
    *
    * @return bool
    *   Whether or not configuration translations are currently being updated.
+   *   If TRUE, LocaleConfigManager is in control of the process and the
+   *   reference data is locale's storage. Changes made to active configuration
+   *   and overrides in this case should not feed back to locale storage.
+   *   On the other hand, when not updating from locale and configuration
+   *   translations change, we need to feed back to the locale storage.
+   */
+  public function isUpdatingTranslationsFromLocale() {
+    return $this->isUpdatingFromLocale;
+  }
+
+  /**
+   * Updates all configuration translations for the names / languages provided.
+   *
+   * To be used when interface translation changes result in the need to update
+   * configuration translations to keep them in sync.
+   *
+   * @param array $names
+   *   Array of names of configuration objects to update.
+   * @param array $langcodes
+   *   (optional) Array of language codes to update. Defaults to all
+   *   configurable languages.
+   *
+   * @return int
+   *   Total number of configuration override and active configuration objects
+   *   updated (saved or removed).
    */
-  public function isUpdatingConfigTranslations() {
-    return $this->isUpdating;
+  public function updateConfigTranslations(array $names, array $langcodes = array()) {
+    $langcodes = $langcodes ? $langcodes : array_keys($this->languageManager->getLanguages());
+    $count = 0;
+    foreach ($names as $name) {
+      $translatable = $this->getTranslatableDefaultConfig($name);
+      if (empty($translatable)) {
+        // If there is nothing translatable in this configuration or not
+        // supported, skip it.
+        continue;
+      }
+
+      $active_langcode = $this->getActiveConfigLangcode($name);
+      $active = $this->configStorage->read($name);
+
+      foreach ($langcodes as $langcode) {
+        $processed = $this->processTranslatableData($name, $active, $translatable, $langcode);
+        if ($langcode != $active_langcode) {
+          // If the language code is not the same as the active storage
+          // language, we should update a configuration override.
+          if (!empty($processed)) {
+            // Update translation data in configuration override.
+            $this->saveTranslationOverride($name, $langcode, $processed);
+            $count++;
+          }
+          else {
+            $override = $this->languageManager->getLanguageConfigOverride($langcode, $name);
+            if (!$override->isNew()) {
+              $data = $this->filterOverride($override->get(), $translatable);
+              if (empty($data)) {
+                // Delete language override if there is no data left at all.
+                // This means all prior translations in the override were locale
+                // managed.
+                $this->deleteTranslationOverride($name, $langcode);
+                $count++;
+              }
+              else {
+                // If there were translatable elements besides locale managed
+                // items, save with only those, and remove the ones managed
+                // by locale only.
+                $this->saveTranslationOverride($name, $langcode, $data);
+                $count++;
+              }
+            }
+          }
+        }
+        elseif (locale_is_translatable($langcode)) {
+          // If the language code is the active storage language, we should
+          // update. If it is English, we should only update if English is also
+          // translatable.
+          $active = NestedArray::mergeDeepArray(array($active, $processed), TRUE);
+          $this->saveTranslationActive($name, $active);
+          $count++;
+        }
+      }
+    }
+    return $count;
+  }
+
+  /**
+   * Filters override data based on default translatable items.
+   *
+   * @param array $override_data
+   *   Configuration override data.
+   * @param array $translatable
+   *   Translatable data array. @see self::getTranslatableData()
+   * @return array
+   *   Nested array of any items of $override_data which did not have keys in
+   *   $translatable. May be empty if $override_data only had items which were
+   *   also in $translatable.
+   */
+  protected function filterOverride(array $override_data, array $translatable) {
+    $filtered_data = array();
+    foreach ($override_data as $key => $value) {
+      if (isset($translatable[$key])) {
+        // If the translatable default configuration has this key, look further
+        // for subkeys or ignore this element for scalar values.
+        if (is_array($value)) {
+          $value = $this->filterOverride($value, $translatable[$key]);
+          if (!empty($value)) {
+            $filtered_data[$key] = $value;
+          }
+        }
+      }
+      else {
+        // If this key was not in the translatable default configuration,
+        // keep it.
+        $filtered_data[$key] = $value;
+      }
+    }
+    return $filtered_data;
   }
 
   /**
diff --git a/core/modules/locale/src/LocaleConfigSubscriber.php b/core/modules/locale/src/LocaleConfigSubscriber.php
index 6c42d50..3e1b9e7 100644
--- a/core/modules/locale/src/LocaleConfigSubscriber.php
+++ b/core/modules/locale/src/LocaleConfigSubscriber.php
@@ -6,35 +6,37 @@
 
 namespace Drupal\locale;
 
-use Drupal\Core\Config\Config;
+use Drupal\Core\Config\ConfigCrudEvent;
+use Drupal\Core\Config\ConfigEvents;
 use Drupal\Core\Config\ConfigFactoryInterface;
-use Drupal\Core\TypedData\TraversableTypedDataInterface;
-use Drupal\language\Config\LanguageConfigOverride;
+use Drupal\Core\Config\StorableConfigBase;
 use Drupal\language\Config\LanguageConfigOverrideCrudEvent;
 use Drupal\language\Config\LanguageConfigOverrideEvents;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 
 /**
- * Updates corresponding string translation when language overrides change.
+ * Updates strings translation when configuration translations change.
  *
- * This reacts to the updating or deleting of configuration language overrides.
- * It checks whether there are string translations associated with the
- * configuration that is being saved and, if so, updates those string
- * translations with the new configuration values and marks them as customized.
- * That way manual updates to configuration will not be inadvertently reverted
- * when updated translations from https://localize.drupal.org are being
- * imported.
+ * This reacts to the updates of translated active configuration and
+ * configuration language overrides. When those updates involve configuration
+ * which was available as default configuration, we need to feed back changes
+ * to any item which was originally part of that configuration to the interface
+ * translation storage. Those updated translations are saved as customized, so
+ * further community translation updates will not undo user changes.
+ *
+ * This subscriber does not respond to deleting active configuration or deleting
+ * configuration translations. The locale storage is additive and we cannot be
+ * sure that only a given configuration translation used a source string. So
+ * we should not remove the translations from locale storage in these cases. The
+ * configuration or override would itself be deleted either way.
+ *
+ * By design locale module only deals with sources in English.
+ *
+ * @see \Drupal\locale\LocaleConfigManager
  */
 class LocaleConfigSubscriber implements EventSubscriberInterface {
 
   /**
-   * The string storage.
-   *
-   * @var \Drupal\locale\StringStorageInterface;
-   */
-  protected $stringStorage;
-
-  /**
    * The configuration factory.
    *
    * @var \Drupal\Core\Config\ConfigFactoryInterface
@@ -51,15 +53,12 @@ class LocaleConfigSubscriber implements EventSubscriberInterface {
   /**
    * Constructs a LocaleConfigSubscriber.
    *
-   * @param \Drupal\locale\StringStorageInterface $string_storage
-   *   The string storage.
    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
    *   The configuration factory.
    * @param \Drupal\locale\LocaleConfigManager $locale_config_manager
    *   The typed configuration manager.
    */
-  public function __construct(StringStorageInterface $string_storage, ConfigFactoryInterface $config_factory, LocaleConfigManager $locale_config_manager) {
-    $this->stringStorage = $string_storage;
+  public function __construct(ConfigFactoryInterface $config_factory, LocaleConfigManager $locale_config_manager) {
     $this->configFactory = $config_factory;
     $this->localeConfigManager = $locale_config_manager;
   }
@@ -68,202 +67,164 @@ public function __construct(StringStorageInterface $string_storage, ConfigFactor
    * {@inheritdoc}
    */
   public static function getSubscribedEvents() {
-    $events[LanguageConfigOverrideEvents::SAVE_OVERRIDE] = 'onSave';
-    $events[LanguageConfigOverrideEvents::DELETE_OVERRIDE] = 'onDelete';
+    $events[LanguageConfigOverrideEvents::SAVE_OVERRIDE] = 'onOverrideSave';
+    $events[ConfigEvents::SAVE] = 'onConfigSave';
     return $events;
   }
 
-
   /**
-   * Updates the translation strings when shipped configuration is saved.
+   * Updates the locale strings when a translated active configuration is saved.
    *
-   * @param \Drupal\language\Config\LanguageConfigOverrideCrudEvent $event
-   *   The language configuration event.
+   * @param \Drupal\Core\Config\ConfigCrudEvent $event
+   *   The configuration event.
    */
-  public function onSave(LanguageConfigOverrideCrudEvent $event) {
-    // Do not mark strings as customized when community translations are being
-    // imported.
-    if ($this->localeConfigManager->isUpdatingConfigTranslations()) {
-      $callable = [$this, 'saveTranslation'];
-    }
-    else {
-      $callable = [$this, 'saveCustomizedTranslation'];
+  public function onConfigSave(ConfigCrudEvent $event) {
+    // Only attempt to feed back configuration translation changes to locale if
+    // the update itself was not initiated by locale data changes.
+    if (!$this->localeConfigManager->isUpdatingTranslationsFromLocale()) {
+      $config = $event->getConfig();
+      $langcode = $config->get('langcode') ?: 'en';
+      $this->updateLocaleStorage($config, $langcode);
     }
-
-    $this->updateTranslationStrings($event, $callable);
   }
 
   /**
-   * Updates the translation strings when shipped configuration is deleted.
+   * Updates the locale strings when a configuration override is saved.
    *
    * @param \Drupal\language\Config\LanguageConfigOverrideCrudEvent $event
    *   The language configuration event.
    */
-  public function onDelete(LanguageConfigOverrideCrudEvent $event) {
-    if ($this->localeConfigManager->isUpdatingConfigTranslations()) {
-      $callable = [$this, 'deleteTranslation'];
-    }
-    else {
-      // Do not delete the string, but save a customized translation with the
-      // source value so that the deletion will not be reverted by importing
-      // community translations.
-      // @see \Drupal\locale\LocaleConfigSubscriber::saveCustomizedTranslation()
-      $callable = [$this, 'saveCustomizedTranslation'];
+  public function onOverrideSave(LanguageConfigOverrideCrudEvent $event) {
+    // Only attempt to feed back configuration override changes to locale if
+    // the update itself was not initiated by locale data changes.
+    if (!$this->localeConfigManager->isUpdatingTranslationsFromLocale()) {
+      $translation_config = $event->getLanguageConfigOverride();
+      $langcode = $translation_config->getLangcode();
+      $reference_config = $this->configFactory->getEditable($translation_config->getName())->get();
+      $this->updateLocaleStorage($translation_config, $langcode, $reference_config);
     }
-
-    $this->updateTranslationStrings($event, $callable);
   }
 
   /**
-   * Updates the translation strings of shipped configuration.
+   * Update locale storage based on configuration translations.
    *
-   * @param \Drupal\language\Config\LanguageConfigOverrideCrudEvent $event
-   *   The language configuration event.
-   * @param $callable
-   *   A callable to apply to each translatable string of the configuration.
+   * @param \Drupal\Core\Config\StorableConfigBase $config
+   *   Active configuration or configuration translation override.
+   * @param string $langcode
+   *   The language code of $config.
+   * @param array $reference_config
+   *   (Optional) Reference configuration to check against if $config was an
+   *   override. This allows us to update locale keys for data not in the
+   *   override but still in the active configuration.
    */
-  protected function updateTranslationStrings(LanguageConfigOverrideCrudEvent $event, $callable) {
-    $translation_config = $event->getLanguageConfigOverride();
-    $name = $translation_config->getName();
-
-    // Only do anything if the configuration was shipped.
-    if ($this->stringStorage->getLocations(['type' => 'configuration', 'name' => $name])) {
-      $source_config = $this->configFactory->getEditable($name);
-      $schema = $this->localeConfigManager->get($name)->getTypedConfig();
-      $this->traverseSchema($schema, $source_config, $translation_config, $callable);
+  protected function updateLocaleStorage(StorableConfigBase $config, $langcode, array $reference_config = array()) {
+    $name = $config->getName();
+    if ($this->localeConfigManager->isSupported($name) && locale_is_translatable($langcode)) {
+      $translatables = $this->localeConfigManager->getTranslatableDefaultConfig($name);
+      $this->processTranslatableData($name, $config->get(), $translatables, $langcode, $reference_config);
     }
   }
 
   /**
-   * Traverses configuration schema and applies a callback to each leaf element.
-   *
-   * It skips leaf elements that are not translatable.
-   *
-   * @param \Drupal\Core\TypedData\TraversableTypedDataInterface $schema
-   *   The respective configuration schema.
-   * @param callable $callable
-   *   The callable to apply to each leaf element. The callable will be called
-   *   with the leaf element and the element key as arguments.
-   * @param string|null $base_key
-   *   (optional) The base key that the schema belongs to. This should be NULL
-   *   for the top-level schema and be populated consecutively when recursing
-   *   into the schema structure.
+   * Process the translatable data array with a given language.
+   *
+   * @param string $name
+   *   The configuration name.
+   * @param array $config
+   *   The active configuration data or override data.
+   * @param array $translatable
+   *   The translatable array structure, see this::getTranslatableData().
+   * @param string $langcode
+   *   The language code to process the array with.
+   * @param array $reference_config
+   *   (Optional) Reference configuration to check against if $config was an
+   *   override. This allows us to update locale keys for data not in the
+   *   override but still in the active configuration.
    */
-  protected function traverseSchema(TraversableTypedDataInterface $schema, Config $source_config, LanguageConfigOverride $translation_config, $callable, $base_key = NULL) {
-    foreach ($schema as $key => $element) {
-      $element_key = implode('.', array_filter([$base_key, $key]));
-
-      // We only care for strings here, so traverse the schema further in the
-      // case of traversable elements.
-      if ($element instanceof TraversableTypedDataInterface) {
-        $this->traverseSchema($element, $source_config, $translation_config, $callable, $element_key);
+  protected function processTranslatableData($name, array $config, array $translatable, $langcode, array $reference_config = array()) {
+    foreach ($translatable as $key => $item) {
+      if (!isset($config[$key])) {
+        if (isset($reference_config[$key])) {
+          $this->resetExistingTranslations($name, $translatable[$key], $reference_config[$key], $langcode);
+        }
+        continue;
+      }
+      if (is_array($item)) {
+        $reference_config = isset($reference_config[$key]) ? $reference_config[$key] : array();
+        $this->processTranslatableData($name, $config[$key], $item, $langcode, $reference_config);
       }
-      // Skip elements which are not translatable.
-      elseif (!empty($element->getDataDefinition()['translatable'])) {
-        $callable(
-          $source_config->get($element_key),
-          $translation_config->getLangcode(),
-          $translation_config->get($element_key)
-        );
+      else {
+        /** @var \Drupal\Core\StringTranslation\TranslationWrapper $item */
+        $this->saveCustomizedTranslation($name, $item->getString(), $item->getOption('context'), $config[$key], $langcode);
       }
     }
   }
 
   /**
-   * Saves a translation string.
-   *
-   * @param string $source_value
-   *   The source string value.
-   * @param string $langcode
-   *   The language code of the translation.
-   * @param string|null $translation_value
-   *   (optional) The translation string value. If omitted, no translation will
-   *   be saved.
+   * Reset existing locale translations to their source values.
+   *
+   * Goes through $translatable to reset any existing translations to the source
+   * string, so prior translations would not reappear in the configuration.
+   *
+   * @param string $name
+   *   The configuration name.
+   * @param array|\Drupal\Core\StringTranslation\TranslationWrapper $translatable
+   *   Either a possibly nested array with TranslationWrapper objects at the
+   *   leaf items or a TranslationWrapper object directly.
+   * @param array|string $reference_config
+   *   Either a possibly nested array with strings at the leaf items or a string
+   *   directly. Only those $translatable items that are also present in
+   *   $reference_config will get translations reset.
+   * @param $langcode
+   *   The language code of the translation being processed.
    */
-  protected function saveTranslation($source_value, $langcode, $translation_value = NULL) {
-    if ($translation_value && ($translation = $this->getTranslation($source_value, $langcode, TRUE))) {
-      if ($translation->isNew() || $translation->getString() != $translation_value) {
-        $translation
-          ->setString($translation_value)
-          ->save();
+  protected function resetExistingTranslations($name, $translatable, $reference_config, $langcode) {
+    if (is_array($translatable)) {
+      foreach ($translatable as $key => $item) {
+        if (isset($reference_config[$key])) {
+          // Process further if the key still exists in the reference active
+          // configuration and the default translation but not the current
+          // configuration override.
+          $this->resetExistingTranslations($name, $item, $reference_config[$key], $langcode);
+        }
       }
     }
+    elseif (!is_array($reference_config)) {
+      /** @var \Drupal\Core\StringTranslation\TranslationWrapper $translatable */
+      $this->saveCustomizedTranslation($name, $translatable->getString(), $translatable->getOption('context'), $reference_config, $langcode);
+    }
   }
 
   /**
    * Saves a translation string and marks it as customized.
    *
-   * @param string $source_value
+   * @param string $name
+   *   The configuration name.
+   * @param string $source
    *   The source string value.
+   * @param string $context
+   *   The source string context.
+   * @param string $translation
+   *   The translation string.
    * @param string $langcode
    *   The language code of the translation.
-   * @param string|null $translation_value
-   *   (optional) The translation string value. If omitted, a customized string
-   *   with the source value will be saved.
-   *
-   * @see \Drupal\locale\LocaleConfigSubscriber::onDelete()
    */
-  protected function saveCustomizedTranslation($source_value, $langcode, $translation_value = NULL) {
-    if ($translation = $this->getTranslation($source_value, $langcode, TRUE)) {
-      if (!isset($translation_value)) {
-        $translation_value = $source_value;
-      }
-      if ($translation->isNew() || $translation->getString() != $translation_value) {
-        $translation
-          ->setString($translation_value)
+  protected function saveCustomizedTranslation($name, $source, $context, $translation, $langcode) {
+    $locale_translation = $this->localeConfigManager->getStringTranslation($name, $langcode, $source, $context);
+    if (!empty($locale_translation)) {
+      // Save this translation as custom if it was a new translation and not the
+      // same as the source. (The interface prefills translation values with the
+      // source). Or if there was an existing translation and the user changed
+      // it (even if it was changed back to the original value). Otherwise the
+      // translation file would be overwritten with the locale copy again later.
+      if (($locale_translation->isNew() && $source != $translation) ||
+          (!$locale_translation->isNew() && $translation != $locale_translation->getString())) {
+        $locale_translation
+          ->setString($translation)
           ->setCustomized(TRUE)
           ->save();
       }
     }
   }
 
-  /**
-   * Deletes a translation string, if it exists.
-   *
-   * @param string $source_value
-   *   The source string value.
-   * @param string $langcode
-   *   The language code of the translation.
-   *
-   * @see \Drupal\locale\LocaleConfigSubscriber::onDelete()
-   */
-  protected function deleteTranslation($source_value, $langcode) {
-    if ($translation = $this->getTranslation($source_value, $langcode, FALSE)) {
-      $translation->delete();
-    }
-  }
-
-  /**
-   * Gets a translation string.
-   *
-   * @param string $source_value
-   *   The source string value.
-   * @param string $langcode
-   *   The language code of the translation.
-   * @param bool $create_fallback
-   *   (optional) By default if a source string could be found and no
-   *   translation in the given language exists yet, a translation object is
-   *   created. This can be circumvented by passing FALSE.
-   *
-   * @return \Drupal\locale\TranslationString|null
-   *   The translation string if one was found or created.
-   */
-  protected function getTranslation($source_value, $langcode, $create_fallback = TRUE) {
-    // There is no point in creating a translation without a source.
-    if ($source_string = $this->stringStorage->findString(['source' => $source_value])) {
-      // Get the translation for this original source string from locale.
-      $conditions = [
-        'lid' => $source_string->lid,
-        'language' => $langcode,
-      ];
-      $translations = $this->stringStorage->getTranslations($conditions + ['translated' => TRUE]);
-      if ($translations) {
-        return reset($translations);
-      }
-      elseif ($create_fallback) {
-        return $this->stringStorage->createTranslation($conditions);
-      }
-    }
-  }
-
 }
diff --git a/core/modules/locale/src/LocaleTypedConfig.php b/core/modules/locale/src/LocaleTypedConfig.php
deleted file mode 100644
index ca31a38..0000000
--- a/core/modules/locale/src/LocaleTypedConfig.php
+++ /dev/null
@@ -1,209 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\locale\LocaleTypedConfig.
- */
-
-namespace Drupal\locale;
-
-use Drupal\Core\Language\LanguageManagerInterface;
-use Drupal\Core\TypedData\ContextAwareInterface;
-use Drupal\Core\TypedData\DataDefinitionInterface;
-use Drupal\Core\Config\Schema\Element;
-use Drupal\Core\Config\TypedConfigManagerInterface;
-use Drupal\Core\TypedData\TraversableTypedDataInterface;
-use Drupal\Core\TypedData\TypedDataInterface;
-
-/**
- * Defines the locale configuration wrapper object.
- */
-class LocaleTypedConfig extends Element {
-
-  /**
-   * The typed configuration data.
-   *
-   * @var \Drupal\Core\Config\Schema\Element
-   */
-  protected $typedConfig;
-
-  /**
-   * The language code for which this is a translation.
-   *
-   * @var string
-   */
-  protected $langcode;
-
-  /**
-   * The locale configuration manager object.
-   *
-   * @var \Drupal\locale\LocaleConfigManager
-   */
-  protected $localeConfig;
-
-  /**
-   * The typed config manager.
-   *
-   * @var \Drupal\Core\Config\TypedConfigManagerInterface
-   */
-  protected $typedConfigManager;
-
-  /**
-   * The language manager.
-   *
-   * @var \Drupal\Core\Language\LanguageManagerInterface
-   */
-  protected $languageManager;
-
-  /**
-   * Constructs a configuration wrapper object.
-   *
-   * @param \Drupal\Core\TypedData\DataDefinitionInterface $definition
-   *   The data definition.
-   * @param string $name
-   *   The configuration object name.
-   * @param string $langcode
-   *   Language code for the source configuration data.
-   * @param \Drupal\locale\LocaleConfigManager $locale_config
-   *   The locale configuration manager object.
-   * @param \Drupal\locale\TypedConfigManagerInterface $typed_config;
-   *   The typed configuration manager interface.
-   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
-   *   The language manager.
-   */
-  public function __construct(DataDefinitionInterface $definition, $name, $langcode, LocaleConfigManager $locale_config, TypedConfigManagerInterface $typed_config, LanguageManagerInterface $language_manager) {
-    parent::__construct($definition, $name);
-    $this->langcode = $langcode;
-    $this->localeConfig = $locale_config;
-    $this->typedConfigManager = $typed_config;
-    $this->languageManager = $language_manager;
-  }
-
-  /**
-   * Gets wrapped typed config object.
-   */
-  public function getTypedConfig() {
-    return $this->typedConfigManager->create($this->definition, $this->value);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getTranslation($langcode) {
-    $options = array(
-      'source' => $this->langcode,
-      'target' => $langcode,
-    );
-    $data = $this->getElementTranslation($this->getTypedConfig(), $options);
-    return $this->typedConfigManager->create($this->definition, $data);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function language() {
-    return $this->languageManager->getLanguage($this->langcode);
-  }
-
-  /**
-   * Checks whether we can translate these languages.
-   *
-   * @param string $from_langcode
-   *   Source language code.
-   * @param string $to_langcode
-   *   Destination language code.
-   *
-   * @return bool
-   *   TRUE if this translator supports translations for these languages.
-   */
-  protected function canTranslate($from_langcode, $to_langcode) {
-    if ($from_langcode == 'en') {
-      return TRUE;
-    }
-    return FALSE;
-  }
-
-  /**
-   * Gets translated configuration data for a typed configuration element.
-   *
-   * @param \Drupal\Core\TypedData\TypedDataInterface $element
-   *   Typed configuration element.
-   * @param array $options
-   *   Array with translation options that must contain the keys defined in
-   *   \Drupal\locale\LocaleTypedConfig::translateElement().
-   *
-   * @return array
-   *   Configuration data translated to the requested language if available,
-   *   an empty array otherwise.
-   */
-  protected function getElementTranslation(TypedDataInterface $element, array $options) {
-    $translation = array();
-    if ($element instanceof TraversableTypedDataInterface) {
-      $translation = $this->getArrayTranslation($element, $options);
-    }
-    elseif ($this->translateElement($element, $options)) {
-      $translation = $element->getValue();
-    }
-    return $translation;
-  }
-
-  /**
-   * Gets translated configuration data for a traversable element.
-   *
-   * @param \Drupal\Core\TypedData\TraversableTypedDataInterface $element
-   *   Typed configuration array element.
-   * @param array $options
-   *   Array with translation options that must contain the keys defined in
-   *   \Drupal\locale\LocaleTypedConfig::translateElement().
-   *
-   * @return array
-   *   Configuration data translated to the requested language.
-   */
-  protected function getArrayTranslation(TraversableTypedDataInterface $element, array $options) {
-    $translation = array();
-    foreach ($element as $key => $property) {
-      $value = $this->getElementTranslation($property, $options);
-      if (!empty($value)) {
-        $translation[$key] = $value;
-      }
-    }
-    return $translation;
-  }
-
-  /**
-   * Translates element's value if it fits our translation criteria.
-   *
-   * For an element to be translatable by locale module it needs to be of base
-   * type 'string' and have 'translatable = TRUE' in the element's definition.
-   * Translatable elements may use these additional keys in their data
-   * definition:
-   * - 'translatable', FALSE to opt out of translation.
-   * - 'translation context', to define the string context.
-   *
-   * @param \Drupal\Core\TypedData\TypedDataInterface $element
-   *   Configuration element.
-   * @param array $options
-   *   Array with translation options that must contain the following keys:
-   *   - 'source', Source language code.
-   *   - 'target', Target language code.
-   *
-   * @return bool
-   *   Whether the element fits the translation criteria.
-   */
-  protected function translateElement(TypedDataInterface $element, array $options) {
-    if ($this->canTranslate($options['source'], $options['target'])) {
-      $definition = $element->getDataDefinition();
-      $value = $element->getValue();
-      if ($value && !empty($definition['translatable'])) {
-        $context = isset($definition['translation context']) ? $definition['translation context'] : '';
-        if ($translation = $this->localeConfig->translateString($this->name, $options['target'], $value, $context)) {
-          $element->setValue($translation);
-          return TRUE;
-        }
-      }
-    }
-    // The element does not have a translation.
-    return FALSE;
-  }
-
-}
diff --git a/core/modules/locale/src/Tests/LocaleConfigManagerTest.php b/core/modules/locale/src/Tests/LocaleConfigManagerTest.php
index 4cc9fb2..58d63cc 100644
--- a/core/modules/locale/src/Tests/LocaleConfigManagerTest.php
+++ b/core/modules/locale/src/Tests/LocaleConfigManagerTest.php
@@ -28,16 +28,16 @@ class LocaleConfigManagerTest extends KernelTestBase {
    * Tests hasTranslation().
    */
   public function testHasTranslation() {
-    $this->installSchema('locale', array('locales_location'));
+    $this->installSchema('locale', array('locales_location', 'locales_source', 'locales_target'));
     $this->installConfig(array('locale_test'));
-    $locale_config_manager = \Drupal::service('locale.config.typed');
+    $locale_config_manager = \Drupal::service('locale.config_manager');
 
     $language = ConfigurableLanguage::createFromLangcode('de');
     $language->save();
-    $result = $locale_config_manager->hasTranslation('locale_test.no_translation', $language);
+    $result = $locale_config_manager->hasTranslation('locale_test.no_translation', $language->getId());
     $this->assertFalse($result, 'There is no translation for locale_test.no_translation configuration.');
 
-    $result = $locale_config_manager->hasTranslation('locale_test.translation', $language);
+    $result = $locale_config_manager->hasTranslation('locale_test.translation', $language->getId());
     $this->assertTrue($result, 'There is a translation for locale_test.translation configuration.');
   }
 }
diff --git a/core/modules/locale/src/Tests/LocaleConfigSubscriberTest.php b/core/modules/locale/src/Tests/LocaleConfigSubscriberTest.php
index f3f7415..c5063bd 100644
--- a/core/modules/locale/src/Tests/LocaleConfigSubscriberTest.php
+++ b/core/modules/locale/src/Tests/LocaleConfigSubscriberTest.php
@@ -68,7 +68,7 @@ protected function setUp() {
     $this->languageManager = $this->container->get('language_manager');
     $this->configFactory = $this->container->get('config.factory');
     $this->stringStorage = $this->container->get('locale.storage');
-    $this->localeConfigManager = $this->container->get('locale.config.typed');
+    $this->localeConfigManager = $this->container->get('locale.config_manager');
 
     $this->installSchema('locale', ['locales_source', 'locales_target', 'locales_location']);
 
@@ -94,7 +94,7 @@ public function testLocaleCreateTranslation() {
     $config_name = 'locale_test.no_translation';
 
     $this->setUpNoTranslation($config_name, 'test', 'Test');
-    $this->saveLocaleTranslationData($config_name, 'test', 'Test (German)');
+    $this->saveLocaleTranslationData($config_name, 'test', 'Test', 'Test (German)');
     $this->assertTranslation($config_name, 'Test (German)', FALSE);
   }
 
@@ -116,7 +116,7 @@ public function testLocaleUpdateTranslation() {
     $config_name = 'locale_test.translation';
 
     $this->setUpTranslation($config_name, 'test', 'English test', 'German test');
-    $this->saveLocaleTranslationData($config_name, 'test', 'Updated German test');
+    $this->saveLocaleTranslationData($config_name, 'test', 'English test', 'Updated German test');
     $this->assertTranslation($config_name, 'Updated German test', FALSE);
   }
 
@@ -142,7 +142,7 @@ public function testLocaleDeleteTranslation() {
 
     $this->setUpTranslation($config_name, 'test', 'English test', 'German test');
     $this->deleteLocaleTranslationData($config_name, 'test', 'English test');
-    $this->assertNoTranslation($config_name, 'English test', FALSE);
+    $this->assertNoTranslation($config_name);
   }
 
   /**
@@ -162,7 +162,7 @@ public function testLocaleDeleteTranslation() {
    */
   protected function setUpNoTranslation($config_name, $key, $source) {
     // Add a source string with the configuration name as a location. This gets
-    // called from locale_config_update_multiple() normally.
+    // called from LocaleConfigManager::updateConfigTranslations() normally.
     $this->localeConfigManager->translateString($config_name, $this->langcode, $source, '');
     $this->languageManager
       ->setConfigOverrideLanguage(ConfigurableLanguage::load($this->langcode));
@@ -192,23 +192,18 @@ protected function setUpNoTranslation($config_name, $key, $source) {
   protected function setUpTranslation($config_name, $key, $source, $translation) {
     // Create source and translation strings for the configuration value and add
     // the configuration name as a location. This would be performed by
-    // locale_translate_batch_import() and locale_config_update_multiple()
-    // normally.
-    $source_object = $this->stringStorage->createString([
-      'source' => $source,
-      'context' => '',
-    ])->save();
-    $this->stringStorage->createTranslation([
-      'lid' => $source_object->getId(),
-      'language' => $this->langcode,
-      'translation' => $translation,
-    ])->save();
-    $this->localeConfigManager->translateString($config_name, $this->langcode, $source, '');
+    // locale_translate_batch_import() invoking
+    // LocaleConfigManager::updateConfigTranslations() normally.
+    $this->localeConfigManager->reset();
+    $this->localeConfigManager
+      ->getStringTranslation($config_name, $this->langcode, $source, '')
+      ->setString($translation)
+      ->save();
     $this->languageManager
       ->setConfigOverrideLanguage(ConfigurableLanguage::load($this->langcode));
 
     $this->assertConfigValue($config_name, $key, $translation);
-    $this->assertTranslation($config_name, $translation, FALSE);
+    $this->assertTranslation($config_name, $translation, TRUE);
   }
 
   /**
@@ -254,12 +249,16 @@ protected function saveLanguageOverride($config_name, $key, $value) {
    * @param string $value
    *   The configuration value to save.
    */
-  protected function saveLocaleTranslationData($config_name, $key, $value) {
+  protected function saveLocaleTranslationData($config_name, $key, $source, $translation) {
     $this->localeConfigManager
-      ->saveTranslationData($config_name, $this->langcode, [$key => $value]);
+      ->getStringTranslation($config_name, $this->langcode, $source, '')
+      ->setString($translation)
+      ->save();
+    $this->localeConfigManager
+      ->saveTranslationOverride($config_name, $this->langcode, [$key => $translation]);
     $this->configFactory->reset($config_name);
 
-    $this->assertConfigValue($config_name, $key, $value);
+    $this->assertConfigValue($config_name, $key, $translation);
   }
 
   /**
@@ -308,7 +307,10 @@ protected function deleteLanguageOverride($config_name, $key, $source_value) {
    *   from the configuration factory after the deletion.
    */
   protected function deleteLocaleTranslationData($config_name, $key, $source_value) {
-    $this->localeConfigManager->deleteTranslationData($config_name, $this->langcode);
+    $this->localeConfigManager
+      ->getStringTranslation($config_name, $this->langcode, $source_value, '')
+      ->delete();
+    $this->localeConfigManager->deleteTranslationOverride($config_name, $this->langcode);
     $this->configFactory->reset($config_name);
 
     $this->assertConfigValue($config_name, $key, $source_value);
diff --git a/core/modules/locale/src/Tests/LocaleConfigTranslationTest.php b/core/modules/locale/src/Tests/LocaleConfigTranslationTest.php
index 27f120d..d5a7c21 100644
--- a/core/modules/locale/src/Tests/LocaleConfigTranslationTest.php
+++ b/core/modules/locale/src/Tests/LocaleConfigTranslationTest.php
@@ -79,12 +79,10 @@ public function testConfigTranslation() {
     );
     $this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations'));
 
-    $wrapper = $this->container->get('locale.config.typed')->get('system.site');
-
     // Get translation and check we've only got the site name.
-    $translation = $wrapper->getTranslation($langcode);
-    $properties = $translation->getElements();
-    $this->assertEqual(count($properties), 1, 'Got the right number of properties after translation');
+    $translation = \Drupal::languageManager()->getLanguageConfigOverride($langcode, 'system.site')->get();
+    $this->assertEqual(count($translation), 1, 'Got the right number of properties after translation.');
+    $this->assertEqual($translation['name'], $site_name, 'Got the right translation for the site name.');
 
     // Check the translated site name is displayed.
     $this->drupalGet($langcode);
@@ -109,12 +107,8 @@ public function testConfigTranslation() {
     );
     $this->drupalPostForm('admin/config/regional/translate', $edit, t('Save translations'));
 
-    $wrapper = $this->container->get('locale.config.typed')->get('core.date_format.medium');
-
-    // Get translation and check we've only got the site name.
-    $translation = $wrapper->getTranslation($langcode);
-    $format = $translation->get('pattern')->getValue();
-    $this->assertEqual($format, 'D', 'Got the right date format pattern after translation.');
+    $translation = \Drupal::languageManager()->getLanguageConfigOverride($langcode, 'core.date_format.medium')->get();
+    $this->assertEqual($translation['pattern'], 'D', 'Got the right date format pattern after translation.');
 
     // Formatting the date 8 / 27 / 1985 @ 13:37 EST with pattern D should
     // display "Tue".
@@ -162,15 +156,8 @@ public function testConfigTranslation() {
     $this->assertTrue(count($translations) == 1 && $translation->source == $string->source && $translation->translation == $image_style_label, 'Got only one translation for image configuration.');
 
     // Try more complex configuration data.
-    $wrapper = $this->container->get('locale.config.typed')->get('image.style.medium');
-
-    $translation = $wrapper->getTranslation($langcode);
-    $property = $translation->get('label');
-    $this->assertEqual($property->getValue(), $image_style_label, 'Got the right translation for image style name after translation');
-
-    // Quick test to ensure translation file exists.
-    $override = \Drupal::languageManager()->getLanguageConfigOverride('xx', 'image.style.medium');
-    $this->assertEqual($override->get('label'), $image_style_label);
+    $translation = \Drupal::languageManager()->getLanguageConfigOverride($langcode, 'image.style.medium')->get();
+    $this->assertEqual($translation['label'], $image_style_label, 'Got the right translation for image style name after translation');
 
     // Uninstall the module.
     $this->drupalPostForm('admin/modules/uninstall', array('uninstall[image]' => "image"), t('Uninstall'));
diff --git a/core/modules/system/src/Tests/Installer/InstallerTranslationMultipleLanguageTest.php b/core/modules/system/src/Tests/Installer/InstallerTranslationMultipleLanguageTest.php
index 94759e4..588f9ca 100644
--- a/core/modules/system/src/Tests/Installer/InstallerTranslationMultipleLanguageTest.php
+++ b/core/modules/system/src/Tests/Installer/InstallerTranslationMultipleLanguageTest.php
@@ -66,8 +66,15 @@ public function testTranslationsLoaded() {
       }
     }
 
-    $config = \Drupal::languageManager()->getLanguageConfigOverride('de', 'user.settings');
+    // German may be in the active config or an override if not set as the
+    // default language.
+    $config = \Drupal::config('user.settings');
+    if ($this->langcode != 'de') {
+      $config = \Drupal::languageManager()->getLanguageConfigOverride('de', 'user.settings');
+    }
     $this->assertEqual($config->get('anonymous'), 'Anonymous German');
+
+    // Spanish is always an override (never used as default language).
     $config = \Drupal::languageManager()->getLanguageConfigOverride('es', 'user.settings');
     $this->assertEqual($config->get('anonymous'), 'Anonymous Spanish');
   }
