mirror of
https://github.com/qingchencloud/clawpanel.git
synced 2026-05-07 08:12:53 +08:00
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
166 lines
4.4 KiB
JavaScript
166 lines
4.4 KiB
JavaScript
/**
|
||
* 迁移脚本:将单体 JSON 语言包拆分为模块化 JS 文件
|
||
* 读取 11 种语言 JSON → 生成 src/locales/modules/*.js
|
||
*/
|
||
const fs = require('fs')
|
||
const path = require('path')
|
||
|
||
const LOCALES_DIR = path.resolve(__dirname, '../src/locales')
|
||
const MODULES_DIR = path.resolve(LOCALES_DIR, 'modules')
|
||
|
||
function readLang(file) {
|
||
const p = path.join(LOCALES_DIR, file)
|
||
return fs.existsSync(p) ? JSON.parse(fs.readFileSync(p, 'utf8')) : {}
|
||
}
|
||
|
||
const zhCN = readLang('zh-CN.json')
|
||
const en = readLang('en.json')
|
||
const zhTW = readLang('zh-TW.json')
|
||
const ja = readLang('ja.json')
|
||
const ko = readLang('ko.json')
|
||
const vi = readLang('vi.json')
|
||
const es = readLang('es.json')
|
||
const pt = readLang('pt.json')
|
||
const ru = readLang('ru.json')
|
||
const fr = readLang('fr.json')
|
||
const de = readLang('de.json')
|
||
|
||
// 模块名映射(JSON key → 文件名)
|
||
const MODULE_FILE_MAP = {
|
||
common: 'common',
|
||
sidebar: 'sidebar',
|
||
instance: 'instance',
|
||
dashboard: 'dashboard',
|
||
services: 'services',
|
||
settings: 'settings',
|
||
models: 'models',
|
||
agents: 'agents',
|
||
gateway: 'gateway',
|
||
security: 'security',
|
||
communication: 'communication',
|
||
channels: 'channels',
|
||
memory: 'memory',
|
||
cron: 'cron',
|
||
usage: 'usage',
|
||
skills: 'skills',
|
||
chat: 'chat',
|
||
chatDebug: 'chat-debug',
|
||
setup: 'setup',
|
||
about: 'about',
|
||
ext: 'ext',
|
||
logs: 'logs',
|
||
assistant: 'assistant',
|
||
toast: 'toast',
|
||
modal: 'modal',
|
||
}
|
||
|
||
// 确保输出目录存在
|
||
if (!fs.existsSync(MODULES_DIR)) {
|
||
fs.mkdirSync(MODULES_DIR, { recursive: true })
|
||
}
|
||
|
||
// 转义 JS 字符串中的特殊字符
|
||
function escapeStr(s) {
|
||
if (typeof s !== 'string') return ''
|
||
return s
|
||
.replace(/\\/g, '\\\\')
|
||
.replace(/'/g, "\\'")
|
||
.replace(/\n/g, '\\n')
|
||
.replace(/\r/g, '')
|
||
}
|
||
|
||
// 参数顺序: zhCN, en, zhTW, ja, ko, vi, es, pt, ru, fr, de
|
||
const LANG_ORDER = [
|
||
{ key: 'zhCN', fallback: null },
|
||
{ key: 'en', fallback: null },
|
||
{ key: 'zhTW', fallback: 'zhCN' },
|
||
{ key: 'ja', fallback: 'en' },
|
||
{ key: 'ko', fallback: 'en' },
|
||
{ key: 'vi', fallback: 'en' },
|
||
{ key: 'es', fallback: 'en' },
|
||
{ key: 'pt', fallback: 'en' },
|
||
{ key: 'ru', fallback: 'en' },
|
||
{ key: 'fr', fallback: 'en' },
|
||
{ key: 'de', fallback: 'en' },
|
||
]
|
||
|
||
// 生成一个模块的 JS 源码(11 语言)
|
||
function generateModule(sectionKey, sections) {
|
||
const keys = Object.keys(sections.zhCN)
|
||
const lines = []
|
||
|
||
lines.push("import { _ } from '../helper.js'")
|
||
lines.push('')
|
||
lines.push('export default {')
|
||
|
||
for (const key of keys) {
|
||
const vals = {}
|
||
for (const { key: lk } of LANG_ORDER) {
|
||
vals[lk] = (sections[lk] && sections[lk][key]) || ''
|
||
}
|
||
|
||
// 计算可省略的参数(和 fallback 相同时省略)
|
||
const params = LANG_ORDER.map(({ key: lk, fallback }) => {
|
||
if (!fallback) return vals[lk] // zhCN, en 必填
|
||
return vals[lk] === vals[fallback] ? '' : vals[lk]
|
||
})
|
||
|
||
// 从尾部截断空参数
|
||
let lastNonEmpty = 1 // 至少保留 zhCN + en
|
||
for (let i = params.length - 1; i >= 2; i--) {
|
||
if (params[i]) { lastNonEmpty = i; break }
|
||
}
|
||
|
||
const parts = params.slice(0, lastNonEmpty + 1).map(v => `'${escapeStr(v)}'`)
|
||
lines.push(` ${safeKey(key)}: _(${parts.join(', ')}),`)
|
||
}
|
||
|
||
lines.push('}')
|
||
lines.push('')
|
||
|
||
return lines.join('\n')
|
||
}
|
||
|
||
// 确保 key 是合法的 JS 标识符,否则加引号
|
||
function safeKey(key) {
|
||
if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key)) return key
|
||
return `'${key}'`
|
||
}
|
||
|
||
let totalKeys = 0
|
||
let fileCount = 0
|
||
|
||
for (const [sectionKey, fileName] of Object.entries(MODULE_FILE_MAP)) {
|
||
const zhCNSection = zhCN[sectionKey]
|
||
if (!zhCNSection) {
|
||
console.warn(`⚠ Section "${sectionKey}" not found in zh-CN.json, skipping`)
|
||
continue
|
||
}
|
||
|
||
const sections = {
|
||
zhCN: zhCNSection,
|
||
en: en[sectionKey] || {},
|
||
zhTW: zhTW[sectionKey] || {},
|
||
ja: ja[sectionKey] || {},
|
||
ko: ko[sectionKey] || {},
|
||
vi: vi[sectionKey] || {},
|
||
es: es[sectionKey] || {},
|
||
pt: pt[sectionKey] || {},
|
||
ru: ru[sectionKey] || {},
|
||
fr: fr[sectionKey] || {},
|
||
de: de[sectionKey] || {},
|
||
}
|
||
|
||
const source = generateModule(sectionKey, sections)
|
||
const outPath = path.join(MODULES_DIR, `${fileName}.js`)
|
||
fs.writeFileSync(outPath, source, 'utf8')
|
||
|
||
const keyCount = Object.keys(zhCNSection).length
|
||
totalKeys += keyCount
|
||
fileCount++
|
||
console.log(`✓ ${fileName}.js (${keyCount} keys)`)
|
||
}
|
||
|
||
console.log(`\n✓ Done: ${fileCount} module files, ${totalKeys} total keys`)
|
||
console.log(` Output: ${MODULES_DIR}`)
|