🐛 fix(client): 注册 locale 字典,修掉设置页显示翻译键的问题

This commit is contained in:
pyh
2026-09-30 15:55:12 +08:00
parent 2f38b7ea3c
commit ca1f9297ae
3 changed files with 42 additions and 7 deletions
+8 -2
View File
@@ -74,6 +74,8 @@ D:\DeepSeek Harness Plugins\dsh-session-notify
- 浏览器半只依赖 **slot 的标准 props**(`useSessions`、`useSessionStatus`)读取会话状态,这是 DSH 官方给插件读会话数据的方式;不轮询、不自己订阅会话事件、不读别的插件的 DOM;
- 只往两个官方 slot 注册:`settings.general.item`(设置行)与 `shell.overlay`(两个条目:轻弹窗层 + 无渲染的观察器,内容永远是 `null`);
- **不要**把"无渲染"条目放进 `sidebar.panellist`:该 slot 的宿主会把**每个条目的 id 当成一个左侧面板按钮**(`entriesOfSlot('sidebar.panellist')` → 面板列表,按钮文字取 `options.label ?? options.id`,条目本身作为图标内联渲染),放进去会在左侧多出一行空面板并挤坏侧边栏。本插件第一版踩过这个坑,现已改挂到通用浮层;
- **文案必须注册字典**:`ctx.locale.register('session-notify', { zh, en })`,否则 `locale.bind(ns)` 在查不到该命名空间时会把 key 原样返回——界面上就会显示成 `settings.title` 这样的原始键(本插件第二版踩过这个坑)。注册会在 locale 修订号上打点,已经渲染出来的条目会自动换上文案,不需要刷新;
- 另外自带一份本地字典兜底:即使拿不到框架的 `t`、或 locale 服务没有保存注册,也会渲染中文/英文文案而不是原始键;
- 样式只用主题 token,不 import 任何 `@deepseek-ai/dsh-client-*` 包(规范要求,也是渲染不被上游改动打断的前提);
- 宿主半不声明 `inject`、不注册服务、不注册路由。
@@ -83,8 +85,12 @@ D:\DeepSeek Harness Plugins\dsh-session-notify
`client.js` 是纯 JavaScript、浏览器端运行,可以在 Node 里用桩模块加载器评估并驱动:
- 模块接线:加载后返回的插件、`inject` 声明、注册集合(含"观察器绝不在 sidebar.* 里"这一条)、以及各组件在空状态 / 权限被拒 / 无 Notification API 下的渲染都不报错;
- 投递规则:40 项断言覆盖上面"行为细节"里的每一条(完成提醒一次、当前会话不打扰、后台走系统通知、授权/提问各提醒一次、运行中延后、子会话跳过、历史不补发、开关生效、弹窗自动消失等)。
- 模块接线:加载后返回的插件、`inject` 声明、注册集合(含"观察器绝不在 `sidebar.*` 里"这一条);
- 国际化:字典已注册且中英键集合一致、设置行与弹窗渲染出的每个字符串都不是原始键、拿不到框架 `t` 时本地字典兜底、locale 服务不保存注册时仍然有文案;
- 渲染健壮性:各组件在空状态 / 权限被拒 / 无 Notification API 下渲染都不报错;
- 投递规则:完成提醒一次、当前会话不打扰、后台走系统通知、授权/提问各提醒一次、运行中延后、子会话跳过、空白会话跳过、历史不补发、开关生效、弹窗自动消失等。
合计 44 项断言。
## License
+33 -4
View File
@@ -375,10 +375,35 @@ window.__ModuleLoader__.load({
return preview(interaction.displayReason?.text ?? interaction.reason?.text ?? '')
}
/**
* Register this package's dictionaries with the locale service.
*
* Without this the service has no entry for the namespace and every
* `locale.bind(NS)` lookup answers with the key itself, so the settings row
* shows `settings.title` instead of its copy. The typed form takes every
* shipped locale in one call, and the older single-locale form stays
* supported as a fallback. Registration bumps the locale revision, so
* already-rendered entries pick the copy up without a reload.
* @param locale - the locale service, or undefined when this profile has none.
* @returns a disposer for whichever registrations were installed.
*/
function registerDictionaries(locale) {
if (locale === undefined || typeof locale.register !== 'function') return () => {}
const dictionaries = { zh, en }
try {
return locale.register(NS, dictionaries)
} catch {
const disposers = Object.entries(dictionaries).map(([id, dict]) => locale.register(NS, id, dict))
return () => {
for (const dispose of disposers) dispose?.()
}
}
}
/**
* Build a translate function for one locale service: the service's own bound
* namespace first, then the current language's dictionary, then the key
* itself, so a missing seat degrades copy instead of rendering.
* namespace first, then this package's dictionaries, then the key itself, so
* a missing seat degrades copy instead of rendering.
* @param locale - the locale service, or undefined when this profile has none.
* @returns a translate function with `{ name }` interpolation.
*/
@@ -387,7 +412,10 @@ window.__ModuleLoader__.load({
if (locale !== undefined && typeof locale.bind === 'function') {
try {
const bound = locale.bind(NS)
if (typeof bound === 'function') return bound(key, params)
if (typeof bound === 'function') {
const translated = bound(key, params)
if (typeof translated === 'string' && translated !== key) return translated
}
} catch {
/* a locale service without this namespace still renders the fallback */
}
@@ -395,7 +423,7 @@ window.__ModuleLoader__.load({
let language = ''
try {
const snapshot = locale?.getSnapshot?.()
language = String(snapshot?.language ?? snapshot?.locale ?? snapshot?.id ?? '')
language = String(snapshot?.active ?? '')
} catch {
language = ''
}
@@ -951,6 +979,7 @@ window.__ModuleLoader__.load({
}
}
ctx.effect(() => registerDictionaries(locale), 'session-notify: dictionaries')
ctx.effect(() => installBridges(), 'session-notify: window focus')
ctx.effect(() => () => {
disposed = true
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@dsh-plugin/session-notify",
"version": "1.0.1",
"version": "1.0.2",
"private": true,
"type": "module",
"description": "会话完成、需要授权、需要回答时提醒你:窗口不在前台用系统通知,窗口在前台用应用内轻弹窗。",