diff --git a/README.md b/README.md index c19b88f..209388f 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/client.js b/client.js index 1817c74..b9ecc36 100644 --- a/client.js +++ b/client.js @@ -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 diff --git a/package.json b/package.json index 1da38b8..e1d119e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@dsh-plugin/session-notify", - "version": "1.0.1", + "version": "1.0.2", "private": true, "type": "module", "description": "会话完成、需要授权、需要回答时提醒你:窗口不在前台用系统通知,窗口在前台用应用内轻弹窗。",