feat: the Insert menu in 13 languages, with Game language and Menu la… #1864
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
No milestone
No assignees
2 participants
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!1864
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/menu-languages"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
…nguage rows
The game itself ships in 16 languages, but its settings menu was English only: a player who plays in Portuguese, Japanese or German gets every option and help line in English.
The translations are machine-made and want checking by speakers of each language: each is one line in menu_i18n.h. One English text is two things: "Switch" is the console's name in Button prompts and the Switch age tool's verb, so it stays English for both.
Tested on an RTX 3060 Laptop, Windows 11: the menu in Portuguese, Japanese, German and Chinese (in play and in RX_VK_SHOT_MENU shots), the Game language row writing language.txt and offering the restart. The suite run on Windows here fails the same tests with and without this change (local tools); the menu's own tests pass. Played on
52ac35c; rebased on51de681, where the renderer does not link yet with or without this change (present_commit and present_defer_into are declared, not defined: #1702?).Assisted-by: Claude Opus 5.5



Nice piece of work: the lookup lives in
ui::text/para/size_of/spaced, so no row needs its own call, and falling back to the English text when a key is missing is the right default. I read the plumbing, fonts, layout and tests and spot-checked translations (pt-BR, de, fr, ru, ja, zh-Hant); I did not build or run it.Blocking
mergeStateStatus: DIRTY), so no CI has run on it. The description also says the renderer does not link on the base it was rebased on (present_commit/present_defer_into). Please rebase and let CI run before this is judged.test_texts_are_the_menuswill then fail for the 9 languages that translate "Ultrawide", which is the intended tripwire, but whoever merges second has to re-translate the new text. Worth merging #2078 first, or coordinating. The test only checks table keys against the menu, so a menu text that has no table entry (or loses it after a rename) silently falls back to English. Fine for a first version.Checked and fine
opts.game_lang,opts.menu_lang) and are not sent to the host, so no HOS_OPT id is needed and nothing collides with 10/44 or the ids in use.menu_languageis the generated ini key (fits the 32-byte buffer),game_langis skipped on save and load, and both rows arepreset_exempt.NewFrameon first use is fine, andload_fontsonly runs once, so thedonelist cannot go stale. Missing system fonts only log "may show as boxes".expand()returns "" for%WINDIR%off Windows, so the Linux path list is safe.tr()pointer lifetime (two generations of 4096) is sound; no lock is held acrosstr_text/table()in a way that can deadlock.spaced/paranow step by whole UTF-8 characters. Clip rects around the rows keep overlong German or Russian text from drawing outside its column, and the Off/On halves cell picks its layout from the translated widths.set_rowout ofvk_menu.cppgot the stub they need.Non-blocking
HOS_LANGUAGE(the host prefers it overlanguage.txt,host/nn_applet.c) and compares the code exactly, while the host acceptspt-brin any case. In both cases Auto and the Game language row would disagree with the language the game actually runs in. See the inline notes.language.txtis also rewritten on every step, and "Later" on the restart question does not restore it. A write that fails (read-only folder) still updates the in-memory value, so the row looks changed. Consider keeping Auto on the language the game started in until restart, and writing only when the row is left.tr()builds astd::stringkey for each lookup, and takes a global mutex once any format exists (always), every frame for every text. Hundreds of lookups are fine; astring_viewkey and a lock-free exact path would avoid the allocations./usr/share/fonts/google-noto-cjk/...and/usr/share/fonts/dejavu-sans-fonts/DejaVuSans.ttf.@ -0,0 +1,297 @@// (hookshot) The Insert menu's languages (vk_menu_i18n.h).Only
language.txtis read here.HOS_LANGUAGEoverrides it in the host (docs/development.md,host/nn_applet.c), and the host also accepts the code in any case (pt-br). With either, this returns a code that is not the game's,game_lang_index()falls to 0 and Auto picks English. ReadingHOS_LANGUAGEfirst and comparing case-insensitively would match the host.This runs on every Left/Right step of the row, and the result of a failed
fopen(..., "w")is ignored whilegame_language_now()is updated anyway, so the row can show a language that was not saved. With Menu language on Auto the menu also switches language on each step. Maybe write when the row is left, or only report success after the write.This and #2417 can't both land. Both add gpu/vk/vk_menu_i18n.h and tests/test_menu_i18n.py, and both send the same vk_menu.cpp texts through a lookup. #2417 has 16 languages, covers the startup screen and is mergeable with green CI, apart from the points still open in its review. This one has things #2417 lacks. It has the Game language row that writes language.txt, and the game's own CJK fonts (FZLTHB, DFPT_C8, AsiaKKRST) with system fallbacks. It also follows host/nn_applet.c's HOS_LANGUAGE rule and has plural forms for Russian and Polish.
a8911f14addressed every point from hckrman101's review, and CI was green on it. It conflicts with main in vk_menu.cpp again. I'd like NotRexed to pick one as the base. Whichever loses, its extras would make a good follow-up on top of the winner, not a second table. The translations in both are machine-made and still need native review.Thanks for the thorough review. Done in
a8911f1, with main merged ind910595(conflicts in vk_menu.cpp, vk_ui.h and test_stick_dead_zone.py; no force push).Blocking
Merged with current main, so CI can run now.
#2078 and the other renamed texts: the 15 texts main renamed or dropped (Ultrawide's "Off (16:9)", the Upscaler and Render resolution hints, "Auto (Large)/(Medium)" and the rest) are out of the tables. main's 64 new texts are translated in all 13 languages, reusing each table's wording for the rows they name. test_texts_are_the_menus passes on the merged menu.
Non-blocking, all addressed
HOS_LANGUAGE: the menu reads the language as host/nn_applet.c does. HOS_LANGUAGE wins over language.txt, both case-insensitively (pt-br is pt-BR). A language.txt line the game does not know, or a BOM, is skipped, and the fallback is en-US.
Stepping the Game language row:
Auto now stays on the language the game started in until the restart, so the menu no longer changes language (or loads a CJK font) at each step.
language.txt is written once the row is left (another row, page or tab, or the menu closed), not on every step.
A failed write is logged and the row goes back to what language.txt says. set_game_language now returns whether it wrote.
"Later" on the restart question keeps the new language for the next start, which is what the row is for.
Plural: "N more settings" uses Russian's and Polish's third form for 5+ (i18n::plural, " settings (many)", only in those two tables). test_menu_i18n checks it.
Perf: the exact lookup uses string_view keys of the tables' own texts (no allocation) and takes no lock once the table is built (call_once). Only a text built from a format still goes through the cache's lock.
Fedora: Noto CJK in both package layouts (google-noto-sans-cjk-fonts, google-noto-cjk) and dejavu-sans-fonts/DejaVuSans.ttf.
Ran locally on Windows: test_menu_i18n, test_rumble_menu, test_framegen_preset and test_stick_dead_zone (with clang) pass, and vk_menu.cpp, vk_menu_i18n.cpp and vk_imgui.cpp compile. The rest of the suite fails here the same way on a clean main (gcc and Linux only), so CI is the real check.
The translations are still machine-made, as the README says; the new ones want a native check too.
This now conflicts with main in
tests/test_stick_dead_zone.py. Could you merge main into the branch (or rebase) when you get a chance? I'll take another look once it's up to date.I went through the Simplified Chinese entries in menu_i18n.h as a native speaker. The translation holds up well beyond what I expected — there is nothing in it I would actually change.
I would like to see it in a release.
Your note says the tables are machine-made and want a native check. I have ~520
Simplified Chinese menu strings reviewed on a v2.1.0 fork:
https://github.com/gx0803/Hookshot/blob/feat/menu-i18n/gpu/vk/menu_zh.json
Happy to export them as plain en -> zh-Hans data (no code, no build deps) so you
can diff against your zh-Hans table. Would that help?
Re-review at
e44b9fce. Both points from my first review are fixed:HOS_LANGUAGEand case: the menu now reads the language the wayhost/nn_applet.cdoes (env overlanguage.txt, any case, unknown line or BOM skipped, en-US fallback).language.txtis written once when the row is left, a failed write is reported and the row goes back, and Auto stays on the language the game started in until the restart, so nothing reloads fonts on each step.The conflict in
tests/test_stick_dead_zone.pythat I commented on is resolved: the branch merges cleanly with main now. Main's newer menu texts are translated in the 13 tables, andtest_menu_i18npasses in CI.CI: the only red check is
test_env_on(HOS_FPS_LOG inhost/oot_high_fps.c), a file this PR does not touch and main already fixed. The branch is 38 commits behind, so please merge main once more and it should go green.The rest, checked:
game_langis skipped on save and load,menu_languageis the only new ini key. No id is taken, so nothing collides with 29, 30, 44, 49, 50, 52-54, 60 or 8192+.spacedandparastep by UTF-8 sequences);language.txtis opened throughgame_module_pathandfopenlike the other game-folder files, which the UTF-8 manifest covers.test_menu_utf8andtest_utf8_pathspass.test_texts_are_the_menusmakes every key one ofvk_menu.cpp's own texts, so the tables hold the menu's wording only. Place and item names stay English and the game's fonts are read from the player's files at run time, nothing is shipped.On #2417 (same menu, 16 languages, plus the startup screen and overlays): the two add
gpu/vk/vk_menu_i18n.handtests/test_menu_i18n.pyand route the same texts through a lookup, so only one can be the base. I'd take this one: it has the Game language row, the game's CJK fonts with system fallbacks, theHOS_LANGUAGErule, the Russian/Polish plurals, and a native Simplified Chinese check (EverChangAn above), and it merges cleanly while #2417 conflicts now. #2417 should then be rebased onto it as a follow-up: its startup-screen and overlay strings go into themenu_i18n.htables as more entries, and its extra pieces (RX_VK_UI_LANG,RX_VK_UI_FONT, a per-language fallback when no font covers the script) are worth adding on top, not a second catalog.Nits, not blocking:
set_game_languagetruncateslanguage.txtwithfopen("w"), so the comment "whole or not at all" is a little strong (a crash mid-write loses the file), and a trailing comment on the code line is dropped. Write to a temp file and rename if you want it exact.test_texts_are_the_menusonly checks the other direction. Fine for now.Good to go once main is merged.
github-sync referenced this pull request2026-11-12 06:22:54 +00:00
@EverChangAn thank you for going through the Simplified Chinese table as a native speaker, that is exactly the check it needed.
@gx0803 yes, that would help, thank you. A plain en -> zh-Hans export is perfect: I'll diff it against the zh-Hans table and open a follow-up PR with the differences, crediting you. Where your wording and the current one differ I'll take yours, since it was reviewed by hand.