跨境项目做久了,最容易磨人的往往不是业务逻辑,而是语言切换。产品经理在中文群里提需求,研发那边用日文回进度,海外客户发来的验收文档全是英文,偶尔还夹着西班牙语的备注。每天打开工作群,至少三四门语言在同一个项目的沟通流里来回穿梭。过去我处理这些问题,基本靠复制粘贴到在线翻译页面,遇到Excel里的几十行原文还必须逐条操作,一天下来光是倒腾“语言”就耗掉两三个小时。
后来我在 OpenClaw 里挂了一个 text-translate 技能,把多语言批量翻译这件事彻底交给自动化,才从这种重复劳动里脱身。简单说,这个技能接收一个文本、一个表格文件或一整个目录,自动识别语言、按目标语言翻译、保留原有格式并输出到指定位置。它不依赖任何一个在线翻译网页,不管翻译十行还是五千行,都能一次性跑完。这套东西很适合跨境团队、外贸跟单、内容出海和一切需要频繁处理多语言资料的岗位。
下面我把这套技能的搭建思路、核心结构、实际配置和踩坑记录完整梳理一遍,给同样被语言切换折磨的同行一个可以直接抄作业的参考。
1. 为什么需要这样一个批量翻译技能
先说一个反直觉的结论:跨境沟通的难点,多数时候不在“翻译质量”,而在“翻译动作的密度”。一段中文需求翻译成英文,机器翻译质量早就够用了。真正让人崩溃的,是一天要做三十次“复制—粘贴—等结果—再复制—再粘贴”,而且这三十次操作往往穿插在写代码、回消息、改文档之间,注意力被反复切碎。
我们组曾经做过一个统计:一个五人的跨境项目小组,每周花在翻译相关动作上的时间,人均大约六小时。其中只有不到三成时间真的在看译文,其余时间都耗在搬运文本和整理格式上。如果只靠人工逐条翻译,这个消耗根本省不下来。
把翻译做成“技能”而不是一个普通脚本,最大的差别在于它能理解任务边界。普通脚本会老老实实翻译你喂给它的每一行文本,不管那行是不是真的需要翻译。text-translate 技能会先做语言检测,跳过本来就符合目标语言的段落,再结合术语表统一专有名词的翻译口径,最后按原文结构输出。比如源文件里混着中英文,目标是英文,那些已经是英文的标题和代码片段会被原样保留,不会被画蛇添足地“翻译”一遍。
我见过不少人用在线翻译工具批量处理文档,结果把产品型号翻译得五花八门:同一款设备,一会儿叫“Model A”,一会儿叫“Type A”,一会儿又是“A型”。这种不一致在人工逐条操作时几乎无法避免,因为在线翻译工具没有记忆,每一条查询都是独立的,不会参考上一条怎么翻。而 text-translate 技能可以通过术语表统一口径,把所有“A型”都锁定为“Model A”。这看起来是小事,但对客户来说,命名不统一直接反映项目管理的专业度。
还有一点常被忽视:批量处理配合缓存,能省下大量的重复成本。跨境项目里,日报、周报、状态同步的文字高度重复,比如“进度正常”“风险可控”“待客户确认”这些句子每周都在出现。普通在线翻译每次都会重新请求一遍,而 text-translate 技能默认开启缓存,按原文哈希值记录译文,重复内容直接命中缓存,不消耗翻译额度,也不浪费时间。
2. text-translate 技能的核心结构拆解
这套技能本质上是一个挂在 OpenClaw 技能目录下的独立插件,由清单文件、翻译引擎和术语表三部分组成。下面逐个拆开看。
2.1 技能清单文件的作用
OpenClaw 的技能体系里,每个技能都靠一个清单文件声明自己是谁、能干什么、接收哪些参数。text-translate 的清单文件长这样:
name: text-translate description: 多语言批量翻译,支持自动识别源语言、术语表统一、格式保留 version: 1.2.0 trigger: - 翻译 - 批量翻译 - 翻译到 parameters: source_path: type: string required: false description: 待翻译的文本、文件路径或目录 target_language: type: string required: true description: 目标语言,如 zh、en、ja、es domain: type: string required: false default: general description: 领域术语表名称,如 it、finance、legal capabilities: formats: - txt - md - csv - xlsx - json max_batch_size: 5000 max_file_size_mb: 20 cache: true这段配置的核心思路是“把能力显式声明出来”,OpenClaw 在运行时会根据 trigger 里的关键词识别用户意图,比如你在对话里输入“把这份文件翻译成日文”,框架会命中翻译和翻译到这两个触发词,然后自动绑定 text-translate 技能去处理。
参数设计上有个细节值得注意:source_path 不是必填的。这是故意留的弹性,因为有时候用户只想翻译一句话,直接在对话框里给原文就够了,没必要走文件路径。但如果你丢了一个目录进去,技能就会进入批量模式,遍历目录下所有支持格式的文件,逐个翻译并写到对应的输出目录。
max_batch_size 和 max_file_size_mb 这两个上限参数也值得说说。翻译引擎每次调用上下文长度有限,所以超过上限的文本必须分批。文本类文件限制在 20MB 以内,这是为了避免有人直接把一个几十 GB 的日志文件丢进去,把翻译队列堵死。真遇上大数据量场景,应该先用脚本做数据抽取,而不是让翻译技能硬啃原始文件。
2.2 翻译引擎的双轨选型
text-translate 支持两套翻译引擎:云端接口和本地模型。清单里不需要固定选哪个,而是通过配置文件在运行时决定。
我在项目里用的是混合策略:默认走本地模型,敏感数据和客户机密走本地强制模式,大批量低优先级的翻译请求才切到云端接口。这么设计的原因很直接:跨境项目的文档往往包含报价信息、验收标准、人员名单,这些内容如果直接发给外部服务,合规上是有风险的。
本地模型的配置项大概这样:
engine: provider: local model: translation_model_small device: cpu max_length: 512 num_workers: 4如果你手上有不错的显卡,把 device 改成 cuda,num_workers 可以拉到 8,吞吐量会明显提升。本地模型的翻译质量近两年进步非常大,日常沟通、邮件、产品文档这种场景完全够用,唯一要忍受的是首次加载模型需要几十秒。
云端接口的优势是长文本处理能力强,翻译文学性文本时的流畅度更好,但每次调用都会产生费用,而且把文本发到外部服务这件事本身就要先在团队内部过一遍合规评估。成熟的用法是“内网文档走本地,公开资料走云端”,两者用同一个技能入口,用户根本不用关心底层走的哪条路。
2.3 术语表:统一翻译口径的关键
术语表是 text-translate 最容易被忽视也最值得花时间配置的部分。它的实现方式很简单,一个 YAML 文件记录原文和译文的固定映射:
terms: - en: delivery zh: 交付物 ja: 成果物 - en: milestone zh: 里程碑 ja: マイルストーン - en: acceptance criteria zh: 验收标准 ja: 受入基準 - en: Model A zh: A型设备 ja: A型デバイス翻译时,技能会做两层处理:源文本里出现的术语直接按映射表替换,不回翻译引擎;翻译引擎返回的译文里,如果检测到术语的另一种译法,也会被纠正回标准口径。这样就保证了整套项目文档里“milestone”永远都是“里程碑”,不会某一天突然变成“阶段节点”。
这个设计背后是我踩过的一个坑:早期不用术语表的时候,英文文档翻译成中文,这周叫“里程碑”,下周叫“阶段节点”,同一个词在客户面前出现了两个叫法,对方专门发邮件来问是不是两个不同的交付节点。从那之后,所有跨境翻译任务我都强制挂术语表,哪怕只是一个只有二十个词的小表,也能避免八成以上的口径混乱。
2.4 格式保留机制
跨语言沟通里,最烦的不是文字翻译得不好,而是翻译完了格式全乱了。Excel 表格翻译完,列宽变了;JSON 文件翻译完,引号丢了;Markdown 文档翻译完,超链接语法被转义了。这些问题在人工处理时很难避免,因为人眼关注的是文字本身,格式错误往往要等到交付之后才被发现。
text-translate 的做法是先解析格式,再翻译文本节点,最后按原结构重建文件。对 Excel 而言,它遍历每个单元格,标记出哪些是内容、哪些是公式,只翻译内容部分,公式保持原样;对 JSON 而言,它递归遍历所有 key 和 value,key 保持原文(避免改坏接口),value 才进入翻译流程。
为了做到这点,技能的底层实现针对不同格式走了不同解析器,而不是简单地把文件读成纯文本。这一点大家在参考实现的时候要特别注意,如果自己写脚本处理,强烈建议先用真实文件做一次格式守恒的回归测试,再交给业务方使用。
3. 从零配置到批量落地
这一节我直接给出完整的落地过程。下面的操作在 OpenClaw 当前常见版本上验证过,稍微改改路径就能直接照搬。
3.1 创建技能目录与清单
先建目录结构,把技能放到 OpenClaw 的 skills 目录下:
mkdir -p skills/text-translate cd skills/text-translate touch manifest.yaml translate.py terms.yaml README.md把第一节的 manifest.yaml 内容填进去,注意 version 字段建议跟着迭代走,方便以后排查问题。manifest 里的 trigger 词最好和团队日常用语保持一致,如果你们习惯说“翻一下”“转成英文”,就加进 trigger 列表,否则用户说了话但技能不触发,会很让人困惑。
3.2 实现翻译主流程
translate.py 的核心逻辑可以拆成四步:读输入、切分文本、调用引擎、写回结果。伪代码如下:
import json import hashlib from pathlib import Path from concurrent.futures import ThreadPoolExecutor class TextTranslator: def __init__(self, engine, term_map, cache_backend): self.engine = engine self.term_map = term_map self.cache_backend = cache_backend def translate_text(self, text, source_lang, target_lang): # 先做原文哈希,命中缓存直接返回 text_hash = hashlib.md5( f"{source_lang}:{target_lang}:{text}".encode() ).hexdigest() cached = self.cache_backend.get(text_hash) if cached: return cached # 执行术语替换 normalized = self._apply_terms(text, source_lang, target_lang) result = self.engine.translate(normalized, target_lang) self.cache_backend.set(text_hash, result) return result def translate_file(self, file_path, target_lang): suffix = Path(file_path).suffix.lower() if suffix in (".xlsx",): return self._translate_excel(file_path, target_lang) elif suffix in (".json",): return self._translate_json(file_path, target_lang) else: return self._translate_plain_text(file_path, target_lang) def _translate_excel(self, file_path, target_lang): from openpyxl import load_workbook wb = load_workbook(file_path) for sheet in wb.worksheets: for row in sheet.iter_rows(): for cell in row: if isinstance(cell.value, str) and cell.value.strip(): cell.value = self.translate_text(cell.value, "auto", target_lang) output_path = file_path.with_name(file_path.stem + "_" + target_lang + file_path.suffix) wb.save(output_path) return output_path这段代码在实现层面有几个值得推敲的地方。
第一个是缓存 key 的构造。我把源语言、目标语言和原文拼在一起做哈希,这意味着同样的中文“待确认”翻译成日文和英文会分别缓存,互不干扰。如果把语言信息去掉,改成只按原文哈希,就会出现一个严重问题:某条文本第一次请求的是英文译文,缓存记录下来了,下一次同样的原文要日文译文,结果命中了英文缓存,直接返回错误结果。这个坑我早期踩过,后来在测试用例里专门加了一条“同一源文不同目标语言”的回归测试才堵住。
第二个是 Excel 遍历时只处理字符串类型的单元格。数值型单元格如果被翻译引擎误判成文本,转了一圈回来 1.25 可能变成“一点二五”,数字变字符串,下游数据处理全崩。所以这里必须加 isinstance 检查,只对 str 类型的值做翻译。
第三个是输出的文件名。我在原文件名后追加了目标语言后缀,report_ja.xlsx这样,翻译文件不会覆盖源文件。这个设计一开始觉得多余,后来发现太必要了——如果没有后缀,团队里有人不小心把输出文件放回源目录,源文件就被覆盖了。
3.3 批量目录处理与并发控制
单文件翻译只是基础,批量目录才是省时间的关键。当输入参数是一个目录时,技能会递归扫描所有支持格式的文件,并分发到线程池执行:
def translate_directory(self, dir_path, target_lang): files = [ p for p in Path(dir_path).rglob("*") if p.suffix.lower() in {".txt", ".md", ".csv", ".xlsx", ".json"} ] with ThreadPoolExecutor(max_workers=4) as executor: futures = { executor.submit(self.translate_file, f, target_lang): f for f in files } results = {} for future in as_completed(futures): source = futures[future] results[source] = future.result() return results并发数控制成 4 是我调出来的经验值。本地 CPU 推理时,并发太高会导致线程之间争抢计算资源,单条翻译的延迟反而变长;并发太低又压不满 CPU。如果你跑的是云端接口,并发可以适当调高到 8 或 12,但要注意接口 QPS 的限制,否则会触发限流,报一堆 429 错误。
批量处理时的日志输出也很关键。我建议每完成一个文件,就打印一条包含文件名和耗时的日志,同时在最终结果里返回所有失败文件列表。批量跑三四十个文件的时候,没有日志就只能干等,完全不知道卡在哪一步。
3.4 调度运行
配置完成后,日常使用非常简单,直接在 OpenClaw 会话里下指令就行:
把 inbound/产品手册_CN.xlsx 翻译成日文,输出到 outbound/技能会自动识别表格、逐行翻译、保留格式并输出新文件。如果是一次性翻译一大段文本,直接贴原文:
翻译到英文:本周已完成设备联调,等待客户反馈参数,预计周五给出验收结论。这种用法最舒服的地方在于,没有上下文割裂感——前一秒还在聊项目计划,下一秒丢一句原文进去,译文直接回到当前对话里,不需要切换到另一个标签页。对于每天都要跨语言沟通的人来说,这个体验比打开翻译网站强太多了。
4. 真实项目里的三种典型用法
配置好的技能不能只是摆着,下面用我们实际跑过的三个场景展示它的价值。
4.1 日报和会议纪要的批量翻译
项目进入联调阶段后,我们每天要同步中英文两种版本的日报。过去是中文写好,再人工翻译成英文发到海外同步群,一篇日报大概 200 字,但这 200 字要消耗十五分钟。现在流程变成了:中文日报写好存到固定目录,技能检测到新文件后自动翻译,生成daily_report_en.md,发送逻辑按文件名取对应语言版本。
这个场景里,真正被节约的不是翻译时间,而是“切换上下文”的时间。以前写日报,写完中文还要想“接下来切换英文思维再写一遍”,现在中文写完就结束了,英文版本由技能代劳,虽然翻译水平不如人工润色,但作为同步信息完全达标。
4.2 产品文档多语化发布
有一次我们要把一套产品操作手册从中文翻译成英文和日文两个版本,手册一共 12 个 Markdown 文件,合计大约六万中文字符。人工翻译找供应商报价,最快要一周;用 text-translate 技能跑,创建术语表花了一小时,翻译加审校一共两个工作日。
具体做法是先把手册里的高频术语拉出来,比如“设备联调”“远程升级”“告警阈值”,建好三语术语表。然后把 12 个文件全部丢进一个目录,一条指令批量翻译。中途遇到几个长句子拆行导致的翻译断裂,通过调整切分策略解决了。最终产出物是三个语言的完整手册目录,格式和排版全部保留,直接发布到帮助中心。唯一的人工工作是找母语同事做了一遍校对,重点关注翻译回英文后是否出现歧义。
4.3 客户消息与工单的辅助沟通
跨境项目里,客户支持工单经常是英文进、中文出,回复前要理解客户意图,回复时要组织英文表达。text-translate 在我们这里承担了“辅助草拟”的角色:收到英文客户消息,先让技能翻成中文快速理解;然后写下中文回复要点,让技能翻成英文,自己再润色一遍发出。
这里有个实用技巧:尽量翻译整段消息,不要只翻译某个词。因为整段翻译保留了上下文语境,客户说“The device is not responding to our commands”和“not responding”单独翻译,后者很容易变成“无响应”,放在完整句子里才能准确理解是对“指令”无响应。text-translate 按段落粒度切分句子,也能更好地保留这个语境。
5. 常见问题与排查技巧实录
这套技能跑了半年多,积攒了不少典型问题,下面把高频问题和排查方法列成速查表,给后来者排雷。
| 现象 | 根本原因 | 解决办法 |
|---|---|---|
| 翻译出来全是乱码 | 文件编码不是 UTF-8,常见于 Windows 下生成的 TXT/CSV | 读取时强制指定编码,优先尝试 utf-8-sig 处理 BOM,再回退 gbk |
| Excel 翻译完公式消失 | 直接遍历单元格时把含公式的单元格当文本翻译了 | 判断 cell.data_type,公式类型(f)跳过翻译只保留公式 |
| 同一个词翻译口径不一致 | 术语表没配置或没挂载成功 | 检查 manifest 里 domain 参数是否匹配术语表目录名 |
| 长文本翻到一半截断 | 目标模型或接口有输入长度限制 | 按句子边界切分成短句块,逐块翻译再拼接 |
| 批量任务频繁请求超时 | 并发数超了接口 QPS 限制 | ThreadPoolExecutor 调低 max_workers,加上指数退避重试 |
| 翻译结果风格前后不一致 | 无状态翻译,每条请求独立 | 在切分时给每块文本加上上下文提示,或统一用一个线程按顺序处理 |
| 缓存命中率低 | 文本里带时间戳、日期等高频变动词 | 翻译前做标准化处理,将纯数字日期替换为占位符再进哈希 |
| 敏感性内容外泄风险 | 数据走了云端接口 | 配置文件里把 engine.provider 锁成 local,云端接口只开放给脱敏数据 |
5.1 编码问题是最隐蔽的坑
跨境团队的文件来自不同地区的人,Windows 中文系统导出的 CSV 常常是 GBK,Mac 上生成的文件是 UTF-8,还有人会存成带 BOM 的 UTF-8。直接按 UTF-8 读 GBK 文件,轻则中文乱码,重则读取抛异常中断整个批量任务。
我的处理方案是写一个编码探测函数,按优先级尝试 utf-8-sig、utf-8、gbk、latin-1,每种编码试读并检测是否出现 Unicode 乱码特征,如果探测失败就跳过该文件并记录日志。这个探测逻辑值得在每个文本型解析器开头复用,一次配置,终身收益。
5.2 长文本切分不能硬切
本地模型和很多云端接口都有最大输入长度限制,超过限制会直接报错或静默截断。静默截断最危险,因为你拿到的译文是完整的,但其实是翻译了半句话之后的结果,意思可能完全对不上。
切分策略上,我测试下来效果最好的是按语义边界切分:优先按段落,其次按句号、分号、换行,最后才按字符数硬切。单纯按字符数切,很容易把一句完整的话拦腰截断,翻译出来的后半句有时会凭空多出前半句的意思。一个可行的实现:先把文本按换行符拆成行,再把超长行按句号拆分,保证每次送入引擎的块都是语义完整的句子。
5.3 语境丢失与风格不稳定
无状态翻译的通病是缺少上下文。一个词在技术文档里和日常对话里的译法完全不同,如果切分粒度太细,“interface”可能被翻成“界面”,但在软件体系里客户更常理解成“接口”。术语表能解决一部分问题,但语境歧义需要更聪明的办法。
我在实现里加了一个可选的“上下文前缀”参数:翻译之前,把文档标题、所在的章节标题、前后句的前五个词拼进提示里,再让引擎翻译当前句。比如:
章节标题:设备联调流程 待翻译文本:连接成功后等待响应。这样翻译引擎知道当前句子处于“联调”语境,“连接”就更可能翻成“connection”,“响应”更可能翻成“response”,而不是“reaction”。这个技巧不需要改模型,只是改变输入的组织方式,效果立竿见影。
5.4 缓存策略的细节
缓存对批量翻译的提速非常明显。我们的日报翻译任务,每周的文本重复率在百分之四十左右,命中缓存的句子直接秒出,整体翻译时间能缩短三分之一。
但缓存也有一些副作用要注意。如果云端接口的翻译质量通过版本升级改善了,旧缓存会让新版本不生效。所以我在缓存表里加了一个 engine_version 字段,引擎版本变化时自动失效全部缓存。这个机制用得上的人可能不多,但一旦需要,手动清缓存真的是一个很蠢的运维操作。
最后分享一点个人体会
搭建 text-translate 技能的过程中,我最深刻的感受是:工具本身不复杂,复杂的是搞清楚自己真正要解决什么问题。如果只是偶尔翻一两句话,在线翻译工具完全够用,不值得折腾这一整套;但如果你的工作节奏是每天面对几十个跨语言文件,那么花半天时间搭这样一个批量翻译技能,投资回报率非常高。
还有一点想提醒大家:批量翻译省下来的时间,一定要拿出一部分做“质量抽检”。再好的术语表、再聪明的切分策略,都不能保证译文百分百没歧义。我们团队的做法是:正式对外交付的文档,必须经过母语同事一轮校对;内部同步用的日报和纪要,直接交给技能翻译,但发送之前快速扫一眼关键数字和结论句就够了。机器翻译负责把人的时间从搬运中解放出来,但最终的质量责任还是要由人来承担。
这个技能后续还可以扩展的方向很多——比如把缓存改成集中存储,让全团队共享翻译记忆;或者接上消息平台的事件流,新消息进来自动触发翻译并把结果回填。如果你也在做跨境项目,建议先从一份术语表开始,它是整个技能里投入产出比最高的一环。