mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-06-26 10:11:36 +08:00
集中发版: 新功能(10) - 心甜Claw 引擎入口(第 3 个引擎模式) - Hermes 22 个 Provider 注册表 + 安装/仪表盘动态加载 - Hermes .env 高级编辑(拒绝触碰托管 Provider 密钥) - Hermes 会话与用量分析增强 - Hermes Dashboard 自动拉起 + Windows POSIX-only 兼容模态 - Hermes Skills 工具集面板 - 官网 Hermes Agent 黑金特色区 + 图文指南 - Boot Manifest 启动页(双语 + 错峰动画) - 官网 Markdown 阅读器图片 lightbox - Hermes Memory 概览卡 改进(9) - Hermes 仪表盘/扩展页全面本地化 - 记忆编辑大尺寸模态 - 日志下载 Web/桌面分流 - 侧边栏导航补全 - 模型备选管理 UI(PR #232) - 模型加载错误 UX 重做(错误卡 + 详情 + 重试) - .page 布局 clamp + .page-narrow - Memory 单列断点提早到 1100px - Web 模式跳过前端热更新检查 修复(12) - Gateway 启动 platforms.api_server.enabled 自修复(含 7 unit test) - Memory 页 overview 卡穿模(旧 flex 列约束 → 自然块流) - Skills 页 hero/toolsets 被压缩(flex-shrink:0) - Web 模式 Skills ReferenceError(补 _readHermesDisabledSkills) - 日志/记忆下载行为分流 - src/pages/models.js 5 处 typo - 删除 56 行 .hm-memory-* 死代码 + line-clamp 标准属性 - Dependabot rustls-webpki / postcss / rand
124 lines
3.3 KiB
JavaScript
124 lines
3.3 KiB
JavaScript
/**
|
||
* i18n 国际化核心模块
|
||
* 模块化多语言架构,支持 zh-CN / en / zh-TW / ja / ko
|
||
*/
|
||
import { buildLocales } from '../locales/index.js'
|
||
|
||
const LANGS = buildLocales()
|
||
const LANG_KEY = 'clawpanel_lang'
|
||
const FALLBACK = 'zh-CN'
|
||
|
||
let _lang = FALLBACK
|
||
let _dict = LANGS[FALLBACK]
|
||
let _listeners = []
|
||
|
||
/**
|
||
* 翻译函数
|
||
* @param {string} key - 点分隔路径,如 'sidebar.dashboard'
|
||
* @param {object} [params] - 插值参数,如 { count: 3 } 替换 {count}
|
||
* @returns {string}
|
||
*/
|
||
export function t(key, params) {
|
||
let val = _resolve(_dict, key)
|
||
if (val === undefined) {
|
||
// fallback 到中文
|
||
val = _resolve(LANGS[FALLBACK], key)
|
||
}
|
||
if (val === undefined) return key
|
||
if (params) {
|
||
for (const [k, v] of Object.entries(params)) {
|
||
val = val.replace(new RegExp(`\\{${k}\\}`, 'g'), String(v))
|
||
}
|
||
}
|
||
return val
|
||
}
|
||
|
||
function _resolve(obj, path) {
|
||
const parts = path.split('.')
|
||
let cur = obj
|
||
for (const p of parts) {
|
||
if (cur == null || typeof cur !== 'object') return undefined
|
||
cur = cur[p]
|
||
}
|
||
return typeof cur === 'string' ? cur : undefined
|
||
}
|
||
|
||
/** 获取当前语言 */
|
||
export function getLang() { return _lang }
|
||
|
||
/** 获取所有可用语言 */
|
||
export function getAvailableLangs() {
|
||
return [
|
||
{ code: 'zh-CN', label: '简体中文' },
|
||
{ code: 'zh-TW', label: '繁體中文' },
|
||
{ code: 'en', label: 'English' },
|
||
{ code: 'ja', label: '日本語' },
|
||
{ code: 'ko', label: '한국어' },
|
||
{ code: 'vi', label: 'Tiếng Việt' },
|
||
{ code: 'es', label: 'Español' },
|
||
{ code: 'pt', label: 'Português' },
|
||
{ code: 'ru', label: 'Русский' },
|
||
{ code: 'fr', label: 'Français' },
|
||
{ code: 'de', label: 'Deutsch' },
|
||
]
|
||
}
|
||
|
||
/** 切换语言 */
|
||
export function setLang(lang) {
|
||
if (!LANGS[lang]) return
|
||
_lang = lang
|
||
_dict = LANGS[lang]
|
||
localStorage.setItem(LANG_KEY, lang)
|
||
_listeners.forEach(fn => { try { fn(lang) } catch {} })
|
||
}
|
||
|
||
/** 监听语言变化 */
|
||
export function onLangChange(fn) {
|
||
_listeners.push(fn)
|
||
return () => { _listeners = _listeners.filter(cb => cb !== fn) }
|
||
}
|
||
|
||
/** 初始化:localStorage > navigator.language > fallback */
|
||
export function initI18n() {
|
||
const saved = localStorage.getItem(LANG_KEY)
|
||
if (saved && LANGS[saved]) {
|
||
_lang = saved
|
||
_dict = LANGS[saved]
|
||
return
|
||
}
|
||
// 自动检测浏览器语言
|
||
const nav = navigator.language || navigator.languages?.[0] || ''
|
||
if (nav === 'zh-TW' || nav === 'zh-HK') {
|
||
_lang = 'zh-TW'
|
||
} else if (nav.startsWith('zh')) {
|
||
_lang = 'zh-CN'
|
||
} else if (nav.startsWith('ja')) {
|
||
_lang = 'ja'
|
||
} else if (nav.startsWith('ko')) {
|
||
_lang = 'ko'
|
||
} else if (nav.startsWith('vi')) {
|
||
_lang = 'vi'
|
||
} else if (nav.startsWith('es')) {
|
||
_lang = 'es'
|
||
} else if (nav.startsWith('pt')) {
|
||
_lang = 'pt'
|
||
} else if (nav.startsWith('ru')) {
|
||
_lang = 'ru'
|
||
} else if (nav.startsWith('fr')) {
|
||
_lang = 'fr'
|
||
} else if (nav.startsWith('de')) {
|
||
_lang = 'de'
|
||
} else if (nav.startsWith('en')) {
|
||
_lang = 'en'
|
||
}
|
||
_dict = LANGS[_lang] || LANGS[FALLBACK]
|
||
|
||
// 桥接 splash 启动屏的语言切换:splash 在 dispatch 'clawpanel-lang-change' 后,应用同步切换
|
||
if (typeof window !== 'undefined') {
|
||
window.addEventListener('clawpanel-lang-change', (e) => {
|
||
const next = e?.detail
|
||
if (next && LANGS[next] && next !== _lang) setLang(next)
|
||
})
|
||
}
|
||
}
|