UNPKG

i18n-automatically-cli

Version:
270 lines (230 loc) 7.76 kB
const fs = require("fs"); const path = require("path"); const chalk = require("chalk"); const ora = require("ora"); const { readConfig, getRootPath, resolveI18nPath } = require("../utils/config"); // 动态导入 inquirer async function getInquirer() { const inquirer = await import("inquirer"); return inquirer.default; } async function generateCommand(options = {}) { try { const config = readConfig(); const zhPath = resolveI18nPath("locale", "zh.json"); // 检查中文语言包是否存在 if (!fs.existsSync(zhPath)) { console.error( chalk.red( `在 ${config.i18nFilePath}/locale/ 文件夹下面未找到 zh.json 语言包文件` ) ); console.log( chalk.yellow('请先运行 "i18n-auto scan" 或 "i18n-auto batch" 扫描中文') ); process.exit(1); } // 读取中文语言包 const zhData = JSON.parse(fs.readFileSync(zhPath, "utf8")); const chineseTexts = Object.values(zhData); if (chineseTexts.length === 0) { console.log(chalk.yellow("中文语言包为空,无需生成其他语言包")); return; } console.log( chalk.blue(`📦 找到 ${chineseTexts.length} 个待翻译的中文文本`) ); // 确定目标语言 const targetLanguages = options.languages || ["en"]; console.log(chalk.blue(`🌍 目标语言: ${targetLanguages.join(", ")}`)); // 如果不需要翻译,只生成模板 if (options.translate === false) { for (const lang of targetLanguages) { await generateLanguageTemplate(lang, zhData, config); } console.log(chalk.green("✅ 语言包模板生成完成!")); return; } // 选择翻译服务 const translateService = await selectTranslateService( options.service, config ); if (!translateService) { console.log(chalk.yellow("取消翻译,仅生成模板")); for (const lang of targetLanguages) { await generateLanguageTemplate(lang, zhData, config); } return; } // 开始翻译 const spinner = ora("正在翻译...").start(); try { for (const lang of targetLanguages) { spinner.text = `正在翻译到 ${lang}...`; await generateTranslatedLanguagePack( lang, zhData, translateService, config ); } spinner.succeed(chalk.green("✅ 语言包生成完成!")); console.log( chalk.gray( `生成路径: ${path.join(getRootPath(), config.i18nFilePath, "locale")}` ) ); } catch (error) { spinner.fail(chalk.red("❌ 翻译失败")); console.error(chalk.red(error)); process.exit(1); } } catch (error) { console.error(chalk.red("生成语言包失败:"), error); process.exit(1); } } async function selectTranslateService(preferredService, config) { // 检查可用的翻译服务 const availableServices = []; if (config.freeGoogle) { availableServices.push({ label: "免费谷歌翻译", value: "google" }); } if (config.baidu && config.baidu.appid && config.baidu.secretKey) { availableServices.push({ label: "百度翻译", value: "baidu" }); } if (config.deepl && config.deepl.authKey) { availableServices.push({ label: "DeepL 翻译", value: "deepl" }); } if (availableServices.length === 0) { console.error(chalk.red("未配置任何翻译服务")); console.log(chalk.yellow('请运行 "i18n-auto config" 配置翻译服务')); return null; } // 如果指定了服务,检查是否可用 if (preferredService) { const service = availableServices.find((s) => s.value === preferredService); if (service) { return preferredService; } else { console.log(chalk.yellow(`指定的翻译服务 "${preferredService}" 不可用`)); } } // 交互式选择翻译服务 const inquirer = await getInquirer(); const { service } = await inquirer.prompt([ { type: "list", name: "service", message: "选择翻译服务:", choices: [ ...availableServices, { label: "跳过翻译,仅生成模板", value: "skip" }, ], }, ]); return service === "skip" ? null : service; } async function generateLanguageTemplate(language, zhData, config) { const langPath = resolveI18nPath("locale", `${language}.json`); // 生成空模板 const template = {}; for (const key of Object.keys(zhData)) { template[key] = ""; } fs.writeFileSync(langPath, JSON.stringify(template, null, 2)); console.log(chalk.gray(` ✓ ${language}.json 模板已生成`)); } async function generateTranslatedLanguagePack( language, zhData, service, config ) { const langPath = resolveI18nPath("locale", `${language}.json`); // 检查是否已存在语言包 let existingData = {}; if (fs.existsSync(langPath)) { existingData = JSON.parse(fs.readFileSync(langPath, "utf8")); } const translatedData = {}; const textsToTranslate = []; // 收集需要翻译的文本 for (const [key, text] of Object.entries(zhData)) { if (existingData[key] && existingData[key].trim()) { // 如果已存在翻译,保留原翻译 translatedData[key] = existingData[key]; } else { // 需要翻译 textsToTranslate.push({ key, text }); } } if (textsToTranslate.length === 0) { console.log(chalk.gray(` ✓ ${language}.json 无需更新`)); return; } console.log( chalk.gray(` ⏳ 翻译 ${textsToTranslate.length} 个文本到 ${language}...`) ); // 批量翻译 const BATCH_SIZE = 20; for (let i = 0; i < textsToTranslate.length; i += BATCH_SIZE) { const batch = textsToTranslate.slice(i, i + BATCH_SIZE); const translations = await translateBatch( batch.map((item) => item.text), service, language, config ); for (let j = 0; j < batch.length; j++) { translatedData[batch[j].key] = translations[j] || batch[j].text; } // 添加延迟以避免API限制 if (i + BATCH_SIZE < textsToTranslate.length) { await new Promise((resolve) => setTimeout(resolve, 1000)); } } // 保存翻译结果 fs.writeFileSync(langPath, JSON.stringify(translatedData, null, 2)); console.log(chalk.gray(` ✓ ${language}.json 已生成/更新`)); } async function translateBatch(texts, service, targetLang, config) { try { switch (service) { case "google": return await translateWithGoogle(texts, targetLang); case "baidu": return await translateWithBaidu(texts, targetLang, config.baidu); case "deepl": return await translateWithDeepL(texts, targetLang, config.deepl); default: throw new Error(`不支持的翻译服务: ${service}`); } } catch (error) { console.error(chalk.yellow(`翻译失败,使用原文: ${error}`)); return texts; // 翻译失败时返回原文 } } async function translateWithGoogle(texts, targetLang) { const { translate } = require("@vitalets/google-translate-api"); const results = await Promise.all( texts.map((text) => translate(text, { to: targetLang })) ); return results.map((result) => result.text); } async function translateWithBaidu(texts, targetLang, baiduConfig) { // 这里需要实现百度翻译API调用 // 为了简化,现在返回原文 console.log(chalk.yellow("百度翻译功能待实现")); return texts; } async function translateWithDeepL(texts, targetLang, deeplConfig) { // 这里需要实现DeepL翻译API调用 // 为了简化,现在返回原文 console.log(chalk.yellow("DeepL翻译功能待实现")); return texts; } module.exports = { generateCommand, };