Put the language picker first in General UI, with a globe and a title
that includes every supported language. Derive its title and search terms
from shared language metadata.
Add regression coverage for the title, icon and row order, and format the
additions with the pinned Rust toolchain.
docs/I18N.md still said the configurator was untranslated, that the
language was set once at startup, and that a translation could reorder
the {} placeholders; the guard rules and the steps for adding a language
were incomplete. It now covers which processes draw translated text and
when each picks up a change, the configurator preview, how auto resolves
in a systemd user service, the fixed placeholder order, the guard's call
forms and its derived-label audit, the limits on local catalog overrides,
search under translation, and every place a new language has to be added.
CONFIG.md, config.example.toml and the catalogs README state the same
timing, override and placeholder rules.
Much of the configurator still drew English in Chinese: row titles and
hints, buttons, accessibility labels, placeholders, validation errors,
status messages such as the shortcut reset and conflict-review notices,
the session card's Created, Opened and Saved prefixes, and the
background-setup status lines. They are now marked where they are drawn,
with format! text turned into trf templates and displayed constants
written as literals so the catalog guard can read them, and the Chinese
catalog gains an entry for each new key.
Values written into config or other programs stay as they were: default
board and profile names, the GNOME shortcut name and default quick-color
labels.
Picking a language set the process language, but every label already on
screen kept its old text, so only dialogs opened afterwards changed. A
reload, Defaults or a finished save replaced the draft without applying
its language, which left the window in the language of the previous
draft. Startup read the config a second time, synchronously, just to pick
the language before the window was built.
The draft's language is now applied on every transition that replaces
the draft, and by the Language row, before the status text for that
transition is built. When the language changes, the shell rebuilds its
pages from the model and retitles the persistent chrome in place. The
model owns everything typed or selected, so nothing entered is lost; the
rebuild carries focus, the caret and selection, and the visible page's
scroll position across, and editor popovers closed by the rebuild no
longer cancel their edits. The window opens in English and switches when
the first load arrives, and that load job prepares every catalog off the
GTK thread.
Text the configurator stored for display was translated once and kept:
the "Never" of a catalog entry, the shortcut recorder's prompt, the
claimants of a pending shortcut conflict. Those now store what they
report and are rendered when drawn. The language list names each
language in itself, so a preview the user cannot read can still be undone,
and search matches the English terms in every language as well as tab,
sub-tab and section titles as displayed.
A GTK test runs the real window on the private headless compositor and
checks a language switch with open editors, focus, caret, scroll and
navigation; both GTK gates require its marker.
The catalog guard read only a string literal that followed tr( or trf( on
the same line, so the calls rustfmt wraps onto the next line were never
checked, and a call whose argument was a constant or an expression was
skipped without a word. Three strings had no catalog entry as a result:
"Light Mode drawing", "Light Mode passthrough" and "Path copied". The
guard compared only the {} counts in the catalog, so a template spelling
{min} and {max} passed while trf left those words in the message, as the
whole-number range error did.
The guard now reads plain and raw literals wherever they open the call,
across lines, and trf(tr("…"), &[…]). Any other argument fails, except in
the action accessors, whose metadata the guard audits directly, now
including the compact labels action_display_label derives. A trf template
must spell only {} and pass as many values as it has placeholders.
The three calls now spell their literals and have Chinese entries, the
range message uses two {} slots, and the trf documentation states that
values fill placeholders in order, so a translation can move them but not
swap two.
Several places used displayed text as an identifier or read it to decide
what to do, so they broke once that text was translated:
- The configurator named its GtkStack pages after their translated tab
titles. The names were registered in the startup language and looked up
in the current one, so after a language change the sidebar and the UI
sub-tabs stopped switching pages. Tabs now have fixed stack names.
- The arrow-style menu built its widget IDs from the translated style
label, and the layout menu from the translated preset label, in both
toolbars ("top.arrow-style.pointy" became "top.arrow-style.尖头"). Both
now derive the ID from the typed value.
- The session catalog cleared its loading notice only when the status
contained the English "Loading sessions", which never matched in
Chinese. The notice is now a typed status that completion recognizes in
any language, and that re-renders when the language changes.
- The background-setup panel chose its failure styling by searching the
feedback for "failed" or "error", so translated failures looked like
successes. The feedback is now typed and carries its outcome, including
a light-control setup whose Hyprland reload failed after the controls
were written.
- The eraser tooltip compared the slider label with a translation; it now
reads a typed flag.
Wrap every user-visible string in the overlay and the configurator with
tr()/trf(), make the UI language selectable at runtime, and ship a
complete Simplified Chinese catalog.
Core:
- src/i18n.rs: process-global language state, tr()/trf() lookup, and
locale resolution (WAYSCRIBER_LANGUAGE > [ui] language > desktop
locale > English)
- src/domain/language.rs: Language enum with serde spellings
- locales/zh-CN.toml: 2247 entries, embedded via include_str!
- config: [ui] language = "auto" | "en" | "zh-CN"
Coverage:
- overlay: help overlay, context menus, board picker, status bar,
toasts, command palette chrome, properties panel, about window,
onboarding hints, top-toolbar menus (wayland + GTK), input HUD,
arrow-style labels, navigation hints, all action labels
- wayscriber-configurator: shell, sidebar tabs, every settings page,
dialogs, and status messages; it loads the language at startup and
again when the Language row changes, so its chrome follows the
overlay
Guards and tests:
- tests/repository_guards/translations.rs: every tr()/trf() literal in
src/ and configurator/src/ must have a catalog entry (call sites are
reported); a value may not repeat its key; placeholder counts must
match
- tests/i18n.rs: catalog loading, override, and detection
- configurator suite: 519 pass; full lib suite unchanged apart from
pre-existing/environment failures
Deliberately untranslated: --help/--version, log messages, keyboard key
names, TOML config keys shown in validation errors, gsettings/systemctl
command arguments, CSS class names, and product names.