Shared translations
User-facing text lives in one JSON file per locale and is compiled into the ELF. The console does not load locale files at runtime.
Current locales match the PS5 console languages (SCE_SYSTEM_SERVICE_PARAM_ID_LANG
0–30): zh-CN.json (简体中文), zh-TW.json (繁體中文), en-US.json
(English; also used for English UK), ja-JP.json (日本語), ko-KR.json
(한국어), fr-FR.json (Français; also French Canada), de-DE.json
(Deutsch), it-IT.json (Italiano), es-ES.json (Español; also Latin
American Spanish), pt-BR.json (Português do Brasil), pt-PT.json
(Português de Portugal), nl-NL.json (Nederlands), fi-FI.json
(Suomi), sv-SE.json (Svenska), da-DK.json (Dansk), no-NO.json
(Norsk), pl-PL.json (Polski), ru-RU.json (Русский), tr-TR.json
(Türkçe), cs-CZ.json (Čeština), hu-HU.json (Magyar), el-GR.json
(Ελληνικά), ro-RO.json (Română), ar-SA.json (العربية, trial),
th-TH.json (ไทย), vi-VN.json (Tiếng Việt), id-ID.json
(Indonesia), and uk-UA.json (Українська).
English UK (18), Spanish LatAm (20), and French Canada (22) reuse the
matching existing catalog. Hebrew / Hindi / Malay / Slovak are not PS5
console languages and have no system language ID.
The generator scans every *.json in this directory. Each locale is
selectable in Toolbox and via toolbox.language. The Settings XML is
still LTR, so Arabic glyphs render but the page layout is not mirrored.
File layout
Each locale file has exactly two sections:
| Section | Keys | Runtime |
|---|---|---|
toolbox |
dotted ids such as fan.enable |
toolbox_i18n::tr("fan.enable") |
notifications |
notify.* ids such as notify.fan.open_failed |
onion_notify(true, "notify.fan.open_failed") |
en-US.json is the fallback locale (meta.fallback: true). Every other
locale file must contain the same keys and the same printf conversions.
The build runs generate_catalog.py and rejects:
- missing keys in any locale
- empty keys or embedded NUL
- mismatched
printfconversions (%s,%d,%i,%u,%X, …)
Generated tables are embedded in shellui (toolbox) and
libonion_platform (notifications).
How to add a Toolbox string
-
Add the same key to every locale JSON file under
toolbox. -
Prefer one complete sentence with
printfplaceholders when values are inserted. Do not split a sentence across several keys and concatenate them in C++ (word order cannot be translated)."cheats.enable_fmt": "Enable/disable %s for %s""cheats.enable_fmt": "为 %s 启用/禁用 %s" -
Format with the shared helper, not a local
snprintfwrapper:toolbox_i18n::format("cheats.enable_fmt", game, cheat); -
Rebuild. Missing the key in one locale fails the catalog step.
-
Switch Toolbox language, leave the page, and reopen it. XML is built when the page opens. The same save path used by log level (
settings_commit→BREW_RELOAD_SETTINGS→LoadSettings) tells daemon and util to apply the new language. That is a config reread, not a SceShellUI inject.
Missing toolbox keys render as the key itself (visible in the menu).
How to add a notification
-
Add a stable
notify.<area>.<name>key to every locale JSON file undernotifications. Never use the English sentence as the key. -
Keep
printfplaceholders identical in every language. -
Pass the key, not the English text:
onion_notify(true, "notify.fan.open_failed"); onion_notify(true, "notify.payload.loading", name); onion_notify_rich("notify.brand", "notify.boot.starting", ...); -
Do not pass a raw English format string. Unknown keys are shown unchanged (English-looking leftover, or a debug
"%s"passthrough).
ShellUI's notify("…") helper forwards to the same catalog. Unpacker
has its own notify() and does not use this catalog.
How to add a language
Catalog compilation already scans this directory. A new JSON file is embedded automatically. To make the language selectable you still need the runtime mapping:
- Add
<locale>.jsonwith the same keys asen-US.json, plusmeta.id(the config.ini value),meta.bcp47, andmeta.fallback: false. - Add
lang.<id>to every locale file (the Toolbox list label). - Add a
kUiLanguage*value, parse/serialize it in settings, and add a Toolbox list item with that integer. - Add the matching
Lang/ONION_NOTIFY_LANG_*values and mapmeta.idinnotify_i18n.c/toolbox_i18n.cpp. - If the PS5 system language should pick it when Toolbox language is
system, add that id inonion_notify_resolve_language(0→ja,2/22→fr,3/20→es,4→de,5→it,6→nl,7→pt-PT,8→ru,9→ko,10→zh-Hant,11→zh-Hans,12→fi,13→sv,14→da,15→no,16→pl,17→pt-BR,19→tr,21→ar,23→cs,24→hu,25→el,26→ro,27→th,28→vi,29→id,30→uk; everything else isen).
Web UI
The browser pkg-installer UI in ../webui/ keeps its own
dictionaries in src/i18n.js — the same locale set as the Toolbox, but
bundled into the pkg-server's single-file page. Its language resolves the
same way as the Toolbox (toolbox.language, else the console system
language) and updates live when the daemon's system-language poll pushes
BREW_UTIL_SET_SYSTEM_LANG to util.
Intentional exclusions
These user-visible strings are not in the JSON catalogs on purpose:
| Surface | Why |
|---|---|
Settings menu label ★OnionHEN Tools |
Equal-length binary patch of ★Debug Settings. Length is fixed. |
HomeUI top-nav OnionHEN |
Brand token, same in every language. |
Notification watermark [OnionHEN] |
Brand prefix in onion_notify_format. |
| About names, handles, Ko-fi URL, project URLs | Proper nouns / addresses. |
| Unpacker start-failure toasts | First-stage loader; no language setting and its own notify(). English only until it is wired to onion_notify. |
| Cheat names / descriptions from cheat files | Come from the cheat JSON, not OnionHEN. |
Logs (LOG_*) are developer-facing and stay English.
Current coverage
Checked against call sites (not just the JSON files):
- Toolbox menus, game-options cheat entry, PKG
GetStringhooks, and About donor/WeChat labels go throughtoolbox_i18n::tr(). - Daemon / util / shellui / bootstrapper toasts go through
notify.*keys. Host tests cover zh-Hans, zh-Hant, en, ja, ko, fr, de, it, es, pt-BR, pl, ru, ar, and th lookup. - Four toolbox keys are unused leftovers from an older menu grouping:
group.lang,group.lang.sub,group.shortcuts,group.shortcuts.sub. They are translated but not shown. - Welcome toast formats
notify.boot.made_bywithONIONHEN_VERSION. The author display name is part of each locale's translation (麒麟in Chinese,Kylinelsewhere). - External plugins own their dynamic UI and service-specific translations; the OnionHEN catalog contains only host-owned strings.
Arabic is a complete key-for-key trial locale, not a native RTL layout.