docs(locale): define extensible language fallbacks

This commit is contained in:
imccyu committed 2026-08-25 20:40:25 +08:00
1 parent c4e0b3b1e7
commit 9d61ab6756
6 files changed
+60 -6

No files matched your search

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-07-30-client-locale-full-rollout.md
2026-07-30-client-locale-full-rollout.md: aeb4deae28b0dfdb9ab75fd64fe3143958cd6910
2026-07-30-client-locale-full-rollout.zh.md: a642b6062cb3dc7a2dfa22dd5d8cf7d9a02e3104
2026-07-30-client-locale-full-rollout.md: 890312bb7c0825763eb16416939ff4930014f54b
2026-07-30-client-locale-full-rollout.zh.md: 78d1f0aa8e3b6da8be3e2c640414cb4325337891
@@ -14,6 +14,8 @@ After the typed locale standard seat landed (`locale:` on register → framework
**Component copy rides the standard `t` seat; deep children take `t` as a plain prop** typed `XxxProps['t']`. The dictionary canon is unchanged: `zh satisfies Record<string, string>` is the key source and `en satisfies Record<XxxKey, string>` locks bilingual balance.
**The built-in locale set is closed; the language catalog is extensible.** The package contributes only `zh` and `en`, and typed namespace registration continues to require that bilingual pair. An external client plugin adds a language through `ctx.effect(() => ctx.locale.addLanguage({ id, label, fallback }))` and contributes partial translations through the existing single-locale dictionary registration. An external language id is its BCP 47 tag for preference storage, dictionary lookup, browser matching, and `<html lang>`; the built-in `zh` definition retains its internal `zh-CN` document tag. Every added language names a registered fallback whose own definition supplies the next fallback, and the chain must terminate at `en`; unknown targets and cycles fail at registration. For each key, lookup walks that chain in the requested namespace, then repeats it in `common`, before displaying the key itself. The Host stores an open string preference; an unavailable saved id remains pending until its language registers, while removal returns an active selection to the available browser match or `en`. Catalog changes advance the `LocaleFace` revision so the Language row follows registration and disposal.
**Zero-Cordis atoms (ui-primitives) take copy as required props.** `HoverCard`, structured Tool blocks, JSON/Markdown renderers, `ConnectionBanner`, and modal chrome remain runtime-independent; localized plugins pass complete dictionary-driven label objects from their own `t` seat and memoize cache-sensitive objects on the `t` identity. The removal of language-bearing defaults and the complete prop inventory are owned by the [locale-owned copy decision](2026-08-23-locale-owned-client-ui-copy.md).
**Every product-authored UI phrase is translated.** Client fallbacks, design labels, trajectory inspection, accessibility names, and formatter units are dictionary-owned under the [locale-owned copy decision](2026-08-23-locale-owned-client-ui-copy.md). User/model/provider/wire text and protocol or code tokens remain verbatim data. Framework-free boot markup still runs before the locale service; the localized application replaces its product copy after activation.
@@ -14,6 +14,8 @@ typed locale 标准席位(`locale:` 注册声明 → 框架注入强类型 `t`
**组件文案走标准 `t` 席位;深层子组件用 prop 下传**,类型写 `XxxProps['t']`。字典规范形态不变:`zh satisfies Record<string, string>` 为 key 源、`en satisfies Record<XxxKey, string>` 锁双语平衡。
**内置 locale 集合封闭,语言目录可扩展。** 本包只提供 `zh` 与 `en`,类型化命名空间注册仍要求这对双语字典。外部 client 插件通过 `ctx.effect(() => ctx.locale.addLanguage({ id, label, fallback }))` 增加语言,并通过既有的单 locale 字典注册贡献不完整翻译。外部语言 id 同时是偏好存储、字典查找、浏览器匹配和 `<html lang>` 使用的 BCP 47 标签;内置 `zh` 定义继续使用内部 `zh-CN` 文档标签。每个新增语言都声明一个已注册的 fallback,fallback 自身的定义给出下一层 fallback,整条链必须终止于 `en`;未知目标和循环在注册时失败。每个 key 先在请求的命名空间中沿链查找,再在 `common` 中重复同一条链,最后显示 key 本身。Host 存储开放字符串偏好;不可用的已保存 id 会保持待采用,直至对应语言注册;定义移除后,正在使用的选择会回落到可用的浏览器匹配或 `en`。目录变更推进 `LocaleFace` revision,使语言设置行跟随注册和 dispose。
**zero-Cordis 原子组件(ui-primitives)通过必填 prop 接收文案。** `HoverCard`、结构化工具块、JSON/Markdown 渲染器、`ConnectionBanner` 和 modal chrome 均保持运行时独立;已本地化插件从自己的 `t` 席位传入完整的字典驱动 label 对象,对缓存敏感的对象按 `t` 身份 memo。移除带语言默认值以及完整 prop 清单由 [locale 归属文案决策](2026-08-23-locale-owned-client-ui-copy.zh.md)负责。
**所有产品编写的 UI 短语都翻译。** client 兜底文案、设计 label、trajectory 检查面、无障碍名称和格式化单位均按 [locale 归属文案决策](2026-08-23-locale-owned-client-ui-copy.zh.md)进入字典。用户/模型/提供方/wire 文本以及协议或代码 token 仍作为数据原样呈现。不依赖框架的 boot 标记仍早于 locale 服务运行;本地化应用激活后会替换其中的产品文案。
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/client/locale/README.md
README.md: 1fa0262e7c1e8aa50f12fd2b7533d97ea4737199
README.zh.md: 45cf657d5f8455fe128f378c2a2b77d919a441da
README.md: 8736c656b2cd0d2ca3ec9f298e6e7edc7dc0ea40
README.zh.md: 84114900b389f394ee27c5e325c7d427e2dd657c
+26 -1
View File
@@ -2,7 +2,31 @@
English | [中文](README.zh.md)
Locale plugin: LocaleRuntime — the `zh`/`en` preference stored as `locale.preference` in `$DSH_HOME/settings.yaml`; when that explicit Host value is absent, a fresh browser starts provisionally in the language `navigator` asks for (primary-subtag matching, with `en` when it asks for no language this app ships). The Host read runs after plugin activation so an unavailable settings service cannot block the page; its result replaces the provisional browser value live. The Client keeps Host settings persistence disabled on non-loopback pages, so their locale selection remains process-local even though Connection authenticates every API method. `locale/change` fires on switches, and the plugin points `<html lang>` at the active locale (`zh-CN`/`en`) on activation and on every switch. The service also owns the ns×locale dictionary registry (typed `register(ns, {zh, en})` checked against `LocaleNamespaceMap`, `bind(ns)`→`TranslateNS<ns>`; lookup chain ns → common → en → key), implements the slot system's `LocaleFace`, and installs itself through `ctx.slots.installLocale`, backing the framework-injected `t` standard seat (`Translate`/`TranslateNS` are ui-slots types; import them from there — this package only re-exports for dictionary owners' convenience). Product-authored Client UI text must enter through these typed dictionaries or an already-localized primitive prop; `verify-client-ui-i18n` enforces that source ownership ([decision](../../../.agents/notes/implemented/architecture/2026-08-23-locale-owned-client-ui-copy.md)). The [Host-backed preferences decision](../../../.agents/notes/implemented/bug-fix/2026-08-06-host-backed-web-preferences.md) owns the persistence boundary.
Locale plugin: LocaleRuntime — the preference stored as `locale.preference` in `$DSH_HOME/settings.yaml`; when that explicit Host value is absent, a fresh browser starts provisionally in the first registered language `navigator` asks for (full-tag then primary-subtag matching, with `en` when none match). The Host read runs after plugin activation so an unavailable settings service cannot block the page; its result replaces the provisional browser value live. A saved external locale waits for its definition to register rather than becoming active while unavailable. The Client keeps Host settings persistence disabled on non-loopback pages, so their locale selection remains process-local even though Connection authenticates every API method. `locale/change` fires on switches, and the plugin points `<html lang>` at the external language id or the built-in language's document tag on activation and on every switch. Product-authored Client UI text must enter through these typed dictionaries or an already-localized primitive prop; `verify-client-ui-i18n` enforces that source ownership ([decision](../../../.agents/notes/implemented/architecture/2026-08-23-locale-owned-client-ui-copy.md)). The [Host-backed preferences decision](../../../.agents/notes/implemented/bug-fix/2026-08-06-host-backed-web-preferences.md) owns the persistence boundary.
The package ships only `zh` and `en`. External client plugins add a selectable language with `ctx.effect(() => ctx.locale.addLanguage({ id, label, fallback }))` and add its dictionaries through the existing `register(ns, locale, dict)` form; unloading the definition removes it from the selector and returns an active selection to the available browser/default locale. An external id is a non-empty ASCII BCP 47-style tag used for persistence, dictionary lookup, browser matching, and `<html lang>`. Its fallback must already be registered, and the resulting chain must terminate at `en`; unknown targets, duplicate ids, and cycles fail at registration. For each key, lookup walks the chain in the requested namespace, repeats it in `common`, then displays the key. The typed `register(ns, { zh, en })` form remains checked against `LocaleNamespaceMap` and requires both built-in dictionaries. LocaleRuntime implements the slot system's `LocaleFace` and installs itself through `ctx.slots.installLocale`, backing the framework-injected `t` standard seat (`Translate`/`TranslateNS` are ui-slots types; import them from there — this package only re-exports them for dictionary owners).
## Language-pack registration
Register the definition and each translated namespace as effects owned by the language-pack plugin:
```js
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',
)
}
```
## Model Experience
@@ -15,3 +39,4 @@ None; this package neither assembles nor sends a provider request.
## Known Limitations and Deferred Work
- **Registry-held text reads its translation once** — copy captured at registration time outside the slot render path (e.g. the `/model` command description in the command registry) keeps the language it was registered under until re-registration; slot-rendered copy follows switches live.
- **Language packs own language-specific behavior** — the registry supplies selection, persistence, browser matching, key fallback, and `<html lang>`; it does not add plural rules or bidirectional layout.
+26 -1
View File
@@ -2,7 +2,31 @@
[English](README.md) | 中文
locale 插件:LocaleRuntime——`zh`/`en` 偏好以 `locale.preference` 存储在 `$DSH_HOME/settings.yaml` 中;若没有显式 Host 值,全新浏览器会暂时使用 `navigator` 请求的语言(按主子标签匹配;若其请求的语言本应用都不提供,则使用 `en`)。Host 读取在插件激活后执行,因此 settings 服务不可用不会阻塞页面;读取结果会实时替换浏览器暂定值。Client 在非 loopback 页面禁用 Host settings 持久化,因此这些页面的 locale 选择仍只保留在进程内,尽管 Connection 会认证每个 API 方法。`locale/change` 仅在切换语言时触发;插件会在激活时以及每次切换时把 `<html lang>` 指向当前 locale(`zh-CN`/`en`)。该服务还拥有 ns×locale 字典注册表(类型化 `register(ns, {zh, en})` 按 `LocaleNamespaceMap` 校验,`bind(ns)`→`TranslateNS<ns>`;查找链 ns → common → en → key),实现 slot 系统的 `LocaleFace`,并经 `ctx.slots.installLocale` 自行安装,支撑框架注入的 `t` 标准席位(`Translate`/`TranslateNS` 是 ui-slots 的类型;请从那里导入——本包的再导出仅为字典所有者提供便利)。产品编写的 Client UI 文本必须经这些 typed 字典或已本地化原子组件 prop 进入展示;`verify-client-ui-i18n` 会强制这项源码归属([决策](../../../.agents/notes/implemented/architecture/2026-08-23-locale-owned-client-ui-copy.zh.md))。该持久化边界由[Host settings 支撑的偏好决策](../../../.agents/notes/implemented/bug-fix/2026-08-06-host-backed-web-preferences.zh.md)拥有。
locale 插件:LocaleRuntime——偏好以 `locale.preference` 存储在 `$DSH_HOME/settings.yaml` 中;若没有显式 Host 值,全新浏览器会暂时使用 `navigator` 请求的第一个已注册语言(先匹配完整标签,再匹配主子标签;若都不匹配,则使用 `en`)。Host 读取在插件激活后执行,因此 settings 服务不可用不会阻塞页面;读取结果会实时替换浏览器暂定值。已保存但尚未注册定义的外部 locale 会等待该定义注册,不会在不可用时成为当前语言。Client 在非 loopback 页面禁用 Host settings 持久化,因此这些页面的 locale 选择仍只保留在进程内,尽管 Connection 会认证每个 API 方法。`locale/change` 仅在切换语言时触发;插件会在激活时以及每次切换时把 `<html lang>` 指向外部语言 id 或内置语言的文档标签。产品编写的 Client UI 文本必须经这些 typed 字典或已本地化原子组件 prop 进入展示;`verify-client-ui-i18n` 会强制这项源码归属([决策](../../../.agents/notes/implemented/architecture/2026-08-23-locale-owned-client-ui-copy.zh.md))。该持久化边界由[Host settings 支撑的偏好决策](../../../.agents/notes/implemented/bug-fix/2026-08-06-host-backed-web-preferences.zh.md)拥有。
本包只内置 `zh` 与 `en`。外部 client 插件通过 `ctx.effect(() => ctx.locale.addLanguage({ id, label, fallback }))` 增加可选语言,并通过既有 `register(ns, locale, dict)` 形式增加该语言的字典;定义卸载后,它会从选择器移除,正在使用的选择则回落到当前可用的浏览器语言或默认语言。外部 id 是用于持久化、字典查找、浏览器匹配和 `<html lang>` 的非空 ASCII BCP 47 风格标签。其 fallback 必须已注册,形成的链必须终止于 `en`;未知目标、重复 id 和循环会在注册时失败。每个 key 先在请求的命名空间中沿链查找,再在 `common` 中重复该链,最后显示 key。类型化 `register(ns, { zh, en })` 形式仍按 `LocaleNamespaceMap` 检查,并要求两份内置字典。LocaleRuntime 实现 slot 系统的 `LocaleFace`,并经 `ctx.slots.installLocale` 自行安装,支撑框架注入的 `t` 标准席位(`Translate`/`TranslateNS` 是 ui-slots 的类型;请从那里导入——本包的再导出仅供字典所有者使用)。
## 语言包注册
语言包插件把语言定义和每个已翻译命名空间注册为自身拥有的 effect:
```js
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',
)
}
```
## 模型体验
@@ -15,3 +39,4 @@ locale 插件:LocaleRuntime——`zh`/`en` 偏好以 `locale.preference` 存
## 已知限制与暂缓事项
- **注册表持有的文本只读取一次翻译**——在 slot 渲染路径之外于注册时捕获的文案(例如 command 注册表中的 `/model` 命令描述)在重新注册前保持注册时的语言;slot 渲染的文案随切换实时更新。
- **语言包负责语言特有行为**——注册表提供选择、持久化、浏览器匹配、逐 key 回退和 `<html lang>`;它不增加复数规则或双向布局。