feat: 支持多语言提示词本地化和界面优化

- 添加 prompt_locale 参数支持简体中文、繁体中文和英文提示词本地化
- 移除内置 agents 配置以简化系统架构
- 更新 ContextBuilder 使用动态提示词模板而非硬编码内容
- 在 AgentLoop、Web 接口和 AgentService 中传递 locale 参数
- 添加输出语言指令确保用户界面内容按指定语言生成
- 扩展前端 LanguageSwitcher 组件支持三种语言选项
- 优化 Header 和侧边栏组件的响应式布局和文本截断处理
- 更新测试用例验证不同语言环境下的提示词正确性
This commit is contained in:
2026-06-10 16:11:05 +08:00
parent 9cc3334ea7
commit fc9fd93c36
51 changed files with 7493 additions and 619 deletions

View File

@ -0,0 +1,32 @@
import { describe, expect, it } from 'vitest';
import { isAppLocale, normalizeAppLocale, pickAppText } from '@/lib/i18n/core';
describe('app locale normalization', () => {
it('accepts simplified Chinese, English, and traditional Chinese locales', () => {
expect(isAppLocale('zh-CN')).toBe(true);
expect(isAppLocale('en-US')).toBe(true);
expect(isAppLocale('zh-Hant')).toBe(true);
});
it('normalizes common traditional Chinese locale tags', () => {
expect(normalizeAppLocale('zh-TW')).toBe('zh-Hant');
expect(normalizeAppLocale('zh-HK')).toBe('zh-Hant');
expect(normalizeAppLocale('zh-Hant')).toBe('zh-Hant');
});
});
describe('app text picker', () => {
it('returns simplified Chinese text for zh-CN', () => {
expect(pickAppText('zh-CN', '任务状态', 'Task status')).toBe('任务状态');
});
it('returns English text for en-US', () => {
expect(pickAppText('en-US', '任务状态', 'Task status')).toBe('Task status');
});
it('returns traditional Chinese text for zh-Hant', () => {
expect(pickAppText('zh-Hant', '任务状态', 'Task status')).toBe('任務狀態');
expect(pickAppText('zh-Hant', '智能体结果', 'Agent results')).toBe('智慧體結果');
});
});

View File

