feat: implement task management store with IPC integration
- Added a new task store in `src-react/stores/task.ts` to manage tasks and their statuses. - Implemented functions for creating, executing, and retrying tasks, along with handling task progress and completion. - Introduced persistence for tasks using IPC. - Created utility functions for normalizing room types and building subtasks. - Added a new CSS file for global styles in `src-react/styles.css`. - Created runtime types in `src-react/types/runtime.ts` and exported them. - Updated the main entry points for Vue and React applications to support dynamic framework loading. - Refactored chat model interfaces and utility functions into `src/shared/chat-model.ts`. - Updated TypeScript configuration to include paths for React components and types. - Enhanced Vite configuration to support both Vue and React frameworks.
This commit is contained in:
15
src-react/i18n/constants.ts
Normal file
15
src-react/i18n/constants.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import type { LanguageCode } from '../types/runtime';
|
||||
|
||||
export const SUPPORTED_LANGUAGE_CODES = ['en', 'zh', 'ja'] as const satisfies readonly LanguageCode[];
|
||||
|
||||
export type { LanguageCode };
|
||||
|
||||
export const SUPPORTED_LANGUAGES = [
|
||||
{ code: 'en', label: 'English' },
|
||||
{ code: 'zh', label: '中文' },
|
||||
{ code: 'ja', label: '日本語' },
|
||||
] as const;
|
||||
|
||||
export type Namespace = 'common' | 'conversation' | 'setting' | 'window';
|
||||
|
||||
export const NAMESPACES = ['common', 'conversation', 'setting', 'window'] as const satisfies readonly Namespace[];
|
||||
107
src-react/i18n/index.ts
Normal file
107
src-react/i18n/index.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { DEFAULT_LANGUAGE } from '../lib/constants';
|
||||
import type { LanguageCode } from '../types/runtime';
|
||||
import type { Namespace } from './constants';
|
||||
import { detectSystemLanguage, resolveSupportedLanguage } from './resolver';
|
||||
import { messages, type MessageTree } from './messages';
|
||||
|
||||
type InterpolationParams = Record<string, string | number>;
|
||||
|
||||
function lookupMessage(source: MessageTree, path: string): unknown {
|
||||
return path.split('.').reduce<unknown>((current, segment) => {
|
||||
if (!current || typeof current !== 'object') return undefined;
|
||||
return (current as Record<string, unknown>)[segment];
|
||||
}, source);
|
||||
}
|
||||
|
||||
function interpolate(template: string, params?: InterpolationParams): string {
|
||||
if (!params) return template;
|
||||
|
||||
return template.replace(/\{(\w+)\}/g, (_match, token) => {
|
||||
const value = params[token];
|
||||
return typeof value === 'undefined' ? `{${token}}` : String(value);
|
||||
});
|
||||
}
|
||||
|
||||
export interface I18nBridge {
|
||||
getLocale(): LanguageCode;
|
||||
setLocale(locale: string | null | undefined): LanguageCode;
|
||||
t(path: string, params?: InterpolationParams): string;
|
||||
has(path: string): boolean;
|
||||
getMessages(locale?: LanguageCode): MessageTree;
|
||||
subscribe(listener: (locale: LanguageCode) => void): () => void;
|
||||
}
|
||||
|
||||
export function createI18nBridge(initialLocale?: string | null): I18nBridge {
|
||||
let locale = resolveSupportedLanguage(initialLocale ?? detectSystemLanguage() ?? DEFAULT_LANGUAGE);
|
||||
const listeners = new Set<(locale: LanguageCode) => void>();
|
||||
|
||||
const notify = () => {
|
||||
for (const listener of listeners) {
|
||||
listener(locale);
|
||||
}
|
||||
};
|
||||
|
||||
const api: I18nBridge = {
|
||||
getLocale() {
|
||||
return locale;
|
||||
},
|
||||
setLocale(nextLocale: string | null | undefined) {
|
||||
const resolved = resolveSupportedLanguage(nextLocale, locale);
|
||||
if (resolved !== locale) {
|
||||
locale = resolved;
|
||||
notify();
|
||||
}
|
||||
return locale;
|
||||
},
|
||||
t(path: string, params?: InterpolationParams) {
|
||||
const localeMessages = messages[locale] ?? messages[DEFAULT_LANGUAGE];
|
||||
const fallbackMessages = messages[DEFAULT_LANGUAGE];
|
||||
const translated = lookupMessage(localeMessages, path) ?? lookupMessage(fallbackMessages, path);
|
||||
|
||||
if (typeof translated === 'string' || typeof translated === 'number') {
|
||||
return interpolate(String(translated), params);
|
||||
}
|
||||
|
||||
return path;
|
||||
},
|
||||
has(path: string) {
|
||||
const localeMessages = messages[locale] ?? messages[DEFAULT_LANGUAGE];
|
||||
const fallbackMessages = messages[DEFAULT_LANGUAGE];
|
||||
return typeof lookupMessage(localeMessages, path) !== 'undefined' || typeof lookupMessage(fallbackMessages, path) !== 'undefined';
|
||||
},
|
||||
getMessages(targetLocale?: LanguageCode) {
|
||||
return messages[targetLocale ?? locale] ?? messages[DEFAULT_LANGUAGE];
|
||||
},
|
||||
subscribe(listener: (locale: LanguageCode) => void) {
|
||||
listeners.add(listener);
|
||||
return () => listeners.delete(listener);
|
||||
},
|
||||
};
|
||||
|
||||
return api;
|
||||
}
|
||||
|
||||
export const i18n = createI18nBridge();
|
||||
|
||||
export function setLocale(locale: string | null | undefined): LanguageCode {
|
||||
return i18n.setLocale(locale);
|
||||
}
|
||||
|
||||
export function getLocale(): LanguageCode {
|
||||
return i18n.getLocale();
|
||||
}
|
||||
|
||||
export function t(path: string, params?: InterpolationParams): string {
|
||||
return i18n.t(path, params);
|
||||
}
|
||||
|
||||
export function hasMessage(path: string): boolean {
|
||||
return i18n.has(path);
|
||||
}
|
||||
|
||||
export function getMessages(locale?: LanguageCode): MessageTree {
|
||||
return i18n.getMessages(locale);
|
||||
}
|
||||
|
||||
export { SUPPORTED_LANGUAGE_CODES, SUPPORTED_LANGUAGES, NAMESPACES } from './constants';
|
||||
export type { LanguageCode, Namespace };
|
||||
112
src-react/i18n/messages.ts
Normal file
112
src-react/i18n/messages.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import type { LanguageCode } from '../types/runtime';
|
||||
|
||||
export interface MessageTree {
|
||||
[key: string]: string | number | MessageTree;
|
||||
}
|
||||
|
||||
export type I18nMessages = Record<LanguageCode, MessageTree>;
|
||||
|
||||
export const messages: I18nMessages = {
|
||||
en: {
|
||||
app: {
|
||||
title: 'ZN-AI',
|
||||
},
|
||||
window: {
|
||||
minimize: 'Minimize',
|
||||
maximize: 'Maximize',
|
||||
restore: 'Restore',
|
||||
close: 'Close',
|
||||
},
|
||||
dialog: {
|
||||
cancel: 'Cancel',
|
||||
confirm: 'Confirm',
|
||||
},
|
||||
theme: {
|
||||
light: 'Light',
|
||||
dark: 'Dark',
|
||||
system: 'System',
|
||||
},
|
||||
language: {
|
||||
zh: 'Chinese',
|
||||
en: 'English',
|
||||
ja: 'Japanese',
|
||||
},
|
||||
common: {
|
||||
loading: 'Loading...',
|
||||
retry: 'Retry',
|
||||
unknownError: 'Unknown error',
|
||||
},
|
||||
conversation: {
|
||||
newConversation: 'New conversation',
|
||||
emptyState: 'No messages yet',
|
||||
},
|
||||
},
|
||||
zh: {
|
||||
app: {
|
||||
title: 'ZN-AI',
|
||||
},
|
||||
window: {
|
||||
minimize: '最小化',
|
||||
maximize: '最大化',
|
||||
restore: '还原',
|
||||
close: '关闭',
|
||||
},
|
||||
dialog: {
|
||||
cancel: '取消',
|
||||
confirm: '确认',
|
||||
},
|
||||
theme: {
|
||||
light: '浅色',
|
||||
dark: '深色',
|
||||
system: '跟随系统',
|
||||
},
|
||||
language: {
|
||||
zh: '中文',
|
||||
en: '英文',
|
||||
ja: '日语',
|
||||
},
|
||||
common: {
|
||||
loading: '加载中...',
|
||||
retry: '重试',
|
||||
unknownError: '未知错误',
|
||||
},
|
||||
conversation: {
|
||||
newConversation: '新建对话',
|
||||
emptyState: '暂无消息',
|
||||
},
|
||||
},
|
||||
ja: {
|
||||
app: {
|
||||
title: 'ZN-AI',
|
||||
},
|
||||
window: {
|
||||
minimize: '最小化',
|
||||
maximize: '最大化',
|
||||
restore: '復元',
|
||||
close: '閉じる',
|
||||
},
|
||||
dialog: {
|
||||
cancel: 'キャンセル',
|
||||
confirm: '確認',
|
||||
},
|
||||
theme: {
|
||||
light: 'ライト',
|
||||
dark: 'ダーク',
|
||||
system: 'システムに従う',
|
||||
},
|
||||
language: {
|
||||
zh: '中国語',
|
||||
en: '英語',
|
||||
ja: '日本語',
|
||||
},
|
||||
common: {
|
||||
loading: '読み込み中...',
|
||||
retry: '再試行',
|
||||
unknownError: '不明なエラー',
|
||||
},
|
||||
conversation: {
|
||||
newConversation: '新しい会話',
|
||||
emptyState: 'メッセージはまだありません',
|
||||
},
|
||||
},
|
||||
};
|
||||
24
src-react/i18n/resolver.ts
Normal file
24
src-react/i18n/resolver.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { SUPPORTED_LANGUAGE_CODES } from './constants';
|
||||
import type { LanguageCode } from '../types/runtime';
|
||||
|
||||
const SUPPORTED_LANGUAGE_SET = new Set<string>(SUPPORTED_LANGUAGE_CODES);
|
||||
|
||||
export function normalizeLocale(locale: string | null | undefined): string {
|
||||
return locale?.trim().toLowerCase().split('_').join('-') ?? '';
|
||||
}
|
||||
|
||||
export function resolveSupportedLanguage(
|
||||
locale: string | null | undefined,
|
||||
fallback: LanguageCode = 'zh',
|
||||
): LanguageCode {
|
||||
const normalizedLocale = normalizeLocale(locale);
|
||||
if (!normalizedLocale) return fallback;
|
||||
|
||||
const [baseLanguage] = normalizedLocale.split('-');
|
||||
return SUPPORTED_LANGUAGE_SET.has(baseLanguage) ? (baseLanguage as LanguageCode) : fallback;
|
||||
}
|
||||
|
||||
export function detectSystemLanguage(): LanguageCode {
|
||||
if (typeof navigator === 'undefined') return 'zh';
|
||||
return resolveSupportedLanguage(navigator.language);
|
||||
}
|
||||
Reference in New Issue
Block a user