Add UI language selector (ui.language), fixes #444 - #465
Conversation
Let users pick the interface language explicitly instead of always following the OS locale. "System default" stays the default, so existing users see no change. - UiLocale resolves ui.language (argv -o pre-scan, then advanced config) and installs it DISPLAY-only, so number/date formatting and the signed output keep following the OS locale. Installed before Constants.RES and the commons-cli option descriptions capture strings. - SupportedLanguages: static tag list + display names, guarded by a test against the actual messages*.properties files. - ResourceProvider.reload(Locale) swaps the shared bundle with English-only fallback (no OS-locale fallback), keeping the ~200 call sites untouched. - Preferences: Language selector as the first row of the General tab, persisted as ui.language; empty removes the key. CLI reads the same key via -o ui.language=<tag> (e.g. --help in German). - Fix Norwegian bundle: messages_nb-NO.properties was never matched by ResourceBundle; renamed to messages_nb.properties so it applies for both nb and nb-NO. - Tests (SupportedLanguagesTest, UiLocaleTest, PreferencesViewModel round-trip, FxTranslationsTest + nb_NO and Preferences.fxml) and docs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Code reviewVerified locally (branch
The architecture is right: Findings1.
|
Install the choice as the base default plus Category.DISPLAY, with Category.FORMAT pinned back to the OS locale. setDefault(Category, ...) leaves Locale.getDefault() alone, and ResourceBundle.getBundle(baseName) resolves against that - which is how JavaFX loads its own control strings (ButtonType.OK/CANCEL, text-field context menus). DISPLAY-only therefore gave a Portuguese-locale user picking German a German app with "Cancelar" buttons. Number/date formatting still follows the OS locale, so signed output is unchanged. Also: - ResourceProvider.reload() takes a ResourceBundle instead of a Locale, so the bundle base name and the fallback policy stay owned by UiLocale and utils.ResourceProvider no longer depends on Constants (which constructs a ResourceProvider itself). The bundle field becomes volatile. - UiLocale.canonicalTag() maps a stored tag to the matching selector item, so a hand-edited zh_CN / de-AT / no shows up as the zh-CN / de / nb entry it actually loads. A bare zh expands to zh-CN rather than falling back to English. - SupportedLanguages.displayName() switches to an en-dash separator when a name already brackets its region, avoiding nested parentheses in "Chinese (China)". - SupportedLanguagesTest loads the English reference bundle with the same no-fallback control as the Norwegian ones; with the default control the test inverted on a machine whose locale is nb. - UiLocaleTest restores Constants.RES, which init() swaps process-wide, so test execution order cannot leak a translated bundle into a later test. - Translations for the three new Preferences keys. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Lets users pick the interface language explicitly instead of always following the auto-detected OS locale. System default stays the default, so existing users see no behaviour change. Closes #444.
Design notes:
design-doc/3.2-language-selector.md.Behaviour
ui.languageinadvanced.properties; empty removes the key.-o ui.language=de(e.g.jsignpdf --help -o ui.language=deprints the help in German).Implementation
UiLocale(engines/api) — resolves the tag (raw-argv-opre-scan, then advanced config), installs it viaLocale.setDefault(DISPLAY, …)+ResourceBundle.clearCache()+Constants.RES.reload(), beforeConstants.RES/ the commons-cli option descriptions capture any strings. Normalizes_→-, maps legacyno→nb, and falls back to the system default for empty/unknown/garbage values.SupportedLanguages— static tag list (jar/jlink/jpackage-safe) + display names, guarded by a test against the actualmessages*.propertiesfiles.ResourceProvider.reload(Locale)— swaps the shared bundle with English-only fallback (a no-fallbackControl), so a missing key never leaks OS-locale text; keeps the ~200 static-import call sites untouched.JSignPdfApp,PreferencesController) useUiLocale.bundle().Norwegian bundle fix
messages_nb-NO.propertieswas never matched byResourceBundle(it builds underscore candidate names), so the complete Norwegian translation silently rendered English. Renamed tomessages_nb.properties, which serves bothnbandnb-NO. No Weblate change required (itsnb_NOlanguage already aliasesnb).Tests
SupportedLanguagesTest— list vs. translation files (guard); Norwegian actually loads fornbandnb-NO.UiLocaleTest— tag parsing, underscore/legacy-nonormalization, unknown/garbage → system default, DISPLAY-only install (FORMAT untouched).PreferencesViewModelTest—ui.languageload/write round-trip, empty removes the key, reset-to-defaults clears it.FxTranslationsTest— extended withnb_NOand aPreferences.fxmlload asserting the Language label.Docs
Advanced-config section in
website/docs/JSignPdf.adoc, documentedui.language=inadvanced.default.properties, and twodistribution/doc/release-notes/3.2.0.mdbullets.🤖 Generated with Claude Code