From 9ecdb0ae26936d2456e3d5e88b0c914b26606ade Mon Sep 17 00:00:00 2001 From: Michael Mayer Date: Tue, 23 Jun 2026 09:06:46 +0000 Subject: [PATCH] UX: Render backend notifications in the current UI locale #5682 Backend *Msg helpers now publish the untranslated message id and params alongside the rendered fallback; the frontend translates the id and applies a positional-placeholder shim so notifications render in each user's UI language. --- frontend/src/common/gettext.js | 35 +++++++++++ frontend/src/component/notify.vue | 7 ++- frontend/tests/vitest/common/gettext.test.js | 39 ++++++++++++ internal/event/publish.go | 18 ++++-- internal/event/publish_test.go | 63 ++++++++++++++++++++ pkg/i18n/i18n.go | 6 ++ pkg/i18n/i18n_test.go | 15 +++++ 7 files changed, 177 insertions(+), 6 deletions(-) create mode 100644 frontend/tests/vitest/common/gettext.test.js create mode 100644 internal/event/publish_test.go diff --git a/frontend/src/common/gettext.js b/frontend/src/common/gettext.js index 2a3ffc2d0..8093654fc 100644 --- a/frontend/src/common/gettext.js +++ b/frontend/src/common/gettext.js @@ -21,6 +21,35 @@ function interpolate(message, params = {}) { }); } +// interpolatePositional substitutes Go printf verbs (%s, %d, %v, %%, …) sequentially from an +// ordered params array, bridging the backend's positional placeholders to the frontend runtime. +function interpolatePositional(message, params) { + if (message === null || message === undefined) { + return ""; + } + + const text = String(message); + + if (!Array.isArray(params) || params.length === 0) { + return text; + } + + let i = 0; + + return text.replace(/%(%|[-+ 0#]*\d*(?:\.\d+)?[a-zA-Z])/g, (match, verb) => { + if (verb === "%") { + return "%"; + } + + if (i >= params.length) { + return match; + } + + const value = params[i++]; + return value === undefined || value === null ? "" : String(value); + }); +} + export let gettext = { $gettext: (msgid, params) => interpolate(msgid, params), $ngettext: (msgid, plural, n, params) => interpolate(n > 1 ? plural : msgid, params), @@ -32,6 +61,12 @@ export function T(msgid, params) { return gettext.$gettext(msgid, params); } +// Tp translates a backend message by its English source id, then substitutes the ordered +// positional params, so backend notifications render in the current UI language. +export function Tp(msgid, params) { + return interpolatePositional(gettext.$gettext(msgid), params); +} + export function $gettext(msgid, params) { return gettext.$gettext(msgid, params); } diff --git a/frontend/src/component/notify.vue b/frontend/src/component/notify.vue index c93555437..1101e0bea 100644 --- a/frontend/src/component/notify.vue +++ b/frontend/src/component/notify.vue @@ -24,6 +24,8 @@