Files
clawpanel/src/lib/i18n.js
晴天 9ee99ead24 chore: release v0.14.0
集中发版:

新功能(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
2026-04-25 23:47:22 +08:00

124 lines
3.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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)
})
}
}