mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-02 18:39:06 +08:00
chore(i18n): improve i18n translation scripts (#8441)
* refactor(i18n): 迁移i18n脚本至TypeScript并添加进度条 - 将check-i18n.js和sync-i18n.js迁移为TypeScript版本 - 添加cli-progress依赖以显示翻译进度条 - 更新package.json中的i18n相关脚本 - 移除不再使用的sort.js工具文件 * refactor(i18n): 重构翻译同步脚本以支持多目录 将翻译文件目录变量重命名为更清晰的名称,并添加对translate目录的支持 优化文件路径处理逻辑,使用path.basename获取文件名 * chore: update i18n * docs(i18n): 更新翻译目录的README说明 更新README文件以更清晰地说明翻译文件的生成方式和使用注意事项 * style(DMXAPISettings): 添加关于国际化的FIXME注释 在PlatformOptions上方添加注释,提醒此处需要国际化处理
This commit is contained in:
parent
185045f805
commit
52c087fd22
@ -50,8 +50,9 @@
|
||||
"typecheck": "npm run typecheck:node && npm run typecheck:web",
|
||||
"typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
|
||||
"typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false",
|
||||
"check:i18n": "node scripts/check-i18n.js",
|
||||
"sync:i18n": "node scripts/sync-i18n.js",
|
||||
"check:i18n": "tsx scripts/check-i18n.ts",
|
||||
"sync:i18n": "tsx scripts/sync-i18n.ts",
|
||||
"update:i18n": "dotenv -e .env -- tsx scripts/update-i18n.ts",
|
||||
"test": "vitest run --silent",
|
||||
"test:main": "vitest run --project main",
|
||||
"test:renderer": "vitest run --project renderer",
|
||||
@ -132,6 +133,7 @@
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@tryfabric/martian": "^1.2.4",
|
||||
"@types/cli-progress": "^3",
|
||||
"@types/diff": "^7",
|
||||
"@types/fs-extra": "^11",
|
||||
"@types/lodash": "^4.17.5",
|
||||
@ -161,6 +163,7 @@
|
||||
"async-mutex": "^0.5.0",
|
||||
"axios": "^1.7.3",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"cli-progress": "^3.12.0",
|
||||
"code-inspector-plugin": "^0.20.14",
|
||||
"color": "^5.0.0",
|
||||
"country-flag-emoji-polyfill": "0.1.8",
|
||||
@ -247,6 +250,7 @@
|
||||
"tar": "^7.4.3",
|
||||
"tiny-pinyin": "^1.3.2",
|
||||
"tokenx": "^1.1.0",
|
||||
"tsx": "^4.20.3",
|
||||
"typescript": "^5.6.2",
|
||||
"undici": "6.21.2",
|
||||
"unified": "^11.0.5",
|
||||
|
||||
@ -1,151 +0,0 @@
|
||||
'use strict'
|
||||
Object.defineProperty(exports, '__esModule', { value: true })
|
||||
exports.main = main
|
||||
var fs = require('fs')
|
||||
var path = require('path')
|
||||
var sort_1 = require('./sort')
|
||||
var translationsDir = path.join(__dirname, '../src/renderer/src/i18n/locales')
|
||||
var baseLocale = 'zh-cn'
|
||||
var baseFileName = ''.concat(baseLocale, '.json')
|
||||
var baseFilePath = path.join(translationsDir, baseFileName)
|
||||
/**
|
||||
* 递归检查并同步目标对象与模板对象的键值结构
|
||||
* 1. 如果目标对象缺少模板对象中的键,抛出错误
|
||||
* 2. 如果目标对象存在模板对象中不存在的键,抛出错误
|
||||
* 3. 对于嵌套对象,递归执行同步操作
|
||||
*
|
||||
* 该函数用于确保所有翻译文件与基准模板(通常是中文翻译文件)保持完全一致的键值结构。
|
||||
* 任何结构上的差异都会导致错误被抛出,以便及时发现和修复翻译文件中的问题。
|
||||
*
|
||||
* @param target 需要检查的目标翻译对象
|
||||
* @param template 作为基准的模板对象(通常是中文翻译文件)
|
||||
* @throws {Error} 当发现键值结构不匹配时抛出错误
|
||||
*/
|
||||
function checkRecursively(target, template) {
|
||||
for (var key in template) {
|
||||
if (!(key in target)) {
|
||||
throw new Error('\u7F3A\u5C11\u5C5E\u6027 '.concat(key))
|
||||
}
|
||||
if (typeof template[key] === 'object' && template[key] !== null) {
|
||||
if (typeof target[key] !== 'object' || target[key] === null) {
|
||||
throw new Error('\u5C5E\u6027 '.concat(key, ' \u4E0D\u662F\u5BF9\u8C61'))
|
||||
}
|
||||
// 递归检查子对象
|
||||
checkRecursively(target[key], template[key])
|
||||
}
|
||||
}
|
||||
// 删除 target 中存在但 template 中没有的 key
|
||||
for (var targetKey in target) {
|
||||
if (!(targetKey in template)) {
|
||||
throw new Error('\u591A\u4F59\u5C5E\u6027 '.concat(targetKey))
|
||||
}
|
||||
}
|
||||
}
|
||||
function isSortedI18N(obj) {
|
||||
// fs.writeFileSync('./test_origin.json', JSON.stringify(obj))
|
||||
// fs.writeFileSync('./test_sorted.json', JSON.stringify(sortedObjectByKeys(obj)))
|
||||
return JSON.stringify(obj) === JSON.stringify((0, sort_1.sortedObjectByKeys)(obj))
|
||||
}
|
||||
/**
|
||||
* 检查 JSON 对象中是否存在重复键,并收集所有重复键
|
||||
* @param obj 要检查的对象
|
||||
* @returns 返回重复键的数组(若无重复则返回空数组)
|
||||
*/
|
||||
function checkDuplicateKeys(obj) {
|
||||
var keys = new Set()
|
||||
var duplicateKeys = []
|
||||
var checkObject = function (obj, path) {
|
||||
if (path === void 0) {
|
||||
path = ''
|
||||
}
|
||||
for (var key in obj) {
|
||||
var fullPath = path ? ''.concat(path, '.').concat(key) : key
|
||||
if (keys.has(fullPath)) {
|
||||
// 发现重复键时,添加到数组中(避免重复添加)
|
||||
if (!duplicateKeys.includes(fullPath)) {
|
||||
duplicateKeys.push(fullPath)
|
||||
}
|
||||
} else {
|
||||
keys.add(fullPath)
|
||||
}
|
||||
// 递归检查子对象
|
||||
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
||||
checkObject(obj[key], fullPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
checkObject(obj)
|
||||
return duplicateKeys
|
||||
}
|
||||
function checkTranslations() {
|
||||
if (!fs.existsSync(baseFilePath)) {
|
||||
throw new Error(
|
||||
'\u4E3B\u6A21\u677F\u6587\u4EF6 '.concat(
|
||||
baseFileName,
|
||||
' \u4E0D\u5B58\u5728\uFF0C\u8BF7\u68C0\u67E5\u8DEF\u5F84\u6216\u6587\u4EF6\u540D'
|
||||
)
|
||||
)
|
||||
}
|
||||
var baseContent = fs.readFileSync(baseFilePath, 'utf-8')
|
||||
var baseJson = {}
|
||||
try {
|
||||
baseJson = JSON.parse(baseContent)
|
||||
} catch (error) {
|
||||
throw new Error('\u89E3\u6790 '.concat(baseFileName, ' \u51FA\u9519\u3002').concat(error))
|
||||
}
|
||||
// 检查主模板是否存在重复键
|
||||
var duplicateKeys = checkDuplicateKeys(baseJson)
|
||||
if (duplicateKeys.length > 0) {
|
||||
throw new Error(
|
||||
'\u4E3B\u6A21\u677F\u6587\u4EF6 '
|
||||
.concat(baseFileName, ' \u5B58\u5728\u4EE5\u4E0B\u91CD\u590D\u952E\uFF1A\n')
|
||||
.concat(duplicateKeys.join('\n'))
|
||||
)
|
||||
}
|
||||
// 检查主模板是否有序
|
||||
if (!isSortedI18N(baseJson)) {
|
||||
throw new Error(
|
||||
'\u4E3B\u6A21\u677F\u6587\u4EF6 '.concat(
|
||||
baseFileName,
|
||||
' \u7684\u952E\u503C\u672A\u6309\u5B57\u5178\u5E8F\u6392\u5E8F\u3002'
|
||||
)
|
||||
)
|
||||
}
|
||||
var files = fs.readdirSync(translationsDir).filter(function (file) {
|
||||
return file.endsWith('.json') && file !== baseFileName
|
||||
})
|
||||
// 同步键
|
||||
for (var _i = 0, files_1 = files; _i < files_1.length; _i++) {
|
||||
var file = files_1[_i]
|
||||
var filePath = path.join(translationsDir, file)
|
||||
var targetJson = {}
|
||||
try {
|
||||
var fileContent = fs.readFileSync(filePath, 'utf-8')
|
||||
targetJson = JSON.parse(fileContent)
|
||||
} catch (error) {
|
||||
throw new Error('\u89E3\u6790 '.concat(file, ' \u51FA\u9519\u3002'))
|
||||
}
|
||||
// 检查有序性
|
||||
if (!isSortedI18N(targetJson)) {
|
||||
throw new Error(
|
||||
'\u7FFB\u8BD1\u6587\u4EF6 '.concat(file, ' \u7684\u952E\u503C\u672A\u6309\u5B57\u5178\u5E8F\u6392\u5E8F\u3002')
|
||||
)
|
||||
}
|
||||
try {
|
||||
checkRecursively(targetJson, baseJson)
|
||||
} catch (e) {
|
||||
throw new Error('\u5728\u68C0\u67E5 '.concat(filePath, ' \u65F6\u51FA\u9519\uFF1A').concat(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
function main() {
|
||||
try {
|
||||
checkTranslations()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
throw new Error(
|
||||
'\u68C0\u67E5\u672A\u901A\u8FC7\u3002\u5C1D\u8BD5\u8FD0\u884C yarn sync:i18n \u4EE5\u89E3\u51B3\u95EE\u9898\u3002'
|
||||
)
|
||||
}
|
||||
}
|
||||
main()
|
||||
@ -1,40 +0,0 @@
|
||||
'use strict'
|
||||
Object.defineProperty(exports, '__esModule', { value: true })
|
||||
exports.sortedObjectByKeys = sortedObjectByKeys
|
||||
// https://github.com/Gudahtt/prettier-plugin-sort-json/blob/main/src/index.ts
|
||||
/**
|
||||
* Lexical sort function for strings, meant to be used as the sort
|
||||
* function for `Array.prototype.sort`.
|
||||
*
|
||||
* @param a - First element to compare.
|
||||
* @param b - Second element to compare.
|
||||
* @returns A number indicating which element should come first.
|
||||
*/
|
||||
function lexicalSort(a, b) {
|
||||
if (a > b) {
|
||||
return 1
|
||||
}
|
||||
if (a < b) {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
/**
|
||||
* 对对象的键按照字典序进行排序(支持嵌套对象)
|
||||
* @param obj 需要排序的对象
|
||||
* @returns 返回排序后的新对象
|
||||
*/
|
||||
function sortedObjectByKeys(obj) {
|
||||
var sortedKeys = Object.keys(obj).sort(lexicalSort)
|
||||
var sortedObj = {}
|
||||
for (var _i = 0, sortedKeys_1 = sortedKeys; _i < sortedKeys_1.length; _i++) {
|
||||
var key = sortedKeys_1[_i]
|
||||
var value = obj[key]
|
||||
// 如果值是对象,递归排序
|
||||
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
||||
value = sortedObjectByKeys(value)
|
||||
}
|
||||
sortedObj[key] = value
|
||||
}
|
||||
return sortedObj
|
||||
}
|
||||
@ -1,143 +0,0 @@
|
||||
'use strict'
|
||||
Object.defineProperty(exports, '__esModule', { value: true })
|
||||
var fs = require('fs')
|
||||
var path = require('path')
|
||||
var sort_1 = require('./sort')
|
||||
var translationsDir = path.join(__dirname, '../src/renderer/src/i18n/locales')
|
||||
var baseLocale = 'zh-cn'
|
||||
var baseFileName = ''.concat(baseLocale, '.json')
|
||||
var baseFilePath = path.join(translationsDir, baseFileName)
|
||||
/**
|
||||
* 递归同步 target 对象,使其与 template 对象保持一致
|
||||
* 1. 如果 template 中存在 target 中缺少的 key,则添加('[to be translated]')
|
||||
* 2. 如果 target 中存在 template 中不存在的 key,则删除
|
||||
* 3. 对于子对象,递归同步
|
||||
*
|
||||
* @param target 目标对象(需要更新的语言对象)
|
||||
* @param template 主模板对象(中文)
|
||||
* @returns 返回是否对 target 进行了更新
|
||||
*/
|
||||
function syncRecursively(target, template) {
|
||||
// 添加 template 中存在但 target 中缺少的 key
|
||||
for (var key in template) {
|
||||
if (!(key in target)) {
|
||||
target[key] =
|
||||
typeof template[key] === 'object' && template[key] !== null ? {} : '[to be translated]:'.concat(template[key])
|
||||
console.log('\u6DFB\u52A0\u65B0\u5C5E\u6027\uFF1A'.concat(key))
|
||||
}
|
||||
if (typeof template[key] === 'object' && template[key] !== null) {
|
||||
if (typeof target[key] !== 'object' || target[key] === null) {
|
||||
target[key] = {}
|
||||
}
|
||||
// 递归同步子对象
|
||||
syncRecursively(target[key], template[key])
|
||||
}
|
||||
}
|
||||
// 删除 target 中存在但 template 中没有的 key
|
||||
for (var targetKey in target) {
|
||||
if (!(targetKey in template)) {
|
||||
console.log('\u79FB\u9664\u591A\u4F59\u5C5E\u6027\uFF1A'.concat(targetKey))
|
||||
delete target[targetKey]
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 检查 JSON 对象中是否存在重复键,并收集所有重复键
|
||||
* @param obj 要检查的对象
|
||||
* @returns 返回重复键的数组(若无重复则返回空数组)
|
||||
*/
|
||||
function checkDuplicateKeys(obj) {
|
||||
var keys = new Set()
|
||||
var duplicateKeys = []
|
||||
var checkObject = function (obj, path) {
|
||||
if (path === void 0) {
|
||||
path = ''
|
||||
}
|
||||
for (var key in obj) {
|
||||
var fullPath = path ? ''.concat(path, '.').concat(key) : key
|
||||
if (keys.has(fullPath)) {
|
||||
// 发现重复键时,添加到数组中(避免重复添加)
|
||||
if (!duplicateKeys.includes(fullPath)) {
|
||||
duplicateKeys.push(fullPath)
|
||||
}
|
||||
} else {
|
||||
keys.add(fullPath)
|
||||
}
|
||||
// 递归检查子对象
|
||||
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
||||
checkObject(obj[key], fullPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
checkObject(obj)
|
||||
return duplicateKeys
|
||||
}
|
||||
function syncTranslations() {
|
||||
if (!fs.existsSync(baseFilePath)) {
|
||||
console.error(
|
||||
'\u4E3B\u6A21\u677F\u6587\u4EF6 '.concat(
|
||||
baseFileName,
|
||||
' \u4E0D\u5B58\u5728\uFF0C\u8BF7\u68C0\u67E5\u8DEF\u5F84\u6216\u6587\u4EF6\u540D'
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
var baseContent = fs.readFileSync(baseFilePath, 'utf-8')
|
||||
var baseJson = {}
|
||||
try {
|
||||
baseJson = JSON.parse(baseContent)
|
||||
} catch (error) {
|
||||
console.error('\u89E3\u6790 '.concat(baseFileName, ' \u51FA\u9519\u3002').concat(error))
|
||||
return
|
||||
}
|
||||
// 检查主模板是否存在重复键
|
||||
var duplicateKeys = checkDuplicateKeys(baseJson)
|
||||
if (duplicateKeys.length > 0) {
|
||||
throw new Error(
|
||||
'\u4E3B\u6A21\u677F\u6587\u4EF6 '
|
||||
.concat(baseFileName, ' \u5B58\u5728\u4EE5\u4E0B\u91CD\u590D\u952E\uFF1A\n')
|
||||
.concat(duplicateKeys.join('\n'))
|
||||
)
|
||||
}
|
||||
// 为主模板排序
|
||||
var sortedJson = (0, sort_1.sortedObjectByKeys)(baseJson)
|
||||
if (JSON.stringify(baseJson) !== JSON.stringify(sortedJson)) {
|
||||
try {
|
||||
fs.writeFileSync(baseFilePath, JSON.stringify(sortedJson, null, 2) + '\n', 'utf-8')
|
||||
console.log('\u4E3B\u6A21\u677F\u5DF2\u6392\u5E8F')
|
||||
} catch (error) {
|
||||
console.error('\u5199\u5165 '.concat(baseFilePath, ' \u51FA\u9519\u3002'), error)
|
||||
return
|
||||
}
|
||||
}
|
||||
var files = fs.readdirSync(translationsDir).filter(function (file) {
|
||||
return file.endsWith('.json') && file !== baseFileName
|
||||
})
|
||||
// 同步键
|
||||
for (var _i = 0, files_1 = files; _i < files_1.length; _i++) {
|
||||
var file = files_1[_i]
|
||||
var filePath = path.join(translationsDir, file)
|
||||
var targetJson = {}
|
||||
try {
|
||||
var fileContent = fs.readFileSync(filePath, 'utf-8')
|
||||
targetJson = JSON.parse(fileContent)
|
||||
} catch (error) {
|
||||
console.error('\u89E3\u6790 '.concat(file, ' \u51FA\u9519\uFF0C\u8DF3\u8FC7\u6B64\u6587\u4EF6\u3002'), error)
|
||||
continue
|
||||
}
|
||||
syncRecursively(targetJson, baseJson)
|
||||
var sortedJson_1 = (0, sort_1.sortedObjectByKeys)(targetJson)
|
||||
try {
|
||||
fs.writeFileSync(filePath, JSON.stringify(sortedJson_1, null, 2) + '\n', 'utf-8')
|
||||
console.log(
|
||||
'\u6587\u4EF6 '.concat(
|
||||
file,
|
||||
' \u5DF2\u6392\u5E8F\u5E76\u540C\u6B65\u66F4\u65B0\u4E3A\u4E3B\u6A21\u677F\u7684\u5185\u5BB9'
|
||||
)
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('\u5199\u5165 '.concat(file, ' \u51FA\u9519\u3002').concat(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
syncTranslations()
|
||||
@ -3,10 +3,11 @@ import * as path from 'path'
|
||||
|
||||
import { sortedObjectByKeys } from './sort'
|
||||
|
||||
const translationsDir = path.join(__dirname, '../src/renderer/src/i18n/locales')
|
||||
const localesDir = path.join(__dirname, '../src/renderer/src/i18n/locales')
|
||||
const translateDir = path.join(__dirname, '../src/renderer/src/i18n/translate')
|
||||
const baseLocale = 'zh-cn'
|
||||
const baseFileName = `${baseLocale}.json`
|
||||
const baseFilePath = path.join(translationsDir, baseFileName)
|
||||
const baseFilePath = path.join(localesDir, baseFileName)
|
||||
|
||||
type I18NValue = string | { [key: string]: I18NValue }
|
||||
type I18N = { [key: string]: I18NValue }
|
||||
@ -113,17 +114,25 @@ function syncTranslations() {
|
||||
}
|
||||
}
|
||||
|
||||
const files = fs.readdirSync(translationsDir).filter((file) => file.endsWith('.json') && file !== baseFileName)
|
||||
const localeFiles = fs
|
||||
.readdirSync(localesDir)
|
||||
.filter((file) => file.endsWith('.json') && file !== baseFileName)
|
||||
.map((filename) => path.join(localesDir, filename))
|
||||
const translateFiles = fs
|
||||
.readdirSync(translateDir)
|
||||
.filter((file) => file.endsWith('.json') && file !== baseFileName)
|
||||
.map((filename) => path.join(translateDir, filename))
|
||||
const files = [...localeFiles, ...translateFiles]
|
||||
|
||||
// 同步键
|
||||
for (const file of files) {
|
||||
const filePath = path.join(translationsDir, file)
|
||||
for (const filePath of files) {
|
||||
const filename = path.basename(filePath)
|
||||
let targetJson: I18N = {}
|
||||
try {
|
||||
const fileContent = fs.readFileSync(filePath, 'utf-8')
|
||||
targetJson = JSON.parse(fileContent)
|
||||
} catch (error) {
|
||||
console.error(`解析 ${file} 出错,跳过此文件。`, error)
|
||||
console.error(`解析 ${filename} 出错,跳过此文件。`, error)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -133,9 +142,9 @@ function syncTranslations() {
|
||||
|
||||
try {
|
||||
fs.writeFileSync(filePath, JSON.stringify(sortedJson, null, 2) + '\n', 'utf-8')
|
||||
console.log(`文件 ${file} 已排序并同步更新为主模板的内容`)
|
||||
console.log(`文件 ${filename} 已排序并同步更新为主模板的内容`)
|
||||
} catch (error) {
|
||||
console.error(`写入 ${file} 出错。${error}`)
|
||||
console.error(`写入 ${filename} 出错。${error}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,9 +4,16 @@
|
||||
* API_KEY=sk-xxxx BASE_URL=xxxx MODEL=xxxx ts-node scripts/update-i18n.ts
|
||||
*/
|
||||
|
||||
import cliProgress from 'cli-progress'
|
||||
import fs from 'fs'
|
||||
import OpenAI from 'openai'
|
||||
|
||||
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://llmapi.paratera.com/v1'
|
||||
const MODEL = process.env.MODEL || 'Qwen3-235B-A22B'
|
||||
const BASE_URL = process.env.BASE_URL || 'https://dashscope.aliyuncs.com/compatible-mode/v1/'
|
||||
const MODEL = process.env.MODEL || 'qwen-plus-latest'
|
||||
|
||||
const INDEX = [
|
||||
// 语言的名称代码用来翻译的模型
|
||||
@ -16,10 +23,7 @@ const INDEX = [
|
||||
{ name: 'Greek', code: 'el-gr', model: MODEL }
|
||||
]
|
||||
|
||||
const fs = require('fs')
|
||||
import OpenAI from 'openai'
|
||||
|
||||
const zh = JSON.parse(fs.readFileSync('src/renderer/src/i18n/locales/zh-cn.json', 'utf8')) as object
|
||||
const zh = JSON.parse(fs.readFileSync('src/renderer/src/i18n/locales/zh-cn.json', 'utf8')) as I18N
|
||||
|
||||
const openai = new OpenAI({
|
||||
apiKey: API_KEY,
|
||||
@ -27,21 +31,23 @@ const openai = new OpenAI({
|
||||
})
|
||||
|
||||
// 递归遍历翻译
|
||||
async function translate(zh: object, obj: object, target: string, model: string, updateFile) {
|
||||
const texts: { [key: string]: string } = {}
|
||||
for (const e in zh) {
|
||||
if (typeof zh[e] == 'object') {
|
||||
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 (!obj[e] || typeof obj[e] != 'object') obj[e] = {}
|
||||
await translate(zh[e], obj[e], target, model, updateFile)
|
||||
} else {
|
||||
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]'))
|
||||
) {
|
||||
// 加入到本层待翻译列表
|
||||
if (!obj[e] || typeof obj[e] != 'string') {
|
||||
texts[e] = zh[e]
|
||||
}
|
||||
toTranslateTexts[key] = baseObj[key]
|
||||
}
|
||||
}
|
||||
if (Object.keys(texts).length > 0) {
|
||||
if (Object.keys(toTranslateTexts).length > 0) {
|
||||
const completion = await openai.chat.completions.create({
|
||||
model: model,
|
||||
response_format: { type: 'json_object' },
|
||||
@ -79,16 +85,16 @@ MAKE SURE TO OUTPUT IN Russian. DO NOT OUTPUT IN UNSPECIFIED LANGUAGE.
|
||||
{
|
||||
role: 'user',
|
||||
content: `
|
||||
You are a robot specifically designed for translation tasks. As a model that has been extensively fine-tuned on ${target} language corpora, you are proficient in using the ${target} 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 ${target} language.
|
||||
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(texts)}
|
||||
${JSON.stringify(toTranslateTexts)}
|
||||
######################################################
|
||||
MAKE SURE TO OUTPUT IN ${target}. DO NOT OUTPUT IN UNSPECIFIED LANGUAGE.
|
||||
MAKE SURE TO OUTPUT IN ${targetLang}. DO NOT OUTPUT IN UNSPECIFIED LANGUAGE.
|
||||
######################################################
|
||||
`
|
||||
}
|
||||
@ -97,37 +103,45 @@ MAKE SURE TO OUTPUT IN ${target}. DO NOT OUTPUT IN UNSPECIFIED LANGUAGE.
|
||||
// 添加翻译后的键值,并打印错译漏译内容
|
||||
try {
|
||||
const result = JSON.parse(completion.choices[0].message.content!)
|
||||
for (const e in texts) {
|
||||
// console.debug('result', result)
|
||||
for (const e in toTranslateTexts) {
|
||||
if (result[e] && typeof result[e] === 'string') {
|
||||
obj[e] = result[e]
|
||||
targetObj[e] = result[e]
|
||||
} else {
|
||||
console.log('[warning]', `missing value "${e}" in ${target} translation`)
|
||||
console.warn(`missing value "${e}" in ${targetLang} translation`)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('[error]', e)
|
||||
for (const e in texts) {
|
||||
console.log('[warning]', `missing value "${e}" in ${target} translation`)
|
||||
console.error(e)
|
||||
for (const e in toTranslateTexts) {
|
||||
console.warn(`missing value "${e}" in ${targetLang} translation`)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 删除多余的键值
|
||||
for (const e in obj) {
|
||||
if (!zh[e]) {
|
||||
delete obj[e]
|
||||
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'))
|
||||
? (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()
|
||||
})()
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
本目录文件使用机器翻译,请勿编辑
|
||||
This directory file is machine translated, please do not edit
|
||||
本目录文件使用`yarn update:i18n`机器翻译生成,请勿手动编辑。
|
||||
This directory contains machine translated files generated by `yarn update:i18n`. Please do not edit manually.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -20,6 +20,7 @@ enum PlatformType {
|
||||
OVERSEA = 'https://ssvip.DMXAPI.com'
|
||||
}
|
||||
|
||||
// FIXME: always Chinese. take consider of i18n
|
||||
const PlatformOptions = [
|
||||
{
|
||||
label: 'www.DMXAPI.cn 人民币站',
|
||||
|
||||
55
yarn.lock
55
yarn.lock
@ -5596,6 +5596,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/cli-progress@npm:^3":
|
||||
version: 3.11.6
|
||||
resolution: "@types/cli-progress@npm:3.11.6"
|
||||
dependencies:
|
||||
"@types/node": "npm:*"
|
||||
checksum: 10c0/d9a2d60b8fc6ccef73368fa20a23d5b16506808a81ec65f7e8eedf58d236ebaf2ab46578936c000c8e39dde825cb48a3cf9195c8b410177efd5388bcf9d07370
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/d3-array@npm:*":
|
||||
version: 3.2.1
|
||||
resolution: "@types/d3-array@npm:3.2.1"
|
||||
@ -7175,6 +7184,7 @@ __metadata:
|
||||
"@testing-library/jest-dom": "npm:^6.6.3"
|
||||
"@testing-library/react": "npm:^16.3.0"
|
||||
"@tryfabric/martian": "npm:^1.2.4"
|
||||
"@types/cli-progress": "npm:^3"
|
||||
"@types/diff": "npm:^7"
|
||||
"@types/fs-extra": "npm:^11"
|
||||
"@types/lodash": "npm:^4.17.5"
|
||||
@ -7204,6 +7214,7 @@ __metadata:
|
||||
async-mutex: "npm:^0.5.0"
|
||||
axios: "npm:^1.7.3"
|
||||
browser-image-compression: "npm:^2.0.2"
|
||||
cli-progress: "npm:^3.12.0"
|
||||
code-inspector-plugin: "npm:^0.20.14"
|
||||
color: "npm:^5.0.0"
|
||||
country-flag-emoji-polyfill: "npm:0.1.8"
|
||||
@ -7295,6 +7306,7 @@ __metadata:
|
||||
tar: "npm:^7.4.3"
|
||||
tiny-pinyin: "npm:^1.3.2"
|
||||
tokenx: "npm:^1.1.0"
|
||||
tsx: "npm:^4.20.3"
|
||||
turndown: "npm:7.2.0"
|
||||
typescript: "npm:^5.6.2"
|
||||
undici: "npm:6.21.2"
|
||||
@ -8597,6 +8609,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cli-progress@npm:^3.12.0":
|
||||
version: 3.12.0
|
||||
resolution: "cli-progress@npm:3.12.0"
|
||||
dependencies:
|
||||
string-width: "npm:^4.2.3"
|
||||
checksum: 10c0/f464cb19ebde2f3880620a2adfaeeefaec6cb15c8e610c8a659ca1047ee90d69f3bf2fdabbb1fe33ac408678e882e3e0eecdb84ab5df0edf930b269b8a72682d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cli-spinners@npm:^2.5.0":
|
||||
version: 2.9.2
|
||||
resolution: "cli-spinners@npm:2.9.2"
|
||||
@ -10486,7 +10507,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"esbuild@npm:^0.25.0, esbuild@npm:^0.25.5":
|
||||
"esbuild@npm:^0.25.0, esbuild@npm:^0.25.5, esbuild@npm:~0.25.0":
|
||||
version: 0.25.8
|
||||
resolution: "esbuild@npm:0.25.8"
|
||||
dependencies:
|
||||
@ -11927,6 +11948,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-tsconfig@npm:^4.7.5":
|
||||
version: 4.10.1
|
||||
resolution: "get-tsconfig@npm:4.10.1"
|
||||
dependencies:
|
||||
resolve-pkg-maps: "npm:^1.0.0"
|
||||
checksum: 10c0/7f8e3dabc6a49b747920a800fb88e1952fef871cdf51b79e98db48275a5de6cdaf499c55ee67df5fa6fe7ce65f0063e26de0f2e53049b408c585aa74d39ffa21
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-uri@npm:^6.0.1":
|
||||
version: 6.0.4
|
||||
resolution: "get-uri@npm:6.0.4"
|
||||
@ -18111,6 +18141,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"resolve-pkg-maps@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "resolve-pkg-maps@npm:1.0.0"
|
||||
checksum: 10c0/fb8f7bbe2ca281a73b7ef423a1cbc786fb244bd7a95cbe5c3fba25b27d327150beca8ba02f622baea65919a57e061eb5005204daa5f93ed590d9b77463a567ab
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"responselike@npm:^2.0.0":
|
||||
version: 2.0.1
|
||||
resolution: "responselike@npm:2.0.1"
|
||||
@ -19730,6 +19767,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tsx@npm:^4.20.3":
|
||||
version: 4.20.3
|
||||
resolution: "tsx@npm:4.20.3"
|
||||
dependencies:
|
||||
esbuild: "npm:~0.25.0"
|
||||
fsevents: "npm:~2.3.3"
|
||||
get-tsconfig: "npm:^4.7.5"
|
||||
dependenciesMeta:
|
||||
fsevents:
|
||||
optional: true
|
||||
bin:
|
||||
tsx: dist/cli.mjs
|
||||
checksum: 10c0/6ff0d91ed046ec743fac7ed60a07f3c025e5b71a5aaf58f3d2a6b45e4db114c83e59ebbb078c8e079e48d3730b944a02bc0de87695088aef4ec8bbc705dc791b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tunnel-agent@npm:^0.6.0":
|
||||
version: 0.6.0
|
||||
resolution: "tunnel-agent@npm:0.6.0"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user