vue-translate-auto
Version:
Vue 国际化自动翻译工具 - 自动提取 $t 包裹的字符串并生成多语言文件,支持增量翻译和多种翻译API
237 lines (197 loc) • 9.57 kB
JavaScript
const fs = require('fs');
const path = require('path');
const {getConfig} = require('./utils');
const Translator = require('./translator');
// 递归遍历目录
function walkDir(dir) {
const files = fs.readdirSync(dir);
return files.reduce((acc, file) => {
const filePath = path.join(dir, file);
const fileStat = fs.statSync(filePath);
var fileSuffix = path.extname(file);
if (fileStat.isDirectory()) {
acc = acc.concat(walkDir(filePath));
} else if (fileStat.isFile() && getConfig().fileSuffix.includes(fileSuffix)) {
acc.push(filePath);
}
return acc;
}, []);
}
// 提取文件中的关键字包裹的字符串
function extractTStrings(file) {
const content = fs.readFileSync(file, 'utf-8');
const keywords = getConfig().extractKeywords;
// 根据配置的关键字动态生成正则表达式
// 使用负向前瞻确保前面不是字母、数字、下划线、$、.
const keywordPattern = keywords.map(keyword =>
`(?<![a-zA-Z0-9_$.])${keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`
).join('|');
const regex = new RegExp(`(${keywordPattern})\\(['"]([^'"]+)['"]\\)`, 'g');
let match;
const matches = [];
while ((match = regex.exec(content)) !== null) {
matches.push(match[2]); // 第二个捕获组是字符串内容
}
return matches;
}
// 翻译并生成多语言文件
async function generateTranslations(sourceJson, config) {
if (!config.translation || !config.translation.enabled) {
console.log('翻译功能未启用,跳过翻译');
return;
}
const translator = new Translator(config.translation);
const { targetLanguages, sourceLanguage, outputDir } = config.translation;
// 确保输出目录存在
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
console.log(`创建输出目录: ${outputDir}`);
}
// 计算需要翻译的语言数量
const languagesToTranslate = targetLanguages.filter(lang => lang !== sourceLanguage);
const totalLanguages = languagesToTranslate.length;
if (totalLanguages === 0) {
console.log('没有需要翻译的目标语言');
return;
}
console.log('\n🌍 开始多语言翻译流程');
console.log(`📋 配置信息:`);
console.log(` • 源语言: ${sourceLanguage}`);
console.log(` • 目标语言: ${languagesToTranslate.join(', ')}`);
console.log(` • 输出目录: ${outputDir}`);
console.log(` • 词汇总数: ${Object.keys(sourceJson).length}`);
console.log('='.repeat(60));
let currentLangIndex = 0;
let totalSuccessCount = 0;
const overallStartTime = Date.now(); // 记录整体开始时间
// 为每种目标语言生成翻译文件
for (const targetLang of targetLanguages) {
if (targetLang === sourceLanguage) {
// 源语言直接复制
const sourceFilePath = path.join(outputDir, `${targetLang}.json`);
fs.writeFileSync(sourceFilePath, JSON.stringify(sourceJson, null, 2), 'utf-8');
console.log(`\n📁 生成源语言文件: ${sourceFilePath}`);
continue;
}
currentLangIndex++;
const langProgress = ((currentLangIndex / totalLanguages) * 100).toFixed(1);
console.log(`\n🎯 [${langProgress}%] (${currentLangIndex}/${totalLanguages}) 开始翻译到 ${targetLang}...`);
console.log('─'.repeat(60));
try {
const targetFilePath = path.join(outputDir, `${targetLang}.json`);
// 读取已有的翻译文件(如果存在)
let existingTranslations = {};
if (fs.existsSync(targetFilePath)) {
try {
const existingContent = fs.readFileSync(targetFilePath, 'utf-8');
existingTranslations = JSON.parse(existingContent);
console.log(`📖 读取已有翻译文件: ${Object.keys(existingTranslations).length} 个词汇`);
} catch (error) {
console.log(`⚠️ 读取已有翻译文件失败,将重新创建: ${error.message}`);
}
}
// 找出需要翻译的新词汇(不在已有翻译中的)
const newWords = {};
const skippedCount = 0;
let newWordCount = 0;
for (const [key, value] of Object.entries(sourceJson)) {
if (existingTranslations.hasOwnProperty(key)) {
// 已翻译过,跳过
} else {
// 新词汇,需要翻译
newWords[key] = value;
newWordCount++;
}
}
console.log(`📊 翻译统计:`);
console.log(` • 总词汇数: ${Object.keys(sourceJson).length}`);
console.log(` • 已翻译: ${Object.keys(existingTranslations).length}`);
console.log(` • 需要翻译: ${newWordCount}`);
let translatedJson = { ...existingTranslations }; // 保留已有翻译
let duration = 0;
let successCount = Object.keys(existingTranslations).length;
if (newWordCount > 0) {
// 只翻译新词汇
const result = await translator.translateBatch(newWords, targetLang, sourceLanguage);
const newTranslations = result.results;
duration = result.totalTime;
// 合并新翻译到已有翻译中
translatedJson = { ...existingTranslations, ...newTranslations };
// 统计新增的成功翻译数
const newSuccessCount = Object.values(newTranslations).filter(text => {
const originalKey = Object.keys(newWords).find(key => newWords[key] === text);
return text !== newWords[originalKey];
}).length;
successCount = Object.keys(existingTranslations).length + newSuccessCount;
} else {
console.log(`✅ 所有词汇都已翻译,无需重新翻译`);
}
// 写入完整的翻译文件
fs.writeFileSync(targetFilePath, JSON.stringify(translatedJson, null, 2), 'utf-8');
totalSuccessCount += successCount;
console.log(`\n✅ 翻译完成: ${targetLang}`);
if (newWordCount > 0) {
console.log(` • 耗时: ${duration}秒`);
console.log(` • 新增翻译: ${newWordCount}`);
}
console.log(` • 总翻译数: ${successCount}/${Object.keys(sourceJson).length}`);
console.log(` • 文件路径: ${targetFilePath}`);
} catch (error) {
console.error(`❌ 翻译到 ${targetLang} 失败:`, error.message);
}
}
// 计算整体耗时
const overallEndTime = Date.now();
const overallTotalTime = ((overallEndTime - overallStartTime) / 1000).toFixed(1);
// 显示整体翻译统计
console.log('\n' + '='.repeat(60));
console.log('🎊 多语言翻译流程完成!');
console.log('📊 整体统计:');
console.log(` • 目标语言数: ${totalLanguages}`);
console.log(` • 总词汇数: ${Object.keys(sourceJson).length}`);
console.log(` • 总成功翻译: ${totalSuccessCount}`);
console.log(` • 平均成功率: ${((totalSuccessCount / (totalLanguages * Object.keys(sourceJson).length)) * 100).toFixed(1)}%`);
console.log(` • 整体耗时: ${overallTotalTime}秒`);
console.log('='.repeat(60));
}
// 主函数
async function main() {
const srcDir = path.resolve(process.cwd(), getConfig().filePath);
const files = walkDir(srcDir);
const allMatches = [];
files.forEach(file => {
const matches = extractTStrings(file);
if (matches.length > 0) {
allMatches.push(...matches);
}
});
// 构建翻译对象
let jsonObj = {}
allMatches.forEach(match => {
jsonObj[match] = match
});
console.log(`📋 提取到 ${Object.keys(jsonObj).length} 个需要翻译的词汇`);
// 检查是否启用翻译功能
const config = getConfig();
if (config.translation && config.translation.enabled) {
// 启用翻译功能,生成翻译文件
console.log('\n开始生成翻译文件...');
await generateTranslations(jsonObj, config);
console.log('\n翻译完成!');
} else {
// 未启用翻译功能,只生成词汇提取文件
const outputPath = path.resolve(process.cwd(), config.targetPath || 'src/lang.json');
const outputDir = path.dirname(outputPath);
// 确保输出目录存在
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// 写入词汇提取文件
fs.writeFileSync(outputPath, JSON.stringify(jsonObj, null, 2), 'utf-8');
console.log(`\n✅ 词汇提取完成!`);
console.log(` • 文件路径: ${outputPath}`);
console.log(` • 词汇数量: ${Object.keys(jsonObj).length}`);
console.log(` • 如需翻译,请在配置中启用 translation.enabled = true`);
}
}
main();