From 6f9fc447bb224449d07179743248168e0888a7b6 Mon Sep 17 00:00:00 2001
From: webchick <drupal@webchick.net>
Date: Fri, 29 Aug 2014 22:17:55 -0700
Subject: [PATCH] =?UTF-8?q?Issue=20#2037979=20by=20G=C3=A1bor=20Hojtsy,=20?=
 =?UTF-8?q?Sweetchuck,=20jhodgdon,=20dawehner:=20Fixed=20'Current=20user's?=
 =?UTF-8?q?=20language'=20views=20filter=20label=20is=20named=20very=20mis?=
 =?UTF-8?q?leading.?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../Drupal/Core/Language/LanguageManager.php  | 29 +++++-
 .../Language/LanguageManagerInterface.php     | 21 ++++-
 .../install/views.view.comments_recent.yml    |  4 +-
 .../views.view.test_field_filters.yml         | 10 +-
 .../field/src/Plugin/views/field/Field.php    | 31 +++----
 .../ConfigurableLanguageManagerInterface.php  | 13 +--
 .../config/install/views.view.content.yml     |  4 +-
 .../config/install/views.view.frontpage.yml   |  8 +-
 .../node/src/Tests/Views/NodeLanguageTest.php | 47 ++++++++++
 .../views.view.test_field_filters.yml         | 10 +-
 .../test_views/views.view.test_language.yml   |  4 +-
 .../views.view.test_field_filters.yml         | 14 +--
 .../views.view.test_taxonomy_tid_field.yml    |  2 +-
 .../views/src/Plugin/views/PluginBase.php     | 92 ++++++++++++++++++-
 .../views/argument/LanguageArgument.php       |  2 +-
 .../views/display/DisplayPluginBase.php       | 23 ++---
 .../Plugin/views/filter/LanguageFilter.php    | 14 +--
 core/modules/views/views.api.php              | 12 ++-
 core/modules/views/views.module               | 29 ------
 core/modules/views/views.views_execution.inc  | 16 ++--
 .../views_ui/src/Tests/ViewEditTest.php       |  4 +-
 21 files changed, 261 insertions(+), 128 deletions(-)

