Skip to content

[Feature]: Interface language setting, with Simplified Chinese #243

Open

Description

@gzx19990101

Problem

MonoCode only speaks English. Every string sits inline in the component that renders it, so there is no seam to translate at, and the app cannot follow a language the OS is already set to. For anyone who does not work in English that is the first thing they hit, and it is not something they can work around.

Proposed solution

A small translation layer, then Simplified Chinese for the surfaces people read most.

Design points worth agreeing on before more of the app is covered:

  • The table is gettext-style: the English source string is the key. t() returns the key when a translation is missing, so a partly translated app degrades to English instead of showing placeholders, and coverage can grow one screen at a time with no big-bang migration.
  • Language defaults to auto and follows the OS, the way the app already treats the color scheme. It persists to localStorage and fires a change event like the other appearance settings, so someone who never opens Settings still sees their own language.
  • The macOS menu bar lives outside the webview and cannot read the preference, so the resolved tag is pushed across to Rust, which rebuilds the app menu and the dock menu from its own label table. Windows and Linux draw their menu bar in the webview, so the same command is a no-op there.
  • Settings search matches whichever language you type in. A row stays reachable by its English name while the interface is Chinese.
  • scripts/check-i18n-keys.mjs fails on a t() key with no translation, or a duplicate key, so coverage cannot silently regress as the app grows.

I have a branch that does this for the window frame and Settings, and opened a PR against it.

Alternatives considered

  • Leave strings inline and maintain a Chinese fork. No merge path back to upstream; every release would need the strings ported again.
  • Keep a second set of components per language. Doubles the surface to keep in sync.
  • A heavier i18n dependency (i18next, react-intl). The app is deliberately light on dependencies and a table plus one hook covers what is needed here.

Additional context

Covered so far: title bar and tab strip, the in-app menu bar, sidebar, composer, settings navigation, every Settings section, the delete-project dialog, and the macOS native menu.

Not covered, and I would rather land the layer than one enormous diff: the transcript, the Inbox and Notes views, the file tree, other dialogs, and UI added after v0.1.47 — the per-project notification categories being the obvious example.

Activity

  1. king20300 commented on Oct 1, 2026

    @king20300
    Contributor

    @hardbeat920 There are now two open i18n PRs with different designs, and it'd help to know which direction you want before more translation work piles up on either:

    Whichever you pick, I'd like to add Traditional Chinese (zh-Hant) as well. Right now both map Taiwan, Hong Kong and Macau systems to Simplified. I'm happy to contribute the locale split and a full zh-Hant table reviewed by a native speaker, so it would be good to settle the language-tag shape (zh-Hans / zh-Hant vs zh-CN / zh-TW) early, before a stored value ships.

    cc @gzx19990101 @wangdada8208

  2. jeremy-prt commented on Oct 7, 2026

    @jeremy-prt

    +1, as discussed on X. Would love the UI to follow the macOS system language by default, with a setting to override it. French would be great for me.

  3. SeniorHan commented on Oct 11, 2026

    @SeniorHan

    I ran into the same fork-in-the-road here and, rather than add a third design, tried to answer the question with something runnable. Posting what I found; happy to throw it away if you'd rather go a different way.

    What I did: took the gettext design from #244 (English source string as the key) and re-implemented it cleanly on current main, since #244 is now 68 commits behind and conflicting. Scope is deliberately small: the framework plus only the language row, so the design is reviewable without a giant translation diff.

    Draft PR, not a request to merge: SeniorHan#1
    It's on my fork, drafted against my own main. All 12 CI jobs pass, including check (macos-latest).

    One deliberate difference from #244: the tag is zh-Hans / zh-Hant, not a bare zh. #244's Lang::from_tag maps every zh* to Simplified, so a Traditional reader gets Simplified — the bug @king20300 reported. Since a stored value already encodes which script, better to fix the tag now than re-tag later. French (worth noting @jeremy-prt's +1) would then be fr with no new machinery.

    What CI caught that I couldn't see on Linux: porting the menu code dropped a #[tauri::command] attribute from a macOS-gated function. Linux compiled fine — the registration is cfg(target_os = "macos"), so the generated symbol was never referenced — and macOS failed with cannot find __cmd__keybindings_set_overrides in menu. Fixing it was trivial, but it's a reminder the macOS job is the one that matters for menu work. That fix is in the second commit.

    Status / caveats. Only the language row is translated; everything else stays English on purpose until there's a direction. No .app was built or launched — menu code is compile/clippy/test-verified only, not run.

    The actual question for @hardbeat920 (same as @king20300's): which design do you want, and do you want this at all? If gettext is the shape, I'm happy to keep going one surface at a time; if you'd prefer #530's nested-key approach, that's a better answer today than more translation piled onto either PR.

    No action needed from me on this unless you want the branch pushed somewhere else or the design changed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions