diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 663d53b..1b5bf71 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -2711,6 +2711,8 @@ function get_t() {
  */
 function drupal_language_initialize() {
   drupal_container()->get('language_manager')->init();
+  // This is still assumed by various functions to be loaded.
+  include_once DRUPAL_ROOT . '/core/includes/language.inc';
 }
 
 /**
diff --git a/core/includes/language.inc b/core/includes/language.inc
index 74d5752..57eb996 100644
--- a/core/includes/language.inc
+++ b/core/includes/language.inc
@@ -101,41 +101,6 @@
  * @link http://drupal.org/node/1497272 Language Negotiation API @endlink
  */
 
-/**
- * Chooses a language based on language negotiation method settings.
- *
- * @param $type
- *   The language type key to find the language for.
- *
- * @param $request
- *   The HttpReqeust object representing the current request.
- *
- * @return
- *   The negotiated language object.
- */
-function language_types_initialize($type, $request = NULL) {
-  // Execute the language negotiation methods in the order they were set up and
-  // return the first valid language found.
-  $negotiation = variable_get("language_negotiation_$type", array());
-
-  foreach ($negotiation as $method_id => $method) {
-    // Skip negotiation methods not appropriate for this type.
-    if (isset($method['types']) && !in_array($type, $method['types'])) {
-      continue;
-    }
-    $language = language_negotiation_method_invoke($method_id, $method, $request);
-    if ($language) {
-      // Remember the method ID used to detect the language.
-      $language->method_id = $method_id;
-      return $language;
-    }
-  }
-
-  // If no other language was found use the default one.
-  $language = language_default();
-  $language->method_id = LANGUAGE_NEGOTIATION_SELECTED;
-  return $language;
-}
 
 /**
  * Returns information about all defined language types.
@@ -426,53 +391,6 @@ function language_negotiation_info() {
   return $negotiation_info;
 }
 
-/**
- * Invokes a language negotiation method and caches the results.
- *
- * @param $method_id
- *   The language negotiation method's identifier.
- * @param $method
- *   (optional) An associative array of information about the method to be
- *   invoked (see hook_language_negotiation_info() for details). If not passed
- *   in, it will be loaded through language_negotiation_info().
- *
- * @param $request
- *   (optional) The HttpRequest object representing the current request.
- *
- * @return
- *   A language object representing the language chosen by the method.
- */
-function language_negotiation_method_invoke($method_id, $method = NULL, $request = NULL) {
-  $results = &drupal_static(__FUNCTION__);
-
-  if (!isset($results[$method_id])) {
-    global $user;
-
-    $languages = language_list();
-
-    if (!isset($method)) {
-      $negotiation_info = language_negotiation_info();
-      $method = $negotiation_info[$method_id];
-    }
-
-    if (isset($method['file'])) {
-      require_once DRUPAL_ROOT . '/' . $method['file'];
-    }
-    // If the language negotiation method has no cache preference or this is
-    // satisfied we can execute the callback.
-    $cache = !isset($method['cache']) || $user->uid || $method['cache'] == variable_get('cache', 0);
-    $callback = isset($method['callbacks']['negotiation']) ? $method['callbacks']['negotiation'] : FALSE;
-    $langcode = $cache && function_exists($callback) ? $callback($languages, $request) : FALSE;
-    $results[$method_id] = isset($languages[$langcode]) ? $languages[$langcode] : FALSE;
-  }
-
-  // Since objects are resources, we need to return a clone to prevent the
-  // language negotiation method cache from being unintentionally altered. The
-  // same methods might be used with different language types based on
-  // configuration.
-  return !empty($results[$method_id]) ? clone($results[$method_id]) : $results[$method_id];
-}
-
  /**
   * Identifies language from configuration.
   *
@@ -492,39 +410,6 @@ function language_from_selected($languages) {
 }
 
 /**
- * Splits the given path into prefix and actual path.
- *
- * Parse the given path and return the language object identified by the prefix
- * and the actual path.
- *
- * @param $path
- *   The path to split.
- * @param $languages
- *   An array of valid languages.
- *
- * @return
- *   An array composed of:
- *    - A language object corresponding to the identified prefix on success,
- *      FALSE otherwise.
- *    - The path without the prefix on success, the given path otherwise.
- */
-function language_url_split_prefix($path, $languages) {
-  $args = empty($path) ? array() : explode('/', $path);
-  $prefix = array_shift($args);
-
-  // Search prefix within enabled languages.
-  $prefixes = language_negotiation_url_prefixes();
-  foreach ($languages as $language) {
-    if (isset($prefixes[$language->langcode]) && $prefixes[$language->langcode] == $prefix) {
-      // Rebuild $path with the language removed.
-      return array($language, implode('/', $args));
-    }
-  }
-
-  return array(FALSE, $path);
-}
-
-/**
  * Returns the possible fallback languages ordered by language weight.
  *
  * @param
diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php
index 035f282..e616fa9 100644
--- a/core/lib/Drupal/Core/CoreBundle.php
+++ b/core/lib/Drupal/Core/CoreBundle.php
@@ -13,6 +13,7 @@
 use Drupal\Core\DependencyInjection\Compiler\RegisterRouteFiltersPass;
 use Drupal\Core\DependencyInjection\Compiler\RegisterRouteEnhancersPass;
 use Drupal\Core\DependencyInjection\Compiler\RegisterParamConvertersPass;
+use Drupal\Core\DependencyInjection\Compiler\RegisterLanguageNegotiationPass;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\DependencyInjection\Reference;
@@ -280,6 +281,8 @@ public function build(ContainerBuilder $container) {
     // Add a compiler pass for upcasting of entity route parameters.
     $container->addCompilerPass(new RegisterParamConvertersPass());
     $container->addCompilerPass(new RegisterRouteEnhancersPass());
+    // Add a compiler pass for registering language negotiators.
+    $container->addCompilerPass(new RegisterLanguageNegotiationPass());
   }
 
   /**
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterLanguageNegotiationPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterLanguageNegotiationPass.php
new file mode 100644
index 0000000..1c2437a
--- /dev/null
+++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterLanguageNegotiationPass.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\DependencyInjection\Compiler\RegisterLanguageNegotiationPass.
+ */
+
+namespace Drupal\Core\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+
+/**
+ * Adds services tagged 'language_negotiation' to the 'language_manager' service.
+ */
+class RegisterLanguageNegotiationPass implements CompilerPassInterface {
+
+  /**
+   * Adds services tagged 'language_negotiation' to the 'language_manager' service.
+   *
+   * @param \Symfony\Component\DependencyInjection\ContainerBuilder $container
+   *  The container to process.
+   */
+  public function process(ContainerBuilder $container) {
+    if (!$container->hasDefinition('language_manager')) {
+      return;
+    }
+    $manager = $container->getDefinition('language_manager');
+    foreach ($container->findTaggedServiceIds('language_negotiation') as $id => $attributes) {
+      $method_id = $attributes[0]['method_id'];
+      $manager->addMethodCall('addNegotiator', array($method_id, new Reference($id)));
+    }
+  }
+}
diff --git a/core/lib/Drupal/Core/Language/LanguageManager.php b/core/lib/Drupal/Core/Language/LanguageManager.php
index fa94205..e65a762 100644
--- a/core/lib/Drupal/Core/Language/LanguageManager.php
+++ b/core/lib/Drupal/Core/Language/LanguageManager.php
@@ -29,6 +29,11 @@ class LanguageManager {
   protected $languages;
 
   /**
+   * An array of language negotiators.
+   */
+  protected $negotiators = array();
+
+  /**
    * Whether or not the language manager has been initialized.
    *
    * @var bool
@@ -89,11 +94,7 @@ public function getLanguage($type) {
     if ($this->isMultilingual() && $this->request) {
       if (!$this->initializing) {
         $this->initializing = TRUE;
-        // @todo Objectify the language system so that we don't have to load an
-        //   include file and call out to procedural code. See
-        //   http://drupal.org/node/1862202
-        include_once DRUPAL_ROOT . '/core/includes/language.inc';
-        $this->languages[$type] = language_types_initialize($type, $this->request);
+        $this->languages[$type] = $this->initializeType($type);
         $this->initializing = FALSE;
       }
       else {
@@ -111,6 +112,65 @@ public function getLanguage($type) {
   }
 
   /**
+   * Adds a language negotiator object to the array of negotiators.
+   */
+  public function addNegotiator($method_id, $negotiator) {
+    $this->negotiators[$method_id] = $negotiator;
+  }
+
+  /**
+   * Initializes the specified language type.
+   */
+  public function initializeType($type) {
+    // Execute the language negotiation methods in the order they were set up and
+    // return the first valid language found.
+    $negotiation = $this->getNegotiationForType($type);
+    foreach ($negotiation as $method_id => $method) {
+      // Skip negotiation methods that have not been registered or that are not
+      // appropriate for this type.
+      if (!isset($this->negotiators[$method_id]) || (isset($method['types']) && !in_array($type, $method['types']))) {
+        continue;
+      }
+
+      if (!isset($this->negotiated[$method_id])) {
+        $this->negotiated[$method_id] = $this->negotiateLanguage($method_id, $method);
+      }
+      // Since objects are references, we need to return a clone to prevent the
+      // language negotiation method cache from being unintentionally altered. The
+      // same methods might be used with different language types based on
+      // configuration.
+      $language = !empty($this->negotiated[$method_id]) ? clone($this->negotiated[$method_id]) : FALSE;
+
+      if ($language) {
+        // Remember the method ID used to detect the language.
+        $language->method_id = $method_id;
+        return $language;
+      }
+    }
+
+    // If no other language was found use the default one.
+    $language = $this->getLanguageDefault();
+    $language->method_id = LanguageNegotiation::LANGUAGE_NEGOTIATION_SELECTED;
+    return $language;
+  }
+
+  /**
+   * Performs language negotiation using the specified negotiation method.
+   */
+  protected function negotiateLanguage($method_id, $method) {
+    global $user;
+    $languages = $this->getLanguageList();
+    // If the language negotiation method has no cache preference or this is
+    // satisfied we can execute the callback.
+    $cache = !isset($method['cache']) || $user->uid || $method['cache'] == variable_get('cache', 0);
+    $langcode = FALSE;
+    if ($cache) {
+      $langcode = $this->negotiators[$method_id]->negotiateLanguage($languages, $this->request);
+    }
+    return isset($languages[$langcode]) ? $languages[$langcode] : FALSE;
+  }
+
+  /**
    * Resets the given language type or all types if none specified.
    *
    * @param string|null $type
@@ -154,7 +214,7 @@ protected function getLanguageTypes() {
    * @return Drupal\Core\Language\Language
    *   A language object.
    */
-  protected function getLanguageDefault() {
+  public function getLanguageDefault() {
     $default_info = variable_get('language_default', array(
       'langcode' => 'en',
       'name' => 'English',
@@ -165,4 +225,15 @@ protected function getLanguageDefault() {
     return new Language($default_info + array('default' => TRUE));
   }
 
+  protected function getLanguageList() {
+    return language_list();
+  }
+
+  /**
+   * Returns the negotiation settings for the specified language type.
+   */
+  protected function getNegotiationForType($type) {
+    return variable_get("language_negotiation_$type", array());
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Language/LanguageNegotiation.php b/core/lib/Drupal/Core/Language/LanguageNegotiation.php
new file mode 100644
index 0000000..a570c7c
--- /dev/null
+++ b/core/lib/Drupal/Core/Language/LanguageNegotiation.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Language\LanguageNegotiation.
+ */
+
+namespace Drupal\Core\Language;
+
+/**
+ *
+ */
+final class LanguageNegotiation {
+
+  /**
+   * No language negotiation. The default language is used.
+   */
+  const LANGUAGE_NEGOTIATION_SELECTED = 'language-selected';
+
+}
diff --git a/core/lib/Drupal/Core/Language/LanguageNegotiationInterface.php b/core/lib/Drupal/Core/Language/LanguageNegotiationInterface.php
new file mode 100644
index 0000000..0a766ac
--- /dev/null
+++ b/core/lib/Drupal/Core/Language/LanguageNegotiationInterface.php
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Language\LanguageNegotiationInterface.
+ */
+
+namespace Drupal\Core\Language;
+
+/**
+ * Interface for language negotiation classes.
+ */
+interface LanguageNegotiationInterface {
+
+  public function getTypes();
+
+  /**
+   * Performs language negotiation.
+   */
+  public function negotiateLanguage($languages = array(), $request = NULL);
+
+}
diff --git a/core/modules/language/language.negotiation.inc b/core/modules/language/language.negotiation.inc
index 9521247..33cd8b8 100644
--- a/core/modules/language/language.negotiation.inc
+++ b/core/modules/language/language.negotiation.inc
@@ -53,328 +53,6 @@
 const LANGUAGE_NEGOTIATION_URL_DOMAIN = 'domain';
 
 /**
- * Identifies the language from the current interface language.
- *
- * @return
- *   The current interface language code.
- */
-function language_from_interface() {
-  return language(LANGUAGE_TYPE_INTERFACE)->langcode;
-}
-
-/**
- * Identify language from the Accept-language HTTP header we got.
- *
- * The algorithm works as follows:
- * - map browser language codes to Drupal language codes.
- * - order all browser language codes by qvalue from high to low.
- * - add generic browser language codes if they aren't already specified
- *   but with a slightly lower qvalue.
- * - find the most specific Drupal language code with the highest qvalue.
- * - if 2 or more languages are having the same qvalue, respect the order of
- *   them inside the $languages array.
- *
- * We perform browser accept-language parsing only if page cache is disabled,
- * otherwise we would cache a user-specific preference.
- *
- * @param $languages
- *   An array of language objects for enabled languages ordered by weight.
- *
- * @return
- *   A valid language code on success, FALSE otherwise.
- */
-function language_from_browser($languages) {
-  if (empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
-    return FALSE;
-  }
-
-  // The Accept-Language header contains information about the language
-  // preferences configured in the user's browser / operating system. RFC 2616
-  // (section 14.4) defines the Accept-Language header as follows:
-  //   Accept-Language = "Accept-Language" ":"
-  //                  1#( language-range [ ";" "q" "=" qvalue ] )
-  //   language-range  = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" )
-  // Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5"
-  $browser_langcodes = array();
-  if (preg_match_all('@(?<=[, ]|^)([a-zA-Z-]+|\*)(?:;q=([0-9.]+))?(?:$|\s*,\s*)@', trim($_SERVER['HTTP_ACCEPT_LANGUAGE']), $matches, PREG_SET_ORDER)) {
-    // Load custom mappings to support browsers that are sending non standard
-    // language codes.
-    $mappings = language_get_browser_drupal_langcode_mappings();
-    foreach ($matches as $match) {
-      if ($mappings) {
-        $langcode = strtolower($match[1]);
-        foreach ($mappings as $browser_langcode => $drupal_langcode) {
-          if ($langcode == $browser_langcode) {
-            $match[1] = $drupal_langcode;
-          }
-        }
-      }
-      // We can safely use strtolower() here, tags are ASCII.
-      // RFC2616 mandates that the decimal part is no more than three digits,
-      // so we multiply the qvalue by 1000 to avoid floating point comparisons.
-      $langcode = strtolower($match[1]);
-      $qvalue = isset($match[2]) ? (float) $match[2] : 1;
-      $browser_langcodes[$langcode] = (int) ($qvalue * 1000);
-    }
-  }
-
-  // We should take pristine values from the HTTP headers, but Internet Explorer
-  // from version 7 sends only specific language tags (eg. fr-CA) without the
-  // corresponding generic tag (fr) unless explicitly configured. In that case,
-  // we assume that the lowest value of the specific tags is the value of the
-  // generic language to be as close to the HTTP 1.1 spec as possible.
-  // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 and
-  // http://blogs.msdn.com/b/ie/archive/2006/10/17/accept-language-header-for-internet-explorer-7.aspx
-  asort($browser_langcodes);
-  foreach ($browser_langcodes as $langcode => $qvalue) {
-    // For Chinese languages the generic tag is either zh-hans or zh-hant, so we
-    // need to handle this separately, we can not split $langcode on the
-    // first occurence of '-' otherwise we get a non-existing language zh.
-    // All other languages use a langcode without a '-', so we can safely split
-    // on the first occurence of it.
-    $generic_tag = '';
-    if (strlen($langcode) > 7 && (substr($langcode, 0, 7) == 'zh-hant' || substr($langcode, 0, 7) == 'zh-hans')) {
-      $generic_tag = substr($langcode, 0, 7);
-    }
-    else {
-      $generic_tag = strtok($langcode, '-');
-    }
-    if (!empty($generic_tag) && !isset($browser_langcodes[$generic_tag])) {
-      // Add the generic langcode, but make sure it has a lower qvalue as the
-      // more specific one, so the more specific one gets selected if it's
-      // defined by both the browser and Drupal.
-      $browser_langcodes[$generic_tag] = $qvalue - 0.1;
-    }
-  }
-
-  // Find the enabled language with the greatest qvalue, following the rules of
-  // RFC 2616 (section 14.4). If several languages have the same qvalue, prefer
-  // the one with the greatest weight.
-  $best_match_langcode = FALSE;
-  $max_qvalue = 0;
-  foreach ($languages as $langcode => $language) {
-    // Language tags are case insensitive (RFC2616, sec 3.10).
-    $langcode = strtolower($langcode);
-
-    // If nothing matches below, the default qvalue is the one of the wildcard
-    // language, if set, or is 0 (which will never match).
-    $qvalue = isset($browser_langcodes['*']) ? $browser_langcodes['*'] : 0;
-
-    // Find the longest possible prefix of the browser-supplied language ('the
-    // language-range') that matches this site language ('the language tag').
-    $prefix = $langcode;
-    do {
-      if (isset($browser_langcodes[$prefix])) {
-        $qvalue = $browser_langcodes[$prefix];
-        break;
-      }
-    }
-    while ($prefix = substr($prefix, 0, strrpos($prefix, '-')));
-
-    // Find the best match.
-    if ($qvalue > $max_qvalue) {
-      $best_match_langcode = $language->langcode;
-      $max_qvalue = $qvalue;
-    }
-  }
-
-  return $best_match_langcode;
-}
-
-/**
- * Identify language from the user preferences.
- *
- * @param $languages
- *   An array of valid language objects.
- *
- * @return
- *   A valid language code on success, FALSE otherwise.
- */
-function language_from_user($languages) {
-  // User preference (only for authenticated users).
-  global $user;
-
-  if ($user->uid && !empty($user->preferred_langcode) && isset($languages[$user->preferred_langcode])) {
-    return $user->preferred_langcode;
-  }
-
-  // No language preference from the user.
-  return FALSE;
-}
-
-/**
- * Identifies admin language from the user preferences.
- *
- * @param $languages
- *   An array of valid language objects.
- *
- * @param \Symfony\Component\HttpFoundation\Request|null $request
- *   (optional) The HttpRequest object representing the current request.
- *   Defaults to NULL.
- *
- * @return
- *   A valid language code on success, FALSE otherwise.
- */
-function language_from_user_admin(array $languages, Request $request = NULL) {
-  // User preference (only for authenticated users).
-  global $user;
-
-  $request_path = $request ? urldecode(trim($request->getPathInfo(), '/')) : _current_path();
-  if ($user->uid && !empty($user->preferred_admin_langcode) && isset($languages[$user->preferred_admin_langcode]) && path_is_admin($request_path)) {
-    return $user->preferred_admin_langcode;
-  }
-
-  // No language preference from the user or not on an admin path.
-  return FALSE;
-}
-
-/**
- * Identify language from a request/session parameter.
- *
- * @param $languages
- *   An array of valid language objects.
- *
- * @return
- *   A valid language code on success, FALSE otherwise.
- */
-function language_from_session($languages) {
-  $param = config('language.negotiation')->get('session.parameter');
-
-  // Request parameter: we need to update the session parameter only if we have
-  // an authenticated user.
-  if (isset($_GET[$param]) && isset($languages[$langcode = $_GET[$param]])) {
-    global $user;
-    if ($user->uid) {
-      $_SESSION[$param] = $langcode;
-    }
-    return $langcode;
-  }
-
-  // Session parameter.
-  if (isset($_SESSION[$param])) {
-    return $_SESSION[$param];
-  }
-
-  return FALSE;
-}
-
-/**
- * Identify language via URL prefix or domain.
- *
- * @param $languages
- *   An array of valid language objects.
- *
- * @param \Symfony\Component\HttpFoundation\Request|null $request
- *   (optional) The HttpRequest object representing the current request.
- *   Defaults to NULL.
- *
- * @return
- *   A valid language code on success, FALSE otherwise.
- */
-function language_from_url($languages, Request $request = NULL) {
-  $language_url = FALSE;
-
-  if (!language_negotiation_method_enabled(LANGUAGE_NEGOTIATION_URL) || !$request) {
-    return $language_url;
-  }
-
-  switch (config('language.negotiation')->get('url.source')) {
-    case LANGUAGE_NEGOTIATION_URL_PREFIX:
-
-      $request_path = urldecode(trim($request->getPathInfo(), '/'));
-
-      list($language, $path) = language_url_split_prefix($request_path, $languages);
-      // Store the correct system path, i.e., the request path without the
-      // language prefix.
-      _language_resolved_path($path);
-
-      if ($language !== FALSE) {
-        $language_url = $language->langcode;
-      }
-      break;
-
-    case LANGUAGE_NEGOTIATION_URL_DOMAIN:
-      // Get only the host, not the port.
-      $http_host= $_SERVER['HTTP_HOST'];
-      if (strpos($http_host, ':') !== FALSE) {
-        $http_host_tmp = explode(':', $http_host);
-        $http_host = current($http_host_tmp);
-      }
-      $domains = language_negotiation_url_domains();
-      foreach ($languages as $language) {
-        // Skip the check if the language doesn't have a domain.
-        if (!empty($domains[$language->langcode])) {
-          // Ensure that there is exactly one protocol in the URL when checking
-          // the hostname.
-          $host = 'http://' . str_replace(array('http://', 'https://'), '', $domains[$language->langcode]);
-          $host = parse_url($host, PHP_URL_HOST);
-          if ($http_host == $host) {
-            $language_url = $language->langcode;
-            break;
-          }
-        }
-      }
-      break;
-  }
-
-  return $language_url;
-}
-
-/**
- * Determines the language to be assigned to URLs when none is detected.
- *
- * The language negotiation process has a fallback chain that ends with the
- * default language negotiation method. Each built-in language type has a
- * separate initialization:
- * - Interface language, which is the only configurable one, always gets a valid
- *   value. If no request-specific language is detected, the default language
- *   will be used.
- * - Content language merely inherits the interface language by default.
- * - URL language is detected from the requested URL and will be used to rewrite
- *   URLs appearing in the page being rendered. If no language can be detected,
- *   there are two possibilities:
- *   - If the default language has no configured path prefix or domain, then the
- *     default language is used. This guarantees that (missing) URL prefixes are
- *     preserved when navigating through the site.
- *   - If the default language has a configured path prefix or domain, a
- *     requested URL having an empty prefix or domain is an anomaly that must be
- *     fixed. This is done by introducing a prefix or domain in the rendered
- *     page matching the detected interface language.
- *
- * @param $languages
- *   (optional) An array of valid language objects. This is passed by
- *   language_negotiation_method_invoke() to every language method callback,
- *   but it is not actually needed here. Defaults to NULL.
- *
- * @param $request
- *   (optional) The HttpRequest object representing the current request.
- *
- * @param $language_type
- *   (optional) The language type to fall back to. Defaults to the interface
- *   language.
- *
- * @return
- *   A valid language code.
- */
-function language_url_fallback($language = NULL, $request = NULL, $language_type = LANGUAGE_TYPE_INTERFACE) {
-  $default = language_default();
-  $prefix = (config('language.negotiation')->get('url.source') == LANGUAGE_NEGOTIATION_URL_PREFIX);
-
-  // If the default language is not configured to convey language information,
-  // a missing URL language information indicates that URL language should be
-  // the default one, otherwise we fall back to an already detected language.
-  $domains = language_negotiation_url_domains();
-  $prefixes = language_negotiation_url_prefixes();
-  if (($prefix && empty($prefixes[$default->langcode])) || (!$prefix && empty($domains[$default->langcode]))) {
-    return $default->langcode;
-  }
-  else {
-    $langcode = language($language_type)->langcode;
-    return $langcode;
-  }
-}
-
-/**
  * Return links for the URL language switcher block.
  *
  * Translation links may be provided by other modules.
diff --git a/core/modules/language/lib/Drupal/language/LanguageBundle.php b/core/modules/language/lib/Drupal/language/LanguageBundle.php
new file mode 100644
index 0000000..9e65ff4
--- /dev/null
+++ b/core/modules/language/lib/Drupal/language/LanguageBundle.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\language\LanguageBundle.
+ */
+
+namespace Drupal\language;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\HttpKernel\Bundle\Bundle;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * Register language module's services to the DIC.
+ */
+class LanguageBundle extends Bundle {
+
+  /**
+   * Overrides Symfony\Component\HttpKernel\Bundle\Bundle::build().
+   */
+  public function build(ContainerBuilder $container) {
+    $container->register('language.language_negotiation_url', 'Drupal\language\LanguageNegotiationUrl')
+      ->addArgument(new Reference('config.factory'))
+      ->addTag('language_negotiation', array('method_id' => LanguageNegotiation::LANGUAGE_NEGOTIATION_URL));
+
+    $container->register('language.language_negotiation_session', 'Drupal\language\LanguageNegotiationSession')
+      ->addArgument(new Reference('config.factory'))
+      ->addTag('language_negotiation', array('method_id' => LanguageNegotiation::LANGUAGE_NEGOTIATION_SESSION));
+
+    $container->register('language.language_negotiation_user', 'Drupal\language\LanguageNegotiationUser')
+      ->addTag('language_negotiation', array('method_id' => LanguageNegotiation::LANGUAGE_NEGOTIATION_USER));
+
+    $container->register('language.language_negotiation_browser', 'Drupal\language\LanguageNegotiationBrowser')
+      ->addArgument(new Reference('config.factory'))
+      ->addTag('language_negotiation', array('method_id' => LanguageNegotiation::LANGUAGE_NEGOTIATION_BROWSER));
+
+    $container->register('language.language_negotiation_ui', 'Drupal\language\LanguageNegotiationUI')
+      ->addArgument(new Reference('language_manager'))
+      ->addTag('language_negotiation', array('method_id' => LanguageNegotiation::LANGUAGE_NEGOTIATION_INTERFACE));
+
+    $container->register('language.language_negotiation_url_fallback', 'Drupal\language\LanguageNegotiationUrlFallback')
+      ->addArgument(new Reference('config.factory'))
+      ->addArgument(new Reference('language_manager'))
+      ->addTag('language_negotiation', array('method_id' => LanguageNegotiation::LANGUAGE_NEGOTIATION_URL_FALLBACK));
+
+    $container->register('language.language_negotiation_user_admin', 'Drupal\language\LanguageNegotiationUserAdmin')
+      ->addTag('language_negotiation', array('method_id' => LanguageNegotiation::LANGUAGE_NEGOTIATION_USER_ADMIN));
+  }
+
+}
diff --git a/core/modules/language/lib/Drupal/language/LanguageNegotiation.php b/core/modules/language/lib/Drupal/language/LanguageNegotiation.php
new file mode 100644
index 0000000..c740faa
--- /dev/null
+++ b/core/modules/language/lib/Drupal/language/LanguageNegotiation.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\language\LanguageNegotiation.
+ */
+
+namespace Drupal\language;
+
+/**
+ * language dependency injection container.
+ */
+final class LanguageNegotiation {
+  /**
+   * The language is determined using path prefix or domain.
+   */
+  const LANGUAGE_NEGOTIATION_URL = 'language-url';
+
+  /**
+   * The language is set based on the browser language settings.
+   */
+  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';
+
+  /**
+   * The language is set based on the user language settings.
+   */
+  const LANGUAGE_NEGOTIATION_USER = 'language-user';
+
+  /**
+   * The language is set based on the user admin language settings.
+   */
+  const LANGUAGE_NEGOTIATION_USER_ADMIN = 'language-user-admin';
+
+  /**
+   * The language is set based on the request/session parameters.
+   */
+  const LANGUAGE_NEGOTIATION_SESSION = 'language-session';
+
+  /**
+   * URL language negotiation: use the path prefix as URL language indicator.
+   */
+  const LANGUAGE_NEGOTIATION_URL_PREFIX = 'path_prefix';
+
+  /**
+   * URL language negotiation: use the domain as URL language indicator.
+   */
+  const LANGUAGE_NEGOTIATION_URL_DOMAIN = 'domain';
+
+}
diff --git a/core/modules/language/lib/Drupal/language/LanguageNegotiationBrowser.php b/core/modules/language/lib/Drupal/language/LanguageNegotiationBrowser.php
new file mode 100644
index 0000000..43c7a1f
--- /dev/null
+++ b/core/modules/language/lib/Drupal/language/LanguageNegotiationBrowser.php
@@ -0,0 +1,149 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\language\LanguageNegotiationBrowser.
+ */
+
+namespace Drupal\language;
+
+use Drupal\Core\Config\ConfigFactory;
+use Drupal\Core\Language\LanguageNegotiationInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Class for identifying language from the Accept-language HTTP header we got.
+ *
+ * The algorithm works as follows:
+ * - map browser language codes to Drupal language codes.
+ * - order all browser language codes by qvalue from high to low.
+ * - add generic browser language codes if they aren't already specified
+ *   but with a slightly lower qvalue.
+ * - find the most specific Drupal language code with the highest qvalue.
+ * - if 2 or more languages are having the same qvalue, respect the order of
+ *   them inside the $languages array.
+ *
+ * We perform browser accept-language parsing only if page cache is disabled,
+ * otherwise we would cache a user-specific preference.
+ *
+ */
+class LanguageNegotiationBrowser implements LanguageNegotiationInterface {
+
+
+  protected $config;
+
+  public function __construct(ConfigFactory $config) {
+    $this->config = $config;
+  }
+
+  public function getTypes() {
+    return array();
+  }
+
+  /**
+   * Overrides Drupal\Core\Language\LanguageNegotiationInterface::negotiateLanguage().
+   */
+  public function negotiateLanguage($languages = array(), $request = NULL) {
+    if (!$request || !$request->server->get('HTTP_ACCEPT_LANGUAGE')) {
+      return FALSE;
+    }
+
+    // The Accept-Language header contains information about the language
+    // preferences configured in the user's browser / operating system. RFC 2616
+    // (section 14.4) defines the Accept-Language header as follows:
+    //   Accept-Language = "Accept-Language" ":"
+    //                  1#( language-range [ ";" "q" "=" qvalue ] )
+    //   language-range  = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" )
+    // Samples: "hu, en-us;q=0.66, en;q=0.33", "hu,en-us;q=0.5"
+    $browser_langcodes = array();
+    if (preg_match_all('@(?<=[, ]|^)([a-zA-Z-]+|\*)(?:;q=([0-9.]+))?(?:$|\s*,\s*)@', trim($request->server->get('HTTP_ACCEPT_LANGUAGE')), $matches, PREG_SET_ORDER)) {
+      // Load custom mappings to support browsers that are sending non standard
+      // language codes.
+      $mappings = array();
+      $config = $this->config->get('language.mappings');
+      if (!$config->isNew()) {
+        $mappings = $config->get();
+      }
+
+      foreach ($matches as $match) {
+        if ($mappings) {
+          $langcode = strtolower($match[1]);
+          foreach ($mappings as $browser_langcode => $drupal_langcode) {
+            if ($langcode == $browser_langcode) {
+              $match[1] = $drupal_langcode;
+            }
+          }
+        }
+        // We can safely use strtolower() here, tags are ASCII.
+        // RFC2616 mandates that the decimal part is no more than three digits,
+        // so we multiply the qvalue by 1000 to avoid floating point comparisons.
+        $langcode = strtolower($match[1]);
+        $qvalue = isset($match[2]) ? (float) $match[2] : 1;
+        $browser_langcodes[$langcode] = (int) ($qvalue * 1000);
+      }
+    }
+
+    // We should take pristine values from the HTTP headers, but Internet Explorer
+    // from version 7 sends only specific language tags (eg. fr-CA) without the
+    // corresponding generic tag (fr) unless explicitly configured. In that case,
+    // we assume that the lowest value of the specific tags is the value of the
+    // generic language to be as close to the HTTP 1.1 spec as possible.
+    // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 and
+    // http://blogs.msdn.com/b/ie/archive/2006/10/17/accept-language-header-for-internet-explorer-7.aspx
+    asort($browser_langcodes);
+    foreach ($browser_langcodes as $langcode => $qvalue) {
+      // For Chinese languages the generic tag is either zh-hans or zh-hant, so we
+      // need to handle this separately, we can not split $langcode on the
+      // first occurence of '-' otherwise we get a non-existing language zh.
+      // All other languages use a langcode without a '-', so we can safely split
+      // on the first occurence of it.
+      $generic_tag = '';
+      if (strlen($langcode) > 7 && (substr($langcode, 0, 7) == 'zh-hant' || substr($langcode, 0, 7) == 'zh-hans')) {
+        $generic_tag = substr($langcode, 0, 7);
+      }
+      else {
+        $generic_tag = strtok($langcode, '-');
+      }
+      if (!empty($generic_tag) && !isset($browser_langcodes[$generic_tag])) {
+        // Add the generic langcode, but make sure it has a lower qvalue as the
+        // more specific one, so the more specific one gets selected if it's
+        // defined by both the browser and Drupal.
+        $browser_langcodes[$generic_tag] = $qvalue - 0.1;
+      }
+    }
+
+    // Find the enabled language with the greatest qvalue, following the rules of
+    // RFC 2616 (section 14.4). If several languages have the same qvalue, prefer
+    // the one with the greatest weight.
+    $best_match_langcode = FALSE;
+    $max_qvalue = 0;
+    foreach ($languages as $langcode => $language) {
+      // Language tags are case insensitive (RFC2616, sec 3.10).
+      $langcode = strtolower($langcode);
+
+      // If nothing matches below, the default qvalue is the one of the wildcard
+      // language, if set, or is 0 (which will never match).
+      $qvalue = isset($browser_langcodes['*']) ? $browser_langcodes['*'] : 0;
+
+      // Find the longest possible prefix of the browser-supplied language ('the
+      // language-range') that matches this site language ('the language tag').
+      $prefix = $langcode;
+      do {
+        if (isset($browser_langcodes[$prefix])) {
+          $qvalue = $browser_langcodes[$prefix];
+          break;
+        }
+      }
+      while ($prefix = substr($prefix, 0, strrpos($prefix, '-')));
+
+      // Find the best match.
+      if ($qvalue > $max_qvalue) {
+        $best_match_langcode = $language->langcode;
+        $max_qvalue = $qvalue;
+      }
+    }
+
+    return $best_match_langcode;
+  }
+
+}
diff --git a/core/modules/language/lib/Drupal/language/LanguageNegotiationSession.php b/core/modules/language/lib/Drupal/language/LanguageNegotiationSession.php
new file mode 100644
index 0000000..c130b95
--- /dev/null
+++ b/core/modules/language/lib/Drupal/language/LanguageNegotiationSession.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\language\LanguageNegotiationUrl.
+ */
+
+namespace Drupal\language;
+
+use Drupal\Core\Config\ConfigFactory;
+use Drupal\Core\Language\LanguageNegotiationInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Identify language from a request/session parameter.
+ */
+class LanguageNegotiationSession implements LanguageNegotiationInterface {
+
+
+  protected $config;
+
+  public function __construct(ConfigFactory $config) {
+    $this->config = $config;
+  }
+
+  public function getTypes() {
+    return array();
+  }
+
+  /**
+   * Overrides Drupal\Core\Language\LanguageNegotiationInterface::negotiateLanguage().
+   */
+  public function negotiateLanguage($languages = array(), $request = NULL) {
+    $param = $this->config->get('language.negotiation')->get('session.parameter');
+
+    // Request parameter: we need to update the session parameter only if we have
+    // an authenticated user.
+    $language = $request->query->get($param);
+    if ($language && isset($languages[$langcode])) {
+      global $user;
+      if ($user->uid) {
+        $_SESSION[$param] = $langcode;
+      }
+      return $langcode;
+    }
+
+    // Session parameter.
+    if (isset($_SESSION[$param])) {
+      return $_SESSION[$param];
+    }
+
+    return FALSE;
+  }
+
+}
diff --git a/core/modules/language/lib/Drupal/language/LanguageNegotiationUI.php b/core/modules/language/lib/Drupal/language/LanguageNegotiationUI.php
new file mode 100644
index 0000000..a1dab14
--- /dev/null
+++ b/core/modules/language/lib/Drupal/language/LanguageNegotiationUI.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\language\LanguageNegotiationUI.
+ */
+
+namespace Drupal\language;
+
+use Drupal\Core\Language\LanguageManager;
+use Drupal\Core\Language\LanguageNegotiationInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Class for identifying the language from the current interface language.
+ */
+class LanguageNegotiationUI implements LanguageNegotiationInterface {
+
+  protected $types = array(LANGUAGE_TYPE_CONTENT);
+
+  protected $languageManager;
+
+  public function __construct(LanguageManager $language_manager) {
+    $this->languageManager = $language_manager;
+  }
+
+  public function getTypes() {
+    return $this->types;
+  }
+
+  /**
+   * Overrides Drupal\Core\Language\LanguageNegotiationInterface::negotiateLanguage().
+   */
+  public function negotiateLanguage($languages = array(), $request = NULL) {
+    return $this->languageManager->getLanguage(LANGUAGE_TYPE_INTERFACE)->langcode;
+  }
+
+}
diff --git a/core/modules/language/lib/Drupal/language/LanguageNegotiationUrl.php b/core/modules/language/lib/Drupal/language/LanguageNegotiationUrl.php
new file mode 100644
index 0000000..8333159
--- /dev/null
+++ b/core/modules/language/lib/Drupal/language/LanguageNegotiationUrl.php
@@ -0,0 +1,91 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\language\LanguageNegotiationUrl.
+ */
+
+namespace Drupal\language;
+
+use Drupal\Core\Config\ConfigFactory;
+use Drupal\Core\Language\LanguageNegotiationInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Class for identifying language via URL prefix or domain.
+ */
+class LanguageNegotiationUrl implements LanguageNegotiationInterface {
+
+  protected $config;
+
+  protected $types = array(LANGUAGE_TYPE_CONTENT, LANGUAGE_TYPE_INTERFACE, LANGUAGE_TYPE_URL);
+
+  public function __construct(ConfigFactory $config) {
+    $this->config = $config;
+  }
+
+  public function getTypes() {
+    return $this->types;
+  }
+
+  /**
+   * Overrides Drupal\Core\Language\LanguageNegotiationInterface::negotiateLanguage().
+   */
+  public function negotiateLanguage($languages = array(), $request = NULL) {
+    if (!$request) {
+      return FALSE;
+    }
+
+    $language_url = FALSE;
+    switch ($this->config->get('language.negotiation')->get('url.source')) {
+      case LanguageNegotiation::LANGUAGE_NEGOTIATION_URL_PREFIX:
+
+        $request_path = urldecode(trim($request->getPathInfo(), '/'));
+        $path_args = explode('/', $request_path);
+        $prefix = array_shift($path_args);
+
+        // Search prefix within enabled languages.
+        $prefixes = $this->config->get('language.negotiation')->get('url.prefixes');
+        $negotiated_language = FALSE;
+        foreach ($languages as $language) {
+          if (isset($prefixes[$language->langcode]) && $prefixes[$language->langcode] == $prefix) {
+            // Rebuild $path with the language removed.
+            _language_resolved_path(implode('/', $path_args));
+            $negotiated_language = $language;
+            break;
+          }
+        }
+
+        if ($negotiated_language !== FALSE) {
+          $language_url = $negotiated_language->langcode;
+        }
+        break;
+
+      case LanguageNegotiation::LANGUAGE_NEGOTIATION_URL_DOMAIN:
+        // Get only the host, not the port.
+        $http_host= $_SERVER['HTTP_HOST'];
+        if (strpos($http_host, ':') !== FALSE) {
+          $http_host_tmp = explode(':', $http_host);
+          $http_host = current($http_host_tmp);
+        }
+        $domains = $this->config->get('language.negotiation')->get('url.domains');
+        foreach ($languages as $language) {
+          // Skip the check if the language doesn't have a domain.
+          if (!empty($domains[$language->langcode])) {
+            // Ensure that there is exactly one protocol in the URL when checking
+            // the hostname.
+            $host = 'http://' . str_replace(array('http://', 'https://'), '', $domains[$language->langcode]);
+            $host = parse_url($host, PHP_URL_HOST);
+            if ($http_host == $host) {
+              $language_url = $language->langcode;
+              break;
+            }
+          }
+        }
+        break;
+    }
+
+    return $language_url;
+  }
+
+}
diff --git a/core/modules/language/lib/Drupal/language/LanguageNegotiationUrlFallback.php b/core/modules/language/lib/Drupal/language/LanguageNegotiationUrlFallback.php
new file mode 100644
index 0000000..6dd5d52
--- /dev/null
+++ b/core/modules/language/lib/Drupal/language/LanguageNegotiationUrlFallback.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\language\LanguageNegotiationUrlFallback.
+ */
+
+namespace Drupal\language;
+
+use Drupal\Core\Config\ConfigFactory;
+use Drupal\Core\Language\LanguageManager;
+use Drupal\Core\Language\LanguageNegotiationInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Class that determines the language to be assigned to URLs when none is detected.
+ *
+ * The language negotiation process has a fallback chain that ends with the
+ * default language negotiation method. Each built-in language type has a
+ * separate initialization:
+ * - Interface language, which is the only configurable one, always gets a valid
+ *   value. If no request-specific language is detected, the default language
+ *   will be used.
+ * - Content language merely inherits the interface language by default.
+ * - URL language is detected from the requested URL and will be used to rewrite
+ *   URLs appearing in the page being rendered. If no language can be detected,
+ *   there are two possibilities:
+ *   - If the default language has no configured path prefix or domain, then the
+ *     default language is used. This guarantees that (missing) URL prefixes are
+ *     preserved when navigating through the site.
+ *   - If the default language has a configured path prefix or domain, a
+ *     requested URL having an empty prefix or domain is an anomaly that must be
+ *     fixed. This is done by introducing a prefix or domain in the rendered
+ *     page matching the detected interface language.
+ */
+class LanguageNegotiationUrlFallback implements LanguageNegotiationInterface {
+
+  protected $types = array(LANGUAGE_TYPE_URL);
+
+  protected $languageManager;
+
+  protected $config;
+
+  public function __construct(ConfigFactory $config, LanguageManager $language_manager) {
+    $this->config = $config;
+    $this->languageManager = $language_manager;
+  }
+
+  public function getTypes() {
+    return $this->types;
+  }
+
+  /**
+   * Overrides Drupal\Core\Language\LanguageNegotiationInterface::negotiateLanguage().
+   */
+  public function negotiateLanguage($languages = array(), $request = NULL) {
+    $default = $this->languageManager->getLanguageDefault();
+    $prefix = ($this->config->get('language.negotiation')->get('url.source') == LanguageNegotiation::LANGUAGE_NEGOTIATION_URL_PREFIX);
+
+    // If the default language is not configured to convey language information,
+    // a missing URL language information indicates that URL language should be
+    // the default one, otherwise we fall back to an already detected language.
+    $domains = $this->config->get('language.negotiation')->get('url.domains');
+    $prefixes = $this->config->get('language.negotiation')->get('url.prefixes');
+    if (($prefix && empty($prefixes[$default->langcode])) || (!$prefix && empty($domains[$default->langcode]))) {
+      return $default->langcode;
+    }
+    else {
+      $langcode = $this->languageManager->get($language_type)->langcode;
+      return $langcode;
+    }
+  }
+
+}
diff --git a/core/modules/language/lib/Drupal/language/LanguageNegotiationUser.php b/core/modules/language/lib/Drupal/language/LanguageNegotiationUser.php
new file mode 100644
index 0000000..c843c25
--- /dev/null
+++ b/core/modules/language/lib/Drupal/language/LanguageNegotiationUser.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\language\LanguageNegotiationUrl.
+ */
+
+namespace Drupal\language;
+
+use Drupal\Core\Language\LanguageNegotiationInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Class for identifying language from the user preferences.
+ */
+class LanguageNegotiationUser implements LanguageNegotiationInterface {
+
+
+  public function getTypes() {
+    return array();
+  }
+
+  /**
+   * Overrides Drupal\Core\Language\LanguageNegotiationInterface::negotiateLanguage().
+   */
+  public function negotiateLanguage($languages = array(), $request = NULL) {
+    // User preference (only for authenticated users).
+    global $user;
+
+    if ($user->uid && !empty($user->preferred_langcode) && isset($languages[$user->preferred_langcode])) {
+      return $user->preferred_langcode;
+    }
+
+    // No language preference from the user.
+    return FALSE;
+  }
+
+}
diff --git a/core/modules/language/lib/Drupal/language/LanguageNegotiationUserAdmin.php b/core/modules/language/lib/Drupal/language/LanguageNegotiationUserAdmin.php
new file mode 100644
index 0000000..e7682dc
--- /dev/null
+++ b/core/modules/language/lib/Drupal/language/LanguageNegotiationUserAdmin.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\language\LanguageNegotiationUserAdmin.
+ */
+
+namespace Drupal\language;
+
+use Drupal\Core\Language\LanguageNegotiationInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Identifies admin language from the user preferences.
+ */
+class LanguageNegotiationUserAdmin implements LanguageNegotiationInterface {
+
+  protected $types = array(LANGUAGE_TYPE_INTERFACE);
+
+  public function getTypes() {
+    return $this->types;
+  }
+
+  /**
+   * Overrides Drupal\Core\Language\LanguageNegotiationInterface::negotiateLanguage().
+   */
+  public function negotiateLanguage($languages = array(), $request = NULL) {
+    // User preference (only for authenticated users).
+    global $user;
+
+    $request_path = $request ? urldecode(trim($request->getPathInfo(), '/')) : _current_path();
+    if ($user->uid && !empty($user->preferred_admin_langcode) && isset($languages[$user->preferred_admin_langcode]) && path_is_admin($request_path)) {
+      return $user->preferred_admin_langcode;
+    }
+
+    // No language preference from the user or not on an admin path.
+    return FALSE;
+  }
+
+}
diff --git a/core/modules/language/lib/Drupal/language/Plugin/block/block/LanguageBlock.php b/core/modules/language/lib/Drupal/language/Plugin/block/block/LanguageBlock.php
index ec34971..ae244f6 100644
--- a/core/modules/language/lib/Drupal/language/Plugin/block/block/LanguageBlock.php
+++ b/core/modules/language/lib/Drupal/language/Plugin/block/block/LanguageBlock.php
@@ -37,6 +37,7 @@ public function build() {
     $build = array();
     $path = drupal_is_front_page() ? '<front>' : current_path();
     list($plugin_id, $type) = explode(':', $this->getPluginId());
+    include_once DRUPAL_ROOT . '/core/includes/language.inc';
     $links = language_negotiation_get_switch_links($type, $path);
 
     if (isset($links->links)) {
diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageBrowserDetectionUnitTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageBrowserDetectionUnitTest.php
index 80a835d..74ab98d 100644
--- a/core/modules/language/lib/Drupal/language/Tests/LanguageBrowserDetectionUnitTest.php
+++ b/core/modules/language/lib/Drupal/language/Tests/LanguageBrowserDetectionUnitTest.php
@@ -9,6 +9,8 @@
 
 use Drupal\simpletest\WebTestBase;
 use Drupal\Core\Language\Language;
+use Drupal\language\LanguageNegotiationBrowser;
+use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Test browser language detection.
@@ -154,8 +156,8 @@ function testLanguageFromBrowser() {
     );
 
     foreach ($test_cases as $accept_language => $expected_result) {
-      $_SERVER['HTTP_ACCEPT_LANGUAGE'] = $accept_language;
-      $result = language_from_browser($languages);
+      $request = Request::create('', 'GET', array(), array(), array(), array('HTTP_ACCEPT_LANGUAGE' => $accept_language));
+      $result = drupal_container()->get('language.language_negotiation_browser')->negotiateLanguage($languages, $request);
       $this->assertIdentical($result, $expected_result, format_string("Language selection '@accept-language' selects '@result', result = '@actual'", array('@accept-language' => $accept_language, '@result' => $expected_result, '@actual' => isset($result) ? $result : 'none')));
     }
   }
diff --git a/core/modules/language/tests/language_test/lib/Drupal/language_test/LanguageTestBundle.php b/core/modules/language/tests/language_test/lib/Drupal/language_test/LanguageTestBundle.php
index b711507..c742134 100644
--- a/core/modules/language/tests/language_test/lib/Drupal/language_test/LanguageTestBundle.php
+++ b/core/modules/language/tests/language_test/lib/Drupal/language_test/LanguageTestBundle.php
@@ -22,7 +22,11 @@ public function build(ContainerBuilder $container) {
     // Overrides language_manager class to test domain language negotiation.
     $definition = $container->getDefinition('language_manager');
     $definition->setClass('Drupal\language_test\LanguageTestManager');
+
+    $container->register('language_test.language_negotiation', 'Drupal\language_test\LanguageTestLanguageNegotiation')
+      ->addTag('language_negotiation', array('method_id' => 'test_language_negotiation_method'));
+    $container->register('language_test.language_negotiation_ts', 'Drupal\language_test\LanguageTestLanguageNegotiationTS')
+      ->addTag('language_negotiation', array('method_id' => 'test_language_negotiation_method_ts'));
   }
 
 }
-
diff --git a/core/modules/language/tests/language_test/lib/Drupal/language_test/LanguageTestLanguageNegotiation.php b/core/modules/language/tests/language_test/lib/Drupal/language_test/LanguageTestLanguageNegotiation.php
new file mode 100644
index 0000000..1c237b9
--- /dev/null
+++ b/core/modules/language/tests/language_test/lib/Drupal/language_test/LanguageTestLanguageNegotiation.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\language\LanguageTestLanguageNegotiation.
+ */
+
+namespace Drupal\language_test;
+
+use Drupal\Core\Language\LanguageNegotiationInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Class for identifying language via URL prefix or domain.
+ */
+class LanguageTestLanguageNegotiation implements LanguageNegotiationInterface {
+
+  protected $types = array(LANGUAGE_TYPE_CONTENT, 'test_language_type', 'fixed_test_language_type');
+
+  public function getTypes() {
+    return $this->types;
+  }
+
+  /**
+   * Overrides Drupal\Core\Language\LanguageNegotiationInterface::negotiateLanguage().
+   */
+  public function negotiateLanguage($languages = array(), $request = NULL) {
+    return 'it';
+  }
+
+}
diff --git a/core/modules/language/tests/language_test/lib/Drupal/language_test/LanguageTestLanguageNegotiationTS.php b/core/modules/language/tests/language_test/lib/Drupal/language_test/LanguageTestLanguageNegotiationTS.php
new file mode 100644
index 0000000..f8bfb17
--- /dev/null
+++ b/core/modules/language/tests/language_test/lib/Drupal/language_test/LanguageTestLanguageNegotiationTS.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\language\LanguageTestLanguageNegotiation.
+ */
+
+namespace Drupal\language_test;
+
+use Drupal\Core\Language\LanguageNegotiationInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Class for identifying language via URL prefix or domain.
+ */
+class LanguageTestLanguageNegotiationTS implements LanguageNegotiationInterface {
+
+  protected $types = array('test_language_type');
+
+  public function getTypes() {
+    return $this->types;
+  }
+
+  /**
+   * Overrides Drupal\Core\Language\LanguageNegotiationInterface::negotiateLanguage().
+   */
+  public function negotiateLanguage($languages = array(), $request = NULL) {
+    return 'it';
+  }
+
+}
