public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Maximiliano Sandoval <m.sandoval@proxmox.com>
To: Kefu Chai <k.chai@proxmox.com>
Cc: pve-devel@lists.proxmox.com
Subject: Re: [PATCH proxmox-i18n v1] add pgettext() and npgettext() support for context-aware translations
Date: Thu, 05 Feb 2026 14:15:33 +0100	[thread overview]
Message-ID: <s8otsvvtjpm.fsf@proxmox.com> (raw)
In-Reply-To: <20260205101613.1067594-2-k.chai@proxmox.com> (Kefu Chai's message of "Thu, 5 Feb 2026 18:16:14 +0800")

Kefu Chai <k.chai@proxmox.com> 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=pgettext:1c,2        # 1c = context, 2 = msgid
>   --keyword=npgettext:1c,2,3     # 1c = context, 2 = singular, 3 = 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') → 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") = 914449940
>   - hash("console menu" + "\x04" + "noVNC") = 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 → POT → PO → 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 <k.chai@proxmox.com>
> ---
>  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="$(shell cd $(2);git rev-parse HEAD)" \
>        --msgid-bugs-address="<support@proxmox.com>" \
>        --copyright-holder="Copyright (C) Proxmox Server Solutions GmbH <support@proxmox.com> & the translation contributors." \
> +      --keyword=pgettext: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=npgettext:1c,2,3 \

>From meson's glib integration in its i18n module
(https://github.com/mesonbuild/meson/blob/61c5859b771eb938f20935cc3da54a9075d24c64/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="$(1)".pot
>  endef
>  
> 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;
>  
> -# current limits:
> -# - we do not support plural. forms
> -# - no message content support
> +# Note: This script now supports plural forms and message context (msgctxt)

I would just remove this note (or fill it with other limitations, if any
is known).

>  
>  my $options = {};
>  GetOptions($options, 't=s', 'o=s', 'v=s') or die "unable to parse options\n";
> @@ -34,6 +32,15 @@ sub fnv31a {
>      return $hval & 0x7fffffff;
>  }
> ...
> ...

-- 
Maximiliano




      reply	other threads:[~2026-02-05 13:15 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-02-05 10:16 Kefu Chai
2026-02-05 13:15 ` Maximiliano Sandoval [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=s8otsvvtjpm.fsf@proxmox.com \
    --to=m.sandoval@proxmox.com \
    --cc=k.chai@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal