Files
clawpanel/src/lib/i18n.js
晴天 985d263dc6 feat: i18n 11 languages + website update + fix #139 #140 #141
i18n:
- Add 9 new locale files (ja/ko/de/es/fr/pt/ru/vi/zh-TW)
- Add multilingual README files for all 11 languages
- Add locale helper, index, and modular translation system
- Add translation generation scripts

Website (docs/index.html):
- Replace 公益AI接口 branding with 晴辰云AI接口
- Remove OpenClaw 独立安装包 promotion block
- Update SEO meta tags (description, keywords, OG, Twitter, JSON-LD)
- Add 11-language README links to footer
- Update 元宝派 link to new URL

Bug fixes:
- fix(cron): delivery format mode:'push' → mode:'announce', remove invalid 'to' field (fixes #141)
- fix(cron): allow single-channel users to select delivery channel
- fix(cron): preserve delivery field in job state for editing
- fix(models): add 'ollama' as recognized API type, prevent overwriting native ollama config (fixes #140)
- fix(models): skip /v1 append for ollama native API baseUrl
- fix(assistant): normalize 'google-generative-ai' consistently, add ollama hints
- fix(version): use CLI path classification for source detection on Windows (fixes #139)
- fix(version): default to 'official' instead of 'chinese' when source unknown
- fix(version): reorder npm global package check based on active CLI
2026-03-24 22:31:11 +08:00

116 lines
3.0 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]
}