 core/modules/ckeditor/ckeditor.services.yml        |    7 ++
 .../lib/Drupal/ckeditor/Plugin/Editor/CKEditor.php |   94 ++++++++++++++++++--
 core/modules/language/config/language.mappings.yml |    3 +
 3 files changed, 99 insertions(+), 5 deletions(-)

diff --git a/core/modules/ckeditor/ckeditor.services.yml b/core/modules/ckeditor/ckeditor.services.yml
index 5715789..ebd0021 100644
--- a/core/modules/ckeditor/ckeditor.services.yml
+++ b/core/modules/ckeditor/ckeditor.services.yml
@@ -2,3 +2,10 @@ services:
   plugin.manager.ckeditor.plugin:
     class: Drupal\ckeditor\CKEditorPluginManager
     arguments: ['@container.namespaces', '@cache.cache', '@language_manager', '@module_handler']
+  cache.ckeditor:
+    class: Drupal\Core\Cache\CacheBackendInterface
+    tags:
+      - { name: cache.bin }
+    factory_method: get
+    factory_service: cache_factory
+    arguments: [ckeditor]
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/Editor/CKEditor.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/Editor/CKEditor.php
index 1faaa05..931a45e 100644
--- a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/Editor/CKEditor.php
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/Editor/CKEditor.php
@@ -7,8 +7,11 @@
 
 namespace Drupal\ckeditor\Plugin\Editor;
 
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\ckeditor\CKEditorPluginManager;
 use Drupal\Core\Language\Language;
+use Drupal\Core\Language\LanguageManager;
 use Drupal\editor\Plugin\EditorBase;
 use Drupal\editor\Annotation\Editor;
 use Drupal\Core\Annotation\Translation;
