refactor(i18n): standardize i18n script names and update base locale

- Rename i18n-related scripts to follow consistent naming pattern (i18n:check, i18n:sync, i18n:auto)
- Change default base locale from zh-cn to en-us
- Remove unused update-i18n.ts script
- Update documentation and CI workflow to reflect script name changes
This commit is contained in:
icarus 2025-10-23 12:25:03 +08:00
parent 9c43bb07c0
commit 16a69e240b
6 changed files with 8 additions and 156 deletions

View File

@ -58,7 +58,7 @@ jobs:
run: yarn typecheck
- name: i18n Check
run: yarn check:i18n
run: yarn i18n:check
- name: Test
run: yarn test

View File

@ -19,7 +19,7 @@ This file provides guidance to AI coding assistants when working with code in th
- **Development**: `yarn dev` - Runs Electron app in development mode with hot reload
- **Debug**: `yarn debug` - Starts with debugging enabled, use `chrome://inspect` to attach debugger
- **Build Check**: `yarn build:check` - **REQUIRED** before commits (lint + test + typecheck)
- If having i18n sort issues, run `yarn sync:i18n` first to sync template
- If having i18n sort issues, run `yarn i18n:sync` first to sync template
- If having formatting issues, run `yarn format` first
- **Test**: `yarn test` - Run all tests (Vitest) across main and renderer processes
- **Single Test**:

View File

@ -54,10 +54,9 @@
"typecheck:node": "tsgo --noEmit -p tsconfig.node.json --composite false",
"typecheck:web": "tsgo --noEmit -p tsconfig.web.json --composite false",
"typecheck:ui": "cd packages/ui && npm run type-check",
"check:i18n": "dotenv -e .env -- tsx scripts/check-i18n.ts",
"sync:i18n": "dotenv -e .env -- tsx scripts/sync-i18n.ts",
"update:i18n": "dotenv -e .env -- tsx scripts/update-i18n.ts",
"auto:i18n": "dotenv -e .env -- tsx scripts/auto-translate-i18n.ts",
"i18n:check": "dotenv -e .env -- tsx scripts/check-i18n.ts",
"i18n:sync": "dotenv -e .env -- tsx scripts/sync-i18n.ts",
"i18n:auto": "dotenv -e .env -- tsx scripts/auto-translate-i18n.ts",
"update:languages": "tsx scripts/update-languages.ts",
"test": "vitest run --silent",
"test:main": "vitest run --project main",
@ -69,7 +68,7 @@
"test:e2e": "yarn playwright test",
"test:lint": "oxlint --deny-warnings && eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --cache",
"test:scripts": "vitest scripts",
"lint": "oxlint --fix && eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --cache && biome lint --write && biome format --write && yarn typecheck && yarn check:i18n && yarn format:check",
"lint": "oxlint --fix && eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --cache && biome lint --write && biome format --write && yarn typecheck && yarn i18n:check && yarn format:check",
"lint:ox": "oxlint --fix && biome lint --write && biome format --write",
"format": "biome format --write && biome lint --write",
"format:check": "biome format && biome lint",

View File

@ -9,7 +9,7 @@ import * as path from 'path'
const localesDir = path.join(__dirname, '../src/renderer/src/i18n/locales')
const translateDir = path.join(__dirname, '../src/renderer/src/i18n/translate')
const baseLocale = process.env.BASE_LOCALE ?? 'zh-cn'
const baseLocale = process.env.BASE_LOCALE ?? 'en-us'
const baseFileName = `${baseLocale}.json`
const baseLocalePath = path.join(__dirname, '../src/renderer/src/i18n/locales', baseFileName)

View File

@ -4,7 +4,7 @@ import * as path from 'path'
import { sortedObjectByKeys } from './sort'
const translationsDir = path.join(__dirname, '../src/renderer/src/i18n/locales')
const baseLocale = process.env.BASE_LOCALE ?? 'zh-cn'
const baseLocale = process.env.BASE_LOCALE ?? 'en-us'
const baseFileName = `${baseLocale}.json`
const baseFilePath = path.join(translationsDir, baseFileName)

View File

