fix(i18n): better-locale 接入改为 activation-order-safe

ctx.get('betterLocale') 是非响应式读取(cordis 只对声明的 inject
依赖重新求值)。如果 better-locale 在 better-sidebar 之后激活,
apply 顶层的 ctx.get 返回 undefined,attachBetterLocale(undefined)
使整个覆盖接入变成 no-op,即使用户后来在设置页选了日语,
better-sidebar 的 t() 也不查询 override store。

改为在 ctx.effect 中订阅 ctx.locale.subscribe:better-locale 在
激活时(若有持久化的 override)和用户切换 override 时都会 bump
locale revision,subscribe 回调触发 sync(),重新读取 ctx.get
('betterLocale')。store 可用后 attachBetterLocale(store) + register
ja dict;store 不可用时 detach。

这解决了插件激活顺序不确定的问题:无论 better-locale 先于还是后于
better-sidebar 激活,override 接入都能正确建立。
This commit is contained in:
HuanLinOTO
2026-08-23 02:31:30 +08:00
parent 38dc807d15
commit 16a809594c
+37 -21
View File
@@ -67,28 +67,44 @@ export function apply(ctx: Context): void {
// 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
isOverrideActive(dshActive: string): boolean
register(ns: string, dicts: Record<string, Record<string, string>>): () => void
subscribe(listener: () => void): () => void
//
// Activation-order-safe: ctx.get('betterLocale') is a non-reactive read
// (cordis only re-evaluates declared `inject` deps). If better-locale
// activates after better-sidebar, the initial read returns undefined.
// We subscribe to the locale revision — better-locale bumps it on
// activation (when a persisted override exists) and on every override
// switch — and re-check ctx.get on each bump, attaching + registering
// the ja dict once the store becomes available.
ctx.effect(() => {
let dispose: (() => void) | undefined
const sync = (): void => {
dispose?.()
dispose = undefined
const store = ctx.get('betterLocale') as
| {
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
}
| undefined
attachBetterLocale(store)
if (store !== undefined) {
dispose = store.register(LOCALE_NS, { ja })
}
| 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')
}
}
// Initial check (picks up the store if better-locale activated first).
sync()
// Re-check on every locale revision bump (better-locale bumps when it
// activates with a persisted override, and when the user switches).
const unsubscribe = ctx.locale.subscribe(sync)
return () => {
unsubscribe()
dispose?.()
attachBetterLocale(undefined)
}
}, 'dsh-better-sidebar: better-locale lazy integration')
// 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