export const APP_LOCALE_COOKIE = 'beaver_locale'; export const APP_LOCALE_STORAGE_KEY = 'beaver_locale'; 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' || value === 'zh-Hant'; } export function normalizeAppLocale(value?: string | null): AppLocale { const probe = value?.trim().toLowerCase() || ''; 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'; } function readCookieLocale(): string | null { if (typeof document === 'undefined') { return null; } const match = document.cookie .split('; ') .find((item) => item.startsWith(`${APP_LOCALE_COOKIE}=`)); if (!match) { return null; } return decodeURIComponent(match.slice(APP_LOCALE_COOKIE.length + 1)); } export function readBrowserAppLocale(): AppLocale { if (typeof window === 'undefined') { return 'zh-CN'; } const fromDocument = document.documentElement.lang; if (fromDocument) { return normalizeAppLocale(fromDocument); } const fromStorage = window.localStorage.getItem(APP_LOCALE_STORAGE_KEY); if (fromStorage) { return normalizeAppLocale(fromStorage); } const fromCookie = readCookieLocale(); if (fromCookie) { return normalizeAppLocale(fromCookie); } return normalizeAppLocale(window.navigator.language); } export function persistAppLocale(locale: AppLocale): void { if (typeof window === 'undefined') { return; } document.documentElement.lang = locale; window.localStorage.setItem(APP_LOCALE_STORAGE_KEY, locale); document.cookie = `${APP_LOCALE_COOKIE}=${encodeURIComponent(locale)}; path=/; max-age=31536000; samesite=lax`; } export function getCurrentAppLocale(): AppLocale { if (typeof window === 'undefined') { return 'zh-CN'; } return readBrowserAppLocale(); } export function pickAppText(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(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 = { 个: '個', 为: '為', 么: '麼', 义: '義', 习: '習', 书: '書', 了: '了', 于: '於', 云: '雲', 产: '產', 仅: '僅', 从: '從', 仓: '倉', 仪: '儀', 们: '們', 优: '優', 会: '會', 传: '傳', 体: '體', 余: '餘', 侧: '側', 侦: '偵', 促: '促', 俩: '倆', 值: '值', 假: '假', 做: '做', 停: '停', 储: '儲', 像: '像', 儿: '兒', 先: '先', 光: '光', 关: '關', 兴: '興', 具: '具', 内: '內', 册: '冊', 写: '寫', 军: '軍', 农: '農', 况: '況', 冻: '凍', 净: '淨', 准: '準', 几: '幾', 击: '擊', 划: '劃', 则: '則', 创: '創', 初: '初', 删: '刪', 别: '別', 到: '到', 制: '製', 剂: '劑', 剩: '剩', 办: '辦', 功: '功', 加: '加', 务: '務', 动: '動', 助: '助', 势: '勢', 包: '包', 区: '區', 协: '協', 单: '單', 卖: '賣', 占: '佔', 卡: '卡', 历: '歷', 压: '壓', 厕: '廁', 厢: '廂', 县: '縣', 参: '參', 双: '雙', 发: '發', 变: '變', 叠: '疊', 号: '號', 后: '後', 向: '向', 吗: '嗎', 启: '啟', 员: '員', 命: '命', 咨: '諮', 哑: '啞', 响: '響', 唤: '喚', 问: '問', 單: '單', 喂: '餵', 器: '器', 团: '團', 园: '園', 困: '困', 图: '圖', 场: '場', 块: '塊', 坏: '壞', 址: '址', 坚: '堅', 坛: '壇', 型: '型', 垃: '垃', 域: '域', 堆: '堆', 填: '填', 增: '增', 墙: '牆', 声: '聲', 处: '處', 备: '備', 复: '復', 够: '夠', 头: '頭', 奖: '獎', 好: '好', 如: '如', 始: '始', 委: '委', 存: '存', 学: '學', 宁: '寧', 它: '它', 安: '安', 完: '完', 实: '實', 审: '審', 客: '客', 宪: '憲', 宽: '寬', 对: '對', 导: '導', 将: '將', 尔: '爾', 尝: '嘗', 层: '層', 属: '屬', 岁: '歲', 岛: '島', 州: '州', 工: '工', 币: '幣', 师: '師', 帐: '帳', 带: '帶', 帮: '幫', 干: '乾', 并: '並', 广: '廣', 庆: '慶', 库: '庫', 应: '應', 废: '廢', 开: '開', 异: '異', 弃: '棄', 张: '張', 强: '強', 归: '歸', 当: '當', 录: '錄', 彻: '徹', 径: '徑', 待: '待', 循: '循', 忆: '憶', 志: '誌', 忧: '憂', 念: '念', 态: '態', 总: '總', 恢: '恢', 息: '息', 您: '您', 情: '情', 想: '想', 意: '意', 愿: '願', 戏: '戲', 战: '戰', 户: '戶', 执: '執', 扩: '擴', 扫: '掃', 扬: '揚', 批: '批', 找: '找', 技: '技', 报: '報', 护: '護', 抽: '抽', 担: '擔', 拥: '擁', 择: '擇', 按: '按', 挥: '揮', 换: '換', 损: '損', 据: '據', 授: '授', 掉: '掉', 接: '接', 控: '控', 推: '推', 提: '提', 插: '插', 揭: '揭', 搜: '搜', 携: '攜', 摄: '攝', 摘: '摘', 播: '播', 操: '操', 支: '支', 收: '收', 改: '改', 放: '放', 效: '效', 数: '數', 文: '文', 断: '斷', 新: '新', 无: '無', 时: '時', 明: '明', 显: '顯', 智: '智', 暂: '暫', 更: '更', 替: '替', 术: '術', 机: '機', 权: '權', 条: '條', 来: '來', 极: '極', 构: '構', 标: '標', 栏: '欄', 树: '樹', 样: '樣', 核: '核', 案: '案', 档: '檔', 检: '檢', 楼: '樓', 次: '次', 款: '款', 步: '步', 残: '殘', 段: '段', 毕: '畢', 气: '氣', 汇: '匯', 汉: '漢', 没: '沒', 法: '法', 注: '註', 泄: '洩', 测: '測', 浏: '瀏', 消: '消', 涉: '涉', 涨: '漲', 润: '潤', 添: '添', 清: '清', 渠: '渠', 渲: '渲', 温: '溫', 滚: '滾', 满: '滿', 漏: '漏', 演: '演', 点: '點', 烦: '煩', 热: '熱', 然: '然', 照: '照', 爱: '愛', 父: '父', 片: '片', 版: '版', 状: '狀', 独: '獨', 环: '環', 现: '現', 理: '理', 画: '畫', 畅: '暢', 疗: '療', 登: '登', 监: '監', 盘: '盤', 码: '碼', 础: '礎', 确: '確', 碍: '礙', 礼: '禮', 离: '離', 种: '種', 称: '稱', 稳: '穩', 窗: '窗', 笔: '筆', 签: '簽', 简: '簡', 算: '算', 管: '管', 类: '類', 粘: '黏', 精: '精', 系: '系', 级: '級', 线: '線', 组: '組', 细: '細', 终: '終', 经: '經', 结: '結', 绝: '絕', 统: '統', 维: '維', 缓: '緩', 编: '編', 缩: '縮', 缺: '缺', 网: '網', 置: '置', 联: '聯', 聊: '聊', 肃: '肅', 背: '背', 能: '能', 脚: '腳', 脱: '脫', 脑: '腦', 自动: '自動', 舰: '艦', 艺: '藝', 节: '節', 范: '範', 荐: '薦', 获: '獲', 营: '營', 落: '落', 著: '著', 藏: '藏', 虑: '慮', 虚: '虛', 虽: '雖', 行: '行', 补: '補', 表: '表', 装: '裝', 规: '規', 视: '視', 觉: '覺', 览: '覽', 计: '計', 订: '訂', 认: '認', 议: '議', 讯: '訊', 记: '記', 讲: '講', 许: '許', 论: '論', 设: '設', 访: '訪', 证: '證', 评: '評', 识: '識', 诉: '訴', 试: '試', 话: '話', 详: '詳', 语: '語', 误: '誤', 请: '請', 读: '讀', 调: '調', 谈: '談', 谢: '謝', 谷: '谷', 账: '帳', 负: '負', 责: '責', 败: '敗', 货: '貨', 质: '質', 资: '資', 赃: '贓', 起: '起', 超: '超', 跃: '躍', 路: '路', 踪: '蹤', 车: '車', 轮: '輪', 软: '軟', 载: '載', 辑: '輯', 输: '輸', 边: '邊', 达: '達', 过: '過', 还: '還', 这: '這', 进: '進', 远: '遠', 连: '連', 迟: '遲', 适: '適', 选: '選', 递: '遞', 通: '通', 逻: '邏', 遗: '遺', 遥: '遙', 邀: '邀', 邮: '郵', 部: '部', 配: '配', 释: '釋', 重: '重', 针: '針', 钥: '鑰', 钟: '鐘', 钮: '鈕', 钱: '錢', 链: '鏈', 错: '錯', 键: '鍵', 镜: '鏡', 长: '長', 门: '門', 闭: '閉', 间: '間', 队: '隊', 阶: '階', 阳: '陽', 阴: '陰', 陈: '陳', 际: '際', 隐: '隱', 难: '難', 雏: '雛', 需: '需', 面: '面', 页: '頁', 项: '項', 顺: '順', 须: '須', 预: '預', 题: '題', 颜: '顏', 风: '風', 飞: '飛', 馆: '館', 验: '驗', 高: '高', 鱼: '魚', 鲜: '鮮', 鸟: '鳥', 麦: '麥', 黄: '黃', }; 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(''); }