@@ -28,6 +31,20 @@
 class CKEditor extends EditorBase implements ContainerFactoryPluginInterface {
 
   /**
+   * The module handler to invoke hooks on.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManager
+   */
+  protected $languageManager;
+
+  /**
    * The CKEditor plugin manager.
    *
    * @var \Drupal\ckeditor\CKEditorPluginManager
@@ -45,17 +62,30 @@ class CKEditor extends EditorBase implements ContainerFactoryPluginInterface {
    *   The plugin implementation definition.
    * @param \Drupal\ckeditor\CKEditorPluginManager $ckeditor_plugin_manager
    *   The CKEditor plugin manager.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler to invoke hooks on.
+   * @param \Drupal\Core\Language\LanguageManager $language_manager
+   *   The language manager.
    */
-  public function __construct(array $configuration, $plugin_id, array $plugin_definition, CKEditorPluginManager $ckeditor_plugin_manager) {
+  public function __construct(array $configuration, $plugin_id, array $plugin_definition, CKEditorPluginManager $ckeditor_plugin_manager, ModuleHandlerInterface $module_handler, LanguageManager $language_manager) {
     parent::__construct($configuration, $plugin_id, $plugin_definition);
     $this->ckeditorPluginManager = $ckeditor_plugin_manager;
+    $this->moduleHandler = $module_handler;
+    $this->languageManager = $language_manager;
   }
 
   /**
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) {
-    return new static($configuration, $plugin_id, $plugin_definition, $container->get('plugin.manager.ckeditor.plugin'));
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('plugin.manager.ckeditor.plugin'),
+      $container->get('module_handler'),
+      $container->get('language_manager')
+    );
   }
 
   /**
@@ -190,8 +220,6 @@ public function settingsFormSubmit(array $form, array &$form_state) {
    * {@inheritdoc}
    */
   public function getJSSettings(EditorEntity $editor) {
-    $language_interface = language(Language::TYPE_INTERFACE);
-
     $settings = array();
 
     // Get the settings for all enabled plugins, even the internal ones.
@@ -201,13 +229,23 @@ public function getJSSettings(EditorEntity $editor) {
       $settings += $plugin->getConfig($editor);
     }
 
+    // Fall back on English if no matching language code was found.
+    $display_langcode = 'en';
+
+    // Map the interface language code to a CKEditor translation.
+    $ckeditor_langcodes = $this->getLangcodes();
+    $language_interface = $this->languageManager->getLanguage(Language::TYPE_INTERFACE);
+    if (isset($ckeditor_langcodes[$language_interface->id])) {
+      $display_langcode = $ckeditor_langcodes[$language_interface->id];
+    }
+
     // Next, set the most fundamental CKEditor settings.
     $external_plugin_files = $this->ckeditorPluginManager->getEnabledPluginFiles($editor);
     $settings += array(
       'toolbar' => $this->buildToolbarJSSetting($editor),
       'contentsCss' => $this->buildContentsCssJSSetting($editor),
       'extraPlugins' => implode(',', array_keys($external_plugin_files)),
-      'language' => $language_interface->id,
+      'language' => $display_langcode,
       // Configure CKEditor to not load styles.js. The StylesCombo plugin will
       // set stylesSet according to the user's settings, if the "Styles" button
       // is enabled. We cannot get rid of this until CKEditor will stop loading
@@ -227,6 +265,52 @@ public function getJSSettings(EditorEntity $editor) {
   }
 
   /**
+   * Returns a list of language codes supported by CKEditor.
+   *
+   * @return array
+   *   An associative array keyed by language codes.
+   */
+  private function getLangcodes() {
+    // Cache the file system based language list calculation because this would
+    // be expensive to calculate all the time. The cache is cleared on core
+    // upgrades which is the only situation the CKEditor file listing should
+    // change.
+    $langcode_cache = cache('ckeditor')->get('langcodes');
+    if (!empty($langcode_cache)) {
+      $langcodes = $langcode_cache->data;
+    }
+    if (empty($langcodes)) {
+      $langcodes = array();
+      // Collect languages included with CKEditor based on file listing.
+      $ckeditor_languages = glob(DRUPAL_ROOT . '/core/assets/vendor/ckeditor/lang/*.js');
+      foreach ($ckeditor_languages as $language_filename) {
+        $langcode = basename($language_filename, '.js');
+        $langcodes[$langcode] = $langcode;
+      }
+      cache('ckeditor')->set('langcodes', $langcodes);
+    }
+
+    // Get language mapping if available to map to Drupal language codes.
+    // This is configurable in the user interface and not expensive to get, so
+    // we don't include it in the cached language list.
+    $language_mappings = $this->moduleHandler->moduleExists('language') ? language_get_browser_drupal_langcode_mappings() : array();
+    foreach ($langcodes as $langcode) {
+      // If this language code is available in a Drupal mapping, use that to
+      // compute a possibility for matching from the Drupal langcode to the
+      // CKEditor langcode.
+      // e.g. CKEditor uses the langcode 'no' for Norwegian, Drupal uses 'nb'.
+      // This would then remove the 'no' => 'no' mapping and replace it with
+      // 'nb' => 'no'. Now Drupal knows which CKEditor translation to load.
+      if (isset($language_mappings[$langcode]) && !isset($langcodes[$language_mappings[$langcode]])) {
+        $langcodes[$language_mappings[$langcode]] = $langcode;
+        unset($langcodes[$langcode]);
+      }
+    }
+
+    return $langcodes;
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function getLibraries(EditorEntity $editor) {
diff --git a/core/modules/language/config/language.mappings.yml b/core/modules/language/config/language.mappings.yml
index e4ef2a1..ede8c9e 100644
--- a/core/modules/language/config/language.mappings.yml
+++ b/core/modules/language/config/language.mappings.yml
@@ -1,5 +1,8 @@
 # Browsers use different language codes to refer to the same languages,
 # these defaults handles the most common cases.
+no: 'nb' # Norwegian
+pt-pt: 'pt' # Portuguese
+zh: 'zh-hans'
 zh-tw: 'zh-hant' # Taiwan Chinese in traditional script
 zh-hk: 'zh-hant' # Hong Kong Chinese in traditional script
 zh-mo: 'zh-hant' # Macao Chinese in traditional script
