Problem/Motivation

This issues discussion has now changed it's focus to determine the users expectation on what language should the content of recovery email is when multiple language is setup. (email requested from /user/password or /fr/user/password). Currently it uses the language of the viewing page (active langcode) where reset email is requested as seen from the example below, but not the user's preferred langcode.

Current behaviour

The reset link provided will direct user according to the language of viewing /user/password page.

Example:
User A with language preference English
visit /zh/user/password => receive a reset content and link to /zh/user/reset...... that is Chinese.

Expected behaviour

User's preference should be respected. Therefore, we should be expecting email shown in English even when the reset request is sent on Chinese page.

Example:
User A with language preference English
visit /zh/user/password => receive a reset content and link to /en/user/reset. Be it English.

Another related issue upon the language of user's email text content not aligning with content/link from token_replacement is addressed in #2991677: Wrong language in token_options in user_mail function.

Original Problem/Motivation

The "reset password" page that comes up after clicking on a one-time login link uses the default site language instead of the user's language.

To reproduce this, you need the localization module activated, more than one language installed, and a user that has changed the language in their profile.

Hans

Steps to reproduce

  • New Drupal install
  • Enable language (language), interface translation(locale)
  • Add new language French, set path prefix for both languages (en, fr)
  • Translate the recovery email content of French without replacing the original tokens.
  • Set User Accounts language preference as English.
  • Log out, or use Incognito mode to request a reset email from French interface "/fr/user/password"
  • Receive email in English with reset link to French page fr/user/reset.
    After #2991677: Wrong language in token_options in user_mail function: Wrong language in token_options in user_mail function this will be consistent: email in English and reset link to English page /user/reset

Proposed resolution

1. #60, fix the submitForm of UserPasswordForm.php.

If you take a close look at user_pass_reset_url it already works with a langcode to translate the link. So the "problem" belongs on the Drupal\user\Form\UserPasswordForm::submitForm which use the UI langcode instead of User Preferred Langcode.

2. #78, fix LanguageRequestSubscriber.php of language module.
By taking the request url into part of the argument and determine whether user object should be set or not when visiting /user/password url, further used for the decision of langcode.

Remaining tasks

1. Decide the behaviour of returning language of email should be.
2. Decide the method to fix.
3. Add tests.
4. Review.

User interface changes

1. Language of Recovery Email's content and link will be altered.

API changes

Data model changes

Release notes snippet

CommentFileSizeAuthor
#128 interdiff_124-128.txt3.34 KBg-brodiei
#128 86287-128.patch5.12 KBg-brodiei
#124 interdiff_120-124.txt793 bytesravi.shankar
#124 86287-124.patch6.36 KBravi.shankar
#120 interdiff_116-120.txt1.11 KBravi.shankar
#120 86287-120.patch6.26 KBravi.shankar
#116 interdiff_112-116.txt773 bytesravi.shankar
#116 86287-116.patch6.23 KBravi.shankar
#112 interdiff-108_112.txt2.6 KBg-brodiei
#112 86287-112.patch6.24 KBg-brodiei
#108 86287-108.patch6.29 KBpaulocs
#108 interdiff-104-108.txt2.54 KBpaulocs
#104 interdiff_102_104.txt5.01 KBg-brodiei
#104 86287-104.patch6.29 KBg-brodiei
#104 86287-test_only-3.patch5.23 KBg-brodiei
#102 interdiff_99_102.txt6.26 KBg-brodiei
#102 86287-102.patch5.59 KBg-brodiei
#102 86287-test_only-2.patch4.52 KBg-brodiei
#97 86287-97.patch5.64 KBg-brodiei
#97 86287-97-test_only.patch4.76 KBg-brodiei
#93 86287-93.patch4.23 KBravi.shankar
#83 D8-1.png20.68 KBrohit-rajput-sahab
#78 translate-reset-password-without-test-86287-78.patch4.23 KBlexhouk
#60 86287-60.patch3.6 KBwengerk
#60 86287-60-test-only.patch2.6 KBwengerk
#59 86287-59-test-only.patch2.6 KBwengerk
#56 translate-reset-password-without-test-86287-56.patch4.28 KBlexhouk
#45 translate-reset-password-test-only-86287-45.patch3.06 KBdrupal_was_my_past
#45 translate-reset-password-with-test-86287-45.patch4.2 KBdrupal_was_my_past
#43 translate-reset-password-test-only-86287-43.patch3.09 KBdrupal_was_my_past
#43 translate-reset-password-with-test-86287-43.patch4.23 KBdrupal_was_my_past
#42 translate-reset-password-test-only-86287-42.patch3.11 KBdrupal_was_my_past
#42 translate-reset-password-with-test-86287-42.patch4.23 KBdrupal_was_my_past
#39 86287-translate-reset-password-page-global-5.patch4.4 KBalexpott
#33 86287-translate-reset-password-page-global-4.patch4.25 KBkrlucas
#32 86287-translate-reset-password-page-global-4.patch4.25 KBkrlucas
#30 86287-translate-reset-password-page-global-4.patch4.27 KBkrlucas
#25 86287-translate-reset-password-page-global-3.patch4.11 KBkrlucas
#21 86287-translate-reset-password-page-global-2.patch4.38 KBsvendecabooter
#15 86287-translate-reset-password-page-global.patch843 bytessvendecabooter
#14 86287-translate-reset-password-page-rev6.patch1.96 KBbrianV
#12 86287-translate-reset-password-page-rev5.patch2.15 KBbrianV
#11 86287-translate-reset-password-page-rev4.patch2.15 KBbrianV
#10 86287-translate-reset-password-page-rev3.patch1.08 KBbrianV
#7 86287-translate-reset-password-page-rev2.patch4.55 KBbrianV
#4 translate-reset-password-dialong.png15.75 KBbrianV
#3 86287-translate-reset-password-page.patch4.39 KBbrianV
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

ricabrantes’s picture

Version: 4.7.3 » 7.x-dev

any news about this?

LAsan’s picture

Bumping.

brianV’s picture

Category: bug » feature
Status: Active » Needs review
FileSize
4.39 KB

Patch attached to go a long way towards fixing this.

The problem is that the user is always anonymous when visiting the page, so the site's default language is always used.

In short, I use the code in the block below to get the user id from the URL, then get that user's preferred language, then pass the language to all calls to t(). This causes all the text to be translated to the user's preferred language.

//fetch uid from the URL, and use it to load the preferred language for the user trying to recover their password.
$uid = arg(2);
$user_language = user_preferred_language(user_load($uid));
$user_language = $user_language->language;

The only thing that doesn't translate is the title, because you can't pass t('Reset Password') as an argument to drupal_set_title(). The only way I can see to get around that is to write a new function for Locale which, when given a string, returns it's translation in the provided language. Then pass this function the string 'Reset Password', and pass the translation to drupal_set_title().

However, that will need to be part of another patch.

brianV’s picture

Attaching a screenshot to show the output. User requesting his password has 'Dutch' chosen as his profile language. Notice the title remains in English.

brianV’s picture

Issue tags: +Usability

Just tagging so this can be found in the 'Usability' queue.

brianV’s picture

I've decided to just include drupal_set_title(t('Reset Password', array(), $user_language)), and leave it up to the translators to add a translation for it.

Also, this new patch integrates the small change made in #455630: Remove HTML tags from t() call in user.pages.inc, so if this patch is included, that one can be closed.

brianV’s picture

Oops - including patch!

catch’s picture

Status: Needs review » Needs work
+  //fetch uid from the URL, and use it to load the preferred language for the user trying to recover their password.

This should start with a capital letter and needs a leading space.

Should we just set global $language instead of all the t() changes?

brianV’s picture

Heh - we don't need that kind of thinking here. :p

I will reroll it with that suggested change. It will make future changes simpler, since people can just use t() without worrying about sending the language parameter.

brianV’s picture

Status: Needs work » Needs review
FileSize
1.08 KB

Rerolled with suggested changes.

brianV’s picture

Final revision!

brianV’s picture

Famous last words. Final patch attached, this time without the added space.

Status: Needs review » Needs work

The last submitted patch failed testing.

brianV’s picture

Status: Needs work » Needs review
FileSize
1.96 KB

Rerolled for current head.

svendecabooter’s picture

The last patch is throwing errors on usage of the 3rd parameter to the t() function.
The 3rd parameter $user_language should become array('lang_code' => $user_language->language)

Attached is another approach to fix this solution, which doesn't require to add the 3rd parameter to each call of the t() function, but rather changes the full language interface to the preferred language of the user requesting a password reset.
I'm not sure whether that might introduce other problems, but on my testsite it seems to apply fine.

brianV’s picture

+++ modules/user/user.pages.inc	2 Nov 2009 14:46:52 -0000
@@ -77,6 +77,12 @@
   global $user;

Does the global $user item work here? Wouldn't the user be visiting the password reset page as anonymous?

If you can confirm that the global $user object is properly populated, then I would say this should be RTBC.

svendecabooter’s picture

The global $user object is used at that point to verify if a user arriving on the password reset page isn't logged in.
So if the $user object is indeed properly populated at that point, an error message is displayed. See comment at line 87 in user.pages.inc.
If the user is anonymous, the $user object will be the default $user object for anonymous users, with $user->uid being 0.

brianV’s picture

Status: Needs review » Reviewed & tested by the community

Doh, I misread the patch. For some reason, I thought you were getting the preferred language for the global $user, instead of the one loaded from the URL.

At any rate, I think this is good to go in.

salvis’s picture

I wanted to try this out, but I was unable to confirm that the 'Reset password' page really did come up in the user's language because I couldn't translate any strings because of #624926: "Translate interface" fails to show untranslated strings.

Thanks for working on it!

webchick’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs tests

I think it makes sense to have test coverage for this.

Can we also expand out the code comment to explain that the reason we're special-casing this page is because the user comes in as anonymous? Also, wrap at 80 chars please. :)

svendecabooter’s picture

Status: Needs work » Needs review
FileSize
4.38 KB

I updated the patch as per webchick's comments.
It's my first core testcase, so feedback is appreciated.

Status: Needs review » Needs work
Issue tags: -Usability, -Needs tests

The last submitted patch failed testing.

Status: Needs work » Needs review

svendecabooter requested that failed test be re-tested.

svendecabooter’s picture

krlucas’s picture

At some point after the original patch was rolled it seems like the global $language_interface was changed to just $language.

New patch attached.

alexpott’s picture

Patch in #25 works great. Manual tests and simpletest both work well.

alexpott’s picture

Status: Needs review » Reviewed & tested by the community

