Commit Graph
3 Commits
Author SHA1 Message Date
Kaj SchittecatandClaude Opus 5 d192bf53db fix: never trust a translation as a printf format string (#258)
pisti87's T-Deck rebooted every time he logged into a repeater, but only in
Hungarian. The login clock-skew warning does:

  snprintf(msg, n, TR("Device clock differs from \"%s\" by %lu min%s"),
           name, minutes, suffix);

and the Hungarian row reordered the conversions to "%lu ... %s ... %s". Varargs
are positional, so snprintf read the name POINTER as an unsigned long and then
took the minute count -- the integer 3 -- as a char* and dereferenced address 3.
Instant panic, every login, Hungarian only. English fit the declared order, so
it never showed there.

Fixing the four bad Hungarian rows is not sufficient: TR() returns a format
string and translations come from .lang files that users download or hand-write,
so any file can crash any device. TR() now compares the ordered conversion
signatures of key and translation and falls back to the English key on a
mismatch -- the key IS the call site's format string, so it is always correct.
The scan runs only for keys containing '%', which is a small minority.

Also fixed the four rows (three were Hungarian-only crashes or dropped values),
bumped hu to v11, and taught audit-lang.py to fail the build on a mismatch so a
future translation PR cannot reintroduce this. Unit-checked that the audit
detects the original bad row and accepts the repaired one.

Reported by pisti87.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 08:14:05 +02:00
Kaj SchittecatandClaude Opus 5 d4ade2e616 i18n: add the 48 UI strings no language file had, as empty rows for translators
The firmware asked for these keys and no .lang file carried them, so they
rendered English in all 13 languages with nothing to show a translator that
they were missing. Mostly strings that shipped with a feature whose PR did not
touch the language files: the SD arbitration and migration warnings, the
Wi-Fi/BLE coexistence messages, the keyboard-navigation vocabulary
(Up/Down/Left/Right/Select/Scroll) and section names (General, Clock & time,
Sensors, Screen, Home, Messages, Keys, Compact, Snake).

They go in with an EMPTY translation, which is the format's own way of saying
'not translated yet': the runtime loader requires a non-empty value
(`if (*p && tab[1])`) and gen-lang-builtin.py requires `if k and v`, so both
skip these rows and the English falls through exactly as before. Confirmed:
i18n_builtin.h regenerates byte-identical. No `# ver:` bump for the same
reason — device behaviour is unchanged, so there is nothing to re-download.

Also fixes the audit's own row parser to match the loader: a line is a comment
only when it has NO tab. Testing for a leading '#' hid every key starting with
LVGL recolor markup ("#7A7F87 Wardrive: …#"), the exact class the loader has a
comment warning about.

Every language is now at 0 missing keys.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 10:24:56 +02:00
Kaj SchittecatandClaude Opus 5 69bace7c15 tooling: audit .lang files against the TR() keys the firmware looks up
Nothing checked this. The .lang files are hand-maintained and i18n_builtin.h is
generated FROM them, so drift is invisible in both directions: a row whose
English no longer appears in any TR() call is translated for nothing, and a
TR() key with no row renders English in every language forever.

Matching mirrors TR() in i18n.cpp, which is the part a grep gets wrong —
adjacent C literals concatenate, LV_SYMBOL_* icon prefixes are stripped before
lookup, non-ASCII is often written as \x escapes, and menu/tab labels reach
TR() through a table field rather than as a literal (resolved by harvesting the
table's initialiser).

Current state: 910 TR() keys; every language carries ~57 dead rows and is
missing ~53 keys (hu: 77 dead, 52 missing).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 09:53:31 +02:00