翻译文件格式对比:JSON / PO / XLIFF / ARB
核心问题:翻译文件应该用什么格式?各种格式有什么优缺点,何时选哪种?
真实场景
你的项目开始时用 JSON 存储翻译,但随着上线了 5 种语言,翻译团队抱怨 JSON 格式没有注释字段、不知道每条文字的上下文、专业翻译工具也不支持。是时候评估是否切换格式了。
四种主流格式对比
graph LR
JSON["JSON\n开发友好\n生态最广"] --- PO["PO/POT\nGettext 标准\n翻译工具支持最好"]
PO --- XLIFF["XLIFF\n企业级标准\nCAT 工具首选"]
XLIFF --- ARB["ARB\nFlutter/Dart 专用\n带注释"]
| 特性 | JSON | PO/POT | XLIFF | ARB |
|---|---|---|---|---|
| 文件大小 | 小 | 中 | 大 | 中 |
| 可读性 | 好 | 好 | 一般 | 好 |
| 注释/上下文 | ❌ | ✅ | ✅ | ✅ |
| 复数支持 | 需框架配合 | ✅ 原生 | ✅ 原生 | ✅ |
| 翻译状态跟踪 | ❌ | ✅ | ✅ | ❌ |
| CAT 工具支持 | 一般 | ✅ 最好 | ✅ 最好 | 一般 |
| Web 框架集成 | ✅ 最好 | 需转换 | 需转换 | Flutter 原生 |
| TMS 平台支持 | ✅ | ✅ | ✅ | 有限 |
JSON 格式
基础结构
// locales/zh-CN.json
{
"common": {
"save": "保存",
"cancel": "取消",
"loading": "加载中..."
},
"product": {
"title": "商品详情",
"addToCart": "加入购物车",
"price": "¥{amount}",
"stockCount": "{count} 件库存"
}
}
优点: - 所有 JavaScript 框架原生支持 - 结构清晰,开发者友好 - 嵌套结构支持按模块组织
缺点: - 无法添加注释(JSON 规范不支持注释) - 没有翻译状态字段(已翻译/待翻译/已过期) - 专业翻译工具支持参差不齐
JSON5 / JSONC(带注释的 JSON)
// locales/zh-CN.jsonc(仅用于开发/文档,需构建时转换)
{
"product": {
// 商品名称,最大 100 字符
"title": "商品详情",
// 购物车按钮,商品有库存时显示
"addToCart": "加入购物车",
// {amount} 是货币金额,已格式化(如 ¥129.99)
"price": "价格:{amount}"
}
}
PO / POT 格式(GNU Gettext)
最古老也是支持最广泛的翻译格式,起源于 GNU gettext 项目。
文件结构
# zh-CN.po
# 语言:简体中文
# 翻译者:团队名称 <team@example.com>
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: zh-CN\n"
"Plural-Forms: nplurals=1; plural=0;\n"
# 注释:翻译者备注
# 引用:src/components/Product.tsx:42
# 上下文:产品详情页的主标题
msgctxt "product-page"
msgid "Product Details"
msgstr "商品详情"
# 带占位符的字符串
# 引用:src/components/Cart.tsx:18
msgid "Add {productName} to cart"
msgstr "将 {productName} 加入购物车"
# 复数形式(英语)
msgid "{count} item"
msgid_plural "{count} items"
msgstr[0] "{count} 件商品"
# 模糊翻译(需要审查)
#, fuzzy
msgid "Checkout"
msgstr "结算"
# 未翻译(空字符串)
msgid "Order Summary"
msgstr ""
POT 文件(模板)
POT(PO Template)是未翻译的模板文件,从源代码提取,然后为每种语言创建 PO 文件:
# 从 Python 代码提取字符串,生成 messages.pot
xgettext --language=Python --keyword=_ --output=messages.pot src/*.py
# 为中文创建 PO 文件
msginit --input=messages.pot --locale=zh_CN --output=zh_CN.po
# 合并新字符串到已有 PO 文件
msgmerge --update zh_CN.po messages.pot
# PO 编译为二进制 MO 文件(用于运行时)
msgfmt zh_CN.po --output-file=zh_CN.mo
在 JavaScript 中使用 PO
npm install gettext.js
# 或将 PO 转换为 JSON:
npm install po2json
// 将 PO 转换为 JSON(构建时)
const po2json = require('po2json');
const fs = require('fs');
const po = fs.readFileSync('./locales/zh-CN.po', 'utf8');
const json = po2json.parse(po, { format: 'jed' });
fs.writeFileSync('./dist/locales/zh-CN.json', JSON.stringify(json));
XLIFF 格式(XML Localization Interchange File Format)
XLIFF 是 OASIS 组织制定的 XML 标准,广泛用于企业级翻译项目和 CAT 工具(Computer-Aided Translation)。
XLIFF 2.0 示例
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="2.0"
xmlns="urn:oasis:names:tc:xliff:document:2.0"
srcLang="en-US"
trgLang="zh-CN">
<file id="product-page">
<unit id="product.title">
<!-- 翻译备注,供翻译者参考 -->
<notes>
<note category="context">商品详情页的主标题</note>
<note category="max-length">30</note>
</notes>
<segment state="translated">
<source>Product Details</source>
<target>商品详情</target>
</segment>
</unit>
<unit id="product.price">
<notes>
<note category="context">{amount} 是货币金额,格式化后的字符串(如 $19.99)</note>
</notes>
<segment state="translated">
<source>Price: {amount}</source>
<target>价格:{amount}</target>
</segment>
</unit>
<!-- 未翻译的条目 -->
<unit id="product.wishlist">
<segment state="initial">
<source>Add to Wishlist</source>
<target></target>
</segment>
</unit>
<!-- 需要审查的条目 -->
<unit id="product.share">
<segment state="reviewed">
<source>Share</source>
<target>分享</target>
</segment>
</unit>
</file>
</xliff>
XLIFF 状态值
| 状态 | 含义 |
|---|---|
initial | 未翻译 |
translated | 已翻译,待审查 |
reviewed | 已审查 |
final | 最终定稿 |
ARB 格式(Application Resource Bundle)
ARB 是 Flutter/Dart 官方使用的翻译格式,JSON 的超集,支持元数据注释。
// app_zh.arb
{
"@@locale": "zh",
"@@last_modified": "2026-03-22T00:00:00Z",
"appTitle": "我的应用",
"@appTitle": {
"description": "应用的主标题,显示在导航栏"
},
"productPrice": "价格:{price}",
"@productPrice": {
"description": "商品价格显示",
"placeholders": {
"price": {
"type": "String",
"example": "¥129.99"
}
}
},
"cartItemCount": "{count, plural, =0{购物车是空的} other{{count} 件商品}}",
"@cartItemCount": {
"description": "购物车商品数量",
"placeholders": {
"count": {
"type": "int"
}
}
}
}
// Flutter 使用
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
Text(AppLocalizations.of(context)!.productPrice(price: '¥129.99'));
格式选型决策树
flowchart TD
A[选择翻译文件格式] --> B{使用的框架?}
B -->|Flutter/Dart| C[ARB ✅]
B -->|Python/PHP/Ruby| D[PO/Gettext ✅]
B -->|React/Vue/Next.js| E{团队规模?}
E -->|小型 < 3 人| F[JSON ✅]
E -->|中型 3-10 人| G{使用 TMS 平台?}
G -->|是| H{平台支持?}
H -->|支持 XLIFF| I[XLIFF ✅]
H -->|支持 JSON| J[JSON ✅]
G -->|否| K[JSON 或 PO ✅]
E -->|大型 > 10 人| L[XLIFF 或 PO ✅]
推荐方案
| 场景 | 推荐格式 | 理由 |
|---|---|---|
| Next.js / Nuxt.js 中小项目 | JSON | 框架原生支持,开发效率最高 |
| 需要专业翻译团队 | PO 或 XLIFF | 翻译工具(OmegaT、SDL Trados)支持最好 |
| 使用 Crowdin/Lokalise TMS | JSON 或 XLIFF | 两者都支持,XLIFF 保留更多上下文 |
| Flutter 应用 | ARB | Flutter 官方标准 |
| 跨平台(iOS/Android/Web) | XLIFF | iOS .strings/Android .xml 可互转 |
格式转换工具
# JSON → PO
npm install i18next-conv
i18next-conv -l zh-CN -s locales/zh-CN.json -t locales/zh-CN.po
# PO → JSON
i18next-conv -l zh-CN -s locales/zh-CN.po -t locales/zh-CN.json
# XLIFF → JSON(使用 xliff-simple-merge 或 xliff-conv)
npm install xliff
// 程序化转换 XLIFF → JSON
const xliff = require('xliff');
const xliffContent = fs.readFileSync('translations.xliff', 'utf8');
const result = await xliff.xliff2js(xliffContent, { /* options */ });
// result: { resources: { 'product-page': { 'product.title': { source: 'Product Details', target: '商品详情' } } } }
下一节:格式选好了,接下来解决翻译 key 的命名混乱问题——如何规划 namespace 结构,让翻译文件可维护。