feat: the Insert menu in 13 languages, with Game language and Menu la… #1864

Merged
roblesleo merged 8 commits from feat/menu-languages into main 2026-11-12 03:29:07 +00:00
roblesleo commented 2026-11-10 18:54:09 +00:00 (Migrated from github.com)

…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.

  • gpu/vk/menu_i18n.h: one table a language (Portuguese (Brazil), Spanish (Latin America and Spain), French, German, Italian, Dutch, Polish, Russian, Japanese, Korean, Chinese (Simplified and Traditional)), each English text of vk_menu.cpp and its translation, about 400 a language, the Audio and Profile rows of #1825 too. Place and item names stay English for now.
  • vk_menu_i18n.cpp looks a text up whole; a text the menu made with snprintf is matched against the table's printf formats ("%d of %d") and put together from the translated one. ui::text, ui::para, ui::size_of and ui::spaced (vk_ui.h) look every text up, so no row needs a call of its own; spaced and para step by whole UTF-8 characters.
  • Menu language row (Gameplay): Auto follows the game's language (language.txt; fr-CA and en-GB take French and English), or a language by its own name. Saved as menu_language. Game language row: the game's 16 languages, written to language.txt for the next start (a restart row); not kept in the options file.
  • Japanese, Chinese and Korean letters: the game's own fonts for them (FZLTHB, DFPT_C8, AsiaKKRST, from the player's files as the menu font is) merged into the menu's fonts, then the system's (Yu Gothic, Microsoft YaHei/JhengHei, Malgun Gothic; Noto CJK on Linux) for what they lack. Japanese takes the system's alone: the game's is a subset, and its kanji mixed with the system's in one word. Other languages get Segoe UI / DejaVu Sans behind the menu fonts for letters they lack. Merged once a language.
  • tests/test_menu_i18n.py: every table text is one of vk_menu.cpp's, every translation keeps its printf conversions in order and its leading and trailing spaces. test_rumble_menu, test_framegen_preset and test_stick_dead_zone cut set_row and menu_init's loop out of vk_menu.cpp: their stubs gain the Game language row's hook.

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 on 51de681, 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
vk_002520
vk_002220
vk_002580

