Files
apes-sidebar/src/client/locales.ts
T
figmar 48e16c73c7 feat(apes-sidebar): Phase 2 — per-tab state(tasks showInactive 持久化)
- useTasksPrefs:ctx.settings.register('apesSidebar.tasks') namespace,
  uSES 观察 + update 写回,applies:'live' 跨 reload 持久化
- tasks 面板加 showInactive 开关:OFF 时只渲染 running 会话
- filterTasksRows 纯函数导出(hook 需渲染,过滤决策单测锁定)
- locales:tasks.showInactive zh/en
- tests/tabs.spec.ts:+4 个 per-tab state 过滤测试(25→29)
- AGENTS.md:Phase 2 更新(per-tab state 落地,scratch 冒烟 +
  apps·msgs·profile feed 延后——better-sidebar 无对应数据源 + 无 dsh CLI)

门禁:typecheck rc=0 · build rc=0 · test 29/29
2026-09-04 20:11:06 +08:00

137 lines
4.4 KiB
TypeScript

/**
* apes-sidebar i18n dictionaries, registered into the DSH locale runtime
* under the `apesSidebar` namespace (follows the DSH i18n system: the
* active locale is the Host-backed preference, not the raw browser language).
*
* zh + en are primary. A third language falls back to en (the better-sidebar
* convention: every new zh key must also have an en entry).
*
* The module tracks the active locale in a small external store so `t()`
* always resolves against the current locale, and React components re-render
* on locale change via {@link useApesLocale} (useSyncExternalStore).
*/
import { useSyncExternalStore } from 'react'
import type { ApesLocaleService } from '../context-types.ts'
const NS = 'apesSidebar'
const zh: Record<string, string> = {
'tab.tasks.title': '任务',
'tab.agents.title': 'Agent',
'tab.messages.title': '消息',
'tab.apps.title': '应用',
'tab.profile.title': '账户',
'tasks.empty': '暂无会话',
'tasks.running': '运行中',
'tasks.current': '当前',
'tasks.subagent': '子代理',
'tasks.showInactive': '显示非运行会话',
'agents.empty': '暂无子代理',
'agents.hint': '子代理会话会在此按父会话分组展示。',
'apps.empty': '暂无应用',
'apps.hint': '工作区应用与外部入口将在此展示。',
'messages.empty': '暂无消息',
'messages.hint': '会话消息流与提醒将在此汇总。',
'profile.model': '模型',
'profile.provider': '提供商',
'profile.unknown': '未知',
'profile.hint': '账户与运行配置概览。',
}
const en: Record<string, string> = {
'tab.tasks.title': 'Tasks',
'tab.agents.title': 'Agents',
'tab.messages.title': 'Messages',
'tab.apps.title': 'Apps',
'tab.profile.title': 'Profile',
'tasks.empty': 'No sessions yet',
'tasks.running': 'running',
'tasks.current': 'current',
'tasks.subagent': 'subagent',
'tasks.showInactive': 'Show inactive sessions',
'agents.empty': 'No subagents yet',
'agents.hint': 'Subagent sessions appear here, grouped by parent.',
'apps.empty': 'No apps yet',
'apps.hint': 'Workspace apps and external entry points appear here.',
'messages.empty': 'No messages yet',
'messages.hint': 'Session message flow and reminders are aggregated here.',
'profile.model': 'Model',
'profile.provider': 'Provider',
'profile.unknown': 'Unknown',
'profile.hint': 'Account and runtime configuration overview.',
}
const DICTS: Record<string, Record<string, string>> = { zh, en }
/** The currently resolved active locale code ('zh' default). */
let activeLocale = 'zh'
const listeners = new Set<() => void>()
function notify(): void {
for (const fn of listeners) fn()
}
/** The active locale snapshot (uSES-safe: returns the same code string). */
export function getActiveLocale(): string {
return activeLocale
}
export function subscribeLocale(fn: () => void): () => void {
listeners.add(fn)
return () => {
listeners.delete(fn)
}
}
/**
* Re-render the calling component whenever the active locale changes.
* Returns the active locale code; components re-resolve `t(key)` against it.
*/
export function useApesLocale(): string {
return useSyncExternalStore(subscribeLocale, getActiveLocale, getActiveLocale)
}
/** Translate a key in the active locale; falls back to en, then the key. */
export function t(key: string): string {
const dict = DICTS[activeLocale] ?? en
return dict[key] ?? en[key] ?? key
}
/**
* Register the dictionaries into the locale runtime AND subscribe to its
* active-locale changes, keeping {@link activeLocale} in sync. Returns the
* combined disposer (locale registrations + the snapshot subscription).
*/
export function registerLocales(locale: ApesLocaleService | undefined): () => void {
if (!locale) return () => undefined
const disposes = [
locale.register(NS, 'zh', zh),
locale.register(NS, 'en', en),
]
let unsub: (() => void) | undefined
try {
// Seed the active locale from the runtime snapshot (uSES-safe read).
activeLocale = locale.getSnapshot().active || 'zh'
unsub = locale.subscribe(() => {
const next = locale.getSnapshot().active || 'en'
if (next !== activeLocale) {
activeLocale = next
notify()
}
})
} catch {
// Locale runtime unavailable (very early boot): stay on the default.
}
return () => {
unsub?.()
for (const d of disposes) {
try {
d()
} catch {
// Disposer already run (double-dispose) — ignore.
}
}
}
}