diff --git a/core/includes/language.inc b/core/includes/language.inc
index 74d5752..11b71c9 100644
--- a/core/includes/language.inc
+++ b/core/includes/language.inc
@@ -13,6 +13,11 @@
 const LANGUAGE_NEGOTIATION_SELECTED = 'language-selected';
 
 /**
+ * The language is determined using the current interface language.
+ */
+const LANGUAGE_NEGOTIATION_INTERFACE = 'language-interface';
+
+/**
  * @defgroup language_negotiation Language Negotiation API functionality
  * @{
  * Functions to customize the language types and the negotiation process.
@@ -49,6 +54,7 @@
  *   unset($language_types[LANGUAGE_TYPE_CONTENT]['fixed']);
  * }
  * @endcode
+ * @todo Fix documentation with the locked parameter.
  *
  * Every language type can have a different set of language negotiation methods
  * assigned to it. Different language types often share the same language
@@ -165,34 +171,15 @@ function language_types_info() {
  * whose language negotiation methods are module-defined and not altered through
  * the user interface.
  *
- * @param $stored
- *   (optional) By default, retrieves values from the 'language_types' variable
- *   to avoid unnecessary hook invocations. If set to FALSE, retrieves values
- *   from the actual language type definitions. This allows reaction to
- *   alterations performed on the definitions by modules installed after the
- *   'language_types' variable is set.
- *
  * @return
  *   An array of language type names.
  */