diff --git a/core/lib/Drupal/Core/Language/LanguageManager.php b/core/lib/Drupal/Core/Language/LanguageManager.php
index 88e236317d08..d86789e0905f 100644
--- a/core/lib/Drupal/Core/Language/LanguageManager.php
+++ b/core/lib/Drupal/Core/Language/LanguageManager.php
@@ -78,6 +78,31 @@ public function getLanguageTypes() {
     return array(LanguageInterface::TYPE_INTERFACE, LanguageInterface::TYPE_CONTENT, LanguageInterface::TYPE_URL);
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getDefinedLanguageTypesInfo() {
+    // This needs to have the same return value as
+    // language_language_type_info(), so that even if the Language module is
+    // not defined, users of this information, such as the Views module, can
+    // access names and descriptions of the default language types.
+    return array(
+      LanguageInterface::TYPE_INTERFACE => array(
+        'name' => $this->t('User interface text'),
+        'description' => $this->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,
+      ),
+      LanguageInterface::TYPE_CONTENT => array(
+        'name' => $this->t('Content'),
+        'description' => $this->t('Order of language detection methods for content. If a version of content is available in the detected language, it will be displayed.'),
+        'locked' => TRUE,
+      ),
+      LanguageInterface::TYPE_URL => array(
+        'locked' => TRUE,
+      ),
+    );
+  }
+
   /**
    * {@inheritdoc}
    */
@@ -120,7 +145,9 @@ public function getLanguages($flags = LanguageInterface::STATE_CONFIGURABLE) {
     // Add the site's default language if flagged as allowed value.
     if ($flags & LanguageInterface::STATE_SITE_DEFAULT) {
       $default = isset($default) ? $default : $this->getDefaultLanguage();
-      // Rename the default language.
+      // Rename the default language. But we do not want to do this globally,
+      // if we're acting on a global object, so clone the object first.
+      $default = clone $default;
       $default->name = $this->t("Site's default language (@lang_name)", array('@lang_name' => $default->name));
       $filtered_languages['site_default'] = $default;
     }
diff --git a/core/lib/Drupal/Core/Language/LanguageManagerInterface.php b/core/lib/Drupal/Core/Language/LanguageManagerInterface.php
index 1a28f189f44f..0bbf453651cc 100644
--- a/core/lib/Drupal/Core/Language/LanguageManagerInterface.php
+++ b/core/lib/Drupal/Core/Language/LanguageManagerInterface.php
@@ -34,19 +34,32 @@ public function isMultilingual();
    * Returns an array of the available language types.
    *
    * @return array
-   *   An array of language type names.
+   *   An array of language type machine names.
    */
   public function getLanguageTypes();
 
+  /**
+   * Returns information about all defined language types.
+   *
+   * @return array
+   *   An associative array of language type information arrays keyed by
+   *   language type machine name, in the format of
+   *   hook_language_types_info(). In some implementing classes, this is based
+   *   on information from hook_language_types_info() and
+   *   hook_language_types_info_alter().
+   */
+  public function getDefinedLanguageTypesInfo();
+
   /**
    * Returns the current language for the given type.
    *
    * @param string $type
-   *   (optional) The language type, e.g. the interface or the content language.
-   *   Defaults to \Drupal\Core\Language\LanguageInterface::TYPE_INTERFACE.
+   *   (optional) The language type; e.g., the interface or the content
+   *   language. Defaults to
+   *   \Drupal\Core\Language\LanguageInterface::TYPE_INTERFACE.
    *
    * @return \Drupal\Core\Language\LanguageInterface
-   *   A language object for the given type.
+   *   The current language object for the given type of language.
    */
   public function getCurrentLanguage($type = LanguageInterface::TYPE_INTERFACE);
 
diff --git a/core/modules/comment/config/install/views.view.comments_recent.yml b/core/modules/comment/config/install/views.view.comments_recent.yml
index 9023ae7e34a4..7a4b5596fc9c 100644
--- a/core/modules/comment/config/install/views.view.comments_recent.yml
+++ b/core/modules/comment/config/install/views.view.comments_recent.yml
@@ -228,7 +228,7 @@ display:
           content: 'No comments available.'
           tokenize: false
           plugin_id: text_custom
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
   block_1:
     provider: views
@@ -239,7 +239,7 @@ display:
     display_options:
       block_description: 'Recent comments'
       block_category: 'Lists (Views)'
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
       allow:
         items_per_page: true
diff --git a/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_field_filters.yml b/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_field_filters.yml
index 692c5d5db86a..aa730124edb5 100644
--- a/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_field_filters.yml
+++ b/core/modules/comment/tests/modules/comment_test_views/test_views/views.view.test_field_filters.yml
@@ -160,7 +160,7 @@ display:
       footer: {  }
       empty: {  }
       arguments: {  }
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
   page_tc:
     display_plugin: page
@@ -169,7 +169,7 @@ display:
     position: 1
     provider: views
     display_options:
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
       path: test-title-filter
       display_description: ''
@@ -180,7 +180,7 @@ display:
     position: 1
     provider: views
     display_options:
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
       path: test-body-paris
       display_description: ''
@@ -241,7 +241,7 @@ display:
     position: 1
     provider: views
     display_options:
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
       path: test-title-paris
       display_description: ''
@@ -301,7 +301,7 @@ display:
     position: 1
     provider: views
     display_options:
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
       path: test-body-filter
       display_description: ''
diff --git a/core/modules/field/src/Plugin/views/field/Field.php b/core/modules/field/src/Plugin/views/field/Field.php
index a42f4c28edf3..0bd93b23d7a6 100644
--- a/core/modules/field/src/Plugin/views/field/Field.php
+++ b/core/modules/field/src/Plugin/views/field/Field.php
@@ -289,20 +289,18 @@ public function query($use_groupby = FALSE) {
       $this->ensureMyTable();
       $this->addAdditionalFields($fields);
 
-      // Filter by langcode, if field translation is enabled.
+      // If we are grouping by something on this field, we want to group by
+      // the displayed value, which is translated. So, we need to figure out
+      // which language should be used to translate the value. See also
+      // $this->field_langcode().
       $field = $field_definition;
       if ($field->isTranslatable() && !empty($this->view->display_handler->options['field_langcode_add_to_query'])) {
         $column = $this->tableAlias . '.langcode';
-        // By the same reason as field_language the field might be
-        // LanguageInterface::LANGCODE_NOT_SPECIFIED in reality so allow it as
-        // well.
-        // @see this::field_langcode()
-        $default_langcode = language_default()->id;
-        $langcode = str_replace(
-          array('***CURRENT_LANGUAGE***', '***DEFAULT_LANGUAGE***'),
-          array($this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT), $default_langcode),
-          $this->view->display_handler->options['field_langcode']
-        );
+        $langcode = $this->view->display_handler->options['field_langcode'];
+        $substitutions = static::queryLanguageSubstitutions();
+        if (isset($substitutions[$langcode])) {
+          $langcode = $substitutions[$langcode];
+        }
         $placeholder = $this->placeholder();
         $langcode_fallback_candidates = $this->languageManager->getFallbackCandidates(array('langcode' => $langcode, 'operation' => 'views_query', 'data' => $this));
         $this->query->addWhereExpression(0, "$column IN($placeholder) OR $column IS NULL", array($placeholder => $langcode_fallback_candidates));
@@ -917,12 +915,11 @@ protected function addSelfTokens(&$tokens, $item) {
    */
   function field_langcode(EntityInterface $entity) {
     if ($this->getFieldDefinition()->isTranslatable()) {
-      $default_langcode = language_default()->id;
-      $langcode = str_replace(
-        array('***CURRENT_LANGUAGE***', '***DEFAULT_LANGUAGE***'),
-        array($this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->id, $default_langcode),
-        $this->view->display_handler->options['field_langcode']
-      );
+      $langcode = $this->view->display_handler->options['field_langcode'];
+      $substitutions = static::queryLanguageSubstitutions();
+      if (isset($substitutions[$langcode])) {
+        $langcode = $substitutions[$langcode];
+      }
 
       // Give the Entity Field API a chance to fallback to a different language
       // (or LanguageInterface::LANGCODE_NOT_SPECIFIED), in case the field has
diff --git a/core/modules/language/src/ConfigurableLanguageManagerInterface.php b/core/modules/language/src/ConfigurableLanguageManagerInterface.php
index 3b52cd483fe0..f3b2ff216b69 100644
--- a/core/modules/language/src/ConfigurableLanguageManagerInterface.php
+++ b/core/modules/language/src/ConfigurableLanguageManagerInterface.php
@@ -44,21 +44,10 @@ public function setNegotiator(LanguageNegotiatorInterface $negotiator);
    * through the user interface.
    *
    * @return array
-   *   An array of language type names.
+   *   An array of language type machine names.
    */
   public function getDefinedLanguageTypes();
 
-  /**
-   * Returns information about all defined language types.
-   *
-   * @return array
-   *   An associative array of language type information arrays keyed by type
-   *   names. Based on information from hook_language_types_info().
-   *
-   * @see hook_language_types_info()
-   */
-  public function getDefinedLanguageTypesInfo();
-
   /**
    * Stores language types configuration.
    *
diff --git a/core/modules/node/config/install/views.view.content.yml b/core/modules/node/config/install/views.view.content.yml
index 132ffee14934..229fed66941a 100644
--- a/core/modules/node/config/install/views.view.content.yml
+++ b/core/modules/node/config/install/views.view.content.yml
@@ -536,7 +536,7 @@ display:
         operator: AND
         groups:
           1: AND
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
     display_plugin: default
     display_title: Master
@@ -558,7 +558,7 @@ display:
         description: 'Find and manage content'
         menu_name: admin
         weight: -10
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
     display_plugin: page
     display_title: Page
diff --git a/core/modules/node/config/install/views.view.frontpage.yml b/core/modules/node/config/install/views.view.frontpage.yml
index 1e27e9ce40d5..3a3dbcb54655 100644
--- a/core/modules/node/config/install/views.view.frontpage.yml
+++ b/core/modules/node/config/install/views.view.frontpage.yml
@@ -130,7 +130,7 @@ display:
               - views
           operator: in
           value:
-            '***CURRENT_LANGUAGE***': '***CURRENT_LANGUAGE***'
+            '***LANGUAGE_language_content***': '***LANGUAGE_language_content***'
           group: 1
           exposed: false
           expose:
@@ -240,7 +240,7 @@ display:
       relationships: {  }
       fields: {  }
       arguments: {  }
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
     display_plugin: default
     display_title: Master
@@ -249,7 +249,7 @@ display:
   page_1:
     display_options:
       path: node
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
     display_plugin: page
     display_title: Page
@@ -286,5 +286,5 @@ display:
           view_mode: rss
           links: false
         provider: views
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
diff --git a/core/modules/node/src/Tests/Views/NodeLanguageTest.php b/core/modules/node/src/Tests/Views/NodeLanguageTest.php
index 11a17b324450..8300ad07ca7d 100644
--- a/core/modules/node/src/Tests/Views/NodeLanguageTest.php
+++ b/core/modules/node/src/Tests/Views/NodeLanguageTest.php
@@ -184,5 +184,52 @@ public function testLanguages() {
         }
       }
     }
+
+    // Override the config for the front page view, so that the language
+    // filter is set to the site default language instead. This should just
+    // show the English nodes, no matter what the content language is.
+    $config = \Drupal::config('views.view.frontpage');
+    $config->set('display.default.display_options.filters.langcode.value', array('***LANGUAGE_site_default***' => '***LANGUAGE_site_default***'));
+    $config->save();
+    foreach ($this->node_titles as $langcode => $titles) {
+      $this->drupalGet(($langcode == 'en' ? '' : "$langcode/") . 'node');
+      foreach ($this->node_titles as $control_langcode => $control_titles) {
+        foreach ($control_titles as $title) {
+          if ($control_langcode == 'en') {
+            $this->assertText($title, 'English title is shown when filtering is site default');
+          }
+          else {
+            $this->assertNoText($title, 'Non-English title is not shown when filtering is site default');
+          }
+        }
+      }
+    }
+
+    // Override the config so that the language filter is set to the UI
+    // language, and make that have a fixed value of 'es'.
+    //
+    // IMPORTANT: Make sure this part of the test is last -- it is changing
+    // language configuration!
+    $config->set('display.default.display_options.filters.langcode.value', array('***LANGUAGE_language_interface***' => '***LANGUAGE_language_interface***'));
+    $config->save();
+    $language_config = \Drupal::config('language.types');
+    $language_config->set('negotiation.language_interface.enabled', array('language-selected' => 1));
+    $language_config->save();
+    $language_config = \Drupal::config('language.negotiation');
+    $language_config->set('selected_langcode', 'es');
+    $language_config->save();
+
+    // With a fixed language selected, there is no language-based URL.
+    $this->drupalGet('node');
+    foreach ($this->node_titles as $control_langcode => $control_titles) {
+      foreach ($control_titles as $title) {
+        if ($control_langcode == 'es') {
+          $this->assertText($title, 'Spanish title is shown when filtering is fixed UI language');
+        }
+        else {
+          $this->assertNoText($title, 'Non-Spanish title is not shown when filtering is fixed UI language');
+        }
+      }
+    }
   }
 }
diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_field_filters.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_field_filters.yml
index 410622a58f6f..30626e1b61b8 100644
--- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_field_filters.yml
+++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_field_filters.yml
@@ -173,7 +173,7 @@ display:
       empty: {  }
       relationships: {  }
       arguments: {  }
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
   page_tf:
     display_plugin: page
@@ -182,7 +182,7 @@ display:
     position: 1
     provider: views
     display_options:
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
       path: test-title-filter
       display_description: ''
@@ -255,7 +255,7 @@ display:
     position: 1
     provider: views
     display_options:
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
       path: test-body-filter
       display_description: ''
@@ -326,7 +326,7 @@ display:
     position: 1
     provider: views
     display_options:
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
       path: test-body-paris
       display_description: ''
@@ -398,7 +398,7 @@ display:
     position: 1
     provider: views
     display_options:
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
       path: test-title-paris
       display_description: ''
diff --git a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_language.yml b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_language.yml
index 68fb2d53efb8..c21e026f10cf 100644
--- a/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_language.yml
+++ b/core/modules/node/tests/modules/node_test_views/test_views/views.view.test_language.yml
@@ -300,7 +300,7 @@ display:
           validate_options: {  }
           plugin_id: language
           provider: views
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
   page_1:
     display_plugin: page
@@ -309,6 +309,6 @@ display:
     position: 1
     provider: views
     display_options:
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
       path: test-language
diff --git a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_field_filters.yml b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_field_filters.yml
index 372c0f5d7063..664f31652384 100644
--- a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_field_filters.yml
+++ b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_field_filters.yml
@@ -145,7 +145,7 @@ display:
       empty: {  }
       relationships: {  }
       arguments: {  }
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
   page_dc:
     display_plugin: page
@@ -154,7 +154,7 @@ display:
     position: 3
     provider: views
     display_options:
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
       display_description: ''
       path: test-desc-filter
@@ -215,7 +215,7 @@ display:
     position: 3
     provider: views
     display_options:
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
       display_description: ''
       path: test-desc-paris
@@ -277,7 +277,7 @@ display:
     position: 1
     provider: views
     display_options:
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
       path: test-name-filter
       display_description: ''
@@ -288,7 +288,7 @@ display:
     position: 1
     provider: views
     display_options:
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
       path: test-name-paris
       display_description: ''
@@ -348,7 +348,7 @@ display:
     position: 3
     provider: views
     display_options:
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
       display_description: ''
       path: test-field-paris
@@ -410,7 +410,7 @@ display:
     position: 3
     provider: views
     display_options:
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
       display_description: ''
       path: test-field-filter
diff --git a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_tid_field.yml b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_tid_field.yml
index 6f2bf4ffd1bb..ed3e0338cc2c 100644
--- a/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_tid_field.yml
+++ b/core/modules/taxonomy/tests/modules/taxonomy_test_views/test_views/views.view.test_taxonomy_tid_field.yml
@@ -172,5 +172,5 @@ display:
       empty: {  }
       relationships: {  }
       arguments: {  }
-      field_langcode: '***CURRENT_LANGUAGE***'
+      field_langcode: '***LANGUAGE_language_content***'
       field_langcode_add_to_query: null
diff --git a/core/modules/views/src/Plugin/views/PluginBase.php b/core/modules/views/src/Plugin/views/PluginBase.php
index 132ceec8fdf5..b66578bb261e 100644
--- a/core/modules/views/src/Plugin/views/PluginBase.php
+++ b/core/modules/views/src/Plugin/views/PluginBase.php
@@ -2,13 +2,14 @@
 
 /**
  * @file
- * Definition of Drupal\views\Plugin\views\PluginBase.
+ * Contains \Drupal\views\Plugin\views\PluginBase.
  */
 
 namespace Drupal\views\Plugin\views;
 
 use Drupal\Component\Utility\String;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\Plugin\PluginBase as ComponentPluginBase;
 use Drupal\Core\Render\Element;
@@ -43,6 +44,13 @@
  */
 abstract class PluginBase extends ComponentPluginBase implements ContainerFactoryPluginInterface {
 
+  /**
+   * Include negotiated languages when listing languages.
+   *
+   * @see \Drupal\views\Plugin\views\PluginBase::listLanguages()
+   */
+  const INCLUDE_NEGOTIATED = 16;
+
   /**
    * Options for this plugin will be held here.
    *
@@ -439,4 +447,86 @@ public function getDependencies() {
     return array();
   }
 
+  /**
+   * Makes an array of languages, optionally including special languages.
+   *
+   * @param int $flags
+   *   (optional) Flags for which languages to return (additive). Options:
+   *   - \Drupal\Core\Language::STATE_ALL (default): All languages
+   *     (configurable and default).
+   *   - \Drupal\Core\Language::STATE_CONFIGURABLE: Configurable languages.
+   *   - \Drupal\Core\Language::STATE_LOCKED: Locked languages.
+   *   - \Drupal\Core\Language::STATE_SITE_DEFAULT: Add site default language;
+   *     note that this is not included in STATE_ALL.
+   *   - \Drupal\views\Plugin\views\PluginBase::INCLUDE_NEGOTIATED: Add
+   *     negotiated language types.
+   *
+   * @return array
+   *   An array of language names, keyed by the language code. Negotiated and
+   *   special languages have special codes that are substituted in queries by
+   *   static::queryLanguageSubstitutions().
+   */
+  protected function listLanguages($flags = LanguageInterface::STATE_ALL) {
+    $manager = \Drupal::languageManager();
+    $list = array();
+
+    // The Language Manager class takes care of the STATE_SITE_DEFAULT case.
+    // It comes in with ID set to 'site_default'. Since this is not a real
+    // language, surround it by '***LANGUAGE_...***', like the negotiated
+    // languages below.
+    $languages = $manager->getLanguages($flags);
+    foreach ($languages as $id => $language) {
+      if ($id == 'site_default') {
+        $id = '***LANGUAGE_' . $id . '***';
+      }
+      $list[$id] = t($language->name);
+    }
+
+    // Add in negotiated languages, if requested.
+    if ($flags & PluginBase::INCLUDE_NEGOTIATED) {
+      $types = $manager->getDefinedLanguageTypesInfo();
+      foreach ($types as $id => $type) {
+        // Omit unnamed types. These are things like language_url, which are
+        // not configurable and do not need to be in this list. And surround
+        // IDs by '***LANGUAGE_...***', to avoid query collisions.
+        if (isset($type['name'])) {
+          $id = '***LANGUAGE_' . $id . '***';
+          $list[$id] = t('Language selected for !type', array('!type' => $type['name']));
+        }
+      }
+    }
+
+    return $list;
+  }
+
+  /**
+   * Returns substitutions for Views queries for languages.
+   *
+   * This is needed so that the language options returned by
+   * $this->listLanguages() are able to be used in queries. It is called
+   * by the Views module implementation of hook_views_query_substitutions()
+   * to get the language-related substitutions.
+   *
+   * @return array
+   *   An array in the format of hook_views_query_substitutions() that gives
+   *   the query substitutions needed for the special language types.
+   */
+  public static function queryLanguageSubstitutions() {
+    $changes = array();
+    $manager = \Drupal::languageManager();
+
+    // Handle default language.
+    $default = $manager->getDefaultLanguage()->id;
+    $changes['***LANGUAGE_site_default***'] = $default;
+
+    // Handle negotiated languages.
+    $types = $manager->getDefinedLanguageTypesInfo();
+    foreach ($types as $id => $type) {
+      if (isset($type['name'])) {
+        $changes['***LANGUAGE_' . $id . '***'] = $manager->getCurrentLanguage($id)->id;
+      }
+    }
+
+    return $changes;
+  }
 }
diff --git a/core/modules/views/src/Plugin/views/argument/LanguageArgument.php b/core/modules/views/src/Plugin/views/argument/LanguageArgument.php
index c73ca6b14906..f6b9687a06f8 100644
--- a/core/modules/views/src/Plugin/views/argument/LanguageArgument.php
+++ b/core/modules/views/src/Plugin/views/argument/LanguageArgument.php
@@ -46,7 +46,7 @@ function title() {
    *   language was not found.
    */
   function language($langcode) {
-    $languages = views_language_list();
+    $languages = $this->listLanguages();
     return isset($languages[$langcode]) ? $languages[$langcode] : t('Unknown language');
   }
 
diff --git a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
index 31f30bc5f66c..7d3232f84097 100644
--- a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
+++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
@@ -579,7 +579,7 @@ protected function defineOptions() {
         'bool' => TRUE,
       ),
       'field_langcode' => array(
-        'default' => '***CURRENT_LANGUAGE***',
+        'default' => '***LANGUAGE_language_content***',
       ),
       'field_langcode_add_to_query' => array(
         'default' => TRUE,
@@ -1245,19 +1245,13 @@ public function optionsSummary(&$categories, &$options) {
       'desc' => t('Allow to set some advanced settings for the query plugin'),
     );
 
-    $languages = array(
-        '***CURRENT_LANGUAGE***' => t("Current user's language"),
-        '***DEFAULT_LANGUAGE***' => t("Default site language"),
-        LanguageInterface::LANGCODE_NOT_SPECIFIED => t('Language neutral'),
-    );
-    if (\Drupal::moduleHandler()->moduleExists('language')) {
-      $languages = array_merge($languages, language_list());
-    }
+    $language_options = $this->listLanguages(LanguageInterface::STATE_ALL | LanguageInterface::STATE_SITE_DEFAULT | PluginBase::INCLUDE_NEGOTIATED);
+
     $options['field_langcode'] = array(
       'category' => 'other',
       'title' => t('Field Language'),
-      'value' => $languages[$this->getOption('field_langcode')],
-      'desc' => t('All fields which support translations will be displayed in the selected language.'),
+      'value' => $language_options[$this->getOption('field_langcode')],
+      'desc' => t('All fields that support translations will be displayed in the selected language.'),
     );
 
     $access_plugin = $this->getPlugin('access');
@@ -1619,12 +1613,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
         // an entity base table. Also, we make sure that there's at least one
         // entity type with a translation handler attached.
         if (in_array($this->view->storage->get('base_table'), $translatable_entity_tables)) {
-          $languages = array(
-            '***CURRENT_LANGUAGE***' => t("Current user's language"),
-            '***DEFAULT_LANGUAGE***' => t("Default site language"),
-            LanguageInterface::LANGCODE_NOT_SPECIFIED => t('Language neutral'),
-          );
-          $languages = array_merge($languages, views_language_list());
+          $languages = $this->listLanguages(LanguageInterface::STATE_ALL | LanguageInterface::STATE_SITE_DEFAULT | PluginBase::INCLUDE_NEGOTIATED);
 
           $form['field_langcode'] = array(
             '#type' => 'select',
diff --git a/core/modules/views/src/Plugin/views/filter/LanguageFilter.php b/core/modules/views/src/Plugin/views/filter/LanguageFilter.php
index 8b08f3335f1f..67d6a4f2160d 100644
--- a/core/modules/views/src/Plugin/views/filter/LanguageFilter.php
+++ b/core/modules/views/src/Plugin/views/filter/LanguageFilter.php
@@ -7,6 +7,9 @@
 
 namespace Drupal\views\Plugin\views\filter;
 
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\views\Plugin\views\PluginBase;
+
 /**
  * Provides filtering by language.
  *
@@ -16,16 +19,13 @@
  */
 class LanguageFilter extends InOperator {
 
+  /**
+   * {@inheritdoc}
+   */
   public function getValueOptions() {
     if (!isset($this->value_options)) {
       $this->value_title = t('Language');
-      $languages = array(
-        '***CURRENT_LANGUAGE***' => t("Current user's language"),
-        '***DEFAULT_LANGUAGE***' => t("Default site language"),
-      );
-      $languages = array_merge($languages, views_language_list());
-      $this->value_options = $languages;
+      $this->value_options = $this->listLanguages(LanguageInterface::STATE_ALL |LanguageInterface::STATE_SITE_DEFAULT | PluginBase::INCLUDE_NEGOTIATED);
     }
   }
-
 }
diff --git a/core/modules/views/views.api.php b/core/modules/views/views.api.php
index a54d5629451e..c7e5133e8f81 100644
--- a/core/modules/views/views.api.php
+++ b/core/modules/views/views.api.php
@@ -559,20 +559,26 @@ function hook_field_views_data_views_data_alter(array &$data, \Drupal\field\Fiel
 /**
  * Replace special strings in the query before it is executed.
  *
+ * The idea is that certain dynamic values can be placed in a query when it is
+ * built, and substituted at run-time, allowing the query to be cached and
+ * still work correctly when executed.
+ *
  * @param \Drupal\views\ViewExecutable $view
  *   The View being executed.
+ *
  * @return array
  *   An associative array where each key is a string to be replaced, and the
  *   corresponding value is its replacement. The strings to replace are often
- *   surrounded with '***', as illustrated in the example implementation.
+ *   surrounded with '***', as illustrated in the example implementation, to
+ *   avoid collisions with other values in the query.
  */
 function hook_views_query_substitutions(ViewExecutable $view) {
   // Example from views_views_query_substitutions().
   return array(
     '***CURRENT_VERSION***' => \Drupal::VERSION,
     '***CURRENT_TIME***' => REQUEST_TIME,
-    '***CURRENT_LANGUAGE***' => \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->id,
-    '***DEFAULT_LANGUAGE***' => \Drupal::languageManager()->getDefaultLanguage()->id,
+    '***LANGUAGE_language_content***' => \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->id,
+    '***LANGUAGE_site_default***' => \Drupal::languageManager()->getDefaultLanguage()->id,
   );
 }
 
diff --git a/core/modules/views/views.module b/core/modules/views/views.module
index 459ff0a9f4e6..5a30bf09c197 100644
--- a/core/modules/views/views.module
+++ b/core/modules/views/views.module
@@ -13,7 +13,6 @@
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Database\Query\AlterableInterface;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\views\Plugin\Derivative\ViewsLocalTask;
@@ -467,34 +466,6 @@ function views_add_contextual_links(&$render_element, $location, ViewExecutable
   }
 }
 
-/**
- * Prepares a list of language names.
- *
- * This is a wrapper around \Drupal::languageManager()->getLanguages() to return
- * a plain key value array.
- *
- * @param string $field
- *   The field of the language object which should be used as the value of the
- *   array.
- * @param int $flags
- *   (optional) Specifies the state of the languages that have to be returned.
- *   It can be: LanguageInterface::STATE_CONFIGURABLE,
- *   LanguageInterface::STATE_LOCKED, LanguageInterface::STATE_ALL.
- *
- * @return array
- *   An array of language names (or $field) keyed by the langcode.
- *
- * @see locale_language_list()
- */
-function views_language_list($field = 'name', $flags = LanguageInterface::STATE_ALL) {
-  $languages = \Drupal::languageManager()->getLanguages($flags);
-  $list = array();
-  foreach ($languages as $language) {
-    $list[$language->id] = ($field == 'name') ? t($language->name) : $language->$field;
-  }
-  return $list;
-}
-
 /**
  * Implements hook_ENTITY_TYPE_create() for 'field_instance_config'.
  */
diff --git a/core/modules/views/views.views_execution.inc b/core/modules/views/views.views_execution.inc
index 96b500610a96..91dead539b83 100644
--- a/core/modules/views/views.views_execution.inc
+++ b/core/modules/views/views.views_execution.inc
@@ -5,19 +5,23 @@
  * Provides views runtime hooks for views.module.
  */
 
-use Drupal\Core\Language\LanguageInterface;
 use Drupal\views\ViewExecutable;
+use Drupal\views\Plugin\views\PluginBase;
 
 /**
  * Implements hook_views_query_substitutions().
  *
- * Substitute current time; this works with cached queries.
+ * Makes the following substitutions:
+ * - Current time.
+ * - Drupal version.
+ * - Special language codes; see
+ *   \Drupal\views\Plugin\views\PluginBase::listLanguages().
  */
 function views_views_query_substitutions(ViewExecutable $view) {
-  return array(
+  $substitutions = array(
     '***CURRENT_VERSION***' => \Drupal::VERSION,
     '***CURRENT_TIME***' => REQUEST_TIME,
-    '***CURRENT_LANGUAGE***' => \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_CONTENT)->id,
-    '***DEFAULT_LANGUAGE***' => \Drupal::languageManager()->getDefaultLanguage()->id,
-  );
+  ) + PluginBase::queryLanguageSubstitutions();
+
+  return $substitutions;
 }
diff --git a/core/modules/views_ui/src/Tests/ViewEditTest.php b/core/modules/views_ui/src/Tests/ViewEditTest.php
index b88ed43b8633..62036f1540f8 100644
--- a/core/modules/views_ui/src/Tests/ViewEditTest.php
+++ b/core/modules/views_ui/src/Tests/ViewEditTest.php
@@ -94,7 +94,7 @@ public function testEditFormOtherOptions() {
     $this->drupalGet('admin/structure/views/view/test_view');
     $langcode_url = 'admin/structure/views/nojs/display/test_view/default/field_langcode';
     $this->assertLinkByHref($langcode_url);
-    $this->assertLink(t("Current user's language"));
+    $this->assertLink(t('Language selected for !type', array('!type' => t('Content'))));
     // Click the link and check the form before language is added.
     $this->drupalGet($langcode_url);
     $this->assertResponse(200);
@@ -106,7 +106,7 @@ public function testEditFormOtherOptions() {
 
     $this->drupalGet('admin/structure/views/nojs/display/test_display/page_1/field_langcode');
     $this->assertResponse(200);
-    $this->assertFieldByName('field_langcode', '***CURRENT_LANGUAGE***');
+    $this->assertFieldByName('field_langcode', '***LANGUAGE_language_content***');
     $this->assertFieldByName('field_langcode_add_to_query', TRUE);
   }
 
-- 
GitLab