diff --git a/core/includes/config.inc b/core/includes/config.inc
index 5e07c54..18a2078 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\ConfigWrapper;
+use Drupal\Core\Config\Metadata\MetadataLookup;
 use Drupal\Core\Config\StorageInterface;
 
 /**
@@ -104,6 +106,36 @@ function config_sync_get_changes(StorageInterface $source_storage, StorageInterf
 }
 
 /**
+ * Retrieves metadata for a configuration object or key.
+ *
+ * @param string $name
+ *   The name or key of the configuration object.
+ *
+ * @return Drupal\Core\Config\ConfigMetadata
+ *   A metadata array.
+ */
+function config_metadata($name) {
+  $metadata = &drupal_static(__FUNCTION__);
+  if (!$metadata) {
+    $metadata = new MetadataLookup();
+  }
+  return $metadata[$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\Wrapper
+ *   A configuration wrapper object.
+ */
+function config_wrapper($name) {
+  return new ConfigWrapper($name, config($name)->get());
+}
+
+/**
  * Writes an array of config file changes from a source storage to a target storage.
  *
  * @param array $config_changes
diff --git a/core/lib/Drupal/Core/Config/Metadata/ConfigWrapper.php b/core/lib/Drupal/Core/Config/Metadata/ConfigWrapper.php
new file mode 100644
index 0000000..b38f72a
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Metadata/ConfigWrapper.php
@@ -0,0 +1,283 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Config\Metadata\ConfigWrapper.
+ */
+
+namespace Drupal\Core\Config\Metadata;
+
+use Drupal\Core\Config\Config;
+use Drupal\Core\Language\Language;
+use Drupal\Core\TypedData\ComplexDataInterface;
+use Drupal\Core\TypedData\TranslatableInterface;
+use InvalidArgumentException;
+
+/**
+ * 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 ConfigWrapper extends WrapperBase implements ComplexDataInterface, TranslatableInterface  {
+
+  /**
+   * The name of the configuration object.
+   *
+   * @var string
+   */
+  protected $name;
+
+  /**
+   * The language code for which this is a translation.
+   *
+   * @var string
+   */
+  protected $translationLangcode;
+
+  /**
+   * Configuration wrapper used as translation source.
+   *
+   * @var Drupal\Core\Config\Metadata\ConfigWrapper
+   */
+  protected $translationSource;
+
+  /**
+   * String storage object that will be used for translations.
+   *
+   * @var Drupal\locale\StringStorageInterface $localeStorage
+   */
+  protected $localeStorage;
+
+  /**
+   * 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, $localeStorage = NULL) {
+    $this->name = $name;
+    $this->data = $data;
+    $this->localeStorage = $localeStorage;
+  }
+
+  /**
+   * Gets the configuration object name.
+   *
+   * @return string
+   *   Name of the configuration object.
+   */
+  public function getName() {
+    return $this->name;
+  }
+
+  /**
+   * 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 array $metadata
+   *   The element's metadata that may contain a 'string_context'
+   *
+   * @return string|FALSE
+   *   Translated string if there is a translation, FALSE if not.
+   */
+   public function getStringTranslation($langcode, $source, $metadata) {
+    if ($source && $this->language()->langcode == 'en' && isset($this->localeStorage)) {
+      $context = isset($metadata['string_context']) ? $metadata['string_context'] : '';
+      $translation = $this->localeStorage->findTranslation(array(
+        'source' => $source,
+        'context' => $context,
+        'language' => $langcode,
+      ));
+      if ($translation) {
+        return $translation->isTranslation() ? $translation->getString() : FALSE;
+      }
+      else {
+        // If this the default data, create source and add name as location.
+        $this->localeStorage->createString(array(
+            'source' => $source,
+            'context' => $context,
+            'location' => $this->getName() . '.yml',
+        ))->save();
+      }
+    }
+    return FALSE;
+  }
+
+  /**
+   * Sets translation parameters.
+   *
+   * @param string $langcode
+   *   The language code for the translation.
+   * @param Drupal\Core\Config\Metadata\ConfigWrapper $source
+   *   Configuration wrapper used as translation source.
+   */
+  public function setTranslation($langcode, $source) {
+    $this->translationLangcode = $langcode;
+    $this->translationSource = $source;
+    return $this;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::getConfigWrapper().
+   */
+  public function getConfigWrapper() {
+    return $this;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::getKey().
+   */
+  public function getKey() {
+    // This is the parent wrapper, so there is no key.
+    return '';
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::getMetadata().
+   */
+  public function getMetadata() {
+    return config_metadata($this->name);
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::getBase().
+   */
+  public function getBase() {
+    return $this->buildDefinition($this->data, $this->getMetadata());
+  }
+
+  /**
+   * 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->setElementData($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);
+      $this->clearElementData($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->getData();
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\ComplexDataInterface::setPropertyValues().
+   */
+  public function setPropertyValues($values) {
+    foreach ($values as $name => $value) {
+      $this->set($name, $value);
+    }
+    return $this;
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinition().
+   */
+  public function getPropertyDefinition($name) {
+    $definition = $this->getElementDefinition($name);
+    return $definition ? $definition : FALSE;
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinitions().
+   */
+  public function getPropertyDefinitions() {
+    return $this->getChildrenMetadata($this->getMetadata());
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\ComplexDataInterface::isEmpty().
+   */
+  public function isEmpty() {
+    return empty($this->data);
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\TranslatableInterface::getTranslationLanguages().
+   */
+  public function getTranslationLanguages($include_default = TRUE) {
+    // These will be all enabled languages.
+    $languages = language_list(LANGUAGE_CONFIGURABLE);
+    $default = $this->language();
+    if ($include_default) {
+      $languages[$default->translationLangcode] = $default;
+    }
+    else {
+      unset($language[$default->translationLangcode]);
+    }
+    return $languages;
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\TranslatableInterface::getTranslation().
+   */
+  public function getTranslation($translationLangcode, $strict = TRUE) {
+    $source = isset($this->translationSource) ? $this->translationSource : $this;
+    $data = $source->getTranslatedData($translationLangcode, $strict);
+    $translation = new ConfigWrapper($this->name, $data);
+    $translation->setTranslation($translationLangcode, $strict, $this);
+    return $translation;
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\TranslatableInterface::language().
+   */
+  public function language() {
+    if ($this->translationLangcode) {
+      $langcode = $this->translationLangcode;
+    }
+    else {
+      $meta = $this->getMetadata();
+      // The default language will be English as this is hardcoded information.
+      $langcode = isset($meta['language']) ? $meta['language'] : 'en';
+    }
+    return new Language(array('langcode' => $langcode));
+  }
+}
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..595ad07
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Metadata/ListElement.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Config\Metadata\ListElement.
+ */
+
+namespace Drupal\Core\Config\Metadata;
+
+use Drupal\Core\TypedData\ListInterface;
+use ArrayAccess;
+use Countable;
+
+/**
+ * Defines configuration element wrapper that is a list of properties.
+ *
+ * This is a nested element that wraps a generic array or list in the
+ * configuration data.
+ */
+class ListElement extends NestedElement implements ListInterface {
+
+  /**
+   * Implements ArrayAccess::offsetExists().
+   */
+  public function offsetExists($offset) {
+    $data = $this->getElementData($offset);
+    return isset($data);
+  }
+
+  /**
+   * Implements ArrayAccess::offsetGet().
+   */
+  public function offsetGet($offset) {
+    return $this->buildElement($offset);
+  }
+
+  /**
+   * Implements ArrayAccess::offsetSet().
+   */
+  public function offsetSet($offset, $value) {
+    if ($value instanceof TypedDataInterface) {
+      $value = $value->getValue();
+    }
+    $this->setElementData($offset, $value);
+  }
+
+  /**
+   * Implements ArrayAccess::offsetUnset().
+   */
+  public function offsetUnset($offset) {
+    $this->clearElementData($offset);
+  }
+
+  /**
+   * Implements Countable::count().
+   */
+  public function count() {
+    return count($this->getData());
+  }
+
+  /**
+   * 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..049ecb1
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Metadata/MetadataLookup.php
@@ -0,0 +1,111 @@
+<?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 entry in an actual
+ * configuration file, the corresponding metadata file contains an entry with
+ * the following sub-keys:
+ * - label: The human-readable name for this configuration item.
+ * - type: The data type of this configuration item. Valid types include string,
+ *   boolean, integer, and other types defined by hook_data_type_info().
+ *
+ * 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.
+ */
+
+namespace Drupal\Core\Config\Metadata;
+
+use Drupal\Core\Utility\CacheArray;
+use Drupal\Component\Utility\NestedArray;
+use Symfony\Component\Yaml\Parser;
+
+/**
+ * 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() {
+    parent::__construct('config:metadata', 'cache');
+  }
+
+  /**
+   * Overrides DrupalCacheArray::resolveCacheMiss().
+   */
+  protected function resolveCacheMiss($offset) {
+    $metadata = array();
+    // Get base metadata if available.
+    if ($basename = $this->getBaseName($offset)) {
+      $metadata = $this->offsetGet($basename);
+    }
+    // Merge current metadata on top of it.
+    if ($meta = $this->readMetadata($offset)) {
+      $metadata = NestedArray::mergeDeep($metadata, $meta);
+    }
+    $this->storage[$offset] = $metadata;
+    return $metadata;
+  }
+
+  /**
+   * Gets parent 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 getBaseName($name) {
+    $parts = explode('.', $name);
+    $last = array_pop($parts);
+    // If this is not the generic metadata (where the last component of the
+    // name is a '%'), try to load that first.
+    if ($last != self::BASE_MARK) {
+      return implode('.', array_merge($parts, array(self::BASE_MARK)));
+    }
+  }
+
+  /**
+   * Reads metadata from file system.
+   *
+   * @param string $name
+   *   Configuration name or key.
+   *
+   * @return array
+   *   Metadata array if found or empty array if not.
+   */
+  protected function readMetadata($name) {
+    if (!isset($this->metadataStorage)) {
+      $this->metadataStorage = new MetadataStorage();
+    }
+    try {
+      return $this->metadataStorage->read($name);
+    }
+    catch (Symfony\Component\Yaml\Exception\ParseException $e) {
+      // @todo Just log an error, but return empty array.
+    }
+    return array();
+  }
+}
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..927793a
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Metadata/MetadataStorage.php
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Config\Metadata\MetadataStorage.
+ */
+
+namespace Drupal\Core\Config\Metadata;
+
+use Drupal\Core\Config\FileStorage;
+
+/**
+ * Defines the file storage controller for metadata files.
+ */
+class MetadataStorage extends FileStorage {
+
+  /**
+   * Metadata map indexing configuration names and modules.
+   *
+   * @var array
+   */
+  protected $metaMap;
+
+  /**
+   * Overrides Drupal\Core\Config\FileStorage::__construct().
+   */
+  public function __construct() {
+    // Do nothing.
+  }
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::exists().
+   */
+  public function exists($name) {
+    return array_key_exists($name, $this->getMetadataMap());
+  }
+
+  /**
+   * Overrides Drupal\Core\Config\FileStorage::getFilePath().
+   */
+  public function getFilePath($name) {
+    $metamap = $this->getMetadataMap();
+    $module = $metamap[$name];
+    // We are looking for $module/meta/$name.yml
+    $directory = drupal_get_path('module', $module) . '/meta';
+    return $directory  . '/' . $name . '.yml';
+  }
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::listAll().
+   */
+  public function listAll($prefix = '') {
+    $names = array_keys($this->getMetadataMap());
+    if (!$prefix) {
+      return $names;
+    }
+    else {
+      foreach ($names as $index => $name) {
+        if (strpos($name, $prefix) !== 0 ) {
+          unset($names[$index]);
+        }
+      }
+      return $names;
+    }
+  }
+
+  /**
+   * Returns a map of all metadata names and the module they belong to.
+   *
+   * @return array
+   *   Array mapping metadata names with module names,
+   */
+  protected function getMetadataMap() {
+    if (!isset($this->metaMap)) {
+      $extension = '.' . self::getFileExtension();
+      foreach (module_list() as $module) {
+        $directory = drupal_get_path('module', $module) . '/meta';
+        if (file_exists($directory)) {
+          $files = glob($directory . '/*' . $extension);
+          foreach ($files as $filename) {
+            $name = basename($filename, $extension);
+            $this->metaMap[$name] = $module;
+          }
+        }
+      }
+    }
+    return $this->metaMap;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Config/Metadata/NestedElement.php b/core/lib/Drupal/Core/Config/Metadata/NestedElement.php
new file mode 100644
index 0000000..17abbab
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Metadata/NestedElement.php
@@ -0,0 +1,182 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Config\Metadata\NestedElement.
+ */
+
+namespace Drupal\Core\Config\Metadata;
+
+use Drupal\Core\Language\Language;
+use Drupal\Core\TypedData\TypedDataInterface;
+use Drupal\Core\TypedData\ContextAwareInterface;
+use Drupal\Component\Utility\NestedArray;
+
+/**
+ * Defines a generic nested configuration wrapper element.
+ *
+ * Wraps configuration data belonging to an intermediate level of the array.
+ * This object is contained in another NestedElement or the a ConfigWrapper.
+ */
+class NestedElement extends WrapperBase implements TypedDataInterface, ContextAwareInterface {
+
+  /**
+   * The configuration key relative to the parent's.
+   *
+   * @var string
+   */
+  protected $key;
+
+  /**
+   * The definition for this typed data.
+   *
+   * @var array
+   */
+  protected $definition;
+
+  /**
+   * Parent configuration wrapper.
+   *
+   * @var Drupal\Core\Config\WrapperInterface
+   */
+  protected $parent;
+
+  /**
+   * Implements Drupal\Core\TypedData\TypedDataInterface::__construct().
+   */
+  public function __construct(array $definition) {
+    $this->definition = $definition;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::setData().
+   */
+  public function setData($value) {
+    $this->data = $value;
+    $this->getParent()->setElementData($this->key, $this->data);
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::setElementData()
+   */
+  public function setElementData($key, $value) {
+    parent::setElementData($key, $value);
+    $this->getParent()->setElementData($this->key, $this->data);
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::getConfigWrapper().
+   */
+  public function getConfigWrapper() {
+    return $this->getParent()->getConfigWrapper();
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::getKey().
+   */
+  public function getKey() {
+    $parent = $this->getParent()->getKey();
+    return ($parent ? $parent . '.' : '') . $this->key;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::getMetadata().
+   */
+  public function getMetadata() {
+    return $this->getParent()->getElementMetadata($this->key);
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::getBase().
+   */
+  public function getBase() {
+    return $this->getDefinition();
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::clearElementData()
+  */
+  public function clearElementData($key) {
+    parent::clearElementData($key);
+    $this->getParent()->setElementData($this->key, $data);
+  }
+
+
+  /**
+   * Implements Drupal\Core\TypedData\TypedDataInterface::getType().
+   */
+  public function getType() {
+    return $this->definition['type'];
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\TypedDataInterface::getDefinition().
+   */
+  public function getDefinition() {
+    return $this->definition;
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\TypedDataInterface::getValue().
+   */
+  public function getValue() {
+    return $this->getData();
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\TypedDataInterface::setValue().
+   */
+  public function setValue($value) {
+    $this->data = $value;
+    // Due to how the constructor works, this function may get called before
+    // the parent is set.
+    // @see Drupal\Core\TypedData\TypedDataManager::createInstance()
+    if ($this->getParent()) {
+      $this->getParent()->set($this->key, $value);
+    }
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\TypedDataInterface::getString().
+   */
+  public function getString() {
+    return (string) $this->getValue();
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\TypedDataInterface::validate().
+   */
+  public function validate() {
+    // This will be ok if we have any config data at all.
+    return isset($this->data);
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\ContextAwareInterface::getName().
+   */
+  public function getName() {
+    return $this->key;
+  }
+
+  /**
+   * Implements Drupal\Core\TypedData\ContextAwareInterface::getName().
+   */
+  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;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Config/Metadata/WrapperBase.php b/core/lib/Drupal/Core/Config/Metadata/WrapperBase.php
new file mode 100644
index 0000000..adc65cd
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Metadata/WrapperBase.php
@@ -0,0 +1,348 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Config\Metadata\WrapperBase.
+ */
+
+namespace Drupal\Core\Config\Metadata;
+
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\TypedData\ListInterface;
+use ArrayIterator;
+use IteratorAggregate;
+
+/**
+ * Base class for all configuration data wrappers.
+ */
+abstract class WrapperBase implements IteratorAggregate, WrapperInterface {
+
+  /**
+   * The configuration data.
+   *
+   * @var array
+   */
+  protected $data;
+
+  /**
+   * Cached metadata for elements.
+   *
+   * @var array
+   */
+  protected $metadata;
+
+  /**
+   * Builds properties using configuration data and metadata.
+   *
+   * @param string $key
+   *   Configuration key.
+   *
+   * @result Drupal\Core\TypedData\TypedDataInterface
+   *   A TypedDataInterface object containing the configuration value.
+   */
+  protected function buildElement($key) {
+    // Build element array to start with.
+    $definition = $this->getElementDefinition($key);
+    $value = $this->getElementData($key);
+    // If this is an end property, the value will be the configuration data.
+    // If it is a NestedElement, it won't be set, but retrieved from
+    // the parent element. See NestedElement::setValue()
+    $context = array('name' => $key, 'parent' => $this);
+    return typed_data()->create($definition, $value, $context);
+  }
+
+  /**
+   * Builds metadata for a child element.
+   *
+   * @param $key
+   *   Element's key.
+   *
+   * @return array
+   *   Configuration metadata for the element.
+   */
+  protected function buildElementMetadata($key) {
+    $metadata = $this->getMetadata();
+    $meta = isset($metadata[$key]) ? $metadata[$key] : array();
+    $definition = $this->getBase();
+    if (isset($definition['elements.base']) && $base = config_metadata($definition['elements.base'])) {
+      // Load common base metadata for all elements.
+      $meta = NestedArray::mergeDeep($base, $meta);
+    }
+    if (isset($definition['elements.name'])) {
+      // Load specific metadata for each element.
+      $name = $definition['elements.name'];
+      $element_key = $key;
+      if (isset($definition['elements.key'])) {
+        // The search key for this one is in the data itself.
+        $key_name = str_replace('%', $key, $definition['elements.key']);
+
+        $element_key = $this->getElementData($key_name);
+      }
+      if ($element_key) {
+        $name = str_replace('%', $element_key, $name);
+      }
+      if ($merge = config_metadata($name)) {
+        $meta = NestedArray::mergeDeep($meta, $merge);
+      }
+    }
+    if (isset($definition['elements.label']) && !isset($meta['.label'])) {
+      // Extract element's label from configuration data.
+      $key_name = str_replace('%', $key, $definition['elements.label']);
+      if ($label = $this->getElementData($key_name)) {
+        $meta['.label'] = $label;
+      }
+    }
+    return $meta;
+  }
+
+  /**
+   * Translates element if it fits our translation criteria.
+   *
+   * @param Drupal\Core\TypedData\TypedDataInterface $element
+   *   Element value.
+   * @param string $langcode
+   *   The language code for the translation.
+   * @param bool $strict
+   *   Whether this is a strict translation.
+   *
+   * @return bool
+   *   Whether the element fits the translation criteria.
+   */
+  protected function translateElement($element, $langcode, $strict) {
+    $definition = $element->getDefinition();
+    $value = $element->getValue();
+    if ($value && is_string($value) && !empty($definition['translatable'])) {
+      if ($translation = $this->getConfigWrapper()->getStringTranslation($langcode, $value, $definition)) {
+        $element->setValue($translation);
+        return TRUE;
+      }
+    }
+    // The element doesn't have a translation, if strict mode we drop it.
+    return !$strict;
+  }
+
+  /**
+   * Builds element definition from metadata.
+   *
+   * @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.
+   */
+  protected function buildDefinition($data, $metadata) {
+    $element = $children = array();
+    foreach ($metadata as $name => $value) {
+      if (strpos($name, '.') === 0) {
+        // Metadata properties will be prefixed by a dot.
+        $element[substr($name, 1)] = $value;
+      }
+      else {
+        // Children elements will be arrays of metadata themselves.
+        $children[$name] = $value;
+      }
+    }
+    // The default type will depend on whether we've got children or not.
+    $element['config.children'] = !empty($children);
+    if (!isset($element['type'])) {
+      $element['type'] = $element['config.children'] || is_array($data) ? 'config_list' : 'string';
+    }
+    return $element;
+  }
+
+  /**
+   * Gets valid configuration data keys.
+   *
+   * @return array
+   *   Array valid configuration data keys.
+   */
+  protected function getAllKeys() {
+    return is_array($this->data) ? array_keys($this->data) : array();
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::getData().
+   */
+  public function getData() {
+    return $this->data;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::setData().
+   */
+  public function setData($value) {
+    $this->data = $value;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::getLabel().
+   */
+  public function getLabel() {
+    $definition = $this->getBase();
+    return isset($definition['label']) ? $definition['label'] : $this->getKey();
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::getTranslatedData()
+   */
+  public function getTranslatedData($langcode, $strict) {
+    $translation = array();
+    foreach ($this->getAllKeys() as $key) {
+      $element = $this->buildElement($key);
+      $value = NULL;
+      if ($element instanceof WrapperInterface) {
+        $value = $element->getTranslatedData($langcode, $strict);
+      }
+      elseif ($this->translateElement($element, $langcode, $strict)) {
+        $value = $element->getValue();
+      }
+      elseif (!$strict) {
+        $value = $element->getValue();
+      }
+      if ($value || !$strict) {
+        $translation[$key] = $value;
+      }
+    }
+    return $translation;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::getElement()
+   */
+  public function getElement($key) {
+    $parts = explode('.', $key);
+    $first = array_shift($parts);
+    $data = $this->getElementData($first);
+    // Only attempt to build the element if it actually has configuration data.
+    if (isset($data) && $element = $this->buildElement($first)) {
+      $subkey = implode('.', $parts);
+      // If not a nested key, return the element whatever it is. But if we need
+      // to find a nested key, this is only possible if it's a WrapperInterface.
+      if (!$subkey) {
+        return $element;
+      }
+      elseif ($element instanceof WrapperInterface) {
+        return $element->getElement($subkey);
+      }
+    }
+    return NULL;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::setElementData()
+   */
+  public function setElementData($key, $value) {
+    $parts = explode('.', $key);
+    if (count($parts) == 1) {
+      $this->data[$key] = $value;
+    }
+    else {
+      NestedArray::setValue($this->data, $parts, $value);
+    }
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::getElementData().
+   */
+  public function getElementData($key) {
+    if (!$key) {
+      return $this->data;
+    }
+    else {
+      $parts = explode('.', $key);
+      if (count($parts) == 1) {
+        return isset($this->data[$key]) ? $this->data[$key] : NULL;
+      }
+      else {
+        $value = NestedArray::getValue($this->data, $parts, $key_exists);
+        return $key_exists ? $value : NULL;
+      }
+    }
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::getElementDefinition().
+   */
+  public function getElementDefinition($key) {
+    return $this->buildDefinition($this->getElementData($key), $this->getElementMetadata($key));
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::getElementMetadata().
+   */
+  public function getElementMetadata($key) {
+    // Metadata for nested elements must be built before.
+    if (!isset($this->metadata[$key])) {
+      $this->metadata[$key] = $this->buildElementMetadata($key);
+    }
+    return $this->metadata[$key];
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::getElementLabel().
+   */
+  public function getElementLabel($key) {
+    $definition = $this->getElementDefinition($key);
+    return isset($definition['label']) ? $definition['label'] : $this->getElementKey($key);
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::getElementKey().
+   */
+  public function getElementKey($key) {
+    $parent = $this->getKey();
+    return $parent ? $parent . '.' . $key : $key;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::clearElementData().
+   */
+  public function clearElementData($key) {
+    $parts = explode('.', $key);
+    if (count($parts) == 1) {
+      unset($this->data[$key]);
+    }
+    else {
+      NestedArray::unsetValue($this->data, $parts);
+    }
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::toArray().
+   */
+  public function toArray() {
+    $array = array();
+    foreach ($this->getAllKeys() as $key) {
+      $array[$key] = $this->buildElement($key);
+    }
+    return $array;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\Metadata\WrapperInterface::toList().
+   */
+  public function toList($base_name = '') {
+    $list = array();
+    foreach ($this->toArray() as $key => $element) {
+      $name = $base_name ? $base_name . '.' . $key : $key;
+      if ($element instanceof WrapperInterface) {
+        $list += $element->toList($name);
+      }
+      else {
+        $list[$name] = $element;
+      }
+    }
+    return $list;
+  }
+
+  /**
+   * Implements IteratorAggregate::getIterator().
+   */
+  public function getIterator() {
+    return new ArrayIterator($this->toArray());
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Config/Metadata/WrapperInterface.php b/core/lib/Drupal/Core/Config/Metadata/WrapperInterface.php
new file mode 100644
index 0000000..c86adc5
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/Metadata/WrapperInterface.php
@@ -0,0 +1,234 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Config\Metadata\WrapperInterface.
+ */
+
+namespace Drupal\Core\Config\Metadata;
+
+use Drupal\Core\TypedData\ComplexDataInterface;
+
+/**
+ * Interface for data structures that contain configuration data and metadata.
+ */
+interface WrapperInterface {
+
+  /**
+   * Gets the base configuration wrapper object.
+   *
+   * @return Drupal\Core\Config\Metadata\ConfigWrapper
+   *   Configuration wrapper object that is the outermost wrapper.
+   */
+  public function getConfigWrapper();
+
+  /**
+   * 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 data for this element.
+   *
+   * @return mixed
+   *   The configuration data for this element.
+   */
+  public function getData();
+
+  /**
+   * Gets typed data definition for the object.
+   *
+   * @return array
+   *   The configuration data for this element.
+   */
+  public function getBase();
+
+  /**
+   * Sets raw configuration data.
+   *
+   * @param $value
+   *   Value to set in the configuration data.
+   */
+  public function setData($value);
+
+  /**
+   * Get translated configuration data.
+   *
+   * @param string $langcode
+   *   The language code for the translation.
+   * @param bool $strict
+   *   Whether the translation should include only translated properties.
+   *
+   * @return array
+   *   Configuration data translated to the requested language.
+   */
+  public function getTranslatedData($langcode, $strict);
+
+  /**
+   * Gets raw metadata array for this element.
+   *
+   * @return array
+   *   The full metadata array for this element.
+   */
+  public function getMetadata();
+
+
+  /**
+   * Gets configuration key for element.
+   *
+   * @param $key
+   *   The element's key.
+   *
+   * @return string
+   *   Configuration key for this structure.
+   */
+  public function getElementKey($key);
+
+  /**
+   * Gets human readable label for element.
+   *
+   * @param $key
+   *   The element's key.
+   *
+   * @return string
+   *   The element's label.
+  */
+  public function getElementLabel($key);
+
+  /**
+   * Gets the built metadata array for a child element.
+   *
+   * The element's metadata is an array that may contain the following
+   * elements:
+   * - Any of the keys used by getElementDefinition() prefixed by a dot.
+   * - Any nested array that will keyed by configuration key and will be mapped
+   *  to a nested WrapperInterface or TypedDataInterface element.
+   *
+   * Example metadata from module/system/meta/system.site.yml
+   *
+   *   name:
+   *     .label: 'Site name'
+   *     .type: 'label'
+   *   mail:
+   *     .label: 'Site mail'
+   *   slogan:
+   *     .label: 'Site slogan'
+   *     .type: 'text'
+   *   page:
+   *     .type: config_list
+   *     403:
+   *       .label: 'Default 403 (access denied) page'
+   *       .type: 'string'
+   *     404:
+   *       .label: 'Default 404 (not found) page'
+   *       .type: 'string'
+   *     front:
+   *       .label: 'Default front page'
+   *       .type: 'string'
+   *
+   * @param $key
+   *   Key from the configuration data array.
+   *
+   * @return array
+   *   The metadata for the element, defaults to empty array().
+   */
+  public function getElementMetadata($key);
+
+  /**
+   * Gets the definition array for a child element.
+   *
+   * The element definition is an array that may contain any of the following
+   * optional elements:
+   * - 'label', Human readable name for the element.
+   * - '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. It may contain the placeholder '%'
+   *   to be replaced by the element's key.
+   * - 'elements.label', Name of the configuration data property to be used as
+   *   the label for the element definition. It may contain the placeholder '%'
+   *   to be replaced by the element's key.
+   * - Any of the keys used for the $definition parameter of
+   *   Drupal\Core\TypedData\TypedDataManager::create().
+   *
+   * @param $key
+   *   Key from the configuration data array.
+   *
+   * @return array
+   *   The definition for the element, defaults to empty array().
+   */
+  public function getElementDefinition($key);
+
+  /**
+   * Gets a nested property object using a key with dot notation.
+   *
+   * @param string $key
+   *   Configuration key.
+   *
+   * @return \Drupal\Core\TypedData\TypedDataInterface|\Drupal\Core\TypedData\ListInterface|null
+   *   The property object or a list of property objects or null if the
+   *   property doesn't exist.
+   */
+  public function getElement($key);
+
+  /**
+   * Gets raw configuration for a child element.
+   *
+   * @param $key
+   *   (optional) Relative key within the configuration data.
+   *
+   * @return array|string|null
+   *   Array or item of configuration data.
+   */
+  public function getElementData($key);
+
+  /**
+   * Sets raw configuration data.
+   *
+   * @param $key
+   *   Relative key within the configuration data.
+   * @param $value
+   *   Value to set in the configuration data.
+   */
+  public function setElementData($key, $value);
+
+  /**
+   * Delete configuration data.
+   *
+   * @param $key
+   *   Relative key within the configuration data.
+   */
+  public function clearElementData($key);
+
+  /**
+   * Gets typed data properties as array.
+   *
+   * @return array
+   *   List of TypedDataInterface objects indexed by key.
+   */
+  public function toArray();
+
+  /**
+   * Gets all contained typed data properties as plain array.
+   *
+   * @param string $base_name
+   *   Base name to prepend to all of the element's names.
+   *
+   * @return array
+   *   List of TypedDataInterface objects indexed by full name (keys with dot
+   *   notation).
+   */
+  public function toList($base_name = '');
+
+}
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..c56ff34
--- /dev/null
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigMetadataTest.php
@@ -0,0 +1,179 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\config\Tests\ConfigMetadataTest.
+ */
+
+namespace Drupal\config\Tests;
+
+use Drupal\Core\Config\ConfigWrapper;
+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(
+      'enabled' => array(
+        '.label' => 'Put site into maintenance mode',
+        '.type' => 'boolean'
+       ),
+      'message' => 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(
+      'name' => array(
+        '.label' => 'Machine name',
+        '.type' => 'string',
+      ),
+      'label' => array(
+        '.label' => 'Label',
+        '.type' => 'label'
+      ),
+      'effects' => array(
+        '.label' =>  'Style effects',
+        '.type' => 'config_list',
+        '.elements.name' => 'image.style.effects.%',
+        '.elements.key' => '%.name'
+      )
+    );
+    $this->assertEqual($metadata, $expected, 'Retrieved the right metadata for image.style.large');
+
+    // Most complex case, fallback to parent name with merging
+    $metadata = config_metadata('image.style.effects.image_scale');
+    $expected = array(
+      '.label' => 'Image scale',
+      'name' => array(
+        '.label' => 'Style name',
+        '.type' => 'string',
+      ),
+      'data' => array(
+        '.label' => 'Data',
+        '.type' => 'config_list',
+        'width' => array(
+          '.label' => 'Width',
+          '.type' => 'integer'
+        ),
+        'height' => array(
+          '.label' =>  'Height',
+          '.type' => 'integer',
+        ),
+        'upscale' => array(
+          '.label' =>  'Upscale',
+          '.type' => 'boolean',
+        )
+      ),
+      'weight' => array(
+        '.label' => 'Weight',
+        '.type' => 'integer'
+      ),
+      'ieid' => array(
+        '.label' =>  'IEID',
+      ),
+
+    );
+    $this->assertEqual($metadata, $expected, 'Retrieved the right metadata for image.style.effects.scale');
+
+  }
+
+  /**
+   * 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(), 'label', '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.');
+
+    // 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->toArray());
+    $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']->getData(), $object->getData(), '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');
+  }
+
+  /**
+   * Tests metadata applied to configuration objects.
+   */
+  function testConfigTranslation() {
+    /*
+    // Create translation for site name using string overrides.
+    $langcode = 'xx';
+    $site_name = $this->randomName(20);;
+    $translation['']['Drupal'] = $site_name;
+    variable_set('locale_custom_strings_' . $langcode, $translation);
+    $this->refreshVariables();
+
+    $wrapper = config_wrapper('system.site');
+
+    // Get strict translation
+    $translation = $wrapper->getTranslation($langcode, TRUE);
+    $properties = $translation->toList();
+    $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
+    $translation = $wrapper->getTranslation($langcode, FALSE);
+    $properties = $translation->toList();
+    $this->assertEqual(count($properties), 6, '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');
+*/
+  }
+}
diff --git a/core/modules/image/meta/image.style.%.yml b/core/modules/image/meta/image.style.%.yml
new file mode 100644
index 0000000..99b54ba
--- /dev/null
+++ b/core/modules/image/meta/image.style.%.yml
@@ -0,0 +1,11 @@
+name:
+  .label: 'Machine name'
+  .type: string
+label:
+  .label: 'Label'
+  .type: label
+effects:
+  .label: 'Style effects'
+  .type: config_list
+  .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..7191867
--- /dev/null
+++ b/core/modules/image/meta/image.style.effects.%.yml
@@ -0,0 +1,11 @@
+name:
+  .label: 'Style name'
+  .type: string
+data:
+  .label: 'Data'
+  .type: config_list
+weight:
+  .label: 'Weight'
+  .type: integer
+ieid:
+  .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..30b017e
--- /dev/null
+++ b/core/modules/image/meta/image.style.effects.image_scale.yml
@@ -0,0 +1,11 @@
+.label: 'Image scale'
+data:
+  width:
+    .label: 'Width'
+    .type: 'integer'
+  height:
+    .label: 'Height'
+    .type: 'integer'
+  upscale:
+    .label: 'Upscale'
+    .type: 'boolean'
diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc
index b7850db..26ad373 100644
--- a/core/modules/locale/locale.bulk.inc
+++ b/core/modules/locale/locale.bulk.inc
@@ -549,6 +549,8 @@ function locale_translate_batch_finished($success, $results) {
 
     // Clear cache and force refresh of JavaScript translations.
     _locale_invalidate_js();
+    // Refresh configuration translations.
+    _locale_invalidate_config();
     cache()->invalidateTags(array('locale' => TRUE));
   }
 }
diff --git a/core/modules/locale/locale.config.inc b/core/modules/locale/locale.config.inc
new file mode 100644
index 0000000..e62627e
--- /dev/null
+++ b/core/modules/locale/locale.config.inc
@@ -0,0 +1,189 @@
+<?php
+
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Config\Config;
+use Drupal\Core\Config\FileStorage;
+use Drupal\Core\Config\NullStorage;
+use Drupal\Core\Config\Metadata\ConfigWrapper;
+
+/**
+ * @file
+ * Configuration update workflows for locale strings.
+ */
+
+/**
+ * Force the configuration translations to be refreshed.
+ *
+ * @param $langcode
+ *   The language code for which the configuration needs to be retranslated.
+ *
+ * @return
+ *   New content of the 'system.configuration_translated' variable.
+ */
+function locale_config_invalidate_language($langcode = NULL) {
+  $translate = state()->get('system.translate_configuration') ?: array();
+  $names = array_keys(locale_config_default_folder());
+
+  if (empty($langcode)) {
+    // Invalidate all languages.
+    $languages = locale_translatable_language_list();
+    foreach ($languages as $lcode => $data) {
+      $translate[$lcode] = $names;
+    }
+  }
+  else {
+    // Invalidate single language.
+    $translate[$langcode] = $names;
+  }
+
+  state()->set('system.translate_configuration', $translate);
+  return $translate;
+}
+
+/**
+ * Translate a few configuration objects for given language.
+ *
+ * @param string $langcode.
+ */
+function locale_config_translate_step($langcode, $number) {
+  $count = 0;
+  $translate = state()->get('system.translate_configuration') ?: array();
+  if (!empty($translate[$langcode])) {
+    while (($name = array_shift($translate[$langcode])) && $count < $number) {
+      $names[] = $name;
+      $count++;
+    }
+    locale_config_update_multiple($names, array($langcode));
+    state()->set('system.translate_configuration', $translate);
+  }
+  return $count;
+}
+
+/**
+ * Update configuration when strings changed.
+ *
+ * This will be the function to use once this patch lands:
+ * http://drupal.org/node/1777070
+ *
+ * @param $lids
+ *   String identifiers for updated / created strings.
+ */
+function locale_config_update_translations($langcode, $lids) {
+  // Collect configuration names for these strings.
+  $locations = locale_storage()->getLocations(array('lid' => $lids, 'type' => 'configuration'));
+
+  $name_strings = array();
+  foreach ($locations as $location) {
+    $name_strings[$locations->name][] = $location->lid;
+  }
+  // Update configuration translation data.
+  if ($name_strings) {
+    locale_config_update_multiple(array_keys($name_strings), array($langcode));
+  }
+}
+
+/**
+ * Create translated configuration for module, all languages.
+ *
+ * This will run:
+ * - When the module is installed.
+ * - When a language is added (then we translate config for all installed modules)
+ */
+function locale_config_import_module($module) {
+  $directory = drupal_get_path('module', $module) . '/config';
+  if (file_exists($directory)) {
+    $storage = new FileStorage($directory);
+    if ($names = $storage->listAll()) {
+      $langcodes = array_keys(language_list());
+      return locale_config_update_multiple($names, $langcodes);
+    }
+  }
+}
+
+/**
+ * Enable language, thus update all modules for that language.
+ */
+function locale_config_refresh_language($langcode) {
+  $names = array_keys(locale_config_default_folder());
+  return locale_config_update_multiple($names, array($langcode));
+}
+
+/**
+ * Update all configuration for names / languages.
+ */
+function locale_config_update_multiple($names, $langcodes) {
+  $count = 0;
+  foreach ($names as $name) {
+    if ($data = locale_config_default_data($name)) {
+      $wrapper = new ConfigWrapper($name, $data, locale_storage());
+      foreach ($langcodes as $lang) {
+        $translation = $wrapper->getTranslation($lang, TRUE)->getData();
+        locale_config_update_storage($name, $lang, $translation);
+        $count++;
+      }
+      unset($wrapper);
+    }
+  }
+  return $count;
+}
+
+/**
+ * Get a default configuration wrapper for config name.
+ *
+ * @todo The config objects are useless for this, ask some changes from the guys.
+ * @todo Format the previous @todo to 80 chars ;-)
+ */
+function locale_config_update_storage($name, $langcode, $data) {
+  $locale_name = 'locale.config.' . $langcode . '.' . $name;
+  $storage = drupal_container()->get('config.storage');
+  // If there are existing translations for whatever reason we don't want to lose them
+  $current = $storage->read($locale_name);
+  $updated = $current ? NestedArray::mergeDeep($current, $data) : $data;
+  if ($updated) {
+    $storage->write($locale_name, $updated);
+  }
+  else {
+    $storage->delete($locale_name);
+  }
+  return $updated;
+}
+
+/**
+ * Get default data for configuration name.
+ */
+function locale_config_default_data($name) {
+  if ($directory = locale_config_default_folder($name)) {
+    $storage = new FileStorage($directory);
+    return $storage->read($name);
+  }
+  else {
+    return array();
+  }
+}
+
+/**
+ * Get all default configuration names and the path on which they are.
+ *
+ * @todo Do the same for themes?
+ */
+function locale_config_default_folder($name = NULL) {
+  $folders = &drupal_static(__FUNCTION__);
+  if (!$folders) {
+    foreach (module_list() as $module) {
+      $directory = drupal_get_path('module', $module) . '/config';
+      if (file_exists($directory)) {
+        $storage = new FileStorage($directory);
+        foreach ($storage->listAll() as $config_name) {
+          $folders[$config_name] = $directory;
+        }
+      }
+    }
+  }
+  if ($name) {
+    return isset($folders[$name]) ? $folders[$name] : NULL;
+  }
+  else {
+    return $folders;
+  }
+}
+
diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module
index 3d3ce1f..e7a05b0 100644
--- a/core/modules/locale/locale.module
+++ b/core/modules/locale/locale.module
@@ -242,6 +242,8 @@ function locale_language_insert($language) {
   cache('page')->flush();
   // Force JavaScript translation file re-creation for the new language.
   _locale_invalidate_js($language->langcode);
+  // Force configuration translation to be re-created.
+  _locale_invalidate_config($language->langcode);
 }
 
 /**
@@ -267,6 +269,7 @@ function locale_language_delete($language) {
   locale_translate_delete_translation_files($language->langcode);
 
   _locale_invalidate_js($language->langcode);
+  _locale_invalidate_config($language->langcode);
 
   // Changing the language settings impacts the interface:
   cache('page')->flush();
@@ -424,6 +427,14 @@ function locale_modules_installed($modules) {
 }
 
 /**
+ * Implements hook_modules_enabled().
+ */
+function locale_modules_enabled($modules) {
+  module_load_include('inc', 'locale', 'locale.config');
+  array_map('locale_config_import_module', $modules);
+}
+
+/**
  * Implements hook_themes_enabled().
  *
  * @todo This is technically wrong. We must not import upon enabling, but upon
@@ -917,6 +928,25 @@ function _locale_invalidate_js($langcode = NULL) {
 }
 
 /**
+ * Force the configuration translations to be refreshed.
+ *
+ * This function sets a refresh flag for a specified language, or all
+ * languages except English, if none specified. JavaScript translation
+ * files are rebuilt (with locale_update_js_files()) the next time a
+ * request is served in that language.
+ *
+ * @param $langcode
+ *   The language code for which the configuration needs to be retranslated.
+ *
+ * @return
+ *   New content of the 'system.configuration_translated' variable.
+ */
+function _locale_invalidate_config($langcode = NULL) {
+  module_load_include('inc', 'locale', 'locale.config');
+  locale_config_invalidate_language($langcode);
+}
+
+/**
  * (Re-)Creates the JavaScript translation file for a language.
  *
  * @param $langcode
@@ -1037,6 +1067,14 @@ function _locale_rebuild_js($langcode = NULL) {
  * Implements hook_language_init().
  */
 function locale_language_init() {
+  // Check whether configuration for this language needs to be rebuilt.
+  $translate = state()->get('system.translate_configuration') ?: array();
+  $langcode = language(LANGUAGE_TYPE_INTERFACE)->langcode;
+  if (!empty($translate[$langcode])) {
+    module_load_include('inc', 'locale', 'locale.config');
+    $count = locale_config_translate_step($langcode, 4);
+  }
+
   // Add locale helper to configuration subscribers.
   drupal_container()->get('dispatcher')->addSubscriber(new LocaleConfigSubscriber());
 }
diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc
index 71aa14d..6e9146a 100644
--- a/core/modules/locale/locale.pages.inc
+++ b/core/modules/locale/locale.pages.inc
@@ -403,6 +403,7 @@ function locale_translate_edit_form_validate($form, &$form_state) {
  */
 function locale_translate_edit_form_submit($form, &$form_state) {
   $langcode = $form_state['values']['langcode'];
+
   foreach ($form_state['values']['strings'] as $lid => $translations) {
     // Get target string, that may be NULL if there's no translation.
     $target = locale_storage()->findTranslation(array('language' => $langcode, 'lid' => $lid));
@@ -437,6 +438,8 @@ function locale_translate_edit_form_submit($form, &$form_state) {
 
   // Force JavaScript translation file recreation for this language.
   _locale_invalidate_js($langcode);
+  // Rebuild configuration translations for this language.
+  _locale_invalidate_config($langcode);
   // Clear locale cache.
   cache()->invalidateTags(array('locale' => TRUE));
 }
diff --git a/core/modules/system/meta/system.maintenance.yml b/core/modules/system/meta/system.maintenance.yml
new file mode 100644
index 0000000..96be6e2
--- /dev/null
+++ b/core/modules/system/meta/system.maintenance.yml
@@ -0,0 +1,6 @@
+enabled:
+  .label: 'Put site into maintenance mode'
+  .type: boolean
+message:
+  .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..7917269
--- /dev/null
+++ b/core/modules/system/meta/system.site.yml
@@ -0,0 +1,17 @@
+name:
+  .label: 'Site name'
+  .type: label
+mail:
+  .label: 'Site mail'
+slogan:
+  .label: 'Site slogan'
+  .type: text
+page:
+  .type: config_list
+  .label: 'Default pages'
+  403:
+    .label: 'Default 403 (access denied) page'
+  404:
+    .label: 'Default 404 (not found) page'
+  front:
+    .label: 'Default front page'
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 255c669..133c4b0 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -2067,6 +2067,31 @@ function system_data_type_info() {
       'class' => '\Drupal\Core\Entity\Field\Type\EntityReferenceItem',
       'list class' => '\Drupal\Core\Entity\Field\Type\Field',
     ),
+    'config_list' => array(
+      'label' => t('Configuration list'),
+      'description' => t('List of configuration data'),
+      'class' => 'Drupal\Core\Config\Metadata\ListElement',
+    ),
+    'config_element' => array(
+      'label' => t('Nested configuration'),
+      'description' => t('Nested configuration data'),
+      'class' => 'Drupal\Core\Config\Metadata\NestedElement',
+    ),
+    // These are UI string types.
+    'label' => array(
+      'label' => t('Label'),
+      'description' => t('Short object name.'),
+      'class' => '\Drupal\Core\TypedData\Type\String',
+      'primitive type' => Primitive::STRING,
+      'translatable' => TRUE,
+    ),
+    'text' => array(
+       'label' => t('Text'),
+       'description' => t('Textual content.'),
+       'class' => '\Drupal\Core\TypedData\Type\String',
+       'primitive type' => Primitive::STRING,
+       'translatable' => TRUE,
+    ),
   );
 }
 
diff --git a/core/modules/user/meta/user.mail.%.yml b/core/modules/user/meta/user.mail.%.yml
new file mode 100644
index 0000000..11b479b
--- /dev/null
+++ b/core/modules/user/meta/user.mail.%.yml
@@ -0,0 +1,7 @@
+.label: 'Mail text'
+subject:
+  .label: 'Subject'
+  .type: 'text'
+body:
+  .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..35cbd52
--- /dev/null
+++ b/core/modules/user/meta/user.mail.yml
@@ -0,0 +1,3 @@
+.label: 'User mails'
+.type: config_list
+.elements.name: 'user.mail.%'