-function language_types_get_configurable($stored = TRUE) {
+function language_types_get_configurable() {
   $configurable = &drupal_static(__FUNCTION__);
-
-  if ($stored && !isset($configurable)) {
-    $types = variable_get('language_types', language_types_get_default());
-    $configurable = array_keys(array_filter($types));
+  if (!isset($configurable)) {
+    $configurable = config('system.language.types')->get('configurable');
+    $configurable = empty($configurable) ? array() : array_keys(array_filter($configurable));
   }
-
-  if (!$stored) {
-    $result = array();
-    foreach (language_types_info() as $type => $info) {
-      if (!isset($info['fixed'])) {
-        $result[] = $type;
-      }
-    }
-    return $result;
-  }
-
   return $configurable;
 }
 
@@ -214,37 +201,62 @@ function language_types_disable($types) {
 
 /**
  * Updates the language type configuration.
+ *
+ * @param array $configurable
+ *   The configuration object containing the user defined preferences for
+ *   language type customizability.
+ * @todo
+ *   We are using variable_set('language_types', $language_types) and
+ *   config('system.language.types')->set('configurable', $configurable)->save()
+ *   at the same time. This is redundant. We need to change variable_set to
+ *   configuration.
  */
-function language_types_set() {
+function language_types_set($configurable) {
   // Ensure that we are getting the defined language negotiation information. An
   // invocation of module_enable() or module_disable() could outdate the cached
   // information.
   drupal_static_reset('language_types_info');
   drupal_static_reset('language_negotiation_info');
 
-  // Determine which language types are configurable and which not by checking
-  // whether the 'fixed' key is defined. Non-configurable (fixed) language types
-  // have their language negotiation settings stored there.
   $language_types = array();
   $negotiation_info = language_negotiation_info();
   foreach (language_types_info() as $type => $info) {
-    if (isset($info['fixed'])) {
-      $language_types[$type] = FALSE;
-      $method_weights = array();
-      foreach ($info['fixed'] as $weight => $method_id) {
-        if (isset($negotiation_info[$method_id])) {
-          $method_weights[$method_id] = $weight;
+    // Check if the language is locked. The configuration of a locked language
+    // type cannot be changed using the UI.
+    if ($locked = !empty($info['locked'])) {
+      // If a locked language has default settings (by using the 'fixed'
+      // property) it will be always configurable. If it has no default
+      // settings, then it won't be configurable.
+      $configurable[$type] = empty($info['fixed']);
+      $language_types[$type] = !$locked;
+      if (!$configurable[$type]) {
+        $method_weights = array();
+        foreach ($info['fixed'] as $weight => $method_id) {
+          if (isset($negotiation_info[$method_id])) {
+            $method_weights[$method_id] = $weight;
+          }
         }
+        language_negotiation_set($type, $method_weights);
       }
-      language_negotiation_set($type, $method_weights);
     }
     else {
-      $language_types[$type] = TRUE;
+      // The configuration of an unlocked language will be based on the
+      // $configurable array (stored in configuration). These values may be
+      // altered using the UI.
+      $language_types[$type] = !empty($configurable[$type]);
+      if (!$language_types[$type] && empty($info['fixed'])) {
+        // If the language is not configurable and there is no default language
+        // negotiation setting then provide one.
+        $method_weights = array(LANGUAGE_NEGOTIATION_INTERFACE);
+        $method_weights = array_flip($method_weights);
+        language_negotiation_set($type, $method_weights);
+      }
     }
   }
 
   // Save enabled language types.
   variable_set('language_types', $language_types);
+  config('system.language.types')->set('configurable', $configurable)->save();
 
   // Ensure that subsequent calls of language_types_get_configurable() return
   // the updated language type information.
@@ -365,7 +377,7 @@ function language_negotiation_set($type, $method_weights) {
 
   $negotiation = array();
   $negotiation_info = language_negotiation_info();
-  $default_types = language_types_get_configurable(FALSE);
+  $default_types = language_types_get_configurable();
 
   // Order the language negotiation method list by weight.
   asort($method_weights);
diff --git a/core/modules/aggregator/aggregator.admin.inc b/core/modules/aggregator/aggregator.admin.inc
index 9b8d4ff..e7e2300 100644
--- a/core/modules/aggregator/aggregator.admin.inc
+++ b/core/modules/aggregator/aggregator.admin.inc
@@ -7,6 +7,8 @@
 
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 use Drupal\aggregator\Plugin\Core\Entity\Feed;
+use Guzzle\Http\Exception\RequestException;
+use Guzzle\Http\Exception\BadResponseException;
 
 /**
  * Page callback: Displays the aggregator administration page.
@@ -96,6 +98,176 @@ function aggregator_view() {
 }
 
 /**
+ * Form constructor for importing feeds from OPML.
+ *
+ * @ingroup forms
+ * @see aggregator_menu()
+ * @see aggregator_form_opml_validate()
+ * @see aggregator_form_opml_submit()
+ */
+function aggregator_form_opml($form, &$form_state) {
+  $period = drupal_map_assoc(array(900, 1800, 3600, 7200, 10800, 21600, 32400, 43200, 64800, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval');
+
+  $form['upload'] = array(
+    '#type' => 'file',
+    '#title' => t('OPML File'),
+    '#description' => t('Upload an OPML file containing a list of feeds to be imported.'),
+  );
+  $form['remote'] = array(
+    '#type' => 'url',
+    '#title' => t('OPML Remote URL'),
+    '#maxlength' => 1024,
+    '#description' => t('Enter the URL of an OPML file. This file will be downloaded and processed only once on submission of the form.'),
+  );
+  $form['refresh'] = array(
+    '#type' => 'select',
+    '#title' => t('Update interval'),
+    '#default_value' => 3600,
+    '#options' => $period,
+    '#description' => t('The length of time between feed updates. Requires a correctly configured <a href="@cron">cron maintenance task</a>.', array('@cron' => url('admin/reports/status'))),
+  );
+  $form['block'] = array('#type' => 'select',
+    '#title' => t('News items in block'),
+    '#default_value' => 5,
+    '#options' => drupal_map_assoc(array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)),
+    '#description' => t("Drupal can make a block with the most recent news items of a feed. You can <a href=\"@block-admin\">configure blocks</a> to be displayed in the sidebar of your page. This setting lets you configure the number of news items to show in a feed's block. If you choose '0' these feeds' blocks will be disabled.", array('@block-admin' => url('admin/structure/block'))),
+  );
+
+  // Handling of categories.
+  $options = array_map('check_plain', db_query("SELECT cid, title FROM {aggregator_category} ORDER BY title")->fetchAllKeyed());
+  if ($options) {
+    $form['category'] = array(
+      '#type' => 'checkboxes',
+      '#title' => t('Categorize news items'),
+      '#options' => $options,
+      '#description' => t('New feed items are automatically filed in the checked categories.'),
+    );
+  }
+  $form['actions'] = array('#type' => 'actions');
+  $form['actions']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Import')
+  );
+
+  return $form;
+}
+
+/**
+ * Form validation handler for aggregator_form_opml().
+ *
+ * @see aggregator_form_opml_submit()
+ */
+function aggregator_form_opml_validate($form, &$form_state) {
+  // If both fields are empty or filled, cancel.
+  if (empty($form_state['values']['remote']) == empty($_FILES['files']['name']['upload'])) {
+    form_set_error('remote', t('You must <em>either</em> upload a file or enter a URL.'));
+  }
+}
+
+/**
+ * Form submission handler for aggregator_form_opml().
+ *
+ * @see aggregator_form_opml_validate()
+ */
+function aggregator_form_opml_submit($form, &$form_state) {
+  $data = '';
+  $validators = array('file_validate_extensions' => array('opml xml'));
+  if ($file = file_save_upload('upload', $validators, FALSE, 0)) {
+    $data = file_get_contents($file->uri);
+  }
+  else {
+    try {
+      $response = Drupal::httpClient()
+        ->get($form_state['values']['remote'])
+        ->send();
+      $data = $response->getBody(TRUE);
+    }
+    catch (BadResponseException $e) {
+      $response = $e->getResponse();
+      watchdog('aggregator', 'Failed to download OPML file due to "%error".', array('%error' => $response->getStatusCode() . ' ' . $response->getReasonPhrase()), WATCHDOG_WARNING);
+      drupal_set_message(t('Failed to download OPML file due to "%error".', array('%error' => $response->getStatusCode() . ' ' . $response->getReasonPhrase())));
+      return;
+    }
+    catch (RequestException $e) {
+      watchdog('aggregator', 'Failed to download OPML file due to "%error".', array('%error' => $e->getMessage()), WATCHDOG_WARNING);
+      drupal_set_message(t('Failed to download OPML file due to "%error".', array('%error' => $e->getMessage())));
+      return;
+    }
+  }
+
+  $feeds = _aggregator_parse_opml($data);
+  if (empty($feeds)) {
+    drupal_set_message(t('No new feed has been added.'));
+    return;
+  }
+
+  foreach ($feeds as $feed) {
+    // Ensure URL is valid.
+    if (!valid_url($feed['url'], TRUE)) {
+      drupal_set_message(t('The URL %url is invalid.', array('%url' => $feed['url'])), 'warning');
+      continue;
+    }
+
+    // Check for duplicate titles or URLs.
+    $result = db_query("SELECT title, url FROM {aggregator_feed} WHERE title = :title OR url = :url", array(':title' => $feed['title'], ':url' => $feed['url']));
+    foreach ($result as $old) {
+      if (strcasecmp($old->title, $feed['title']) == 0) {
+        drupal_set_message(t('A feed named %title already exists.', array('%title' => $old->title)), 'warning');
+        continue 2;
+      }
+      if (strcasecmp($old->url, $feed['url']) == 0) {
+        drupal_set_message(t('A feed with the URL %url already exists.', array('%url' => $old->url)), 'warning');
+        continue 2;
+      }
+    }
+
+    $new_feed = entity_create('aggregator_feed', array(
+      'title' => $feed['title'],
+      'url' => $feed['url'],
+      'refresh' => $form_state['values']['refresh'],
+      'block' => $form_state['values']['block'],
+    ));
+    $new_feed->categories = $form_state['values']['category'];
+    $new_feed->save();
+  }
+
+  $form_state['redirect'] = 'admin/config/services/aggregator';
+}
+
+/**
+ * Parses an OPML file.
+ *
+ * Feeds are recognized as <outline> elements with the attributes "text" and
+ * "xmlurl" set.
+ *
+ * @param $opml
+ *   The complete contents of an OPML document.
+ *
+ * @return
+ *   An array of feeds, each an associative array with a "title" and a "url"
+ *   element, or NULL if the OPML document failed to be parsed. An empty array
+ *   will be returned if the document is valid but contains no feeds, as some
+ *   OPML documents do.
+ */
+function _aggregator_parse_opml($opml) {
+  $feeds = array();
+  $xml_parser = drupal_xml_parser_create($opml);
+  if (xml_parse_into_struct($xml_parser, $opml, $values)) {
+    foreach ($values as $entry) {
+      if ($entry['tag'] == 'OUTLINE' && isset($entry['attributes'])) {
+        $item = $entry['attributes'];
+        if (!empty($item['XMLURL']) && !empty($item['TEXT'])) {
+          $feeds[] = array('title' => $item['TEXT'], 'url' => $item['XMLURL']);
+        }
+      }
+    }
+  }
+  xml_parser_free($xml_parser);
+
+  return $feeds;
+}
+
+/**
  * Page callback: Refreshes a feed, then redirects to the overview page.
  *
  * @param \Drupal\aggregator\Plugin\Core\Entity\Feed $feed
diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module
index 9755428..b544908 100644
--- a/core/modules/aggregator/aggregator.module
+++ b/core/modules/aggregator/aggregator.module
@@ -112,8 +112,11 @@ function aggregator_menu() {
   );
   $items['admin/config/services/aggregator/add/opml'] = array(
     'title' => 'Import OPML',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('aggregator_form_opml'),
+    'access arguments' => array('administer news feeds'),
     'type' => MENU_LOCAL_ACTION,
-    'route_name' => 'aggregator_opml_add',
+    'file' => 'aggregator.admin.inc',
   );
   $items['admin/config/services/aggregator/remove/%aggregator_feed'] = array(
     'title' => 'Remove items',
diff --git a/core/modules/aggregator/aggregator.routing.yml b/core/modules/aggregator/aggregator.routing.yml
index d4f1f6c..bf02d5c 100644
--- a/core/modules/aggregator/aggregator.routing.yml
+++ b/core/modules/aggregator/aggregator.routing.yml
@@ -25,10 +25,3 @@ aggregator_feed_add:
     _controller: '\Drupal\aggregator\Routing\AggregatorController::feedAdd'
   requirements:
     _permission: 'administer news feeds'
-
-aggregator_opml_add:
-  pattern: '/admin/config/services/aggregator/add/opml'
-  defaults:
-    _form: '\Drupal\aggregator\Form\OpmlFeedAdd'
-  requirements:
-    _permission: 'administer news feeds'
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Form/OpmlFeedAdd.php b/core/modules/aggregator/lib/Drupal/aggregator/Form/OpmlFeedAdd.php
deleted file mode 100644
index 7e1121e..0000000
--- a/core/modules/aggregator/lib/Drupal/aggregator/Form/OpmlFeedAdd.php
+++ /dev/null
@@ -1,267 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\aggregator\Form\OpmlFeedAdd.
- */
-
-namespace Drupal\aggregator\Form;
-
-use Drupal\Core\ControllerInterface;
-use Drupal\Core\Database\Connection;
-use Drupal\Core\Entity\EntityManager;
-use Drupal\Core\Entity\Query\QueryFactory;
-use Drupal\Core\Form\FormInterface;
-use Symfony\Component\DependencyInjection\ContainerInterface;
-use Guzzle\Http\Exception\RequestException;
-use Guzzle\Http\Exception\BadResponseException;
-use Guzzle\Http\Client;
-
-/**
- * Imports feeds from OPML.
- */
-class OpmlFeedAdd implements ControllerInterface, FormInterface {
-
-  /**
-   * The database connection object.
-   *
-   * @var \Drupal\Core\Database\Connection
-   */
-  protected $database;
-
-  /**
-   * The entity query factory object.
-   *
-   * @var \Drupal\Core\Entity\Query\QueryFactory
-   */
-  protected $queryFactory;
-
-  /**
-   * The entity manager.
-   *
-   * @var \Drupal\Core\Entity\EntityManager
-   */
-  protected $entityManager;
-
-  /**
-   * The HTTP client to fetch the feed data with.
-   *
-   * @var \Guzzle\Http\Client
-   */
-  protected $httpClient;
-
-  /**
-   * Constructs a database object.
-   *
-   * @param \Drupal\Core\Database\Connection; $database
-   *   The database object.
-   * @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
-   *   The entity query object.
-   * @param \Drupal\Core\Entity\EntityManager $entity_manager
-   *   The entity manager.
-   * @param \Guzzle\Http\Client
-   *   The Guzzle HTTP client.
-   */
-  public function __construct(Connection $database, QueryFactory $query_factory, EntityManager $entity_manager, Client $http_client) {
-    $this->database = $database;
-    $this->queryFactory = $query_factory;
-    $this->entityManager = $entity_manager;
-    $this->httpClient = $http_client;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public static function create(ContainerInterface $container) {
-    return new static(
-      $container->get('database'),
-      $container->get('entity.query'),
-      $container->get('plugin.manager.entity'),
-      $container->get('http_default_client')
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getFormID() {
-    return 'aggregator_opml_add';
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function buildForm(array $form, array &$form_state) {
-    $period = drupal_map_assoc(array(900, 1800, 3600, 7200, 10800, 21600, 32400, 43200,
-      64800, 86400, 172800, 259200, 604800, 1209600, 2419200), 'format_interval');
-
-    $form['upload'] = array(
-      '#type' => 'file',
-      '#title' => t('OPML File'),
-      '#description' => t('Upload an OPML file containing a list of feeds to be imported.'),
-    );
-    $form['remote'] = array(
-      '#type' => 'url',
-      '#title' => t('OPML Remote URL'),
-      '#maxlength' => 1024,
-      '#description' => t('Enter the URL of an OPML file. This file will be downloaded and processed only once on submission of the form.'),
-    );
-    $form['refresh'] = array(
-      '#type' => 'select',
-      '#title' => t('Update interval'),
-      '#default_value' => 3600,
-      '#options' => $period,
-      '#description' => t('The length of time between feed updates. Requires a correctly configured <a href="@cron">cron maintenance task</a>.', array('@cron' => url('admin/reports/status'))),
-    );
-    $form['block'] = array(
-      '#type' => 'select',
-      '#title' => t('News items in block'),
-      '#default_value' => 5,
-      '#options' => drupal_map_assoc(array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)),
-      '#description' => t("Drupal can make a block with the most recent news items of a feed. You can <a href=\"@block-admin\">configure blocks</a> to be displayed in the sidebar of your page. This setting lets you configure the number of news items to show in a feed's block. If you choose '0' these feeds' blocks will be disabled.", array('@block-admin' => url('admin/structure/block'))),
-    );
-
-    // Handling of categories.
-    $options = array_map('check_plain', $this->database->query("SELECT cid, title FROM {aggregator_category} ORDER BY title")->fetchAllKeyed());
-    if ($options) {
-      $form['category'] = array(
-        '#type' => 'checkboxes',
-        '#title' => t('Categorize news items'),
-        '#options' => $options,
-        '#description' => t('New feed items are automatically filed in the checked categories.'),
-      );
-    }
-    $form['actions'] = array('#type' => 'actions');
-    $form['actions']['submit'] = array(
-      '#type' => 'submit',
-      '#value' => t('Import'),
-    );
-
-    return $form;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function validateForm(array &$form, array &$form_state) {
-    // If both fields are empty or filled, cancel.
-    if (empty($form_state['values']['remote']) == empty($_FILES['files']['name']['upload'])) {
-      form_set_error('remote', t('You must <em>either</em> upload a file or enter a URL.'));
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function submitForm(array &$form, array &$form_state) {
-    $data = '';
-    $validators = array('file_validate_extensions' => array('opml xml'));
-    if ($file = file_save_upload('upload', $validators, FALSE, 0)) {
-      $data = file_get_contents($file->uri);
-    }
-    else {
-      // @todo Move this to a fetcher implementation.
-      try {
-        $response = $this->httpClient->get($form_state['values']['remote'])->send();
-        $data = $response->getBody(TRUE);
-      }
-      catch (BadResponseException $e) {
-        $response = $e->getResponse();
-        watchdog('aggregator', 'Failed to download OPML file due to "%error".', array('%error' => $response->getStatusCode() . ' ' . $response->getReasonPhrase()), WATCHDOG_WARNING);
-        drupal_set_message(t('Failed to download OPML file due to "%error".', array('%error' => $response->getStatusCode() . ' ' . $response->getReasonPhrase())));
-        return;
-      }
-      catch (RequestException $e) {
-        watchdog('aggregator', 'Failed to download OPML file due to "%error".', array('%error' => $e->getMessage()), WATCHDOG_WARNING);
-        drupal_set_message(t('Failed to download OPML file due to "%error".', array('%error' => $e->getMessage())));
-        return;
-      }
-    }
-
-    $feeds = $this->parseOpml($data);
-    if (empty($feeds)) {
-      drupal_set_message(t('No new feed has been added.'));
-      return;
-    }
-
-    // @todo Move this functionality to a processor.
-    foreach ($feeds as $feed) {
-      // Ensure URL is valid.
-      if (!valid_url($feed['url'], TRUE)) {
-        drupal_set_message(t('The URL %url is invalid.', array('%url' => $feed['url'])), 'warning');
-        continue;
-      }
-
-      // Check for duplicate titles or URLs.
-      $query = $this->queryFactory->get('aggregator_feed');
-      $condition = $query->orConditionGroup()
-        ->condition('title', $feed['title'])
-        ->condition('url', $feed['url']);
-      $ids = $query
-        ->condition($condition)
-        ->execute();
-      $result = $this->entityManager
-        ->getStorageController('aggregator_feed')
-        ->load($ids);
-      foreach ($result as $old) {
-        if (strcasecmp($old->label(), $feed['title']) == 0) {
-          drupal_set_message(t('A feed named %title already exists.', array('%title' => $old->label())), 'warning');
-          continue 2;
-        }
-        if (strcasecmp($old->url->value, $feed['url']) == 0) {
-          drupal_set_message(t('A feed with the URL %url already exists.', array('%url' => $old->url->value)), 'warning');
-          continue 2;
-        }
-      }
-
-      $new_feed = $this->entityManager
-        ->getStorageController('aggregator_feed')
-        ->create(array(
-          'title' => $feed['title'],
-          'url' => $feed['url'],
-          'refresh' => $form_state['values']['refresh'],
-          'block' => $form_state['values']['block'],
-        ));
-      $new_feed->categories = $form_state['values']['category'];
-      $new_feed->save();
-    }
-
-    $form_state['redirect'] = 'admin/config/services/aggregator';
-  }
-
-  /**
-   * Parses an OPML file.
-   *
-   * Feeds are recognized as <outline> elements with the attributes "text" and
-   * "xmlurl" set.
-   *
-   * @todo Move this functionality to a parser.
-   *
-   * @param $opml
-   *   The complete contents of an OPML document.
-   *
-   * @return
-   *   An array of feeds, each an associative array with a "title" and a "url"
-   *   element, or NULL if the OPML document failed to be parsed. An empty array
-   *   will be returned if the document is valid but contains no feeds, as some
-   *   OPML documents do.
-   */
-  protected function parseOpml($opml) {
-    $feeds = array();
-    $xml_parser = drupal_xml_parser_create($opml);
-    if (xml_parse_into_struct($xml_parser, $opml, $values)) {
-      foreach ($values as $entry) {
-        if ($entry['tag'] == 'OUTLINE' && isset($entry['attributes'])) {
-          $item = $entry['attributes'];
-          if (!empty($item['XMLURL']) && !empty($item['TEXT'])) {
-            $feeds[] = array('title' => $item['TEXT'], 'url' => $item['XMLURL']);
-          }
-        }
-      }
-    }
-    xml_parser_free($xml_parser);
-
-    return $feeds;
-  }
-
-}
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/FeedParserTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/FeedParserTest.php
index 5f4f9f9..caf2e7d 100644
--- a/core/modules/aggregator/lib/Drupal/aggregator/Tests/FeedParserTest.php
+++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/FeedParserTest.php
@@ -100,6 +100,6 @@ function testInvalidFeed() {
     // Update the feed. Use the UI to be able to check the message easily.
     $this->drupalGet('admin/config/services/aggregator');
     $this->clickLink(t('Update items'));
-    $this->assertRaw(t('The feed from %title seems to be broken because of error', array('%title' => $feed->label())));
+    $this->assertRaw(t('The feed from %title seems to be broken because of error "%error"', array('%title' => $feed->label(), '%error' => "[curl] 6: Couldn't resolve host 'http' [url] /")));
   }
 }
diff --git a/core/modules/language/language.admin.inc b/core/modules/language/language.admin.inc
index f59fb38..94271cc 100644
--- a/core/modules/language/language.admin.inc
+++ b/core/modules/language/language.admin.inc
@@ -357,11 +357,17 @@ function language_negotiation_configure_form() {
   $form = array(
     '#submit' => array('language_negotiation_configure_form_submit'),
     '#theme' => 'language_negotiation_configure_form',
-    '#language_types' => language_types_get_configurable(FALSE),
     '#language_types_info' => language_types_info(),
     '#language_negotiation_info' => language_negotiation_info(),
   );
-
+  $form['#language_types'] = array();
+  $configurable = config('system.language.types')->get('configurable');
+  foreach ($form['#language_types_info'] as $type => $info) {
+    // Show locked language types only if they they are configurable.
+    if (empty($info['locked']) || $configurable[$type]) {
+      $form['#language_types'][] = $type;
+    }
+  }
   foreach ($form['#language_types'] as $type) {
     language_negotiation_configure_form_table($form, $type);
   }
@@ -389,6 +395,19 @@ function language_negotiation_configure_form_table(&$form, $type) {
     '#show_operations' => FALSE,
     'weight' => array('#tree' => TRUE),
   );
+  // Only show configurability checkbox for the unlocked language types.
+  if (empty($info['locked'])) {
+    $configurable = config('system.language.types')->get('configurable');
+    $table_form['configurable'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Customize %language_name language detection to differ from User interface text language detection settings.', array('%language_name' => $info['name'])),
+      '#default_value' => !empty($configurable[$type]),
+      '#attributes' => array('class' => array('language-customization-checkbox')),
+      '#attached' => array(
+        'js' => array(drupal_get_path('module', 'language') . '/language.admin.js' => array('type' => 'file')),
+      ),
+    );
+  }
 
   $negotiation_info = $form['#language_negotiation_info'];
   $enabled_methods = variable_get("language_negotiation_$type", array());
@@ -519,12 +538,13 @@ function theme_language_negotiation_configure_form($variables) {
       'rows' => $rows,
       'attributes' => array('id' => "language-negotiation-methods-$type"),
     );
-    $table  = theme('table', $variables);
+    $table  = drupal_render($form[$type]['configurable']);
+    $table .= theme('table', $variables);
     $table .= drupal_render_children($form[$type]);
 
     drupal_add_tabledrag("language-negotiation-methods-$type", 'order', 'sibling', "language-method-weight-$type");
 
-    $output .= '<div class="form-item">' . $title . $description . $table . '</div>';
+    $output .= '<div class="form-item table-' . $type . '-wrapper">' . $title . $description . $table . '</div>';
   }
 
   $output .= drupal_render_children($form);
@@ -537,11 +557,16 @@ function theme_language_negotiation_configure_form($variables) {
 function language_negotiation_configure_form_submit($form, &$form_state) {
   $configurable_types = $form['#language_types'];
 
+  $configurable = config('system.language.types')->get('configurable');
+
   foreach ($configurable_types as $type) {
     $method_weights = array();
     $enabled_methods = $form_state['values'][$type]['enabled'];
     $enabled_methods[LANGUAGE_NEGOTIATION_SELECTED] = TRUE;
     $method_weights_input = $form_state['values'][$type]['weight'];
+    if (isset($form_state['values'][$type]['configurable'])) {
+      $configurable[$type] = !empty($form_state['values'][$type]['configurable']);
+    }
 
     foreach ($method_weights_input as $method_id => $weight) {
       if ($enabled_methods[$method_id]) {
@@ -555,7 +580,13 @@ function language_negotiation_configure_form_submit($form, &$form_state) {
 
   // Update non-configurable language types and the related language negotiation
   // configuration.
-  language_types_set();
+  language_types_set($configurable);
+
+  // Clear block definitions cache since the available blocks and their names
+  // may have been changed based on the configurable types.
+  if (module_exists('block')) {
+    drupal_container()->get('plugin.manager.block')->clearCachedDefinitions();
+  }
 
   $form_state['redirect'] = 'admin/config/regional/language/detection';
   drupal_set_message(t('Language negotiation configuration saved.'));
diff --git a/core/modules/language/language.admin.js b/core/modules/language/language.admin.js
new file mode 100644
index 0000000..68e4d3e
--- /dev/null
+++ b/core/modules/language/language.admin.js
@@ -0,0 +1,35 @@
+(function ($) {
+
+"use strict";
+
+/**
+ * Makes language negotiation inherit user interface negotiation.
+ */
+Drupal.behaviors.negotiationLanguage = {
+  attach: function (context) {
+    var $context = $(context);
+    // Given a customization checkbox derive the language type being changed.
+    var toggleTable = function ($checkbox) {
+      // Get the language detection type such as User interface text language
+      // detection or Content language detection.
+      var detectiontype = $checkbox.attr('name').replace('[configurable]', '');
+      var $table = $('.table-' + detectiontype + '-wrapper');
+      if ($checkbox.is(':checked')) {
+        $table.find('table, .tabledrag-toggle-weight-wrapper').show();
+      }
+      else {
+        $table.find('table, .tabledrag-toggle-weight-wrapper').hide();
+      }
+    };
+    // Bind hide/show + rearrange to customization checkboxes.
+    $('body').once('negotiation-language-admin-bind').on('click', '#language-negotiation-configure-form :input.language-customization-checkbox', function (e) {
+      toggleTable($(e.target));
+    });
+    // Initially, hide language detection types that are not customized.
+    $('#language-negotiation-configure-form :input.language-customization-checkbox:not(:checked)').each(function (index, element) {
+      toggleTable($(element));
+    });
+  }
+};
+
+})(jQuery);
diff --git a/core/modules/language/language.install b/core/modules/language/language.install
index b16c53b..78d3a36 100644
--- a/core/modules/language/language.install
+++ b/core/modules/language/language.install
@@ -22,7 +22,7 @@ function language_install() {
 
   // Enable URL language detection for each configurable language type.
   require_once DRUPAL_ROOT . '/core/includes/language.inc';
-  foreach (language_types_get_configurable(FALSE) as $type) {
+  foreach (language_types_get_configurable() as $type) {
     language_negotiation_set($type, array(LANGUAGE_NEGOTIATION_URL => 0));
   }
 }
diff --git a/core/modules/language/language.module b/core/modules/language/language.module
index 6bbb5a7..03f04a9 100644
--- a/core/modules/language/language.module
+++ b/core/modules/language/language.module
@@ -607,14 +607,17 @@ function language_language_types_info() {
     LANGUAGE_TYPE_INTERFACE => array(
       'name' => t('User interface text'),
       'description' => t('Order of language detection methods for user interface text. If a translation of user interface text is available in the detected language, it will be displayed.'),
+      'locked' => TRUE,
     ),
     LANGUAGE_TYPE_CONTENT => array(
       'name' => t('Content'),
       'description' => t('Order of language detection methods for content. If a version of content is available in the detected language, it will be displayed.'),
       'fixed' => array(LANGUAGE_NEGOTIATION_INTERFACE),
+      'locked' => TRUE,
     ),
     LANGUAGE_TYPE_URL => array(
       'fixed' => array(LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_URL_FALLBACK),
+      'locked' => TRUE,
     ),
   );
 }
@@ -714,8 +717,9 @@ function language_negotiation_include() {
  * Implements hook_modules_enabled().
  */
 function language_modules_enabled($modules) {
-  language_negotiation_include();
-  language_types_set();
+  // Load configurability options from configuration.
+  $configurable = config('system.language.types')->get('configurable');
+  language_types_set($configurable);
   language_negotiation_purge();
 }
 
diff --git a/core/modules/language/language.negotiation.inc b/core/modules/language/language.negotiation.inc
index ca41c78..4e29e47 100644
--- a/core/modules/language/language.negotiation.inc
+++ b/core/modules/language/language.negotiation.inc
@@ -18,11 +18,6 @@
 const LANGUAGE_NEGOTIATION_BROWSER = 'language-browser';
 
 /**
- * The language is determined using the current interface language.
- */
-const LANGUAGE_NEGOTIATION_INTERFACE = 'language-interface';
-
-/**
  * If no URL language, language is determined using an already detected one.
  */
 const LANGUAGE_NEGOTIATION_URL_FALLBACK = 'language-url-fallback';
diff --git a/core/modules/language/lib/Drupal/language/Plugin/Derivative/LanguageBlock.php b/core/modules/language/lib/Drupal/language/Plugin/Derivative/LanguageBlock.php
index d451b27..f5260f9 100644
--- a/core/modules/language/lib/Drupal/language/Plugin/Derivative/LanguageBlock.php
+++ b/core/modules/language/lib/Drupal/language/Plugin/Derivative/LanguageBlock.php
@@ -38,11 +38,17 @@ public function getDerivativeDefinition($derivative_id, array $base_plugin_defin
   public function getDerivativeDefinitions(array $base_plugin_definition) {
     include_once DRUPAL_ROOT . '/core/includes/language.inc';
     $info = language_types_info();
-    foreach (language_types_get_configurable(FALSE) as $type) {
+    $configurable_types = language_types_get_configurable();
+    foreach ($configurable_types as $type) {
       $this->derivatives[$type] = $base_plugin_definition;
       $this->derivatives[$type]['admin_label'] = t('Language switcher (!type)', array('!type' => $info[$type]['name']));
       $this->derivatives[$type]['cache'] = DRUPAL_NO_CACHE;
     }
+    // If there is just one configurable type then change the title of the
+    // block.
+    if (count($configurable_types) == 1) {
+      $this->derivatives[reset($configurable_types)]['admin_label'] = t('Language switcher');
+    }
     return $this->derivatives;
   }
 
diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageNegotiationInfoTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageNegotiationInfoTest.php
index 8aea763..5357139 100644
--- a/core/modules/language/lib/Drupal/language/Tests/LanguageNegotiationInfoTest.php
+++ b/core/modules/language/lib/Drupal/language/Tests/LanguageNegotiationInfoTest.php
@@ -68,6 +68,7 @@ function testInfoAlterations() {
       $form_field => TRUE,
       $type . '[enabled][' . $test_method_id . ']' => TRUE,
       $test_type . '[enabled][' . $test_method_id . ']' => TRUE,
+      $test_type . '[configurable]' => TRUE,
     );
     $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings'));
 
@@ -157,8 +158,9 @@ protected function languageNegotiationUpdate($op = 'enable') {
    */
   protected function checkFixedLanguageTypes() {
     drupal_static_reset('language_types_info');
+    $configurable = language_types_get_configurable();
     foreach (language_types_info() as $type => $info) {
-      if (isset($info['fixed'])) {
+      if (!in_array($type, $configurable) && isset($info['fixed'])) {
         $negotiation = variable_get("language_negotiation_$type", array());
         $equal = count($info['fixed']) == count($negotiation);
         while ($equal && list($id) = each($negotiation)) {
diff --git a/core/modules/language/tests/language_test/language_test.module b/core/modules/language/tests/language_test/language_test.module
index cb97937..0b98ed5 100644
--- a/core/modules/language/tests/language_test/language_test.module
+++ b/core/modules/language/tests/language_test/language_test.module
@@ -30,6 +30,7 @@ function language_test_language_types_info() {
       ),
       'fixed_test_language_type' => array(
         'fixed' => array('test_language_negotiation_method'),
+        'locked' => TRUE,
       ),
     );
   }
@@ -40,7 +41,7 @@ function language_test_language_types_info() {
  */
 function language_test_language_types_info_alter(array &$language_types) {
   if (state()->get('language_test.content_language_type')) {
-    unset($language_types[LANGUAGE_TYPE_CONTENT]['fixed']);
+    $language_types[LANGUAGE_TYPE_CONTENT]['locked'] = FALSE;
   }
 }
 
diff --git a/core/modules/system/config/system.language.types.yml b/core/modules/system/config/system.language.types.yml
new file mode 100644
index 0000000..a0ed7ac
--- /dev/null
+++ b/core/modules/system/config/system.language.types.yml
@@ -0,0 +1,4 @@
+configurable:
+  language_interface: '1'
+  language_content: '0'
+  language_url: '0'
diff --git a/core/modules/system/language.api.php b/core/modules/system/language.api.php
index 7c7375f..7f5a996 100644
--- a/core/modules/system/language.api.php
+++ b/core/modules/system/language.api.php
@@ -44,10 +44,13 @@ function hook_language_switch_links_alter(array &$links, $type, $path) {
  *   may contain the following elements:
  *   - name: The human-readable language type identifier.
  *   - description: A description of the language type.
+ *   - locked: A boolean indicating if the user can choose wether to configure
+ *     the language type or not using the UI.
  *   - fixed: A fixed array of language negotiation method identifiers to use to
- *     initialize this language. Defining this key makes the language type
- *     non-configurable, so it will always use the specified methods in the
- *     given priority order. Omit to make the language type configurable.
+ *     initialize this language. If locked is set to TRUE and fixed is set, it
+ *     will always use the specified methods in the given priority order. If not
+ *     present and locked is TRUE then LANGUAGE_NEGOTIATION_INTERFACE will be
+ *     used.
  *
  * @see hook_language_types_info_alter()
  * @ingroup language_negotiation
@@ -57,8 +60,10 @@ function hook_language_types_info() {
     'custom_language_type' => array(
       'name' => t('Custom language'),
       'description' => t('A custom language type.'),
+      'locked' => FALSE,
     ),
     'fixed_custom_language_type' => array(
+      'locked' => TRUE,
       'fixed' => array('custom_language_negotiation_method'),
     ),
   );
diff --git a/core/modules/translation_entity/translation_entity.module b/core/modules/translation_entity/translation_entity.module
index e4dca3e..60c59c4 100644
--- a/core/modules/translation_entity/translation_entity.module
+++ b/core/modules/translation_entity/translation_entity.module
@@ -63,9 +63,9 @@ function translation_entity_module_implements_alter(&$implementations, $hook) {
  * Implements hook_language_type_info_alter().
  */
 function translation_entity_language_types_info_alter(array &$language_types) {
-  // Make content language negotiation configurable by removing its predefined
-  // configuration.
-  unset($language_types[LANGUAGE_TYPE_CONTENT]['fixed']);
+  // Make content language negotiation configurable by removing unsetting the
+  // 'locked' flag.
+  $language_types[LANGUAGE_TYPE_CONTENT]['locked'] = FALSE;
 }
 
 /**
