diff -u b/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module --- b/core/modules/content_translation/content_translation.module +++ b/core/modules/content_translation/content_translation.module @@ -69,6 +69,7 @@ // Make content language negotiation configurable by removing the 'locked' // flag. $language_types[LanguageInterface::TYPE_CONTENT]['locked'] = FALSE; + unset($language_types[LanguageInterface::TYPE_CONTENT]['fixed']); } /** diff -u b/core/modules/content_translation/src/ContentTranslationHandler.php b/core/modules/content_translation/src/ContentTranslationHandler.php --- b/core/modules/content_translation/src/ContentTranslationHandler.php +++ b/core/modules/content_translation/src/ContentTranslationHandler.php @@ -622,7 +622,7 @@ $source = $form_state->getValue(array('source_langcode', 'source')); $entity_type_id = $entity->getEntityTypeId(); - $form_state->setRedirect("entity.$entity_type_id.content_translation_add", array( + $form_state->setRedirect('content_translation.translation_add_' . $entity_type_id, array( $entity_type_id => $entity->id(), 'source' => $source, 'target' => $form_object->getFormLangcode($form_state), @@ -652,14 +652,14 @@ $source = $form_state->getValue(array('source_langcode', 'source')); $entity_type_id = $entity->getEntityTypeId(); - $form_state->setRedirect('content_translation.translation_add_' . $entity_type_id, array( + $form_state->setRedirect("entity.$entity_type_id.content_translation_add", array( $entity_type_id => $entity->id(), 'source' => $source, 'target' => $form_object->getFormLangcode($form_state), $form_state->setRedirectUrl($entity->urlInfo('delete-form')); } else { - $form_state->setRedirect("entity.$entity_type_id.content_translation_delete", [ + $form_state->setRedirect('content_translation.translation_delete_' . $entity_type_id, [ $entity_type_id => $entity->id(), 'language' => $form_object->getFormLangcode($form_state), ]); @@ -689,7 +689,7 @@ $form_state->setRedirectUrl($entity->urlInfo('delete-form')); } else { - $form_state->setRedirect('content_translation.translation_delete_' . $entity_type_id, [ + $form_state->setRedirect("entity.$entity_type_id.content_translation_delete", [ $entity_type_id => $entity->id(), 'language' => $form_object->getFormLangcode($form_state), ]); reverted: --- b/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationContentLanguage.php +++ /dev/null @@ -1,147 +0,0 @@ -get(static::QUERY_PARAMETER); - - $language_enabled = array_key_exists($langcode, $this->languageManager->getLanguages()); - return $language_enabled ? $langcode : NULL; - } - - /** - * Implements Drupal\Core\PathProcessor\InboundPathProcessorInterface::processOutbound(). - */ - public function processOutbound($path, &$options = array(), Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) { - // Only run if the LanguageNegotiationContentLanguage outbound function is - // being executed before the outbound function of LanguageNegotiationUrl. - $enabled_methods = $this->config->get('language.types')->get('negotiation.language_content.enabled') ? : []; - $run = isset($enabled_methods[LanguageNegotiationUrl::METHOD_ID]) ? $enabled_methods[static::METHOD_ID] < $enabled_methods[LanguageNegotiationUrl::METHOD_ID] : TRUE; - - if ($run && $request && isset($options['route_name']) && (isset($options['language']) || $langcode = $this->getLangcode($request))) { - $route_name = $options['route_name']; - $route_provider = \Drupal::getContainer()->get('router.route_provider'); - $route_name_path = $route_provider->getRouteByName($route_name)->getPath(); - - if (in_array($route_name_path, $this->getContentEntityPaths())) { - if (isset($options['language'])) { - $langcode = $options['language']->getId(); - unset($options['language']); - } - - if (isset($options['query']) && is_string($options['query'])) { - $query = array(); - parse_str($options['query'], $query); - $options['query'] = $query; - } - else { - $options['query'] = []; - } - if (!isset($options['query'][static::QUERY_PARAMETER])) { - $query_addon = [static::QUERY_PARAMETER => $langcode]; - $options['query'] += $query_addon; - $path .= (strpos($path, '?') !== FALSE ? '&' : '?') . UrlHelper::buildQuery($query_addon); - } - if ($bubbleable_metadata) { - // Cached URLs that have been processed by this outbound path - // processor must be: - $bubbleable_metadata - // - varied by the content language query parameter. - ->addCacheContexts(['url.query_args:' . static::QUERY_PARAMETER]); - } - } - } - - return $path; - } - - /** - * {@inheritdoc} - */ - public function getLanguageSwitchLinks(Request $request, $type, Url $url) { - $links = []; - $query = []; - parse_str($request->getQueryString(), $query); - - foreach ($this->languageManager->getNativeLanguages() as $language) { - $langcode = $language->getId(); - $query[static::QUERY_PARAMETER] = $langcode; - $links[$langcode] = [ - 'url' => $url, - 'title' => $language->getName(), - 'attributes' => ['class' => ['language-link']], - 'query' => $query, - ]; - } - - return $links; - } - - /** - * Returns the paths for the link templates of all content entities. - * - * @return array - * The array of the link templates paths of all content entities. - */ - protected function getContentEntityPaths() { - if ($this->contentEntityPaths === NULL) { - $this->contentEntityPaths = []; - $entity_types = \Drupal::entityManager()->getDefinitions(); - foreach ($entity_types as $entity_type_id => $entity_type) { - if ($entity_type->isSubclassOf('\Drupal\Core\Entity\ContentEntityInterface')) { - $this->contentEntityPaths = array_merge($this->contentEntityPaths, array_values($entity_type->getLinkTemplates())); - } - } - } - - return $this->contentEntityPaths; - } - -} diff -u b/core/tests/Drupal/Tests/Core/PathProcessor/PathProcessorTest.php b/core/tests/Drupal/Tests/Core/PathProcessor/PathProcessorTest.php --- b/core/tests/Drupal/Tests/Core/PathProcessor/PathProcessorTest.php +++ b/core/tests/Drupal/Tests/Core/PathProcessor/PathProcessorTest.php @@ -61,7 +61,7 @@ // Create a URL-based language negotiation method definition. $method_definitions = array( LanguageNegotiationUrl::METHOD_ID => array( - 'class' => '\Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl', + 'class' => LanguageNegotiationUrl::class, 'weight' => 9, ), ); @@ -136,7 +136,7 @@ $negotiator->expects($this->any()) ->method('getNegotiationMethods') ->will($this->returnValue(array(LanguageNegotiationUrl::METHOD_ID => array( - 'class' => 'Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl', + 'class' => LanguageNegotiationUrl::class, 'weight' => 9, )))); $method = new LanguageNegotiationUrl(); only in patch2: unchanged: --- /dev/null +++ b/core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationContentEntity.php @@ -0,0 +1,281 @@ +routeProvider = $route_provider; + } + + /** + * {@inheritdoc} + */ + public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { + return new static($container->get('router.route_provider')); + } + + /** + * {@inheritdoc} + */ + public function getLangcode(Request $request = NULL) { + $langcode = $request->get(static::QUERY_PARAMETER); + + $language_enabled = array_key_exists($langcode, $this->languageManager->getLanguages()); + return $language_enabled ? $langcode : NULL; + } + + /** + * Implements Drupal\Core\PathProcessor\InboundPathProcessorInterface::processOutbound(). + */ + public function processOutbound($path, &$options = [], Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) { + // Check if processing conditions are met. + if (!($request && isset($options['route_name']) && ($outbound_route_name = $options['route_name']) && $this->meetsConfigurationCondition() && $this->meetsContentEntityRoutesCondition($outbound_route_name, $request))) { + return $path; + } + + if (isset($options['language']) || $langcode = $this->getLangcode($request)) { + // If the language option is set, unset it, so that the url language + // negotiator does not rewrite the url. + if (isset($options['language'])) { + $langcode = $options['language']->getId(); + unset($options['language']); + } + + if (isset($options['query']) && is_string($options['query'])) { + $query = []; + parse_str($options['query'], $query); + $options['query'] = $query; + } + else { + $options['query'] = []; + } + + if (!isset($options['query'][static::QUERY_PARAMETER])) { + $query_addon = [static::QUERY_PARAMETER => $langcode]; + $options['query'] += $query_addon; + $path .= (strpos($path, '?') !== FALSE ? '&' : '?') . UrlHelper::buildQuery($query_addon); + } + + if ($bubbleable_metadata) { + // Cached URLs that have been processed by this outbound path + // processor must be: + $bubbleable_metadata + // - varied by the content language query parameter. + ->addCacheContexts(['url.query_args:' . static::QUERY_PARAMETER]); + } + } + + return $path; + } + + /** + * {@inheritdoc} + */ + public function getLanguageSwitchLinks(Request $request, $type, Url $url) { + $links = []; + $query = []; + parse_str($request->getQueryString(), $query); + + foreach ($this->languageManager->getNativeLanguages() as $language) { + $langcode = $language->getId(); + $query[static::QUERY_PARAMETER] = $langcode; + $links[$langcode] = [ + 'url' => $url, + 'title' => $language->getName(), + 'attributes' => ['class' => ['language-link']], + 'query' => $query, + ]; + } + + return $links; + } + + /** + * Determines the execution permission based on the current configuration. + * + * Requirement: the content entity language negotiator has higher priority + * than the url language negotiator. + * + * @return bool + */ + protected function meetsConfigurationCondition() { + if (!isset($this->meetsConfigurationCondition)) { + // Only run if the LanguageNegotiationContentEntity outbound function is + // being executed before the outbound function of LanguageNegotiationUrl. + $enabled_methods_content = $this->config->get('language.types')->get('negotiation.language_content.enabled') ? : []; + + // Check if the content language is configured to be dependent on the + // url negotiator directly or indirectly over the interface negotiator. + $check_interface_method = FALSE; + if (isset($enabled_methods_content[LanguageNegotiationUI::METHOD_ID])) { + $enabled_methods_interface = $this->config->get('language.types')->get('negotiation.language_interface.enabled') ? : []; + $check_interface_method = isset($enabled_methods_interface[LanguageNegotiationUrl::METHOD_ID]) ? TRUE : FALSE; + } + if ($check_interface_method) { + $check_against_weight = $enabled_methods_content[LanguageNegotiationUI::METHOD_ID]; + $check_against_weight = isset($enabled_methods_content[LanguageNegotiationUI::METHOD_ID]) ? max($check_against_weight, $enabled_methods_content[LanguageNegotiationUrl::METHOD_ID]) : $check_against_weight; + } + else { + $check_against_weight = isset($enabled_methods_content[LanguageNegotiationUrl::METHOD_ID]) ? $enabled_methods_content[LanguageNegotiationUrl::METHOD_ID] : PHP_INT_MAX; + } + + $this->meetsConfigurationCondition = $enabled_methods_content[static::METHOD_ID] < $check_against_weight; + } + + return $this->meetsConfigurationCondition; + } + + /** + * Determines if content entity route condition is met. + * + * Requirements: currently being on an content entity route and processing + * outbound url pointing to the same content entity. + * + * @param $outbound_route_name + * The route name for the current outbound url being processed. + * @param \Symfony\Component\HttpFoundation\Request $request + * The HttpRequest object representing the current request. + * + * @return bool + */ + protected function meetsContentEntityRoutesCondition($outbound_route_name, Request $request) { + $content_entity_path_for_current_route = $this->getContentEntityPathForCurrentRequest($request); + + if ($content_entity_path_for_current_route) { + return $this->meetsOutboundRouteToCurrentContentEntityCondition($outbound_route_name, $request); + } + + return FALSE; + } + + /** + * Returns the content entity path from the current request. + * + * @param \Symfony\Component\HttpFoundation\Request $request + * The HttpRequest object representing the current request. + * + * @return string + */ + protected function getContentEntityPathForCurrentRequest(Request $request) { + $content_entity_path_for_current_route = ''; + + if ($current_route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME)) { + $current_route_path = $this->routeProvider->getRouteByName($current_route_name)->getPath(); + $content_entity_path_for_current_route = isset($this->getContentEntityPaths()[$current_route_path]) ? $this->getContentEntityPaths()[$current_route_path] : ''; + } + + return $content_entity_path_for_current_route; + } + + /** + * Determines if outbound route points to the current entity. + * + * + * @param $outbound_route_name + * The route name for the current outbound url being processed. + * @param \Symfony\Component\HttpFoundation\Request $request + * The HttpRequest object representing the current request. + * + * @return bool + */ + protected function meetsOutboundRouteToCurrentContentEntityCondition($outbound_route_name, Request $request) { + if ($content_entity_path_for_current_route = $this->getContentEntityPathForCurrentRequest($request)) { + $outbound_route_path = $this->routeProvider->getRouteByName($outbound_route_name)->getPath(); + if (isset($this->getContentEntityPaths()[$outbound_route_path]) && $this->getContentEntityPaths()[$outbound_route_path]) { + return TRUE; + } + } + + return FALSE; + } + + /** + * Returns the paths for the link templates of all content entities. + * + * The array will be keyed by the link template paths with the corresponding + * entity type ids as values. + * + * @return array + * The array of the link templates paths of all content entities, as keys. + */ + protected function getContentEntityPaths() { + if (!isset($this->contentEntityPaths)) { + $this->contentEntityPaths = []; + $entity_types = \Drupal::entityManager()->getDefinitions(); + foreach ($entity_types as $entity_type_id => $entity_type) { + if ($entity_type->isSubclassOf('\Drupal\Core\Entity\ContentEntityInterface')) { + $entity_paths = array_fill_keys($entity_type->getLinkTemplates(), $entity_type_id); + $this->contentEntityPaths = array_merge($this->contentEntityPaths, $entity_paths); + } + } + } + + return $this->contentEntityPaths; + } +} only in patch2: unchanged: --- a/core/modules/language/src/Tests/EntityUrlLanguageTest.php +++ b/core/modules/language/src/Tests/EntityUrlLanguageTest.php @@ -7,22 +7,34 @@ namespace Drupal\language\Tests; +use Drupal\Core\Language\LanguageInterface; use Drupal\entity_test\Entity\EntityTest; use Drupal\language\Entity\ConfigurableLanguage; -use Drupal\simpletest\KernelTestBase; +use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity; +use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl; +use Symfony\Cmf\Component\Routing\RouteObjectInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Route; /** * Tests the language of entity URLs. * @group language */ -class EntityUrlLanguageTest extends KernelTestBase { +class EntityUrlLanguageTest extends LanguageTestBase { /** * Modules to enable. * * @var array */ - public static $modules = ['language', 'entity_test', 'user', 'system']; + public static $modules = ['entity_test', 'user']; + + /** + * The entity being used for testing. + * + * @var \Drupal\Core\Entity\ContentEntityInterface + */ + protected $entity; protected function setUp() { parent::setUp(); @@ -37,33 +49,108 @@ protected function setUp() { ConfigurableLanguage::create(['id' => 'es'])->save(); ConfigurableLanguage::create(['id' => 'fr'])->save(); - $this->config('language.types')->setData([ - 'configurable' => ['language_interface'], - 'negotiation' => ['language_interface' => ['enabled' => ['language-url' => 0]]], - ])->save(); - $this->config('language.negotiation')->setData([ - 'url' => [ - 'source' => 'path_prefix', - 'prefixes' => ['en' => 'en', 'es' => 'es', 'fr' => 'fr'] - ], - ])->save(); - $this->kernel->rebuildContainer(); - $this->container = $this->kernel->getContainer(); - \Drupal::setContainer($this->container); + $config = $this->config('language.negotiation'); + $config->set('url.prefixes', ['en' => 'en', 'es' => 'es', 'fr' => 'fr']) + ->save(); + + $this->rebuildContainer(); + + $this->createTranslatableEntity(); } /** * Ensures that entity URLs in a language have the right language prefix. */ public function testEntityUrlLanguage() { - $entity = EntityTest::create(); - $entity->addTranslation('es', ['name' => 'name spanish']); - $entity->addTranslation('fr', ['name' => 'name french']); - $entity->save(); - - $this->assertTrue(strpos($entity->urlInfo()->toString(), '/en/entity_test/' . $entity->id()) !== FALSE); - $this->assertTrue(strpos($entity->getTranslation('es')->urlInfo()->toString(), '/es/entity_test/' . $entity->id()) !== FALSE); - $this->assertTrue(strpos($entity->getTranslation('fr')->urlInfo()->toString(), '/fr/entity_test/' . $entity->id()) !== FALSE); + $this->assertTrue(strpos($this->entity->urlInfo()->toString(), '/en/entity_test/' . $this->entity->id()) !== FALSE); + $this->assertTrue(strpos($this->entity->getTranslation('es')->urlInfo()->toString(), '/es/entity_test/' . $this->entity->id()) !== FALSE); + $this->assertTrue(strpos($this->entity->getTranslation('fr')->urlInfo()->toString(), '/fr/entity_test/' . $this->entity->id()) !== FALSE); + } + + /** + * Ensures correct entity URLs with the method language-content-entity enabled. + * + * Test case with the method language-content-entity enabled and configured with + * higher and also with lower priority than the method language-url. + */ + public function testEntityUrlLanguageWithLanguageContentEnabled() { + // Define the method language-content-entity with a higher priority than + // language-url. + $config = $this->config('language.types'); + $config->set('configurable', [LanguageInterface::TYPE_INTERFACE, LanguageInterface::TYPE_CONTENT]); + $config->set('negotiation.language_content.enabled', [ + LanguageNegotiationContentEntity::METHOD_ID => 0, + LanguageNegotiationUrl::METHOD_ID => 1 + ]); + $config->save(); + + // In order to reflect the changes for a multilingual site in the container + // we have to rebuild it. + $this->rebuildContainer(); + + // Without being on an content entity route the default entity url tests + // should still pass. + $this->testEntityUrlLanguage(); + + // Now switching to an entity route, so that the url links are generated + // while being on an entity route. + $this->setCurrentRequestForEntityRoute(); + + // The method language-content-entity should run before language-url and + // append query parameter for the content language and prevent language-url + // from overwriting the url. + $this->assertTrue(strpos($this->entity->urlInfo('canonical')->toString(), '/en/entity_test/' . $this->entity->id() . '?' . LanguageNegotiationContentEntity::QUERY_PARAMETER . '=en') !== FALSE); + $this->assertTrue(strpos($this->entity->getTranslation('es')->urlInfo('canonical')->toString(), '/en/entity_test/' . $this->entity->id() . '?' . LanguageNegotiationContentEntity::QUERY_PARAMETER . '=es') !== FALSE); + $this->assertTrue(strpos($this->entity->getTranslation('fr')->urlInfo('canonical')->toString(), '/en/entity_test/' . $this->entity->id() . '?' . LanguageNegotiationContentEntity::QUERY_PARAMETER . '=fr') !== FALSE); + + // Define the method language-url with a higher priority than + // language-content-entity. This configuration should match the default one, + // where the language-content-entity is turned off. + $config->set('negotiation.language_content.enabled', [ + LanguageNegotiationUrl::METHOD_ID => 0, + LanguageNegotiationContentEntity::METHOD_ID => 1 + ]); + $config->save(); + + // In order to reflect the changes for a multilingual site in the container + // we have to rebuild it. + $this->rebuildContainer(); + + // The default entity url tests should pass again with the current + // configuration. + $this->testEntityUrlLanguage(); + } + + /** + * Sets the current request pointing to an entity route. + */ + protected function setCurrentRequestForEntityRoute() { + $path = '/entity_test/{entity_test}'; + $route_name = 'entity.entity_test.canonical'; + + $request = Request::create($path); + $request->attributes->set(RouteObjectInterface::ROUTE_NAME, $route_name); + $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route($path)); + \Drupal::requestStack()->push($request); + } + + /** + * Creates a translated entity. + */ + protected function createTranslatableEntity() { + $this->entity = EntityTest::create(); + $this->entity->addTranslation('es', ['name' => 'name spanish']); + $this->entity->addTranslation('fr', ['name' => 'name french']); + $this->entity->save(); + } + + /** + * Force a container rebuild. + */ + protected function rebuildContainer() { + $this->kernel->rebuildContainer(); + $this->container = $this->kernel->getContainer(); + \Drupal::setContainer($this->container); } } only in patch2: unchanged: --- /dev/null +++ b/core/modules/language/src/Tests/LanguageNegotiationContentEntityTest.php @@ -0,0 +1,189 @@ + 'es'])->save(); + ConfigurableLanguage::create(['id' => 'fr'])->save(); + + // In order to reflect the changes for a multilingual site in the container + // we have to rebuild it. + $this->rebuildContainer(); + + $this->createTranslatableEntity(); + + $user = $this->drupalCreateUser(array('view test entity')); + $this->drupalLogin($user); + } + + /** + * Tests default with content language remaining same as interface language. + */ + public function testDefaultConfiguration() { + $translation = $this->entity; + $url = $translation->urlInfo()->toString(); + $this->drupalGet($url); + $last = $this->container->get('state')->get('language_test.language_negotiation_last'); + $last_content_language = $last[LanguageInterface::TYPE_CONTENT]; + $last_interface_language = $last[LanguageInterface::TYPE_INTERFACE]; + $this->assertTrue(($last_interface_language == $last_content_language) && ($last_content_language == $translation->language()->getId()), 'Interface language and Content language are the same as the translation language of the entity.'); + + $translation = $this->entity->getTranslation('es'); + $url = $translation->urlInfo()->toString(); + $this->drupalGet($url); + $last = $this->container->get('state')->get('language_test.language_negotiation_last'); + $last_content_language = $last[LanguageInterface::TYPE_CONTENT]; + $last_interface_language = $last[LanguageInterface::TYPE_INTERFACE]; + $this->assertTrue(($last_interface_language == $last_content_language) && ($last_content_language == $translation->language()->getId()), 'Interface language and Content language are the same as the translation language of the entity.'); + + $translation = $this->entity->getTranslation('fr'); + $url = $translation->urlInfo()->toString(); + $this->drupalGet($url); + $last = $this->container->get('state')->get('language_test.language_negotiation_last'); + $last_content_language = $last[LanguageInterface::TYPE_CONTENT]; + $last_interface_language = $last[LanguageInterface::TYPE_INTERFACE]; + $this->assertTrue(($last_interface_language == $last_content_language) && ($last_content_language == $translation->language()->getId()), 'Interface language and Content language are the same as the translation language of the entity.'); + } + + /** + * Test case for enabling the language negotiator language_content_entity. + */ + public function testEnabledLanguageContentNegotiator() { + // Define the method language-url with a higher priority than + // language-content-entity. This configuration should match the default one, + // where the language-content-entity is turned off. + $config = $this->config('language.types'); + $config->set('configurable', [LanguageInterface::TYPE_INTERFACE, LanguageInterface::TYPE_CONTENT]); + $config->set('negotiation.language_content.enabled', [ + LanguageNegotiationUrl::METHOD_ID => 0, + LanguageNegotiationContentEntity::METHOD_ID => 1 + ]); + $config->save(); + + // In order to reflect the changes for a multilingual site in the container + // we have to rebuild it. + $this->rebuildContainer(); + + // The tests for the default configuration should still pass. + $this->testDefaultConfiguration(); + + // Define the method language-content-entity with a higher priority than + // language-url. + $config->set('negotiation.language_content.enabled', [ + LanguageNegotiationContentEntity::METHOD_ID => 0, + LanguageNegotiationUrl::METHOD_ID => 1 + ]); + $config->save(); + + // In order to reflect the changes for a multilingual site in the container + // we have to rebuild it. + $this->rebuildContainer(); + + // The method language-content-entity should run before language-url and + // append query parameter for the content language and prevent language-url + // from overwriting the url. + $default_site_langcode = $this->config('system.site')->get('default_langcode'); + + // Now switching to an entity route, so that the url links are generated + // while being on an entity route. + $this->setCurrentRequestForEntityRoute(); + + $translation = $this->entity; + $this->drupalGet($translation->urlInfo()); + $last = $this->container->get('state')->get('language_test.language_negotiation_last'); + $last_content_language = $last[LanguageInterface::TYPE_CONTENT]; + $last_interface_language = $last[LanguageInterface::TYPE_INTERFACE]; + $this->assertTrue(($last_interface_language == $default_site_langcode) && ($last_interface_language == $last_content_language) && ($last_content_language == $translation->language()->getId()), 'Interface language and Content language are the same as the default translation language of the entity.'); + $this->assertTrue($last_interface_language == $default_site_langcode, 'Interface language did not change from the default site language.'); + $this->assertTrue($last_content_language == $translation->language()->getId(), 'Content language matches the current entity translation language.'); + + $translation = $this->entity->getTranslation('es'); + $this->drupalGet($translation->urlInfo()); + $last = $this->container->get('state')->get('language_test.language_negotiation_last'); + $last_content_language = $last[LanguageInterface::TYPE_CONTENT]; + $last_interface_language = $last[LanguageInterface::TYPE_INTERFACE]; + $this->assertTrue($last_interface_language == $default_site_langcode, 'Interface language did not change from the default site language.'); + $this->assertTrue($last_content_language == $translation->language()->getId(), 'Content language matches the current entity translation language.'); + + $translation = $this->entity->getTranslation('fr'); + $this->drupalGet($translation->urlInfo()); + $last = $this->container->get('state')->get('language_test.language_negotiation_last'); + $last_content_language = $last[LanguageInterface::TYPE_CONTENT]; + $last_interface_language = $last[LanguageInterface::TYPE_INTERFACE]; + $this->assertTrue($last_interface_language == $default_site_langcode, 'Interface language did not change from the default site language.'); + $this->assertTrue($last_content_language == $translation->language()->getId(), 'Content language matches the current entity translation language.'); + } + + /** + * Sets the current request pointing to an entity route. + */ + protected function setCurrentRequestForEntityRoute() { + $path = '/entity_test/{entity_test}'; + $route_name = 'entity.entity_test.canonical'; + + $request = Request::create($path); + $request->attributes->set(RouteObjectInterface::ROUTE_NAME, $route_name); + $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route($path)); + \Drupal::requestStack()->push($request); + } + + /** + * Creates a translated entity. + */ + protected function createTranslatableEntity() { + $this->entity = EntityTest::create(); + $this->entity->addTranslation('es', ['name' => 'name spanish']); + $this->entity->addTranslation('fr', ['name' => 'name french']); + $this->entity->save(); + } + + /** + * Force a container rebuild. + */ + protected function rebuildContainer() { + $this->kernel->rebuildContainer(); + $this->container = $this->kernel->getContainer(); + \Drupal::setContainer($this->container); + } +}