Repository navigation
i18n: route all player-facing text through L10n - #25
Conversation
Every player-visible string — chat alerts, command feedback, config screens, HUD, nametags — was hardcoded English; it now goes through dev.iustitia.i18n.L10n and lives in lang/en_us.json. Check ids, flag labels, /ius command names and config keys stay raw identifiers: the live-test assertions and documented commands rely on them. L10n fails open: an unknown key renders as the key itself, a bad % spec degrades to the entry's raw text instead of vanilla's "Format error:", and neither can throw into a render or tick path. Lookup is uncached on purpose — Language is swapped on a language change, and every caller, including the per-frame HUD lines, must see the new language immediately. en_us.json is the source of truth; 23 translations sit beside it (machine-generated, not yet proofread by native speakers). verify_contribution.py --static gained check_language_assets — key coverage, per-language key parity, duplicate keys, and % / § spec drift — the only place a missing key can be caught before release, since L10n would otherwise just show the key.
|
Thanks for taking this on. The shape of it makes sense to me: one chokepoint in I cannot merge it at this size with machine translations included. Twenty-three unproofread languages plus every screen touched in one PR is more than I can review with confidence, and the translations would ship looking fluent when nobody has checked them. Here is what I would like instead. First rebase past #22, since you both touch One more thing to check on your side: the uncached lookup on per-frame HUD lines. That has potential to cost framerate on busy servers, so please measure it by using |
|
I've pushed a merge resolution to your branch to clear the conflict with #22 (now in main), so you don't need to rebase. It keeps your L10n calls everywhere, keeps both the new keybind category and your KDoc in Keybinds.kt , adds the new key.category.iustitia.keybinds key to all 23 translations (proper noun, same value), and updates the en_us hub hint to point at the new Iustitia category. Verifier passes on the merged tree. One thing I left for you: the translated hint sentences still say the old category name. |
The merge that resolved ThoriaDevelopment#22 added key.category.iustitia.keybinds to all 23 translations and rewrote the en_us hub hint, but left the translated hints naming the old generic category, so every non-English client was told to look under "Miscellaneous" / "杂项" / "Sonstiges" -- a menu entry that no longer exists for these binds. Replaces the final segment of the Options -> Controls -> <category> path in iustitia.screen.keybinds.hint with the literal "Iustitia" (a proper noun, so it is not translated) across all 23 files. Menu wording, colour codes and placeholders are untouched; the § and % counts still match en_us, which is what check_language_assets asserts.
|
This might be a translation issue—the earlier description isn’t quite accurate. Those 23 language files were actually translated by AI, not by Google-Translate-style mechanical translation. That said, AI translations still aren’t as natural as native-speaker translations. For now, I plan to keep only the en_us.json source file and the zh_cn.json file, which will be translated and reviewed by native Chinese speakers. (It hasn’t been done yet, but it’s at the top of my list.) |
Refine all zh_cn strings for Chinese punctuation, terminology, and en_us consistency.
|
Thanks for trimming this to en_us plus zh_cn. That helps. |
|
For me, all public holidays are already over, and I most likely won’t have much time to deal with these things. Yes, I’m still busy with my studies. I think this is an interesting mod, and I really like it. You may need to handle these things, or someone else may need to help. To be honest, given the size of the mod’s user base, probably no one will carefully proofread every translation, or we can just leave it as it is. Of course, I’m not blaming anyone—after all, this has only just begun. I’m sorry for leaving a mess behind. Due to time constraints, here’s a somewhat informal report: the flight check, or the landing/on-ground state check, has some issues. It may be a bit hard to describe. I wanted to record a video to show you, but I can’t. You can try making the second account sneak at the edge of the void, and you’ll find it triggering fly(B) like crazy. To be more specific, when half of the hitbox is over the void, it gets flagged as fly(B). It seems to be because it doesn’t take the player’s hitbox into account and only uses the center coordinates. There’s also a minor issue: the speed check reports many tiny VLs, almost every second, but they don’t accumulate; they just stay at 1 over time. However, this still shows that there are some subtle discrepancies in the speed check that could be optimized (English is for AI translation.) |
) First, reviewable slice of #25 as requested in its review: the L10n chokepoint, the verify_contribution.py language-asset check, en_us.json keys, and four migrated call sites (HudOverlay, PlayerSearchScreen, TranscriptPanelScreen, WatchState). No translations are included. The verifier's generated-key pins (check descriptions, presets, keybind hub rows, tier labels) are left for the migration PR, since those call sites are not on L10n yet. Co-authored-by: Rgxaa <169570958+Rgxaa@users.noreply.github.com>
…lopment#37/ThoriaDevelopment#32/ThoriaDevelopment#45 conflicts) - HudOverlay: adopt main's per-tick PanelModel architecture (ThoriaDevelopment#42); L10n calls already live in panelModel() from the auto-merge. - YaclScreenBuilder: keep ThresholdBounds-aware thresholdOption (ThoriaDevelopment#32), add the creative-flight-exempt option via new L10n keys (ThoriaDevelopment#37). - IustitiaCommand: keep L10n subcommand rows; update en_us presets / threshold texts to match ThoriaDevelopment#45/ThoriaDevelopment#23. - verify_contribution: keep the generated-key pins (check descriptions, presets, keybind binds, tier labels) now that those call sites are on L10n. - CONTRIBUTING: keep the full-migration wording of the user-facing-text section. - lang: add iustitia.cfg.option.creativeFlightExempt.* to en_us + zh_cn.
The KDoc's "Lookup is deliberately not cached" read as if each frame re-loads translations, which invited performance concerns about the HUD lines. The lookup is just vanilla's Language hashmap (~12ns, the same path Text.translatable uses); what goes un-memoized is only the String.format result (~0.2us). Skipping it keeps a runtime language switch instant for every caller and leaves no stale cache to invalidate. Hot HUD lines are already built once per client tick.
|
I dug into what the "uncached" claim actually refers to. L10n resolves every string through vanilla's own Language.getInstance().get(key), the same lookup Text.translatable uses. Vanilla's caching has two layers. The first is the key-to-string hashmap built once at resource reload; we get that for free, about 12 ns per lookup, and language files are never re-read at runtime. The second is the memoized formatted result inside TranslatableTextContent, the only layer L10n skips; vanilla keeps it fresh by comparing the Language reference. We skip that layer on purpose, because for this mod's text it would lose more than it saves. Most call sites are one-shot: command feedback, chat alerts, config screens. The formatted string is used once and discarded, so a memo entry would be written and never read. The strings also carry new variables nearly every time (player names, VLs, scores), so the hit rate would be low anyway. The lines that actually render every frame are already cached one level up by the per-tick panel model in #42, so they don't format per frame at all. Skipping the memo also removes the stale-cache failure mode vanilla has hit before: MC-162441, signs that don't update after a language switch. Numbers: Language.get ≈ 12 ns, String.format ≈ 140–260 ns. Outside the tick-cached panel, the per-frame HUD path only formats the lag icon and replay status, at most a couple of calls per frame, well under 0.01% of the frame budget. On the report: I haven't produced a meaningful /ius debugfps run yet. I'm not sure how to get the most out of the command, and I don't have a busy server like the one you mentioned to reproduce that scenario on. The code paths above don't suggest a performance problem, but that's an argument, not a measurement. If you tell me what you'd look for in the output, I'll run it and post what I get. |
The share strings from ThoriaDevelopment#45 were hardcoded English: decode/schema failures and the /ius presets export/import feedback. Added 32 keys to en_us and zh_cn. The tests assert on the key now, because L10n returns the key when no language is loaded.
The earlier zh translation was confusing; at the very least I could not tell what it was saying. That is fixed now. Over the past two weeks I built an agent pipeline for it. In total it ran for 231.5 hours, used 5,987,059,713 tokens, took 71,681 steps, made 100,968 tool calls and spawned 5,540 subagent sessions; the session files alone take up 1.6 GB. Roughly, the loop works like this: take one string to translate with its en_us value and its current zh_cn value (plus any pending change and reason), put it behind a long hand-written prompt template, and start a fresh session that: 1. finds the string in the source and works out what it means from the code around it; 2. cross-checks the Minecraft-Mod-Language-Modpack 1.21 community translation corpus to match the wording I already expect in Chinese Minecraft modding; 3. argues the two sides against each other until it lands on one answer; 4. writes the change and the reason for it into a JSON file on my disk. It then walks from key 1 to key 668 and repeats, nine rounds in all, until the file stops changing. 277 of the 668 strings ended up rewritten, 554 diff lines in total, one deletion plus one insertion each. Round 1 produced 500+ changes, the count fell every round after that, and the smallest round had 2. The model was DeepSeek V4.1 Flash 1017, the strongest open model right now, and as far as I know it has more Chinese training data, so it does better in Chinese. (I am not advertising it, just making clear that I did not use a junk model.) The same workflow can be pointed at the other language files, though each one still takes a lot of time and tokens to grind through. I can provide it if needed.
|
The earlier zh translation was confusing; at the very least I could not tell what it was saying. That is fixed now. Over the past two weeks I built an agent pipeline for it. In total it ran for 231.5 hours, used 5,987,059,713 tokens, took 71,681 steps, made 100,968 tool calls and spawned 5,540 subagent sessions; the session files alone take up 1.6 GB.🤯🤯🤯 Roughly, the loop works like this: take one string to translate with its en_us value and its current zh_cn value (plus any pending change and reason), put it behind a long hand-written prompt template, and start a fresh session that:
It then walks from key 1 to key 668 and repeats, nine rounds in all, until the file stops changing. 277 of the 668 strings ended up rewritten, 554 diff lines in total, one deletion plus one insertion each. Round 1 produced 500+ changes, the count fell every round after that, and the smallest round had 2. The model was DeepSeek V4.1 Flash 1017, the strongest open model right now, and as far as I know it has more Chinese training data, so it does better in Chinese. (I am not AD it, just making clear that I did not use a * model.) The same workflow can be pointed at the other language files, though each one still takes a lot of time and tokens to grind through. I can provide it if needed. |
The migration's safety argument rests entirely on L10n degrading instead of throwing, and nothing tested it: a pure-JVM test never loads a language file, so every key comes back as the key and the formatting branch was unreachable. Extract formatEntry() from s() so the % spec path can be asserted directly, and add L10nTest covering the key-miss sentinel, substitution, a malformed spec, a missing argument and %%. Behaviour is unchanged; s() still returns the key when the lookup itself throws and the raw entry when formatting fails. Also record where localized prose now leaves the client: the report JSON tier and the .iusclip alert label. checkId stays the raw identifier in both. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
|
Maintainer note: I pushed What it adds: an extracted I also rewrote the description, which still claimed 23 shipped translations after they were removed, and recorded Still open, and I am making the call on both: |
Summary
Every player-visible string — chat alerts, command feedback, config screens, HUD, nametags — was hardcoded English, so the mod spoke English whatever the client language was. All of it now goes through
dev.iustitia.i18n.L10n, which landed as the pattern in #43; this PR is the migration on top of it plus theen_us.jsonkey set.Check ids, flag labels,
/iuscommand names and config keys stay raw identifiers: the live-test assertions and documented commands rely on them.Changes
/iuscommand feedback, config (YACL) screens, HUD, nametags, flags/tiers, keybind hub, info screens, replay/clip/session/UI screens.en_us.json36 → 668 keys.zh_cn.jsonadded at full key parity (668/668, 0 drift either way).verify_contribution.py --staticextended: runtime-assembled keys are now pinned — check descriptions fromchecks.json, preset label/blurb keys, keybind ids, and everyTierconstant. That is what letsKeybindsdrop its hardcoded English fallback instead of silently showing English.L10n.formatEntrysplit out ofL10n.sso the fail-open formatting path is reachable from a pure-JVM test; newL10nTestasserts the key-miss sentinel, argument substitution, a malformed%spec, a missing argument, and%%.Snapshotreport JSONtier,.iusclipalertlabel).Affected areas
Verification
python scripts/verify_contribution.py --static: PASS on this branch (warnings only: nobuild/selftest-manifest.json, andkey.category.iustitia.keybindsis referenced fromKeybindsrather than anL10ncall)../gradlew test: PASS, including the 5 newL10ntests../gradlew build: PASS in CI at this head (see checks).zh_cnand with an unsupported language;/iuscommands, config screen, HUD, alerts, tier labels and the keybind hub follow the client language, with English fallback, and identifiers stayed raw. No screenshot, recording or log was attached, so this is a report rather than evidence. It is not repeatable from this PR as it stands./ius debugfpsfor the uncached per-frame lookup: NOT RUN. No measurement exists. The argument in the thread (Language.get≈ 12 ns vsString.format140–260 ns, and the per-frame HUD lines are already tick-cached by hud: resolve crosshair target once per frame and cache panel model pe… #42) is an argument, not a number.Known limitations
zh_cn.jsonis machine-translated (AI pipeline, nine reflection rounds, 277 strings rewritten) and has not been proofread by a native speaker. Merging this ships unreviewed translation.en_us.jsonis the reviewed source of truth; a wrongzh_cnstring is a text-only defect, but it is still unreviewed text in users' clients.debugfpsmeasurement for the uncached lookup (above).zh_cnclient carries a Chinesetier, and a.iusclipcarries the alert labels of the client that recorded it.checkIdstays raw in both, so nothing that matches identifiers breaks.en_us.jsonkey. A missed key fails open to the key itself and--staticcatches it before release.AI assistance
zh_cntranslation; AI assistance for the call-site migration.L10nfail-open tests and the export-field notes, and recorded the untested items above as untested.