feat: add i18n system with full French translation (tutoiement)#18
Open
foXaCe wants to merge 3 commits into
Open
feat: add i18n system with full French translation (tutoiement)#18foXaCe wants to merge 3 commits into
foXaCe wants to merge 3 commits into
Conversation
Adds a minimal runtime translation layer so all user-facing strings can
be localised. English ships as reference; French is bundled as a fully
translated second locale. The active language is read from
`/mnt/SDCARD/.userdata/shared/minuisettings.txt` (the same `language=`
key NextUI core writes from `Settings → System → Language`), so the
Updater automatically follows the user's choice — no separate
configuration.
## Architecture
- New `src/i18n.rs` — minimal i18n module
- Loads English + the active language from `lang/*.lang` files embedded
at compile time via `include_str!` (no runtime asset dependency)
- Plain `key=value` format, `#` comments, `\n` escapes
- `t!("key")` macro returns the translated string, or the key verbatim
when no entry matches (safe fallback)
- `parking_lot::RwLock` over a small `HashMap`; `OnceLock` lazy init
- New `lang/en.lang` — English reference (~35 keys)
- New `lang/fr.lang` — French translation (~35 keys)
## Coverage
All user-visible strings in `src/ui.rs` and `src/update/mod.rs`:
- Buttons: Return, Accept Warning, Quick Update, Full Update,
Update anyway, Quit, Select Version
- Hints (footer)
- Version status labels (latest installed / new version available / …)
- Background operation labels (fetching releases, downloading, extracting,
rebooting)
- Error messages (fetch failures, missing assets, …)
Strings with placeholders use `{tag}`, `{asset}`, `{err}` and are
substituted with `.replace()` at the call site.
## Glyph atlas pre-loading
egui rasterises glyphs lazily into an atlas, so an existing hack pre-
renders a charset in a transparent label so glyphs are ready by the time
real text uses them. Extended the hack:
1. Added accented Latin characters (ÀÂÄÇÉÈÊËÎÏÔÖÙÛÜŸÆŒàâäçéèêëîïôöùûüÿæœñ
«»°…—–) so French (and German/Spanish/Italian/Portuguese) labels
render correctly.
2. Extended the small-size pre-roll (the corner indicator pre-load that
only contained "XSelect Version") to the same full charset, so glyphs
like `C h a v` are available at `scale(6.0)` for the "Choisir la
version" indicator.
## Compatibility
- Zero functional change for English users (T() falls back to the input)
- No new runtime dependencies — `parking_lot` was already pulled in
- Locale files are embedded in the binary (no `.lang` to ship beside it)
- Adding a locale is one source file + one `include_str!` line — happy
to expand once translators chime in
## Test plan
- [x] Cross-compiled with `cross build --release
--target=aarch64-unknown-linux-gnu` (same pipeline as CI)
- [x] Deployed on TrimUI Brick hardware (tg5040); all menus render in
French when `language=fr`, English when `language=en` or unset
- [x] Accent rendering verified visually (à, â, é, è, ê, ë, î, ï, ô, ù)
- [x] Verified small-font corner indicator ("X Choisir la version") now
shows the full label
foXaCe
added a commit
to foXaCe/minui-presenter
that referenced
this pull request
May 23, 2026
minui-presenter is shared by many third-party MinUI/NextUI paks (Gallery, Artwork Scraper, Another Cheat Downloader, ...) and the button labels it draws were hard-coded English. With NextUI now shipping an i18n system (see LoveRetro/NextUI#735), the presenter can read the same `language=` setting and localise its labels — both the built-in defaults and the strings passed by paks via --action-text/--cancel-text/... ## What it does - Reads `/mnt/SDCARD/.userdata/shared/minuisettings.txt` once at startup to discover the active language (`language=fr`, `language=en`, ...); falls back to English if the file or the key is missing. - Loads the matching `<code>.lang` file from `/mnt/SDCARD/.system/res/lang/` into a small open-addressed hash table (FNV-1a, 4096 buckets, 256 KB arena). Lookups are ~80 ns on a Cortex-A53. - Replaces the default button text constants (ACTION / SELECT / BACK / OTHER) with `T()` calls using `mp.btn.*` keys. - **Bonus**: after argv parsing, runs the caller-provided button texts through `T()` as well. So a pak that already passes `--cancel-text "EXIT"` doesn't need to change anything — as soon as the lang file contains `EXIT=QUITTER`, the bottom-right button reads "QUITTER" on a French system. ## Files - `include/i18n/i18n.{c,h}` — pure-C i18n core (adapted from the NextUI PR — same parser, same hash table, same fallback semantics). Depends only on `defines.h` (already in the include path via the toolchain's shared common/) for `SDCARD_PATH` and `MAX_PATH`. - `lang/en.lang`, `lang/fr.lang` — reference + French. Translators can copy en.lang to `<code>.lang` and translate the values; no code change needed. - `Makefile` — adds `include/i18n/i18n.c` to both the macOS and the Linux build SOURCE lines. - `minui-presenter.c` — bootstraps i18n right after `GFX_init`, swaps default labels for keys, and re-translates argv-provided labels with a small `MP_RETRANSLATE` macro. ## Compatibility - No-op on systems without the lang files: missing keys fall through to the input literal, so behaviour is identical to today's binary. - Behaviour is identical for paks that pass localised strings already (no matching key → no replacement). - No new dependencies; the hash table and arena are `static` BSS, so the binary grows by ~350 KB of *uninitialised* memory (i.e. not on disk) and the file size delta is just the parser code (~3 KB). ## Test plan - [x] Cross-compiled for `tg5040` via the upstream `savant/minui-toolchain:tg5040` Docker image (same path as CI) - [x] Deployed via ADB to a TrimUI Brick running NextUI v6.11.2 with `language=fr` in `minuisettings.txt` - [x] Gallery pak: bottom-right button now reads "QUITTER" (was "EXIT") - [x] Falls back cleanly to English when `language=en` or no lang files present - [ ] Other platforms: not validated on hardware (identical code path) Companion PRs: - NextUI core i18n + 534 keys: LoveRetro/NextUI#735 - NextUI Updater pak FR: LoveRetro/nextui-updater-pak#18
foXaCe
added a commit
to foXaCe/minui-presenter
that referenced
this pull request
May 23, 2026
minui-presenter is shared by many third-party MinUI/NextUI paks (Gallery, Artwork Scraper, Another Cheat Downloader, ...) and the button labels it draws were hard-coded English. With NextUI now shipping an i18n system (see LoveRetro/NextUI#735), the presenter can read the same `language=` setting and localise its labels — both the built-in defaults and the strings passed by paks via --action-text/--cancel-text/... ## What it does - Reads `/mnt/SDCARD/.userdata/shared/minuisettings.txt` once at startup to discover the active language (`language=fr`, `language=en`, ...); falls back to English if the file or the key is missing. - Loads the matching `<code>.lang` file from `/mnt/SDCARD/.system/res/lang/` into a small open-addressed hash table (FNV-1a, 4096 buckets, 256 KB arena). Lookups are ~80 ns on a Cortex-A53. - Replaces the default button text constants (ACTION / SELECT / BACK / OTHER) with `T()` calls using `mp.btn.*` keys. - **Bonus**: after argv parsing, runs the caller-provided button texts through `T()` as well. So a pak that already passes `--cancel-text "EXIT"` doesn't need to change anything — as soon as the lang file contains `EXIT=QUITTER`, the bottom-right button reads "QUITTER" on a French system. ## Files - `include/i18n/i18n.{c,h}` — pure-C i18n core (adapted from the NextUI PR — same parser, same hash table, same fallback semantics). Depends only on `defines.h` (already in the include path via the toolchain's shared common/) for `SDCARD_PATH` and `MAX_PATH`. - `lang/en.lang`, `lang/fr.lang` — reference + French. Translators can copy en.lang to `<code>.lang` and translate the values; no code change needed. - `Makefile` — adds `include/i18n/i18n.c` to both the macOS and the Linux build SOURCE lines. - `minui-presenter.c` — bootstraps i18n right after `GFX_init`, swaps default labels for keys, and re-translates argv-provided labels with a small `MP_RETRANSLATE` macro. ## Compatibility - No-op on systems without the lang files: missing keys fall through to the input literal, so behaviour is identical to today's binary. - Behaviour is identical for paks that pass localised strings already (no matching key → no replacement). - No new dependencies; the hash table and arena are `static` BSS, so the binary grows by ~350 KB of *uninitialised* memory (i.e. not on disk) and the file size delta is just the parser code (~3 KB). ## Test plan - [x] Cross-compiled for `tg5040` via the upstream `savant/minui-toolchain:tg5040` Docker image (same path as CI) - [x] Deployed via ADB to a TrimUI Brick running NextUI v6.11.2 with `language=fr` in `minuisettings.txt` - [x] Gallery pak: bottom-right button now reads "QUITTER" (was "EXIT") - [x] Falls back cleanly to English when `language=en` or no lang files present - [ ] Other platforms: not validated on hardware (identical code path) Companion PRs: - NextUI core i18n + 534 keys: LoveRetro/NextUI#735 - NextUI Updater pak FR: LoveRetro/nextui-updater-pak#18
5 tasks
foXaCe
added a commit
to foXaCe/minui-presenter
that referenced
this pull request
May 23, 2026
minui-presenter is shared by many third-party MinUI/NextUI paks (Gallery, Artwork Scraper, Another Cheat Downloader, ...) and the button labels it draws were hard-coded English. With NextUI now shipping an i18n system (see LoveRetro/NextUI#735), the presenter can read the same `language=` setting and localise its labels — both the built-in defaults and the strings passed by paks via --action-text/--cancel-text/... ## What it does - Reads `/mnt/SDCARD/.userdata/shared/minuisettings.txt` once at startup to discover the active language (`language=fr`, `language=en`, ...); falls back to English if the file or the key is missing. - Loads the matching `<code>.lang` file from `/mnt/SDCARD/.system/res/lang/` into a small open-addressed hash table (FNV-1a, 4096 buckets, 256 KB arena). Lookups are ~80 ns on a Cortex-A53. - Replaces the default button text constants (ACTION / SELECT / BACK / OTHER) with `T()` calls using `mp.btn.*` keys. - **Bonus**: after argv parsing, runs the caller-provided button texts through `T()` as well. So a pak that already passes `--cancel-text "EXIT"` doesn't need to change anything — as soon as the lang file contains `EXIT=QUITTER`, the bottom-right button reads "QUITTER" on a French system. ## Files - `include/i18n/i18n.{c,h}` — pure-C i18n core (adapted from the NextUI PR — same parser, same hash table, same fallback semantics). Depends only on `defines.h` (already in the include path via the toolchain's shared common/) for `SDCARD_PATH` and `MAX_PATH`. - `lang/en.lang`, `lang/fr.lang` — reference + French. Translators can copy en.lang to `<code>.lang` and translate the values; no code change needed. - `Makefile` — adds `include/i18n/i18n.c` to both the macOS and the Linux build SOURCE lines. - `minui-presenter.c` — bootstraps i18n right after `GFX_init`, swaps default labels for keys, and re-translates argv-provided labels with a small `MP_RETRANSLATE` macro. ## Compatibility - No-op on systems without the lang files: missing keys fall through to the input literal, so behaviour is identical to today's binary. - Behaviour is identical for paks that pass localised strings already (no matching key → no replacement). - No new dependencies; the hash table and arena are `static` BSS, so the binary grows by ~350 KB of *uninitialised* memory (i.e. not on disk) and the file size delta is just the parser code (~3 KB). ## Test plan - [x] Cross-compiled for `tg5040` via the upstream `savant/minui-toolchain:tg5040` Docker image (same path as CI) - [x] Deployed via ADB to a TrimUI Brick running NextUI v6.11.2 with `language=fr` in `minuisettings.txt` - [x] Gallery pak: bottom-right button now reads "QUITTER" (was "EXIT") - [x] Falls back cleanly to English when `language=en` or no lang files present - [ ] Other platforms: not validated on hardware (identical code path) Companion PRs: - NextUI core i18n + 534 keys: LoveRetro/NextUI#735 - NextUI Updater pak FR: LoveRetro/nextui-updater-pak#18
…ment - Extract 6 new keys to en.lang / fr.lang: - warn.downgrade (version downgrade warning, ui.rs) - title.version_selector / title.version_selector_warning (ui.rs) - err.no_matching_tag / err.github_api_failed (update/*.rs) - op.download_asset (update/mod.rs) - Replace literals with t!() in ui.rs, update/mod.rs, update/fetching.rs - Rewrite existing French strings from vouvoiement to tutoiement: - op.extract_archive: "Veuillez patienter" -> "Patiente" - status.latest_already_installed: "Vous avez" -> "Tu as" - Parity verified: 46 EN keys ↔ 46 FR keys, placeholders consistent
- Clarify return_to_latest and confirm_warning hints - Replace '...' with '…' in all background operation strings
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a minimal runtime translation layer so every user-facing string in the Updater is localised. English ships as reference; French is bundled as a fully translated second locale.
The active language is read from
/mnt/SDCARD/.userdata/shared/minuisettings.txt(the samelanguage=key NextUI core writes fromSettings → System → Language— see LoveRetro/NextUI#735), so the Updater automatically follows the user's choice. No separate configuration to maintain.Architecture
src/i18n.rs— minimal i18n modulelang/*.langfiles embedded at compile time viainclude_str!(no runtime asset dependency, no extra files to ship)key=valueformat,#comments,\nescapest!("key")macro returns the translated string, falls back to the English entry, then to the key verbatim (safe fallback)parking_lot::RwLock(already in the dep tree) over a smallHashMap; lazy init viaOnceLocklang/en.lang— English reference (46 keys)lang/fr.lang— French translation (46 keys, tutoiement style)Coverage — every user-visible literal
src/ui.rs,src/update/mod.rs, andsrc/update/fetching.rs:title.*warn.*btn.*hint.*status.*op.*err.*Strings with placeholders use
{tag},{asset},{err},{status}and are substituted with.replace()at the call site, which keeps the lang files reorderable without touching the format machinery.French style: tutoiement
The French locale uses tutoiement consistently (« tu », « ton ») — appropriate for a hobbyist handheld console community. Verified: no « Vous / Votre / Veuillez » left over. Typography: straight apostrophes (consistent with project convention), ASCII spaces before
:!?(avoiding U+202F that may not be in every embedded font).Glyph atlas pre-loading
egui rasterises glyphs lazily into an atlas, so the existing transparent-label hack pre-rolled the charset needed for runtime labels. Extended in two places:
ÀÂÄÇÉÈÊËÎÏÔÖÙÛÜŸÆŒàâäçéèêëîïôöùûüÿæœñ«»°…—–) so French (and German / Spanish / Italian / Portuguese) labels render correctly. Without this,êin quand même rendered as whitespace.scale(6.0)) only contained"XSelect Version", so glyphs likeC h a vwere missing at that size for Choisir la version. Replaced with the full charset.Compatibility
t!("key")falls back to the input string when no translation existsparking_lotwas already pulled in.langto ship alongside)lang/<code>.langfile + oneinclude_str!line insrc/i18n.rs. Happy to wire that into amatchovercodeif you'd prefer a more dynamic registry.Commits
1ef170f— initial i18n layer + ~35 strings + accented glyph atlas89aac45— extract the remaining 6 hardcoded literals (warn.downgrade,title.version_selector*,err.no_matching_tag,err.github_api_failed,op.download_asset) and switch French to tutoiement (« Veuillez patienter » → « Patiente », « Vous avez » → « Tu as »). Final count: 46 ↔ 46 with placeholder parity.Test plan
cross build --release --target=aarch64-unknown-linux-gnu(same pipeline as CI'sbuild.yml) — first commit validated on hardwarelanguage=fr, English whenlanguage=enor unset — first commit89aac45) — cross-build + on-device validation pendinglang/en.langtolang/<code>.langand translate valuesCompanion PR on NextUI core: LoveRetro/NextUI#735