Ollama运行translategemma-27b-it实操:构建Chrome插件实现网页图文即时翻译
你是不是经常遇到这样的场景:浏览外文网站时,看到一段关键的文字或者一张包含重要信息的截图,却因为语言不通而卡住?传统的网页翻译插件要么只能处理纯文本,要么对图片里的文字束手无策。今天,我要分享一个非常实用的解决方案:用Ollama部署translategemma-27b-it模型,然后自己动手做一个Chrome插件,实现网页图文即时翻译。
这个方案的核心是Google最新开源的TranslateGemma模型,它不仅能翻译文本,还能看懂图片里的文字并翻译出来。最棒的是,它体积小巧,在你的笔记本电脑上就能流畅运行。想象一下,以后浏览任何外文网页,无论是文字还是图片,鼠标一点就能看到中文翻译,是不是很酷?
这篇文章我会手把手带你完成两件事:第一,在本地用Ollama快速部署translategemma-27b-it模型;第二,写一个简单的Chrome插件,让它调用这个模型,实现网页上的图文即时翻译。整个过程不需要复杂的配置,跟着做就能搞定。
1. 准备工作与环境搭建
在开始之前,我们先来了解一下今天要用到的核心工具和模型。
1.1 核心工具介绍
Ollama是一个让你能在本地电脑上轻松运行各种大语言模型的工具。它把复杂的模型部署过程简化成了几条命令,特别适合我们这种想要快速尝试新模型的开发者。你不需要懂太多深度学习框架的知识,Ollama帮你搞定一切。
translategemma-27b-it是今天的主角,它是Google基于Gemma 3模型开发的一个专门用于翻译的模型。它有270亿个参数,听起来很大,但在翻译模型里算是比较轻量的。最关键的是,它支持55种语言的互译,而且具备图文对话能力——这意味着你不仅可以给它一段文字让它翻译,还可以给它一张图片,它能识别图片里的文字并翻译出来。
1.2 快速安装Ollama
安装Ollama非常简单,根据你的操作系统选择对应的方法:
Windows用户: 直接访问Ollama官网,下载安装程序,双击运行即可。安装完成后,你会在开始菜单里找到Ollama。
macOS用户: 打开终端,运行这条命令:
curl -fsSL https://ollama.com/install.sh | shLinux用户: 同样在终端里运行:
curl -fsSL https://ollama.com/install.sh | sh安装完成后,打开终端(Windows用户打开命令提示符或PowerShell),输入ollama --version,如果能看到版本号,说明安装成功了。
1.3 部署translategemma-27b-it模型
模型部署只需要一条命令。打开终端,输入:
ollama run translategemma:27b第一次运行时会自动下载模型文件,文件大小约15GB,根据你的网速可能需要一些时间。下载完成后,模型就自动加载并运行了。
为了验证模型是否正常工作,我们可以先做个简单的测试。在Ollama运行后出现的提示符后面,输入:
Translate "Hello, how are you?" to Chinese.如果看到类似“你好,你好吗?”这样的中文回复,说明模型运行正常。按Ctrl+D可以退出交互模式,但模型服务会在后台继续运行。
2. 模型基础使用与效果体验
在开始写插件之前,我们先来熟悉一下translategemma-27b-it的基本用法,看看它的翻译效果到底怎么样。
2.1 纯文本翻译测试
让我们从最简单的文本翻译开始。重新运行ollama run translategemma:27b,然后尝试几个不同场景的翻译:
商务邮件翻译:
你是一名专业的英文(en)至中文(zh-Hans)翻译员。请将以下商务邮件翻译成中文,保持正式、专业的语气: Dear Mr. Zhang, Thank you for your inquiry regarding our latest product line. We have attached the detailed specifications and pricing information as requested. Our sales team will contact you within 24 hours to discuss potential collaboration opportunities. Best regards, John Smith Sales Director技术文档翻译:
你是一名专业的技术文档翻译员。请将以下API说明从英文翻译成中文,确保技术术语准确: The `createUser` endpoint accepts a JSON object containing user details. Required fields include `username` (string, 3-20 characters), `email` (valid email format), and `password` (string, minimum 8 characters with at least one uppercase letter and one number). The response will include a `user_id` and `created_at` timestamp.文学片段翻译:
请将以下英文诗歌片段翻译成中文,尽量保持诗意和韵律: Two roads diverged in a yellow wood, And sorry I could not travel both And be one traveler, long I stood And looked down one as far as I could To where it bent in the undergrowth;你可以多试几种不同类型的文本,感受一下模型的翻译质量。我个人的体验是,对于技术文档和商务文本,它的准确度很高;对于文学性较强的文本,虽然能准确传达意思,但诗意的保留还有提升空间。
2.2 图文翻译能力展示
这才是translategemma-27b-it最厉害的地方——它能看懂图片里的文字并翻译出来。不过,在命令行里直接给模型发图片有点复杂,我们需要用Base64编码。别担心,我写了一个简单的Python脚本来帮你测试:
import base64 import requests import json # 读取图片并转换为Base64 def image_to_base64(image_path): with open(image_path, "rb") as image_file: return base64.b64encode(image_file.read()).decode('utf-8') # 准备请求 def translate_image(image_path, target_language="中文"): # 将图片转换为Base64 image_base64 = image_to_base64(image_path) # 构建提示词 prompt = f"""你是一名专业的翻译员。请将图片中的文字翻译成{target_language},准确传达原文的含义。 仅输出翻译结果,无需额外解释。""" # 发送请求到Ollama url = "http://localhost:11434/api/generate" data = { "model": "translategemma:27b", "prompt": prompt, "images": [image_base64], "stream": False } response = requests.post(url, json=data) return response.json()["response"] # 使用示例 if __name__ == "__main__": # 替换为你的图片路径 result = translate_image("example.png", "中文") print("翻译结果:", result)要运行这个脚本,你需要先安装requests库:pip install requests。然后找一张包含外文文字的图片(比如英文菜单、路标、产品说明书等),把脚本里的example.png换成你的图片路径,运行就能看到翻译结果了。
我测试了几种不同类型的图片:
- 英文路牌:翻译准确,方向指示清晰
- 日文产品说明书:能识别日文汉字和平假名,翻译成通顺的中文
- 法文菜单:食物名称翻译得很地道,还保留了文化特色
这个图文翻译功能对我们做Chrome插件特别有用,因为网页上很多信息都是以图片形式存在的。
3. 构建Chrome翻译插件
现在进入最有趣的部分——自己动手做一个Chrome插件。别被“浏览器插件”这个词吓到,其实它就是一个包含几个特定文件的文件夹而已。
3.1 创建插件基础结构
首先,在你的电脑上新建一个文件夹,命名为WebTranslator。在这个文件夹里,创建以下三个文件:
manifest.json(插件的配置文件):
{ "manifest_version": 3, "name": "网页图文翻译器", "version": "1.0", "description": "使用本地AI模型实现网页图文即时翻译", "permissions": ["activeTab", "scripting"], "host_permissions": ["http://localhost/*"], "action": { "default_popup": "popup.html", "default_icon": { "16": "icon16.png", "48": "icon48.png", "128": "icon128.png" } }, "icons": { "16": "icon16.png", "48": "icon48.png", "128": "icon128.png" } }popup.html(点击插件图标时弹出的界面):
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <style> body { width: 300px; padding: 15px; font-family: Arial, sans-serif; } .container { display: flex; flex-direction: column; gap: 10px; } select, button { padding: 8px; font-size: 14px; } button { background-color: #4285f4; color: white; border: none; border-radius: 4px; cursor: pointer; } button:hover { background-color: #3367d6; } #status { margin-top: 10px; font-size: 12px; color: #666; } .loading { display: none; text-align: center; color: #4285f4; } </style> </head> <body> <div class="container"> <h3>网页图文翻译器</h3> <div> <label for="targetLang">目标语言:</label> <select id="targetLang"> <option value="zh-Hans">简体中文</option> <option value="en">English</option> <option value="ja">日本語</option> <option value="ko">한국어</option> <option value="fr">Français</option> <option value="es">Español</option> </select> </div> <div> <label for="translationType">翻译类型:</label> <select id="translationType"> <option value="text">仅翻译选中文本</option> <option value="image">翻译页面图片</option> <option value="both">翻译文本和图片</option> </select> </div> <button id="translateBtn">开始翻译</button> <div class="loading" id="loading"> 翻译中,请稍候... </div> <div id="status"></div> </div> <script src="popup.js"></script> </body> </html>popup.js(处理用户交互的JavaScript代码):
document.getElementById('translateBtn').addEventListener('click', async () => { const targetLang = document.getElementById('targetLang').value; const translationType = document.getElementById('translationType').value; const loadingElement = document.getElementById('loading'); const statusElement = document.getElementById('status'); // 显示加载状态 loadingElement.style.display = 'block'; statusElement.textContent = ''; try { // 获取当前活跃的标签页 const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); // 执行翻译 const result = await chrome.scripting.executeScript({ target: { tabId: tab.id }, func: translatePageContent, args: [targetLang, translationType] }); statusElement.textContent = '翻译完成!'; statusElement.style.color = '#0f9d58'; } catch (error) { statusElement.textContent = `翻译失败:${error.message}`; statusElement.style.color = '#db4437'; console.error('翻译错误:', error); } finally { loadingElement.style.display = 'none'; } }); // 这个函数会被注入到网页中执行 function translatePageContent(targetLang, translationType) { // 这里先留空,具体实现我们稍后添加 console.log(`开始翻译,目标语言:${targetLang},类型:${translationType}`); return '翻译功能准备就绪'; }至于图标文件(icon16.png、icon48.png、icon128.png),你可以先找三个16x16、48x48、128x128像素的图片放在文件夹里,或者用简单的文字图标代替。网上有很多免费的图标资源可以用。
3.2 实现文本翻译功能
现在我们来完善translatePageContent函数,让它真正能翻译网页上的文字。修改popup.js中的这个函数:
function translatePageContent(targetLang, translationType) { // 只处理文本翻译的情况 if (translationType === 'image' || translationType === 'both') { // 图片翻译稍后处理 console.log('文本翻译部分'); } // 获取选中的文本 const selectedText = window.getSelection().toString().trim(); if (selectedText) { // 如果有选中的文本,翻译选中的部分 return translateSelectedText(selectedText, targetLang); } else { // 如果没有选中文本,翻译整个页面的可见文本 return translateVisibleText(targetLang); } } // 翻译选中的文本 async function translateSelectedText(text, targetLang) { try { const translation = await callTranslationAPI(text, null, targetLang); // 显示翻译结果(简单用alert演示,实际可以做得更美观) alert(`原文:${text}\n\n翻译:${translation}`); return `已翻译选中文本:${text.substring(0, 50)}...`; } catch (error) { console.error('文本翻译失败:', error); return '文本翻译失败'; } } // 翻译页面可见文本 async function translateVisibleText(targetLang) { // 获取所有文本节点 const textNodes = []; const walker = document.createTreeWalker( document.body, NodeFilter.SHOW_TEXT, null, false ); let node; while (node = walker.nextNode()) { if (node.textContent.trim() && isElementVisible(node.parentElement)) { textNodes.push(node); } } // 分批翻译,避免一次请求太大 const batchSize = 5; let translatedCount = 0; for (let i = 0; i < textNodes.length; i += batchSize) { const batch = textNodes.slice(i, i + batchSize); const texts = batch.map(node => node.textContent.trim()).filter(t => t.length > 0); if (texts.length === 0) continue; try { const translations = await Promise.all( texts.map(text => callTranslationAPI(text, null, targetLang)) ); // 用翻译结果替换原文 batch.forEach((node, index) => { if (translations[index]) { node.textContent = translations[index]; translatedCount++; } }); // 稍微延迟一下,避免请求太快 await new Promise(resolve => setTimeout(resolve, 100)); } catch (error) { console.error('批量翻译失败:', error); } } return `已翻译 ${translatedCount} 处文本`; } // 检查元素是否可见 function isElementVisible(element) { if (!element) return false; const style = window.getComputedStyle(element); if (style.display === 'none' || style.visibility === 'hidden') { return false; } const rect = element.getBoundingClientRect(); if (rect.width === 0 || rect.height === 0) { return false; } return true; } // 调用翻译API的核心函数 async function callTranslationAPI(text, imageBase64, targetLang) { const sourceLang = detectLanguage(text); const prompt = buildTranslationPrompt(text, sourceLang, targetLang); const requestBody = { model: "translategemma:27b", prompt: prompt, stream: false }; // 如果有图片,添加图片数据 if (imageBase64) { requestBody.images = [imageBase64]; } try { const response = await fetch('http://localhost:11434/api/generate', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(requestBody) }); if (!response.ok) { throw new Error(`API请求失败:${response.status}`); } const data = await response.json(); return data.response.trim(); } catch (error) { console.error('API调用错误:', error); throw error; } } // 检测文本语言(简化版) function detectLanguage(text) { // 这里可以用更复杂的检测逻辑 // 简单根据字符范围判断 if (/[\u4e00-\u9fff]/.test(text)) return 'zh-Hans'; if (/[\u3040-\u309f\u30a0-\u30ff]/.test(text)) return 'ja'; if (/[\uac00-\ud7af]/.test(text)) return 'ko'; if (/[àâçéèêëîïôûùüÿœæ]/.test(text)) return 'fr'; if (/[áéíóúñ¿¡]/.test(text)) return 'es'; return 'en'; // 默认英文 } // 构建翻译提示词 function buildTranslationPrompt(text, sourceLang, targetLang) { const languageNames = { 'zh-Hans': '简体中文', 'en': '英语', 'ja': '日语', 'ko': '韩语', 'fr': '法语', 'es': '西班牙语' }; return `你是一名专业的${languageNames[sourceLang] || sourceLang}至${languageNames[targetLang] || targetLang}翻译员。你的目标是准确传达原文的含义与细微差别,同时遵循目标语言的语法、词汇及文化敏感性规范。 请翻译以下内容,仅输出翻译结果,无需额外解释或评论: ${text}`; }3.3 添加图片翻译功能
现在让我们增强插件,让它能处理网页上的图片。我们需要修改几个地方:
首先,在popup.js中添加图片处理函数:
// 翻译页面图片 async function translatePageImages(targetLang) { // 获取所有图片 const images = Array.from(document.querySelectorAll('img')).filter(img => { return isElementVisible(img) && img.naturalWidth > 50 && img.naturalHeight > 50; }); let translatedCount = 0; for (const img of images) { try { // 将图片转换为Base64 const imageBase64 = await getImageBase64(img); if (!imageBase64) continue; // 调用翻译API const translation = await callTranslationAPI( "请翻译图片中的文字", imageBase64, targetLang ); if (translation) { // 在图片旁边显示翻译结果 showTranslationNearImage(img, translation); translatedCount++; } // 延迟一下,避免请求太快 await new Promise(resolve => setTimeout(resolve, 500)); } catch (error) { console.error(`图片翻译失败(${img.src}):`, error); } } return `已翻译 ${translatedCount} 张图片`; } // 获取图片的Base64编码 function getImageBase64(img) { return new Promise((resolve) => { // 如果图片来自不同域,可能需要代理 if (img.crossOrigin && img.crossOrigin !== 'anonymous') { console.warn('跨域图片可能无法获取'); resolve(null); return; } const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.naturalWidth || img.width; canvas.height = img.naturalHeight || img.height; // 设置跨域属性 img.crossOrigin = 'anonymous'; img.onload = function() { try { ctx.drawImage(img, 0, 0); const base64 = canvas.toDataURL('image/png').split(',')[1]; resolve(base64); } catch (error) { console.error('图片转换失败:', error); resolve(null); } }; img.onerror = function() { console.error('图片加载失败:', img.src); resolve(null); }; // 如果图片已经加载完成 if (img.complete && img.naturalWidth > 0) { img.onload(); } else { // 重新加载图片 const src = img.src; img.src = ''; img.src = src; } }); } // 在图片附近显示翻译结果 function showTranslationNearImage(img, translation) { // 创建翻译结果显示框 const translationBox = document.createElement('div'); translationBox.className = 'ai-translation-result'; translationBox.style.cssText = ` position: absolute; background: rgba(255, 255, 255, 0.95); border: 2px solid #4285f4; border-radius: 8px; padding: 10px; max-width: 300px; z-index: 10000; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); font-size: 14px; line-height: 1.4; `; translationBox.innerHTML = ` <div style="color: #4285f4; font-weight: bold; margin-bottom: 5px;">AI翻译:</div> <div>${translation}</div> <button style=" margin-top: 8px; padding: 4px 8px; background: #4285f4; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px; " onclick="this.parentElement.remove()">关闭</button> `; // 定位到图片附近 const rect = img.getBoundingClientRect(); translationBox.style.left = `${rect.right + 10}px`; translationBox.style.top = `${rect.top}px`; document.body.appendChild(translationBox); // 添加一些样式,让翻译框更美观 const style = document.createElement('style'); style.textContent = ` .ai-translation-result { animation: fadeIn 0.3s ease-in; } @keyframes fadeIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } } `; document.head.appendChild(style); }然后更新translatePageContent函数,让它支持图片翻译:
function translatePageContent(targetLang, translationType) { return new Promise(async (resolve) => { let result = ''; try { // 根据选择的类型执行不同的翻译 if (translationType === 'text' || translationType === 'both') { const selectedText = window.getSelection().toString().trim(); if (selectedText) { const textResult = await translateSelectedText(selectedText, targetLang); result += textResult + ' '; } else { const visibleTextResult = await translateVisibleText(targetLang); result += visibleTextResult + ' '; } } if (translationType === 'image' || translationType === 'both') { const imageResult = await translatePageImages(targetLang); result += imageResult; } resolve(result || '翻译完成'); } catch (error) { console.error('翻译过程出错:', error); resolve(`翻译过程中出现错误:${error.message}`); } }); }3.4 安装和测试插件
现在我们的插件已经基本完成了,让我们来安装并测试它:
加载插件到Chrome:
- 打开Chrome浏览器,在地址栏输入
chrome://extensions/ - 打开右上角的“开发者模式”
- 点击“加载已解压的扩展程序”
- 选择你创建的
WebTranslator文件夹 - 插件就会出现在扩展程序列表里
- 打开Chrome浏览器,在地址栏输入
测试文本翻译:
- 打开一个英文网页(比如维基百科的某个英文页面)
- 选中一段文字
- 点击插件图标,选择“简体中文”作为目标语言
- 选择“仅翻译选中文本”
- 点击“开始翻译”
- 你应该能看到一个弹窗显示翻译结果
测试整页翻译:
- 在同一个页面,不要选中任何文字
- 点击插件图标,保持设置不变
- 点击“开始翻译”
- 页面上的文字会逐渐被翻译成中文
测试图片翻译:
- 找一个有文字图片的网页(比如有英文截图的博客)
- 点击插件图标,选择“翻译页面图片”
- 点击“开始翻译”
- 图片旁边会出现翻译结果框
如果你遇到“跨域请求被阻止”的错误,可能是因为图片来自不同的域名。这种情况下,你可以尝试修改Ollama的CORS设置,或者使用代理服务器。不过对于大多数情况,我们的插件应该能正常工作。
4. 优化与进阶功能
基础功能已经实现了,但我们可以让它更好用。这里分享几个优化建议,你可以根据自己的需求选择实现。
4.1 性能优化建议
翻译整个网页可能会比较慢,特别是图片多的时候。这里有几个优化方法:
分批处理与延迟加载:
// 改进的批量处理函数 async function processInBatches(items, processFn, batchSize = 3, delay = 200) { const results = []; for (let i = 0; i < items.length; i += batchSize) { const batch = items.slice(i, i + batchSize); // 并行处理当前批次 const batchPromises = batch.map(item => processFn(item)); const batchResults = await Promise.all(batchPromises); results.push(...batchResults); // 如果不是最后一批,延迟一下 if (i + batchSize < items.length) { await new Promise(resolve => setTimeout(resolve, delay)); } } return results; } // 使用示例 const allTextNodes = getTextNodes(); const translations = await processInBatches( allTextNodes, node => translateTextNode(node, targetLang), 5, // 每批5个 100 // 每批间隔100ms );缓存翻译结果:
// 简单的缓存实现 const translationCache = new Map(); async function getCachedTranslation(text, targetLang) { const cacheKey = `${text}|${targetLang}`; if (translationCache.has(cacheKey)) { return translationCache.get(cacheKey); } const translation = await callTranslationAPI(text, null, targetLang); translationCache.set(cacheKey, translation); // 限制缓存大小 if (translationCache.size > 1000) { const firstKey = translationCache.keys().next().value; translationCache.delete(firstKey); } return translation; }4.2 用户体验改进
更好的翻译结果显示: 与其用alert弹窗,不如在页面上直接显示翻译结果。我们可以创建一个更美观的浮动窗口:
function createFloatingTranslationPanel() { const panel = document.createElement('div'); panel.id = 'ai-translation-panel'; panel.style.cssText = ` position: fixed; top: 20px; right: 20px; width: 350px; background: white; border-radius: 12px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15); z-index: 99999; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; display: none; `; panel.innerHTML = ` <div style=" padding: 16px; border-bottom: 1px solid #e0e0e0; display: flex; justify-content: space-between; align-items: center; "> <h3 style="margin: 0; color: #202124;">AI翻译结果</h3> <button id="close-panel" style=" background: none; border: none; font-size: 20px; cursor: pointer; color: #5f6368; ">×</button> </div> <div style="padding: 16px; max-height: 400px; overflow-y: auto;"> <div id="translation-content"></div> </div> `; document.body.appendChild(panel); // 关闭按钮事件 panel.querySelector('#close-panel').onclick = () => { panel.style.display = 'none'; }; return panel; } // 显示翻译结果 function showTranslationInPanel(original, translation) { let panel = document.getElementById('ai-translation-panel'); if (!panel) { panel = createFloatingTranslationPanel(); } const contentDiv = panel.querySelector('#translation-content'); contentDiv.innerHTML = ` <div style="margin-bottom: 16px;"> <div style="color: #5f6368; font-size: 12px; margin-bottom: 4px;">原文:</div> <div style=" background: #f8f9fa; padding: 12px; border-radius: 8px; border-left: 4px solid #dadce0; margin-bottom: 12px; ">${original}</div> </div> <div> <div style="color: #5f6368; font-size: 12px; margin-bottom: 4px;">翻译:</div> <div style=" background: #e8f0fe; padding: 12px; border-radius: 8px; border-left: 4px solid #4285f4; color: #202124; ">${translation}</div> </div> `; panel.style.display = 'block'; }翻译进度显示: 在翻译大量内容时,显示进度条会让用户知道发生了什么:
function createProgressOverlay(totalItems) { const overlay = document.createElement('div'); overlay.id = 'translation-progress'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(255, 255, 255, 0.9); z-index: 99998; display: flex; flex-direction: column; justify-content: center; align-items: center; `; overlay.innerHTML = ` <div style=" background: white; padding: 24px; border-radius: 12px; box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1); width: 300px; text-align: center; "> <h3 style="margin-top: 0; color: #202124;">翻译中...</h3> <div style=" height: 4px; background: #e0e0e0; border-radius: 2px; margin: 20px 0; overflow: hidden; "> <div id="progress-bar" style=" height: 100%; background: #4285f4; width: 0%; transition: width 0.3s ease; "></div> </div> <div id="progress-text" style="color: #5f6368; font-size: 14px;"> 准备开始... </div> </div> `; document.body.appendChild(overlay); return { update: (current, message) => { const progress = Math.min((current / totalItems) * 100, 100); document.getElementById('progress-bar').style.width = `${progress}%`; document.getElementById('progress-text').textContent = message; }, remove: () => { overlay.remove(); } }; }4.3 处理常见问题
在实际使用中,你可能会遇到一些问题。这里是一些常见问题的解决方法:
Ollama服务未启动:
// 在调用API前检查服务是否可用 async function checkOllamaService() { try { const response = await fetch('http://localhost:11434/api/tags', { method: 'GET', timeout: 3000 }); return response.ok; } catch { return false; } } // 使用前检查 async function safeCallTranslationAPI(text, imageBase64, targetLang) { const isServiceAvailable = await checkOllamaService(); if (!isServiceAvailable) { throw new Error('翻译服务未启动。请确保Ollama正在运行,并且translategemma:27b模型已加载。'); } return callTranslationAPI(text, imageBase64, targetLang); }处理大图片: translategemma模型对图片大小有限制,我们需要调整大图片:
function resizeImageBase64(base64, maxWidth = 896, maxHeight = 896) { return new Promise((resolve) => { const img = new Image(); img.onload = function() { const canvas = document.createElement('canvas'); let width = img.width; let height = img.height; // 计算缩放比例 if (width > maxWidth || height > maxHeight) { const ratio = Math.min(maxWidth / width, maxHeight / height); width *= ratio; height *= ratio; } canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0, width, height); const resizedBase64 = canvas.toDataURL('image/jpeg', 0.8).split(',')[1]; resolve(resizedBase64); }; img.src = `data:image/png;base64,${base64}`; }); }网络错误处理:
async function robustAPICall(requestBody, retries = 3) { for (let attempt = 1; attempt <= retries; attempt++) { try { const response = await fetch('http://localhost:11434/api/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(requestBody), signal: AbortSignal.timeout(30000) // 30秒超时 }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } return await response.json(); } catch (error) { if (attempt === retries) { throw error; } // 等待一段时间后重试 await new Promise(resolve => setTimeout(resolve, 1000 * attempt)); } } }5. 总结
通过这篇文章,我们完成了一个完整的项目:从在本地部署translategemma-27b-it翻译模型,到构建一个功能丰富的Chrome翻译插件。这个插件不仅能翻译网页文字,还能识别并翻译图片中的文字,而且全部在本地运行,保护了你的隐私。
回顾一下我们实现的核心功能:
- 本地AI模型部署:用Ollama一键部署translategemma-27b-it,支持55种语言互译
- 图文翻译能力:模型既能处理纯文本翻译,也能看懂图片里的文字
- Chrome插件开发:从零开始创建了一个完整的浏览器扩展
- 智能翻译策略:支持选中翻译、整页翻译、图片翻译多种模式
- 良好的用户体验:浮动翻译面板、进度显示、错误处理等
这个方案的几个独特优势:
- 完全本地运行:你的数据不会上传到任何服务器,隐私有保障
- 免费开源:Ollama和translategemma都是开源的,没有使用限制
- 可定制性强:你可以随意修改插件代码,添加自己想要的功能
- 离线可用:只要模型下载好了,没有网络也能使用
如果你想让插件更强大,可以考虑这些扩展方向:
- 添加翻译历史记录功能
- 支持自定义快捷键触发翻译
- 实现划词翻译(鼠标划选文字自动翻译)
- 添加更多翻译模型支持
- 开发其他浏览器版本(Firefox、Edge等)
最重要的是,通过这个项目,你不仅得到了一个实用的翻译工具,还掌握了本地部署AI模型和开发浏览器插件的能力。这些技能可以应用到很多其他场景,比如开发智能摘要插件、内容改写工具、代码解释器等。
现在,打开你的浏览器,试试这个自己亲手打造的AI翻译插件吧。相信它会成为你浏览外文网站时的得力助手。
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。