feat: 界面文案跟随 DSH 的 i18n(ctx.locale 语言偏好、实时切换、词典入注册表)
This commit is contained in:
@@ -104,6 +104,15 @@ function buildNewTabOptions(state: SidebarState, ctx: Context, scope: SessionSco
|
||||
export function Sidebar(props: { ctx: Context; store: SidebarStore }) {
|
||||
const { ctx, store } = props
|
||||
|
||||
// Copy freshness: re-render the whole tree when the DSH locale switches.
|
||||
// The module-level t() reads the active locale at call time, so a root
|
||||
// re-render alone re-localizes every panel (no memo barriers below).
|
||||
const localeRevision = useSyncExternalStore(
|
||||
useMemo(() => (callback: () => void) => ctx.locale.subscribe(callback), [ctx]),
|
||||
useCallback(() => ctx.locale.getSnapshot().active, [ctx]),
|
||||
)
|
||||
void localeRevision
|
||||
|
||||
// Narrow (mobile) viewports collapse the two panels into one: the right
|
||||
// panel becomes a full-width drawer holding BOTH workbenches, the bottom
|
||||
// panel (and its toggle button) disappears, and the layout push is
|
||||
|
||||
+15
-3
@@ -22,12 +22,13 @@ import { registerImeGuard } from './ime-guard.ts'
|
||||
import { loadPrefs } from './prefs.ts'
|
||||
import { SideCardSection } from './SideCardSection.tsx'
|
||||
import { api } from './api.ts'
|
||||
import { t } from './locales.ts'
|
||||
import { LOCALE_NS, attachLocale, t, zh, en } from './locales.ts'
|
||||
import css from './sidebar.module.css'
|
||||
import './layout.css'
|
||||
|
||||
/** Services required before mounting (provided by the client runtime). */
|
||||
export const inject = ['slots', 'sessions', 'connection', 'workspaces']
|
||||
/** Services required before mounting (provided by the client runtime; the
|
||||
* locale service backs the sidebar's copy — see locales.ts). */
|
||||
export const inject = ['slots', 'sessions', 'connection', 'workspaces', 'locale']
|
||||
|
||||
/**
|
||||
* Error boundary over the sidebar tree: a render error must never blank the
|
||||
@@ -69,6 +70,17 @@ class SidebarBoundary extends Component<{ children: ReactNode }, { error: string
|
||||
* @param ctx - the client cordis context (slots, sessions).
|
||||
*/
|
||||
export function apply(ctx: Context): void {
|
||||
// The sidebar follows the DSH i18n system: attach the locale service so
|
||||
// the module-level t()/isZh() resolve the Host-backed language preference
|
||||
// (and switch live — the Sidebar root subscribes to it), and register the
|
||||
// plugin's dictionaries into the shared locale registry. The disposers
|
||||
// run on fiber disposal, so re-activation (HMR) re-registers cleanly.
|
||||
attachLocale(ctx.locale)
|
||||
ctx.effect(() => {
|
||||
const offZh = ctx.locale.register(LOCALE_NS, 'zh', zh)
|
||||
const offEn = ctx.locale.register(LOCALE_NS, 'en', en)
|
||||
return () => { offZh(); offEn() }
|
||||
}, 'dsh-better-sidebar: dictionaries')
|
||||
// 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
|
||||
|
||||
+46
-11
@@ -1,11 +1,16 @@
|
||||
/**
|
||||
* Minimal zh/en copy for the sidebar. The plugin renders outside the slot
|
||||
* system's locale seat, so it reads the browser language directly (the DSH
|
||||
* locale preference and the browser language agree in practice; both default
|
||||
* from navigator.language anyway).
|
||||
* Minimal zh/en copy for the sidebar. The copy follows the DSH i18n system:
|
||||
* the client apply attaches the locale service (`ctx.locale`, provided by
|
||||
* `@deepseek-ai/dsh-client-locale`) through {@link attachLocale}, and
|
||||
* `t()`/`isZh()` resolve the active locale from it — the Host-backed
|
||||
* `locale.preference` wins over the raw browser language and switches live.
|
||||
* Without an attached service (standalone/test compositions) the browser
|
||||
* language is used, matching the previous behavior. The dictionaries are
|
||||
* also registered into the DSH locale registry under {@link LOCALE_NS}.
|
||||
*/
|
||||
|
||||
const zh = {
|
||||
/** The zh dictionary (also registered into the DSH locale registry under {@link LOCALE_NS}). */
|
||||
export const zh = {
|
||||
explorer: '资源管理器',
|
||||
git: '源代码管理',
|
||||
terminal: '终端',
|
||||
@@ -204,7 +209,8 @@ const zh = {
|
||||
taskKillError: '终止失败',
|
||||
}
|
||||
|
||||
const en: Record<keyof typeof zh, string> = {
|
||||
/** The en dictionary (key-set-equal to zh, enforced by the type annotation). */
|
||||
export const en: Record<keyof typeof zh, string> = {
|
||||
explorer: 'Explorer',
|
||||
git: 'Source Control',
|
||||
terminal: 'Terminal',
|
||||
@@ -403,13 +409,42 @@ const en: Record<keyof typeof zh, string> = {
|
||||
taskKillError: 'Kill failed',
|
||||
}
|
||||
|
||||
/** Translate a copy key in the browser's language (zh → zh, else en). */
|
||||
/**
|
||||
* The dictionary namespace this plugin owns in the DSH locale registry
|
||||
* (`'sidebar'` is taken by DSH's own ui-sidebar, hence this distinct name).
|
||||
*/
|
||||
export const LOCALE_NS = 'betterSidebar'
|
||||
|
||||
/** The DSH locale service attached by the client apply (absent → browser detection). */
|
||||
let localeService: { getSnapshot(): { active: string } } | undefined
|
||||
|
||||
/**
|
||||
* Attach (or detach, with undefined) the DSH locale service. The sidebar
|
||||
* mounts its own React root outside the slot system's locale seat, so the
|
||||
* service rides this module-level holder: components keep calling the plain
|
||||
* `t()` function, and the Sidebar root's locale subscription re-renders the
|
||||
* whole tree on switches.
|
||||
*/
|
||||
export function attachLocale(service: { getSnapshot(): { active: string } } | undefined): void {
|
||||
localeService = service
|
||||
}
|
||||
|
||||
/**
|
||||
* The active locale id ('zh' | 'en'): the DSH locale service's snapshot when
|
||||
* attached, else the browser language.
|
||||
*/
|
||||
function activeLocale(): string {
|
||||
return localeService?.getSnapshot().active
|
||||
?? (typeof navigator !== 'undefined' ? navigator.language : '')
|
||||
?? 'en'
|
||||
}
|
||||
|
||||
/** Translate a copy key in the active locale (zh → zh, else en). */
|
||||
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 {
|
||||
const lang = typeof navigator !== 'undefined' ? navigator.language : 'en'
|
||||
const dict = lang.toLowerCase().startsWith('zh') ? zh : en
|
||||
const dict = activeLocale().toLowerCase().startsWith('zh') ? zh : en
|
||||
let text = dict[key]
|
||||
if (params !== undefined) {
|
||||
for (const [name, value] of Object.entries(params)) {
|
||||
@@ -419,9 +454,9 @@ export function t(key: CopyKey, params?: Record<string, string | number>): strin
|
||||
return text
|
||||
}
|
||||
|
||||
/** Whether the browser language is Chinese (used for selectors). */
|
||||
/** Whether the active locale is Chinese (used for selectors). */
|
||||
export function isZh(): boolean {
|
||||
return typeof navigator !== 'undefined' && navigator.language.toLowerCase().startsWith('zh')
|
||||
return activeLocale().toLowerCase().startsWith('zh')
|
||||
}
|
||||
|
||||
/** Format an ISO 8601 author date relative to now (刚刚 / N 分钟前 / N 小时前 / 昨天 / date). */
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
*/
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { mediaUrl } from './api.ts'
|
||||
import { t } from './locales.ts'
|
||||
import { isZh, t } from './locales.ts'
|
||||
import { BinaryFallback, type LoadState, type OfficeViewProps } from './office-shared.tsx'
|
||||
import { xlsxWorkbookToUniver } from './xlsx-to-univer.ts'
|
||||
import css from './sidebar.module.css'
|
||||
@@ -54,14 +54,14 @@ export function XlsxView(props: OfficeViewProps): JSX.Element {
|
||||
const XLSX = await import('xlsx')
|
||||
const { createUniver, LocaleType, mergeLocales } = await import('@univerjs/presets')
|
||||
const { UniverSheetsCorePreset } = await import('@univerjs/preset-sheets-core')
|
||||
// Locales pick the browser language; falls back to en-US.
|
||||
const isZh = typeof navigator !== 'undefined' && navigator.language.toLowerCase().startsWith('zh')
|
||||
const localePack = await (isZh
|
||||
// Locales follow the DSH i18n system (the active locale); falls
|
||||
// back to the browser language, then en-US.
|
||||
const localePack = await (isZh()
|
||||
? import('@univerjs/preset-sheets-core/locales/zh-CN').then(m => m.default).catch(() => null)
|
||||
: import('@univerjs/preset-sheets-core/locales/en-US').then(m => m.default).catch(() => null))
|
||||
|
||||
const wb = XLSX.read(buf, { type: 'array' })
|
||||
const locale = isZh ? LocaleType.ZH_CN : LocaleType.EN_US
|
||||
const locale = isZh() ? LocaleType.ZH_CN : LocaleType.EN_US
|
||||
const workbookData = xlsxWorkbookToUniver(wb, '0.25.1', locale)
|
||||
|
||||
if (cancelled) return
|
||||
|
||||
Reference in New Issue
Block a user