🟩 1. 初衷和问题背景
在博客中引入翻译功能,常见做法有两种:
- 嵌入 Google Translate 插件 ✅ 快捷,但影响样式、不可控;
- 自己调用 Google API 实现 ✅ 灵活,但有缓存/性能/兼容性问题。
于是我决定打造一个完全自主可控的翻译系统,适配我博客使用的模版 Hexo+Butterfly。

🟩 2. 实现亮点
- ✅ 支持中文 ↔ 英文切换;
- ⚙️ 使用
translate.googleapis.com
API;
- 🔄 分批翻译文本(30条一批),解决长度限制;
- 🧠 缓存翻译结果,切换语言无需重复调用;
- 💡 支持标题、正文、段落、代码块翻译;
- 🌐 提供”整页翻译”功能;
- 🎯 自动识别当前语言,保留用户偏好;
- 🧼 翻译状态进度提示,用户体验更友好;
- 🧱 代码模块清晰,易于拓展到法语、日语等。
🟩 3. 小Bug & 已知问题
- 🔁 当前版本中,语言切换时可能需要点两次(因为加载+缓存尚未写入);
- 🧪 后续考虑使用 MutationObserver 监控 PJAX 页面刷新内容自动绑定;
- 📦 正在测试是否适配移动端(如按钮定位)。
🟩 4. 核心代码解析
下面我将展示并讲解一些关键代码部分,而不是全部代码(全代码有700+行)。
4.1 系统初始化与状态管理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| (function () { console.log('DEBUG: 初始化翻译系统 - 版本 1.8.0 (纯整页翻译版)');
let currentLang = localStorage.getItem('blog_language') || 'zh'; let isTranslating = false; let isInitialized = false; const originalContent = new Map(); const translatedContent = new Map(); const translateCache = new Map();
if (window.autoTranslateInitialized) { console.log('DEBUG: 翻译系统已经初始化,跳过重复初始化'); return; } window.autoTranslateInitialized = true; function initTranslation() { } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initTranslation); } else { initTranslation(); } })();
|
解析: 系统初始化与状态管理部分,使用立即执行函数避免全局变量污染。Map数据结构用于缓存原始内容和翻译内容,确保快速切换语言时无需重复调用API。
4.2 智能语言检测
1 2 3 4 5 6
| function detectLanguage(text) { const chineseChars = text.match(/[\u4e00-\u9fa5]/g) || []; return chineseChars.length / text.length > 0.5 ? 'zh' : 'en'; }
|
解析: 通过Unicode字符范围匹配中文字符,计算文本中中文字符占比,实现简单高效的语言检测。当中文字符占比超过50%时,判定为中文文本。
4.3 整页翻译按钮功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| function addStandaloneTranslateButton() { if (document.getElementById('standalone-translate-btn')) return; const btn = document.createElement('button'); btn.id = 'standalone-translate-btn'; btn.style.position = 'fixed'; btn.style.bottom = '32px'; btn.style.right = '32px'; btn.style.zIndex = '99999'; btn.style.background = '#4a7dbe'; btn.style.color = '#fff'; btn.style.border = 'none'; btn.style.borderRadius = '50%'; btn.style.width = '48px'; btn.style.height = '48px'; btn.style.boxShadow = '0 2px 8px rgba(0,0,0,0.15)'; btn.style.cursor = 'pointer'; btn.style.fontSize = '16px'; btn.style.transition = 'background 0.2s, opacity 0.3s'; btn.style.opacity = '0.9'; btn.title = currentLang === 'zh' ? 'Translate to English' : '切换为中文'; btn.textContent = currentLang === 'zh' ? 'EN' : '中'; btn.addEventListener('click', () => { if (isTranslating) return; if (currentLang === 'zh') { translate('en'); } else { restoreOriginalContent(); } }); document.body.appendChild(btn); }
|
解析: 在页面右下角添加一个固定位置的翻译按钮,点击时可以在中英文之间切换。按钮根据当前语言状态显示不同的文本和提示。
4.4 分批翻译处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| async function translate(targetLang) { const elements = findTranslatableElements(container); elements.forEach(el => { if (!originalContent.has(el)) { originalContent.set(el, el.textContent); } });
const batchSize = 30; let completedBatches = 0; const totalBatches = Math.ceil(elements.length / batchSize); for (let i = 0; i < elements.length; i += batchSize) { const batch = elements.slice(i, i + batchSize); const texts = batch.map(el => el.textContent.trim()).filter(Boolean); completedBatches++; const progress = Math.floor((completedBatches / totalBatches) * 100); showTranslationProgressBar(progress, `翻译进度: ${completedBatches}/${totalBatches}`); const cacheKey = `${combined}_zh-CN_en`; let result; if (translateCache.has(cacheKey)) { result = translateCache.get(cacheKey); console.log('DEBUG: 使用缓存的翻译结果'); } else { const combined = texts.join('\n[SPLIT]\n'); result = await translateText(combined, 'zh-CN', 'en'); if (result) { translateCache.set(cacheKey, result); } } } }
|
解析: 这段代码展示了如何解决API长度限制问题——将所有需要翻译的文本分成多批(每批30个元素),使用特殊分隔符[SPLIT]
连接发送,翻译后再拆分回来。增加了缓存机制,避免重复请求相同内容,大大提高效率同时避免频率限制。
4.5 Google翻译API调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| async function translateText(text, from, to) { try { const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=${from}&tl=${to}&dt=t&q=${encodeURIComponent(text)}`; const response = await fetch(url); if (!response.ok) throw new Error('翻译API请求失败'); const data = await response.json(); return data[0].map(item => item[0]).join(''); } catch (error) { console.error('翻译失败:', error); return null; } }
|
解析: 直接调用Google翻译API,无需认证密钥。使用client=gtx
参数和合适的请求格式可免费使用。返回数据需要特殊处理:取第一层数组的每个元素的第一个值,并拼接起来。
🟩 5. 后续计划
- ✅ 兼容夜间模式;
- 🧪 添加本地词汇表(Glossary);
- 🪙 引入我自己的 API Token(避免免费限流);
- 🔀 添加更多语言的支持(法语、日语等);
🟩 6. 结尾
1 2 3
| 如果你觉得这个功能不错,欢迎点个赞 ⭐,或者留言告诉我你想翻译的语言 😄
🆕 最新版本更加简洁高效,仅保留了整页翻译功能,通过右下角固定的翻译按钮一键切换语言!
|
🟩 7. 开源声明
本翻译系统已开源,欢迎大家学习、使用和二次开发!
项目地址:Github开源仓库地址