feat(i18n): 接入 @huanlin/dsh-plugin-better-locale 支持 ja 覆盖

新增 ja 词典(src/client/locales-ja.ts,326 keys,与 zh key-set-equal),
通过可选 peer @huanlin/dsh-plugin-better-locale 接入第三语言覆盖:

- locales.ts: 新增 attachBetterLocale(store) + ja dict export;t() 先查
  store.getOverride(active, 'betterSidebar', key),命中则返回覆盖文本,
  否则走原 zh/en 链;isZh() 在 override 激活时返回 false
- client/index.tsx: apply 中 ctx.get('betterLocale') 取得 override store
  (optional,未安装时为 undefined),attachBetterLocale 注入 t();同时
  register('betterSidebar', { ja }) 把 ja 词典注册进 better-locale 的
  store,让外部 ctx.locale.lookup 调用者也能拿到 ja 文本
- Sidebar.tsx: 新增 betterLocaleActive uSES 订阅 store.subscribe,覆盖
  切换时全树重渲染(DSH locale 的 active 字段不变,既有 localeRevision
  uSES 不会触发)
- package.json: @huanlin/dsh-plugin-better-locale 加为 optional peer
  + devDep link
- tests/locales.spec.ts: 新增 7 个 better-locale override 测试(命中/
  fallback/detach/interpolate/ja dict 完整性 smoke check)

未安装 better-locale 时 ctx.get 返回 undefined,整段为 no-op,zh/en
行为完全不变。
This commit is contained in:
HuanLinOTO
2026-08-23 00:52:20 +08:00
parent d9b8f15d9e
commit 7ceee777b4
9 changed files with 607 additions and 6 deletions
+32 -1
View File
@@ -24,7 +24,7 @@ import { registerSettingsNavIcon } from './settings-nav-icon.ts'
import { loadExternalDisable, loadPrefs } from './prefs.ts'
import { SideCardSection } from './SideCardSection.tsx'
import { api } from './api.ts'
import { LOCALE_NS, attachLocale, t, zh, en } from './locales.ts'
import { LOCALE_NS, attachLocale, attachBetterLocale, t, zh, en, ja } from './locales.ts'
import css from './sidebar.module.css'
import './layout.css'
@@ -57,6 +57,37 @@ export function apply(ctx: Context): void {
const offEn = ctx.locale.register(LOCALE_NS, 'en', en)
return () => { offZh(); offEn() }
}, 'dsh-better-sidebar: dictionaries')
// Opt-in third-language support through @huanlin/dsh-plugin-better-locale.
// When that plugin is installed, it publishes `ctx.betterLocale` (the
// override store) and patches LocaleRuntime.prototype.lookup to consult
// it. We mirror the same override awareness into the sidebar's own `t()`:
// attachBetterLocale() makes t() consult the store's getOverride first,
// so the sidebar's chrome (which bypasses ctx.locale and calls t()
// directly) also switches to the override language. We also register
// the ja dict with the better-locale store so external callers of
// ctx.locale.lookup('betterSidebar', key) get the override text too.
// Optional peer: ctx.get returns undefined when better-locale is absent,
// in which case the whole block is a no-op and the zh/en chain runs.
const betterLocale = ctx.get('betterLocale') as
| {
readonly active: string | undefined
getOverride(dshActive: string, ns: string, key: string): string | undefined
register(ns: string, dicts: Record<string, Record<string, string>>): () => void
subscribe(listener: () => void): () => void
}
| undefined
attachBetterLocale(betterLocale)
if (betterLocale !== undefined) {
ctx.effect(() => {
// Register the ja dict for the betterSidebar namespace. Better-locale's
// patched lookup consults this when the user has selected the ja
// override; the dict stays owned by better-sidebar (the namespace
// owner), so a single source of truth covers both ctx.locale callers
// and the internal t().
return betterLocale.register(LOCALE_NS, { ja })
}, 'dsh-better-sidebar: better-locale ja dict')
}
// One store instance per activation: production code creates it only here,
// then hands it to the mounted panel and closes over it in the slot
// registrations (the official createXXXStore() factory rule — no