diff --git a/core/lib/Drupal/Component/Utility/SortArray.php b/core/lib/Drupal/Component/Utility/SortArray.php index 64ceb89..e505863 100644 --- a/core/lib/Drupal/Component/Utility/SortArray.php +++ b/core/lib/Drupal/Component/Utility/SortArray.php @@ -119,6 +119,25 @@ public static function sortByWeightAndTitleKey($a, $b, $weight_key = 'weight', $ } /** + * Sorts a structured array first by weight, then by label. + * + * @param array $a + * The first item to compare. + * @param array $b + * The second item to compare. + * @param string $weight_key + * (optional) The weight key to use. Defaults to 'weight'. + * @param string $title_key + * (optional) The title key to use. Defaults to 'label'. + * + * @return int + * The comparison result for uasort(). + */ + public static function sortByWeightAndLabelKey($a, $b, $weight_key = 'weight', $title_key = 'label') { + return self::sortByWeightAndTitleKey($a, $b, $weight_key, $title_key); + } + + /** * Sorts a string array item by an arbitrary key. * * @param array $a diff --git a/core/lib/Drupal/Core/Language/Language.php b/core/lib/Drupal/Core/Language/Language.php index f1a1cbe..bbc46d1 100644 --- a/core/lib/Drupal/Core/Language/Language.php +++ b/core/lib/Drupal/Core/Language/Language.php @@ -207,7 +207,7 @@ public function __construct(array $options = array()) { * The array of language objects keyed by langcode. */ public static function sort(&$languages) { - uasort($languages, 'Drupal\Component\Utility\SortArray::sortByWeightAndTitleKey'); + uasort($languages, 'Drupal\Component\Utility\SortArray::sortByWeightAndLabelKey'); } } diff --git a/core/lib/Drupal/Core/Language/LanguageManager.php b/core/lib/Drupal/Core/Language/LanguageManager.php index 6ddf673..c680052 100644 --- a/core/lib/Drupal/Core/Language/LanguageManager.php +++ b/core/lib/Drupal/Core/Language/LanguageManager.php @@ -142,6 +142,14 @@ public function getLanguages($flags = Language::STATE_CONFIGURABLE) { /** * {@inheritdoc} */ + public function getNativeLanguages() { + // In a language unaware site we don't have translated languages. + return $this->getLanguages(); + } + + /** + * {@inheritdoc} + */ public function getLanguage($langcode) { $languages = $this->getLanguages(Language::STATE_ALL); return isset($languages[$langcode]) ? $languages[$langcode] : NULL; diff --git a/core/lib/Drupal/Core/Language/LanguageManagerInterface.php b/core/lib/Drupal/Core/Language/LanguageManagerInterface.php index 16c7c01..32f0b61 100644 --- a/core/lib/Drupal/Core/Language/LanguageManagerInterface.php +++ b/core/lib/Drupal/Core/Language/LanguageManagerInterface.php @@ -91,6 +91,15 @@ public function getDefaultLanguage(); public function getLanguages($flags = Language::STATE_CONFIGURABLE); /** + * Returns a list of languages set up on the site in their native form. + * + * @return array + * An associative array of languages, keyed by the language code, ordered + * by weight ascending and name ascending. + */ + public function getNativeLanguages(); + + /** * Returns a language object from the given language code. * * @param string $langcode diff --git a/core/modules/language/lib/Drupal/language/ConfigurableLanguageManager.php b/core/modules/language/lib/Drupal/language/ConfigurableLanguageManager.php index a7fb8f6..8311974 100644 --- a/core/modules/language/lib/Drupal/language/ConfigurableLanguageManager.php +++ b/core/modules/language/lib/Drupal/language/ConfigurableLanguageManager.php @@ -261,7 +261,7 @@ public function getLanguages($flags = Language::STATE_CONFIGURABLE) { $default = $this->getDefaultLanguage(); $this->languages = array($default->id => $default); - // Retrieve the config storage to list available languages. + // Retrieve a list of languages from configuration storage. $prefix = 'language.entity.'; $storage = $this->configFactory->get($prefix . Language::LANGCODE_NOT_SPECIFIED)->getStorage(); $config_ids = $storage->listAll($prefix); @@ -292,6 +292,46 @@ public function getLanguages($flags = Language::STATE_CONFIGURABLE) { /** * {@inheritdoc} */ + public function getNativeLanguages() { + $languages = array(); + $default = $this->getDefaultLanguage(); + + // Retrieve the config storage to list available languages. + $prefix = 'language.entity.'; + $storage = $this->configFactory->get($prefix . Language::LANGCODE_NOT_SPECIFIED)->getStorage(); + $config_ids = $storage->listAll($prefix); + + $langcodes = array_map(function($value) use ($prefix) { + return str_replace($prefix, '', $value); + }, $config_ids); + // Instantiate languages from config objects. + $original_language = $this->configFactory->getLanguage(); + $i = 0; + + foreach ($config_ids as $config_id) { + $langcode = $langcodes[$i]; + $data = $this->configFactory->get($config_id)->get(); + // Initialize default property so callers have an easy reference and can + // save the same object without data loss. + $data['default'] = ($langcode == $default->id); + $data['name'] = $data['label']; + $language = new Language($data); + if (!$language->locked) { + $languages[$langcode] = $language; + } + ++$i; + } + $this->configFactory->setLanguage($original_language); + + // Sort the language list by weight. + Language::sort($languages); + + return $languages; + } + + /** + * {@inheritdoc} + */ public function updateLockedLanguageWeights() { $max_weight = 0; diff --git a/core/modules/language/lib/Drupal/language/Plugin/LanguageNegotiation/LanguageNegotiationSession.php b/core/modules/language/lib/Drupal/language/Plugin/LanguageNegotiation/LanguageNegotiationSession.php index e56278d..2414d6f 100644 --- a/core/modules/language/lib/Drupal/language/Plugin/LanguageNegotiation/LanguageNegotiationSession.php +++ b/core/modules/language/lib/Drupal/language/Plugin/LanguageNegotiation/LanguageNegotiationSession.php @@ -131,7 +131,7 @@ function getLanguageSwitchLinks(Request $request, $type, $path) { $query = array(); parse_str($request->getQueryString(), $query); - foreach ($this->languageManager->getLanguages() as $language) { + foreach ($this->languageManager->getNativeLanguages() as $language) { $langcode = $language->id; $links[$langcode] = array( 'href' => $path, diff --git a/core/modules/language/lib/Drupal/language/Plugin/LanguageNegotiation/LanguageNegotiationUrl.php b/core/modules/language/lib/Drupal/language/Plugin/LanguageNegotiation/LanguageNegotiationUrl.php index eacb5c4..71a93ca 100644 --- a/core/modules/language/lib/Drupal/language/Plugin/LanguageNegotiation/LanguageNegotiationUrl.php +++ b/core/modules/language/lib/Drupal/language/Plugin/LanguageNegotiation/LanguageNegotiationUrl.php @@ -188,7 +188,7 @@ public function processOutbound($path, &$options = array(), Request $request = N function getLanguageSwitchLinks(Request $request, $type, $path) { $links = array(); - foreach ($this->languageManager->getLanguages() as $language) { + foreach ($this->languageManager->getNativeLanguages() as $language) { $links[$language->id] = array( 'href' => $path, 'title' => $language->name, diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageSwitchingTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageSwitchingTest.php index b4c4616..65445c2 100644 --- a/core/modules/language/lib/Drupal/language/Tests/LanguageSwitchingTest.php +++ b/core/modules/language/lib/Drupal/language/Tests/LanguageSwitchingTest.php @@ -7,6 +7,7 @@ namespace Drupal\language\Tests; +use Drupal\Core\Config\FileStorage; use Drupal\Core\Language\Language; use Drupal\simpletest\WebTestBase; @@ -20,7 +21,7 @@ class LanguageSwitchingTest extends WebTestBase { * * @var array */ - public static $modules = array('language', 'block', 'language_test'); + public static $modules = array('locale', 'language', 'block', 'language_test'); public static function getInfo() { return array( @@ -42,23 +43,26 @@ function setUp() { * Functional tests for the language switcher block. */ function testLanguageBlock() { - // Enable the language switching block.. - $block = $this->drupalPlaceBlock('language_block:' . Language::TYPE_INTERFACE, array( - 'id' => 'test_language_block', - // Ensure a 2-byte UTF-8 sequence is in the tested output. - 'label' => $this->randomName(8) . '×', - )); - // Add language. $edit = array( 'predefined_langcode' => 'fr', ); $this->drupalPostForm('admin/config/regional/language/add', $edit, t('Add language')); + // Set the native language name. + $this->saveNativeLanguageName('fr', 'Français'); + // Enable URL language detection and selection. $edit = array('language_interface[enabled][language-url]' => '1'); $this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings')); + // Enable the language switching block. + $block = $this->drupalPlaceBlock('language_block:' . Language::TYPE_INTERFACE, array( + 'id' => 'test_language_block', + // Ensure a 2-byte UTF-8 sequence is in the tested output. + 'label' => $this->randomName(8) . '×', + )); + $this->doTestLanguageBlockAuthenticated($block->label()); $this->doTestLanguageBlockAnonymous($block->label()); } @@ -134,6 +138,7 @@ protected function doTestLanguageBlockAnonymous($block_label) { 'active' => array(), 'inactive' => array(), ); + $labels = array(); foreach ($language_switcher->ul->li as $link) { $classes = explode(" ", (string) $link['class']); list($langcode) = array_intersect($classes, array('en', 'fr')); @@ -150,9 +155,12 @@ protected function doTestLanguageBlockAnonymous($block_label) { else { $anchors['inactive'][] = $langcode; } + + $labels[] = (string) $link->a; } $this->assertIdentical($links, array('active' => array('en'), 'inactive' => array('fr')), 'Only the current language list item is marked as active on the language switcher block.'); $this->assertIdentical($anchors, array('active' => array('en'), 'inactive' => array('fr')), 'Only the current language anchor is marked as active on the language switcher block.'); + $this->assertIdentical($labels, array('English', 'Français'), 'The language links labels are in their own language on the language switcher block.'); } /** @@ -283,4 +291,19 @@ protected function doTestLanguageLinkActiveClassAnonymous() { $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode is marked active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode))); } + /** + * Saves the native name of a language entity in configuration as a label. + * + * @param string $langcode + * The language code of the language. + * @param string $label + * The native name of the language. + */ + private function saveNativeLanguageName($langcode, $label) { + $file_storage = new FileStorage($this->configDirectories[CONFIG_ACTIVE_DIRECTORY]); + $file_storage->write('language.config.' . $langcode . '.language.entity.' . $langcode, + array('label' => $label) + ); + } + }