…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. - gpu/vk/menu_i18n.h: one table a language (Portuguese (Brazil), Spanish (Latin America and Spain), French, German, Italian, Dutch, Polish, Russian, Japanese, Korean, Chinese (Simplified and Traditional)), each English text of vk_menu.cpp and its translation, about 400 a language, the Audio and Profile rows of #1825 too. Place and item names stay English for now. - vk_menu_i18n.cpp looks a text up whole; a text the menu made with snprintf is matched against the table's printf formats ("%d of %d") and put together from the translated one. ui::text, ui::para, ui::size_of and ui::spaced (vk_ui.h) look every text up, so no row needs a call of its own; spaced and para step by whole UTF-8 characters. - Menu language row (Gameplay): Auto follows the game's language (language.txt; fr-CA and en-GB take French and English), or a language by its own name. Saved as menu_language. Game language row: the game's 16 languages, written to language.txt for the next start (a restart row); not kept in the options file. - Japanese, Chinese and Korean letters: the game's own fonts for them (FZLTHB, DFPT_C8, AsiaKKRST, from the player's files as the menu font is) merged into the menu's fonts, then the system's (Yu Gothic, Microsoft YaHei/JhengHei, Malgun Gothic; Noto CJK on Linux) for what they lack. Japanese takes the system's alone: the game's is a subset, and its kanji mixed with the system's in one word. Other languages get Segoe UI / DejaVu Sans behind the menu fonts for letters they lack. Merged once a language. - tests/test_menu_i18n.py: every table text is one of vk_menu.cpp's, every translation keeps its printf conversions in order and its leading and trailing spaces. test_rumble_menu, test_framegen_preset and test_stick_dead_zone cut set_row and menu_init's loop out of vk_menu.cpp: their stubs gain the Game language row's hook. 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 on 51de681, 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 <img width="1280" height="720" alt="vk_002520" src="https://github.com/user-attachments/assets/a25594c6-b60d-4145-b5ea-f4e51cb49bb9" /> <img width="1280" height="720" alt="vk_002220" src="https://github.com/user-attachments/assets/8bcd1f43-db95-4f95-825e-2533e93b064a" /> <img width="1280" height="720" alt="vk_002580" src="https://github.com/user-attachments/assets/71547176-5317-4ac6-b04d-5db359356f01" />
hckrman101 (Migrated from github.com) reviewed 2026-11-11 02:21:42 +00:00
hckrman101 (Migrated from github.com) left a comment

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

  • The branch conflicts with main (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.
  • #2078 renames the Ultrawide label. test_texts_are_the_menus will 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

  • Option ids: the two new rows are menu-only (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_language is the generated ini key (fits the 32-byte buffer), game_lang is skipped on save and load, and both rows are preset_exempt.
  • Fonts: ImGui is 1.92.x (dynamic atlas), so merging fonts before NewFrame on first use is fine, and load_fonts only runs once, so the done list 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 across tr_text/table() in a way that can deadlock. spaced/para now 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.
  • The tests that cut set_row out of vk_menu.cpp got the stub they need.

Non-blocking

  • The menu ignores HOS_LANGUAGE (the host prefers it over language.txt, host/nn_applet.c) and compares the code exactly, while the host accepts pt-br in 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.
  • With Menu language on Auto, stepping through the Game language row changes the whole menu under the player on every step (and reads a 10-20 MB CJK font the first time it reaches ja/ko/zh). language.txt is 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.
  • Plural forms: the restart line builds "N more settings" from one singular and one plural. Russian ("5 параметра") and Polish ("5 ustawienia") need a third form for 5+. Small, as N is a handful of rows.
  • Perf nit: tr() builds a std::string key for each lookup, and takes a global mutex once any format exists (always), every frame for every text. Hundreds of lookups are fine; a string_view key and a lock-free exact path would avoid the allocations.
  • Nit: Linux font paths cover Debian/Arch only; Fedora has /usr/share/fonts/google-noto-cjk/... and /usr/share/fonts/dejavu-sans-fonts/DejaVuSans.ttf.
  • Translations look natural in the ones I sampled (Restart needed, Menu language, Game language, setting/more). The README note that they want native review is the right call.
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** - The branch conflicts with main (`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. - #2078 renames the Ultrawide label. `test_texts_are_the_menus` will 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** - Option ids: the two new rows are menu-only (`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_language` is the generated ini key (fits the 32-byte buffer), `game_lang` is skipped on save and load, and both rows are `preset_exempt`. - Fonts: ImGui is 1.92.x (dynamic atlas), so merging fonts before `NewFrame` on first use is fine, and `load_fonts` only runs once, so the `done` list 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 across `tr_text`/`table()` in a way that can deadlock. `spaced`/`para` now 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. - The tests that cut `set_row` out of `vk_menu.cpp` got the stub they need. **Non-blocking** - The menu ignores `HOS_LANGUAGE` (the host prefers it over `language.txt`, `host/nn_applet.c`) and compares the code exactly, while the host accepts `pt-br` in 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. - With Menu language on Auto, stepping through the Game language row changes the whole menu under the player on every step (and reads a 10-20 MB CJK font the first time it reaches ja/ko/zh). `language.txt` is 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. - Plural forms: the restart line builds "N more settings" from one singular and one plural. Russian ("5 параметра") and Polish ("5 ustawienia") need a third form for 5+. Small, as N is a handful of rows. - Perf nit: `tr()` builds a `std::string` key for each lookup, and takes a global mutex once any format exists (always), every frame for every text. Hundreds of lookups are fine; a `string_view` key and a lock-free exact path would avoid the allocations. - Nit: Linux font paths cover Debian/Arch only; Fedora has `/usr/share/fonts/google-noto-cjk/...` and `/usr/share/fonts/dejavu-sans-fonts/DejaVuSans.ttf`. - Translations look natural in the ones I sampled (Restart needed, Menu language, Game language, setting/more). The README note that they want native review is the right call.
@ -0,0 +1,297 @@
// (hookshot) The Insert menu's languages (vk_menu_i18n.h).
hckrman101 (Migrated from github.com) commented 2026-11-11 02:21:42 +00:00

Only language.txt is read here. HOS_LANGUAGE overrides 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. Reading HOS_LANGUAGE first and comparing case-insensitively would match the host.

Only `language.txt` is read here. `HOS_LANGUAGE` overrides 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. Reading `HOS_LANGUAGE` first and comparing case-insensitively would match the host.
hckrman101 (Migrated from github.com) commented 2026-11-11 02:21:42 +00:00

This runs on every Left/Right step of the row, and the result of a failed fopen(..., "w") is ignored while game_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 runs on every Left/Right step of the row, and the result of a failed `fopen(..., "w")` is ignored while `game_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.
Joey-Magnus (Migrated from github.com) reviewed 2026-11-11 21:46:11 +00:00
Joey-Magnus (Migrated from github.com) left a comment

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. a8911f14 addressed 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.

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. a8911f14 addressed 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.
roblesleo commented 2026-11-11 21:53:08 +00:00 (Migrated from github.com)

Thanks for the thorough review. Done in a8911f1, with main merged in d910595 (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.

Thanks for the thorough review. Done in a8911f1, with main merged in d910595 (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.
hckrman101 commented 2026-11-11 22:55:38 +00:00 (Migrated from github.com)

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.

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.
EverChangAn commented 2026-11-12 00:55:38 +00:00 (Migrated from github.com)

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.

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.
gx0803 commented 2026-11-12 02:43:35 +00:00 (Migrated from github.com)

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?

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?
hckrman101 (Migrated from github.com) approved these changes 2026-11-12 03:28:42 +00:00
hckrman101 (Migrated from github.com) left a comment

Re-review at e44b9fce. Both points from my first review are fixed:

  • HOS_LANGUAGE and case: the menu now reads the language the way host/nn_applet.c does (env over language.txt, any case, unknown line or BOM skipped, en-US fallback).
  • Game language row: language.txt is 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.py that I commented on is resolved: the branch merges cleanly with main now. Main's newer menu texts are translated in the 13 tables, and test_menu_i18n passes in CI.

CI: the only red check is test_env_on (HOS_FPS_LOG in host/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:

  • Option ids: Game language and Menu language are menu-only (not sent to the host), game_lang is skipped on save and load, menu_language is the only new ini key. No id is taken, so nothing collides with 29, 30, 44, 49, 50, 52-54, 60 or 8192+.
  • UTF-8: texts stay whole characters (spaced and para step by UTF-8 sequences); language.txt is opened through game_module_path and fopen like the other game-folder files, which the UTF-8 manifest covers. test_menu_utf8 and test_utf8_paths pass.
  • Game text: test_texts_are_the_menus makes every key one of vk_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.h and tests/test_menu_i18n.py and 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, the HOS_LANGUAGE rule, 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 the menu_i18n.h tables 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_language truncates language.txt with fopen("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.
  • A text the menu gets later without a table entry falls back to English silently; test_texts_are_the_menus only checks the other direction. Fine for now.
  • The translations are still machine-made; gx0803 offered a reviewed zh-Hans set above, worth diffing against yours.

Good to go once main is merged.

Re-review at e44b9fce. Both points from my first review are fixed: - `HOS_LANGUAGE` and case: the menu now reads the language the way `host/nn_applet.c` does (env over `language.txt`, any case, unknown line or BOM skipped, en-US fallback). - Game language row: `language.txt` is 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.py` that I commented on is resolved: the branch merges cleanly with main now. Main's newer menu texts are translated in the 13 tables, and `test_menu_i18n` passes in CI. CI: the only red check is `test_env_on` (HOS_FPS_LOG in `host/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: - Option ids: Game language and Menu language are menu-only (not sent to the host), `game_lang` is skipped on save and load, `menu_language` is the only new ini key. No id is taken, so nothing collides with 29, 30, 44, 49, 50, 52-54, 60 or 8192+. - UTF-8: texts stay whole characters (`spaced` and `para` step by UTF-8 sequences); `language.txt` is opened through `game_module_path` and `fopen` like the other game-folder files, which the UTF-8 manifest covers. `test_menu_utf8` and `test_utf8_paths` pass. - Game text: `test_texts_are_the_menus` makes every key one of `vk_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.h` and `tests/test_menu_i18n.py` and 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, the `HOS_LANGUAGE` rule, 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 the `menu_i18n.h` tables 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_language` truncates `language.txt` with `fopen("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. - A text the menu gets later without a table entry falls back to English silently; `test_texts_are_the_menus` only checks the other direction. Fine for now. - The translations are still machine-made; gx0803 offered a reviewed zh-Hans set above, worth diffing against yours. Good to go once main is merged.
Owner

@roblesleo commented on GitHub · 2026-11-12 13:16 UTC · original

@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.

> [@roblesleo](https://github.com/roblesleo) commented on GitHub · 2026-11-12 13:16 UTC · [original](https://github.com/NotRexed/Hookshot/pull/1864#issuecomment-6824655025) @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. <!-- github-sync:c:6824655025 -->
Commenting is not possible because the repository is archived.
No description provided.