Fix #34540 use template language for __(TranslationKey)__ substitution (#38546)

`make_substitutions()` resolves `__(TranslationKey)__` tokens via the
third $outputlangs argument, falling back to the global $langs (the
operator's language) when nothing is passed in. The mail-template
helpers in `html.formmail.class.php` already build the recipient
language at line 509-518 from `this->param['langsmodels']` (or from
the template's own `lang` column in `llx_c_email_templates`), but the
three call sites that turn the template body and subject into the
final preview dropped the argument:

- line 1041, when rebuilding repeated `__LINES__` from the template
  `content_lines`
- line 1086, when substituting the template body for the form preview
- line 1478, when substituting the topic in `getHtmlForTopic()`

So a translation key set up in two languages was always resolved
with the operator's locale, never the recipient's, even when the
operator picked the French template for a French customer (or when
the template carried an explicit `lang` column). The PDF flow was
already correct because it routes its substitutions through the
already-localised `$outputlangs` of the document generator.

Pass $outputlangs at the two existing sites in the main render path,
and compute it from the template's `lang` column in the helper
`getHtmlForTopic()` so the subject and the body are localised
together. Templates with no explicit `lang` still resolve via the
operator's language, preserving the prior behaviour for that case.

Signed-off-by: Dolicraft <contact@dolicraft.com>
Co-authored-by: Dolicraft <contact@dolicraft.com>
This commit is contained in:
w3houde 2026-06-02 04:39:08 +07:00 committed by GitHub
parent 026b93f3ee
commit c01f807b44
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1038,7 +1038,7 @@ class FormMail extends Form
$defaultlines = $arraydefaultmessage->content_lines;
if (isset($defaultlines)) {
foreach ($this->substit_lines as $lineid => $substit_line) {
$lines .= make_substitutions($defaultlines, $substit_line)."\n";
$lines .= make_substitutions($defaultlines, $substit_line, $outputlangs)."\n";
}
}
$this->substit['__LINES__'] = $lines;
@ -1083,7 +1083,10 @@ class FormMail extends Form
if (GETPOSTISSET("message") && !GETPOST('modelselected')) {
$defaultmessage = GETPOST("message", "restricthtml");
} else {
$defaultmessage = make_substitutions($defaultmessage, $this->substit);
// Pass $outputlangs so __(TranslationKey)__ in the template body is resolved
// in the language of the selected email template, not the operator's language
// (see issue #34540).
$defaultmessage = make_substitutions($defaultmessage, $this->substit, $outputlangs);
// Clean first \n and br (to avoid empty line when CONTACTCIVNAME is empty)
$defaultmessage = preg_replace("/^(<br>)+/", "", $defaultmessage);
$defaultmessage = preg_replace("/^\n+/", "", $defaultmessage);
@ -1460,7 +1463,7 @@ class FormMail extends Form
*/
public function getHtmlForTopic($arraydefaultmessage, $helpforsubstitution)
{
global $langs, $form;
global $conf, $langs, $form;
$defaulttopic = GETPOST('subject', 'restricthtml');
@ -1472,7 +1475,17 @@ class FormMail extends Form
}
}
$defaulttopic = make_substitutions($defaulttopic, $this->substit);
// Resolve __(TranslationKey)__ in the language of the selected template
// (see issue #34540). Falls back to the caller's language when the template
// has no explicit language pinned.
$outputlangs = $langs;
if (is_object($arraydefaultmessage) && !empty($arraydefaultmessage->lang)) {
$outputlangs = new Translate("", $conf);
$outputlangs->setDefaultLang($arraydefaultmessage->lang);
$outputlangs->load('other');
}
$defaulttopic = make_substitutions($defaulttopic, $this->substit, $outputlangs);
$out = '<tr>';
$out .= '<td class="fieldrequired">';