Problem/Motivation

When the language is negotiated via a user-preference and url-prefix, and the user sets an administrative language, the LanguageInterface::TYPE_URL language-type seems to be negotiated to the sites default-language.

The most visible result is that when accessing the site via eg. /da/page1 (da being the url-prefix for Danish) - the main-menu on that page, is rendered with /en/ prefixes.

Mismatching path prefixes

Steps to reproduce:

  1. Install a english site with the standard profile
  2. Enable the language module
  3. Access /admin/config/regional/language and add a language (eg Danish)
  4. Access /admin/config/regional/language/detection and enable "Account administration pages"
  5. Configure the URL detection to use /en as prefix for english
  6. Add an article and enable "Provide a menu link" so that a link is added to the Main Menu
  7. Access /en and verify that the main-menu tabs on the front-page links to the article with a /en prefix
  8. Access /da and verify that the main-menu tabs on the front-page links to the article with a /da prefix
  9. Edit the users preferences, and set "Administration pages language" to any language (eg danish)
  10. Access /en and /da and see that no matter what prefix is used, all menu-links are prefixed with /en (the default language)

In the last step I would have expected no change from the previous visits to the front-page as I have only changed a configuration relevant for the admin-interface. If the preference is set to "No preference" the prefixes behaves normally.

The following code in ConfigurableLanguageManager seems to be the culprit. The first time a language-type is requested, a initialization flag is set and the initialization is begun. As long this flag is set, the sites default language is returned - this seems like a sensible precaution in case the initialization itself requests a language. When the initialization is completed, the temporary value is replaced with negotiated value.

The problem is that this flag affects all language-types so that if another language-type is requested, a "temporary" value is again set - but as we then hit the initialization-flag, it is never overwritten, and is then made permanent.

  public function getCurrentLanguage($type = LanguageInterface::TYPE_INTERFACE) {
    if (!isset($this->negotiatedLanguages[$type])) {
      // Ensure we have a valid value for this language type.
      $this->negotiatedLanguages[$type] = $this->getDefaultLanguage();

      if ($this->negotiator && $this->isMultilingual()) {
        if (!$this->initializing) {
          $this->initializing = TRUE;
          $negotiation = $this->negotiator->initializeType($type);
          $this->negotiatedLanguages[$type] = reset($negotiation);
          $this->negotiatedMethods[$type] = key($negotiation);
          $this->initializing = FALSE;
        }
        // If the current interface language needs to be retrieved during
        // initialization we return the system language. This way string
        // translation calls happening during initialization will return the
        // original strings which can be translated by calling them again
        // afterwards. This can happen for instance while parsing negotiation
        // method definitions.
        elseif ($type == LanguageInterface::TYPE_INTERFACE) {
          return new Language(array('id' => LanguageInterface::LANGCODE_SYSTEM));
        }
      }
    }

    return $this->negotiatedLanguages[$type];
  }

Proposed resolution

A possible solution would be to maintain seperate flags for each language-type. Another would be to return the default-language during initialization straight away, without persisting it if we're initializing. The first approach seems the best.

Comments

danquah created an issue. See original summary.

danquah’s picture

Added patch that implements the first suggestion, maintaining seperate flags for each language-type.

danquah’s picture

Status: Active » Needs review
danquah’s picture

Title: language_url is never negotiated due to race-condition when a administrative language is set » Wrong url-language negotiated when the user has an admin-language
xano’s picture

StatusFileSize
new1.7 KB
new1.95 KB

I improved some code comments, and fixed the name of the variable passed on to unset(); For clarity, I also converted the associative array to use boolean values (the keys are important, the values aren't).

lauriii’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests

The fix itself looks good. I think we should add test coverage for this.

+++ b/core/modules/language/src/ConfigurableLanguageManager.php
@@ -90,11 +90,16 @@ class ConfigurableLanguageManager extends LanguageManager implements Configurabl
+   * An array of language-types currently being initialized, keyed by language-
+   * type.

The short description should fit on one line.

xano’s picture

Status: Needs work » Needs review
Issue tags: -Needs tests
StatusFileSize
new6.3 KB
new7.92 KB
new6.68 KB

This addresses all feedback from #6.

The last submitted patch, 7: drupal_2772891_7-test-only.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 7: drupal_2772891_7.patch, failed testing.

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new1.14 KB
new7.88 KB
new5.29 KB

Status: Needs review » Needs work

The last submitted patch, 10: drupal_2772891_9-test-only.patch, failed testing.

xano’s picture

Status: Needs work » Needs review
StatusFileSize
new5.29 KB
new7.88 KB
new1.14 KB

Apparently patch upload order matters?

The last submitted patch, 12: drupal_2772891_9-test-only.patch, failed testing.

danquah’s picture

FYI the test is failing because the prophecy language-object tracks the number of methodcalls made on int, which changes the object and leads to uasort complaining that the custom comparison-function over in Drupal\Core\Language\Language changes the array-elements it is comparing.

Apparently one way to fix it is to fall back to plain old phpunit mocks.

https://github.com/phpspec/prophecy/issues/161

lauriii’s picture

Status: Needs review » Needs work

I think the suggestion from @danquah is totally worth testing

danquah’s picture

OK, think I have a working test now, I'll post it later today.

danquah’s picture

Status: Needs work » Needs review
StatusFileSize
new7.94 KB
new7.86 KB
new9.77 KB

Changes

  • Added comments
  • Switched the mocking of LanguageInteface to use phpunit mocks
  • Reverted the Language::sort in the Language Manager back to emitting notices/warnings
  • Refactored the test to use inner/outer language instead of first/second. Felt that inner/outer better reflected the recursive issue we're attempting to test.

The last submitted patch, 17: keyed-language-negotiation-flags-2772891_17-test-only.patch, failed testing.

xano’s picture

StatusFileSize
new7.88 KB
new9.79 KB
new6.44 KB

@danquah: That is some excellent documentation you added there! Thank you for explaining our test approach so thoroughly!

I fixed some minor language issues, and I tried to clarify the comment on what exactly happens if Prophecy is used. I hope that's an improvement.

xano’s picture

StatusFileSize
new9.81 KB
new1.1 KB

Small docs update, because I misunderstood @danquah's research.

The last submitted patch, 19: drupal_2772891_19-test-only.patch, failed testing.

Version: 8.1.x-dev » 8.2.x-dev

Drupal 8.1.9 was released on September 7 and is the final bugfix release for the Drupal 8.1.x series. Drupal 8.1.x will not receive any further development aside from security fixes. Drupal 8.2.0-rc1 is now available and sites should prepare to upgrade to 8.2.0.

Bug reports should be targeted against the 8.2.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.3.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.2.x-dev » 8.3.x-dev

Drupal 8.2.6 was released on February 1, 2017 and is the final full bugfix release for the Drupal 8.2.x series. Drupal 8.2.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.3.0 on April 5, 2017. (Drupal 8.3.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.3.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.4.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

wannesdr’s picture

This issue is a duplicate of #2189267 . Does anyone know whether any work done here can be transferred?

Since it is a duplicate I will close it. Any new contributions or info must go to #2189267

wannesdr’s picture

Status: Needs review » Closed (duplicate)