From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [IPv6:2a01:7e0:0:424::9]) by lore.proxmox.com (Postfix) with ESMTPS id 960271FF145 for ; Thu, 05 Feb 2026 14:15:07 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id E9E90102F0; Thu, 5 Feb 2026 14:15:38 +0100 (CET) From: Maximiliano Sandoval To: Kefu Chai Subject: Re: [PATCH proxmox-i18n v1] add pgettext() and npgettext() support for context-aware translations In-Reply-To: <20260205101613.1067594-2-k.chai@proxmox.com> (Kefu Chai's message of "Thu, 5 Feb 2026 18:16:14 +0800") References: <20260205101613.1067594-2-k.chai@proxmox.com> User-Agent: mu4e 1.12.9; emacs 30.1 Date: Thu, 05 Feb 2026 14:15:33 +0100 Message-ID: MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Bm-Milter-Handled: 55990f41-d878-4baa-be0a-ee34c49e34d2 X-Bm-Transport-Timestamp: 1770297256269 X-SPAM-LEVEL: Spam detection results: 0 AWL 0.086 Adjusted score from AWL reputation of From: address BAYES_00 -1.9 Bayes spam probability is 0 to 1% DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment KAM_SHORT 0.001 Use of a URL Shortener for very short URL RCVD_IN_VALIDITY_CERTIFIED_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_RPBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. RCVD_IN_VALIDITY_SAFE_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to Validity was blocked. See https://knowledge.validity.com/hc/en-us/articles/20961730681243 for more information. SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record Message-ID-Hash: 5MNVKJUSCBM4PZHKY7BP2BGBVY4MAMQN X-Message-ID-Hash: 5MNVKJUSCBM4PZHKY7BP2BGBVY4MAMQN X-MailFrom: m.sandoval@proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: pve-devel@lists.proxmox.com X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox VE development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: Kefu Chai writes: Thanks for this! I will need more time to test and review this, but some small comments bellow. > This commit adds message context (msgctxt) support to the JavaScript > i18n tooling, enabling pgettext() and npgettext() functions. This > allows different translations for the same English word based on usage > context (e.g., "Save" in a menu vs "Save" for disk operations). While this is an interesting possibility, it is more interesting to use it when, for example, one use is a verb and another is for a noun, or when there are two different nouns altogether, as for example "Monitor" which is used indiscriminately for "Ceph Monitors" or "QEMU Monitors" in the web UI. > Changes: > - po2js.pl: Add fnv31a_ctxt() to hash context+msgid combinations > - po2js.pl: Change from load_file_ashash to load_file_asarray to > properly handle multiple msgid entries with different contexts > - po2js.pl: Add pgettext() and npgettext() JavaScript functions > - Makefile: Add --keyword flags for xgettext to extract context > > Implementation details: > - Uses composite hash: hash(msgctxt + "\x04" + msgid) > - \x04 is the standard gettext EOT separator > - Backward compatible: messages without context use hash(msgid) > - Same flat catalog structure, no nesting required > > JavaScript API: > pgettext(context, msgid) > - Translates message with context > - Example: pgettext("menu", "Save") vs pgettext("disk", "Save") > - Returns msgid if no translation found > > npgettext(context, singular, plural, n) > - Translates message with context and plural support > - Example: npgettext("file", "1 file", "{n} files", count) > - Returns appropriate singular/plural form based on n > > PO file format: > msgctxt "menu" > msgid "Save" > msgstr "Guardar" > > msgctxt "disk operation" > msgid "Save" > msgstr "Almacenar" > > # Without context (traditional) > msgid "Save" > msgstr "Salvar" > > Extraction: > xgettext now recognizes: > --keyword=3Dpgettext:1c,2 # 1c =3D context, 2 =3D msgid > --keyword=3Dnpgettext:1c,2,3 # 1c =3D context, 2 =3D singular, 3 = =3D plural > > Backward compatibility: > - Existing PO files without msgctxt work unchanged > - Existing gettext()/ngettext() calls work unchanged > - Hash values for non-context messages are identical > - Same catalog file format > > Tested with following steps: > Modified pve-manager submodule: > File: pve-manager/www/manager6/button/ConsoleButton.js > - Changed gettext('Console') =E2=86=92 pgettext('button', 'Console') > - Added pgettext('console menu', 'noVNC/SPICE/xterm.js') > - Added npgettext('console', '{0} console', '{0} consoles', count) > > Ran 'make update_pot' to verify xgettext extraction: > Successfully extracted msgctxt entries to pve-manager.pot: > msgctxt "button" > msgid "Console" > > msgctxt "console menu" > msgid "noVNC" > > msgctxt "console" > msgid "{0} console" > msgid_plural "{0} consoles" > > Created test PO file with context translations and verified po2js.pl > generates correct context-aware hashes: > - hash("button" + "\x04" + "Console") =3D 914449940 > - hash("console menu" + "\x04" + "noVNC") =3D 418124897 > - Different contexts produce unique hashes for same msgid > > All tests passed: > - xgettext extracts msgctxt from JavaScript > - po2js.pl processes msgctxt from PO files > - Context-aware hashing produces unique digests > - Generated JS includes pgettext/npgettext functions > - Complete workflow: JS =E2=86=92 POT =E2=86=92 PO =E2=86=92 JS catalog > > This matches the existing Rust pgettext/npgettext implementation > in proxmox-yew-widget-toolkit, enabling consistent context-aware > translations across the Proxmox ecosystem. > > Signed-off-by: Kefu Chai > --- > Makefile | 2 ++ > po2js.pl | 69 +++++++++++++++++++++++++++++++++++++++++++++++++------- > 2 files changed, 63 insertions(+), 8 deletions(-) > > diff --git a/Makefile b/Makefile > index 86bd723..424918e 100644 > --- a/Makefile > +++ b/Makefile > @@ -155,6 +155,8 @@ define potupdate > --package-version=3D"$(shell cd $(2);git rev-parse HEAD)" \ > --msgid-bugs-address=3D"" \ > --copyright-holder=3D"Copyright (C) Proxmox Server Solutions GmbH = & the translation contributors." \ > + --keyword=3Dpgettext:1c,2 \ This one is correct, however I think that xgettext by default recognizes all {p,np,n,}gettext as keywords. This would be useful, if you want to define custom fns for translations, e.g. tr(), i18n(), _() or C_() the later two used as an example by glib, e.g. https://docs.gtk.org/glib/i18n.html. > + --keyword=3Dnpgettext:1c,2,3 \ >>From meson's glib integration in its i18n module (https://github.com/mesonbuild/meson/blob/61c5859b771eb938f20935cc3da54a907= 5d24c64/mesonbuild/modules/i18n.py#L105C1-L105C30) I think this should be "1c,2" instead, however as I said it would be preferable to not redefine the default keywords. > --output=3D"$(1)".pot > endef >=20=20 > diff --git a/po2js.pl b/po2js.pl > index 316c0bd..11939f8 100755 > --- a/po2js.pl > +++ b/po2js.pl > @@ -8,9 +8,7 @@ use Getopt::Long; > use JSON; > use Locale::PO; >=20=20 > -# current limits: > -# - we do not support plural. forms > -# - no message content support > +# Note: This script now supports plural forms and message context (msgct= xt) I would just remove this note (or fill it with other limitations, if any is known). >=20=20 > my $options =3D {}; > GetOptions($options, 't=3Ds', 'o=3Ds', 'v=3Ds') or die "unable to parse = options\n"; > @@ -34,6 +32,15 @@ sub fnv31a { > return $hval & 0x7fffffff; > } > ... > ... --=20 Maximiliano