mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-06-03 06:40:10 +08:00
feat: multi-OpenClaw CLI detection/binding + i18n infrastructure
Multi-OpenClaw Detection & Binding: - Add resolve_openclaw_cli_path() and classify_cli_source() in utils.rs - Support openclawCliPath binding in clawpanel.json (user selects CLI) - VersionInfo now includes cli_path, cli_source, all_installations - scan_all_installations() detects all OpenClaw installs on system - Dashboard shows CLI source label + multi-install warning - Settings page: CLI binding UI with auto-detect and manual selection - dev-api.js synced with cli_path/cli_source fields for Web mode i18n Infrastructure: - Create src/lib/i18n.js core module (t(), setLang(), initI18n()) - Create src/locales/zh-CN.json and src/locales/en.json - Sidebar fully i18n-ized (nav labels, sections, instance switcher) - Dashboard stat cards fully i18n-ized - Settings page: language switcher UI (live reload) - initI18n() called in main.js on startup
This commit is contained in:
89
src/lib/i18n.js
Normal file
89
src/lib/i18n.js
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* i18n 国际化核心模块
|
||||
* 支持中文(zh-CN)和英文(en),按需扩展
|
||||
*/
|
||||
import zhCN from '../locales/zh-CN.json'
|
||||
import en from '../locales/en.json'
|
||||
|
||||
const LANGS = { 'zh-CN': zhCN, en }
|
||||
const LANG_KEY = 'clawpanel_lang'
|
||||
const FALLBACK = 'zh-CN'
|
||||
|
||||
let _lang = FALLBACK
|
||||
let _dict = zhCN
|
||||
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(zhCN, 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: 'en', label: 'English' },
|
||||
]
|
||||
}
|
||||
|
||||
/** 切换语言 */
|
||||
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.startsWith('zh')) {
|
||||
_lang = 'zh-CN'
|
||||
} else if (nav.startsWith('en')) {
|
||||
_lang = 'en'
|
||||
}
|
||||
_dict = LANGS[_lang] || zhCN
|
||||
}
|
||||
Reference in New Issue
Block a user