说实话,一开始我并没有打算自己从头写一个翻译工具。直到有一次,朋友发来一份产品需求文档让我帮忙翻译,我打开常用的翻译软件,通篇"上下文"时而译成"语境"、时而译成"环境","并发"直接被翻成"同时发生",关键术语乱到没法看。为了不再被这类工具气到,我花了一个周末,用大模型接口搓了个翻译程序出来。后来因为自己用得太顺手,又把它封装成了跨端应用,一路迭代到了现在的v6.1.0版本,也就是"浔川AI翻译"。
这篇文章就把整个开发过程掰开揉碎讲一遍,包括技术栈怎么选、大模型接口怎么封装、流式输出怎么做、上下文记忆和历史记录怎么设计,以及从HBuilderX打包到微信小程序上线的完整链路,最后还有我在v6.1.0里做的实测数据。适合两类人看:一类是准备入门AI应用开发、但一直没动手的开发者;另一类是已经在用各种翻译接口、想进一步优化翻译体验的产品或技术同学。文章里涉及的关键代码我都会贴出来,你可以直接照着改。
1. 为什么放着现成翻译软件不用,非要自己写一个
在动手之前,我先认真整理了一遍「现成翻译工具到底哪里不好用」。如果不把这个想清楚,很容易做成一个"套壳调用接口"的玩具,没必要也没价值。
1.1 现成翻译工具的三大痛点
第一个痛点是术语一致性。同一个词在一篇文档里,翻译工具可能给出三种译法。尤其是技术类、医疗类、法律类文本,术语不统一会直接导致理解偏差。我用某款在线翻译测试过一篇英文技术博客,"shell"这个词在全文里分别被译成"外壳""贝壳""命令行环境",人工校对的时间比自己翻译还长。
第二个痛点是格式破坏。我经常翻译带Markdown标记的文档、带换行和缩进的日志、带表格的说明性文字。普通翻译接口往往把格式打乱,或者把代码块里的变量名也当成普通英文给译了。等到我复制回编辑器,整个结构都废了。
第三个痛点是缺乏上下文。翻译软件的核心单位是"句子",但很多词单独看和放在上下文里完全是两个意思。比如"it"指代的是前一句的哪个对象,比如一个转折词承接的是上文的哪层逻辑,这些都需要更大的上下文窗口才能处理。传统翻译引擎做不到,而大模型恰恰擅长这个。
1.2 从"翻译工具"到"翻译助手":产品定位的变化
想清楚痛点之后,我把产品定位从"翻译工具"改成了"翻译助手"。工具只负责把一段文字从A语言变成B语言,助手则要理解需求、记忆偏好、给出更贴近使用场景的结果。
具体来说,"浔川AI翻译"从设计之初就确定了几个原则:
- 翻译单位是"对话",而不是单句。用户发一句英文,程序不只返回译文,还会理解这可能是上一句的延续。
- 支持术语表。用户可以把"concurrency"锁定为"并发",以后所有翻译都优先用这个词。
- 保留原文格式。如果输入是Markdown,输出也尽量是Markdown;如果输入是代码片段,代码部分不做翻译。
- 所有历史记录保存在本地,不走服务端存储,保护隐私。
这个定位决定了我后面所有的技术选型。它不是做一个API套壳,而是做一个有记忆、有偏好的"AI翻译agent"——虽然这个agent只负责一件事,但它的工作方式已经和普通翻译工具有了本质区别。
2. 技术底座怎么选:大模型接口、跨端框架与端侧存储
选型这个环节我纠结了挺久,因为翻译类应用对延迟、成本、稳定性都很敏感。下面把这些选择的思考过程完整说一遍,也好给你做AI应用开发时参考。
2.1 为什么选大模型接口而不是传统翻译API
传统翻译API的优势是快、便宜、稳定,但它解决不了术语一致和上下文理解的问题。大模型接口虽然单次调用更贵、延迟更高,但翻译质量尤其是长文本、专业文本的质量,明显上了一个台阶。我们对出版社的朋友做过盲测,大模型翻译的文本在可读性和术语准确率上,比传统API高出不少。
在模型选型上,我的核心考量有三个:
- 上下文长度:至少需要支持8K以上,因为多轮对话时要携带历史消息。
- 输出稳定性:JSON输出的字段要稳定,便于程序解析。
- 成本与配额:个人开发者最怕的是调用成本失控。
我最终选用的是兼容OpenAI接口格式的大模型服务,这样的好处是,以后想换模型供应商,只需要改基础地址和密钥,代码几乎不用动。市场上有好几家国产大模型都提供这种兼容接口,按量付费也不贵。
2.2 前端框架:为什么最终选了uni-app
做前端容器时,我认真对比过三条路:微信原生小程序、Taro、uni-app。最后选了uni-app,最主要的原因是开发效率。项目里同一套代码可以编译成微信小程序、H5、App,我一个个人开发者实在没有精力给每个端单独写一套。而且uni-app基于Vue语法,生态里有很多现成组件,遇到问题时社区资料也足够多。
另外,uni-app配套的HBuilderX确实方便。从新建项目、写代码、真机调试到云打包,一条龙完成。对于个人开发者和独立开发者来说,省掉了大量环境配置的麻烦。我在后面第7节会专门讲HBuilderX打包发布的流程。
目录结构大致是这样:
└── 浔川AI翻译 ├── pages │ ├── index // 首页:翻译主界面 │ ├── history // 历史记录 │ └── settings // 设置:术语表、暗色模式等 ├── utils │ ├── request.js // 网络请求封装 │ ├── sse.js // 流式响应解析 │ └── storage.js // 本地存储封装 ├── static └── manifest.json // uni-app配置文件细心的读者会发现,我并没有把大模型接口的密钥放在前端代码里。这是所有AI应用开发里最重要的一条安全底线——前端直接放密钥,等于把钥匙挂在大门上。我这里的做法在3.1节里详细说。
2.3 数据流向:从用户输入到译文显示的全过程
整个翻译链路是这样的:用户在输入框输入文本,前端把文本连同历史消息一起发给云函数,云函数在这个中间层拼接好system prompt和用户消息,再调用大模型接口。大模型返回流式数据,云函数以流式方式转发给前端,前端逐字渲染出来。整个过程中,模型供应商的密钥只存在于云函数环境变量里,前端永远接触不到。
用户输入 → uni-app前端 → uniCloud云函数 → 大模型接口 ↑ ↓ 逐字渲染 ←← 流式数据 ←←←←←←这个链路看起来简单,但每一步都有很多细节,比如流式数据怎么解析、超时怎么处理、术语表怎么注入,我逐个展开讲。
3. 核心引擎开发:API封装、流式输出与翻译质量调优
核心引擎是"浔川AI翻译"的心脏,也是v6.1.0版本里优化最多的部分。下面按模块拆解。
3.1 请求层封装与密钥保护:为什么必须加一个中间层
直接让前端调用大模型接口,逻辑上也能跑通,但有两个致命问题。第一,接口密钥暴露在前端代码里,任何人通过抓包就能拿到,然后盗刷你的额度;第二,前端直接拼Prompt,很多业务逻辑混在一起,后续维护非常痛苦。
我的做法是在uniCloud上部署一个云函数作为代理层。前端请求云函数,云函数再请求大模型。云函数里用环境变量保存密钥,前端只能看到云函数的URL,拿不到任何敏感信息。
云函数核心代码大概是这样的:
// 云函数:translate const crypto = require('crypto') const modelApiKey = process.env.MODEL_API_KEY // 从环境变量读取,不硬编码 exports.main = async (event) => { const { messages, temperature = 0.3 } = event const url = 'https://api.example.com/v1/chat/completions' const body = { model: 'translation-model', messages, temperature, stream: true // 开启流式返回 } // 注意:uniCloud云函数支持返回流式响应,这里需要配合前端做SSE解析 return await fetchStream(url, body, modelApiKey) }考虑到有些读者还没有接触过uniCloud,补充一句:它是uni-app配套的云开发平台,支持云函数、云数据库、云存储。如果你不打算用uniCloud,也可以用微信云开发,或者在服务器上用Node.js单独部署一个代理服务,思路都是一样的——前端不直接持有密钥,所有敏感操作收口到服务端。
3.2 流式输出的前端处理:让译文逐字"打"出来
很多人第一次用大模型翻译时会觉得"怎么这么慢"。如果等整体结果返回再显示,可能需要好几秒。加上流式输出之后,用户体验会好很多:内容一个字一个字蹦出来,用户能第一时间看到开头部分,也愿意等后面的内容。
大模型的流式返回一般走SSE(Server-Sent Events),格式是一段段以data:开头的文本,最后以一个[DONE]标记结束。前端要做的事情是解析这段流,把增量内容追加到页面上。
在uni-app里,我封装了这样一个请求函数:
// utils/request.js export function streamTranslate(payload, onChunk, onDone, onError) { const requestTask = uni.request({ url: 'https://your-cloud-function-url/translate', method: 'POST', data: payload, enableChunked: true, // 关键:开启分块接收 success(res) { if (res.statusCode === 200) onDone() }, fail(err) { onError(err) } }) // 监听分块数据 requestTask.onChunkReceived((response) => { const arrayBuffer = response.data const text = new TextDecoder().decode(arrayBuffer) // 按行解析SSE const lines = text.split('\n') for (const line of lines) { if (line.startsWith('data: ')) { const data = line.slice(6) if (data === '[DONE]') continue try { const json = JSON.parse(data) const delta = json.choices[0]?.delta?.content || '' onChunk(delta) } catch (e) { // 忽略解析失败的分片,多半是半包数据 } } } }) return requestTask }这里有个非常容易踩的坑:SSE数据在网络上传输时,一个事件块可能被拆成多个网络包,也可能多个事件块合并成一个网络包。如果直接用split('\n')去切,可能会出现解析失败。我前几个版本就在这里翻过车,后来引入了缓存队列的写法:把没解析完的残片先存起来,等下一个数据包到达时拼接在一起再解析。
let buffer = '' function handleChunk(text) { buffer += text const lines = buffer.split('\n') buffer = lines.pop() // 最后一段可能不完整,留到下次 for (const line of lines) { // 解析处理 } }另外,prettier一点的处理是加一个"停止生成"按钮。用户发现翻译不满,可以直接中断请求,这个时候调用requestTask.abort(),并且标记当前状态为已停止,避免后续的数据包继续刷新界面。
3.3 让译文更像"人话"的三个调优手段
只把文本丢给大模型,出来的结果不一定好用。我在v3.0之后开始沉淀一套翻译Prompt模板和控制规则,到v6.1.0已经是第三版。
第一个是System Prompt设计。我会明确告诉大模型它是"专业翻译助手",并给出严格要求:
你是"浔川AI翻译",一名资深专业翻译。你的任务是把用户输入的文本翻译成目标语言。 要求: 1. 保持原文格式,Markdown标记、换行、代码块不得破坏。 2. 术语保持全文一致,优先使用用户提供的术语表。 3. 译文要自然通顺,不要逐字直译。 4. 如果输入是代码,只翻译注释部分,代码本身保持不变。 5. 只输出译文,不做任何解释。最后一条"只输出译文,不做任何解释"非常重要。如果没有这一条,模型有时会自作主张加上"以下是翻译结果:"之类的提示语,干扰程序处理。
第二个是术语表注入。用户在小程序里设置的术语会拼进Prompt里,格式很简单:
用户术语表: concurrency -> 并发 throughput -> 吞吐量 latency -> 延迟每次翻译之前,程序会把术语表追加到系统指令末尾。实测对专业文档的术语一致性提升非常明显。不过要注意,术语表不能太长,超过20条后会稀释其他指令的效果,甚至导致模型注意力跑偏。目前我限制用户最多设置30条术语。
第三个是后处理规则。大模型偶尔会输出带多余空格的译文,或者把英文引号用到中文译文里,还会把全角标点混在一起。我的做法是在前端渲染前过一遍轻量级后处理:
- 把中文译文里的英文逗号、句号替换成中文标点;
- 把两个连续的空格压缩成一个;
- 去掉译文首尾的空白字符。
效果用一句话概括:用户看到的内容更干净了,虽然这些规则都很简单,但对观感的提升是实实在在的。
4. 上下文对话与历史记录:把"翻译"变成"会话"
v6.1.0这个版本,最大的改动就是上下文对话能力。之前版本是"一问一答"式的翻译,升级之后,用户可以和它连续对话,它能理解"上一句的它指的是什么"、"这句话是上一句的延续还是新话题"。
4.1 多轮上下文怎么存:滑动窗口机制
大模型本身没有记忆,所谓的"上下文意识"完全靠把历史消息重新发给它来实现。所以工程上要解决的问题是:怎么在有限的上下文窗口里,以最少的token保留最有价值的信息。
我维护一个messages数组,结构是这样的:
[ { role: 'system', content: systemPrompt }, { role: 'user', content: '请把这句话翻译成中文:The quick brown fox jumps over the lazy dog.' }, { role: 'assistant', content: '敏捷的棕色狐狸跳过了懒狗。' }, { role: 'user', content: '其中"lazy"还有别的译法吗?' } ]每次翻译新内容时,把整个数组发给大模型。但数组不能无限增长,否则token成本和响应时间都会暴涨。我采用的策略是滑动窗口:
- 只保留最近6轮(12条)消息;
- 超出的更早消息,用一条摘要替代:"前面你们讨论了XXX,话题已结束";
- 摘要由大模型在每满6轮时生成一次,和普通翻译请求一样走流式接口。
用摘要替代旧消息,是兼顾成本与效果的关键。一开始我图省事直接保留全部历史,结果翻译一篇长文章到了后面几轮,单次请求耗时暴涨,费用也明显上去了。改成滑动窗口后,单次请求稳定在2秒以内。
4.2 源语言自动识别:不需要用户手动切换
既然是"助手",就不应该让用户每次手动选"从英语翻译成中文"还是"从中文翻译成英语"。v5.0开始我加入了语言自动识别。
识别逻辑分两层:
第一层是规则判断。前端拿到输入文本后,先统计字符的Unicode范围。如果中文字符占比超过80%,判定源语言为中文;如果英文字母占比超过90%,判定为英语;否则继续走第二层。
第二层是模型兜底。规则判断不确定时,把这个任务交给大模型,让它返回一个JSON:
{ "source_lang": "ja", "target_lang": "zh" }然后把这个结果拼接进下一次翻译请求的Prompt里,保证同一次会话内语言方向是稳定的。这里有个细节:语言识别要请求一次模型,翻译又要请求一次,在用户感知上会多出几百毫秒。为了不让用户等太久,我做了个并行优化——先把文本发给云函数,云函数同时做"语言识别"和"翻译"两次调用,等两者都返回后再统一交付。虽然单次翻译没有变快,但总耗时可感知地缩短了。
4.3 历史会话的本地管理:只存译文,不存原文
翻译内容往往涉及隐私,用户可能不希望自己的文本被服务端留存。我在设计历史记录时,做了一个大胆决定:译文和原文都只存在本地存储uni.setStorageSync里,云函数只负责转发,不落盘存储。
本地存储的封装很简单:
// utils/storage.js const MAX_HISTORY = 200 export function saveHistory(record) { const list = uni.getStorageSync('history') || [] list.unshift(record) if (list.length > MAX_HISTORY) list.pop() uni.setStorageSync('history', list) }把上限设在200条,是因为微信小程序的本地存储容量有限(单个key上限约1MB),而翻译记录动辄几百字,加上时间戳和语言方向,200条已经接近安全阈值。超过这个数量自动淘汰最早的记录,类似一个简单的FIFO队列。用户也可以在设置页一键清空历史,这个按钮我放在比较显眼的位置,方便隐私敏感用户使用。
5. 界面交互与用户体验:从"能用"到"好用"
程序开发和普通网页开发有一个很大区别:用户不会给你第二次机会。界面丑一点、交互别扭一点,用户转身就走。这一节讲我在界面交互上的取舍。
5.1 输入区与结果区的布局思路
主界面我采用了上下两栏结构:上栏是译文结果区,下栏是输入区,和主流聊天软件的布局一致。这样的好处是用户已经习惯这种交互,不需要学习成本。
输入框我用的是textarea组件,做了三处优化:
- 自动增高。输入多行文字时,输入框会随着内容变高,而不是出现内部滚动条;
- 右下角的"翻译"按钮始终可见,不在键盘上方被遮挡;
- 支持粘贴检测。用户从其他App复制文本后,打开小程序时弹出气泡提示"检测到剪贴板内容,是否直接翻译",省去手动粘贴的步骤。
翻译结果区默认显示双语对照:原文在上,译文在下。用户也可以切换成"只看译文"模式,界面会清爽很多。这个开关我放在了右上角的设置图标里,而不是主界面上,目的是减少视觉噪音。
5.2 复制、朗读、重新翻译:三个提升留存率的小功能
翻译结果出来之后,用户大概率要做三件事:复制译文、听一下发音、对译文不满意重新翻译一次。这三个需求我在界面上各放了一个按钮。
复制按钮调用uni.setClipboardData,复制成功后会有一个轻提示。这里有个细节:用户有时只想复制部分内容,而不是整段。我做了手势优化,长按译文区域会弹出操作菜单,可以选"全选复制"或"部分选择"。实测下来,这个功能被使用的频率比预期的还高。
朗读功能在微信小程序环境里,用的是同声传译插件。它不仅能读译文,还可以选择男声/女声、调节语速。不过小程序插件的包体积比较大,我最终只在设置页做了一个开关,默认关闭,需要用的用户自己去打开。对于H5版本,直接调浏览器自带的SpeechSynthesis接口就行,代码量非常小。
重新翻译这个按钮是v6.0加的。加了之后我才发现,用户对译文的改译需求是刚需。有人觉得译文太文绉绉,希望换成大白话;有人希望译文更正式。所以v6.1.0里我把"重新翻译"升级成了"换一种风格",点击后弹出四个选项:直白、书面、简练、详细。选完直接带着风格指令重新请求大模型,不用把原文再粘贴一遍。
5.3 暗色模式与无障碍适配
v6.1.0另外一个显眼变化是支持了暗色模式。这里不是简单地换个背景色,而是所有颜色都走CSS变量:
page { --bg-primary: #ffffff; --bg-secondary: #f5f5f5; --text-primary: #1a1a1a; --text-secondary: #666666; } @media (prefers-color-scheme: dark) { page { --bg-primary: #1a1a1a; --bg-secondary: #2d2d2d; --text-primary: #e6e6e6; --text-secondary: #999999; } }实际开发中要注意,按钮、输入框、列表项、空状态页面的背景都要跟着变量走,否则会出现白底白字或者黑底黑字的尴尬情况。我踩过的坑是:只改了主背景和文字颜色,结果弹出菜单还是白色的,在暗色模式下亮得刺眼。后来统一排查了所有弹层、Sheet、Toast组件的配色,才算真正做完。
无障碍方面,我给关键按钮都加了aria-label,翻译结果区域的字体支持跟随系统设置放大。国内不少用户会把手机字体调到很大,如果程序不做适配,译文会显示不全。这块改动不大,但口碑提升立竿见影。
6. 性能、异常兜底与内容合规:上线前必须过的一关
程序开发最怕的不是功能做不出来,而是做出来之后线上出问题。翻译应用尤其如此,模型接口不稳定、用户输入不合法、网络环境复杂,每一个坑都可能导致用户流失。v6.1.0发布前,我把性能、异常和合规这三件事系统梳理了一遍。
6.1 请求超时与失败重试:指数退避策略
大模型服务的响应时间波动很大,高峰期可能从几百毫秒飙到十几秒。如果前端直接给用户转圈等待,体验会很差。我的策略是设置双段时间控制:
- 建立连接阶段,超时5秒;
- 流式响应阶段,如果连续30秒没有任何数据块,判定超时。
超时后的处理分三层。第一层是前端提示"请求超时,正在重试(第1次)";第二层最多重试2次,每次间隔2秒;第三层如果仍然失败,自动降级为普通翻译API——这个降级接口不需要流式,但能把结果先给用户,至少保证"能翻出来",而不是"完全不可用"。
重试之间加一个指数退避,间隔分别是2秒和4秒。不要用固定间隔,否则多个用户同时失败重试时,会给服务器造成瞬时压力。具体实现就是setTimeout(fn, 2000 * Math.pow(2, retryCount)),很简单的公式,但很管用。
6.2 渲染性能优化:长文本分段渲染
流式输出时,如果把整个译文都放进一个<text>组件里,长文本场景下,每一次增量更新都会触发整段文本的重新布局,用户会明显感到卡顿。v4.0时我做过一次实测,翻译一篇2000字的英文文档,用单组件渲染的帧率只有30fps左右,肉眼可见的掉帧。
后来改成按段落渲染:每收到一个换行符,就把当前段落追加到段落数组里,页面用v-for渲染多个段落组件。这样每次只更新最后一个段落,前面的段落已经定型,不再参与重排。优化之后,同样2000字文档的渲染帧率能稳定在55fps以上。
6.3 内容安全与合规:负责任的AI产品必须做的事
这一点必须多说几句。翻译类应用天然要面对用户输入的各种文本,如果不做任何内容安全控制,很容易被滥用成一个绕过内容审核的工具。所有AI应用开发者在把产品发布到应用市场之前,都必须想清楚这个问题。
我在这版程序里做了三层防护:
第一层,前端对输入做基本的敏感词预检,命中明显的违规词时直接拦截,提示"输入内容包含敏感信息,请修改后重试";
第二层,云函数在调用大模型之前,再跑一遍服务端的内容安全接口(微信云开发自带这个能力),对文本做一次更全面的鉴别,判断是否涉及违规或不适宜内容;
第三层,大模型返回的译文在展示给用户之前,也会用同样的内容安全接口做检查。如果译文被判定违规,前端会显示"该内容无法展示",而不是把模型输出的原文直接渲染出来。
这套逻辑在开发时确实增加了一些工作量,但我认为是必须的。一个负责任的AI产品,应该在设计阶段就考虑内容安全问题,而不是等到被通报下架再去补救。
7. 调试、打包与发布:从HBuilderX到微信小程序的完整流程
代码写完了,怎么把它变成用户手机上的一个小程序?这一步对不熟悉发布流程的新手来说,往往是最容易卡壳的。我把v6.1.0实际走过的发布链路完整记录在这里。
7.1 真机调试:先在开发者工具里跑通,再上真机
真机调试之前,先用微信开发者工具打开HBuilderX导出的编译产物,检查三件事:
第一,页面路由是否正常。重点检查从主页面跳转到历史记录、设置页面的路径是否正确;
第二,接口能否通。在开发者工具里勾选"不校验合法域名",可以暂时跳过域名校验,方便本地联调。但记住,这只是开发阶段的权宜之计,正式发布前必须配置真实域名;
第三,本地存储是否生效。在开发者工具的Storage面板里,可以直接看到history这个key的数据结构,方便排查存储逻辑问题。
真正的问题是开发者工具里一切正常,一上真机就不行。常见的有三类:
- 手机和电脑不在同一局域网,导致真机调试连不上。微信开发者工具把调试服务跑在电脑上,手机需要通过局域网访问,两个设备必须在同一WiFi下;
- 小程序的request合法域名没有配置,真机上接口直接被拦截。进入微信公众平台,在"开发管理-开发设置-服务器域名"里,把云函数域名加入request合法域名列表;
- 手机端样式错乱。开发者工具的模拟器和真机的渲染内核不完全一致,尤其是
textarea组件,在部分安卓机型上会有光标错位问题,需要针对性做兼容。
7.2 云打包配置:证书、AppID与密钥管理
HBuilderX的云打包流程,我总结成四个步骤:
- 注册小程序AppID。去微信公众平台申请,个人主体可以免费注册,审核一般1-3天;
- 在HBuilderX的
manifest.json里填入AppID,并选择"微信小程序"作为发行目标; - 配置uniCloud云函数,确保已经关联到正确的前端云空间;
- 点击"发行-小程序-微信",HBuilderX会自动编译并在微信开发者工具里打开编译产物。在开发者工具里点击"上传",就能把代码包提交到微信后台。
之后进入微信公众平台,在"版本管理"里找到刚提交的开发版本,点击"提交审核"。审核一般需要1-7天,内容涉及工具类目时,通常1-2天就能过。这里提醒一点:提审前一定要把"服务器域名"配好,否则审核人员打开小程序时所有接口都请求失败,大概率会被拒。
7.3 v6.1.0版本迭代记录:版本号不是随便打的
从v1.0到v6.1.0,中间经历了十几次小版本迭代。我习惯采用语义化版本号:主版本号表示架构性变化,次版本号表示功能新增,修订号表示问题修复和细节优化。v6.1.0的意思是:第6个大版本里,第1次功能更新,而这次更新没有包含架构重构。
简单回顾一下各版本的关键节点:
| 版本 | 核心变化 |
|---|---|
| v1.0 | 调用大模型接口完成基础翻译 |
| v2.0 | 接入流式输出,翻译过程可见 |
| v3.0 | 优化Prompt模板,加入术语一致性控制 |
| v4.0 | 增加历史记录,实现本地存储 |
| v5.0 | 支持语言自动识别,加入朗读功能 |
| v6.0 | 新增用户术语表,改译风格切换 |
| v6.1.0 | 引入上下文滑动窗口,新增暗色模式,优化长文本渲染 |
这样记录版本最大的好处是,出了线上问题能快速定位——比如v6.1.0里我改过历史记录模块,如果用户反馈历史记录丢失,先查这一版的缓存逻辑,再往前查v4.0的基础实现,排查范围会小很多。
8. 实测数据与个人使用感受
v6.1.0做完之后,我自己连续用了两周,也拉了几个朋友小范围内测。这节放一些真实数据和使用感受,方便你评估这套方案的实际效果。
8.1 翻译效果的定量对比
我拿三段典型文本做了测试:一段技术文档(英译中)、一段产品发布会演讲稿(英译中)、一段知乎问题(中译英)。对比对象是某主流在线翻译工具和浔川AI翻译v6.1.0。
| 指标 | 主流工具 | 浔川AI翻译 |
|---|---|---|
| 技术文档术语一致性 | 73% | 96% |
| 译文可读性(5分制) | 3.6 | 4.4 |
| 平均单句耗时(秒) | 0.8 | 1.9 |
| 格式保留(完整/部分/丢失) | 部分保留 | 完整保留 |
| 人工校对成本(相对值) | 基准 | 降低约60% |
可以看到,耗时确实比传统翻译API慢,但翻译质量提升非常明显。尤其是"格式保留"这一项,很多用户跟我说这是他们坚持用浔川而不是传统工具的根本原因——省去了大量重新排版的时间。
8.2 实测中的意外情况和处理
测试过程中遇到一个比较有意思的问题:大模型在翻译某些网络用语时,会把带有特定文化背景的梗直接字面翻译,导致译文完全不可读。比如"the ball is in your court"会被直译成"球在你球场里",而不是"现在轮到你行动了"。这是Prompt调优救不回来的,本质上是模型训练语料的问题。
最终的处理方案是:在术语表里允许用户添加"短语级"术语,比如:
the ball is in your court -> 现在轮到你行动了这个短语会以更高优先级注入Prompt。虽然只解决了部分问题,但至少给了用户一个可操作的兜底路径。如果你做的是面向特定行业的AI翻译应用,我强烈建议把"短语级术语库"做成标配功能。
8.3 几点真实感受
代码写到现在,最大的体会是:AI应用开发的门槛不在调用接口,而在把接口能力落成一个真正能解决用户问题的产品。流式输出、上下文、术语表、格式保留,这些细节单拎出来都不难,但组合起来才是用户愿意留下来用的理由。
还有一点是,做这类工具型小程序,千万不要想着一步到位。我的习惯是每半个月发一个小版本,每次只改一个核心体验点,然后看用户反馈数据。v6.1.0的两个重点(上下文窗口、暗色模式)都是用户反馈最集中的方向,改完之后留存率的提升是能感知到的。
技术方面我仍然在持续优化。下一步打算把术语表做成"自动提取"——用户在历史记录里手动纠正过译文的词,自动进术语库候选列表,省去手动录入的麻烦。如果你也在做AI翻译相关的小工具,建议你也试试这个方向,数据的闭环往往比功能堆砌更有价值。