diff --git a/core/includes/config.inc b/core/includes/config.inc
index 3e22534..1562ff0 100644
--- a/core/includes/config.inc
+++ b/core/includes/config.inc
@@ -3,6 +3,8 @@
 use Drupal\Core\Config\Config;
 use Drupal\Core\Config\FileStorage;
 use Drupal\Core\Config\NullStorage;
+use Drupal\Core\Config\Metadata\TypedConfig;
+use Drupal\Core\Config\Metadata\MetadataLookup;
 use Drupal\Core\Config\StorageInterface;
 
 /**
@@ -169,6 +171,198 @@ function config_sync_get_changes(StorageInterface $source_storage, StorageInterf
 }
 
 /**
+ * Retrieves metadata for a configuration object or key.
+ *
+ * The metadata has the same structure as the configuration object itself but
+ * the configuration values are replaced by an array containing exactly one
+ * element. The key is .definition and the value is a $definition array to
+ * be passed to typed_data()->create() or
+ * \Drupal\Core\TypedData\TypedDataManager::create().
+ *
+ * For example, for the config file core/modules/system/config/system.site.yml:
+ * @code
+ *   name: Site-Install
+ *   mail: admin@example.com
+ *   slogan: 'This is an example site'
+ *   page:
+ *     403: 'pages/403'
+ *     404: 'pages/404'
+ * @endcode
+ *
+ * The metadata in core/modules/system/meta/system.site.yml:
+ * @code
+ *   name:
+ *     .definition:
+ *       label: 'Site name'
+ *       type: text
+ *   mail:
+ *     .definition:
+ *       label: 'Site mail'
+ *   slogan:
+ *     .definition:
+ *       label: 'Site slogan'
+ *       type: text
+ *   page:
+ *     .definition:
+ *       label: 'Default pages'
+ *     403:
+ *       .definition:
+ *         label: 'Default 403 (access denied) page'
+ *     404:
+ *       .definition:
+ *         label: 'Default 404 (not found) page'
+ * @endcode
+ *
+ * Elements that don't have an explicit type will default to the type 'string'
+ * unless they have children elements (like 'page' in the example) or the
+ * 'list' property set to true, in which case they will default to the type
+ * 'config_element'.
+ *
+ * Nested lists of elements, that contain additional metadata for each of the
+ * elements in a different file are also supported.
+ *
+ * Here are parts of core/modules/user/config/user.mail.yml:
+ * @code
+ *  status_blocked:
+ *    body: "[user:name],\n\nYour account on [site:name] has been blocked.\n\n--  [site:name] team"
+ *    subject: 'Account details for [user:name] at [site:name] (blocked)'
+ *   status_canceled:
+ *     body: "[user:name],\n\nYour account on [site:name] has been canceled.\n\n--  [site:name] team"
+ *     subject: 'Account details for [user:name] at [site:name] (canceled)'
+ * @endcode
+ *
+ * The metadata is in core/modules/user/meta/user.mail.yml:
+ * @code
+ *   .definition:
+ *     label: 'User mails'
+ *     list: '1'
+ *     list settings:
+ *       elements name: 'user.mail.%'
+ * @endcode
+ *
+ * For every element, meta/user.mail.%.yml is read as specified by the
+ * 'elements name' list settings:
+ * @end
+ *   .definition:
+ *     label: 'Mail text'
+ *   subject:
+ *     .definition:
+ *       label: 'Subject'
+ *       type: 'text'
+ *   body:
+ *     .definition:
+ *       label: 'Body'
+ *       type: 'text'
+ * @endcode
+ *
+ * As said above, the type for list elements is config_element, so the
+ * $definition array for the status_blocked array is:
+ * @code
+ *   array(
+ *     'type' => 'config_element',
+ *     'label' => 'Mail text',
+ *   );
+ * @endcode
+ *
+ * The % in meta/user.mail.%.yml is a placeholder and the array key can be
+ * used for more specific metadata. So, meta/user.mail.status_canceled.yml
+ * could be used to specify some metadata only for the status_canceled list
+ * element.
+ *
+ * Other lists use a property inside a list element for this key. For example,
+ * core/modules/image/config/image.style.medium.yml:
+ * @code
+ *   name: medium
+ *   label: Medium (220x220)
+ *   effects:
+ *     bddf0d06-42f9-4c75-a700-a33cafa25ea0:
+ *       name: image_scale
+ *       data:
+ *         width: '220'
+ *         height: '220'
+ *         upscale: '1'
+ *       weight: '0'
+ *       ieid: bddf0d06-42f9-4c75-a700-a33cafa25ea0
+ * @endcode
+ *
+ * The metadata is in core/modules/image/meta/image.style.%.yml:
+ * @code
+ *   name:
+ *     .definition:
+ *       label: 'Machine name'
+ *       type: string
+ *   label:
+ *     .definition:
+ *       label: 'Label'
+ *       type: text
+ *   effects:
+ *     .definition:
+ *       label: 'Style effects'
+ *       list: '1'
+ *       list settings:
+ *         elements name: 'image.style.effects.%'
+ *         elements key: 'name'
+ * @endcode
+ *
+ * In this case we want to reuse the metadata for 'image_scale' every time
+ * that effect is used. So the elements key is set to name, which in this
+ * example will indeed be 'image_scale' so to find the metadata for the
+ * bddf0d06-42f9-4c75-a700-a33cafa25ea0 list element,
+ * meta/image.style.effects.image_scale.yml is used if it exists and
+ * meta/image.style.effects.%.yml if it does not.
+ *
+ * For example here are some parts of
+ * meta/image.style.effects.image_scale.yml:
+ * @code
+ *   .definition:
+ *     label: 'Image scale'
+ *   name:
+ *     .definition:
+ *       label: 'Machine name'
+ *       type: string
+ *   ieid:
+ *     .definition:
+ *       label: 'IEID'
+ *   data:
+ *     .definition:
+ *       label: 'Data'
+ *     width:
+ *       .definition:
+ *         label: 'Width'
+ *         type: 'integer'
+ * @endcode
+ *
+ * @see Drupal\Core\TypedData\TypedDataManager::create()
+ * @see Drupal\Core\Config\Metadata\ElementBase
+ * @see Drupal\Core\Config\Metadata\ListElement
+ *
+ * @param string $name
+ *   The name or key of the configuration object, the same as passed to
+ *   config().
+ *
+ * @return Drupal\Core\Config\Metadata\MetadataLookup
+ *   A metadata array.
+ *
+ * @see Drupal\Core\Config\Metadata\ElementInterface::getMetadata()
+ */
+function config_metadata($name) {
+  return drupal_container()->get('config.metadata')->offsetGet($name);
+}
+
+/**
+ * Retrieves a configuration wrapper object to access data as typed properties.
+ *
+ * @param string $name
+ *   The name of the configuration object to retrieve.
+ *
+ * @return Drupal\Core\Config\Metadata\TypedConfig
+ *   A configuration wrapper object.
+ */
+function config_wrapper($name) {
+  return drupal_container()->get('config.typed')->get($name);
+}
+
+/**
  * Writes an array of config file changes from a source storage to a target storage.
  *
  * @param array $config_changes
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index d5983b6..5327a77 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -690,6 +690,12 @@ function install_tasks($install_state) {
       'type' => 'batch',
       'run' => $needs_translations ? INSTALL_TASK_RUN_IF_NOT_COMPLETED : INSTALL_TASK_SKIP,
     ),
+    'install_update_configuration_translations' => array(
+      'display_name' => st('Translate configuration'),
+      'display' => $needs_translations,
+      'type' => 'batch',
+      'run' => $needs_translations ? INSTALL_TASK_RUN_IF_NOT_COMPLETED : INSTALL_TASK_SKIP,
+    ),
     'install_finished' => array(
       'display_name' => st('Finished'),
     ),
@@ -1673,6 +1679,20 @@ function install_import_translations_remaining(&$install_state) {
 }
 
 /**
+ * Creates configuration translations.
+ *
+ * @param $install_state
+ *   An array of information about the current installation state.
+ *
+ * @return
+ *   The batch definition, if there are configuration objects to update.
+ */
+function install_update_configuration_translations(&$install_state) {
+  include_once drupal_get_path('module', 'locale') . '/locale.bulk.inc';
+  return locale_config_batch_update_components(array('langcode' => $install_state['parameters']['langcode']));
+}
+
+/**
  * Performs final installation steps and displays a 'finished' page.
  *
  * @param $install_state
diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php
index 6fc2a87..f07c798 100644
--- a/core/lib/Drupal/Core/Config/Config.php
+++ b/core/lib/Drupal/Core/Config/Config.php
@@ -323,7 +323,7 @@ public function clear($key) {
   /**
    * Loads configuration data into this object.
    *
-   * @return Drupal\Core\Config\Config
+   * @return \Drupal\Core\Config\Config
    *   The configuration object.
    */
   public function load() {
diff --git a/core/lib/Drupal/Core/Config/ConfigFactory.php b/core/lib/Drupal/Core/Config/ConfigFactory.php
index ca36ce7..c959673 100644
--- a/core/lib/Drupal/Core/Config/ConfigFactory.php
+++ b/core/lib/Drupal/Core/Config/ConfigFactory.php
@@ -58,7 +58,7 @@ public function __construct(StorageInterface $storage, EventDispatcher $event_di
    * @param string $name
    *   The name of the configuration object to construct.
    *
-   * @return Drupal\Core\Config\Config
+   * @return \Drupal\Core\Config\Config
    *   A configuration object with the given $name.
    */
   public function get($name) {
diff --git a/core/lib/Drupal/Core/Config/InstallStorage.php b/core/lib/Drupal/Core/Config/InstallStorage.php
index eceef01..ad3476c 100644
--- a/core/lib/Drupal/Core/Config/InstallStorage.php
+++ b/core/lib/Drupal/Core/Config/InstallStorage.php
@@ -15,6 +15,13 @@
 class InstallStorage extends FileStorage {
 
   /**
+   * Folder map indexed by configuration name
+   *
+   * @var array
+   */
+  protected $folders;
+
+  /**
    * Overrides Drupal\Core\Config\FileStorage::__construct().
    */
   public function __construct() {
@@ -38,17 +45,9 @@ public function __construct() {
    *   afterwards check for a corresponding module or theme.
    */
   public function getFilePath($name) {
-    // Extract the owner.
-    $owner = strtok($name, '.');
-    // Determine the path to the owner.
-    $path = FALSE;
-    foreach (array('profile', 'module', 'theme') as $type) {
-      if ($path = drupal_get_path($type, $owner)) {
-        $file = $path . '/config/' . $name . '.' . static::getFileExtension();
-        if (file_exists($file)) {
-          return $file;
-        }
-      }
+    $folders = $this->getAllFolders();
+    if (isset($folders[$name])) {
+      return $folders[$name] . '/' . $name . '.' . $this->getFileExtension();
     }
     // If any code in the early installer requests a configuration object that
     // does not exist anywhere as default config, then that must be mistake.
@@ -90,6 +89,75 @@ public function rename($name, $new_name) {
    * @throws Drupal\Core\Config\StorageException
    */
   public function listAll($prefix = '') {
-    throw new StorageException('List operation is not allowed during install.');
+    $names = array_keys($this->getAllFolders());
+    if (!$prefix) {
+      return $names;
+    }
+    else {
+      $return = array();
+      foreach ($names as $index => $name) {
+        if (strpos($name, $prefix) === 0 ) {
+          $return[$index] = $names[$index];
+        }
+      }
+      return $return;
+    }
+  }
+
+  /**
+   * Returns a map of all metadata names and the module they belong to.
+   *
+   * @return array
+   *   An array mapping metadata names with directories.
+   */
+  protected function getAllFolders() {
+    if (!isset($this->folders)) {
+      $this->folders = $this->getComponentNames('profile', array(drupal_get_profile()));
+      $this->folders += $this->getComponentNames('module', module_list());
+      $this->folders += $this->getComponentNames('theme', array_keys(list_themes()));
+    }
+    return $this->folders;
+  }
+
+  /**
+   * Get all configuration names and folders for a list of modules or themes.
+   *
+   * @param string $type
+   *   Type of components: 'module' | 'theme' | 'profile'
+   * @param array $list
+   *   Array of theme or module names.
+   *
+   * @return array
+   *   Folders indexed by configuration name.
+   */
+  public function getComponentNames($type, $list) {
+    $extension = '.' . $this->getFileExtension();
+    $folders = array();
+    foreach ($list as $name) {
+      $directory = $this->getComponentFolder($type, $name);
+      if (file_exists($directory)) {
+        $files = glob($directory . '/*' . $extension);
+        foreach ($files as $filename) {
+          $name = basename($filename, $extension);
+          $folders[$name] = $directory;
+        }
+      }
+    }
+    return $folders;
+  }
+
+  /**
+   * Get folder inside each component that contains the files.
+   *
+   * @param string $type
+   *   Component type: 'module' | 'theme' | 'profile'
+   * @param string $name
+   *   Component name.
+   *
+   * @return string
+   *   The configuration folder name for this component.
+   */
+  protected function getComponentFolder($type, $name) {
+    return drupal_get_path($type, $name) . '/config';
   }
 }
diff --git a/core/lib/Drupal/Core/Config/Metadata/ConfigElement.php b/core/lib/Drupal/Core/Config/Metadata/ConfigElement.php
new file mode 100644
index 0000000..742cccc
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Metadata/ConfigElement.php
@@ -0,0 +1,108 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Config\Metadata\ConfigElement.
+ */
+
+namespace Drupal\Core\Config\Metadata;
+
+use Drupal\Core\Language\Language;
+use Drupal\Core\TypedData\ComplexDataInterface;
+use InvalidArgumentException;
+
+/**
+ * Defines a generic 'configuration element' data type.
+ *
+ * Wraps configuration data and metadata allowing access to configuration data
+ * using the ComplexDataInterface API. This object may contain any number and
+ * type of nested properties.
+ */
+class ConfigElement extends ElementBase implements ComplexDataInterface {
+  /**
+   * Implements Drupal\Core\TypedData\ComplexDataInterface::get().
+   */
+  public function get($property_name) {
+    if ($property = $this->getElement($property_name)) {
+      return $property;
+    }
+    else {
+      throw new InvalidArgumentException(format_string("The configuration property @key doesn't exist.", array(
+          '@key' => $property_name,
+      )));
+    }
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\ComplexDataInterface::set().
+   */
+  public function set($property_name, $value) {
+    // Set the data into the configuration array but behave according to the
+    // interface specification when we've got a null value.
+    if (isset($value)) {
+      $this->value[$property_name] = $value;
+      return $this->get($property_name);
+    }
+    else {
+      // In these objects, when clearing the value, the property is gone.
+      // As this needs to return a property, we get it before we delete it.
+      $property = $this->get($property_name);
+      unset($this->value[$property_name]);
+      $property->setValue($value);
+      return $property;
+    }
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\ComplexDataInterface::getProperties().
+   */
+  public function getProperties($include_computed = FALSE) {
+    return $this->toArray();
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\ComplexDataInterface::getPropertyValues().
+   */
+  public function getPropertyValues() {
+    return $this->value;
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\ComplexDataInterface::setPropertyValues().
+   */
+  public function setPropertyValues($values) {
+    foreach ($values as $name => $value) {
+      $this->value[$name] = $value;
+    }
+    return $this;
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinition().
+   */
+  public function getPropertyDefinition($name) {
+    $value = isset($this->value[$name]) ? $this->value[$name] : NULL;
+    $metadata = $this->getElementMetadata($key);
+    $definition = ElementBase::buildDefinition($value, $metadata);
+    return $definition ? $definition : FALSE;
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinitions().
+   */
+  public function getPropertyDefinitions() {
+    $list = array();
+    foreach ($this->getAllKeys() as $key) {
+      $list[$key] = $this->getPropertyDefinition($key);
+    }
+    return $list;
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\ComplexDataInterface::isEmpty().
+   */
+  public function isEmpty() {
+    return empty($this->value);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Config/Metadata/ElementBase.php b/core/lib/Drupal/Core/Config/Metadata/ElementBase.php
new file mode 100644
index 0000000..d92f574
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Metadata/ElementBase.php
@@ -0,0 +1,293 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Config\Metadata\ElementBase.
+ */
+
+namespace Drupal\Core\Config\Metadata;
+
+use Drupal\Core\TypedData\TypedDataInterface;
+use Drupal\Core\TypedData\Type\TypedData;
+use ArrayIterator;
+use IteratorAggregate;
+
+/**
+ * Defines a 'configuration wrapper element' data type.
+ *
+ * Wraps configuration data and metadata allowing access to configuration data
+ * using the TypedDataInterface API. These objects may be nested.
+ */
+abstract class ElementBase extends TypedData implements IteratorAggregate, ElementInterface {
+
+  /**
+   * The configuration key relative to the parent's.
+   *
+   * @var string
+   */
+  protected $key;
+
+  /**
+   * The configuration data.
+   *
+   * @var array
+   */
+  protected $value;
+
+  /**
+   * Full metadata for the element.
+   *
+   * @var array
+   */
+  protected $metadata;
+
+  /**
+   * Parent configuration wrapper.
+   *
+   * @var \Drupal\Core\Config\Metadata\ElementInterface
+   */
+  protected $parent;
+
+  /**
+   * Gets contained typed data object.
+   *
+   * @param string $key
+   *   Configuration key.
+   *
+   * @return \Drupal\Core\TypedData\TypedDataInterface|null
+   *   The property object or null if the property doesn't exist.
+   */
+  protected function getElement($key) {
+    // Only attempt to build the element if it actually has configuration data.
+    if (isset($this->value[$key])) {
+      return $this->buildElement($this->value[$key], $this->getElementMetadata($key), $key, $this);
+    }
+    else {
+      return NULL;
+    }
+  }
+
+  /**
+   * Gets the built metadata array for a child element.
+   *
+   * @param $key
+   *   Key from the configuration data array.
+   *
+   * @return array
+   *   The metadata for the element, defaults to an empty array.
+   */
+  protected function getElementMetadata($key) {
+    // Metadata for nested elements must be built before.
+    if (!isset($this->metadata[$key])) {
+      $this->metadata[$key] = $this->buildElementMetadata($key, $this->value[$key]);
+    }
+    return $this->metadata[$key];
+  }
+
+  /**
+   * Builds metadata for a child element.
+   *
+   * @param string $key
+   *   Element's key.
+   * @param mixed $data
+   *   Configuration data for the element.
+   *
+   * @return array
+   *   Configuration metadata for the element.
+   */
+  protected function buildElementMetadata($key, $data) {
+    return isset($this->metadata[$key]) ? $this->metadata[$key] : array();
+  }
+
+  /**
+   * Gets valid configuration data keys.
+   *
+   * @return array
+   *   Array of valid configuration data keys.
+   */
+  protected function getAllKeys() {
+    return is_array($this->value) ? array_keys($this->value) : array();
+  }
+
+  /**
+   * Gets typed data properties as array.
+   *
+   * @return array
+   *   List of TypedDataInterface objects indexed by key.
+   */
+  protected function toArray() {
+    $array = array();
+    foreach ($this->getAllKeys() as $key) {
+      $array[$key] = $this->getElement($key);
+    }
+    return array_filter($array);
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\ElementInterface::getKey().
+   */
+  public function getKey() {
+    $parent = isset($this->parent) ? $this->parent->getKey() : NULL;
+    return $parent || is_numeric($parent) ? $parent . '.' . $this->key : $this->key;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\ElementInterface::getLabel().
+   */
+  public function getLabel() {
+    return isset($this->definition['label']) ? $this->definition['label'] : $this->getKey();
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\ElementInterface::getMetadata().
+   */
+  public function getMetadata() {
+    return $this->metadata;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\ElementInterface::setMetadata().
+   */
+  public function setMetadata($metadata) {
+    $this->metadata = $metadata;
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\TypedDataInterface::validate().
+   */
+  public function validate() {
+    // This will be ok if we have any config data at all.
+    return isset($this->value);
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\ContextAwareInterface::getName().
+   */
+  public function getName() {
+    return $this->key;
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\ContextAwareInterface::setName().
+   */
+  public function setName($name) {
+    $this->key = $name;
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\ContextAwareInterface::getParent().
+  */
+  public function getParent() {
+    return $this->parent;
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\ContextAwareInterface::setParent().
+  */
+  public function setParent($parent) {
+    $this->parent = $parent;
+  }
+
+  /**
+   * Implements ArrayAccess::offsetExists().
+   */
+  public function offsetExists($offset) {
+    return isset($this->value[$offset]);
+  }
+
+  /**
+   * Implements ArrayAccess::offsetGet().
+   */
+  public function offsetGet($offset) {
+    return $this->getElement($offset);
+  }
+
+  /**
+   * Implements ArrayAccess::offsetSet().
+   */
+  public function offsetSet($offset, $value) {
+    if ($value instanceof TypedDataInterface) {
+      $value = $value->getValue();
+    }
+    $this->value[$offset] = $value;
+  }
+
+  /**
+   * Implements ArrayAccess::offsetUnset().
+   */
+  public function offsetUnset($offset) {
+    unset($this->value[$offset]);
+  }
+
+  /**
+   * Implements Countable::count().
+   */
+  public function count() {
+    return count($this->getValue());
+  }
+
+  /**
+   * Implements IteratorAggregate::getIterator().
+   */
+  public function getIterator() {
+    return new ArrayIterator($this->toArray());
+  }
+
+  /**
+   * Builds a typed data element for a piece of configuration data.
+   *
+   * @param mixed $value
+   *   Configuration data to be used as value.
+   * @param array $metadata
+   *   Metadata for the element.
+   * @param string $key
+   *   Nested configuration key to be used as element's name.
+   * @param \Drupal\Core\Config\Metadata\ElementInterface $parent
+   *   (optional) Element to be used as parent of this one. Defaults to NULL.
+   *
+   * @return \Drupal\Core\TypedData\TypedDataInterface
+   *   A TypedDataInterface object containing the configuration value.
+   */
+  public static function buildElement($value, $metadata, $key, $parent = NULL) {
+    $definition = self::buildDefinition($value, $metadata);
+    $context = array('name' => $key, 'parent' => $parent);
+    $element = typed_data()->create($definition, $value, $context);
+    if ($element instanceof ElementInterface) {
+      // If this is a nested ConfigElement we need to set the metadata for it.
+      $element->setMetadata($metadata);
+    }
+    return $element;
+  }
+
+  /**
+   * Builds element definition from configuration metadata.
+   *
+   * Though the configuration metadata contains basically the typed data
+   * element's definition, we need to take out the dot prefix from property
+   * names and add some default data types.
+   *
+   * @param mixed $data
+   *   The configuration data for the element.
+   *
+   * @param array $metadata
+   *   The raw metadata array for the element.
+   *
+   * @return array
+   *   The elemement's data definition.
+   */
+  public static function buildDefinition($data, $metadata) {
+    $definition = isset($metadata['.definition']) ? $metadata['.definition'] : array();
+    unset($metadata['.definition']);
+    // The default type will depend on whether we've got children or not.
+    if (!isset($definition['type'])) {
+      if (!empty($metadata) || is_array($data) || !empty($definition['list']) || !empty($definition['list settings'])) {
+        $definition['type'] = 'config_element';
+      }
+      else {
+        $definition['type'] = 'string';
+      }
+    }
+    return $definition;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Config/Metadata/ElementInterface.php b/core/lib/Drupal/Core/Config/Metadata/ElementInterface.php
new file mode 100644
index 0000000..c1d5276
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Metadata/ElementInterface.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Config\Metadata\ElementInterface.
+ */
+
+namespace Drupal\Core\Config\Metadata;
+
+use Drupal\Core\TypedData\ContextAwareInterface;
+use Drupal\Core\TypedData\TypedDataInterface;
+use ArrayAccess;
+use Countable;
+use Traversable;
+
+/**
+ * Interface for data structures that contain configuration data and metadata.
+ */
+interface ElementInterface extends Traversable, ArrayAccess, Countable, TypedDataInterface, ContextAwareInterface {
+
+  /**
+   * Gets configuration key for this object.
+   *
+   * @return string
+   *   Configuration key for this structure.
+   */
+  public function getKey();
+
+  /**
+   * Gets human readable label for this structure or children of it.
+   *
+   * @return string
+   *   The object label.
+   */
+  public function getLabel();
+
+  /**
+   * Gets raw configuration metadata array for this element.
+   *
+   * @return array
+   *   The full metadata array for this element.
+   *
+   * @see config_metadata()
+   */
+  public function getMetadata();
+
+  /**
+   * Sets the configuration metadata array for the element.
+   *
+   * @param array $metadata
+   *   Array of configuration metadata.
+   *
+   * @see config_metadata()
+   */
+  public function setMetadata($metadata);
+
+}
diff --git a/core/lib/Drupal/Core/Config/Metadata/ListElement.php b/core/lib/Drupal/Core/Config/Metadata/ListElement.php
new file mode 100644
index 0000000..659628c
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Metadata/ListElement.php
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Config\Metadata\ListElement.
+ */
+
+namespace Drupal\Core\Config\Metadata;
+
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\TypedData\ListInterface;
+
+/**
+ * Defines a 'configuration wrapper' that contains a list of elements.
+ *
+ * This is a configuration element that wraps a list of nested elements in the
+ * configuration data.
+ *
+ * Supported list settings (below the definition's 'list settings' key) are:
+ * - 'elements name', Configuration name for each of the nested elements, may
+ *   contain the placeholder '%' to replace by the element's key.
+ * - 'elements key', Name of the configuration data property to be used as the
+ *   key for name replacement.
+ */
+class ListElement extends ElementBase implements ListInterface {
+
+  /**
+   * Overrides Drupal\Core\Config\Metadata\ElementBase::buildElementMetadata().
+   */
+  protected function buildElementMetadata($key, $data) {
+    $meta = parent::buildElementMetadata($key, $data);
+    // When the parent element is a list, elements may have special settings.
+    if (isset($this->definition['list settings'])) {
+      $settings = $this->definition['list settings'];
+      if (isset($settings['elements_base']) && $base = config_metadata($settings['elements_base'])) {
+        // Load common base metadata for all elements.
+        $meta = NestedArray::mergeDeep($base, $meta);
+      }
+      if (isset($settings['elements name'])) {
+        // Load specific metadata for each element.
+        $name = $settings['elements name'];
+        $element_key = $key;
+        if (isset($settings['elements key'])) {
+          // The search key for this one is in the data itself.
+          $element_key = isset($data[$settings['elements key']]) ? $data[$settings['elements key']] : NULL;
+        }
+        if ($element_key) {
+          $name = str_replace('%', $element_key, $name);
+        }
+        if ($merge = config_metadata($name)) {
+          $meta = NestedArray::mergeDeep($meta, $merge);
+        }
+      }
+      if (isset($settings['elements_label']) && !isset($meta['.label'])) {
+        // Extract element's label from configuration data.
+        if (!empty($data[$settings['elements_label']])) {
+          $meta['.label'] = $data[$settings['elements_label']];
+        }
+      }
+    }
+    return $meta;
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\ListInterface::isEmpty().
+   */
+  public function isEmpty() {
+    return empty($this->data);
+  }
+}
diff --git a/core/lib/Drupal/Core/Config/Metadata/MetadataLookup.php b/core/lib/Drupal/Core/Config/Metadata/MetadataLookup.php
new file mode 100644
index 0000000..8e1f4ac
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Metadata/MetadataLookup.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Config\Metadata\MetadataLookup.
+ *
+ * Metadata lives in a modules 'meta' directory, next to the 'config' directory,
+ * which contains the actual configuration. For each configuration file in the
+ * 'config' directory there may be another one with the same name in the 'meta'
+ * directory that contains the metadata for that configuration object.
+ *
+ * The name of the configuration object may contain the placeholder '%' as the
+ * last component, which denotes generic metadata for all objects of this
+ * type. For the 'large' image style, for example, both "image.style.%" and
+ * "image.style.large" would be valid metadata files. In case both of these
+ * files exist, we load the generic metadata (in this example from
+ * "image.style.%") first and add the specific metadata (in this example from
+ * "image.style.large") on top of it.
+ *
+ * @see config_metadata()
+ */
+
+namespace Drupal\Core\Config\Metadata;
+
+use Drupal\Core\Utility\CacheArray;
+
+/**
+ * Extends CacheArray to allow searching and caching metadata.
+ */
+class MetadataLookup extends CacheArray {
+
+  /**
+   * Filesystem marker for base metadata name.
+   */
+  const BASE_MARK = '%';
+
+  /**
+   * Storage to retrieve the metadata.
+   *
+   * @var \Drupal\Core\Config\Metadata\MetadataStorage
+   */
+  protected $metadataStorage;
+
+  /**
+   * Constructs a ConfigMetadata cache object.
+   */
+  public function __construct(MetadataStorage $metadata_storage) {
+    $this->metadataStorage = $metadata_storage;
+    parent::__construct('config:metadata', 'cache');
+  }
+
+  /**
+   * Overrides DrupalCacheArray::resolveCacheMiss().
+   */
+  protected function resolveCacheMiss($offset) {
+    $metadata = $this->metadataStorage->read($offset);
+    // If no metadata with this exact name, try the fallback name.
+    if ($metadata === FALSE && ($basename = $this->getFallbackName($offset))) {
+      $metadata = $this->offsetGet($basename);
+    }
+    $this->storage[$offset] = $metadata ? $metadata : array();
+    return $this->storage[$offset];
+  }
+
+  /**
+   * Gets fallback metadata name.
+   *
+   * @param string $name
+   *   Configuration name or key.
+   *
+   * @return string
+   *   Same name with the last part replaced by the filesystem marker.
+   */
+  protected static function getFallbackName($name) {
+    $replaced = preg_replace('/\.[^.]+$/', '.' . self::BASE_MARK, $name);
+    if ($replaced != $name) {
+      return $replaced;
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Config/Metadata/MetadataStorage.php b/core/lib/Drupal/Core/Config/Metadata/MetadataStorage.php
new file mode 100644
index 0000000..5f16f41
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Metadata/MetadataStorage.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Config\Metadata\MetadataStorage.
+ */
+
+namespace Drupal\Core\Config\Metadata;
+
+use Drupal\Core\Config\InstallStorage;
+
+/**
+ * Defines the file storage controller for metadata files.
+ */
+class MetadataStorage extends InstallStorage {
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::exists().
+   */
+  public function exists($name) {
+    return array_key_exists($name, $this->getAllFolders());
+  }
+
+  /**
+   * Overrides Drupal\Core\Config\InstallStorage::getComponentFolder().
+   */
+  protected function getComponentFolder($type, $name) {
+    return drupal_get_path($type, $name) . '/meta';
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Config/Metadata/TypedConfig.php b/core/lib/Drupal/Core/Config/Metadata/TypedConfig.php
new file mode 100644
index 0000000..ef4e3cc
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Metadata/TypedConfig.php
@@ -0,0 +1,141 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Config\Metadata\TypedConfig.
+ */
+
+namespace Drupal\Core\Config\Metadata;
+
+use Drupal\Core\Config\Config;
+use Drupal\Core\Language\Language;
+use Drupal\Core\TypedData\ComplexDataInterface;
+use Drupal\Core\TypedData\ListInterface;
+
+/**
+ * Defines the configuration wrapper object.
+ *
+ * This object wraps the full configuration object's data and is the outermost
+ * level for configuration wrappers. All of the others are nested in this one.
+ */
+class TypedConfig extends Config {
+
+  /**
+   * The base typed data element for this configuration data.
+   *
+   * @var \Drupal\Core\TypedData\TypedDataInterface
+   */
+  protected $base;
+
+  /**
+   * Constructs a configuration wrapper object.
+   *
+   * @param string $name
+   *   The configuration object name.
+   * @param array $data
+   *   The configuration data.
+   */
+  public function __construct($name, $data) {
+    $this->name = $name;
+    $this->data = $data;
+  }
+
+  /**
+   * Gets data from this config object.
+   *
+   * @return mixed
+   *   The data that was requested.
+   */
+  public function getData() {
+    return $this->data;
+  }
+
+  /**
+   * Overrides Drupal\Core\Config\Config::setData().
+   */
+  public function setData(array $data) {
+    $this->data = $data;
+    $this->setBase();
+    return $this;
+  }
+
+  /**
+   * Overrides Drupal\Core\Config::get()
+   *
+   * Unlike the get() method of the base configuration object, this one returns
+   * a typed data object instead of raw configuration data.
+   *
+   * @return \Drupal\Core\TypedData\TypedDataInterface
+   *   The data that was requested.
+   */
+  public function get($key = '') {
+    $element = $this->getBase();
+    if (!empty($key)) {
+      // Get nested element recursively.
+      $parts = explode('.', $key);
+      while ($element && $name = array_shift($parts)) {
+        if ($element instanceof ListInterface) {
+          $element = $element[$name];
+        }
+        elseif ($element instanceof ComplexDataInterface) {
+          $element = $element->get($name);
+        }
+        else {
+          $element = NULL;
+        }
+      }
+
+    }
+    return $element;
+  }
+
+  /**
+   * Overrides Drupal\Core\Config\Config::set().
+   */
+  public function set($key, $value) {
+    parent::set($key, $value);
+    $this->setBase();
+    return $this;
+  }
+
+  /**
+   * Overrides Drupal\Core\Config\Config::clear().
+   */
+  public function clear($key) {
+    parent::clear($key);
+    $this->setBase();
+    return $this;
+  }
+
+  /**
+   * Gets the configuration language.
+   *
+   * @return \Drupal\Core\Language\Language
+   *   The language object.
+   */
+  public function getLanguage() {
+    // The default language will be English as this is hardcoded information.
+    $langcode = isset($this->data['language']) ? $this->data['language'] : 'en';
+    return new Language(array('langcode' => $langcode));
+  }
+
+  /**
+   * Gets the base configuration element.
+   */
+  protected function getBase() {
+    if (!isset($this->base)) {
+      $this->base = ElementBase::buildElement($this->data, config_metadata($this->name), '');
+    }
+    return $this->base;
+  }
+
+  /**
+   * Resets the base element's data.
+   */
+  protected function setBase() {
+    if (isset($this->base)) {
+      $this->base->setValue($this->data);
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Config/Metadata/TypedConfigFactory.php b/core/lib/Drupal/Core/Config/Metadata/TypedConfigFactory.php
new file mode 100644
index 0000000..76d97d5
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Metadata/TypedConfigFactory.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Config\Metadata\TypedConfigFactory.
+ */
+
+namespace Drupal\Core\Config\Metadata;
+
+use Drupal\Core\Config\ConfigFactory;
+
+/**
+ * Factory for configuration wrapper objects to access data as typed properties.
+ */
+class TypedConfigFactory {
+
+  /**
+   * @var \Drupal\Core\Config\ConfigFactory
+   */
+  protected $configFactory;
+
+  /**
+   * Constructs the typed data config factory.
+   *
+   * @param \Drupal\Core\Config\ConfigFactory $config_factory
+   */
+  function __construct(ConfigFactory $config_factory) {
+    $this->configFactory = $config_factory;
+  }
+
+  /**
+   * Returns configuration wrapper object to access data as typed properties.
+   *
+   * @param string $name
+   * @return TypedConfig
+   */
+  function get($name) {
+    return new TypedConfig($name, $this->configFactory->get($name)->load()->get());
+  }
+
+}
diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php
index 622ce3d..59ce22d 100644
--- a/core/lib/Drupal/Core/CoreBundle.php
+++ b/core/lib/Drupal/Core/CoreBundle.php
@@ -68,6 +68,13 @@ public function build(ContainerBuilder $container) {
     $container->register('entity.query', 'Drupal\Core\Entity\Query\QueryFactory')
       ->addArgument(new Reference('service_container'));
 
+    // Add the config metadata service.
+    $container->register('config.metadata.storage', 'Drupal\Core\Config\Metadata\MetadataStorage');
+    $container->register('config.metadata', 'Drupal\Core\Config\Metadata\MetadataLookup')
+      ->addArgument(new Reference('config.metadata.storage'));
+    $container->register('config.typed', 'Drupal\Core\Config\Metadata\TypedConfigFactory')
+      ->addArgument(new Reference('config.factory'));
+
     $container->register('router.dumper', 'Drupal\Core\Routing\MatcherDumper')
       ->addArgument(new Reference('database'));
     $container->register('router.builder', 'Drupal\Core\Routing\RouteBuilder')
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigMetadataTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigMetadataTest.php
new file mode 100644
index 0000000..ceec0fc
--- /dev/null
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigMetadataTest.php
@@ -0,0 +1,122 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\config\Tests\ConfigMetadataTest.
+ */
+
+namespace Drupal\config\Tests;
+
+use Drupal\Core\Config\TypedConfig;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests Metadata for configuration objects.
+ */
+class ConfigMetadataTest extends WebTestBase {
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('locale', 'image');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Configuration metadata',
+      'description' => 'Tests Metadata for configuration objects.',
+      'group' => 'Configuration',
+    );
+  }
+
+  /**
+   * Tests the basic metadata retrieval layer.
+   */
+  function testBasicMetadata() {
+    // Simple case, straight metadata.
+    $metadata = config_metadata('system.maintenance');
+    $expected = array();
+    $expected['enabled']['.definition'] = array(
+      'label' => 'Put site into maintenance mode',
+      'type' => 'boolean'
+    );
+    $expected['message']['.definition'] = array(
+      'label' =>  'Message to display when in maintenance mode',
+      'type' => 'text',
+    );
+    $this->assertEqual($metadata, $expected, 'Retrieved the right metadata for system.maintenance');
+    // More complex case, fallback to parent name.
+    $metadata = config_metadata('image.style.large');
+    $expected = array();
+    $expected['name']['.definition'] = array(
+      'label' => 'Machine name',
+      'type' => 'string',
+    );
+    $expected['label']['.definition'] = array(
+      'label' => 'Label',
+      'type' => 'text'
+    );
+    $expected['effects']['.definition'] = array(
+      'label' =>  'Style effects',
+      'list' => '1',
+      'list settings' => array(
+        'elements name' => 'image.style.effects.%',
+        'elements key' => 'name'
+      ),
+    );
+    $this->assertEqual($metadata, $expected, 'Retrieved the right metadata for image.style.large');
+  }
+
+  /**
+   * Tests metadata applied to configuration objects.
+   */
+  function testConfigMetadata() {
+    // Try some simple properties.
+    $meta = config_wrapper('system.site');
+    $property = $meta->get('name');
+    $this->assertTrue(is_a($property, 'Drupal\Core\TypedData\Type\String'), 'Got the right wrapper fo the site name property.');
+    $this->assertEqual($property->getType(), 'text', 'Got the right string type for site name data.');
+    $this->assertEqual($property->getValue(), 'Drupal', 'Got the right string value for site name data.');
+
+    $property = $meta->get('page.front');
+    $this->assertTrue(is_a($property, 'Drupal\Core\TypedData\Type\String'), 'Got the right wrapper fo the page.front property.');
+    $this->assertEqual($property->getType(), 'string', 'Got the right type for page.front data (undefined).');
+    $this->assertEqual($property->getValue(), 'user', 'Got the right value for page.front data.');
+
+    // Check nested array of properties.
+    $list = $meta->get('page');
+    $this->assertEqual(count($list), 3, 'Got a list with the right number of properties for site page data');
+    $this->assertTrue(isset($list['front']) && isset($list['403']) && isset($list['404']), 'Got a list with the right properties for site page data.');
+    $this->assertEqual($list['front']->getValue(), 'user', 'Got the right value for page.front data from the list.');
+    // And test some ComplexDataInterface methods.
+    $properties = $list->getProperties();
+    $this->assertTrue(count($properties) == 3 && $properties['front'] == $list['front'], 'Got the right properties for site page.');
+    $values = $list->getPropertyValues();
+    $this->assertTrue(count($values) == 3 && $values['front'] == 'user', 'Got the right property values for site page.');
+
+    // Now let's try something more complex, with nested objects.
+    $wrapper = config_wrapper('image.style.large');
+    $effects = $wrapper->get('effects');
+    // The function is_array() doesn't work with ArrayAccess, so we use count().
+    $this->assertTrue(count($effects) == 1, 'Got an array with effects for image.style.large data');
+    $ieid = key($effects->getValue());
+    $effect = $wrapper->get('effects.' . $ieid);
+    $this->assertTrue(count($effect['data']) && $effect['name']->getValue() == 'image_scale', 'Got data for the image scale effect from metadata.');
+    $this->assertEqual($effect['data']['width']->getType(), 'integer', 'Got the right type for the scale effect width.');
+    $this->assertEqual($effect['data']['width']->getValue(), 480, 'Got the right value for the scale effect width.' );
+    // Try config key and getting nested properties.
+    $key = 'effects.' . $ieid . '.data';
+    $this->assertEqual($effect['data']->getKey(), $key, 'Got the right configuration key for a nested image styple effect');
+    $object = $wrapper->get($key);
+    $this->assertEqual(get_class($effect['data']), get_class($object), 'Got the same object class using different keys with dot notation.');
+    $this->assertEqual($effect['data']->getValue(), $object->getValue(), 'Got the same object values using different keys with dot notation.');
+
+    // Finally update some object using a configuration wrapper.
+    $new_slogan = 'Site slogan for testing configuration metadata';
+    $wrapper = config_wrapper('system.site');
+    $wrapper->set('slogan', $new_slogan);
+    $site_slogan = $wrapper->get('slogan');
+    $this->assertEqual($site_slogan->getValue(), $new_slogan, 'Successfully updated the contained configuration data');
+  }
+
+}
diff --git a/core/modules/contact/meta/contact.category.%.yml b/core/modules/contact/meta/contact.category.%.yml
new file mode 100644
index 0000000..6a772e3
--- /dev/null
+++ b/core/modules/contact/meta/contact.category.%.yml
@@ -0,0 +1,23 @@
+.definition:
+  settings:
+    label: label
+id:
+  .definition:
+    type: string
+    label: 'Id'
+label:
+  .definition:
+    type: text
+    label: 'Label'
+recipients:
+  .definition:
+    list: '1'
+    label: 'Recipients'
+reply:
+  .definition:
+    type: text
+    label: 'Reply'
+weight:
+  .definition:
+    type: integer
+    label: 'Weight'
diff --git a/core/modules/image/meta/image.style.%.yml b/core/modules/image/meta/image.style.%.yml
new file mode 100644
index 0000000..6975590
--- /dev/null
+++ b/core/modules/image/meta/image.style.%.yml
@@ -0,0 +1,15 @@
+name:
+  .definition:
+    label: 'Machine name'
+    type: string
+label:
+  .definition:
+    label: 'Label'
+    type: text
+effects:
+  .definition:
+    label: 'Style effects'
+    list: '1'
+    list settings:
+      elements name: 'image.style.effects.%'
+      elements key: 'name'
diff --git a/core/modules/image/meta/image.style.effects.%.yml b/core/modules/image/meta/image.style.effects.%.yml
new file mode 100644
index 0000000..4ae6bc9
--- /dev/null
+++ b/core/modules/image/meta/image.style.effects.%.yml
@@ -0,0 +1,16 @@
+.definition:
+  label: 'Image style effect'
+name:
+  .definition:
+    label: 'Style name'
+    type: string
+data:
+  .definition:
+    label: 'Data'
+weight:
+  .definition:
+    label: 'Weight'
+    type: integer
+ieid:
+  .definition:
+    label: 'IEID'
diff --git a/core/modules/image/meta/image.style.effects.image_scale.yml b/core/modules/image/meta/image.style.effects.image_scale.yml
new file mode 100644
index 0000000..6f76c34
--- /dev/null
+++ b/core/modules/image/meta/image.style.effects.image_scale.yml
@@ -0,0 +1,28 @@
+.definition:
+  label: 'Image scale'
+name:
+  .definition:
+    label: 'Machine name'
+    type: string
+weight:
+  .definition:
+    label: 'Weight'
+    type: integer
+ieid:
+  .definition:
+    label: 'IEID'
+data:
+  .definition:
+    label: 'Data'
+  width:
+    .definition:
+      label: 'Width'
+      type: 'integer'
+  height:
+    .definition:
+      label: 'Height'
+      type: 'integer'
+  upscale:
+    .definition:
+      label: 'Upscale'
+      type: 'boolean'
diff --git a/core/modules/locale/lib/Drupal/locale/LocaleConfigSubscriber.php b/core/modules/locale/lib/Drupal/locale/LocaleConfigSubscriber.php
index 3d2fd4a..fb09c0b 100644
--- a/core/modules/locale/lib/Drupal/locale/LocaleConfigSubscriber.php
+++ b/core/modules/locale/lib/Drupal/locale/LocaleConfigSubscriber.php
@@ -18,6 +18,7 @@
  * $config is always a DrupalConfig object.
  */
 class LocaleConfigSubscriber implements EventSubscriberInterface {
+
   /**
    * Override configuration values with localized data.
    *
diff --git a/core/modules/locale/lib/Drupal/locale/LocaleTypedConfig.php b/core/modules/locale/lib/Drupal/locale/LocaleTypedConfig.php
new file mode 100644
index 0000000..0c62c56
--- /dev/null
+++ b/core/modules/locale/lib/Drupal/locale/LocaleTypedConfig.php
@@ -0,0 +1,257 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\locale\LocaleTypedConfig.
+ */
+
+namespace Drupal\locale;
+
+use Drupal\Core\Language\Language;
+use Drupal\Core\Config\Metadata\TypedConfig;
+use Drupal\Core\Config\Metadata\ElementInterface;
+use Drupal\Core\TypedData\TranslatableInterface;
+
+/**
+ * Defines the locale configuration wrapper object.
+ *
+ * This is an extension of the base TypedConfig class that provides
+ * translations using the localization system.
+ */
+class LocaleTypedConfig extends TypedConfig implements TranslatableInterface {
+
+  /**
+   * The language code for which this is a translation.
+   *
+   * @var string
+   */
+  protected $translationLangcode;
+
+  /**
+   * Configuration wrapper used as translation source.
+   *
+   * @var \Drupal\locale\LocaleTypedConfig
+   */
+  protected $translationSource;
+
+  /**
+   * String storage object that will be used for translations.
+   *
+   * @var \Drupal\locale\StringStorageInterface $localeStorage
+   */
+  protected $localeStorage;
+
+  /**
+   * Temporary translations cache.
+   *
+   * @var array
+   */
+  protected $translations;
+
+  /**
+   * Constructs a configuration wrapper object.
+   *
+   * @param string $name
+   *   The configuration object name.
+   * @param array $data
+   *   The configuration data.
+   * @param \Drupal\locale\StringStorageInterface $localeStorage;
+   *   (optional) Locale string storage object that will be used for translations.
+   */
+  public function __construct($name, $data, StringStorageInterface $localeStorage = NULL) {
+    parent::__construct($name, $data);
+    $this->localeStorage = $localeStorage;
+  }
+
+  /**
+   * Sets translation parameters.
+   *
+   * @param string $langcode
+   *   The language code for the translation.
+   * @param \Drupal\locale\LocaleTypedConfig $source
+   *   Configuration wrapper used as translation source.
+   */
+  public function setTranslation($langcode, LocaleTypedConfig $source) {
+    $this->translationLangcode = $langcode;
+    $this->translationSource = $source;
+    return $this;
+  }
+
+  /**
+   * Checks whether we can translate these languages.
+   *
+   * @param string $from_langcode
+   *   Source language code.
+   * @param string $to_langcode
+   *   Destination language code.
+   *
+   * @return boolean
+   *   TRUE if this translator supports translations for these languages.
+   */
+  protected function canTranslate($from_langcode, $to_langcode) {
+    if ($from_langcode == 'en') {
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\TranslatableInterface::getTranslationLanguages().
+   */
+  public function getTranslationLanguages($include_default = TRUE) {
+    $languages = locale_translatable_language_list();
+    $default = $this->language();
+    if ($include_default) {
+      $languages[$default->langcode] = $default;
+    }
+    else {
+      unset($languages[$default->langcode]);
+    }
+    return $languages;
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\TranslatableInterface::getTranslation().
+   */
+  public function getTranslation($langcode, $strict = TRUE) {
+    if (isset($this->translationSource)) {
+      return $this->translationSource->getTranslation($langcode, $strict);
+    }
+    else {
+      $options = array(
+        'source' => $this->language()->langcode,
+        'target' => $langcode,
+        'strict' => $strict,
+      );
+      $data = $this->getTranslatedData($this->get(), $options);
+      $translation = new LocaleTypedConfig($this->name, $data);
+      $translation->setTranslation($langcode, $strict, $this);
+      return $translation;
+    }
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\TranslatableInterface::language().
+   */
+  public function language() {
+    if (isset($this->translationLangcode)) {
+      return new Language(array('langcode' => $this->translationLangcode));
+    }
+    else {
+      return $this->getLanguage();
+    }
+  }
+
+  /**
+   * Get translated configuration data.
+   *
+   * @param \Traversable $elements
+   *   Configuration elements.
+   * @param array $options
+   *   Array with options that will depend on the translator used.
+   *
+   * @return array
+   *   Configuration data translated to the requested language.
+   */
+  protected function getTranslatedData($elements, $options) {
+    $strict = !empty($options['strict']);
+    $translation = array();
+    foreach ($elements as $key => $element) {
+      $value = NULL;
+      if ($element instanceof ElementInterface) {
+        $value = $this->getTranslatedData($element, $options);
+      }
+      elseif ($this->translateElement($element, $options)) {
+        $value = $element->getValue();
+      }
+      elseif (!$strict) {
+        $value = $element->getValue();
+      }
+      if ($value || !$strict) {
+        $translation[$key] = $value;
+      }
+    }
+    return $translation;
+  }
+
+  /**
+   * Translates element if it fits our translation criteria.
+   *
+   * For an element to be translatable by locale module it needs to be of type
+   * 'text'. Translatable elements may define the string context by using the
+   * 'locale context' key under the element definition's 'constraints'.
+   *
+   * @param \Drupal\Core\TypedData\TypedDataInterface $element
+   *   Configuration element.
+   * @param array $options
+   *   Array with translation options that are dependent on the translator.
+   *
+   * @return bool
+   *   Whether the element fits the translation criteria.
+   */
+  public function translateElement($element, $options) {
+    if ($this->canTranslate($options['source'], $options['target'])) {
+      $definition = $element->getDefinition();
+      $value = $element->getValue();
+      if ($value && is_string($value) && $element->getType() == 'text') {
+        $context = isset($definition['constraints']['locale context']) ? $definition['constraints']['locale context'] : '';
+        if ($translation = $this->translateString($options['target'], $value, $context)) {
+          $element->setValue($translation);
+          return TRUE;
+        }
+      }
+    }
+    // The element doesn't have a translation, if strict mode we drop it.
+    return empty($options['strict']);
+  }
+
+  /**
+   * Translates string using the localization system.
+   *
+   * So far we only know how to translate strings from English so we check
+   * whether the source data is English.
+   * Unlike regular t() translations, strings will be added to the source
+   * tables only if this is marked as default data.
+   *
+   * @param $langcode
+   *   Language code to translate to.
+   * @param string $source
+   *   The source string.
+   * @param string $context
+   *   The string context.
+   *
+   * @return string|bool
+   *   Translated string if there is a translation, FALSE if not.
+   */
+  protected function translateString($langcode, $source, $context) {
+    if ($source) {
+      // Preload all translations for this configuration name and language.
+      if (!isset($this->translations[$langcode])) {
+        $this->translations[$langcode] = array();
+        foreach ($this->localeStorage->getTranslations(array('language' => $langcode, 'type' => 'configuration', 'name' => $this->name)) as $string){
+          $this->translations[$langcode][$string->context][$string->source] = $string;
+        }
+      }
+      if (!isset($this->translations[$langcode][$context][$source])) {
+        if ($translation = $this->localeStorage->findTranslation(array('source' => $source, 'context' => $context, 'language' => $langcode))) {
+          // The translation was there but the location was missing.
+          // Convert to SourceString because it may not have translation.
+          $string = $this->localeStorage->createString((array) $translation)
+            ->addLocation('configuration', $this->name)
+            ->save();
+        }
+        else {
+          // Add missing source string with the right location.
+          $translation = $this->localeStorage
+            ->createString(array('source' => $source, 'context' => $context))
+            ->addLocation('configuration', $this->name)
+            ->save();
+        }
+        $this->translations[$langcode][$context][$source] = $translation;
+      }
+      $translation = $this->translations[$langcode][$context][$source];
+      return $translation->isTranslation() ? $translation->getString() : FALSE;
+    }
+    return FALSE;
+  }
+}
diff --git a/core/modules/locale/lib/Drupal/locale/StringBase.php b/core/modules/locale/lib/Drupal/locale/StringBase.php
index e2d9bcc..2d86cb8 100644
--- a/core/modules/locale/lib/Drupal/locale/StringBase.php
+++ b/core/modules/locale/lib/Drupal/locale/StringBase.php
@@ -52,7 +52,7 @@
   /**
    * The locale storage this string comes from or is to be saved to.
    *
-   * @var Drupal\locale\StringStorageInterface
+   * @var \Drupal\locale\StringStorageInterface
    */
   protected $storage;
 
@@ -189,7 +189,7 @@ public function save() {
     }
     else {
       throw new StringStorageException(format_string('The string cannot be saved because its not bound to a storage: @string', array(
-        '@string' => $string->getString()
+        '@string' => $this->getString()
       )));
     }
     return $this;
diff --git a/core/modules/locale/lib/Drupal/locale/StringStorageInterface.php b/core/modules/locale/lib/Drupal/locale/StringStorageInterface.php
index d02774f..0fb26ad 100644
--- a/core/modules/locale/lib/Drupal/locale/StringStorageInterface.php
+++ b/core/modules/locale/lib/Drupal/locale/StringStorageInterface.php
@@ -79,7 +79,7 @@ public function getLocations(array $conditions = array());
    *   (optional) Array with conditions that will be used to filter the strings
    *   returned and may include all of the conditions defined by getStrings().
    *
-   * @return Drupal\locale\SourceString|null
+   * @return \Drupal\locale\SourceString|null
    *   Minimal TranslationString object if found, NULL otherwise.
    */
   public function findString(array $conditions);
@@ -95,7 +95,7 @@ public function findString(array $conditions);
    *   (optional) Array with conditions that will be used to filter the strings
    *   returned and may include all of the conditions defined by getStrings().
    *
-   * @return Drupal\locale\TranslationString|null
+   * @return \Drupal\locale\TranslationString|null
    *   Minimal TranslationString object if found, NULL otherwise.
    */
   public function findTranslation(array $conditions);
@@ -103,13 +103,13 @@ public function findTranslation(array $conditions);
   /**
    * Save string object to storage.
    *
-   * @param Drupal\locale\StringInterface $string
+   * @param \Drupal\locale\StringInterface $string
    *   The string object.
    *
-   * @return Drupal\locale\StringStorageInterface
+   * @return \Drupal\locale\StringStorageInterface
    *   The called object.
    *
-   * @throws Drupal\locale\StringStorageException
+   * @throws \Drupal\locale\StringStorageException
    *   In case of failures, an exception is thrown.
    */
   public function save($string);
@@ -117,13 +117,13 @@ public function save($string);
   /**
    * Delete string from storage.
    *
-   * @param Drupal\locale\StringInterface $string
+   * @param \Drupal\locale\StringInterface $string
    *   The string object.
    *
-   * @return Drupal\locale\StringStorageInterface
+   * @return \Drupal\locale\StringStorageInterface
    *   The called object.
    *
-   * @throws Drupal\locale\StringStorageException
+   * @throws \Drupal\locale\StringStorageException
    *   In case of failures, an exception is thrown.
    */
   public function delete($string);
@@ -166,7 +166,7 @@ public function countTranslations();
    * @param array $values
    *   (optional) Array with initial values. Defaults to empty array.
    *
-   * @return Drupal\locale\SourceString
+   * @return \Drupal\locale\SourceString
    *   New source string object.
    */
   public function createString($values = array());
@@ -177,7 +177,7 @@ public function createString($values = array());
    * @param array $values
    *   (optional) Array with initial values. Defaults to empty array.
    *
-   * @return Drupal\locale\TranslationString
+   * @return \Drupal\locale\TranslationString
    *   New string translation object.
    */
   public function createTranslation($values = array());
diff --git a/core/modules/locale/lib/Drupal/locale/Tests/LocaleConfigTranslationTest.php b/core/modules/locale/lib/Drupal/locale/Tests/LocaleConfigTranslationTest.php
new file mode 100644
index 0000000..23b0ff6
--- /dev/null
+++ b/core/modules/locale/lib/Drupal/locale/Tests/LocaleConfigTranslationTest.php
@@ -0,0 +1,152 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\locale\Tests\LocaleConfigTranslationTest.
+ */
+
+namespace Drupal\locale\Tests;
+
+use Drupal\Core\Language\Language;
+use Drupal\simpletest\WebTestBase;
+use Drupal\locale\LocaleTypedConfig;
+
+/**
+ * Tests Metadata for configuration objects.
+ */
+class LocaleConfigTranslationTest extends WebTestBase {
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('locale');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Configuration translation',
+      'description' => 'Tests translation of configuration strings.',
+      'group' => 'Locale',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+    // Add a default locale storage for all these tests.
+    $this->storage = locale_storage();
+  }
+
+  /**
+   * Tests basic configuration translation.
+   */
+  function testConfigTranslation() {
+    // Add custom language
+    $langcode = 'xx';
+    $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'translate interface', 'administer modules'));
+    $this->drupalLogin($admin_user);
+    $name = $this->randomName(16);
+    $edit = array(
+      'predefined_langcode' => 'custom',
+      'langcode' => $langcode,
+      'name' => $name,
+      'direction' => '0',
+    );
+    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
+    $language = new Language(array('langcode' => $langcode));
+    // Set path prefix.
+    $edit = array( "prefix[$langcode]" => $langcode );
+    $this->drupalPost('admin/config/regional/language/detection/url', $edit, t('Save configuration'));
+
+    // Check site name string exists and create translation for it.
+    $string = $this->storage->findString(array('source' => 'Drupal', 'context' => '', 'type' => 'configuration'));
+    $this->assertTrue($string, 'Configuration strings have been created upon installation.');
+
+    // Translate using the UI so configuration is refreshed.
+    $site_name = $this->randomName(20);
+    $search = array(
+      'string' => $string->source,
+      'langcode' => $langcode,
+      'translation' => 'all',
+    );
+    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $textarea = current($this->xpath('//textarea'));
+    $lid = (string) $textarea[0]['name'];
+    $edit = array(
+      $lid => $site_name,
+    );
+    $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Save translations'));
+
+    $data = config('system.site')->get();
+    $wrapper = new LocaleTypedConfig('system.site', $data, $this->storage);
+
+    // Get strict translation and check we've got only the site name.
+    $translation = $wrapper->getTranslation($langcode, TRUE);
+    $properties = $translation->get()->getProperties();
+    $this->assertEqual(count($properties), 1, 'Got the right number of properties with strict translation');
+    $this->assertEqual($properties['name']->getValue(), $site_name, 'Got the right translation for site name with strict translation');
+    // Get non strict translation and check we've got all properties.
+    $translation = $wrapper->getTranslation($langcode, FALSE);
+    $properties = $translation->get()->getProperties();
+    $this->assertTrue(count($properties) == 4 && count($wrapper->get('page')) == 3, 'Got the right number of properties with non strict translation');
+    $this->assertEqual($properties['name']->getValue(), $site_name, 'Got the right translation for site name with non strict translation');
+
+    // Check the translated site name is displayed.
+    $this->drupalGet($langcode);
+    $this->assertText($site_name, 'The translated site name is displayed after translations refreshed.');
+
+    // Assert strings from image module config are not available.
+    $string = $this->storage->findString(array('source' => 'Medium (220x220)', 'context' => '', 'type' => 'configuration'));
+    $this->assertFalse($string, 'Configuration strings have been created upon installation.');
+
+    // Enable the image module
+    $this->drupalPost('admin/modules', array('modules[Core][image][enable]' => "1"), t('Save configuration'));
+    $this->resetAll();
+
+    $string = $this->storage->findString(array('source' => 'Medium (220x220)', 'context' => '', 'type' => 'configuration'));
+    $this->assertTrue($string, 'Configuration strings have been created upon installation.');
+    $locations = $string->getLocations();
+    $this->assertTrue(isset($locations['configuration']) && isset($locations['configuration']['image.style.medium']), 'Configuration string has been created with the right location');
+    // Check the string is unique and has no translation yet.
+    $translations = $this->storage->getTranslations(array('language' => $langcode, 'type' => 'configuration', 'name' => 'image.style.medium'));
+    $translation = reset($translations);
+    $this->assertTrue(count($translations) == 1 && $translation->source == $string->source && empty($translation->translation), 'Got only one string for image configuration and has no translation.');
+
+    // Translate using the UI so configuration is refreshed.
+    $image_style_label = $this->randomName(20);
+    $search = array(
+      'string' => $string->source,
+      'langcode' => $langcode,
+      'translation' => 'all',
+    );
+    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
+    $textarea = current($this->xpath('//textarea'));
+    $lid = (string) $textarea[0]['name'];
+    $edit = array(
+      $lid => $image_style_label,
+    );
+    $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Save translations'));
+    // Check the right single translation has been created.
+    $translations = $this->storage->getTranslations(array('language' => $langcode, 'type' => 'configuration', 'name' => 'image.style.medium'));
+    $translation = reset($translations);
+    $this->assertTrue(count($translations) == 1 && $translation->source == $string->source && $translation->translation == $image_style_label, 'Got only one translation for image configuration.');
+
+    // This really should be testing entity_load_multiple.
+    $data = config('image.style.medium')->get();
+    $wrapper = new LocaleTypedConfig('image.style.medium', $data, $this->storage);
+    $translation = $wrapper->getTranslation($langcode, TRUE);
+    $property = $translation->get('label');
+    $this->assertEqual($property->getValue(), $image_style_label, 'Got the right translation for image style name with strict translation');
+
+    // Quick test to ensure translation file exists.
+    $this->assertEqual(config('locale.config.xx.image.style.medium')->get('label'), $image_style_label);
+
+    // Disable and uninstall the module.
+    $this->drupalPost('admin/modules', array('modules[Core][image][enable]' => FALSE), t('Save configuration'));
+    $this->drupalPost('admin/modules/uninstall', array('uninstall[image]' => "image"), t('Uninstall'));
+    $this->drupalPost(NULL, array(), t('Uninstall'));
+
+    // Ensure that the translated configuration has been removed.
+    $this->assertFalse(config('locale.config.xx.image.style.medium')->get('label'), 'Translated configuration for image module removed.');
+  }
+
+}
diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc
index 0be2aec..2043e86 100644
--- a/core/modules/locale/locale.bulk.inc
+++ b/core/modules/locale/locale.bulk.inc
@@ -6,10 +6,13 @@
  */
 
 use Drupal\Component\Gettext\PoStreamWriter;
+use Drupal\Component\Utility\NestedArray;
+use Drupal\locale\LocaleTypedConfig;
 use Drupal\locale\Gettext;
 use Drupal\locale\PoDatabaseReader;
 use Drupal\Core\Language\Language;
-
+use Drupal\Core\Config\InstallStorage;
+use Drupal\Core\Config\StorageException;
 
 /**
  * Form constructor for the translation import screen.
@@ -123,6 +126,7 @@ function locale_translate_import_form_submit($form, &$form_state) {
       'langcode' => $form_state['values']['langcode'],
       'overwrite_options' => $form_state['values']['overwrite_options'],
       'customized' => $form_state['values']['customized'] ? LOCALE_CUSTOMIZED : LOCALE_NOT_CUSTOMIZED,
+      'refresh_configuration' => TRUE,
     );
     $batch = locale_translate_batch_build(array($file->uri => $file), $options);
     batch_set($batch);
@@ -290,6 +294,10 @@ function locale_translate_add_language_set_batch($options) {
   if ($batch = locale_translate_batch_import_files($options)) {
     batch_set($batch);
   }
+  // Create or update all configuration translations for this language.
+  if ($batch = locale_config_batch_update_components($options)) {
+    batch_set($batch);
+  }
 }
 
 /**
@@ -389,8 +397,12 @@ function locale_translate_get_interface_translation_files($langcode = NULL) {
  *     are customized translations or come from a community source. Use
  *     LOCALE_CUSTOMIZED or LOCALE_NOT_CUSTOMIZED. Optional, defaults to
  *     LOCALE_NOT_CUSTOMIZED.
+ *   - 'refresh_configuration': Whether or not to refresh Configuration strings
+ *     after the import. Optional, defaults to FALSE.
  *   - 'finish_feedback': Whether or not to give feedback to the user when the
  *     batch is finished. Optional, defaults to TRUE.
+ *   - 'components': Array of arrays of components to refresh the configuration
+ *     indexed by type ('module' or 'theme'). Optional, defaults to none.
  *
  * @return
  *   A batch structure or FALSE if $files was empty.
@@ -400,6 +412,7 @@ function locale_translate_batch_build($files, $options) {
     'overwrite_options' => array(),
     'customized' => LOCALE_NOT_CUSTOMIZED,
     'finish_feedback' => TRUE,
+    'refresh_configuration' => FALSE,
   );
   $t = get_t();
   if (count($files)) {
@@ -408,6 +421,8 @@ function locale_translate_batch_build($files, $options) {
       // We call locale_translate_batch_import for every batch operation.
       $operations[] = array('locale_translate_batch_import', array($file->uri, $options));
     }
+    // Add a final step to refresh JavaScript and configuration strings.
+    $operations[] = array('locale_translate_batch_refresh', array($options));
     $batch = array(
       'operations'    => $operations,
       'title'         => $t('Importing interface translations'),
@@ -517,12 +532,57 @@ function locale_translate_batch_import($filepath, $options, &$context) {
 }
 
 /**
+ * Refresh translations after importing strings.
+ */
+function locale_translate_batch_refresh($options, &$context) {
+  if (!isset($context['sandbox']['refresh'])) {
+    $strings = $langcodes = array();
+    if (isset($context['results']['stats'])) {
+      // Get list of unique string identifiers and language codes updated.
+      $langcodes = array_unique(array_values($context['results']['languages']));
+      foreach ($context['results']['stats'] as $filepath => $report) {
+        $strings = array_merge($strings, $report['strings']);
+      }
+    }
+    if ($strings) {
+      $context['message'] = t('Updating translations for JavaScript and Configuration strings.');
+      $strings = array_unique($strings);
+      // Clear cache and force refresh of JavaScript translations.
+      _locale_refresh_translations($langcodes, $strings);
+      // Check whether we need to refresh configuration objects.
+      if ($options['refresh_configuration'] && $names = _locale_config_string_names($strings)) {
+        $context['sandbox']['refresh']['names'] = $names;
+        $context['sandbox']['refresh']['languages'] = $langcodes;
+        $context['sandbox']['refresh']['count'] = count($names);
+        $context['results']['stats']['configuration'] = 0;
+      }
+    }
+    if (isset($context['sandbox']['refresh'])) {
+      // We will update configuration on next steps.
+      $context['finished'] = 1 / $context['sandbox']['refresh']['count'];
+    }
+    else {
+      $context['finished'] = 1;
+    }
+  }
+  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']);
+    $context['results']['stats']['configuration'] += $count;
+    // Not perfect but will give some idea of progress.
+    $context['finished'] = 1 - count($context['sandbox']['refresh']['names']) / $context['sandbox']['refresh']['count'];
+  }
+  else {
+    $context['finished'] = 1;
+  }
+}
+
+/**
  * Finished callback of system page locale import batch.
  */
 function locale_translate_batch_finished($success, $results) {
   if ($success) {
-    $additions = $updates = $deletes = $skips = 0;
-    $strings = $langcodes = array();
+    $additions = $updates = $deletes = $skips = $configuration = 0;
     drupal_set_message(format_plural(count($results['files']), 'One translation file imported.', '@count translation files imported.'));
     $skipped_files = array();
     // If there are no results and/or no stats (eg. coping with an empty .po
@@ -536,11 +596,7 @@ function locale_translate_batch_finished($success, $results) {
         if ($report['skips'] > 0) {
           $skipped_files[] = $filepath;
         }
-        $strings = array_merge($strings, $report['strings']);
       }
-      // Get list of unique string identifiers and language codes updated.
-      $strings = array_unique($strings);
-      $langcodes = array_unique(array_values($results['languages']));
     }
     drupal_set_message(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes)));
     watchdog('locale', 'The translation was succesfully imported. %number new strings added, %update updated and %delete removed.', array('%number' => $additions, '%update' => $updates, '%delete' => $deletes));
@@ -554,10 +610,9 @@ function locale_translate_batch_finished($success, $results) {
       drupal_set_message($skip_message, 'error');
       watchdog('locale', '@count disallowed HTML string(s) in files: @files.', array('@count' => $skips, '@files' => implode(',', $skipped_files)), WATCHDOG_WARNING);
     }
-
-    if ($strings) {
-      // Clear cache and force refresh of JavaScript translations.
-      _locale_refresh_translations($langcodes, $strings);
+    // Merge feedback about configuration updates too.
+    if (isset($results['stats']['configuration'])) {
+      locale_config_batch_finished($success, $results);
     }
   }
 }
@@ -627,3 +682,232 @@ function locale_translate_delete_translation_files($langcode) {
   }
   return $return;
 }
+
+/**
+ * Build a locale batch to refresh configuration.
+ *
+ * @param array $options
+ *   An array with options that can have the following elements:
+ *   - 'langcode': The language code. Optional, defaults to NULL, which means
+ *     that all translatable languages will be refreshed.
+ *   - 'finish_feedback': Whether or not to give feedback to the user when the
+ *     batch is finished. Optional, defaults to TRUE.
+ * @param array $components
+ *   (optional) Array of component lists indexed by type. If not present or it
+ *   is an empty array, it will update all components.
+ */
+function locale_config_batch_update_components($options, $components = array()) {
+  $langcodes = isset($options['langcode']) ? array($options['langcode']) : array_keys(locale_translatable_language_list());
+  if ($langcodes && $names = _locale_config_component_names($components)) {
+    return locale_config_batch_build($names, $langcodes, $options);
+  }
+}
+
+/**
+ * Creates a locale batch to refresh specific configuration.
+ *
+ * @param array $names
+ *   List of configuration object names to update.
+ * @param array $langcodes
+ *   List of language codes to refresh.
+ * @param array $options
+ *   An array with options that can have the following elements:
+ *   - 'finish_feedback': Whether or not to give feedback to the user when the
+ *     batch is finished. Optional, defaults to TRUE.
+ */
+function locale_config_batch_build($names, $langcodes, $options = array()) {
+  $options += array('finish_feedback' => TRUE);
+  $t = get_t();
+  foreach ($names as $name) {
+    $operations[] = array('locale_config_batch_refresh_name', array($name, $langcodes));
+  }
+  $batch = array(
+      'operations'    => $operations,
+      'title'         => $t('Updating configuration translations'),
+      'init_message'  => $t('Starting update'),
+      'error_message' => $t('Error updating configuration translations'),
+      'file'          => drupal_get_path('module', 'locale') . '/locale.bulk.inc',
+  );
+  if (!empty($options['finish_feedback'])) {
+    $batch['completed'] = 'locale_config_batch_finished';
+  }
+  return $batch;
+}
+
+/**
+ * Perform configuration translation refresh as a batch step.
+ *
+ * @param $context
+ *   Contains a list of files imported.
+ */
+function locale_config_batch_refresh_name($name, $langcodes, &$context) {
+  if (!isset($context['result']['stats']['configuration'])) {
+    $context['result']['stats']['configuration'] = 0;
+  }
+  $context['result']['stats']['configuration'] += locale_config_update_multiple(array($name), $langcodes);
+  $context['result']['names'][] = $name;
+  $context['result']['langcodes'] = $langcodes;
+  $context['finished'] = 1;
+}
+
+/**
+ * Finished callback of system page locale import batch.
+ */
+function locale_config_batch_finished($success, $results) {
+  if ($success) {
+    $configuration = isset($results['stats']['configuration']) ? $results['stats']['configuration'] : 0;
+    if ($configuration) {
+      drupal_set_message(t('The configuration was successfully updated. There are %number configuration objects updated.', array('%number' => $configuration)));
+      watchdog('locale', 'The configuration was successfully updated. %number configuration objects updated.', array('%number' => $configuration));
+    }
+    else {
+      drupal_set_message(t('No configuration objects have been updated.'));
+      watchdog('locale', 'No configuration objects have been updated.', array(), WATCHDOG_WARNING);
+    }
+  }
+}
+
+/**
+ * Delete configuration for language.
+ *
+ * @param $langcode
+ *   Language code to delete.
+ */
+function locale_config_delete_language($langcode) {
+  $locale_name = 'locale.config.' . $langcode;
+  $storage = drupal_container()->get('config.storage');
+  foreach ($storage->listAll($locale_name) as $name) {
+    $storage->delete($name);
+  }
+}
+
+/**
+ * Update 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($names, $langcodes = array()) {
+  $default_storage = new InstallStorage();
+  $config_storage = drupal_container()->get('config.storage');
+  $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list());
+  $count = 0;
+  foreach ($names as $name) {
+    $wrapper = NULL;
+    try {
+      if ($data = $default_storage->read($name)) {
+        $updated = $config_storage->read($name);
+        if ($data = _locale_config_data_compare($data, $updated)) {
+          $wrapper = new LocaleTypedConfig($name, $data, locale_storage());
+        }
+      }
+      foreach ($langcodes as $langcode) {
+        $locale_name = 'locale.config.' . $langcode . '.' . $name;
+        $translation = $wrapper ? $wrapper->getTranslation($langcode, TRUE)->getData() : NULL;
+        if ($translation) {
+          $config_storage->write($locale_name, $translation);
+          $count++;
+        }
+        else {
+          $config_storage->delete($locale_name);
+        }
+      }
+    }
+    catch (StorageException $e) {
+      // The configuration name does not exist, just continue.
+    }
+  }
+  return $count;
+}
+
+/**
+ * Compare default configuration with updated data.
+ *
+ * @param array $default
+ *   Default configuration data.
+ * @param array $updated
+ *   Current configuration data.
+ * @return array
+ *   The elements of default configuration that haven't changed.
+ */
+function _locale_config_data_compare($default, $updated) {
+  // Speed up comparison, specially for install operations.
+  if ($default === $updated) {
+    return $default;
+  }
+  $result = array();
+  foreach ($default as $key => $value) {
+    if (isset($updated[$key])) {
+      if (is_array($value)) {
+        $result[$key] = _locale_config_data_compare($value, $updated[$key]);
+      }
+      elseif ($value === $updated[$key]) {
+        $result[$key] = $value;
+      }
+    }
+  }
+  return $result;
+}
+
+/**
+ * Delete configuration for uninstalled components.
+ */
+function locale_config_delete_components($components) {
+  $names = _locale_config_component_names($components);
+  $langcodes = array_keys(locale_translatable_language_list());
+  if ($names && $langcodes) {
+    $storage = drupal_container()->get('config.storage');
+    foreach ($names as $name) {
+      foreach ($langcodes as $langcode) {
+        $storage->delete('locale.config.' . $langcode . '.' . $name);
+      }
+    }
+  }
+}
+
+/**
+ * Get configuration names associated with components.
+ *
+ * @param array $components
+ *   Array with string identifiers.
+ *
+ * @return array
+ *   Array of configuration object names.
+ */
+function _locale_config_component_names($components) {
+  $storage = new InstallStorage();
+  if ($components) {
+    $names = array();
+    foreach ($components as $type => $list) {
+      // InstallStorage::getComponentNames returns a list of folders keyed by
+      // config name.
+      $names = array_merge($names, array_keys($storage->getComponentNames($type, $list)));
+    }
+    return $names;
+  }
+  else {
+    return $storage->listAll();
+  }
+}
+
+/**
+ * Get configuration names associated with strings.
+ *
+ * @param array $lids
+ *   Array with string identifiers.
+ *
+ * @return array
+ *   Array of configuration object names.
+ */
+function _locale_config_string_names($lids) {
+  $names = array();
+  $locations = locale_storage()->getLocations(array('sid' => $lids, 'type' => 'configuration'));
+  foreach ($locations as $location) {
+    $names[$location->name] = $location->name;
+  }
+  return $names;
+}
diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module
index 85ed5d1..a0d695a 100644
--- a/core/modules/locale/locale.module
+++ b/core/modules/locale/locale.module
@@ -265,6 +265,7 @@ function locale_language_delete($language) {
   // Remove interface translation files.
   module_load_include('inc', 'locale', 'locale.bulk');
   locale_translate_delete_translation_files($language->langcode);
+  locale_config_delete_language($language->langcode);
 
   _locale_invalidate_js($language->langcode);
 
@@ -415,12 +416,21 @@ function locale_get_plural($count, $langcode = NULL) {
   return $plural_indexes[$langcode][$count];
 }
 
-
 /**
  * Implements hook_modules_installed().
  */
 function locale_modules_installed($modules) {
-  locale_system_update($modules);
+  $components['module'] = $modules;
+  locale_system_update($components);
+}
+
+/**
+ * Implements hook_modules_uninstalled().
+ */
+function locale_modules_uninstalled($modules) {
+  include_once drupal_get_path('module', 'locale') . '/locale.bulk.inc';
+  $components['module'] = $modules;
+  locale_config_delete_components($components);
 }
 
 /**
@@ -430,7 +440,8 @@ function locale_modules_installed($modules) {
  *   initial installation. The theme system is missing an installation hook.
  */
 function locale_themes_enabled($themes) {
-  locale_system_update($themes);
+  $components['theme'] = $themes;
+  locale_system_update($components);
 }
 
 /**
@@ -440,8 +451,8 @@ function locale_themes_enabled($themes) {
  * right away, or start a batch if more files need to be imported.
  *
  * @param $components
- *   An array of component (theme and/or module) names to import
- *   translations for.
+ *   An array of arrays of component (theme and/or module) names to import
+ *   translations for, indexed by type.
  *
  * @todo
  *   This currently imports all .po files available, independent of
@@ -458,6 +469,9 @@ function locale_system_update($components) {
     if ($batch = locale_translate_batch_import_files(array(), TRUE)) {
       batch_set($batch);
     }
+    if ($batch = locale_config_batch_update_components(array(), $components)) {
+      batch_set($batch);
+    }
   }
 }
 
@@ -794,6 +808,27 @@ function _locale_refresh_translations($langcodes, $lids) {
 }
 
 /**
+ * Refresh 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($langcodes, $lids) {
+  if ($lids && $langcodes) {
+    include_once drupal_get_path('module', 'locale') . '/locale.bulk.inc';
+    if ($names = _locale_config_string_names($lids)) {
+      locale_config_update_multiple($names, $langcodes);
+    }
+  }
+}
+
+/**
  * Parses a JavaScript file, extracts strings wrapped in Drupal.t() and
  * Drupal.formatPlural() and inserts them into the database.
  *
diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc
index 8ea8780..8842ba5 100644
--- a/core/modules/locale/locale.pages.inc
+++ b/core/modules/locale/locale.pages.inc
@@ -439,7 +439,8 @@ function locale_translate_edit_form_submit($form, &$form_state) {
   }
 
   if ($updated) {
-    // Clear cache and force refresh of JavaScript translations.
+    // Clear cache and refresh configuration and JavaScript translations.
+    _locale_refresh_configuration(array($langcode), $updated);
     _locale_refresh_translations(array($langcode), $updated);
   }
 
diff --git a/core/modules/system/meta/system.maintenance.yml b/core/modules/system/meta/system.maintenance.yml
new file mode 100644
index 0000000..fb5e88b
--- /dev/null
+++ b/core/modules/system/meta/system.maintenance.yml
@@ -0,0 +1,8 @@
+enabled:
+  .definition:
+    label: 'Put site into maintenance mode'
+    type: boolean
+message:
+  .definition:
+    label: 'Message to display when in maintenance mode'
+    type: text
diff --git a/core/modules/system/meta/system.site.yml b/core/modules/system/meta/system.site.yml
new file mode 100644
index 0000000..918f48b
--- /dev/null
+++ b/core/modules/system/meta/system.site.yml
@@ -0,0 +1,23 @@
+name:
+  .definition:
+    label: 'Site name'
+    type: text
+mail:
+  .definition:
+    label: 'Site mail'
+slogan:
+  .definition:
+    label: 'Site slogan'
+    type: text
+page:
+  .definition:
+    label: 'Default pages'
+  403:
+    .definition:
+      label: 'Default 403 (access denied) page'
+  404:
+    .definition:
+      label: 'Default 404 (not found) page'
+  front:
+    .definition:
+      label: 'Default front page'
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 843ed78..ef811f5 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -2032,6 +2032,12 @@ function system_data_type_info() {
       'class' => '\Drupal\Core\TypedData\Type\String',
       'primitive type' => Primitive::STRING,
     ),
+    'text' => array(
+      'label' => t('Text'),
+      'description' => t('Human readable string.'),
+      'class' => '\Drupal\Core\TypedData\Type\String',
+      'primitive type' => Primitive::STRING,
+    ),
     'integer' => array(
       'label' => t('Integer'),
       'class' => '\Drupal\Core\TypedData\Type\Integer',
@@ -2113,6 +2119,12 @@ function system_data_type_info() {
       'class' => '\Drupal\Core\Entity\Field\Type\EntityReferenceItem',
       'list class' => '\Drupal\Core\Entity\Field\Type\Field',
     ),
+    'config_element' => array(
+      'label' => t('Configuration element'),
+      'description' => t('Configuration data'),
+      'class' => 'Drupal\Core\Config\Metadata\ConfigElement',
+      'list class' => 'Drupal\Core\Config\Metadata\ListElement',
+    ),
   );
 }
 
diff --git a/core/modules/user/meta/user.mail.%.yml b/core/modules/user/meta/user.mail.%.yml
new file mode 100644
index 0000000..36ee7f3
--- /dev/null
+++ b/core/modules/user/meta/user.mail.%.yml
@@ -0,0 +1,10 @@
+.definition:
+  label: 'Mail text'
+subject:
+  .definition:
+    label: 'Subject'
+    type: 'text'
+body:
+  .definition:
+    label: 'Body'
+    type: 'text'
diff --git a/core/modules/user/meta/user.mail.yml b/core/modules/user/meta/user.mail.yml
new file mode 100644
index 0000000..91e9030
--- /dev/null
+++ b/core/modules/user/meta/user.mail.yml
@@ -0,0 +1,5 @@
+.definition:
+  label: 'User mails'
+  list: '1'
+  list settings:
+    elements name: 'user.mail.%'
