diff --git a/apps/clients/web/src/adapters/browserThemePreferences.test.ts b/apps/clients/web/src/adapters/browserThemePreferences.test.ts index a99c5069..cf5707c5 100644 --- a/apps/clients/web/src/adapters/browserThemePreferences.test.ts +++ b/apps/clients/web/src/adapters/browserThemePreferences.test.ts @@ -1,14 +1,17 @@ import { describe, expect, it, vi } from 'vitest' -import { THEME_STORAGE_KEY } from '@termflow/client-ui' +import { LOCALE_STORAGE_KEY, setLocale, THEME_STORAGE_KEY } from '@termflow/client-ui' import { createBrowserThemePreferences, createBrowserThemeTarget } from './browserThemePreferences' describe('browser theme adapters', () => { - it('persists only a validated theme identifier', () => { + it('persists only a validated theme identifier and wires locale preferences', () => { const storage = { getItem: vi.fn(() => 'cloud-cobalt'), setItem: vi.fn() } const preferences = createBrowserThemePreferences(storage) expect(preferences.load()).toBe('cloud-cobalt') preferences.save('midnight-indigo') expect(storage.setItem).toHaveBeenCalledWith(THEME_STORAGE_KEY, 'midnight-indigo') + setLocale('en') + expect(storage.setItem).toHaveBeenCalledWith(LOCALE_STORAGE_KEY, 'en') + setLocale('zh') }) it('rejects unknown stored values and applies through the injected root element', () => { diff --git a/apps/clients/web/src/adapters/browserThemePreferences.ts b/apps/clients/web/src/adapters/browserThemePreferences.ts index f4171006..2ff2ef8f 100644 --- a/apps/clients/web/src/adapters/browserThemePreferences.ts +++ b/apps/clients/web/src/adapters/browserThemePreferences.ts @@ -1,6 +1,9 @@ import { + configureLocalePreferences, isThemeId, + LOCALE_STORAGE_KEY, THEME_STORAGE_KEY, + type Locale, type ThemeId, type ThemePreferences, type ThemeTarget, @@ -10,6 +13,15 @@ type ThemeStorage = Pick type ThemeRoot = Pick export function createBrowserThemePreferences(storage: ThemeStorage = globalThis.localStorage): ThemePreferences { + configureLocalePreferences({ + load() { + const v = storage.getItem(LOCALE_STORAGE_KEY) + return v === 'en' || v === 'zh' ? v : null + }, + save(loc: Locale) { + storage.setItem(LOCALE_STORAGE_KEY, loc) + }, + }) return { load() { const stored = storage.getItem(THEME_STORAGE_KEY) diff --git a/docs/assets/demo_termflow.gif b/docs/assets/demo_termflow.gif index e1b28f74..de80ebb5 100644 Binary files a/docs/assets/demo_termflow.gif and b/docs/assets/demo_termflow.gif differ diff --git a/packages/client-core/src/http/apiError.ts b/packages/client-core/src/http/apiError.ts index 97a56159..b465cffc 100644 --- a/packages/client-core/src/http/apiError.ts +++ b/packages/client-core/src/http/apiError.ts @@ -20,16 +20,20 @@ const safeMessagesEn: Record = { http_capability_denied: 'Client network capability configuration is invalid. Please update or reinstall TermFlow.', } -function isEnglishLocale(): boolean { - try { - return typeof localStorage !== 'undefined' && typeof localStorage.getItem === 'function' && localStorage.getItem('termflow_lang') === 'en' - } catch { - return false - } +export type ApiErrorLocale = 'zh' | 'en' + +let currentApiErrorLocale: ApiErrorLocale = 'zh' + +export function setApiErrorLocale(locale: ApiErrorLocale): void { + currentApiErrorLocale = locale +} + +export function getApiErrorLocale(): ApiErrorLocale { + return currentApiErrorLocale } export function safeMessageFor(kind: ApiErrorKind): string { - return isEnglishLocale() ? safeMessagesEn[kind] : safeMessagesZh[kind] + return currentApiErrorLocale === 'en' ? safeMessagesEn[kind] : safeMessagesZh[kind] } export interface ApiErrorDetails { diff --git a/packages/client-ui/src/components/agent/AgentChatSession.vue b/packages/client-ui/src/components/agent/AgentChatSession.vue index 5a17d636..8b6a0503 100644 --- a/packages/client-ui/src/components/agent/AgentChatSession.vue +++ b/packages/client-ui/src/components/agent/AgentChatSession.vue @@ -226,7 +226,7 @@ const bindingLabel = computed(() => { const binding = detail.value?.binding return binding === undefined ? t('agent.conversations.defaultTitle', { id: props.conversationId.slice(0, 8) }) - : `Binding ${binding.binding_id} · Term ${binding.term_id}` + : t('agent.chat.bindingLabel', { bindingId: binding.binding_id, termId: binding.term_id }) }) /** diff --git a/packages/client-ui/src/components/computers/ComputerNameEditor.vue b/packages/client-ui/src/components/computers/ComputerNameEditor.vue index c319ff3d..55962d9b 100644 --- a/packages/client-ui/src/components/computers/ComputerNameEditor.vue +++ b/packages/client-ui/src/components/computers/ComputerNameEditor.vue @@ -12,12 +12,12 @@ diff --git a/packages/client-ui/src/components/computers/EnrollmentDialog.vue b/packages/client-ui/src/components/computers/EnrollmentDialog.vue index 4b4f59a6..2053080f 100644 --- a/packages/client-ui/src/components/computers/EnrollmentDialog.vue +++ b/packages/client-ui/src/components/computers/EnrollmentDialog.vue @@ -38,11 +38,11 @@ diff --git a/packages/client-ui/src/views/TerminalView.vue b/packages/client-ui/src/views/TerminalView.vue index eeb294f6..f9284ba4 100644 --- a/packages/client-ui/src/views/TerminalView.vue +++ b/packages/client-ui/src/views/TerminalView.vue @@ -18,7 +18,7 @@