Updating to RTBC

Dries’s picture

Thanks for getting involved with Drupal core development at the core sprint here at DrupalCon. This is a good start but needs some further work. We'll help/guide you along the way.

+++ modules/user/user.pages.inc	2010-04-22 12:25:28.529949248 -0700
@@ -89,6 +89,14 @@ function user_pass_submit($form, &$form_
+  // Set the interface to the user's preferred language
+  // Since a user comes in anonymous on this page, we fetch the uid from the URL
+  // and retrieve their language settings based on that.
+  $uid = arg(2);
+  $user_language = user_preferred_language(user_load($uid));
+  $language = $user_language;

This code could actually be moved down into the else-case. We already load the $user object further down in the function so we should avoid loading it twice.

+++ modules/user/user.test	2010-04-22 12:21:18.535984887 -0700
@@ -1585,3 +1585,86 @@ class UserTokenReplaceTestCase extends D
+    // now add a translated string to the locale system

To comply with the guidelines, code comments should start with a capital letter, and end with a point. Various comments in this patch need to be updated.

Dries’s picture

Status: Reviewed & tested by the community » Needs work
krlucas’s picture

Status: Needs work » Needs review
FileSize
4.27 KB

Cleaned up the comments in the patch; moved the global language assignment into the if/else.

fending’s picture

Status: Needs review » Needs work

Some formatting issues; comments given directly to patch author @krlucas.

krlucas’s picture

removed spaces preceding line break.

p.s. not the patch author just the patch fixer ;)

krlucas’s picture

gah. missed one.

fending’s picture

Status: Needs work » Reviewed & tested by the community

That does it. Did manual testing and the automated .test works dandily. RTBC, thanks for playing.

svendecabooter’s picture

Issue tags: -Needs tests

Thanks krlucas for giving this some love :)

Georg’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Usability

The last submitted patch, 86287-translate-reset-password-page-global-4.patch, failed testing.

dddave’s picture

I guess this needs only a slight update to reflect some changes made to HEAD to get back to RTBC. Please, somebody with the skills to do so fix this patch so that this nasty issue is not included in D7. That would be terrible.

Good karma awaits the person who finishes this!

alexpott’s picture

Status: Needs work » Needs review
FileSize
4.4 KB

Re-rolled patch against latest Drupal cvs head...

dddave perhaps you can test it (for some karma)?

dddave’s picture

I am going to set up my first D7 test install tonight. I'll give it a test right after D7 is up and running.

dddave’s picture

I am currently strunggling to get my mail server going to test this out. I am not the brightest bulp in in this regard so this might take a while...

drupal_was_my_past’s picture

Moved this to 8.x. Re-rolled #39.

drupal_was_my_past’s picture

Status: Needs review » Needs work

The last submitted patch, translate-reset-password-with-test-86287-43.patch, failed testing.

drupal_was_my_past’s picture

Assigned: Unassigned » drupal_was_my_past
Status: Needs work » Needs review
FileSize
4.2 KB
3.06 KB

Re-roll patch from #43.

valthebald’s picture

Issue tags: -Usability

Status: Needs review » Needs work

The last submitted patch, translate-reset-password-with-test-86287-45.patch, failed testing.

valthebald’s picture

Status: Needs work » Needs review
Issue tags: +Usability
salvis’s picture

Status: Needs review » Needs work

There's no point in re-testing. The patch needs to be re-rolled.

klonos’s picture

Assigned: drupal_was_my_past » Unassigned

...perhaps this would help get a few more eyes on this.

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

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.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.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.

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

Drupal 8.3.6 was released on August 2, 2017 and is the final full bugfix release for the Drupal 8.3.x series. Drupal 8.3.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.4.0 on October 4, 2017. (Drupal 8.4.0-alpha1 is available for testing.)

Bug reports should be targeted against the 8.4.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.5.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.4.x-dev » 8.5.x-dev

Drupal 8.4.4 was released on January 3, 2018 and is the final full bugfix release for the Drupal 8.4.x series. Drupal 8.4.x will not receive any further development aside from critical and security fixes. Sites should prepare to update to 8.5.0 on March 7, 2018. (Drupal 8.5.0-alpha1 is available for testing.)

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

lexhouk’s picture

lexhouk’s picture

Status: Needs work » Needs review
joachim’s picture

