Let a Strings() translation number its format directives

Strings() formats with string.format, which fills directives in argument order. A language that puts two values the other way round from English ("ATTACK de PIKACHU" for "PIKACHU's ATTACK") could not say so: the translation either kept the English order or swapped the values. The official FireRed, Gold and Red localizations reorder values like this in dozens of battle and menu lines.

A translation can now number its directives the way POSIX printf does ("%2$s de\n%1$s monte!"), and Strings() hands the arguments over in that order. Flags and widths are kept ("%3$03d") and "%%" stays a literal percent. A translation numbers all of its directives or none, and may only number arguments the source has; it may leave one out, as the official translations sometimes drop a name. Otherwise it falls back to the English source and logs one warning, like a translation with the wrong number of directives.

Sources never carry numbered directives, and a translation without them takes the same string.format path as before, so English and existing catalogs render the same text. A translation that still cannot format its arguments falls back to the English source formatted with them, instead of the raw template. An older engine given a numbered translation cannot format it and prints the template, so a catalog using numbered directives needs this engine.
This commit is contained in:
thibautbus
2026-09-19 19:21:30 +02:00
parent d74a96f645
commit ed0fd91dbb
2 changed files with 101 additions and 10 deletions
+26
View File
@@ -87,6 +87,12 @@ return function(mod)
mod.content.strings:override("OFF", "NON")
mod.content.strings:override("options.musicFilter|OFF", "AUCUN")
mod.content.strings:override("%d of %d", "%d perdus")
mod.content.strings:override("%s's %s\nrose!", "%2$s de\n%1$s monte!")
mod.content.strings:override("%s gave %s\n%d items.", "%3$03d objets\ndonnes a %2$s par %1$s (%%).")
mod.content.strings:override("%s's %s\nfell!", "%2$s de\n%s baisse!")
mod.content.strings:override("%s's %s\nhurt %s!", "%2$s de %1$s\nblesse %4$s!")
mod.content.strings:override("%s is\nabout to use %s.\nWill %s change?", "%2$s va être envoyé\npar %1$s. Changer?")
mod.content.strings:override("Got %s!", "%1$d obtenu!")
end
]],
}
@@ -144,6 +150,26 @@ do
eq(complaints(), mid, "the arity complaint is not repeated")
end
-- ------- a translation can number its directives to reorder the values
do
eq(Strings("%s's %s\nrose!", "PIKACHU", "ATTACK"), "ATTACK de\nPIKACHU monte!",
"numbered directives take the arguments in the order they name")
eq(Strings("%s gave %s\n%d items.", "RED", "BLUE", 7), "007 objets\ndonnes a BLUE par RED (%).",
"numbered directives keep their flags, and %% stays a literal percent")
local before = complaints()
eq(Strings("%s's %s\nfell!", "PIKACHU", "ATTACK"), "PIKACHU's ATTACK\nfell!",
"mixing numbered and plain directives falls back to the English source")
eq(Strings("%s's %s\nhurt %s!", "A", "B", "C"), "A's B\nhurt C!",
"a numbered translation that names a missing argument falls back to the English source")
check(complaints() >= before + 2, "both numbering mistakes are reported")
eq(Strings("%s is\nabout to use %s.\nWill %s change?", "BLUE", "PIDGEY", "RED"), "PIDGEY va être envoyé\npar BLUE. Changer?",
"a numbered translation may leave an argument out")
eq(Strings("Got %s!", "POTION"), "Got POTION!",
"a translation that cannot format its arguments falls back to the formatted English")
end
-- The catalog is module state, and the aggregator runs every suite in one
-- Lua process: leaving it loaded translated the battle text underneath the
-- suites that run after this one (parity_J asserted on "But, it failed!"