refactor(i18n): 覆盖只在 DSH active=en 时生效(借用英文槽位)
better-locale 的覆盖本质上是借用 DSH 的英文槽位来渲染第三语言: - store.getOverride(dshActive, ns, key) 改为只在 dshActive === 'en' 时返回覆盖文本;DSH 在 zh 下覆盖完全惰性(保持原生 zh) - 新增 store.isOverrideActive(dshActive):返回 active !== undefined && dshActive === 'en',供 better-sidebar 的 isZh() 判断 - isZh() 改用 isOverrideActive(dshActive) 判断(仅在 DSH=en 且有 override 时返回 false),zh 下返回 true - LanguageSwitcher 接收 dshActive prop,选了非 native 且 DSH 非 en 时显示「请将 DSH 切换到英文以查看 [语言名]」提示 - 新增 switchToEnHint copy key(zh + en) - 测试:better-locale +3 store tests(en-only / inert on zh / isOverrideActive),+1 patch test(inert on zh);better-sidebar +1 locales test(override inert while DSH on zh),FakeBetterLocale 加 isOverrideActive 方法
This commit is contained in:
@@ -595,7 +595,7 @@ interface OpenTabSeed {
|
||||
| **id 冲突** | `registerTab` / `registerFileViewer` 对重复 id 抛错;建议用包前缀(`my-plugin:xxx`) |
|
||||
| **家族右面板互斥(v0.13.0+)** | 读取 `aionui-panel` 设置命名空间的 `rightPanel`:解析为 `'aionui-panel'` 时整个侧边栏不挂载(`settings.get` 路由返回 `externalDisable: true`,客户端挂载门 + 各类接管一并停用;`settings/document-updated` 推送实时生效,无 `remote` 服务的部署回退为启动时判定)。未安装 aionui 或提供方为其他值时不受影响 |
|
||||
| **i18n 跟随** | 侧边栏界面文案跟随 DSH 的 `ctx.locale`(`@deepseek-ai/dsh-client-locale`):词典注册在 `betterSidebar` 命名空间,语言偏好(Host-backed `locale.preference`)与浏览器语言不一致时以 DSH 为准并实时切换;locale 服务缺失时回退浏览器语言。插件自身的 `t()`(`src/client/locales.ts`)由 `apply()` 挂接服务;消费插件**不要**依赖此内部函数——标题等字段传字符串或 `() => string` 即可(i18n 友好)。⚠️ 渲染 DSH 的 `MarkdownText` 时必须传 `codeLabels={{ copyLabel: t('copy'), copiedLabel: t('copied') }}`——该组件 cordis-free,漏传则代码块复制按钮回退硬编码中文 |
|
||||
| **第三语言覆盖(ja 等)** | 可选 peer `@huanlin/dsh-plugin-better-locale`(`peerDependenciesMeta.optional: true`)提供 ja/ko/... 等第三语言覆盖。安装后 `apply()` 调 `ctx.get('betterLocale')` 取得 override store 并经 `attachBetterLocale()` 注入 `t()`:`t()` 先查 `store.getOverride(active, 'betterSidebar', key)`,命中则返回覆盖文本(如 ja),否则走原 zh/en 链。Sidebar root 另订阅 store 的 `subscribe()`(uSES on `store.active`),覆盖切换时全树重渲染(DSH locale 的 `active` 字段不变,所以既有 `localeRevision` uSES 不会触发)。本插件同时把 ja 词典 `register('betterSidebar', { ja })` 进 better-locale 的 store,让外部 `ctx.locale.lookup('betterSidebar', key)` 调用者也能拿到 ja 文本。未安装 better-locale 时 `ctx.get` 返回 undefined,整段为 no-op,zh/en 行为不变。新增 zh key 时**必须**同步加 ja 翻译(`src/client/locales-ja.ts`),否则该 key 在 ja 覆盖下回退到 en |
|
||||
| **第三语言覆盖(ja 等)** | 可选 peer `@huanlin/dsh-plugin-better-locale`(`peerDependenciesMeta.optional: true`)提供 ja/ko/... 等第三语言覆盖。覆盖**借用 DSH 的英文槽位**:`store.getOverride(dshActive, ns, key)` 只在 `dshActive === 'en'` 时返回覆盖文本,DSH 在 zh 下覆盖完全惰性(保持原生 zh)。安装后 `apply()` 调 `ctx.get('betterLocale')` 取得 override store 并经 `attachBetterLocale()` 注入 `t()`:`t()` 先查 `store.getOverride(active, 'betterSidebar', key)`,命中则返回覆盖文本(如 ja),否则走原 zh/en 链;`isZh()` 改用 `store.isOverrideActive(active)` 判断(仅在 DSH=en 且有 override 时返回 false)。Sidebar root 另订阅 store 的 `subscribe()`(uSES on `store.active`),覆盖切换时全树重渲染(DSH locale 的 `active` 字段不变,所以既有 `localeRevision` uSES 不会触发)。本插件同时把 ja 词典 `register('betterSidebar', { ja })` 进 better-locale 的 store,让外部 `ctx.locale.lookup('betterSidebar', key)` 调用者也能拿到 ja 文本。better-locale 的 LanguageSwitcher 在用户选了非 native 且 DSH 非 en 时显示「请将 DSH 切换到英文以查看 [语言名]」提示。未安装 better-locale 时 `ctx.get` 返回 undefined,整段为 no-op,zh/en 行为不变。新增 zh key 时**必须**同步加 ja 翻译(`src/client/locales-ja.ts`),否则该 key 在 ja 覆盖下回退到 en |
|
||||
| **懒加载 chunk** | 内置重依赖(xterm/CodeMirror)在独立 bundle(`lib/client-<name>.js`)中,经 `/sidebar/bundle` 路由按需下发;每个脚本把 factory 赋到插件自有全局注册表 `globalThis.__dshChunks__[<name>]`,由 `src/client/chunk-loader.ts` 用自定义 require(externals 经 `__DSH_MODULES__` seed 分支解析)物化——**不经过** `__ModuleLoader__` 注册;**核心 bundle 禁止静态 import `src/client/chunks/*`**(会把库拖回启动路径);对消费插件透明——懒加载只作用于内置 descriptor,`component` 契约(`(props) => ReactNode` 纯渲染函数)不变 |
|
||||
|
||||
---
|
||||
|
||||
@@ -73,6 +73,7 @@ export function apply(ctx: Context): void {
|
||||
| {
|
||||
readonly active: string | undefined
|
||||
getOverride(dshActive: string, ns: string, key: string): string | undefined
|
||||
isOverrideActive(dshActive: string): boolean
|
||||
register(ns: string, dicts: Record<string, Record<string, string>>): () => void
|
||||
subscribe(listener: () => void): () => void
|
||||
}
|
||||
|
||||
+19
-6
@@ -703,10 +703,17 @@ let localeService: { getSnapshot(): { active: string } } | undefined
|
||||
* (absent → no override; the zh/en chain runs). The store's `active`
|
||||
* field holds the user's chosen override id (e.g. `'ja'`); `undefined`
|
||||
* means "no override, use DSH native zh/en".
|
||||
*
|
||||
* The override only takes effect when DSH's active locale is `'en'`
|
||||
* (it borrows DSH's English slot to render a third language). While
|
||||
* DSH is on `'zh'` the override is inert — `getOverride` returns
|
||||
* `undefined` and `isOverrideActive` returns `false` — so `t()` and
|
||||
* `isZh()` fall through to the native zh/en chain unchanged.
|
||||
*/
|
||||
let betterLocaleStore: {
|
||||
readonly active: string | undefined
|
||||
getOverride(dshActive: string, ns: string, key: string): string | undefined
|
||||
isOverrideActive(dshActive: string): boolean
|
||||
} | undefined
|
||||
|
||||
/**
|
||||
@@ -752,10 +759,11 @@ export type CopyKey = keyof typeof zh
|
||||
|
||||
/** Translate a copy key; `{name}` placeholders interpolate from `params`. */
|
||||
export function t(key: CopyKey, params?: Record<string, string | number>): string {
|
||||
// 1. better-locale override (e.g. ja) wins when an override is active
|
||||
// and the store has a translation for this (ns, key). The DSH active
|
||||
// locale is forwarded for forward compatibility (a future mode could
|
||||
// gate overrides on DSH being 'en'); the current store ignores it.
|
||||
// 1. better-locale override (e.g. ja) wins when an override is active,
|
||||
// DSH's active locale is 'en' (the override borrows the en slot),
|
||||
// and the store has a translation for this (ns, key). The store's
|
||||
// getOverride returns undefined otherwise (no override, DSH on zh,
|
||||
// or missing key) and the zh/en chain runs.
|
||||
const dshActive = localeService?.getSnapshot().active ?? ''
|
||||
const override = betterLocaleStore?.getOverride(dshActive, LOCALE_NS, key)
|
||||
let text: string | undefined = override
|
||||
@@ -780,9 +788,14 @@ export function t(key: CopyKey, params?: Record<string, string | number>): strin
|
||||
|
||||
/** Whether the active locale is Chinese (used for selectors). */
|
||||
export function isZh(): boolean {
|
||||
// An active override is not Chinese — short-circuit so selectors pick
|
||||
// An override is only "effectively active" when DSH is on 'en' (the
|
||||
// override borrows the en slot). While DSH is on 'zh' the override is
|
||||
// inert — the user sees native zh, so isZh() returns true. When an
|
||||
// override is effectively active, the rendered text is neither zh nor
|
||||
// en (it's ja/ko/...), so isZh() returns false to route selectors to
|
||||
// the non-zh branch (e.g. date format, pluralization).
|
||||
if (betterLocaleStore?.active !== undefined) return false
|
||||
const dshActive = localeService?.getSnapshot().active ?? ''
|
||||
if (betterLocaleStore?.isOverrideActive(dshActive) === true) return false
|
||||
return activeLocale().toLowerCase().startsWith('zh')
|
||||
}
|
||||
|
||||
|
||||
+23
-2
@@ -113,17 +113,21 @@ describe('locales (DSH i18n following)', () => {
|
||||
})
|
||||
})
|
||||
|
||||
/** Minimal fake of the better-locale override store (the subset t() reads). */
|
||||
/** Minimal fake of the better-locale override store (the subset t()/isZh() read). */
|
||||
class FakeBetterLocale {
|
||||
active: string | undefined = undefined
|
||||
private readonly dict: Record<string, Record<string, Record<string, string>>>
|
||||
constructor(dict: Record<string, Record<string, Record<string, string>>>) {
|
||||
this.dict = dict
|
||||
}
|
||||
getOverride(_dshActive: string, ns: string, key: string): string | undefined {
|
||||
getOverride(dshActive: string, ns: string, key: string): string | undefined {
|
||||
if (this.active === undefined) return undefined
|
||||
if (dshActive !== 'en') return undefined
|
||||
return this.dict[ns]?.[this.active]?.[key]
|
||||
}
|
||||
isOverrideActive(dshActive: string): boolean {
|
||||
return this.active !== undefined && dshActive === 'en'
|
||||
}
|
||||
subscribe(_listener: () => void): () => void {
|
||||
return () => {}
|
||||
}
|
||||
@@ -175,6 +179,23 @@ describe('locales (better-locale override)', () => {
|
||||
expect(isZh()).toBe(true)
|
||||
})
|
||||
|
||||
it('override is inert while DSH is on zh (override borrows the en slot)', () => {
|
||||
const locale = new FakeLocale()
|
||||
locale.switchTo('zh')
|
||||
attachLocale(locale)
|
||||
|
||||
const betterLocale = new FakeBetterLocale({
|
||||
[LOCALE_NS]: { ja: { explorer: 'エクスプローラー' } },
|
||||
})
|
||||
betterLocale.active = 'ja'
|
||||
attachBetterLocale(betterLocale)
|
||||
|
||||
// Override is set to 'ja' but DSH is on zh — override is inert,
|
||||
// native zh text wins. The user must switch DSH to en to see ja.
|
||||
expect(t('explorer')).toBe('资源管理器')
|
||||
expect(isZh()).toBe(true)
|
||||
})
|
||||
|
||||
it('returns to the zh/en chain after detaching the override store', () => {
|
||||
const locale = new FakeLocale()
|
||||
locale.switchTo('en')
|
||||
|
||||
Reference in New Issue
Block a user