feat(locale): derive the initial Settings language from the browser
A first visit resolved to Chinese regardless of the browser: LocaleService read `dsh.locale` and fell straight back to `zh` when nothing was stored, ignoring the languages the browser already states it reads. The initial locale now resolves through three ordered sources — the persisted preference, then `navigator` (first entry of the ordered language list whose primary subtag names a shipped locale, so `zh-Hans-CN` -> zh and `en-GB` -> en), then `FALLBACK_LOCALE`. An explicit choice still wins and nothing writes the detected locale back to storage, so "has the user chosen?" stays a question only the stored value answers. Specs asserting the shipped Chinese copy now state the browser they assume: the web e2e scenarios open their page with `locale: ZH_BROWSER_LOCALE`, and package specs pin it through the new `pinBrowserLanguages` test helper. `settings-chrome.e2e.ts` gains an English-browser scenario as the assembled-app proof.
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/client/locale/README.md
|
||||
README.md: c2adbcabc77def740094288da4643032873aa5b8
|
||||
README.zh.md: c6ecb31e21d7513a4e7d579b17588107ccd7ea59
|
||||
README.md: 7f780092af9bc7079cc5080c06e986bef2dfdbce
|
||||
README.zh.md: 62c037977115d33b834fe60b042431e44d208524
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
English | [中文](README.zh.md)
|
||||
|
||||
Locale plugin: LocaleService — the browser locale preference (`zh`/`en`, persisted under `dsh.locale`; `locale/change` fires on switches only) plus the ns×locale dictionary registry (typed `register(ns, {zh, en})` checked against `LocaleNamespaceMap`, `bind(ns)`→`TranslateNS<ns>`; lookup chain ns → common → zh → key). The service implements the slot system's `LocaleFace` and installs itself through `ctx.slots.installLocale`, backing the framework-injected `t` standard seat (`Translate`/`TranslateNS` are ui-slots types; import them from there — this package only re-exports for dictionary owners' convenience).
|
||||
Locale plugin: LocaleService — the browser locale preference (`zh`/`en`, persisted under `dsh.locale`; with nothing persisted a fresh browser opens in the language `navigator` asks for — matched on the primary subtag, `zh` when it asks for none this app ships; `locale/change` fires on switches only) plus the ns×locale dictionary registry (typed `register(ns, {zh, en})` checked against `LocaleNamespaceMap`, `bind(ns)`→`TranslateNS<ns>`; lookup chain ns → common → zh → key). The service implements the slot system's `LocaleFace` and installs itself through `ctx.slots.installLocale`, backing the framework-injected `t` standard seat (`Translate`/`TranslateNS` are ui-slots types; import them from there — this package only re-exports for dictionary owners' convenience).
|
||||
|
||||
## Model Experience
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[English](README.md) | 中文
|
||||
|
||||
locale 插件:LocaleService——浏览器 locale 偏好(`zh`/`en`,以 `dsh.locale` 持久化;`locale/change` 仅在切换语言时触发),加上 ns×locale 字典注册表(类型化 `register(ns, {zh, en})` 按 `LocaleNamespaceMap` 校验,`bind(ns)`→`TranslateNS<ns>`;查找链 ns → common → zh → key)。该服务实现 slot 系统的 `LocaleFace` 并经 `ctx.slots.installLocale` 自行安装,支撑框架注入的 `t` 标准席位(`Translate`/`TranslateNS` 是 ui-slots 的类型;请从那里导入——本包的再导出仅为字典所有者提供便利)。
|
||||
locale 插件:LocaleService——浏览器 locale 偏好(`zh`/`en`,以 `dsh.locale` 持久化;未持久化偏好时,全新浏览器以 `navigator` 请求的语言开场——按主子标签匹配,若其请求的语言本应用都不提供则为 `zh`;`locale/change` 仅在切换语言时触发),加上 ns×locale 字典注册表(类型化 `register(ns, {zh, en})` 按 `LocaleNamespaceMap` 校验,`bind(ns)`→`TranslateNS<ns>`;查找链 ns → common → zh → key)。该服务实现 slot 系统的 `LocaleFace` 并经 `ctx.slots.installLocale` 自行安装,支撑框架注入的 `t` 标准席位(`Translate`/`TranslateNS` 是 ui-slots 的类型;请从那里导入——本包的再导出仅为字典所有者提供便利)。
|
||||
|
||||
## 模型体验
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ declare module 'cordis' {
|
||||
}
|
||||
}
|
||||
|
||||
/** Fallback locale consulted after the active locale misses (also the default). */
|
||||
/** Fallback locale consulted after the active locale misses (also the last-resort initial locale). */
|
||||
export const FALLBACK_LOCALE: LocaleId = 'zh'
|
||||
|
||||
/** Shared namespace for shell-level texts. */
|
||||
@@ -123,7 +123,7 @@ export class LocaleService {
|
||||
*/
|
||||
constructor(ctx: Context) {
|
||||
this.ctx = ctx
|
||||
this.snapshot = Object.freeze({ active: restorePreference(), locales: LOCALES, revision: 0 })
|
||||
this.snapshot = Object.freeze({ active: resolveInitialLocale(), locales: LOCALES, revision: 0 })
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -288,17 +288,45 @@ export class LocaleService {
|
||||
}
|
||||
}
|
||||
|
||||
/** Read the persisted locale id; unknown or unreadable values fall back to zh. */
|
||||
function restorePreference(): LocaleId {
|
||||
/**
|
||||
* The locale a fresh service opens with: an explicit preference the user
|
||||
* already chose wins over the browser's own language, which in turn wins over
|
||||
* {@link FALLBACK_LOCALE} (non-browser boots and browsers set to a language
|
||||
* this app does not ship).
|
||||
*/
|
||||
function resolveInitialLocale(): LocaleId {
|
||||
return restorePreference() ?? detectBrowserLocale() ?? FALLBACK_LOCALE
|
||||
}
|
||||
|
||||
/** Read the persisted locale id; unknown or unreadable values read as no preference. */
|
||||
function restorePreference(): LocaleId | undefined {
|
||||
// Non-browser runs (node e2e booting the client tree) have no localStorage.
|
||||
if (typeof localStorage === 'undefined') return FALLBACK_LOCALE
|
||||
if (typeof localStorage === 'undefined') return undefined
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY)
|
||||
if (stored === 'zh' || stored === 'en') return stored
|
||||
} catch {
|
||||
// Storage access can throw (privacy mode); the default below covers it.
|
||||
// Storage access can throw (privacy mode); an unreadable store simply
|
||||
// records no preference, and the browser language decides instead.
|
||||
}
|
||||
return FALLBACK_LOCALE
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* The first shipped locale the browser asks for, matched on the primary
|
||||
* subtag so every regional variant lands on its language (`zh-Hans-CN` -> zh,
|
||||
* `en-GB` -> en). `navigator.language` trails the ordered `languages` list
|
||||
* because a browser may expose only the former.
|
||||
*/
|
||||
function detectBrowserLocale(): LocaleId | undefined {
|
||||
// Non-browser runs (node e2e booting the client tree) have no navigator.
|
||||
if (typeof navigator === 'undefined') return undefined
|
||||
for (const tag of [...navigator.languages, navigator.language]) {
|
||||
const primary = tag.toLowerCase().split('-')[0]
|
||||
const match = LOCALES.find(locale => locale.id === primary)
|
||||
if (match) return match.id
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
/** Persist the locale id; storage failures are non-fatal (preference resets next boot). */
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Language row registration, snapshot projection into the row store, and
|
||||
* recovery after an HMR collapse of the declaring entry. */
|
||||
import { Context } from 'cordis'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { SlotsService } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import { apply, inject, SETTINGS_NS } from '@deepseek-ai/dsh-client-locale/client'
|
||||
import type { LanguageRowInjected, LocaleService } from '@deepseek-ai/dsh-client-locale/client'
|
||||
@@ -36,6 +36,16 @@ function faceOf(slots: SlotsService) {
|
||||
}
|
||||
|
||||
describe('locale apply', () => {
|
||||
// A fresh service opens in the browser's language, so these wiring specs
|
||||
// pin one to keep their zh baseline independent of the test environment.
|
||||
beforeEach(() => {
|
||||
vi.stubGlobal('navigator', { languages: ['zh-CN'], language: 'zh-CN' })
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
it('declares the slot service', () => {
|
||||
expect(inject).toEqual(['slots'])
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @vitest-environment jsdom
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { Context } from 'cordis'
|
||||
import type { LocaleSnapshot } from '@deepseek-ai/dsh-client-locale/client'
|
||||
import { LocaleService, STORAGE_KEY } from '@deepseek-ai/dsh-client-locale/client'
|
||||
@@ -11,9 +11,20 @@ const make = (): { ctx: Context; svc: LocaleService; events: LocaleSnapshot[] }
|
||||
return { ctx, svc: new LocaleService(ctx), events }
|
||||
}
|
||||
|
||||
/** Pin the browser environment a fresh service reads its initial locale from. */
|
||||
const stubLanguages = (...tags: string[]): void => {
|
||||
vi.stubGlobal('navigator', { languages: tags, language: tags[0] ?? '' })
|
||||
}
|
||||
|
||||
describe('LocaleService', () => {
|
||||
beforeEach(() => {
|
||||
localStorage.clear()
|
||||
// A Chinese browser is the baseline these specs assert their zh state on.
|
||||
stubLanguages('zh-CN')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
it('translates through the active-locale -> zh -> key chain', () => {
|
||||
@@ -132,23 +143,45 @@ describe('LocaleService', () => {
|
||||
expect(() => { svc.setLocale('fr') }).toThrow('not registered')
|
||||
})
|
||||
|
||||
it('restores a persisted locale and falls back to zh on garbage', () => {
|
||||
it('restores a persisted locale over the browser language, and garbage reads as no preference', () => {
|
||||
localStorage.setItem(STORAGE_KEY, 'en')
|
||||
expect(make().svc.getLocale().active).toBe('en')
|
||||
localStorage.setItem(STORAGE_KEY, 'fr')
|
||||
expect(make().svc.getLocale().active).toBe('zh')
|
||||
})
|
||||
|
||||
it('runs without localStorage (node boots): defaults on read, no-op on write', () => {
|
||||
it('opens in the browser language when nothing is persisted, matching regional variants on their primary subtag', () => {
|
||||
stubLanguages('en-GB', 'zh-CN')
|
||||
expect(make().svc.getLocale().active).toBe('en')
|
||||
stubLanguages('zh-Hant-TW')
|
||||
expect(make().svc.getLocale().active).toBe('zh')
|
||||
// An unshipped language walks the list to the first one this app ships.
|
||||
stubLanguages('fr-FR', 'en-US')
|
||||
expect(make().svc.getLocale().active).toBe('en')
|
||||
// Only `language` populated (browsers that expose no ordered list).
|
||||
vi.stubGlobal('navigator', { languages: [], language: 'en-US' })
|
||||
expect(make().svc.getLocale().active).toBe('en')
|
||||
// No shipped language anywhere in the browser's preferences: zh remains
|
||||
// the product default rather than an arbitrary near-match.
|
||||
stubLanguages('fr-FR', 'de')
|
||||
expect(make().svc.getLocale().active).toBe('zh')
|
||||
})
|
||||
|
||||
it('runs without localStorage or navigator (node boots): defaults on read, no-op on write', () => {
|
||||
vi.stubGlobal('localStorage', undefined)
|
||||
try {
|
||||
const { svc } = make()
|
||||
expect(svc.getLocale().active).toBe('zh')
|
||||
svc.setLocale('en')
|
||||
expect(svc.getLocale().active).toBe('en')
|
||||
} finally {
|
||||
vi.unstubAllGlobals()
|
||||
}
|
||||
vi.stubGlobal('navigator', undefined)
|
||||
const { svc } = make()
|
||||
expect(svc.getLocale().active).toBe('zh')
|
||||
svc.setLocale('en')
|
||||
expect(svc.getLocale().active).toBe('en')
|
||||
})
|
||||
|
||||
it('keeps the browser language out of the way once a preference exists', () => {
|
||||
stubLanguages('en-US')
|
||||
const { svc } = make()
|
||||
svc.setLocale('zh')
|
||||
expect(localStorage.getItem(STORAGE_KEY)).toBe('zh')
|
||||
expect(make().svc.getLocale().active).toBe('zh')
|
||||
})
|
||||
|
||||
it('exposes the two shipped locales with self-described labels', () => {
|
||||
|
||||
@@ -38,6 +38,7 @@ export { TestWorkspaces } from './workspaces.ts'
|
||||
export { conversationSnapshot, workspaceListState } from './fixtures.ts'
|
||||
export type { SessionBehaviorOverrides, SessionFixture, Stabilizer } from './fixtures.ts'
|
||||
export { makeTranslate } from './translate.ts'
|
||||
export { pinBrowserLanguages } from './locale-env.ts'
|
||||
|
||||
/** Erased register face for the internal root call (the public declare seam holds the typing). */
|
||||
type ErasedRegister = (options: object, component: unknown) => () => void
|
||||
|
||||
25
packages/client/test-runtime/src/locale-env.ts
Normal file
25
packages/client/test-runtime/src/locale-env.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Browser-language pin for specs that assert localized copy. A fresh
|
||||
* LocaleService with no stored preference opens in the language `navigator`
|
||||
* asks for, and jsdom reports the runner's own (`en-US`) — so a spec asserting
|
||||
* the product's Chinese copy states the browser it assumes instead of
|
||||
* inheriting the machine's.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Override `navigator.languages`/`navigator.language` for the current spec.
|
||||
* @param primary - most preferred BCP 47 tag; also becomes `navigator.language`.
|
||||
* @param rest - further tags in preference order.
|
||||
* @returns restore function handing the properties back to the environment.
|
||||
*/
|
||||
export function pinBrowserLanguages(primary: string, ...rest: string[]): () => void {
|
||||
Object.defineProperty(navigator, 'languages', { value: [primary, ...rest], configurable: true })
|
||||
Object.defineProperty(navigator, 'language', { value: primary, configurable: true })
|
||||
return () => {
|
||||
// Deleting the own properties uncovers the environment's own accessors
|
||||
// again (Navigator declares both readonly, hence the erased receiver).
|
||||
const own = navigator as unknown as Record<string, unknown>
|
||||
delete own.languages
|
||||
delete own.language
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,7 @@
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-client-locale": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-runtime": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-test-runtime": "workspace:^",
|
||||
"@deepseek-ai/dsh-goal": "workspace:^",
|
||||
"@deepseek-ai/dsh-plan-mode": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-layout": "workspace:^",
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
// guards would mask. Rendering-path acceptance lives in
|
||||
// chat-toolview-slot.spec.tsx.
|
||||
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { SlotTestRuntime } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { SlotTestRuntime, pinBrowserLanguages } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import type { SessionBehaviorOverrides } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { LocaleService } from '@deepseek-ai/dsh-client-locale/client'
|
||||
import type { ISession, SessionId } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
@@ -25,6 +25,12 @@ import type {
|
||||
} from '@deepseek-ai/dsh-client-ui-conversation/client'
|
||||
import type { createChatStore } from '../src/client/stores.ts'
|
||||
|
||||
// The service reads its initial locale from the browser; these specs assert
|
||||
// the shipped Chinese copy, so they state the browser they assume.
|
||||
let restoreLanguages: () => void
|
||||
beforeEach(() => { restoreLanguages = pinBrowserLanguages('zh-CN') })
|
||||
afterEach(() => { restoreLanguages() })
|
||||
|
||||
const ROOT = 'root-1' as SessionId
|
||||
|
||||
type ChatInstance = ReturnType<ReturnType<typeof createChatStore>['create']>
|
||||
|
||||
@@ -24,9 +24,15 @@ import { cleanup, fireEvent, waitFor, within } from '@testing-library/react'
|
||||
import { LocaleService } from '@deepseek-ai/dsh-client-locale/client'
|
||||
import type { ISession, SessionId, TodoItem, ToolResultNode } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type { PropsRenderSlots } from '@deepseek-ai/dsh-client-ui-slots'
|
||||
import { SlotTestRuntime } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { SlotTestRuntime, pinBrowserLanguages } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { apply, inject } from '@deepseek-ai/dsh-client-ui-conversation/client'
|
||||
|
||||
// The service reads its initial locale from the browser; these specs assert
|
||||
// the shipped Chinese copy, so they state the browser they assume.
|
||||
let restoreLanguages: () => void
|
||||
beforeEach(() => { restoreLanguages = pinBrowserLanguages('zh-CN') })
|
||||
afterEach(() => { restoreLanguages() })
|
||||
|
||||
const SID = 's1' as SessionId
|
||||
|
||||
/** jsdom has no ResizeObserver; the composer seat publishes its height through one. */
|
||||
|
||||
@@ -8,13 +8,19 @@
|
||||
// machinery spec (chat-toolview-slot.spec.tsx) and the shell e2e; this spec
|
||||
// stops at the assembly surface.
|
||||
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { SlotTestRuntime } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { SlotTestRuntime, pinBrowserLanguages } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { resolveSlotLabel } from '@deepseek-ai/dsh-client-ui-slots'
|
||||
import { LocaleService } from '@deepseek-ai/dsh-client-locale/client'
|
||||
import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import { apply, inject } from '@deepseek-ai/dsh-client-ui-conversation/client'
|
||||
|
||||
// The service reads its initial locale from the browser; these specs assert
|
||||
// the shipped Chinese copy, so they state the browser they assume.
|
||||
let restoreLanguages: () => void
|
||||
beforeEach(() => { restoreLanguages = pinBrowserLanguages('zh-CN') })
|
||||
afterEach(() => { restoreLanguages() })
|
||||
|
||||
const ROOT = 'root-1' as SessionId
|
||||
const CHILD = 'child-1' as SessionId
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
"@deepseek-ai/dsh-client-locale": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-runtime": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-schema-form": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-test-runtime": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-settings": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "workspace:^",
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
/** Models section registration: declaration-aware deferral, the locale-following label thunk, and HMR recovery. */
|
||||
import { Context } from 'cordis'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { resolveSlotLabel } from '@deepseek-ai/dsh-client-ui-slots'
|
||||
import { SlotsService } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import { LocaleService } from '@deepseek-ai/dsh-client-locale/client'
|
||||
import { pinBrowserLanguages } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { apply, inject, refreshIfLoaded } from '@deepseek-ai/dsh-client-ui-models/client'
|
||||
import { ModelsSection } from '../src/client/ModelsSection.tsx'
|
||||
import { DeepSeekOnboardingDialog } from '../src/client/DeepSeekOnboardingDialog.tsx'
|
||||
|
||||
// The service reads its initial locale from the browser; these specs assert
|
||||
// the shipped Chinese copy, so they state the browser they assume.
|
||||
let restoreLanguages: () => void
|
||||
beforeEach(() => { restoreLanguages = pinBrowserLanguages('zh-CN') })
|
||||
afterEach(() => { restoreLanguages() })
|
||||
|
||||
async function bench() {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SlotsService).await()
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-client-locale": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-runtime": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-test-runtime": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-settings": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "workspace:^",
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
/** Ownerless-copy registrations: the four seats, the dictionaries, thunked labels, and HMR recovery. */
|
||||
import { Context } from 'cordis'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { resolveSlotLabel } from '@deepseek-ai/dsh-client-ui-slots'
|
||||
import { SlotsService } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import { LocaleService } from '@deepseek-ai/dsh-client-locale/client'
|
||||
import { pinBrowserLanguages } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { apply, inject } from '@deepseek-ai/dsh-client-ui-settings-general/client'
|
||||
import { CloseLabel, HeaderContent, TriggerContent } from '../src/client/chrome.tsx'
|
||||
import { GeneralSection } from '../src/client/GeneralSection.tsx'
|
||||
|
||||
// The service reads its initial locale from the browser; these specs assert
|
||||
// the shipped Chinese copy, so they state the browser they assume.
|
||||
let restoreLanguages: () => void
|
||||
beforeEach(() => { restoreLanguages = pinBrowserLanguages('zh-CN') })
|
||||
afterEach(() => { restoreLanguages() })
|
||||
|
||||
/** The four seats this plugin fills (slot name → expected component). */
|
||||
const SEATS = [
|
||||
['settings.trigger', TriggerContent],
|
||||
|
||||
@@ -8,12 +8,18 @@
|
||||
* holes (sidebar.workspaces / sidebar.settings) have no registrant here, so
|
||||
* the snapshots pin the shell chrome itself.
|
||||
*/
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { act, cleanup, waitFor } from '@testing-library/react'
|
||||
import { SlotTestRuntime } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { SlotTestRuntime, pinBrowserLanguages } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { LocaleService } from '@deepseek-ai/dsh-client-locale/client'
|
||||
import { apply, inject } from '@deepseek-ai/dsh-client-ui-sidebar/client'
|
||||
|
||||
// The service reads its initial locale from the browser; these specs assert
|
||||
// the shipped Chinese copy, so they state the browser they assume.
|
||||
let restoreLanguages: () => void
|
||||
beforeEach(() => { restoreLanguages = pinBrowserLanguages('zh-CN') })
|
||||
afterEach(() => { restoreLanguages() })
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,13 +6,20 @@
|
||||
* sessionId, and unregisters on fiber teardown.
|
||||
*/
|
||||
import { Context } from 'cordis'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { LocaleService } from '@deepseek-ai/dsh-client-locale/client'
|
||||
import { pinBrowserLanguages } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { createScope, scopeOf, SlotsService } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type { SessionId } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import { apply, inject, SlashService } from '@deepseek-ai/dsh-client-ui-slash/client'
|
||||
import type { MenuViewInjected } from '@deepseek-ai/dsh-client-ui-slash/client'
|
||||
|
||||
// The service reads its initial locale from the browser; these specs assert
|
||||
// the shipped Chinese copy, so they state the browser they assume.
|
||||
let restoreLanguages: () => void
|
||||
beforeEach(() => { restoreLanguages = pinBrowserLanguages('zh-CN') })
|
||||
afterEach(() => { restoreLanguages() })
|
||||
|
||||
const sid = (k: string): SessionId => k as SessionId
|
||||
|
||||
async function bench() {
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"devDependencies": {
|
||||
"@deepseek-ai/dsh-client-locale": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-runtime": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-test-runtime": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-primitives": "workspace:^",
|
||||
"@deepseek-ai/dsh-client-ui-slots": "workspace:^",
|
||||
"@deepseek-ai/dsh-invariants": "workspace:^",
|
||||
|
||||
@@ -2,14 +2,21 @@
|
||||
* locale service, declaration-aware Appearance row registration, snapshot
|
||||
* projection into the row store, and HMR collapse recovery. */
|
||||
import { Context } from 'cordis'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
|
||||
import { SlotsService } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import { LocaleService } from '@deepseek-ai/dsh-client-locale/client'
|
||||
import { pinBrowserLanguages } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { apply, inject, SETTINGS_NS } from '@deepseek-ai/dsh-client-ui-theme/client'
|
||||
import type { AppearanceRowInjected, ThemeService } from '@deepseek-ai/dsh-client-ui-theme/client'
|
||||
import { AppearanceRow } from '../src/client/AppearanceRow.tsx'
|
||||
import type { createAppearanceRowStore } from '../src/client/settings-store.ts'
|
||||
|
||||
// The service reads its initial locale from the browser; these specs assert
|
||||
// the shipped Chinese copy, so they state the browser they assume.
|
||||
let restoreLanguages: () => void
|
||||
beforeEach(() => { restoreLanguages = pinBrowserLanguages('zh-CN') })
|
||||
afterEach(() => { restoreLanguages() })
|
||||
|
||||
const SLOT = 'settings.general.item'
|
||||
|
||||
async function bench() {
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import { Context } from 'cordis'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { SlotsService } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import { LocaleService } from '@deepseek-ai/dsh-client-locale/client'
|
||||
import { pinBrowserLanguages } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { apply, inject } from '@deepseek-ai/dsh-client-ui-workspace/client'
|
||||
import type { WorkspaceBrowserInjected, WorkspacePickerInjected } from '@deepseek-ai/dsh-client-ui-workspace/client'
|
||||
import { WorkspaceBrowser } from '../src/client/WorkspaceBrowser.tsx'
|
||||
import { WorkspacePicker } from '../src/client/WorkspacePicker.tsx'
|
||||
|
||||
// The service reads its initial locale from the browser; these specs assert
|
||||
// the shipped Chinese copy, so they state the browser they assume.
|
||||
let restoreLanguages: () => void
|
||||
beforeEach(() => { restoreLanguages = pinBrowserLanguages('zh-CN') })
|
||||
afterEach(() => { restoreLanguages() })
|
||||
|
||||
async function bench() {
|
||||
const ctx = new Context()
|
||||
await ctx.plugin(SlotsService).await()
|
||||
|
||||
@@ -14,10 +14,16 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { cleanup, fireEvent, waitFor, within } from '@testing-library/react'
|
||||
import type { ISession, SessionId, WorkspaceId } from '@deepseek-ai/dsh-client-runtime/client'
|
||||
import type { PropsRenderSlots } from '@deepseek-ai/dsh-client-ui-slots'
|
||||
import { SlotTestRuntime } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { SlotTestRuntime, pinBrowserLanguages } from '@deepseek-ai/dsh-client-test-runtime'
|
||||
import { LocaleService } from '@deepseek-ai/dsh-client-locale/client'
|
||||
import { apply, inject } from '@deepseek-ai/dsh-client-ui-workspace/client'
|
||||
|
||||
// The service reads its initial locale from the browser; these specs assert
|
||||
// the shipped Chinese copy, so they state the browser they assume.
|
||||
let restoreLanguages: () => void
|
||||
beforeEach(() => { restoreLanguages = pinBrowserLanguages('zh-CN') })
|
||||
afterEach(() => { restoreLanguages() })
|
||||
|
||||
const SID = 's1' as SessionId
|
||||
|
||||
afterEach(cleanup)
|
||||
|
||||
Reference in New Issue
Block a user