UNPKG

i18n-ai-trans

Version:

i18n-translate 是一个高效、简介的多语言翻译工具,安装后只需简单几步就能实现整个多语言文本的快速翻译。

286 lines (241 loc) 9.22 kB
const path = require('path') const { execSync } = require("child_process") const fs = require('fs'); const { readFileSync, readdirSync, writeFileSync } = fs; const { getLangStat } = require('./utils'); /** * 查找两个嵌套JSON对象之间的差异路径 * @param {Object} oldObj - 旧的JSON对象 * @param {Object} newObj - 新的JSON对象 * @param {String} parentPath - 父路径 * @param {Set} changedPaths - 变更路径集合 */ function findChangedPaths(oldObj, newObj, parentPath = '', changedPaths = new Set()) { // 检查旧对象中存在但新对象中不存在或值已更改的键 for (const key in oldObj) { const currentPath = parentPath ? `${parentPath}.${key}` : key; // 键在新对象中不存在 if (!(key in newObj)) { changedPaths.add(currentPath); continue; } // 两者都是对象,需要递归检查 if (typeof oldObj[key] === 'object' && oldObj[key] !== null && typeof newObj[key] === 'object' && newObj[key] !== null) { findChangedPaths(oldObj[key], newObj[key], currentPath, changedPaths); } // 值不同 else if (JSON.stringify(oldObj[key]) !== JSON.stringify(newObj[key])) { changedPaths.add(currentPath); } } // 检查新对象中存在但旧对象中不存在的键 for (const key in newObj) { if (!(key in oldObj)) { const currentPath = parentPath ? `${parentPath}.${key}` : key; changedPaths.add(currentPath); } } return changedPaths; } /** * 根据路径清空对象中的特定值 * @param {Object} obj - 要修改的对象 * @param {String} path - 点分隔的路径,如 "a.b.c" */ function clearValueAtPath(obj, path) { const parts = path.split('.'); let current = obj; // 遍历路径的所有部分,除了最后一个 for (let i = 0; i < parts.length - 1; i++) { const part = parts[i]; // 如果路径中间的对象不存在,则不需要继续 if (!current[part] || typeof current[part] !== 'object') { return; } current = current[part]; } // 获取最后一个部分(要清空的键) const lastPart = parts[parts.length - 1]; // 如果该键存在,将其清空 if (lastPart in current) { if (typeof current[lastPart] === 'object' && current[lastPart] !== null) { // 如果是对象,递归清空所有值 clearObjectValues(current[lastPart]); } else { current[lastPart] = ""; } } } /** * 递归清空对象中的所有值 * @param {Object} obj - 要清空的对象 */ function clearObjectValues(obj) { for (const key in obj) { if (typeof obj[key] === 'object' && obj[key] !== null) { clearObjectValues(obj[key]); } else { obj[key] = ""; } } } async function execEmpty({ gitRoot, filePath, fileName }) { if (!gitRoot) throw "请配置gitRoot"; const dir = path.dirname(filePath); const relativePath = path.relative(gitRoot, filePath).replace(/\\/g, '/'); // 获取旧内容 let oldContent = {}; try { const oldContentStr = await execSync(`git show HEAD~1:${relativePath}`, { cwd: gitRoot, encoding: "utf-8" }); oldContent = JSON.parse(oldContentStr); } catch (err) { console.warn(`获取文件 ${relativePath} 的历史版本失败:`, err.message); console.log('将使用空对象作为旧内容进行比较'); } // 获取新内容 let newContent = {}; try { const newContentStr = await readFileSync(filePath, "utf-8"); newContent = JSON.parse(newContentStr); } catch (err) { console.error(`读取文件 ${filePath} 失败:`, err.message); return; } // 获取变化的路径 const changedPaths = findChangedPaths(oldContent, newContent); if (changedPaths.size === 0) { console.log('没有变化过的key'); return; } console.log(`找到 ${changedPaths.size} 个变化的路径`); // 获取所有的多语言文件 const files = await readdirSync(dir).filter(item => { if (item.includes(fileName)) return false; return item.includes('.json'); }); // 遍历多语言文件 for (const item of files) { const absPath = path.join(dir, item); try { const res = await readFileSync(absPath, "utf-8"); const data = JSON.parse(res); // 清空所有变化路径的值 changedPaths.forEach(path => { clearValueAtPath(data, path); }); await writeFileSync(absPath, JSON.stringify(data, null, 2), 'utf-8'); console.log(`已更新文件: ${item}`); } catch (err) { console.error(`处理文件 ${absPath} 时出错:`, err.message); } } console.log('清空完成'); } async function getChangedPathsForFile(gitRoot, filePath) { const relativePath = path.relative(gitRoot, filePath).replace(/\\/g, '/'); try { // 获取旧内容 let oldContent = {}; try { const oldContentStr = await execSync(`git show HEAD~1:${relativePath}`, { cwd: gitRoot, encoding: "utf-8" }); oldContent = JSON.parse(oldContentStr); } catch (err) { console.warn(`获取文件 ${relativePath} 的历史版本失败:`, err.message); console.log('将使用空对象作为旧内容进行比较'); } // 获取新内容 let newContent = {}; try { const newContentStr = await fs.readFileSync(filePath, "utf-8"); newContent = JSON.parse(newContentStr); } catch (err) { console.error(`读取文件 ${filePath} 失败:`, err.message); return new Set(); } return findChangedPaths(oldContent, newContent); } catch (err) { console.warn(`获取文件 ${relativePath} 的变更信息失败`, err.message); return new Set(); } } function getAllJsonFiles(dirPath) { let jsonFiles = []; const items = fs.readdirSync(dirPath); for (const item of items) { const fullPath = path.join(dirPath, item); const stats = fs.lstatSync(fullPath); if (stats.isDirectory()) { // 递归处理子目录 jsonFiles = jsonFiles.concat(getAllJsonFiles(fullPath)); } else if (item.endsWith('.json')) { jsonFiles.push(fullPath); } } return jsonFiles; } function findCorrespondingFile(sourceFile, sourceDir, targetDir) { // 获取相对于源目录的路径 const relativePath = path.relative(sourceDir, sourceFile); // 构建目标语言中对应的文件路径 return path.join(targetDir, relativePath); } async function execEmptyForDirectory(config) { const { gitRoot, sourceDir, targetLangs } = config; // 使用utils.js中的函数获取源语言内容 const sourceFiles = getAllJsonFiles(sourceDir); for (const sourceFile of sourceFiles) { // 获取变更的路径 const changedPaths = await getChangedPathsForFile(gitRoot, sourceFile); if (changedPaths.size === 0) continue; console.log(`文件 ${path.relative(process.cwd(), sourceFile)}${changedPaths.size} 个路径发生变化`); // 处理每个目标语言 for (const lang of targetLangs) { const targetDir = path.join(path.dirname(sourceDir), lang); const targetFile = findCorrespondingFile(sourceFile, sourceDir, targetDir); // 确保目标文件存在 if (!fs.existsSync(targetFile)) { const targetFileDir = path.dirname(targetFile); if (!fs.existsSync(targetFileDir)) { fs.mkdirSync(targetFileDir, { recursive: true }); } fs.writeFileSync(targetFile, '{}', 'utf-8'); } // 清空变更的键 try { const content = fs.readFileSync(targetFile, 'utf-8'); const data = JSON.parse(content); changedPaths.forEach(path => { clearValueAtPath(data, path); }); fs.writeFileSync(targetFile, JSON.stringify(data, null, 2), 'utf-8'); console.log(`已更新文件: ${path.relative(process.cwd(), targetFile)}`); } catch (err) { console.error(`处理文件 ${targetFile} 时出错:`, err.message); } } } console.log('所有变更路径清空完成'); } function empty(config) { const absGitRoot = path.join(process.cwd(), config.gitRoot); const { isDir, isFile } = getLangStat(path.join(process.cwd(), config.translateDir), config.sourceLang); if (isDir) { const absSourceDir = path.join(process.cwd(), config.translateDir, config.sourceLang); execEmptyForDirectory({ gitRoot: absGitRoot, sourceDir: absSourceDir, targetLangs: config.langs.filter(item => item !== config.sourceLang) }); } else if (isFile) { const absFilePath = path.join(process.cwd(), config.translateDir, `${config.sourceLang}.json`); execEmpty({ gitRoot: absGitRoot, filePath: absFilePath, fileName: `${config.sourceLang}.json` }); } else { console.error(`找不到语言源文件或目录: ${config.sourceLang}`); } } module.exports = { empty };