Version: 8.5.x-dev » 8.7.x-dev
Status: Needs review » Needs work
Issue tags: +Needs reroll
+++ b/core/modules/language/src/EventSubscriber/LanguageRequestSubscriber.php
@@ -87,6 +109,16 @@ public function onContainerInitializeSubrequestFinished() {
+    if ($this->currentUser->isAnonymous()) {
+      $uri = $this->requestStack->getCurrentRequest()->getRequestUri();
+      if (preg_match('/^\/user\/reset\/(\d+)$/', $uri, $matches)) {
+        $account = $this->entityTypeManager->getStorage('user')
+          ->load($matches[1]);
+        if ($account) {
+          $this->currentUser = $account;
+        }
+      }
+    }

Putting a special case in this service for one path does not seem like a good idea at all.

Can we go back to the patch at #45, which needs a reroll?

wengerk’s picture

As suggested by #58, I restart from scratch (as the previous patch was 7 years ago).

To start with this issue, here the test-only so it obviously fail, to demonstrate the issue.
I will work on the necessary following patch on the next days (if nobody already creates the fix before ^^).

wengerk’s picture

After digging on the code of resetPassword, I found a solution to make it work properly, but it will certainly create some "side effects", let's tests them with testbot.

If you take a close look at user_pass_reset_url it already works with a langcode to translate the link. So the "problem" belongs on the Drupal\user\Form\UserPasswordForm::submitForm which use the UI langcode instead of User Preferred Langcode.

For now if you "reset your password" using the Spanish (es) langcode, then you will receive the mail in Spanish (es), even when the use has a preferred langcode. We maybe need to update the Issue summary to reflect this - it's not the default site language but UI..

Here is the idea behind my patch:

Use the preferred langcode when the user has one or use the UI langcode if the user never set his preferred langcode.
By doing this, I avoid using the default site language (which would be used if getPreferredLangcode return NULL) when the user go thought /es/user/password without having a preferred langcode. So I maintained the default behaviour - using the UI langcode when user has not preferred langcode.

Also, should we add some code coverage to asserts the following ?!
- When user has no preferred langcode & use a localised form of Reset Password (Eg. /es/user/password) it will receive a link in Spanish
- When a user has a preferred langcode & use a localised form of Reset Password (Eg. /es/user/password) it will receive a link in his preferred langcode.

Thanks for you review & your suggestions !

wengerk’s picture

Status: Needs work » Needs review
Issue tags: -Needs reroll

The last submitted patch, 60: 86287-60-test-only.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

salvis’s picture

Thanks for taking on this 12 year old issue, wengerk!

wengerk’s picture

joachim’s picture

Status: Needs review » Needs work
+++ b/core/modules/user/src/Form/UserPasswordForm.php
@@ -133,9 +133,16 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
+    // Get the UI langcode as default lang.
     $langcode = $this->languageManager->getCurrentLanguage()->getId();
 
-    $account = $form_state->getValue('account');
+    // When the user has a preferred lang, use it instead of UI lang.
+    if ($account->getPreferredLangcode()) {
+      $langcode = $account->getPreferredLangcode();
+    }

I look at this code, and I think: surely there must be something that encapsulates this logic, so we don't have to do it?

Isn't this the job of the language negotiation system? Is this code in LanguageNegotiationUser perhaps not working because on the reset password page the user isn't logged in yet?

  public function getLangcode(Request $request = NULL) {
    $langcode = NULL;

    // User preference (only for authenticated users).
    if ($this->languageManager && $this->currentUser->isAuthenticated()) {
      $preferred_langcode = $this->currentUser->getPreferredLangcode(FALSE);
      $languages = $this->languageManager->getLanguages();
      if (!empty($preferred_langcode) && isset($languages[$preferred_langcode])) {
        $langcode = $preferred_langcode;
      }
    }
Dajka’s picture

Im using only one language on my site. (not english) I try to overwrite the system emails sent to users when an account gets activated, it is overwitten on both the /admin/config/people/accounts and also the /admin/config/regional/translate admin pages.

The problem is that all the emails gets sent out as the original translaton, and not the overwritten. Only the pending admin approval email gets sent as the one I entered. all the others stay the same.

I hope this is related and helps to figure out the issue.

salvis’s picture

@Dajka: no, I don't think so, but thank you for looking through the existing issues.

Please create a new issue, and be sure to select the Drupal version that you have.

wengerk’s picture

Status: Needs work » Needs review

Hey @joachim, thanks for your message in #65.

Indeed, it can't work because the code in LanguageNegotiationUser required the user to be logged in.

Do you have any idea how we can continue on this issue - in the case my solution in #60 does not fit ?

alienzed’s picture

Here's a question, why does "the code in LanguageNegotiationUser required the user to be logged in"? Anonymous users need LanguageNegotiation too.

That's the bug...

wengerk’s picture

@alienzed I agree with you, but it's another question. That this issue can't resolve whiteout going out-of-scoop. Should we then open a new Issue & add it as blocker for this one ?

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

salvis’s picture

Anonymous users need LanguageNegotiation too.

That's the bug...

This issue is about a special edge case where we know who the user is (we have to assume we know, for the purpose of sending the password reset email), even though they're not logged in, and they have their preferred language set in their profile.

Is there any justification for not sending the email in their preferred language? I don't think so. I don't think language negotiation should play any role here, but if we really need to have it, would it make sense to temporarily impersonate the user for this purpose?

alienzed’s picture

All good questions... Personally I resolved my issue by following the pack and making sure my user has the right language set (although in my case, since I am actually manipulating the user for this request, I set the language the page is in to the profile right before calling _user_mail_notify())

@wengerk's suggestion of opening a new issue and referencing it as blocking is probably the way to go, since, again, why should anonymous users not have LanguageNegotiation? Unfortunately I have no appetite to push on that at this point.

As for this specific issue, what do we think is more appropriate?
a) The language of the page
b) The language in the user profile

Of course the profile is a more authoritative data point, but that is assuming their language preference truly reflects what the user wants and not what was set and forgotten by default during initial registration
Then, what makes more sense for someone who has clearly lost access to their account, perhaps due to a relatively long absence from the site. Can we take anything from the fact they are browsing the site in a specific language? Furthermore, wouldn't it be a bit weird to request the reset in english, and then to get the email in french (or whatever languages are available...)

