Summary
The user_mail function respects the language value specfied by _user_mail_notify() for the text of user emails, but it ignores it for the tokens in emails, such as password recovery links. Therefore in some cases the text and token languages can be different. For example, you can get email content in french, but the password reset URL comes as "/en/user/reset/..." .
Cause
There are a total of three API setting $langcode along the way to set the language of recovery email.
- submitForm() in UserPasswordFrom.php, taking the current browsing langcode and passed to _user_mail_notify().
- _user_mail_notify in user.module line 1042, it has a condition written to use the $langcode as $account->getPreferredLangcode if not specified. Here is where it sends an email request by invoking MailManagerInterface::mail() service and passing $langcode in its parameter, which goes through user_mail (hook_mail) to modify it's email contents.
- user_mail in user.module line 772, it has set to retrieve it's email content base on getPreferredLangcode of User while token option was set to follow the $message[langcode] variable from MailManagerInterface::mail().
The descriptions above reveals the bug how email's text content doesn't align with content replaced by tokens (URL with langcode prefix in this case) is within user_mail, whereas it should use the langcode that _user_mail_notify() provided as said by @jonathanshaw #23.
To understand the details of discussion, you can start from #21.
Password reset urls and account preferred language
This issue does not fully address password recovery emails not respecting the user's preferred language, because the language is also set in UserPasswordForm::submitForm() and there is an old issue for that: #86287: Password reset process ignores the user's language preference.
Original Problem/Motivation
In the user_mail function, the language manager is set to the preferred langcode of the user.
$language = $language_manager->getLanguage($params['account']->getPreferredLangcode());
$original_language = $language_manager->getConfigOverrideLanguage();
$language_manager->setConfigOverrideLanguage($language);
In some cases if we load config, the mail_config is translated in the language of the user:
$mail_config = \Drupal::config('user.mail');
While loading the token options, an other langcode is set?!
Steps required to reproduce the bug
While sending a "Password Recovery" mail, the mail subject and body is in the language of the user.
The [user:one-time-login-url] token is in another language.
Solution, see patch.
Proposed resolution
New proposal
Quoting #23:
The only thing we need to fix is in user_mail():
$language = $language_manager->getLanguage($params['account']->getPreferredLangcode());
should become
$language = $language_manager->getLanguage($langcode);
user_mail() should use the langcode that _user_mail_notify() has provided.
Original proposal
Set the correct langcode in $token_options array:
$token_options = ['langcode' => $language->getId(), 'callback' => 'user_mail_tokens', 'clear' => TRUE];
See Patch.
Remaining tasks
None.
User interface changes
None
API changes
None
Data model changes
None.