all lists on lists.proxmox.com
 help / color / mirror / Atom feed
* [PATCH v4 i18n 0/1] add pgettext() and npgettext() support for context-aware translations
@ 2026-06-10  7:59 Kefu Chai
  2026-06-10  7:59 ` [PATCH v4 i18n 1/1] " Kefu Chai
  0 siblings, 1 reply; 2+ messages in thread
From: Kefu Chai @ 2026-06-10  7:59 UTC (permalink / raw)
  To: pve-devel

Changes since v3:
- Drop the submodule bumps. The pgettext/npgettext stubs they would
  have pulled in are already present in the current proxmox-i18n
  submodule pointers, picked up by routine bumps since v3 was sent.

Kefu Chai (1):
  add pgettext() and npgettext() support for context-aware translations

 Makefile |  1 +
 po2js.pl | 69 ++++++++++++++++++++++++++++++++++++++++++++++++--------
 2 files changed, 61 insertions(+), 9 deletions(-)

-- 
2.47.3





^ permalink raw reply	[flat|nested] 2+ messages in thread

* [PATCH v4 i18n 1/1] add pgettext() and npgettext() support for context-aware translations
  2026-06-10  7:59 [PATCH v4 i18n 0/1] add pgettext() and npgettext() support for context-aware translations Kefu Chai
@ 2026-06-10  7:59 ` Kefu Chai
  0 siblings, 0 replies; 2+ messages in thread
From: Kefu Chai @ 2026-06-10  7:59 UTC (permalink / raw)
  To: pve-devel

Context-aware translations (msgctxt in gettext) distinguish identical
msgids that mean different things in different parts of the UI, for
example "Monitor" for Ceph versus QEMU.

To keep msgid+msgctxt pairs distinct in the generated JS catalog,
po2js.pl now hashes the two together with the standard gettext EOT
separator ("\x04") between them. Messages without a context fall
through to the existing fnv31a(msgid). Locale::PO::load_file_ashash()
conflates rows that share the same msgid, so switch to
load_file_asarray() to preserve the distinct entries. The generated
catalog gains JS pgettext() and npgettext() helpers that perform the
same lookup at runtime.

The Makefile change adds --keyword=npgettext:1c,2,3 to xgettext.
pgettext is in xgettext's default JavaScript keyword list, but
npgettext is not: it was omitted from the defaults in gettext 0.18.3
(2013) when JavaScript support first landed, and never added since.
Without the explicit flag, xgettext silently drops npgettext() calls
during 'make update_pot'.

See "xgettext Invocation" in the GNU gettext manual:
https://www.gnu.org/software/gettext/manual/html_node/xgettext-Invocation.html

Signed-off-by: Kefu Chai <k.chai@proxmox.com>
---
 Makefile |  1 +
 po2js.pl | 69 ++++++++++++++++++++++++++++++++++++++++++++++++--------
 2 files changed, 61 insertions(+), 9 deletions(-)

diff --git a/Makefile b/Makefile
index 16aab32..e1d9633 100644
--- a/Makefile
+++ b/Makefile
@@ -155,6 +155,7 @@ 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=npgettext:1c,2,3 \
 	  --output="$(1)".pot
 endef
 
diff --git a/po2js.pl b/po2js.pl
index 316c0bd..4b7b044 100755
--- a/po2js.pl
+++ b/po2js.pl
@@ -8,10 +8,6 @@ use Getopt::Long;
 use JSON;
 use Locale::PO;
 
-# current limits:
-# - we do not support plural. forms
-# - no message content support
-
 my $options = {};
 GetOptions($options, 't=s', 'o=s', 'v=s') or die "unable to parse options\n";
 
@@ -34,6 +30,15 @@ sub fnv31a {
     return $hval & 0x7fffffff;
 }
 
+# Hash function for messages with context
+sub fnv31a_ctxt {
+    my ($msgctxt, $msgid) = @_;
+    return fnv31a($msgid) if !length($msgctxt);  # Empty context = no context
+    # Use EOT character (0x04) as separator, standard in gettext
+    my $combined = $msgctxt . "\x04" . $msgid;
+    return fnv31a($combined);
+}
+
 my $catalog = {};
 my $plurals_catalog = {};
 
@@ -41,11 +46,20 @@ my $nplurals = 2;
 my $plural_forms = "n!=1";
 
 foreach my $filename (@ARGV) {
-    my $href = Locale::PO->load_file_ashash($filename)
+    my $aref = Locale::PO->load_file_asarray($filename)
         || die "unable to load '$filename'\n";
 
     my $charset;
-    my $hpo = $href->{'""'} || die "no header";
+    # Find header entry (msgid "")
+    my $hpo;
+    foreach my $po (@$aref) {
+        if ($po->msgid eq '""') {
+            $hpo = $po;
+            last;
+        }
+    }
+    die "no header" if !$hpo;
+
     my $header = $hpo->dequote($hpo->msgstr);
     if ($header =~ m|^Content-Type:\s+text/plain;\s+charset=(\S+)$|im) {
         $charset = $1;
@@ -58,8 +72,7 @@ foreach my $filename (@ARGV) {
 	$plural_forms = $2;
     }
 
-    foreach my $k (keys %$href) {
-        my $po = $href->{$k};
+    foreach my $po (@$aref) {
         next if $po->fuzzy(); # skip fuzzy entries
         my $ref = $po->reference();
 
@@ -76,9 +89,18 @@ foreach my $filename (@ARGV) {
         my $qmsgid_plural = decode($charset, $po->msgid_plural);
         my $msgid_plural = $po->dequote($qmsgid_plural);
 
+        # Extract message context if present
+        my $msgctxt = '';
+        if (defined($po->msgctxt)) {
+            my $qmsgctxt = decode($charset, $po->msgctxt);
+            $msgctxt = $po->dequote($qmsgctxt);
+        }
+
         next if !length($msgid) && !length($msgid_plural); # skip header
 
-        my $digest = fnv31a($msgid);
+        my $digest = length($msgctxt) > 0
+            ? fnv31a_ctxt($msgctxt, $msgid)
+            : fnv31a($msgid);
 
         die "duplicate digest" if $catalog->{$digest};
 
@@ -150,6 +172,35 @@ function ngettext(singular, plural, n) {
     }
     return translation[msg_idx];
 }
+
+function fnv31a_ctxt(context, text) {
+    // Use EOT character (0x04) as separator
+    var combined = context + "\\x04" + text;
+    return fnv31a(combined);
+}
+
+function pgettext(context, msgid) {
+    var digest = fnv31a_ctxt(context, msgid);
+    var data = __proxmox_i18n_msgcat__[digest];
+    if (!data) {
+        return msgid;  // Return msgid (not context) as fallback
+    }
+    return data[0] || msgid;
+}
+
+function npgettext(context, singular, plural, n) {
+    const msg_idx = Number($plural_forms);
+    const digest = fnv31a_ctxt(context, singular);
+    const translation = __proxmox_i18n_plurals_msgcat__[digest];
+    if (!translation || msg_idx >= translation.length) {
+        if (n === 1) {
+            return singular;
+        } else {
+            return plural;
+        }
+    }
+    return translation[msg_idx];
+}
 __EOD
 
 if ($outfile) {
-- 
2.47.3





^ permalink raw reply related	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-06-10  8:00 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-10  7:59 [PATCH v4 i18n 0/1] add pgettext() and npgettext() support for context-aware translations Kefu Chai
2026-06-10  7:59 ` [PATCH v4 i18n 1/1] " Kefu Chai

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal