Files
clawpanel/scripts/split-locales.cjs
晴天 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

166 lines
4.4 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.
/**
* 迁移脚本:将单体 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}`)