{"_id":"@buddhilive/dsh-client-locale","name":"@buddhilive/dsh-client-locale","dist-tags":{"alpha":"0.1.2-alpha.3","latest":"0.1.2-alpha.3"},"versions":{"0.1.2-alpha.3":{"name":"@buddhilive/dsh-client-locale","description":"Locale plugin: Host-backed preference, extensible language catalog, browser fallback, and typed built-in dictionaries","version":"0.1.2-alpha.3","publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/Buddhilive/buddhi-ai-harness.git","directory":"packages/client/locale"},"type":"module","main":"lib/index.js","types":"lib/types/index.d.ts","exports":{".":{"types":"./lib/types/index.d.ts","default":"./lib/index.js"},"./invariant":{"types":"./lib/types/invariant.d.ts","default":"./lib/invariant.js"},"./client":{"types":"./lib/types/client/index.d.ts","default":"./lib/client.js"},"./src/*":"./src/*","./package.json":"./package.json"},"dsh":{"client":{"inject":["@buddhilive/dsh-client-connection","@buddhilive/dsh-client-ui-renderer","@buddhilive/dsh-client-ui-settings","@buddhilive/dsh-api-remotes"],"platform":"web","immediately":true}},"license":"MIT","peerDependencies":{"@deepseek-ai/cordis":"^4.0.2"},"devDependencies":{"@types/react":"~18.3.1","react":"^18.2.0","@deepseek-ai/cordis":"^4.0.2","@buddhilive/dsh-api-remotes":"^0.1.2-alpha.3","@buddhilive/dsh-client-store":"^0.1.2-alpha.3","@buddhilive/dsh-client-ui-primitives":"^0.1.2-alpha.3","@buddhilive/dsh-client-test-runtime":"^0.1.2-alpha.3","@buddhilive/dsh-client-ui-renderer":"^0.1.2-alpha.3","@buddhilive/dsh-client-ui-settings":"^0.1.2-alpha.3","@buddhilive/dsh-client-ui-slots":"^0.1.2-alpha.3","@buddhilive/dsh-settings":"^0.1.2-alpha.3","@buddhilive/dsh-invariants":"^0.1.2-alpha.3","@buddhilive/dsh-client-connection":"^0.1.2-alpha.3"},"dependencies":{"@deepseek-ai/schemastery":"^3.18.2"},"scripts":{"bundle":"tsdown","watch":"tsdown --watch"},"_id":"@buddhilive/dsh-client-locale@0.1.2-alpha.3","bugs":{"url":"https://github.com/Buddhilive/buddhi-ai-harness/issues"},"homepage":"https://github.com/Buddhilive/buddhi-ai-harness#readme","_integrity":"sha512-79pJHFo65uZqDEAonGWAO6V8Hs6I85RWO0sM6b+Dn/u2jf5vZ7RY9nQdVl+RhNurtERm0L9lqi7y3lFPU+Xzcg==","_resolved":"C:\\DevDojo\\Buddhi\\buddhi-ai-harness\\dist\\npm\\buddhilive-dsh-client-locale-0.1.2-alpha.3.tgz","_from":"file:C:/DevDojo/Buddhi/buddhi-ai-harness/dist/npm/buddhilive-dsh-client-locale-0.1.2-alpha.3.tgz","_nodeVersion":"24.13.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-79pJHFo65uZqDEAonGWAO6V8Hs6I85RWO0sM6b+Dn/u2jf5vZ7RY9nQdVl+RhNurtERm0L9lqi7y3lFPU+Xzcg==","shasum":"dac40e1e745d224ad5e6373cc0faa2549cf1330f","tarball":"https://registry.npmjs.org/@buddhilive/dsh-client-locale/-/dsh-client-locale-0.1.2-alpha.3.tgz","fileCount":18,"unpackedSize":96946,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCIzoFl7f0bPIG/SyLsH5wQiVaUEivpM6vB1bh+i+wd4wIgB8DMBvL+2hOKWWYWXAOPYzQ8+vVaQ6QuOuOj13qrZIc="}]},"_npmUser":{"name":"buddhilive","email":"visitbudkavin@gmail.com"},"directories":{},"maintainers":[{"name":"buddhilive","email":"visitbudkavin@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/dsh-client-locale_0.1.2-alpha.3_1788165995545_0.7601442957810669"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-31T08:46:35.377Z","0.1.2-alpha.3":"2026-08-31T08:46:35.690Z","modified":"2026-08-31T08:46:35.922Z"},"maintainers":[{"name":"buddhilive","email":"visitbudkavin@gmail.com"}],"description":"Locale plugin: Host-backed preference, extensible language catalog, browser fallback, and typed built-in dictionaries","homepage":"https://github.com/Buddhilive/buddhi-ai-harness#readme","repository":{"type":"git","url":"git+https://github.com/Buddhilive/buddhi-ai-harness.git","directory":"packages/client/locale"},"bugs":{"url":"https://github.com/Buddhilive/buddhi-ai-harness/issues"},"license":"MIT","readme":"---\ndescription: \"面向用户与插件作者的 web GUI 本地化说明：zh/en 偏好、浏览器派生回退、类型化命名空间词典与框架翻译席位。\"\nkind: \"package-reference\"\n---\n\n# @buddhilive/dsh-client-locale\n\n[English](README.md) | 中文\n\n## 概述\n\n`dsh-client-locale` 为 web GUI 提供本地化：用户在“设置 → 常规”中从已注册语言中选择，UI 文案会立即切换。本包内置 `zh` 与 `en`，外部 client 插件可以增加语言及其命名空间字典。在 loopback 页面上，该选择以 `locale.preference` 存储在 `$DSH_HOME/settings.yaml` 中；非 loopback 页面即使由 Connection 认证所有 API 方法，也只在进程内保留选择。全新浏览器会先临时使用 `navigator` 请求的第一个已注册语言，直到允许读取的 Host 偏好到达并实时替换。插件作者使用内置字典形式时会获得完整类型检查，并通过框架 `t` 席位翻译；经 slot 渲染的文案会随语言切换即时更新。\n\n## 目录\n\n- [使用本包](#use-this-package)\n- [理解实现](#understand-the-implementation)\n- [进一步探索](#further-exploration)\n- [模型体验](#model-experience)\n- [已知限制与延期工作](#known-limitations-and-deferred-work)\n- [开发备注](#dev-note)\n\n-----\n\n<a id=\"use-this-package\"></a>\n## 使用本包\n\n只要 web GUI 需要语言切换或翻译文案就使用它：已发布的设置行覆盖用户侧，插件作者则注册自己的词典。挂载无需任何配置——本包随客户端树一起激活。\n\n### 选择语言\n\n打开“设置 → 常规”并选择一种已注册语言。生效中的 locale 会立即应用：UI 文案切换、`<html lang>` 指向外部 id 或内置语言的文档标签，选择写入持久设置分区。没有显式 Host 偏好的浏览器会按完整标签、再按主子标签选择 `navigator` 请求的第一个已注册语言，无法匹配时回退到 English。已存储的外部 locale 会等待其定义注册，不会在不可用时生效。\n\n### 注册词典\n\n用已合并进 `LocaleNamespaceMap` 的命名空间调用 `ctx.locale.register(ns, { zh, en })`；编译器会对照该命名空间的类型化键并集检查每个键，并要求两个内置 locale 齐全。消费方通过 `ctx.locale.bind(ns)` 或框架注入的 `t` 席位翻译。UI 已挂载后再注册的词典无需重新挂载即可生效。\n\n### 注册语言包\n\n外部 client 插件把语言定义和每个已翻译命名空间注册为自身拥有的 effect；定义与字典可以按任意顺序注册：\n\n```js\nexport const inject = ['locale']\n\nexport function apply(ctx) {\n  ctx.effect(\n    () => ctx.locale.addLanguage({ id: 'ja', label: '日本語', fallback: 'en' }),\n    'my-locale: language',\n  )\n  ctx.effect(\n    () => ctx.locale.register('common', 'ja', {\n      cancel: 'キャンセル',\n      close: '閉じる',\n    }),\n    'my-locale: common dictionary',\n  )\n}\n```\n\n外部 id 必须是非空的 ASCII BCP 47 风格标签。它的 fallback 必须已经注册，且整条链必须终止于 `en`；未知目标、重复 id 与循环会在注册时失败。查找时先在请求命名空间内遍历生效语言的 fallback 链，再在 `common` 中遍历该链，最后显示键本身。卸载语言定义会将其从选择器移除，并让生效中的选择回落到可用的浏览器语言或默认语言。\n\n### Host 半侧做什么\n\nHost 通过 settings 服务为 loopback 页面持久化偏好。Client 会刻意拒绝非 loopback 页面使用该 settings scope，因此即使 Connection 认证所有 API 方法，它们的 locale 选择仍只存在于进程内。\n\n-----\n\n<a id=\"understand-the-implementation\"></a>\n## 理解实现\n\n<details>\n<summary>实现细节——点击展开</summary>\n\n本节解释 locale 服务的构建方式；可观察行为已在[使用本包](#use-this-package)中说明。\n\n### 设计理念\n\n一个 `LocaleRuntime` 同时拥有偏好与词典注册表，并且自身就是 slot 系统的 `LocaleFace`：`getSnapshot`／`subscribe` 通过 `ctx.slots.installLocale` 支撑框架注入的 `t` 席位。不可变快照携带生效中的 locale、可选择的 locale 列表与单调 revision；词典注册与 locale 切换都会推进 revision，但只有切换会发出 `locale/change` 事件。产品编写的 Client UI 文本必须来自这些带类型的字典，或来自已经本地化的 primitive prop；`verify-client-ui-i18n` 强制执行该源码归属（见[决策](../../../.agents/notes/implemented/architecture/2026-08-23-locale-owned-client-ui-copy.zh.md)）。\n\n### 偏好解析\n\n临时 locale 来自浏览器（`navigator.languages` 先按完整标签、再按主子标签匹配，以 English 作为回退），在允许使用的 Host-backed settings scope 送达其存储偏好之前生效。Host 读取在插件激活后运行，因此 settings scope 不可用或被拒绝都不会阻塞页面，结果会实时替换临时值。已存储的外部 locale 会等待其定义注册。`setLocale` 是唯一写入入口；即使 id 已与生效中的 locale 匹配也会持久化，因为生效中的值可能是临时的，必须能在共享同一 home 的其他浏览器上存活。\n\n### 词典查找\n\n带类型的对象形式要求两个内置 locale 都有完整字典；逐 locale 形式允许语言包独立注册每个命名空间。逐键查找会先在请求命名空间中沿生效语言声明的 fallback 链查找，再在 `common` 中重复该链，最后显示键本身。绑定的翻译函数按命名空间保持稳定身份，因此可以挂在 inject 表面上而不破坏 memoization。\n\n### 源码地图\n\n| 文件 | 职责 |\n|---|---|\n| [`src/client/index.ts`](src/client/index.ts) | `LocaleRuntime`、词典注册表、Language 行注册、`locale/change` 事件 |\n| [`src/index.ts`](src/index.ts) | node 半侧：注册 `locale` 设置命名空间 |\n| [`src/locale-settings.ts`](src/locale-settings.ts) | `locale.preference` 的持久 schema |\n| [`src/locales/`](src/locales/) | 已发布的 `zh`／`en` 词典 |\n\n</details>\n\n-----\n\n<a id=\"further-exploration\"></a>\n## 进一步探索\n\n当 locale 约定不够用时阅读以下页面：它所实现的 slot 面孔、它所依托的设置界面，以及偏好背后的持久化决策。\n\n- [客户端 slot 系统](../ui-slots/README.zh.md)——本包实现的 slot 模型与 `LocaleFace` 席位。\n- [Host 支撑偏好决策](../../../.agents/notes/implemented/bug-fix/2026-08-06-host-backed-web-preferences.zh.md)——偏好为何持久化在 Host 设置中而非浏览器里。\n- [设置组地图](../../settings/README.zh.md)——存储该偏好的设置服务。\n- [客户端组地图](../README.zh.md)——本包所属的浏览器半侧。\n\n-----\n\n<a id=\"model-experience\"></a>\n## 模型体验\n\n无。locale 服务属于浏览器侧 UI 插件层，不注册任何面向模型的内容。\n\n#### KV Cache 影响\n\n无；该包既不组装也不发送提供方请求。\n\n## 已知限制与延期工作\n\n<a id=\"known-limitations-and-deferred-work\"></a>\n\n\n这些限制说明本地化在哪些地方不完整，或在注册时被冻结。它们是当前包约束，不是任务积压。\n\n- **注册表持有的文本只读取一次翻译**——在 slot 渲染路径之外于注册时捕获的文案（例如 command 注册表中的 `/model` 命令描述）在重新注册前保持注册时的语言；slot 渲染的文案随切换实时更新。\n- **语言包负责语言特有行为**——注册表提供选择、持久化、浏览器匹配、逐 key 回退和 `<html lang>`；它不增加复数规则或双向布局。\n\n<a id=\"dev-note\"></a>\n### 开发备注\n\n<details>\n<summary>维护者的工作上下文——点击展开</summary>\n\n无。\n\n</details>\n","readmeFilename":"README.zh.md","_rev":"1-964a487696e152ec8d3dbfa249cf6db0"}