Daniel, 2026-10-02: “Please do localization in English, German and Chinese. Please make light/dark mode or better yet themes. Do it whenever you see fit.”

What is wanted

  1. The UI in three languages — English, German, Chinese (Simplified) — chosen in Settings → You, defaulting to the system language (desktop: LANG/the OS locale; web: navigator.languages; Android: the device locale), falling back to English for anything not translated. Switching applies without a restart.
  2. Themes, not only light/dark: a theme is a named set of colours (and maybe fonts) for the whole app — workbench, editors (CodeMirror, Milkdown, the Rust editor, xterm, the Rust terminal), the graph view, the flow canvas, tables, KaTeX. At least Dark, Light, and Follow system (prefers-color-scheme); more themes as files, so users and extensions can add their own.

Where things stand (checked 2026-10-02)

  • Text: every UI string is a Rust literal in the panels, menus, the palette and the status bar (about 130 in shell/src alone, more in each editor and in the JS bundles’ own strings). No translation layer exists.
  • Themes: Settings.theme exists ("dark" by default) but only the Rust code editor reads it; the shell’s CSS uses dioxus-workbench’s --wb-* variables and our --mk-* ones, with about 580 hard-coded hex colours across the stylesheets; CodeMirror ships the one-dark theme; the graph renderer has its own colours.

Design notes (for whoever does it)

  • Strings: a t!("key")-style lookup with Fluent (fluent-bundle, Mozilla’s format; plural and gender rules for German, no inflection trouble for Chinese) or a smaller key → string table per language; files under packages/shell/locales/{en,de,zh-CN}/*.ftl. Each extension brings its own strings (a contribution, like settings), so the catalogue split of Milestone 18 - Library Refactor phase 4 is the natural moment. Keys, not English text, are the ids, so a changed English wording does not orphan a translation.
  • JS bundles keep no strings of their own where possible (rule of packages/js: Rust owns the text); the few they have (CodeMirror’s search panel, Milkdown’s menus) get their phrases from Rust at init.
  • Chinese: a CJK font in the font stacks (Noto Sans CJK / system), and the IME paths that issue #19 already flags (Enter during composition must not send).
  • Themes: one token set (--mk-bg, --mk-fg, --mk-accent, --mk-border, syntax colours, graph palette, the eight source colours of spec 009) defined per theme, mapped onto --wb-* for the workbench; every stylesheet uses tokens only (a CI check can grep for hex colours outside theme files). CodeMirror, xterm and Milkdown get their themes generated from the same tokens; the graph renderer takes its palette as data. Theme files as TOML/JSON in the user’s config directory and as an extension contribution.
  • Both are settings that belong in the state store (Internal State), synced across devices.

Done when

The three languages switch live in Settings, with every built-in panel, menu, command title and dialog translated (German and Chinese reviewed by a native speaker — Daniel for German); Dark, Light and Follow-system work everywhere listed above with no hard-coded colour left outside the theme files; a fourth theme can be dropped into the config directory and selected. E2E: a suite switches language and theme and checks a few labels and computed colours.

Outcome (2026-10-03)

Done on the spec-030 branch in three parts — Fluent strings for the shell and the workspace, every extension in three languages, themes. How it works and the rules: Languages and Themes. Problems met: P-151 (subscribing inside a command effect), P-152 (dynamic text in a style element breaks hydration).

Open from Done when: the German and Chinese texts are drafts that still need a native speaker’s review; the JS bundles’ own phrases (CodeMirror’s search panel) are English; the IME composition case of issue #19 is unchanged.