fix: notification emails ignored the user's language and were sent in English

On the server only the default (English) translation bundle is loaded at
startup. Translating a notification to a user's language called
TAPi18n.__(key, params, lang) with lng=lang, but that resource bundle was
never added on the server, so i18next fell back to English — every
notification email went out in English regardless of the user's profile
language. (The client works because switching language calls setLanguage,
which loads the bundle.)

Add TAPi18n.ensureLanguageLoaded(language), which loads the bundle on
demand if i18next does not already have it, and await it before translating
in the email notification, EmailLocalization.sendEmail and outgoing webhook
paths.

Closes #5875

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lauri OjansivuandClaude Opus 4.8 committed 2026-06-23 15:37:56 +03:00
1 parent e5edb54a53
commit 22f996903b
4 files changed
+23 -1

No files matched your search

+10
View File
@@ -113,6 +113,16 @@ export const TAPi18n = {
await this.i18n.changeLanguage(language);
this.current.set(language);
},
// Make sure the resource bundle for `language` is available before a
// synchronous __() call. On the server only the default (English) bundle is
// loaded at startup, so translating to a user's language (e.g. notification
// emails) otherwise silently fell back to English. See #5875.
async ensureLanguageLoaded(language) {
if (!language || !this.i18n) return;
if (!this.isLanguageSupported(language)) return;
if (this.i18n.hasResourceBundle(language, DEFAULT_NAMESPACE)) return;
await this.loadLanguage(language);
},
// Return translation by key
__(key, options, language) {
this.current.dep.depend();
+4
View File
@@ -34,6 +34,10 @@ const EmailLocalization = {
lang = TAPi18n.getLanguage() || 'en';
}
// #5875: ensure the chosen language's bundle is loaded on the server before
// translating, otherwise __() falls back to English.
await TAPi18n.ensureLanguageLoaded(lang);
// Translate subject and text using the determined language
const subject = TAPi18n.__(options.subject, options.params || {}, lang);
let text = options.text;
+6 -1
View File
@@ -7,7 +7,7 @@ import { Notifications } from '/server/notifications/notifications';
// buffer each user's email text in a queue, then flush them in single email
Meteor.startup(() => {
Notifications.subscribe('email', (user, title, description, params) => {
Notifications.subscribe('email', async (user, title, description, params) => {
try {
if (
!user ||
@@ -19,6 +19,11 @@ Meteor.startup(() => {
return;
}
// #5875: the server only loads the English bundle at startup, so make sure
// the recipient's language is loaded before translating the subject/body —
// otherwise notification emails silently fall back to English.
await TAPi18n.ensureLanguageLoaded(user.getLanguage());
// add quote to make titles easier to read in email text
const quoteParams = { ...params };
['card', 'list', 'oldList', 'board', 'comment'].forEach(key => {
+3
View File
@@ -135,6 +135,9 @@ Meteor.methods({
if (!user || typeof user.getLanguage !== 'function') {
return;
}
// #5875: load the recipient's language bundle on the server before
// translating, otherwise it falls back to English.
await TAPi18n.ensureLanguageLoaded(user.getLanguage());
const descriptionText = TAPi18n.__(
description,
quoteParams,