Think about that as if it were a human to human interaction. You walk into a store you've been to many times, they know you can speak french as you have spoken french in the past (this is your preference as far as they are concerned), you go up to the counter and ask the cashier a question in English. Should the cashier respond in English or in French?
English I think is the intuitive 'human' choice here, because it is immediately obvious that the person speaks English and they've chosen their most up to date language preference by means of speaking in that language.
French is the OCD technical guy's choice, being a configuration setting that should be respected.

So personally I think the page's language is the intuitive authority here, just for conversation's sake :)

Thoughts?

salvis’s picture

We probably agree that the domain prefix and path methods of language selection should be respected, if the site supports them. In the absence of these two methods, "the language of the page" for the anonymous user is the default language or the browser language.

I would not change "the language of the page" after the user has entered their email address, because that would confirm that the email address is known on the page, but for the purpose of sending the recovery email, we know what profile to use.

So the question is only relevant between browser language and profile setting, and I guess it depends on which one you prioritize on your site.

You seem to prioritize the browser language before the user profile, so for your users the site's language changes with the browser, and the users have no control through the profile. Let's assume for a moment that your site supports English and Chinese. User A comes in speaking Chinese (having their browser set to Chinese) and enters user B's email address. User B, who doesn't understand Chinese, gets a Chinese email from your site, and they'll be scared.

I prioritize the user profile above the browser, because I believe that people should be allowed to choose their preferred language for my site (maybe different for different sites!) without having to change their browser settings. If they're used to interact with my page in French, they expect to get email in French. It's OK if the login page is in some other language (they're used to that), but it's not OK when they're nervous because they can't log in, and they get recovery instructions in the wrong language.

I set the language the page is in to the profile right before calling _user_mail_notify())

So, actually, you're in the same camp as I am — why are you reasoning the other way? "just for the conversation's sake"? This doesn't help this 12-year old issue to make progress...

alienzed’s picture

just to be clear, by:

I set the language the page is in to the profile right before calling _user_mail_notify())

I meant that ultimately I enforce the language as defined by path prefix. I set That language to the user's profile right before sending the email, so as to override their profile's preference.

I think this really depends on the situation. Maybe it should be a setting...

salvis’s picture

@alienzed:

I meant that ultimately I enforce the language as defined by path prefix.

If Anonymous comes in with a path prefix or a domain prefix, I'd expect the email to be sent in that language for sure, if that's how you've configured your language detection priorities.

If it isn't then that's a clear bug, but it's not the topic of this thread. This thread is about having the site configured to use the profile language but the profile language being ignored when sending the password recovery email. The OP specifically says "user that has changed the language in their profile."

So, please stop derailing this thread.

alienzed’s picture

Are the issues not directly related? No one but a user can request a password reset email, and users all have a default language preference set on account creation. Thus there is always a language preference, and password reset emails only work on accounts, which have the preference, thus

If Anonymous comes in with a path prefix or a domain prefix, I'd expect the email to be sent in that language for sure, if that's how you've configured your language detection priorities.

is not how it works now. The Profile Preference is taken instead of the page's language as defined by path prefix/domain/whatever. I did not mean to derail the thread at all, I arrived here because I had the same problem and had to fix the user's profile programmatically prior to sending the email, because it was not respecting the page's language.

lexhouk’s picture

sano’s picture

I re-rolled the #39 (Drupal 7) patch for the D 7.67 version. Not sure if it is OK to post it here.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

sumithb’s picture

The patch #78 is not working in Drupal-8.7.10. An error is appearing.

Too few arguments to function Drupal\\language\\EventSubscriber\\LanguageRequestSubscriber::__construct(), 4 passed in /var/www/html/[rootfolder]/core/lib/Drupal/Component/DependencyInjection/Container.php on line 281 and exactly 6 expected in /var/www/html/[rootfolder]/core/modules/language/src/EventSubscriber/LanguageRequestSubscriber.php on line 81

rohit-rajput-sahab’s picture

I'm also facing this issue in Drupal Version 8.7.5.

Please provide a good solution.

rohit-rajput-sahab’s picture

FileSize
20.68 KB

During the user registration, If the user goes through the 'Arabic' language then he/she will receive all emails in 'Arabic' language. For more information see the below attached file.

rohit-rajput-sahab’s picture

Status: Needs review » Reviewed & tested by the community

All emails are sent as per the user's preferred language.

For example:-
The user has registered a new account of the german language. In the user profile, german language is set automatically by language module. So, all emails are sent to german language.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
Issue tags: +Needs issue summary update

@rohit-drupal I'm not sure about the solution in #78 as @joachim points out #58 putting a special case in the service feels wrong. #60 was taking another approach that looks more correct. Looking the discussion after that patch I think what we need to need is agree what the behaviour should be - and detail what the current behaviour is for what we want to fix - in the issue summary.

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

jonathanshaw’s picture

#2991677-13: Wrong language in token_options in user_mail function has a test that is not needed for that issue in the end, but could be useful to this issue.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

g-brodiei’s picture

After working on #2991677: Wrong language in token_options in user_mail function, we discovered and fixed the bug to ensure both email content and token content reflects the same $langcode for any email sent by user_mail.

The fix on #60 looks more right with me as well, submitForm() it should not be passing the browsers language to _user_mail_notify() when we're expecting to see the user's preferred language on return.

If we can agree on this direction, I'd be happy to push out a patch base on #60, or we can review it again.