@ -1,147 +0,0 @@
/**
* 使 OpenAI i18n translate
*
* API_KEY=sk-xxxx BASE_URL=xxxx MODEL=xxxx ts-node scripts/update-i18n.ts
*/
import OpenAI from '@cherrystudio/openai'
import cliProgress from 'cli-progress'
import fs from 'fs'
type I18NValue = string | { [key: string]: I18NValue }
type I18N = { [key: string]: I18NValue }
const API_KEY = process.env.API_KEY
const BASE_URL = process.env.BASE_URL || 'https://dashscope.aliyuncs.com/compatible-mode/v1/'
const MODEL = process.env.MODEL || 'qwen-plus-latest'
const INDEX = [
// 语言的名称代码用来翻译的模型
{ name: 'France', code: 'fr-fr', model: MODEL },
{ name: 'Spanish', code: 'es-es', model: MODEL },
{ name: 'Portuguese', code: 'pt-pt', model: MODEL },
{ name: 'Greek', code: 'el-gr', model: MODEL }
]
const zh = JSON.parse(fs.readFileSync('src/renderer/src/i18n/locales/zh-cn.json', 'utf8')) as I18N
const openai = new OpenAI({
apiKey: API_KEY,
baseURL: BASE_URL
})
// 递归遍历翻译
async function translate(baseObj: I18N, targetObj: I18N, targetLang: string, model: string, updateFile) {
const toTranslateTexts: { [key: string]: string } = {}
for (const key in baseObj) {
if (typeof baseObj[key] == 'object') {
// 遍历下一层
if (!targetObj[key] || typeof targetObj[key] != 'object') targetObj[key] = {}
await translate(baseObj[key], targetObj[key], targetLang, model, updateFile)
} else if (
!targetObj[key] ||
typeof targetObj[key] != 'string' ||
(typeof targetObj[key] === 'string' && targetObj[key].startsWith('[to be translated]'))
) {
// 加入到本层待翻译列表
toTranslateTexts[key] = baseObj[key]
}
}
if (Object.keys(toTranslateTexts).length > 0) {
const completion = await openai.chat.completions.create({
model: model,
response_format: { type: 'json_object' },
messages: [
{
role: 'user',
content: `
You are a robot specifically designed for translation tasks. As a model that has been extensively fine-tuned on Russian language corpora, you are proficient in using the Russian language.
Now, please output the translation based on the input content. The input will include both Chinese and English key values, and you should output the corresponding key values in the Russian language.
When translating, ensure that no key value is omitted, and maintain the accuracy and fluency of the translation. Pay attention to the capitalization rules in the output to match the source text, and especially pay attention to whether to capitalize the first letter of each word except for prepositions. For strings containing \`{{value}}\`, ensure that the format is not disrupted.
Output in JSON.
######################################################
INPUT
######################################################
${JSON.stringify({
confirm: '确定要备份数据吗?',
select_model: '选择模型',
title: '文件',
deeply_thought: '已深度思考(用时 {{seconds}} 秒)'
})}
######################################################
MAKE SURE TO OUTPUT IN Russian. DO NOT OUTPUT IN UNSPECIFIED LANGUAGE.
######################################################
`
},
{
role: 'assistant',
content: JSON.stringify({
confirm: 'Подтвердите резервное копирование данных?',
select_model: 'Выберите Модель',
title: 'Файл',
deeply_thought: 'Глубоко продумано (заняло {{seconds}} секунд)'
})
},
{
role: 'user',
content: `
You are a robot specifically designed for translation tasks. As a model that has been extensively fine-tuned on ${targetLang} language corpora, you are proficient in using the ${targetLang} language.
Now, please output the translation based on the input content. The input will include both Chinese and English key values, and you should output the corresponding key values in the ${targetLang} language.
When translating, ensure that no key value is omitted, and maintain the accuracy and fluency of the translation. Pay attention to the capitalization rules in the output to match the source text, and especially pay attention to whether to capitalize the first letter of each word except for prepositions. For strings containing \`{{value}}\`, ensure that the format is not disrupted.
Output in JSON.
######################################################
INPUT
######################################################
${JSON.stringify(toTranslateTexts)}
######################################################
MAKE SURE TO OUTPUT IN ${targetLang}. DO NOT OUTPUT IN UNSPECIFIED LANGUAGE.
######################################################
`
}
]
})
// 添加翻译后的键值,并打印错译漏译内容
try {
const result = JSON.parse(completion.choices[0].message.content!)
// console.debug('result', result)
for (const e in toTranslateTexts) {
if (result[e] && typeof result[e] === 'string') {
targetObj[e] = result[e]
} else {
console.warn(`missing value "${e}" in ${targetLang} translation`)
}
}
} catch (e) {
console.error(e)
for (const e in toTranslateTexts) {
console.warn(`missing value "${e}" in ${targetLang} translation`)
}
}
}
// 删除多余的键值
for (const e in targetObj) {
if (!baseObj[e]) {
delete targetObj[e]
}
}
// 更新文件
updateFile()
}
let count = 0
;(async () => {
const bar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic)
bar.start(INDEX.length, 0)
for (const { name, code, model } of INDEX) {
const obj = fs.existsSync(`src/renderer/src/i18n/translate/${code}.json`)
? (JSON.parse(fs.readFileSync(`src/renderer/src/i18n/translate/${code}.json`, 'utf8')) as I18N)
: {}
await translate(zh, obj, name, model, () => {
fs.writeFileSync(`src/renderer/src/i18n/translate/${code}.json`, JSON.stringify(obj, null, 2), 'utf8')
})
count += 1
bar.update(count)
}
bar.stop()
})()