mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 14:41:24 +08:00
* feat(i18n): 优化i18n check脚本 * fix: 移除重复的代码行和错误信息 * fix: 重新排序 * fix: i18n sort * test * test * test * test * test * test * feat(i18n): 添加同步翻译脚本并重构检查脚本 重构 check-i18n 脚本为纯检查功能,不再自动修改翻译文件 新增 sync-i18n 脚本用于自动同步翻译文件结构 更新 package.json 添加 sync:i18n 命令 移除不再需要的 husky pre-commit.js 钩子 * docs(i18n): 移除未使用的测试翻译文本 * feat(scripts): 添加对象键名排序功能及测试 添加 lexicalSort 函数和 sortedObjectByKeys 函数用于按字典序排序对象键名 新增测试用例验证排序功能 * feat(i18n): 添加翻译文件键值排序检查功能 添加对i18n翻译文件键值字典序的检查功能,确保翻译文件保持一致的排序结构 * refactor(i18n): 优化同步逻辑并添加键排序功能 移除syncRecursively的返回值检查,简化同步流程 添加对翻译文件的键排序功能,使用sortedObjectByKeys工具 确保主模板和翻译文件在同步后都保持有序 * fix(i18n): 重新排序翻译文件 * style(scripts): 格式化sort.js * chore: 将 test:sort 重命名为 test:scripts
152 lines
5.1 KiB
JavaScript
152 lines
5.1 KiB
JavaScript
'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()
|