@ -1,12 +1,12 @@
export const APP_LOCALE_COOKIE = 'beaver_locale';
export const APP_LOCALE_STORAGE_KEY = 'beaver_locale';
export const APP_LOCALES = ['zh-CN', 'en-US'] as const;
export const APP_LOCALES = ['zh-CN', 'en-US', 'zh-Hant'] as const;
export type AppLocale = (typeof APP_LOCALES)[number];
export function isAppLocale(value: string | null | undefined): value is AppLocale {
return value === 'zh-CN' || value === 'en-US';
return value === 'zh-CN' || value === 'en-US' || value === 'zh-Hant';
}
export function normalizeAppLocale(value?: string | null): AppLocale {
@ -14,6 +14,14 @@ export function normalizeAppLocale(value?: string | null): AppLocale {
if (probe.startsWith('en')) {
return 'en-US';
}
if (
probe === 'zh-hant' ||
probe.startsWith('zh-tw') ||
probe.startsWith('zh-hk') ||
probe.startsWith('zh-mo')
) {
return 'zh-Hant';
}
return 'zh-CN';
}
@ -71,6 +79,507 @@ export function getCurrentAppLocale(): AppLocale {
return readBrowserAppLocale();
}
export function pickAppText<T>(locale: AppLocale, zhValue: T, enValue: T): T {
return locale === 'en-US' ? enValue : zhValue;
export function pickAppText<T>(locale: string | null | undefined, zhValue: T, enValue: T): T {
const appLocale = normalizeAppLocale(locale);
if (appLocale === 'en-US') {
return enValue;
}
if (appLocale === 'zh-Hant') {
return toTraditionalValue(zhValue);
}
return zhValue;
}
function toTraditionalValue<T>(value: T): T {
return typeof value === 'string' ? (toTraditionalChinese(value) as T) : value;
}
const SIMPLIFIED_TO_TRADITIONAL_PHRASES: Array<[string, string]> = [
['智能体', '智慧體'],
['Agent Team', 'Agent Team'],
];
const SIMPLIFIED_TO_TRADITIONAL_CHARS: Record<string, string> = {
: '個',
: '為',
: '麼',
: '義',
: '習',
: '書',
: '了',
: '於',
: '雲',
: '產',
: '僅',
: '從',
: '倉',
: '儀',
: '們',
: '優',
: '會',
: '傳',
: '體',
: '餘',
: '側',
: '偵',
: '促',
: '倆',
: '值',
: '假',
: '做',
: '停',
: '儲',
: '像',
: '兒',
: '先',
: '光',
: '關',
: '興',
: '具',
: '內',
: '冊',
: '寫',
: '軍',
: '農',
: '況',
: '凍',
: '淨',
: '準',
: '幾',
: '擊',
: '劃',
: '則',
: '創',
: '初',
: '刪',
: '別',
: '到',
: '製',
: '劑',
: '剩',
: '辦',
: '功',
: '加',
: '務',
: '動',
: '助',
: '勢',
: '包',
: '區',
: '協',
: '單',
: '賣',
: '佔',
: '卡',
: '歷',
: '壓',
: '廁',
: '廂',
: '縣',
: '參',
: '雙',
: '發',
: '變',
: '疊',
: '號',
: '後',
: '向',
: '嗎',
: '啟',
: '員',
: '命',
: '諮',
: '啞',
: '響',
: '喚',
: '問',
: '單',
: '餵',
: '器',
: '團',
: '園',
: '困',
: '圖',
: '場',
: '塊',
: '壞',
: '址',
: '堅',
: '壇',
: '型',
: '垃',
: '域',
: '堆',
: '填',
: '增',
: '牆',
: '聲',
: '處',
: '備',
: '復',
: '夠',
: '頭',
: '獎',
: '好',
: '如',
: '始',
: '委',
: '存',
: '學',
: '寧',
: '它',
: '安',
: '完',
: '實',
: '審',
: '客',
: '憲',
: '寬',
: '對',
: '導',
: '將',
: '爾',
: '嘗',
: '層',
: '屬',
: '歲',
: '島',
: '州',
: '工',
: '幣',
: '師',
: '帳',
: '帶',
: '幫',
: '乾',
: '並',
广: '廣',
: '慶',
: '庫',
: '應',
: '廢',
: '開',
: '異',
: '棄',
: '張',
: '強',
: '歸',
: '當',
: '錄',
: '徹',
: '徑',
: '待',
: '循',
: '憶',
: '誌',
: '憂',
: '念',
: '態',
: '總',
: '恢',
: '息',
: '您',
: '情',
: '想',
: '意',
: '願',
: '戲',
: '戰',
: '戶',
: '執',
: '擴',
: '掃',
: '揚',
: '批',
: '找',
: '技',
: '報',
: '護',
: '抽',
: '擔',
: '擁',
: '擇',
: '按',
: '揮',
: '換',
: '損',
: '據',
: '授',
: '掉',
: '接',
: '控',
: '推',
: '提',
: '插',
: '揭',
: '搜',
: '攜',
: '攝',
: '摘',
: '播',
: '操',
: '支',
: '收',
: '改',
: '放',
: '效',
: '數',
: '文',
: '斷',
: '新',
: '無',
: '時',
: '明',
: '顯',
: '智',
: '暫',
: '更',
: '替',
: '術',
: '機',
: '權',
: '條',
: '來',
: '極',
: '構',
: '標',
: '欄',
: '樹',
: '樣',
: '核',
: '案',
: '檔',
: '檢',
: '樓',
: '次',
: '款',
: '步',
: '殘',
: '段',
: '畢',
: '氣',
: '匯',
: '漢',
: '沒',
: '法',
: '註',
: '洩',
: '測',
: '瀏',
: '消',
: '涉',
: '漲',
: '潤',
: '添',
: '清',
: '渠',
: '渲',
: '溫',
: '滾',
: '滿',
: '漏',
: '演',
: '點',
: '煩',
: '熱',
: '然',
: '照',
: '愛',
: '父',
: '片',
: '版',
: '狀',
: '獨',
: '環',
: '現',
: '理',
: '畫',
: '暢',
: '療',
: '登',
: '監',
: '盤',
: '碼',
: '礎',
: '確',
: '礙',
: '禮',
: '離',
: '種',
: '稱',
: '穩',
: '窗',
: '筆',
: '簽',
: '簡',
: '算',
: '管',
: '類',
: '黏',
: '精',
: '系',
: '級',
线: '線',
: '組',
: '細',
: '終',
: '經',
: '結',
: '絕',
: '統',
: '維',
: '緩',
: '編',
: '縮',
: '缺',
: '網',
: '置',
: '聯',
: '聊',
: '肅',
: '背',
: '能',
: '腳',
: '脫',
: '腦',
: '自動',
: '艦',
: '藝',
: '節',
: '範',
: '薦',
: '獲',
: '營',
: '落',
: '著',
: '藏',
: '慮',
: '虛',
: '雖',
: '行',
: '補',
: '表',
: '裝',
: '規',
: '視',
: '覺',
: '覽',
: '計',
: '訂',
: '認',
: '議',
: '訊',
: '記',
: '講',
: '許',
: '論',
: '設',
访: '訪',
: '證',
: '評',
: '識',
: '訴',
: '試',
: '話',
: '詳',
: '語',
: '誤',
: '請',
: '讀',
: '調',
: '談',
: '謝',
: '谷',
: '帳',
: '負',
: '責',
: '敗',
: '貨',
: '質',
: '資',
: '贓',
: '起',
: '超',
: '躍',
: '路',
: '蹤',
: '車',
: '輪',
: '軟',
: '載',
: '輯',
: '輸',
: '邊',
: '達',
: '過',
: '還',
: '這',
: '進',
: '遠',
: '連',
: '遲',
: '適',
: '選',
: '遞',
: '通',
: '邏',
: '遺',
: '遙',
: '邀',
: '郵',
: '部',
: '配',
: '釋',
: '重',
: '針',
: '鑰',
: '鐘',
: '鈕',
: '錢',
: '鏈',
: '錯',
: '鍵',
: '鏡',
: '長',
: '門',
: '閉',
: '間',
: '隊',
: '階',
: '陽',
: '陰',
: '陳',
: '際',
: '隱',
: '難',
: '雛',
: '需',
: '面',
: '頁',
: '項',
: '順',
: '須',
: '預',
: '題',
: '顏',
: '風',
: '飛',
: '館',
: '驗',
: '高',
: '魚',
: '鮮',
: '鳥',
: '麥',
: '黃',
};
export function toTraditionalChinese(value: string): string {
let converted = value;
for (const [source, target] of SIMPLIFIED_TO_TRADITIONAL_PHRASES) {
converted = converted.split(source).join(target);
}
return Array.from(converted)
.map((char) => SIMPLIFIED_TO_TRADITIONAL_CHARS[char] ?? char)
.join('');
}