description: "Localization for the web GUI: the zh/en preference, browser-derived fallback, typed namespace dictionaries, and the framework translation seat, for users and plugin authors."
English | 中文
Use dsh-client-locale to switch the web GUI between the shipped English and Chinese locales or languages added by client plugins. User selections take effect immediately; loopback pages persist them in $DSH_HOME/settings.yaml, while non-loopback pages keep them only for the current process. New browsers use the first supported language requested by the browser until an allowed stored preference arrives. Plugin authors add typed namespace dictionaries and translate through the public locale API; slot-rendered copy updates without a reload.
Use it wherever the web GUI needs a language switch or translated copy: the shipped settings row covers users, and plugin authors register their own dictionaries. Nothing needs configuration to mount — the package activates with the client tree.
Open Settings → General and select a registered language. The active locale is applied immediately: the UI copy switches, <html lang> points at the external id or built-in document tag, and the choice is written to the durable settings section. A browser without an explicit Host preference selects the first registered language that matches navigator by full tag and then primary subtag, falling back to English. A stored external locale waits for its definition to register instead of becoming active while unavailable.
Call ctx.locale.register(ns, { zh, en }) with a namespace merged into LocaleNamespaceMap; the compiler checks every key against the namespace's typed key union and requires both shipped locales. Consumers translate through ctx.locale.bind(ns) or the framework-injected t seat. A dictionary registered after the UI is already mounted is picked up without a remount.
An external client plugin registers the language definition and each translated namespace as owned effects; definitions and dictionaries may register in either order:
export const inject = ['locale']
export function apply(ctx) {
ctx.effect(
() => ctx.locale.addLanguage({ id: 'ja', label: '日本語', fallback: 'en' }),
'my-locale: language',
)
ctx.effect(
() => ctx.locale.register('common', 'ja', {
cancel: 'キャンセル',
close: '閉じる',
}),
'my-locale: common dictionary',
)
}
An external id is a non-empty ASCII BCP 47-style tag. Its fallback must already be registered, and the chain must terminate at en; unknown targets, duplicate ids, and cycles fail at registration. Lookup walks the fallback chain in the requested namespace, repeats it in common, then displays the key. Unloading a definition removes it from the selector and returns an active selection to the available browser/default locale.
The Host persists the preference through the settings service on loopback pages. The Client deliberately withholds that settings scope on non-loopback pages, so their locale selection remains process-local even though Connection authenticates every API method.
Read these when the locale contract is not enough: the slot face it implements, the settings surface it rides, and the persistence decision behind the preference.
LocaleFace seat this package implements.None, as the locale service is a browser-side UI plugin layer that registers nothing model-facing.
None; this package neither assembles nor sends a provider request.
These limits define where localization is incomplete or frozen at registration time. They are current package constraints, not a task backlog.
/model command description in the command registry) keeps the language it was registered under until re-registration; slot-rendered copy follows switches live.<html lang>; it does not add plural rules or bidirectional layout.Runtime invariant: No companion is published. The locale catalog and dictionaries have no independent runtime source to compare against; registration disposal, preference resolution, and fallback lookup are asserted by behavior specs.