Skip to content

Add interface language framework with Simplified Chinese (first slice) - #1

Draft
SeniorHan wants to merge 3 commits into
mainfrom
feat/i18n-layer
Draft

SeniorHan wants to merge 3 commits into
mainfrom
feat/i18n-layer

Conversation

@SeniorHan

Copy link
Copy Markdown
Owner

Draft, not for review yet — opening this so the design is visible and CI can build it.

What this is

A gettext-style i18n layer: the English source string is the key, so t() falls
back to the key itself when a translation is missing. The app stays English on
surfaces that aren't covered yet, and coverage can grow one screen at a time
instead of in one big migration.

This is the same design as upstream PR hardbeat920#244, re-implemented cleanly on current
main (hardbeat920#244 is 68 commits behind). One deliberate difference: language tags are
zh-Hans / zh-Hant rather than a bare zh, so a Traditional Chinese table can
be added later without re-tagging stored values. main's design precedent here is
PR hardbeat920#244's Lang::from_tag, which maps every zh* to Simplified — that's the bug
reported on hardbeat920#243.

Included in this PR (first slice)

  • src/shared/lib/i18n.ts — the table, t() with {name} placeholder
    interpolation, and resolveLanguage() (auto follows the OS, same as the color
    scheme does).
  • src/shared/hooks/useI18n.ts — useT() / useLanguage() over
    useSyncExternalStore, with a storage listener so other windows stay in sync.
  • Settings → General → Language: Auto / English / Simplified Chinese.
  • Settings search matches localized labels (typing 语言 finds the row); section
    labels and descriptions localize too.
  • macOS native menu + dock menu rebuild in Chinese on boot and on every change.
    The menu bar lives outside the webview and can't read localStorage, so the
    frontend pushes the resolved tag to Rust via set_menu_language; the Rust side
    mirrors the decision and rebuilds. The Edit menu's standard items take explicit
    text because Tauri's defaults are English.
  • A guard test scans the source for t("…") calls and fails when a key has no
    Chinese entry, so a new call can't silently ship English.

Verification

  • Linux: npm run check:web (5209 tests) + npm run check:rust (602 tests),
    clippy -D warnings, cargo fmt --check, and a production vite build.
  • macOS 26.5.2 / arm64: cargo check, cargo clippy -D warnings,
    cargo fmt --check, cargo test --lib (648 passed). This is where a dropped
    #[tauri::command] on a macOS-gated command surfaced — fixed in the second
    commit; Linux could not catch it because the registration is cfg-gated.

Not verified: no full .app was built or launched (the Mac here has only
Command Line Tools, not Xcode). The menu code compiles and passes clippy/tests;
the menu behavior has not been run end-to-end.

Scope is deliberately small

Only the language row itself is translated. Everything else in Settings and the
rest of the app remain English until we know whether upstream wants this at all —
see the open question on hardbeat920#243 about which of the two i18n PRs should land.

Introduce a gettext-style i18n layer: the English source string is the
key, so `t()` falls back to the key when a translation is missing and the
app stays English-only on surfaces not yet covered. Coverage can then grow
one screen at a time instead of in one big migration.

The preference (`auto` | `en` | `zh-Hans`) follows the OS by default, the
same way the color scheme does. It persists to localStorage and fires a
change event that `useT()` subscribes to through `useSyncExternalStore`.
Tags are `zh-Hans`/`zh-Hant` rather than a bare `zh`, so a Traditional
table can be added later without re-tagging stored values.

First slice, on the General settings page:
- a Language row with an Auto / English / Simplified Chinese control
- the settings search matches localized labels (typing 语言 finds the row)
  and localizes section labels and descriptions

The macOS menu bar lives outside the webview and cannot read localStorage,
so the frontend pushes the resolved tag to Rust with `set_menu_language`
on boot and on every change; the native menu and the dock menu are rebuilt
from a mirror of that decision. The Edit menu's standard items take
explicit text because Tauri's defaults are English.

A guard test scans the source for `t("…")` calls and fails when a key has
no Chinese entry, so a new call cannot silently ship English.
Making the menu builder language-aware dropped this command's
#[tauri::command] attribute. Linux/Windows never noticed because the
registration in lib.rs is cfg-gated to macOS, so the generated
__cmd__ symbol was simply not referenced; macOS failed to compile with
"cannot find __tauri_command_name_keybindings_set_overrides in menu".

Restore the attribute and the macOS-only gate, matching main.
The guard test shelled out to grep, which Windows runners do not have:
it found zero keys and failed with "expected 0 to be greater than 0".
Walk the source tree with fs/readdirSync and match in-process so the test
is platform-independent.
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