人工翻译工作流设计
High Contrast
Dark Mode
Light Mode
Sepia
Forest
3 min read626 words

人工翻译工作流设计

核心问题:如何建立可持续运行的翻译工作流?如何让翻译团队高效工作、减少沟通成本?


真实场景

你的产品每周新增约 50 个翻译字符串,有一支 3 人翻译团队(中→英、中→日、中→韩各一人)。目前通过邮件发 Excel,翻译完后开发者手动复制回代码,既低效又容易出错。


翻译工作流的三个核心问题

  1. 触发:什么时候需要翻译?(代码变更时)
  2. 分发:如何把待翻译内容给到翻译团队?
  3. 回流:翻译完成后如何回到代码仓库?

工作流架构

graph LR Dev["开发者\n写代码,提取 key"] --> Git["Git 仓库\n翻译文件"] Git --> Export["导出待翻译内容\n(新增/变更 key)"] Export --> Trans["翻译团队\n翻译、校对、审核"] Trans --> Import["翻译结果导入"] Import --> Git Git --> CI["CI 检查\n完整性验证"] CI --> Prod["生产部署"]

构建高效翻译包

差异化导出(只发送新增/变更内容)

// scripts/export-for-translation.js
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
const SOURCE_LOCALE = 'zh-CN';
const LOCALES_DIR = 'src/locales';
// 获取相对于上一个 release tag 的变更
function getChangedFiles() {
try {
const lastTag = execSync('git describe --tags --abbrev=0').toString().trim();
const changed = execSync(
`git diff --name-only ${lastTag}...HEAD -- ${LOCALES_DIR}/${SOURCE_LOCALE}/`
).toString().trim().split('\n').filter(Boolean);
return changed;
} catch {
// 没有 tag 时,返回所有文件
return fs.readdirSync(`${LOCALES_DIR}/${SOURCE_LOCALE}`)
.map(f => `${LOCALES_DIR}/${SOURCE_LOCALE}/${f}`);
}
}
// 找出需要翻译的 key(已有翻译的语言中,对应 key 为空)
function findUntranslatedKeys(namespace, targetLocale) {
const sourcePath = path.join(LOCALES_DIR, SOURCE_LOCALE, `${namespace}.json`);
const targetPath = path.join(LOCALES_DIR, targetLocale, `${namespace}.json`);
if (!fs.existsSync(sourcePath)) return {};
const source = JSON.parse(fs.readFileSync(sourcePath, 'utf-8'));
const target = fs.existsSync(targetPath)
? JSON.parse(fs.readFileSync(targetPath, 'utf-8'))
: {};
// 扁平化后对比
const flat = (obj, prefix = '') => Object.entries(obj).reduce((acc, [k, v]) => {
const key = prefix ? `${prefix}.${k}` : k;
return typeof v === 'object' ? { ...acc, ...flat(v, key) } : { ...acc, [key]: v };
}, {});
const sourceFlat = flat(source);
const targetFlat = flat(target);
return Object.fromEntries(
Object.entries(sourceFlat).filter(([key]) => !targetFlat[key] || targetFlat[key] === '')
);
}
// 生成翻译包(XLSX 格式,包含上下文信息)
const targetLocales = ['en-US', 'ja-JP', 'ko-KR'];
const namespaces = ['common', 'product', 'cart', 'checkout', 'errors'];
const timestamp = new Date().toISOString().split('T')[0];
for (const locale of targetLocales) {
const untranslated = {};
for (const ns of namespaces) {
const missing = findUntranslatedKeys(ns, locale);
if (Object.keys(missing).length > 0) {
untranslated[ns] = missing;
}
}
if (Object.keys(untranslated).length > 0) {
const outputPath = `translation-packages/${timestamp}-${locale}.json`;
fs.mkdirSync('translation-packages', { recursive: true });
fs.writeFileSync(outputPath, JSON.stringify(untranslated, null, 2));
console.log(`✅ 生成翻译包: ${outputPath} (${Object.values(untranslated).reduce((sum, ns) => sum + Object.keys(ns).length, 0)} 条待翻译)`);
} else {
console.log(`ℹ️  ${locale}: 无需翻译`);
}
}

翻译包格式(带上下文)

// translation-packages/2026-03-22-en-US.json
{
"product": {
"detail.newBadge": {
"source": "新品",
"context": "商品详情页右上角的标签,表示新上架商品",
"maxLength": 10,
"screenshot": "https://cdn.example.com/screenshots/product-detail.png"
},
"detail.limitedOffer": {
"source": "限时优惠",
"context": "价格旁边的标签,表示限时折扣活动",
"maxLength": 20
}
},
"checkout": {
"payment.splitPayment": {
"source": "分期付款",
"context": "结算页面的付款方式选项之一",
"maxLength": 30
}
}
}

