From c01f807b44dd6c5fbff959c8efc155cf0d3e09b0 Mon Sep 17 00:00:00 2001 From: w3houde Date: Tue, 2 Jun 2026 04:39:08 +0700 Subject: [PATCH] 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 Co-authored-by: Dolicraft --- htdocs/core/class/html.formmail.class.php | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/htdocs/core/class/html.formmail.class.php b/htdocs/core/class/html.formmail.class.php index c546462f144..f15202c3861 100644 --- a/htdocs/core/class/html.formmail.class.php +++ b/htdocs/core/class/html.formmail.class.php @@ -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("/^(
)+/", "", $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 = ''; $out .= '';