jonathanshaw’s picture

Issue tags: +Bug Smash Initiative

I think #60 is still too complex.
1) We don't infer the user's preferred language from the current language when registering a user, so why do we do it when resetting a password? It seems to be a badly-implemented edge-case feature.
2) #74 makes an excellent point:

Let's assume for a moment that your site supports English and Chinese. User A comes in speaking Chinese (having their browser set to Chinese) and enters user B's email address. User B, who doesn't understand Chinese, gets a Chinese email from your site, and they'll be scared.

I think it best to just remove any code in UserPasswordForm about the current language and leave it to _user_mail_notify() to send email in user's language.

If a site really wants to have emails sent in the browsing language if the user has no preferred language, then they can swap out the User entity class and override the getPreferredLangcode() method. A simple case for contrib/custom.

My bigger concern is with the language of the password reset email. The IS only mentions the language of the page the user visits after clicking on the one-time login url in the email. This is the "token language" that @g-brodiei fixed in #2991677: Wrong language in token_options in user_mail function. But as I understand it, I would have expected the current code to result in the text of the email to be in the language the visitor was browsing in not the user's preferred language. Can someone confirm this is what happens, or point me to a test about this?

g-brodiei’s picture

Update issue summary.

g-brodiei’s picture

ravi.shankar’s picture

Here I have just added reroll of patch #78, it still NW as per comment #85.

jonathanshaw’s picture

Discussed with @pameeela, @g-brodiei and Kristen pol on slack. A few things emerged:

- when a user registers, their browsing language at registation becomes their account preferred language
- this means that the only people without a preferred language are those from before language is enabled. They're a real edge case, and it's not unreasonable to assume they're comfortable in the site default language.
- another scenario related to #74 where using the browsing language as the password reset language is problematic is remote support, where someone other than the user tries to help them by triggering a password reset for them

Given this, I think we should ignore browsing language entirely. It's simpler, safer and will very rarely be a significant problem.

Additionally, the IS says:

