* [pve-devel] [PATCH proxmox-i18n 1/2] format with proxmox-perltidy
@ 2025-07-31 11:25 Maximiliano Sandoval
2025-07-31 11:26 ` [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
0 siblings, 2 replies; 4+ messages in thread
From: Maximiliano Sandoval @ 2025-07-31 11:25 UTC (permalink / raw)
To: pve-devel
Signed-off-by: Maximiliano Sandoval <m.sandoval@proxmox.com>
---
po2js.pl | 69 ++++++++++++++++++++++++++------------------------------
1 file changed, 32 insertions(+), 37 deletions(-)
diff --git a/po2js.pl b/po2js.pl
index ee5bbd1..c5268d7 100755
--- a/po2js.pl
+++ b/po2js.pl
@@ -26,14 +26,10 @@ sub fnv31a {
my $hval = 0x811c9dc5;
foreach my $c (unpack('C*', $string)) {
- $hval ^= $c;
- $hval += (
- (($hval << 1) ) +
- (($hval << 4) ) +
- (($hval << 7) ) +
- (($hval << 8) ) +
- (($hval << 24) ) );
- $hval = $hval & 0xffffffff;
+ $hval ^= $c;
+ $hval +=
+ ((($hval << 1)) + (($hval << 4)) + (($hval << 7)) + (($hval << 8)) + (($hval << 24)));
+ $hval = $hval & 0xffffffff;
}
return $hval & 0x7fffffff;
}
@@ -41,51 +37,50 @@ sub fnv31a {
my $catalog = {};
foreach my $filename (@ARGV) {
- my $href = Locale::PO->load_file_ashash($filename) ||
- die "unable to load '$filename'\n";
-
+ my $href = Locale::PO->load_file_ashash($filename)
+ || die "unable to load '$filename'\n";
+
my $charset;
my $hpo = $href->{'""'} || die "no header";
my $header = $hpo->dequote($hpo->msgstr);
if ($header =~ m|^Content-Type:\s+text/plain;\s+charset=(\S+)$|im) {
- $charset = $1;
+ $charset = $1;
} else {
- die "unable to get charset\n" if !$charset;
+ die "unable to get charset\n" if !$charset;
}
-
foreach my $k (keys %$href) {
- my $po = $href->{$k};
- next if $po->fuzzy(); # skip fuzzy entries
- my $ref = $po->reference();
+ my $po = $href->{$k};
+ next if $po->fuzzy(); # skip fuzzy entries
+ my $ref = $po->reference();
- # skip unused entries
- next if !$ref;
+ # skip unused entries
+ next if !$ref;
- # skip entries if t is defined (pve/pmg) and the string is
- # not used there or in the widget toolkit
- next if $options->{t} && $ref !~ m/($options->{t}|proxmox)\-/;
-
- my $qmsgid = decode($charset, $po->msgid);
- my $msgid = $po->dequote($qmsgid);
+ # skip entries if t is defined (pve/pmg) and the string is
+ # not used there or in the widget toolkit
+ next if $options->{t} && $ref !~ m/($options->{t}|proxmox)\-/;
- my $qmsgstr = decode($charset, $po->msgstr);
- my $msgstr = $po->dequote($qmsgstr);
+ my $qmsgid = decode($charset, $po->msgid);
+ my $msgid = $po->dequote($qmsgid);
- next if !length($msgid); # skip header
-
- next if !length($msgstr); # skip untranslated entries
+ my $qmsgstr = decode($charset, $po->msgstr);
+ my $msgstr = $po->dequote($qmsgstr);
- my $digest = fnv31a($msgid);
+ next if !length($msgid); # skip header
- die "duplicate digest" if $catalog->{$digest};
+ next if !length($msgstr); # skip untranslated entries
- $catalog->{$digest} = [ $msgstr ];
- # later, we can add plural forms to the array
+ my $digest = fnv31a($msgid);
+
+ die "duplicate digest" if $catalog->{$digest};
+
+ $catalog->{$digest} = [$msgstr];
+ # later, we can add plural forms to the array
}
}
-my $json = to_json($catalog, {canonical => 1, utf8 => 1});
+my $json = to_json($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
@@ -120,8 +115,8 @@ function gettext(buf) {
__EOD
if ($outfile) {
- open(my $fh, '>', $outfile) ||
- die "unable to open '$outfile' - $!\n";
+ open(my $fh, '>', $outfile)
+ || die "unable to open '$outfile' - $!\n";
print $fh $content;
} else {
print $content;
--
2.47.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 4+ messages in thread* [pve-devel] [PATCH proxmox-i18n 2/2] po2js: add ngettext support
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
2025-07-31 11:37 ` Maximiliano Sandoval
2025-07-31 12:01 ` [pve-devel] applied: [PATCH proxmox-i18n 1/2] format with proxmox-perltidy Thomas Lamprecht
1 sibling, 1 reply; 4+ messages in thread
From: Maximiliano Sandoval @ 2025-07-31 11:26 UTC (permalink / raw)
To: pve-devel
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
^ permalink raw reply [flat|nested] 4+ messages in thread* Re: [pve-devel] [PATCH proxmox-i18n 2/2] po2js: add ngettext support
2025-07-31 11:26 ` [pve-devel] [PATCH proxmox-i18n 2/2] po2js: add ngettext support Maximiliano Sandoval
@ 2025-07-31 11:37 ` Maximiliano Sandoval
0 siblings, 0 replies; 4+ messages in thread
From: Maximiliano Sandoval @ 2025-07-31 11:37 UTC (permalink / raw)
To: pve-devel
Maximiliano Sandoval <m.sandoval@proxmox.com> writes:
> @@ -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]) {
This should be:
if (!translation || msg_idx >= translation.length) {
or similar. It is possible a message might be missing translations for
certain plural forms.
> + if (n === 1) {
> + return singular;
> + } else {
> + return plural;
> + }
> + }
> + return translation[msg_idx];
> +}
> __EOD
>
> if ($outfile) {
--
Maximiliano
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 4+ messages in thread
* [pve-devel] applied: [PATCH proxmox-i18n 1/2] format with proxmox-perltidy
2025-07-31 11:25 [pve-devel] [PATCH proxmox-i18n 1/2] format with proxmox-perltidy Maximiliano Sandoval
2025-07-31 11:26 ` [pve-devel] [PATCH proxmox-i18n 2/2] po2js: add ngettext support Maximiliano Sandoval
@ 2025-07-31 12:01 ` Thomas Lamprecht
1 sibling, 0 replies; 4+ messages in thread
From: Thomas Lamprecht @ 2025-07-31 12:01 UTC (permalink / raw)
To: pve-devel, Maximiliano Sandoval
On Thu, 31 Jul 2025 13:25:59 +0200, Maximiliano Sandoval wrote:
>
Worked here and I could now change all sites to consistently use plural variant
of Warnings, which is currently the only label that caused conflicts.
That said, if we extend usage and always need to change all repos of the
previous singular-only use sites it might get a bit of a PITA...
Anyhow: Applied, thanks!
[1/2] format with proxmox-perltidy
commit: cef9669fb45fedc041e82a910020d2afaf288b68
[2/2] po2js: add ngettext support
commit: 49573acf7fda7c9421c8eba2ec7c5942ae07915e
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2025-07-31 12:00 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-07-31 11:25 [pve-devel] [PATCH proxmox-i18n 1/2] format with proxmox-perltidy Maximiliano Sandoval
2025-07-31 11:26 ` [pve-devel] [PATCH proxmox-i18n 2/2] po2js: add ngettext support Maximiliano Sandoval
2025-07-31 11:37 ` Maximiliano Sandoval
2025-07-31 12:01 ` [pve-devel] applied: [PATCH proxmox-i18n 1/2] format with proxmox-perltidy Thomas Lamprecht
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox