Skip to content

feat(i18n): add interface localization with 10 languages - #105

Merged
kemokempo merged 6 commits into
otty-shell:mainfrom
kaluli123123:agent/pr88-i18n-rebase
Oct 1, 2026
Merged

kemokempo merged 6 commits into
otty-shell:mainfrom
kaluli123123:agent/pr88-i18n-rebase

Conversation

@kaluli123123

Copy link
Copy Markdown
Contributor

Summary

  • add interface localization for ten languages with a Settings > General > Language selector
  • keep System as the default and preserve compatibility with settings files that do not contain general.language
  • embed type-checked locale catalogs without adding dependencies
  • rebuild localized settings navigation paths when the active locale changes

Replacement for #88

This is a conflict-resolved replacement for #88, rebased onto the current main. The two original feature commits retain Evan (houko) as their author.

The replacement resolves the repository reorganization and later settings changes by:

  • moving the localization module from the former otty/src path to app/src/i18n
  • preserving the current pane-equalization setting and behavior
  • translating the pane-layout field and equalization option in all ten catalogs
  • documenting interface localization in the README

Verification

  • cargo +nightly fmt -- --check
  • cargo clippy --workspace --all-targets --all-features -- -D warnings
  • cargo deny check — passed; reports the existing yanked transitive versions core2 0.4.0 and spin 0.9.8
  • cargo test --workspace --all-features — 561 passed, 2 ignored
  • cargo llvm-cov --workspace --all-features --fail-under-lines 80 — tests passed, but the repository-wide line coverage gate remains below its configured threshold: 68.17% on this branch versus 67.97% on current main
  • macOS runtime with LC_ALL=zh_CN.UTF-8 — Chinese Settings UI rendered, including the language control and pane-layout text
  • macOS runtime with LC_ALL=ja_JP.UTF-8 — Japanese Quick Launch UI rendered

Not verified

  • live language selection followed by save and restart persistence
  • Linux runtime rendering

houko and others added 4 commits September 8, 2026 11:47
Introduces a localization layer for the application UI and ships an English and a Simplified Chinese catalog. Previously every user-facing string was a hardcoded English literal spread across view, type, and error modules, so the interface could not be translated at all.

Translations live in JSON files under `otty/src/i18n/locales/` and are embedded with `include_str!`, keeping the binary self-contained. `Key` stays a Rust enum so call sites remain type-checked, and serde deserializes each catalog into `HashMap<Key, String>`; an unknown key in a JSON file fails deserialization, and tests assert that both catalogs cover every `Key` and contain no extras. No new dependency is required — `serde` and `serde_json` were already used by the crate.

The active locale is process-global rather than threaded through every widget signature, which would have touched every view function for no benefit. `Settings > General > Language` offers System / English / 简体中文, persisted as `general.language`; `System` resolves through the POSIX variables `LC_ALL`, `LC_MESSAGES`, `LANG`. Settings written by older builds simply lack the field and fall back to `System`.

Section titles double as `TreePath` segments in the settings navigation tree, so a language change invalidates every stored path. `SettingsState::replace_with_settings` now rebuilds the tree and the selected path, and the locale is applied before that happens — both at startup in `load_initial_settings_state` and on save in the reducer. The settings tests read their paths from the tree instead of hardcoding English titles, so they no longer depend on which locale is active.

Theme preset names (One Dark, Solarized Dark, Dracula) are left untranslated as proper nouns, as are sample placeholder values such as `example.com` and `~/.ssh/id_ed25519`. Log messages and infrastructure error types stay in English; only strings that reach the UI are localized.
Adds Traditional Chinese, Japanese, Korean, French, German, Spanish, Brazilian Portuguese, and Russian, bringing the shipped set to ten languages.

Adding a language previously meant touching three places: a `Locale` variant, a hand-written `OnceLock` in `catalog`, and a matching `LanguageSetting` variant. That does not scale past a couple of languages, so both types are now data-driven. `Locale::ALL` is the single list, `catalog` caches into an array indexed by `Locale::index`, and `LanguageSetting` collapsed from one variant per language into `System | Fixed(Locale)` with its selector derived from `Locale::ALL`. Adding a language is now a JSON file plus one enum variant, with no view or settings changes.

Locale detection handles the cases that matter for these languages. Chinese resolves by script rather than by primary subtag, so `zh-TW`, `zh-HK`, `zh-MO`, and an explicit `Hant` select Traditional while everything else Chinese selects Simplified. POSIX strings are normalized before matching, so `de_AT@euro` and `ja_JP.UTF-8` resolve correctly. A stored `general.language` value, in contrast, must match a shipped tag exactly, so an unrecognized or partial tag keeps following the system instead of silently picking a language.

Three tests were added that matter specifically for hand-written translations: every locale keeps the `{title}`, `{command}`, `{error}`, and `{index}` placeholders in its templates; no catalog entry or palette label is blank; and every `Locale::index` matches its position in `ALL`, which is the invariant the catalog array indexing relies on. The existing completeness tests now iterate `Locale::ALL` rather than a hardcoded pair, so a new language is covered the moment it is listed.

Language names in the selector are endonyms (日本語, Русский, Português (Brasil)) so a user can find their language without already being able to read the current interface language.
Use the native system locale provider, honor explicit Chinese scripts, preserve literal template arguments, refresh the settings tab title, and localize visible quick-launch diagnostics.
@kemokempo

Copy link
Copy Markdown
Contributor

need rebase

Bring in otty-shell#96 (block settings edits while a save is in flight).

Conflict in app/src/widgets/settings/view/settings_form.rs: keep the
localized labels from this branch and main's save lock (Save/Reset
disabled and inputs read-only while saving).

Also add LanguageChanged to the reducer's in-flight edit guard, so a
language change made during a save is ignored like other draft edits,
with a regression test.
@kaluli123123

Copy link
Copy Markdown
Contributor Author

Merged current main (369e54c, #96) into this branch instead of rebasing, so there is no force-push and the existing history stays reviewable. A squash merge will still land this as one clean commit.

Conflict resolved in app/src/widgets/settings/view/settings_form.rs: kept the localized labels from this PR together with #96's save lock (Save/Reset disabled and inputs read-only while a save is in flight). #96 added no new user-visible strings, so the catalogs are unchanged.

One follow-up from the merge: LanguageChanged (added by this PR) was missing from the reducer's in-flight edit guard added in #96, so a language change during a save could be overwritten. It is now in the guard, with a regression test (given_save_in_flight_when_language_changed_then_draft_unchanged) that fails without the fix.

Verification on 0438519:

  • cargo +nightly fmt -- --check: pass
  • cargo clippy --workspace --all-targets --all-features -- -D warnings: pass
  • cargo test --workspace --all-features: 571 passed, 0 failed, 2 ignored
  • cargo deny check: advisories, bans, licenses, sources ok
  • cargo llvm-cov --workspace --all-features: line coverage 68.26% vs 67.94% on main

Comment thread app/src/widgets/settings/view/settings_form.rs
The settings tree is rendered through TreeView, which sorts sibling rows
by title. That put Appearance before General in English and made the
order depend on the active language.

Add TreeView::keep_source_order() so callers with a fixed page list can
render rows in the order they provide, use it for the settings
navigation, and order the sections as General, Appearance, Terminal.
@kemokempo
kemokempo merged commit 4ad4359 into otty-shell:main Oct 1, 2026
7 checks passed
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.

3 participants