Receive Email reset link of French, but not English, while the email content itself is French.
(This is the original behaviour prior to fix of #2991677: Wrong language in token_options in user_mail function, i

But shouldn't it say:

Receive email in English with reset link to French page fr/user/reset.
After #2991677: Wrong language in token_options in user_mail function this will be consistent: email in English and reset link to English page /user/reset

g-brodiei’s picture

Issue summary: View changes

Thank you for summarising the discussion on slack @jonathanshaw!

Yeah, it's much clearer the way you rephrase the sentence~ thanks.

Updated the IS.

g-brodiei’s picture

Assigned: Unassigned » g-brodiei
Issue summary: View changes
g-brodiei’s picture

Issue summary: View changes
Status: Needs work » Needs review
FileSize
4.76 KB
5.64 KB

Providing patch base on the approach to:

1. Use the default language instead of browser language when user accounts preferred language is not set.
2. Use the preferred language when it's set, assure the reset URL respects it.
3. Set the langcode correctly from submitForm() in UserPasswordForm.php

Notice:
1.The method $account->getPreferredLangcode() will automatically fallback to the site's default language by it's default. It will only return empty string when the argument passed in is false. therefore, we don't need to set a condition check in submitForm().

// Set default langcode is "en" and preferred_langcode is "".

// gets "en",
$account->getPreferredLangcode()

// gets "", when default langcode is "en" and preferred_langcode is "".
$account->getPreferredLangcode(false)

Updated IS on task to do.

g-brodiei’s picture

Assigned: g-brodiei » Unassigned

The last submitted patch, 97: 86287-97-test_only.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

jonathanshaw’s picture

Title: "reset password" page ignores the user's language selection » Password reset page ignores the user's language preference
Category: Feature request » Bug report
Status: Needs review » Needs work

Yay! Looking good.

  1. +++ b/core/modules/user/src/Form/UserPasswordForm.php
    @@ -168,9 +168,11 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
    +    // $langcode will fallback to default langcode if property preferred_lancode is not set.
    +    $langcode = $account->getPreferredLangcode();
    ...
         $mail = _user_mail_notify('password_reset', $account, $langcode);
    

    Do we need to do this at all? Can't we leave it to _user_mail_notify() to handle langcode entirely?

  2. +++ b/core/modules/user/tests/src/Functional/UserPasswordResetTest.php
    @@ -205,6 +213,88 @@ public function testUserPasswordReset() {
    +  public function testUserPasswordResetPreferredLanguage() {
    

    There's quite a lot of repetition in this test. It feels like either (a) it should be 3 different tests, with some shared logic in setUp() and some helper methods, or (b) it needs an @dataProvider.

  3. +++ b/core/modules/user/tests/src/Functional/UserPasswordResetTest.php
    @@ -205,6 +213,88 @@ public function testUserPasswordReset() {
    +    $this->assertStringNotContainsString("$default_language_code/user/reset/", $resetURL);
    

    Let's assert what it does contain as well.

  4. +++ b/core/modules/user/tests/src/Functional/UserPasswordResetTest.php
    @@ -205,6 +213,88 @@ public function testUserPasswordReset() {
    +    ¶
    ...
    +    // ¶
    ...
    +  ¶
    

    Extra spaces at end of line I think

  5. +++ b/core/modules/user/tests/src/Functional/UserPasswordResetTest.php
    @@ -205,6 +213,88 @@ public function testUserPasswordReset() {
    +    // Test reset URL respects preferred language over other lanugage settings.
    

    spelling "language"

  6. +++ b/core/modules/user/tests/src/Functional/UserPasswordResetTest.php
    @@ -205,6 +213,88 @@ public function testUserPasswordReset() {
    +    $this->assertMailString('body', '/fr/user/reset', 1);
    

    I find it curious this does not use $resetURL, maybe there's a good reason.

  7. Needs a coding standards check
jonathanshaw’s picture

Title: Password reset page ignores the user's language preference » Password reset process ignores the user's language preference
g-brodiei’s picture

1. Removed $langcode from formSubmit() as we do not need it to make use of preferred_langcode from account, it is determined automatically within _user_mail_notify. (Thanks for the suggestion! totally missed that.)
2. Combined repeated code to two helper method to help with assertion.
3. Fixed coding standard, removed empty space.

jonathanshaw’s picture

  1. I'm afraid I still find the test a little difficult to understand, I think a committer might not be happy with it. Could we make it work with a simple data provider like this?
    [
      [
        'browsing_lang' => NULL,
        'account_lang' => NULL,
        'expected_reset_lang' => 'en',
      ],
      [
        'browsing_lang' => NULL,
        'account_lang' => 'zh',
        'expected_reset_lang' => 'zh',
      ],
      [
        'browsing_lang' => 'zh',
        'account_lang' => NULL,
        'expected_reset_lang' => 'en',
      ],
      [
        'browsing_lang' => 'zh',
        'account_lang' => 'zh',
        'expected_reset_lang' => 'zh',
      ],
      [
        'browsing_lang' => 'zh',
        'account_lang' => 'fr',
        'expected_reset_lang' => 'fr',
      ],
    ]

    It would be so much easier to understand the intention of the test.

  2. +++ b/core/modules/user/tests/src/Functional/UserPasswordResetTest.php
    @@ -205,6 +213,88 @@ public function testUserPasswordReset() {
    +    // Unset language prefixes.
    +    $config = $this->config('language.negotiation');
    +    $config->set('url.prefixes', [])->save();
    

    I'm not familiar with language testing, but I'm surprised if these lines are needed. The test isolation is usually pretty good even without explicit resetting like this.

  3. +++ b/core/modules/user/tests/src/Functional/UserPasswordResetTest.php
    @@ -205,6 +213,88 @@ public function testUserPasswordReset() {
    +   * default lanugage is different.
    

    spelling: 'language' not 'lanugage'

g-brodiei’s picture

Finally got this into a dataprovider, just too many variables mixed together LoL!

Gonna cross my finger and see how this one goes!

1. Removed repetitive code from test, and use dataprovider to loop through all assertions.
2. Removed final config reset of language.negotiation and prefixes.

g-brodiei’s picture

Status: Needs work » Needs review

The last submitted patch, 104: 86287-test_only-3.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

paulocs’s picture

Assigned: Unassigned » paulocs
paulocs’s picture

I got:

86287-104.patch:57: trailing whitespace.
   * 
86287-104.patch:81: trailing whitespace.
    
86287-104.patch:85: trailing whitespace.
  
warning: 3 lines add whitespace errors.

when applying patch #104 so I'm attaching new patch to fix code standard.

Except that, I think patch fixes the problem and it looks good to me.

g-brodiei’s picture

Ahhhh! Coding standards! The spaces, thanks @paulocs!

jonathanshaw’s picture

Nice!

+++ b/core/modules/user/tests/src/Functional/UserPasswordResetTest.php
@@ -205,6 +213,108 @@ public function testUserPasswordReset() {
+    $this->account->preferred_langcode = $setPreferredLangcode;
+    $this->account->save();
+    $this->assertSame($actualPreferredLangcode, $this->account->getPreferredLangcode(FALSE));
...
+        'setPreferredLangcode' => 'null',

Should preferred langcode really be the string 'null'? I'd have expected

if (!empty$setPreferredLangcode)) {
  this->account->preferred_langcode = $setPreferredLangcode;
  $this->account->save();  
  $this>assertSame($actualPreferredLangcode, $this->account->getPreferredLangcode(FALSE));
}

In which case there's no need for $actualPreferredLangcode at all, you can simpy do

$this>assertSame($setPreferredLangcode, 
$this->account->getPreferredLangcode(FALSE));
g-brodiei’s picture

Assigned: paulocs » g-brodiei
g-brodiei’s picture

Wow, thanks for the suggestion @jonathanshaw ! The code looks much cleaner now

Thanks for pointing out the NULL settings for preferred language. For this, under the suggestion you made to remove $actualPreferredLangcode, I changed the test below for preferred langcode to set as an empty string instead of 'null' string.

'Test language prefix set as zh, visiting zh with preferred language as \'\'' => [
        'setPreferredLangcode' => '',
        'activeLangcode' => 'zh-hant'

1. Made change base on suggestions of #110
2. Update test description in dataprovider to fit each scenario

g-brodiei’s picture

Assigned: g-brodiei » Unassigned
jonathanshaw’s picture

Status: Needs review » Reviewed & tested by the community

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 112: 86287-112.patch, failed testing. View results

ravi.shankar’s picture

Status: Needs work » Needs review
FileSize
6.23 KB
773 bytes

Here I have fixed failed test of patch #112.

jonathanshaw’s picture

Status: Needs review » Reviewed & tested by the community

Thanks @ravi.shankar

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 116: 86287-116.patch, failed testing. View results

jonathanshaw’s picture

Issue tags: +Novice

drupalGetHeader() is deprecated in drupal:9.2.0 and is removed from drupal:10.0.0. Use $this->getSession()->getResponseHeader() instead. See https://www.drupal.org/node/3168383
4x in UserPasswordResetTest::testUserPasswordResetPreferredLanguage from Drupal\Tests\user\Functional

Looks like we need to chase HEAD.

ravi.shankar’s picture

Status: Needs work » Needs review
FileSize
6.26 KB
1.11 KB

Fixed failed test of patch #116.

quietone’s picture

Status: Needs review » Needs work

I just skimmed the patch and notices a missing doc bloc.

+++ b/core/modules/user/tests/src/Functional/UserPasswordResetTest.php
@@ -205,6 +213,105 @@ public function testUserPasswordReset() {
+  public function languagePrefixTestProvider() {

Needs a doc bloc.

Leaving novice tag for this work. If you make this change remember to remove the novice tag. thx.

ayushmishra206’s picture

Assigned: Unassigned » ayushmishra206
ayushmishra206’s picture

Assigned: ayushmishra206 » Unassigned
ravi.shankar’s picture

Status: Needs work » Needs review
Issue tags: -Novice
FileSize
6.36 KB
793 bytes

Addressed comment #121.

jonathanshaw’s picture

Status: Needs review » Reviewed & tested by the community

Thanks!

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/modules/user/src/Form/UserPasswordForm.php
    @@ -168,11 +168,9 @@ public function validateForm(array &$form, FormStateInterface $form_state) {
    -    $mail = _user_mail_notify('password_reset', $account, $langcode);
    

    This is the only place in core that passes in a langcode that is not the preferred langcode. This issue seems to suggest that all email should be sent to a user with their preferred langcode - which makes sense to me. We should file a follow-up to discuss deprecating this argument.

  2. +++ b/core/modules/user/tests/src/Functional/UserPasswordResetTest.php
    @@ -205,6 +213,110 @@ public function testUserPasswordReset() {
    +    \Drupal::configFactory()->reset('language.negotiation');
    

    This is not necessary. We're rebuilding the container in the next line.

  3. +++ b/core/modules/user/tests/src/Functional/UserPasswordResetTest.php
    @@ -205,6 +213,110 @@ public function testUserPasswordReset() {
    +    // Test Default langcode is different from active langcode when visiting different.
    +    if ($setPreferredLangcode !== 'en') {
    +      $this->assertLangcodeDifferent($prefix, $activeLangcode, 'en');
    +    }
    +
    +    // Test password reset with language prefixes.
    +    $this->assertLanguagePrefixResetUrl($visitingUrl, $expectedResetUrl, $unexpectedResetUrl);
    

    I think adding the assert functions makes it harder to work out what is being tested. Generally we should only add assert functions when they are called multiple times like in a loop and that's not happening here.

  4. +++ b/core/modules/user/tests/src/Functional/UserPasswordResetTest.php
    @@ -205,6 +213,110 @@ public function testUserPasswordReset() {
    +    $this->assertSame($activeLangcode, $this->getSession()->getResponseHeader('Content-language'));
    +    $this->assertSame($defaultLangcode, $this->languageManager->getDefaultLanguage()->getId());
    +    $this->assertNotSame($this->languageManager->getDefaultLanguage()->getId(), $this->getSession()->getResponseHeader('Content-language'));
    

    Given the two assertSame()s the assertNotSame seems superfluous.

  5. This behaviour was introduced in #82499: Email text posting in the user's language - which is the issue that introduce the idea of a preferred user langcode! I think it was done to maintain existing behaviour. And not really an active choice. Pinging @Gábor Hojtsy...
Gábor Hojtsy’s picture

I cannot think of why we decided in #82499: Email text posting in the user's language to send the pw reset email in the page language. The only case would be if new users are not saved with the right (page language) code when they get created. Then you would get the wrong email if we use the user preferred langcode. But I believe that is not the case, so this behaviour change is definitely good.

g-brodiei’s picture

Status: Needs work » Needs review
FileSize
5.12 KB
3.34 KB

Updated test code base on #126, thanks!

jonathanshaw’s picture

jonathanshaw’s picture

Status: Needs review » Reviewed & tested by the community
alexpott’s picture

Updating credit for reviews that made reviews that affected the patch.

alexpott’s picture

Version: 9.2.x-dev » 9.1.x-dev
Status: Reviewed & tested by the community » Fixed

Committed and pushed a719eefbf1 to 9.2.x and b2984279e6 to 9.1.x. Thanks!

  • alexpott committed a719eef on 9.2.x
    Issue #86287 by g-brodiei, brianV, drupal_was_my_past, ravi.shankar,...

  • alexpott committed b298427 on 9.1.x
    Issue #86287 by g-brodiei, brianV, drupal_was_my_past, ravi.shankar,...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

Utilvideo’s picture

Hi, about this patch, we on anonymous always send English mail (EN is default, but page is current language is RO)

jonathanshaw’s picture

This issue is closed. Please open a new issue if you have a bug to report or feature to request.

Please state clearly (a) what the current behaviour is and (b) what you think the behaviour should be.