上下文提供最佳实践

上下文(Context)是翻译质量的关键。翻译人员在不知道字符串使用场景的情况下,很容易选错词义。

上下文字段

interface TranslationContext {
source: string;        // 源语言文字
context: string;       // 使用场景说明
maxLength?: number;    // 最大字符数限制(UI 空间限制)
screenshot?: string;   // 截图 URL
characterLimit?: {
characters: number;
note: string;        // 说明(如"导航菜单,空间有限")
};
glossary?: string[];   // 需要使用的术语表词汇
doNotTranslate?: string[]; // 不翻译的词(品牌名、技术术语)
}

在代码中添加上下文注释

// 使用 i18n-ally 识别的特殊注释格式
// @i18n-context: 商品价格标签,显示在商品卡片右上角
// @i18n-max-length: 20
t('product.priceTag')
// 或者在 key 命名时就传达上下文
// 明确的 key 名比任何注释都有效
t('product.card.discountBadge')    // 比 t('badge') 清晰多了
t('checkout.shippingAddress.city') // 明确是结算地址的城市字段

术语表管理

// glossary/zh-CN-en-US.json
{
"platform": "Platform",         // 平台(不翻译为 stage)
"SKU": "SKU",                   // 不翻译
"闪购": "Flash Sale",           // 固定译法
"满减": "Spend & Save",         // 营销术语,固定译法
"收藏": "Wishlist",             // 不用 Save/Favorite
"店铺": "Store",                // 不用 Shop
"大促": "Major Campaign",       // 不用 Big Sale
"运费险": "Shipping Insurance"  // 专有名词
}

翻译人员在翻译前必须阅读术语表,保证品牌词汇和专业术语的一致性。TMS 平台(Crowdin/Lokalise 等)支持自动高亮术语表词汇。


审校流程设计

graph LR T["翻译人员\n初稿"] --> R["审校人员\n语言审查"] R --> QA["QA\n产品上下文确认"] QA -->|通过| G["Git PR\n合并"] QA -->|返修| T R -->|重大问题| T

四眼原则

# 翻译审校流程
## 审校维度
| 维度 | 审校要点 |
|------|---------|
| 准确性 | 含义是否与原文一致?是否有遗漏或增添? |
| 流畅性 | 是否符合目标语言的表达习惯? |
| 一致性 | 与术语表和历史翻译是否一致? |
| 长度 | 是否超出 UI 字符限制? |
| 格式 | 占位符({name})是否保留且位置合理? |
| 文化适当性 | 是否存在文化冒犯风险? |
## 审校者职责
- 不改变原文含义
- 与翻译人员讨论分歧(不单方面修改)
- 维护并更新术语表

翻译记忆(Translation Memory)

翻译记忆(TM)是过去翻译过的句子数据库,当新内容与历史翻译相似时自动建议:

// translation-memory/zh-en.tmx(TMX 格式,标准 TM 格式)
<?xml version="1.0" encoding="utf-8"?>
<tmx version="1.4">
<header creationtool="custom" datatype="plaintext" segtype="sentence" srclang="zh-CN"/>
<body>
<tu>
<tuv xml:lang="zh-CN"><seg>加入购物车</seg></tuv>
<tuv xml:lang="en-US"><seg>Add to Cart</seg></tuv>
</tu>
<tu>
<tuv xml:lang="zh-CN"><seg>商品详情</seg></tuv>
<tuv xml:lang="en-US"><seg>Product Details</seg></tuv>
</tu>
</body>
</tmx>

TMS 平台(Crowdin、Lokalise、Phrase)会自动管理 TM,相似度超过阈值(通常 75%+)的字符串会自动应用历史翻译建议,大幅提升效率、降低成本。


翻译工作量估算

语言 每千词价格(参考) 每日产出(专业译者) 特殊要求
中→英 $80-150 2000-3000 词 最多可用资源
中→日 $120-200 1500-2500 词 需要日语母语者
中→韩 $100-180 1500-2000 词 需要韩语母语者
中→阿拉伯 $150-250 1000-1500 词 RTL,需测试
中→俄 $100-160 1500-2000 词 西里尔字母

翻译债务管理

# 统计各语言翻译债务(未翻译字符数)
node scripts/i18n-debt-report.js
# 输出示例:
# 语言      | 总 Key | 已翻译 | 完成率 | 估计工作量
# en-US     | 1250   | 1250   | 100%  | 完成
# ja-JP     | 1250   | 1100   | 88%   | 约 2 天
# ko-KR     | 1250   | 800    | 64%   | 约 5 天
# ar-SA     | 1250   | 400    | 32%   | 约 10 天(含 RTL 测试)

下一节:纯人工翻译成本高,对于低优先级内容可以先用机器翻译快速填充,再让人工审查。

DeepL / Google Translate API 机器翻译集成