Skip to content

feat: add i18n system with full French translation (tutoiement)#18

Open
foXaCe wants to merge 3 commits into
LoveRetro:mainfrom
foXaCe:feature/i18n-translation
Open

feat: add i18n system with full French translation (tutoiement)#18
foXaCe wants to merge 3 commits into
LoveRetro:mainfrom
foXaCe:feature/i18n-translation

Conversation

@foXaCe

@foXaCe foXaCe commented May 23, 2026

Copy link
Copy Markdown

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 same language= key NextUI core writes from Settings → System → Language — see LoveRetro/NextUI#735), so the Updater automatically follows the user's choice. No separate configuration to maintain.

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, no extra files to ship)
    • Plain key=value format, # comments, \n escapes
    • t!("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 small HashMap; lazy init via OnceLock
  • New lang/en.lang — English reference (46 keys)
  • New lang/fr.lang — French translation (46 keys, tutoiement style)

Coverage — every user-visible literal

src/ui.rs, src/update/mod.rs, and src/update/fetching.rs:

Category Prefix Examples
Window / screen titles title.* Version Selector, Version Selector Warning
Warnings warn.* Downgrade warning (multi-line)
Buttons btn.* Return, Accept Warning, Quick Update, Full Update, Update anyway, Quit, Select Version
Hints (footer) hint.* Quit NextUI Updater, Ignore current version, Extract full zip files…
Version status status.* You currently have the latest available version, New version available, Latest version: NextUI {tag}
Background operations op.* Fetching latest NextUI release..., Downloading {asset}..., Extracting {asset}..., Rebooting system...
Errors err.* Update failed: {err}, No .pakz asset found, Latest release has no matching tag: {tag}, GitHub API request failed: {status}, …

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:

  1. Main hack — added accented Latin characters (ÀÂÄÇÉÈÊËÎÏÔÖÙÛÜŸÆŒàâäçéèêëîïôöùûüÿæœñ«»°…—–) so French (and German / Spanish / Italian / Portuguese) labels render correctly. Without this, ê in quand même rendered as whitespace.
  2. Small-size pre-roll (the corner version-selector indicator at scale(6.0)) only contained "XSelect Version", so glyphs like C h a v were missing at that size for Choisir la version. Replaced with the full charset.

Compatibility

  • Zero functional change for English users — t!("key") falls back to the input string when no translation exists
  • No new runtime dependencies — parking_lot was already pulled in
  • Locale files are embedded in the binary at compile time (no .lang to ship alongside)
  • Adding a new locale = one lang/<code>.lang file + one include_str! line in src/i18n.rs. Happy to wire that into a match over code if you'd prefer a more dynamic registry.

Commits

  • 1ef170f — initial i18n layer + ~35 strings + accented glyph atlas
  • 89aac45 — 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-compiled with cross build --release --target=aarch64-unknown-linux-gnu (same pipeline as CI's build.yml) — first commit validated on hardware
  • Deployed on TrimUI Brick hardware (tg5040) — first commit
  • All menus render in French when language=fr, English when language=en or unset — first commit
  • Accent rendering verified visually with a framebuffer screenshot (à, â, é, è, ê, ë, î, ï, ô, ù all render)
  • Small-font corner indicator "X Choisir la version" displays fully
  • Second commit (89aac45) — cross-build + on-device validation pending
  • Smart Pro (tg5050) — built but not validated on hardware (identical Rust code path, should be a no-op)
  • Translators welcome: copy lang/en.lang to lang/<code>.lang and translate values

Companion PR on NextUI core: LoveRetro/NextUI#735

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
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
@foXaCe foXaCe changed the title feat: add i18n translation system with French locale feat: add i18n system with full French translation (tutoiement) May 24, 2026
- Clarify return_to_latest and confirm_warning hints
- Replace '...' with '…' in all background operation strings
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant