UNPKG

i18n-automatically-cli

Version:
422 lines (368 loc) 11.8 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) { // 检查是否配置了百度翻译凭据 if (!baiduConfig || !baiduConfig.appid || !baiduConfig.secretKey) { throw new Error("百度翻译未正确配置:缺少 appid 或 secretKey"); } // 百度翻译API实现 const crypto = require("crypto"); const axios = require("axios"); const appId = baiduConfig.appid; const secretKey = baiduConfig.secretKey; const from = "zh"; // 源语言固定为中文 const to = targetLang; // 目标语言 const salt = Date.now(); // 随机数 // 百度翻译API支持的语言代码映射 const langMap = { "en": "en", "zh": "zh", "yue": "yue", "wyw": "wyw", "ja": "jp", "ko": "kor", "fr": "fra", "es": "spa", "th": "th", "ar": "ara", "ru": "ru", "pt": "pt", "de": "de", "it": "it", "el": "el", "nl": "nl", "pl": "pl", "bg": "bul", "et": "est", "da": "dan", "fi": "fin", "cs": "cs", "ro": "rom", "sl": "slo", "sv": "swe", "hu": "hu", "vi": "vie" }; const toLang = langMap[to] || to; // 处理多个文本的翻译 const results = []; for (const text of texts) { // 构造需要翻译的文本 const query = text; // 生成签名 const str1 = appId + query + salt + secretKey; const sign = crypto.createHash("md5").update(str1).digest("hex"); try { const response = await axios.post("https://fanyi-api.baidu.com/api/trans/vip/translate", null, { params: { q: query, from: from, to: toLang, appid: appId, salt: salt, sign: sign }, timeout: 10000 // 10秒超时 }); if (response.data && response.data.trans_result) { results.push(response.data.trans_result[0].dst); } else { console.error(`百度翻译API错误: ${response.data.error_msg || '未知错误'}`); results.push(text); // 返回原文 } } catch (error) { console.error(`百度翻译API请求失败: ${error.message}`); results.push(text); // 返回原文 } } return results; } async function translateWithDeepL(texts, targetLang, deeplConfig) { // 检查是否配置了DeepL凭据 if (!deeplConfig || !deeplConfig.authKey) { throw new Error("DeepL翻译未正确配置:缺少 authKey"); } // DeepL API实现 const axios = require("axios"); // DeepL API支持的语言代码映射 const langMap = { "bg": "BG", // 保加利亚语 "cs": "CS", // 捷克语 "da": "DA", // 丹麦语 "de": "DE", // 德语 "el": "EL", // 希腊语 "en": "EN", // 英语 "es": "ES", // 西班牙语 "et": "ET", // 爱沙尼亚语 "fi": "FI", // 芬兰语 "fr": "FR", // 法语 "hu": "HU", // 匈牙利语 "id": "ID", // 印尼语 "it": "IT", // 意大利语 "ja": "JA", // 日语 "ko": "KO", // 韩语 "lt": "LT", // 立陶宛语 "lv": "LV", // 拉脱维亚语 "nl": "NL", // 荷兰语 "pl": "PL", // 波兰语 "pt": "PT", // 葡萄牙语 "ro": "RO", // 罗马尼亚语 "ru": "RU", // 俄语 "sk": "SK", // 斯洛伐克语 "sl": "SL", // 斯洛文尼亚语 "sv": "SV", // 瑞典语 "tr": "TR", // 土耳其语 "uk": "UK", // 乌克兰语 "zh": "ZH" // 中文 }; const toLang = langMap[targetLang.toUpperCase()] || targetLang.toUpperCase(); const isPro = deeplConfig.isPro; const apiUrl = isPro ? "https://api.deepl.com/v2/translate" : "https://api-free.deepl.com/v2/translate"; try { const response = await axios.post( apiUrl, { text: texts, target_lang: toLang, source_lang: "ZH" // 源语言固定为中文 }, { headers: { "Authorization": `DeepL-Auth-Key ${deeplConfig.authKey}`, "Content-Type": "application/json" }, timeout: 15000 // 15秒超时 } ); if (response.data && response.data.translations) { return response.data.translations.map(translation => translation.text); } else { console.error("DeepL API错误: 未返回翻译结果"); return texts; // 返回原文 } } catch (error) { console.error(`DeepL API请求失败: ${error.message}`); if (error.response) { console.error(`错误详情: ${error.response.status} - ${error.response.data}`); } return texts; // 返回原文 } } module.exports = { generateCommand, };