Skip to content

feat: add UI translation system (i18n) with live language switching#735

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

feat: add UI translation system (i18n) with live language switching#735
foXaCe wants to merge 17 commits into
LoveRetro:mainfrom
foXaCe:feature/i18n-translation-system

Conversation

@foXaCe
Copy link
Copy Markdown

@foXaCe foXaCe commented May 21, 2026

Summary

Adds a runtime translation layer so all user-facing strings can be localized without restarting the app. English ships as the reference language; French is bundled as a complete second locale (425 keys).

The default behavior is unchanged for existing installs: with no .lang files present or language=en, every T(key) falls back to the literal — which for legacy strings is the original English text → zero regression.

Architecture

  • New workspace/all/common/i18n.{c,h} — minimal i18n core
    • Hash table (FNV-1a, 1024 buckets, 32 KB arena)
    • Plain key=value .lang files (no JSON dependency)
    • T(key) returns the translation, or the input verbatim when no entry matches (safe fallback)
    • I18N_init(code) called from GFX_init; I18N_reload(code) hot-swaps the table at runtime
  • New skeleton/SYSTEM/res/lang/{en,fr}.lang (425 paired keys)
  • NextUISettings.language[8] persisted in minuisettings.txt (defaults to \"en\"; backward-compatible read)

Coverage

All user-visible strings flow through T():

  • Launcher: nextui.c
  • In-game menu: minarch/{ma_menu,ma_frontend_opts,ma_saves,ra_integration}.c
  • Settings UI (settings/*.cpp/hpp): Appearance, Display, Audio, System, FN switch, In-Game, RetroAchievements (incl. sync overlay), About, Bluetooth, WiFi, color picker, keyboard prompt
  • Hardware hints in common/api.c
  • Tools: battery, clock, gametime, ledcontrol, minput, bootlogo

Hot-reload (Settings only)

`Settings → System → Language` switches locale live without exit:

  • `AbstractMenuItem::getName/getDesc` and `MenuItem::getLabel/getLabels` now call `T()` at each access (storing keys, not pre-translated strings)
  • `ScopedOverlay` and `MenuList::showOverlay` apply `T()` internally
  • `getRawName()` added so `selectByName` survives a language change

Other apps (launcher, minarch, tools) load the active language on startup — they are short-lived processes relaunched per action, so per-app hot-reload is unnecessary.

Build

`i18n.c` linked into every app that calls `T()`: nextui, minarch, settings, battery, clock, gametime, ledcontrol, minput, bootlogo (9 makefiles updated). Pure C module; safe to include from C++ via the existing `extern "C"` block in `settings/menu.hpp`.

Resource impact:

  • Disk: ~7 KB per locale (en.lang ≈ 20 KB, fr.lang ≈ 22 KB)
  • RAM: ~24 KB for the active table (8 KB buckets + 16 KB arena)
  • CPU: FNV-1a + open-addressed lookup ≈ 80 ns per `T()` call on Cortex-A53 @ 1.2 GHz; negligible vs. `TTF_RenderUTF8_Blended` already on the hot path

Compatibility

  • No-op for users without .lang files installed — behavior identical to before this PR
  • New `language=` line appended to `minuisettings.txt` (older consumers ignore it)
  • Adding a locale = drop `.lang` into `.system/res/lang/`; auto-discovered by Settings via `discoverLanguages()`
  • Latin-script locales fully covered by the existing `font1.ttf` (BPreplay). CJK / Cyrillic would need a complementary font pack — could naturally pair with #729 (custom-font picker) for a later phase

Test plan

  • Built natively (PLATFORM=desktop) and cross-compiled for tg5040 (Docker toolchain)
  • Smoke-tested the i18n module standalone (table load, lookup, fallback on missing key, hot-reload between en ↔ fr)
  • Settings runs without crash, full navigation Appearance/Display/System/FN switch/In-Game/RetroAchievements/About
  • Live language switch (Settings → System → Language → Français) updates every menu item, value, description and overlay without restart
  • Deployed on TrimUI Brick hardware; main launcher, in-game menu, and Tools paks all render in the selected language
  • Smart Pro (tg5050) — built but not validated on hardware (identical platform code path, should be a no-op)
  • Translators welcome: copy `en.lang` to `.lang` and translate values

Notes

  • Hardware button labels (A, B, X, Y, L1/R1, MENU, POWER…) are kept untranslated since they are physically silkscreened on the device
  • Language autonyms (English, Français, Deutsch…) are kept in their own language by convention
  • Keyboard virtual keys "shift", "space", "enter" are left as-is since they also serve as internal identifiers via `strcmp` and refactoring that would be disproportionate for this PR

Happy to split this into smaller PRs (core i18n, launcher, settings, tools…) if that's preferred for review.

foXaCe added 3 commits May 21, 2026 19:35
Adds a runtime translation layer so all user-facing strings can be
localized without restarting the app. English ships as the reference
language; French is bundled as a complete second locale (425 keys).

## Architecture

- New `workspace/all/common/i18n.{c,h}` — minimal i18n core:
  - Hash table (FNV-1a, 1024 buckets, 32 KB arena)
  - Parser for plain `key=value` `.lang` files (no JSON dep)
  - Lookup `T(key)` returns the translated string, or the input
    verbatim when no entry matches → safe fallback
  - `I18N_init(code)` called from `GFX_init`; `I18N_reload(code)`
    hot-swaps the table at runtime
- New `skeleton/SYSTEM/res/lang/{en,fr}.lang` (425 key pairs)
- `NextUISettings.language[8]` persisted in `minuisettings.txt`
  (defaults to "en"; backward-compatible read)

## Coverage

All user-visible strings are passed through `T()`:

- Launcher: `workspace/all/nextui/nextui.c`
- In-game menu: `workspace/all/minarch/{ma_menu,ma_frontend_opts,
  ma_saves,ra_integration}.c`
- Settings UI (`workspace/all/settings/*.cpp/hpp`): main menus,
  Appearance, Display, Audio, System, FN switch, In-Game,
  RetroAchievements (including sync overlay messages), About,
  Bluetooth, WiFi, color picker, keyboard prompt
- Hardware hints in `workspace/all/common/api.c`
- Tools: `battery`, `clock`, `gametime`, `ledcontrol`, `minput`,
  `bootlogo` (button hints + on-screen labels)

## Hot-reload (Settings only)

`Settings → System → Language` switches locale live without exit:

- `AbstractMenuItem::getName/getDesc` and `MenuItem::getLabel/getLabels`
  now call `T()` at each access (storing i18n keys, not pre-translated
  strings)
- `ScopedOverlay` and `MenuList::showOverlay` apply `T()` internally
- `getRawName()` added for `selectByName` so selection survives a
  language change
- Other apps (launcher, minarch, tools) load the active language on
  startup — since they're short-lived processes relaunched per action,
  no hot-reload is required there

## Build

- `i18n.c` linked into every app that calls `T()`: `nextui`, `minarch`,
  `settings`, `battery`, `clock`, `gametime`, `ledcontrol`, `minput`,
  `bootlogo` (9 makefiles updated)
- Pure C module; safe to include from C++ via `extern "C"` (already
  done in `settings/menu.hpp`)

## Compatibility

- Default behavior unchanged: with no `.lang` files installed or
  `language=en`, every `T(key)` falls back to the literal key — which
  for legacy strings is the original English text → zero regression
- New `language=` line appended to `minuisettings.txt` (older
  consumers can simply ignore it)
- Adding a new locale = drop `<code>.lang` into `.system/res/lang/`;
  it is auto-discovered by Settings via `discoverLanguages()`
Covers the ~30 options of the in-game Frontend menu (MENU → Options →
Frontend during gameplay) plus their option-value labels. Adds ~90 new
key pairs to en.lang and fr.lang (total: 514 keys).

## Changes

- workspace/all/minarch/ma_config.c
  - Replace literal English strings in .name / .desc with i18n keys
    (e.g. `"Screen Scaling"` → `"frontend.opt.screen_scaling"`)
  - Replace label arrays (onoff_labels, scaling_labels, resample_labels,
    rewind_enable_labels, rewind_compression_accel_labels,
    ambient_labels, effect_labels, overlay_labels) with key-based
    entries
  - getScreenScalingDesc() returns a key instead of a literal
- workspace/all/minarch/ma_menu.c
  - Wrap rendering sites in T() so the displayed text is translated at
    each frame (live-reloadable):
    - item->name (3 sites)
    - item->values[item->value] (1 site)
    - item->values[j] (size calc, 1 site)
    - list/item->desc footer (size + blit, 2 sites)
- skeleton/SYSTEM/res/lang/en.lang
- skeleton/SYSTEM/res/lang/fr.lang
  - +~90 paired keys covering option names, descriptions, and value
    labels for the Frontend menu
…ollections)

The main launcher built its root menu through Entry_new(path, ENTRY_DIR),
which derives the displayed name from the directory path
(getDisplayName()), bypassing translation. Switch those root entries to
Entry_newNamed with i18n keys so the labels render in the active
language.

## Changes

- workspace/all/nextui/nextui.c
  - getRoot(): Tools / Recently Played / Collections now built with
    Entry_newNamed + T() keys
  - getQuickEntries(): same fix for the Quickswitcher entries
- skeleton/SYSTEM/res/lang/en.lang
- skeleton/SYSTEM/res/lang/fr.lang
  - +3 paired keys: launcher.tools, launcher.recently_played,
    launcher.collections

Brings total to 517 paired keys.
foXaCe added 6 commits May 23, 2026 07:24
Pass the derived display name through T() in Entry_new so well-known
pak and folder names (Settings, Updater, Pak Store, ...) can be
localised via the .lang files without renaming the actual directories
on disk.

Behaviour:
- en.lang lists each key with the original English value as its value,
  so English users see no change (T() returns "Settings" → "Settings").
- fr.lang translates the entries that have a natural French equivalent
  (Settings → Réglages, Updater → Mise à jour, USB Mass Storage →
  Stockage USB, ...). Proper nouns (Pak Store, ScrapeGoat, LED'oh!)
  are deliberately left untranslated.
- ROM folders, custom paks, and any name not present in the .lang fall
  back to the raw input — zero risk of regression for third-party
  content.

Extending the list = one line per locale (e.g. `MyPak=Mon Pak` in
fr.lang). No code change needed.

Brings total to 534 paired keys.

## Files

- workspace/all/nextui/nextui.c
  - Entry_new now wraps the getDisplayName() result with T()
- skeleton/SYSTEM/res/lang/en.lang
- skeleton/SYSTEM/res/lang/fr.lang
  - +17 pak / tool folder name keys covering the bundled and most
    popular community paks
Third-party pak names are their authors' identity — translating them
would be confusing and would diverge from how the rest of the ecosystem
(Discord, README, screenshots) refers to them. Restrict Entry_new's
T() fallthrough to NextUI-bundled paks only:

Kept (NextUI core, workspace/all/*):
  Settings, Battery, Clock, Game Tracker, Bootlogo, Input, LedControl
  + USB Mass Storage (third-party but a generic technical term)

Removed (third-party — keep author name):
  Updater, Aesthetics, Pak Store, Files, Gallery, Remove Loading,
  Artwork Scraper, Another Cheat Downloader, ScrapeGoat, LED'oh!, ...

Behaviour unchanged for these: T("Aesthetics") has no entry → falls
back to "Aesthetics" verbatim, so they render exactly as before.
The Battery tool's hints were already translated but the on-screen
labels (Since Charge, Current, Remaining, Longest), the zoom title
(Battery usage: Last N hours) and the "calculating" placeholder were
still rendered with hard-coded English strings.

## Changes

- workspace/all/battery/battery.c
  - Format strings (battery.{since_charge,current,remaining,longest}_fmt)
    are now resolved via T() so each label is translated at draw time.
  - The zoom title goes through T("battery.usage_fmt") with the time
    range itself (T("battery.range.{4h,8h,16h}")) as the format arg.
  - The "calculating" placeholder in session_left is refreshed from
    T("battery.calculating") right after GFX_init has loaded the
    language. Buffer grown from 12 to 32 bytes to hold longer
    translations.
- skeleton/SYSTEM/res/lang/{en,fr}.lang
  - +9 new keys (battery.*).
  - French range labels deliberately kept short ("4 dern. h" instead of
    "4 dernières heures") so they fit in the title pill alongside the
    "Utilisation batterie :" prefix.
… batterie')

The Battery tool already shows a battery icon in the corner — having the
word 'batterie' in the title is redundant in French and pushed the time-
range suffix off-screen. Dropping it lets 'Utilisation : 16 dernières
heures' fit on a single line, no abbreviation needed.
Refined the previous rule. Brand names (Aesthetics, ScrapeGoat, LED'oh!,
Pak Store, Updater) stay untouched, but generic descriptive names that
map 1:1 across languages and read as function labels (Files → Fichiers,
Gallery → Galerie) should follow translation like the other system
tools.
minui-presenter is shared by several third-party paks (Gallery,
Artwork Scraper, Another Cheat Downloader, ...) that pass English UI
labels (EXIT, OK, CANCEL, ...) via the --action-text / --cancel-text
options. With i18n now wired into minui-presenter
(https://github.com/josegonzalez/minui-presenter PR pending), those
literals can be re-mapped to localised strings without modifying each
pak's launch.sh — provided the lang file lists a key with the
literal's exact spelling.

Adds the matching keys to NextUI's central en/fr lang files.
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
Adds OK, BACK and two --message strings (No screenshots found,
Screenshots directory not found) so paks using minui-presenter for
status messages inherit French automatically once the upstream PR
lands.
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 2 commits May 23, 2026 08:40
The Clock pak hard-coded the YYYY/MM/DD field order in both the
rendering and the cursor positioning. Drives this from a new
clock.date_order key (YMD/DMY/MDY); en defaults to YMD (ISO),
fr uses DMY.

The cursor still navigates the logical sequence (year/month/day),
just the on-screen positions follow the locale's order. Hour/minute/
second positions are unchanged since the date block keeps the same
total width (100 px @1x) regardless of order.
Adds 80 ps.* keys consumed by the companion PR on
LoveRetro/nextui-pak-store. Includes button labels, screen titles,
settings, info screen, install/uninstall/update flows, storefront
categories and the splash text.

Companion PR: <pak-store-pr-url>
foXaCe added 2 commits May 23, 2026 11:17
Adds ~150 sg.* keys consumed by the companion PR on
Helaas/nextui-scrapegoat-pak. Covers main menu, library list, ROM
detail, queue/progress screens, settings, dialogs, warnings.

The parser now decodes \\n / \\t / \\\\ escapes so multi-line dialog
strings (cancel-all confirmation, background-scraping warning, ...)
can sit on a single .lang line. Existing single-line keys are
unaffected.

Also localises btn.up_down / btn.left_right (was hardcoded U/D and
L/R literals in gametime/bootlogo/battery/ledcontrol) so the button
hints render as H/B and G/D in French. New gametime.title_fmt key
for the Game Tracker top title.

Companion PRs:
- Helaas/nextui-scrapegoat-pak#9
- Helaas/Apostrophe#47 (queue widget i18n hook)
Short labels rendered as inline status badges in ScrapeGoat's ROM
list (queued / searching / downloading / cloning / matching) and as
the type tags (art / cht / pdf). Companion to
Helaas/nextui-scrapegoat-pak#9 review fix.
- ~80 ae.* keys for the Aesthetics pak (companion PR pending on
  redria7/nextui-aesthetics) covering main menu, settings, theme
  components, decoration browser, dialogs.
- 6 ps.* keys for the gabagool download manager footer
  (Close / Cancel Download / Cancel All Downloads / Show Speed /
  Hide Speed) + the download title. Consumed by the updated PR on
  LoveRetro/nextui-pak-store and the gabagool fork
  BrandonKowalski/gabagool#18 that adds the override hooks.
foXaCe added 2 commits May 24, 2026 09:35
Adds 30 ae.* keys (15 per locale × 2) for the Aesthetics theme
manager surface that was left untranslated by the initial i18n
pass:

  - ae.btn.{details,hide_theme,unhide_theme,trash_it}
  - ae.dl.{refresh_catalog,hidden_themes,help_no_entries,help_a,help_x}
  - ae.msg.{delete_theme,deleted,delete_failed,error_prefix,
           name_exists,theme_renamed,select_one_component,
           delete_components,delete_components_failed,
           delete_components_done,update_error,updates_done,
           download_failed,clear_deco,deleted_path,
           delete_path_failed,unsupported_action,delete_deco,
           copy_image_to,copy_failed,copy_success}

Covers the download catalog screen (browse / hide / details flow),
the theme delete/rename/copy actions, and the bulk component editor
messages. French translations keep the project's tutoiement style
and reuse the existing terminology choices (« Supprimer », « Échec »,
« Sélectionner », …).
Companion to nextui-scrapegoat-pak commit 1ead06e which routed 9
more ui.c strings through T(). Adds the matching translations
here so the runtime can resolve them — without these, T() returns
the bare key (e.g. "sg.cheat.fallback_name_fmt") as the visible
label.

New sg.* keys (149 → 149 EN ↔ FR parity preserved):
  - sg.cheat.fallback_name_fmt       Code %d / Cheat %d
  - sg.cheat.list_title_fmt          Codes triche (%d) / Cheats (%d)
  - sg.tag.disabled                  [désactivé] / [disabled]
  - sg.error.cache_inspect_failed_fmt
  - sg.error.cache_open_failed_fmt
  - sg.error.cache_rmdir_failed_fmt
  - sg.error.cache_unlink_failed_fmt
  - sg.error.cheat_cache_open_failed_fmt
  - sg.error.cheat_cache_rmdir_failed_fmt

Also rewrites 7 existing FR strings from vouvoiement to tutoiement
for style consistency with the rest of the file:
  - sg.error.queue_active_clear      Attendez → Attends
  - sg.error.cache_cleared_stale     Redémarrez → Redémarre
  - sg.error.bg_start_failed         Essayez → Essaie
  - sg.warn.bg_still_active          Fermez/rouvrez → Ferme/rouvre
  - sg.warn.no_internet              Connectez-vous → Connecte-toi
  - sg.warn.no_credentials           allez/ajoutez vos → va/ajoute tes
  - sg.warn.no_manual_dir            Allez/configurez/Il vous faudra → Va/configure/Il te faudra

All %s/%d placeholders preserved; encoding UTF-8 clean.
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