diff --git a/core/modules/language/language.negotiation.inc b/core/modules/language/language.negotiation.inc
index 48a8083..0ff02d8 100644
--- a/core/modules/language/language.negotiation.inc
+++ b/core/modules/language/language.negotiation.inc
@@ -82,6 +82,22 @@ function language_from_browser($languages) {
   $browser_langcodes = array();
   if (preg_match_all('@(?<=[, ]|^)([a-zA-Z-]+|\*)(?:;q=([0-9.]+))?(?:$|\s*,\s*)@', trim($_SERVER['HTTP_ACCEPT_LANGUAGE']), $matches, PREG_SET_ORDER)) {
     foreach ($matches as $match) {
+      // Handle Chinese so it maps right, some browsers are sending
+      // wrong langcodes.
+      switch (strtolower($match[1])) {
+        case 'zh-tw': // Taiwan.
+        case 'zh-hk': // Hong Kong.
+        case 'zh-mo': // Macau.
+        case 'zh-cht': // Traditional Chinese.
+          $match[1] = 'zh-hant';
+          break;
+        case 'zh-cn': // China.
+        case 'zh-sg': // Singapore.
+        case 'zh-chs': // Simplified Chinese.
+          $match[1] = 'zh-hans';
+          break;
+      }
+
       // 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.
@@ -102,7 +118,10 @@ function language_from_browser($languages) {
   foreach ($browser_langcodes as $langcode => $qvalue) {
     $generic_tag = strtok($langcode, '-');
     if (!isset($browser_langcodes[$generic_tag])) {
-      $browser_langcodes[$generic_tag] = $qvalue;
+      // 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;
     }
   }
 
diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageBrowserDetectionUnitTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageBrowserDetectionUnitTest.php
index 3586ba8..beb18c5 100644
--- a/core/modules/language/lib/Drupal/language/Tests/LanguageBrowserDetectionUnitTest.php
+++ b/core/modules/language/lib/Drupal/language/Tests/LanguageBrowserDetectionUnitTest.php
@@ -58,6 +58,13 @@ class LanguageBrowserDetectionUnitTest extends UnitTestBase {
       'eh-oh-laa-laa' => new Language(array(
         'langcode' => 'eh-oh-laa-laa',
       )),
+      // Chinese languages.
+      'zh-hans' => new Language(array(
+        'langcode' => 'zh-hans',
+      )),
+      'zh-hant' => new Language(array(
+        'langcode' => 'zh-hant',
+      )),
     );
 
     $test_cases = array(
@@ -66,8 +73,8 @@ class LanguageBrowserDetectionUnitTest extends UnitTestBase {
       'en-US,en,fr-CA,fr,es-MX' => 'en',
       'fr,en' => 'en',
       'en,fr' => 'en',
-      'en-US,fr' => 'en',
-      'fr,en-US' => 'en',
+      'en-US,fr' => 'en-US',
+      'fr,en-US' => 'en-US',
       'fr,fr-CA' => 'fr-CA',
       'fr-CA,fr' => 'fr-CA',
       'fr' => 'fr-CA',
@@ -120,6 +127,21 @@ class LanguageBrowserDetectionUnitTest extends UnitTestBase {
       'de,pl' => FALSE,
       'iecRswK4eh' => FALSE,
       $this->randomName(10) => FALSE,
+
+      // Chinese langcodes.
+      'zh-cn, en-us;q=0.90, en;q=0.80, zh;q=0.70' => 'zh-hans',
+      'zh-tw, en-us;q=0.90, en;q=0.80, zh;q=0.70' => 'zh-hant',
+      'zh-hant, en-us;q=0.90, en;q=0.80, zh;q=0.70' => 'zh-hant',
+      'zh-hans, en-us;q=0.90, en;q=0.80, zh;q=0.70' => 'zh-hans',
+      'zh-cn' => 'zh-hans',
+      'zh-sg' => 'zh-hans',
+      'zh-tw' => 'zh-hant',
+      'zh-hk' => 'zh-hant',
+      'zh-mo' => 'zh-hant',
+      'zh-hans' => 'zh-hans',
+      'zh-hant' => 'zh-hant',
+      'zh-chs' => 'zh-hans',
+      'zh-cht' => 'zh-hant',
     );
 
     foreach ($test_cases as $accept_language => $expected_result) {
