|
Pokered Save Editor 2
Pokemon Red & Blue save file editor - Qt 6 C++/QML
|
Added 2026-06-13. The app now has a full Qt Linguist translation pipeline. Source language is en_US (the literal strings in the code). English is what ships; the machinery is in place so adding a locale is a small, well-defined task.
The original idea was "a stringlist/stringfile in a folder." For a Qt app that's the less clean route — it reinvents what the framework already does well and would be a hack by this project's bar. Qt's built-in system gives us a translations/ folder of per-language catalogs and plugs straight into the engine with zero custom string-loading code:
This covers UI chrome only (button labels, headers, tooltips, menus). Game data (Pokémon / move / item / location names) is a separate, larger effort — those live in the DB JSON and are tied to the ROM's region/encoding, so they must NOT go through Qt translations. See "Out of scope" below.
| Piece | Location |
|---|---|
| Catalog(s) | projects/app/translations/pse_<locale>.ts (currently just pse_en.ts) |
| CMake wiring | projects/app/CMakeLists.txt → the "Internationalization (i18n)" block (qt_add_translations) |
| LinguistTools dep | projects/CMakeLists.txt → find_package(Qt6 ... LinguistTools) |
| Translator install | projects/app/src/boot/boot.cpp → installTranslators() (called in createApp() right after the QApplication is constructed, before the MainWindow/any QML) |
| Embedded .qm | compiled into the exe at :/i18n/pse_<locale>.qm (RESOURCE_PREFIX /i18n) |
qt_add_translations creates two targets:
lupdate scans an explicit file list (PSE_I18N_SOURCES, a GLOB_RECURSE over ui/*.qml + src/*.{cpp,h}) because the QML lives inside app.qrc, not in the target's SOURCES — Qt's auto source-collection would otherwise miss it. lupdate also picks up mainwindow.ui (the native menu).
No code change is needed beyond the one TS_FILES line.
installTranslators() resolves the locale as: the ui/language QSettings value (a locale name like "fr"), else QLocale::system(). It then load()s :/i18n/pse_<locale>.qm. If no catalog matches — or a particular string is untranslated — the UI falls back to the en_US source text. That graceful degradation is intentional (context/principles.md): a missing translation never blanks a label. A Qt-builtins translator (qtbase_<locale>.qm) is also loaded best-effort for standard dialog text.
There is no in-app language switcher yet (live retranslation needs engine retranslate() + re-evaluating bindings). The ui/language setting is the hook; building the switcher is a future task. To test another locale today, set the ui/language registry value (org "Twilight", app "Pokered Save Editor") or override with QT_QPA/-platform env + LANG.
Wrapped (qsTr/tr): the bulk of visible chrome — text / title / placeholderText / ToolTip.text literals across the QML tree (≈139), a few prose concatenations rewritten as qsTr("… %1 …").arg(x) (e.g. the name byte-counter), the Router screen titles via QT_TRANSLATE_NOOP("Screen", …) (translated at point of use in router.cpp, because loadScreens() runs before the translator exists), and a handful of C++ model display strings ("None", "Clear Recent Files", the Money⇄Coins labels).
Left as-is, on purpose:
Pokémon, move, item, and location names are data, not UI chrome. In Gen 1 they're region/ encoding-specific (a French/German/Japanese ROM encodes names differently), and they're entangled with the save format and the in-game font/character tables. Localizing them is its own project and must not be routed through Qt translations. Track it separately if/when desired.
Both tools run normally. During the initial setup, lupdate appeared to hang from the automation shell — that turned out to be stacked Windows UAC elevation dialogs (commands launched from the agent shell can trigger a UAC prompt that blocks until accepted; several had queued up while unattended). It was not a tooling problem. Refreshing the catalog via the update_translations target (or Qt Creator → "Update Translations") works as expected; the initial pse_en.ts (200 messages / 42 contexts) was generated that way. Heads-up only: build/translation commands run from the agent shell may pop a UAC prompt that needs accepting.