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:
HuanLinOTO committed 2026-08-23 01:42:01 +08:00
1 parent 7ceee777b4
commit 38dc807d15
4 files changed
+44 -9

No files matched your search

+1
View File
@@ -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
View File
@@ -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')
}