diff --git a/tests/src/Kernel/LanguageTest.php b/tests/src/Kernel/LanguageTest.php new file mode 100644 index 0000000..c8d092d --- /dev/null +++ b/tests/src/Kernel/LanguageTest.php @@ -0,0 +1,367 @@ +setParameter('language.default_values', $language); + $this->container + ->register('language.default', 'Drupal\Core\Language\LanguageDefault') + ->addArgument('%language.default_values%'); + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->token = $this->container->get('token'); + + // Use a version of the language manager in which the various languages can + // be easily overridden during the test. We need to do this here instead of + // in ::register() since the container is being altered by + // LanguageServiceProvider::alter() after the services have been registered. + $this->languageManager = new MockLanguageManager( + $this->container->get('language.default'), + $this->container->get('config.factory'), + $this->container->get('module_handler'), + $this->container->get('language.config_factory_override'), + $this->container->get('request_stack') + ); + $this->container->set('language_manager', $this->languageManager); + + foreach ($this->langcodes as $langcode) { + // Enable test languages. + $this->languages[$langcode] = ConfigurableLanguage::createFromLangcode($langcode); + $this->languages[$langcode]->save(); + + // Populate language prefixes and domains to use in the test. + $this->language_prefixes[$langcode] = "$langcode-prefix"; + $this->language_domains[$langcode] = $langcode . '.example.com'; + } + + // Set language negotiation prefixes and domains to values that are uniquely + // identifiable in the test. + $language_negotiation_config = $this->config('language.negotiation'); + $language_negotiation_config->set('url.prefixes', $this->language_prefixes); + $language_negotiation_config->set('url.domains', $this->language_domains); + $language_negotiation_config->save(); + } + + /** + * Tests the language tokens. + * + * @dataProvider languageTokenReplacementDataProvider + */ + public function testLanguageTokenReplacement($token, $langcode, $expected_result) { + $bubbleable_metadata = new BubbleableMetadata(); + $options = $langcode ? ['langcode' => $langcode] : []; + // The part of the token name between the last `:` and the closing bracket + // is the machine name of the token. + preg_match('/\[.+:(.+)\]/', $token, $matches); + $name = $matches[1]; + $replacements = $this->token->generate('language', [$name => $token], [], $options, $bubbleable_metadata); + $this->assertEquals($expected_result, $replacements[$token]); + } + + /** + * Tests retrieving the interface and content language from the current page. + * + * @dataProvider currentPageLanguageTokenReplacementDataProvider + */ + public function testCurrentPageLanguageTokenReplacement($token, $langcode, $expected_result) { + // Set the interface language to Dutch. + $this->languageManager->setCurrentLanguage(LanguageInterface::TYPE_INTERFACE, $this->languages['nl']); + // Set the content language to Hungarian. + $this->languageManager->setCurrentLanguage(LanguageInterface::TYPE_CONTENT, $this->languages['hu']); + + $options = $langcode ? ['langcode' => $langcode] : []; + $result = $this->token->replace($token, [], $options); + $this->assertEquals($expected_result, $result); + } + + /** + * Provides test data for ::testLanguageTokenReplacement(). + * + * @return array + * An array of test cases. Each test case is an array with the following + * values: + * - The token to test. + * - An optional language code to pass as an option. + * - The expected result of the token replacement. + * + * @see testLanguageTokenReplacement() + */ + public function languageTokenReplacementDataProvider() { + return [ + [ + // Test the replacement of the name of the site default language. + '[language:name]', + // We are not overriding the language by passing a language code as an + // option. This means that the default language should be used which has + // been set to Portuguese. + NULL, + // The expected result. + 'Portuguese, Portugal', + ], + // Test the replacement of the other properties of the default language. + [ + '[language:langcode]', + NULL, + 'pt-pt', + ], + [ + '[language:direction]', + NULL, + 'ltr', + ], + [ + '[language:domain]', + NULL, + 'pt-pt.example.com', + ], + [ + '[language:prefix]', + NULL, + 'pt-pt-prefix', + ], + // Now repeat the entire test but override the language to use by passing + // Bulgarian as an option. + [ + '[language:name]', + 'bg', + 'Bulgarian', + ], + [ + '[language:langcode]', + 'bg', + 'bg', + ], + [ + '[language:direction]', + 'bg', + 'ltr', + ], + [ + '[language:domain]', + 'bg', + 'bg.example.com', + ], + [ + '[language:prefix]', + 'bg', + 'bg-prefix', + ], + ]; + } + + /** + * Provides test data for ::testCurrentPageLanguageTokenReplacement(). + * + * @return array + * An array of test cases. Each test case is an array with the following + * values: + * - The token to test. + * - An optional language code to pass as an option. + * - The expected result of the token replacement. + * + * @see testCurrentPageLanguageTokenReplacement() + */ + public function currentPageLanguageTokenReplacementDataProvider() { + return [ + [ + // Test the replacement of the language name token, taken from the + // interface language of the current page. + '[current-page:language-interface:name]', + // We are not overriding the language by passing a language code as an + // option. This means that the language should be taken from the + // interface language which has been set to Dutch. + NULL, + // The expected result. + 'Dutch', + ], + // Test the token name in the content language. + [ + '[current-page:language-content:name]', + NULL, + 'Hungarian', + ], + // Test the other tokens both for the content and interface languages. + [ + '[current-page:language-interface:langcode]', + NULL, + 'nl', + ], + [ + '[current-page:language-content:langcode]', + NULL, + 'hu', + ], + [ + '[current-page:language-interface:direction]', + NULL, + 'ltr', + ], + [ + '[current-page:language-content:direction]', + NULL, + 'ltr', + ], + [ + '[current-page:language-interface:domain]', + NULL, + 'nl.example.com', + ], + [ + '[current-page:language-content:domain]', + NULL, + 'hu.example.com', + ], + [ + '[current-page:language-interface:prefix]', + NULL, + 'nl-prefix', + ], + [ + '[current-page:language-content:prefix]', + NULL, + 'hu-prefix', + ], + // Now repeat the entire test with Bulgarian passed as an option. This + // should not affect the results, the language should be sourced from the + // current page. + [ + // Test the replacement of the language name token, taken from the + // interface language of the current page. + '[current-page:language-interface:name]', + // We are not overriding the language by passing a language code as an + // option. This means that the language should be taken from the + // interface language which has been set to Dutch. + 'bg', + // The expected result. + 'Dutch', + ], + // Test the token name in the content language. + [ + '[current-page:language-content:name]', + 'bg', + 'Hungarian', + ], + // Test the other tokens both for the content and interface languages. + [ + '[current-page:language-interface:langcode]', + 'bg', + 'nl', + ], + [ + '[current-page:language-content:langcode]', + 'bg', + 'hu', + ], + [ + '[current-page:language-interface:direction]', + 'bg', + 'ltr', + ], + [ + '[current-page:language-content:direction]', + 'bg', + 'ltr', + ], + [ + '[current-page:language-interface:domain]', + 'bg', + 'nl.example.com', + ], + [ + '[current-page:language-content:domain]', + 'bg', + 'hu.example.com', + ], + [ + '[current-page:language-interface:prefix]', + 'bg', + 'nl-prefix', + ], + [ + '[current-page:language-content:prefix]', + 'bg', + 'hu-prefix', + ], + ]; + } + +} diff --git a/tests/src/Kernel/MockLanguageManager.php b/tests/src/Kernel/MockLanguageManager.php new file mode 100644 index 0000000..b2d7b72 --- /dev/null +++ b/tests/src/Kernel/MockLanguageManager.php @@ -0,0 +1,42 @@ +currentLanguages[$type])) { + return $this->currentLanguages[$type]; + } + return parent::getCurrentLanguage($type); + } + + /** + * Sets the current language of the given type to use during tests. + * + * @param string $type + * The language type. + * @param \Drupal\Core\Language\LanguageInterface $language + * The language. + */ + public function setCurrentLanguage($type, LanguageInterface $language) { + $this->currentLanguages[$type] = $language; + } + +} diff --git a/token.tokens.inc b/token.tokens.inc index ba15160..2cc0779 100644 --- a/token.tokens.inc +++ b/token.tokens.inc @@ -8,6 +8,7 @@ use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\FieldableEntityInterface; use Drupal\Core\Entity\TypedData\EntityDataDefinitionInterface; +use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Render\BubbleableMetadata; use Drupal\Core\Render\Element; use Drupal\Component\Utility\Crypt; @@ -268,6 +269,36 @@ function token_token_info() { 'type' => 'menu-link', ]; + // Language tokens. + $info['types']['language'] = [ + 'name' => t('Language'), + 'description' => t('Tokens related to site language.'), + ]; + $info['tokens']['language']['name'] = [ + 'name' => t('Language name'), + 'description' => t('The language name.'), + ]; + $info['tokens']['language']['langcode'] = [ + 'name' => t('Language code'), + 'description' => t('The language code.'), + ]; + $info['tokens']['language']['direction'] = [ + 'name' => t('Direction'), + 'description' => t('Whether the language is written left-to-right (ltr) or right-to-left (rtl).'), + ]; + $info['tokens']['language']['native'] = [ + 'name' => t('Native name'), + 'description' => t('The language native name.'), + ]; + $info['tokens']['language']['domain'] = [ + 'name' => t('Domain'), + 'description' => t('The domain name to use for the language.'), + ]; + $info['tokens']['language']['prefix'] = [ + 'name' => t('Path prefix'), + 'description' => t('Path prefix for URLs in the language.'), + ]; + // Current page tokens. $info['types']['current-page'] = [ 'name' => t('Current page'), @@ -291,6 +322,16 @@ function token_token_info() { 'description' => t('The value of a specific query string field of the current page.'), 'dynamic' => TRUE, ]; + $info['tokens']['current-page']['language-interface'] = [ + 'name' => t('Language UI'), + 'description' => t('The active user interface language.'), + 'type' => 'language', + ]; + $info['tokens']['current-page']['language-content'] = [ + 'name' => t('Language content'), + 'description' => t('The active content language.'), + 'type' => 'language', + ]; // URL tokens. $info['types']['url'] = [ @@ -740,6 +781,45 @@ function token_tokens($type, array $tokens, array $data = [], array $options = [ } + // Language tokens. + if ($type == 'language' && !empty($langcode)) { + $language = $language_manager->getLanguage($langcode); + if ($language) { + foreach ($tokens as $name => $original) { + switch ($name) { + case 'name': + $replacements[$original] = $language->getName(); + break; + case 'langcode': + $replacements[$original] = $langcode; + break; + case 'direction': + $replacements[$original] = $language->getDirection(); + break; + case 'native': + $replacements[$original] = $language->native; + break; + case 'domain': + if (!isset($language_url_domains)) { + $language_url_domains = \Drupal::config('language.negotiation')->get('url.domains'); + } + if (isset($language_url_domains[$langcode])) { + $replacements[$original] = $language_url_domains[$langcode]; + } + break; + case 'prefix': + if (!isset($language_url_prefixes)) { + $language_url_prefixes = \Drupal::config('language.negotiation')->get('url.prefixes'); + } + if (isset($language_url_prefixes[$langcode])) { + $replacements[$original] = $language_url_prefixes[$langcode]; + } + break; + } + } + } + } + // Current page tokens. if ($type == 'current-page') { $request = \Drupal::request(); @@ -782,6 +862,18 @@ function token_tokens($type, array $tokens, array $data = [], array $options = [ $replacements[$original] = (int) $page + 1; break; } + // [current-page:language-interface:*] chained tokens. + if ($language_interface_tokens = \Drupal::token()->findWithPrefix($tokens, 'language-interface')) { + $language_interface = $language_manager->getCurrentLanguage(LanguageInterface::TYPE_INTERFACE); + $langcode = $language_interface->getId(); + $replacements += \Drupal::token()->generate('language', $language_interface_tokens, $data, ['langcode' => $langcode] + $options, $bubbleable_metadata); + } + // [current-page:language-content:*] chained tokens. + if ($language_content_tokens = \Drupal::token()->findWithPrefix($tokens, 'language-content')) { + $language_content = $language_manager->getCurrentLanguage(LanguageInterface::TYPE_CONTENT); + $langcode = $language_content->getId(); + $replacements += \Drupal::token()->generate('language', $language_content_tokens, $data, ['langcode' => $langcode] + $options, $bubbleable_metadata); + } } // @deprecated