From: Maximiliano Sandoval <m.sandoval@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH proxmox-i18n 2/2] po2js: add ngettext support
Date: Thu, 31 Jul 2025 13:26:00 +0200 [thread overview]
Message-ID: <20250731112822.297009-2-m.sandoval@proxmox.com> (raw)
In-Reply-To: <20250731112822.297009-1-m.sandoval@proxmox.com>
We add new array for plural forms.
We use the msgid as the identifier translatable strings with a plural
form. This is the same as done for .mo files, from [1]:
> However, only the singular of the original string takes part in the
> hash table lookup.
We also obtain the plural forms and the number of plural forms directly
from the $LANG.po file, this requires the file to have a Plural-Forms
header that is valid, otherwise it would fail to evaluated by the
ngettext function when calling `Number($plural_forms)`.
Regarding the fallback to the original string, from [2]:
> The parameter n is used to determine the plural form. If no message
> catalog is found msgid1 is returned if n == 1, otherwise msgid2.
We default to the non-translated singular/plural depending on whether
n==1.
Regarding the default for $nplurals and $plural_forms: We select the
values for "Two forms, singular used for one only" from [2], this would
match with germanic and latin families.
This partially restores commit 2235ad7c7c79e29f09d3f3205ad86e862b5ac14c.
[1] https://www.gnu.org/software/gettext/manual/html_node/MO-Files.html
[2] https://www.gnu.org/software/gettext/manual/html_node/Plural-forms.html
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
---
po2js.pl | 52 +++++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 45 insertions(+), 7 deletions(-)
diff --git a/po2js.pl b/po2js.pl
index c5268d7..d5a78a6 100755
--- a/po2js.pl
+++ b/po2js.pl
@@ -35,6 +35,10 @@ sub fnv31a {
}
my $catalog = {};
+my $plurals_catalog = {};
+
+my $nplurals = 2;
+my $plural_forms = "n!=1";
foreach my $filename (@ARGV) {
my $href = Locale::PO->load_file_ashash($filename)
@@ -49,6 +53,11 @@ foreach my $filename (@ARGV) {
die "unable to get charset\n" if !$charset;
}
+ if ($header =~ m|^Plural-Forms:\s+nplurals\s?=\s?([123456]);\s+plural=(.*);$|im) {
+ $nplurals = $1 + 0;
+ $plural_forms = $2;
+ }
+
foreach my $k (keys %$href) {
my $po = $href->{$k};
next if $po->fuzzy(); # skip fuzzy entries
@@ -64,23 +73,37 @@ foreach my $filename (@ARGV) {
my $qmsgid = decode($charset, $po->msgid);
my $msgid = $po->dequote($qmsgid);
- my $qmsgstr = decode($charset, $po->msgstr);
- my $msgstr = $po->dequote($qmsgstr);
+ my $qmsgid_plural = decode($charset, $po->msgid_plural);
+ my $msgid_plural = $po->dequote($qmsgid_plural);
- next if !length($msgid); # skip header
-
- next if !length($msgstr); # skip untranslated entries
+ next if !length($msgid) && !length($msgid_plural); # skip header
my $digest = fnv31a($msgid);
die "duplicate digest" if $catalog->{$digest};
- $catalog->{$digest} = [$msgstr];
- # later, we can add plural forms to the array
+ if (defined($po->msgstr)) {
+ my $qmsgstr = decode($charset, $po->msgstr);
+ my $msgstr = $po->dequote($qmsgstr);
+
+ next if !length($msgstr); # skip untranslated entries
+ $catalog->{$digest} = [$msgstr];
+ }
+
+ if (defined(my $plurals = $po->msgstr_n)) {
+ for my $case (sort { $a <=> $b } keys $plurals->%*) {
+ my $qmsgstr_n = decode($charset, $plurals->{$case});
+ my $msgstr_n = $po->dequote($qmsgstr_n);
+
+ next if !length($msgstr_n); # skip untranslated entries
+ push $plurals_catalog->{$digest}->@*, $msgstr_n;
+ }
+ }
}
}
my $json = to_json($catalog, { canonical => 1, utf8 => 1 });
+my $plurals_json = to_json($plurals_catalog, { canonical => 1, utf8 => 1 });
my $version = $options->{v} // ("dev-build " . localtime());
my $content = "// $version\n"; # write version to the beginning to better avoid stale cache
@@ -91,6 +114,7 @@ $content .= "// Proxmox Message Catalog: $outfile\n" if $outfile;
$content .= <<__EOD;
__proxmox_i18n_msgcat__ = $json;
+__proxmox_i18n_plurals_msgcat__ = $plurals_json;
function fnv31a(text) {
var len = text.length;
@@ -112,6 +136,20 @@ function gettext(buf) {
}
return data[0] || buf;
}
+
+function ngettext(singular, plural, n) {
+ const msg_idx = Number($plural_forms);
+ const digest = fnv31a(singular);
+ const translation = __proxmox_i18n_plurals_msgcat__[digest];
+ if (!translation[msg_idx]) {
+ if (n === 1) {
+ return singular;
+ } else {
+ return plural;
+ }
+ }
+ return translation[msg_idx];
+}
__EOD
if ($outfile) {
--
2.47.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
next prev parent reply other threads:[~2025-07-31 11:27 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-07-31 11:25 [pve-devel] [PATCH proxmox-i18n 1/2] format with proxmox-perltidy Maximiliano Sandoval
2025-07-31 11:26 ` Maximiliano Sandoval [this message]
2025-07-31 11:37 ` [pve-devel] [PATCH proxmox-i18n 2/2] po2js: add ngettext support Maximiliano Sandoval
2025-07-31 12:01 ` [pve-devel] applied: [PATCH proxmox-i18n 1/2] format with proxmox-perltidy Thomas Lamprecht
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=20250731112822.297009-2-m.sandoval@proxmox.com \
--to=m.sandoval@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