UNPKG

vue-translate-auto

Version:

Vue 国际化自动翻译工具 - 自动提取 $t 包裹的字符串并生成多语言文件,支持增量翻译和多种翻译API

321 lines (275 loc) 12.4 kB
const https = require('https'); const querystring = require('querystring'); const crypto = require('crypto'); class Translator { constructor(config) { this.config = config; this.cache = new Map(); // 翻译缓存 } // 翻译单个文本 async translateText(text, targetLang, sourceLang = 'auto') { const cacheKey = `${text}_${sourceLang}_${targetLang}`; // 检查缓存 if (this.cache.has(cacheKey)) { return this.cache.get(cacheKey); } // 在线翻译 try { let result; switch (this.config.api.provider) { case 'google': result = await this.translateWithGoogle(text, targetLang, sourceLang); break; case 'baidu': result = await this.translateWithBaidu(text, targetLang, sourceLang); break; case 'youdao': result = await this.translateWithYoudao(text, targetLang, sourceLang); break; default: // 使用免费的Google翻译API result = await this.translateWithGoogle(text, targetLang, sourceLang); } // 缓存结果 this.cache.set(cacheKey, result); return result; } catch (error) { // 不在这里打印错误,由上层重试逻辑处理 throw error; // 重新抛出错误,让调用方处理 } } // Google翻译(免费版本) async translateWithGoogle(text, targetLang, sourceLang = 'auto') { return new Promise((resolve, reject) => { const url = 'https://translate.googleapis.com/translate_a/single'; const params = { client: 'gtx', sl: sourceLang, tl: targetLang, dt: 't', q: text }; const fullUrl = `${url}?${querystring.stringify(params)}`; https.get(fullUrl, (res) => { let data = ''; res.on('data', (chunk) => data += chunk); res.on('end', () => { try { const result = JSON.parse(data); if (result && result[0] && result[0][0]) { resolve(result[0][0][0]); } else { reject(new Error('翻译结果格式错误')); } } catch (error) { reject(error); } }); }).on('error', reject); }); } // 百度翻译 async translateWithBaidu(text, targetLang, sourceLang = 'auto') { // 需要百度翻译API密钥 if (!this.config.api.apiKey || !this.config.api.apiSecret) { throw new Error('百度翻译需要API密钥'); } console.log('暂不支持百度翻译') // 这里实现百度翻译API调用 // 由于需要API密钥,暂时返回原文 return text; } // 有道翻译 async translateWithYoudao(text, targetLang, sourceLang = 'auto') { // 需要有道翻译API密钥 if (!this.config.api.apiKey || !this.config.api.apiSecret) { throw new Error('有道翻译需要API密钥'); } return new Promise((resolve, reject) => { try { // 有道翻译API参数 const appKey = this.config.api.apiKey; const appSecret = this.config.api.apiSecret; const salt = Date.now().toString(); const curtime = Math.round(Date.now() / 1000).toString(); // 计算签名 const signStr = appKey + this.truncateQuery(text) + salt + curtime + appSecret; const sign = crypto.createHash('sha256').update(signStr).digest('hex'); // 语言代码转换 const fromLang = this.convertToYoudaoLangCode(sourceLang); const toLang = this.convertToYoudaoLangCode(targetLang); // 请求参数 const params = { q: text, from: fromLang, to: toLang, appKey: appKey, salt: salt, sign: sign, signType: 'v3', curtime: curtime }; const postData = querystring.stringify(params); const options = { hostname: 'openapi.youdao.com', port: 443, path: '/api', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(postData) }, timeout: 10000 }; const req = https.request(options, (res) => { let data = ''; res.on('data', (chunk) => data += chunk); res.on('end', () => { try { const result = JSON.parse(data); if (result.errorCode === '0' && result.translation && result.translation.length > 0) { // 清理翻译结果中的特殊字符 let translation = result.translation[0]; // 移除开头的引号和特殊字符 translation = translation.replace(/^["'"']/, '').replace(/["'"']$/, ''); resolve(translation); } else { // 不在这里打印错误,由上层重试逻辑处理 reject(new Error(`有道翻译失败: ${result.errorCode}`)); } } catch (error) { reject(error); } }); }); req.on('error', (error) => { reject(error); }); req.on('timeout', () => { req.destroy(); reject(new Error('有道翻译请求超时')); }); req.write(postData); req.end(); } catch (error) { reject(error); } }); } // 截断查询字符串(有道翻译要求) truncateQuery(q) { const len = q.length; if (len <= 20) { return q; } else { return q.substring(0, 10) + len + q.substring(len - 10, len); } } // 转换为有道翻译的语言代码 convertToYoudaoLangCode(lang) { return lang; } // 创建进度条 createProgressBar(current, total, barWidth = 30) { const progress = current / total; const filledWidth = Math.round(barWidth * progress); const emptyWidth = barWidth - filledWidth; const filledBar = '█'.repeat(filledWidth); const emptyBar = '░'.repeat(emptyWidth); const percentage = (progress * 100).toFixed(1); return `[${filledBar}${emptyBar}] ${percentage}% (${current}/${total})`; } // 批量翻译(确保100%在线翻译成功) async translateBatch(texts, targetLang, sourceLang = 'auto') { const results = {}; const totalTexts = Object.keys(texts).length; let currentIndex = 0; let successCount = 0; let totalRetries = 0; const startTime = Date.now(); // 记录开始时间 console.log(`\n🚀 开始翻译到 ${targetLang} 语言`); console.log(`📊 总计需要翻译: ${totalTexts} 个词汇`); console.log('─'.repeat(60)); for (const [key, text] of Object.entries(texts)) { currentIndex++; // 更新进度条(在同一行实时更新) const progressBar = this.createProgressBar(currentIndex, totalTexts); process.stdout.write(`\r${progressBar} 正在翻译: ${text}`); // 尝试翻译,失败时重试(增加重试次数和延迟) let translatedText = text; let currentRetryCount = 0; const maxRetries = 8; // 增加重试次数到8次 while (currentRetryCount <= maxRetries) { try { translatedText = await this.translateText(text, targetLang, sourceLang); if (translatedText !== text) { successCount++; } break; // 成功则跳出循环 } catch (error) { currentRetryCount++; totalRetries++; if (currentRetryCount > maxRetries) { // 只有在重试次数用完后才打印错误信息 console.error(`\n❌ 翻译最终失败: ${text} -> ${targetLang}, 错误: ${error.message}`); translatedText = text; } else { // 指数退避策略,延迟时间逐渐增加 const baseDelay = 3000; // 基础延迟3秒 const delayTime = Math.min(baseDelay * Math.pow(2, currentRetryCount - 1), 30000); // 最大30秒 // 在重试时更新进度条状态,不打印错误 process.stdout.write(`\r${progressBar} ⚠️ 重试中 (${currentRetryCount}/${maxRetries}): ${text}`); await new Promise(resolve => setTimeout(resolve, delayTime)); // 重试后恢复正常显示 process.stdout.write(`\r${progressBar} 正在翻译: ${text}`); } } } results[key] = translatedText; // 显示翻译完成状态(在同一行更新) const status = translatedText !== text ? '✓ 完成' : '❌ 失败'; process.stdout.write(`\r${progressBar} ${status}: ${text}`); // 增加延迟避免API限制 const delay = 500; // 0.5秒间隔 await new Promise(resolve => setTimeout(resolve, delay)); } const endTime = Date.now(); // 记录结束时间 const totalTime = ((endTime - startTime) / 1000).toFixed(1); // 计算总时间 // 完成时换行 console.log('\n'); // 显示最终统计 console.log('─'.repeat(60)); console.log(`🎉 ${targetLang} 语言翻译完成!`); console.log(`📊 最终统计:`); console.log(` • 总词汇数: ${totalTexts}`); console.log(` • 成功翻译: ${successCount}`); console.log(` • 失败词汇: ${totalTexts - successCount}`); console.log(` • 成功率: ${((successCount / totalTexts) * 100).toFixed(1)}%`); console.log(` • 总重试次数: ${totalRetries}`); console.log(` • 总耗时: ${totalTime}秒`); console.log('─'.repeat(60)); return { results, totalTime }; // 返回结果和耗时 } // 获取语言代码映射 getLanguageCode(lang) { const langMap = { 'en': 'en', 'zh-CN': 'zh-CN', 'zh': 'zh-CN', 'ja': 'ja', 'japanese': 'ja', 'ko': 'ko', 'korean': 'ko', 'fr': 'fr', 'french': 'fr', 'de': 'de', 'german': 'de', 'es': 'es', 'spanish': 'es', 'ru': 'ru', 'russian': 'ru' }; return langMap[lang] || lang; } } module.exports = Translator;