3、资源文件设计(上):.ts文件、.po文件、.json文件的结构与选择标准
各位同行,咱们接着聊多语言切换。上一章讲了整体架构,今天咱们把目光聚焦到最基础、也最容易被忽视的一环——资源文件。
说白了,资源文件就是存放翻译文本的“仓库”。仓库设计得好,后续维护就轻松;设计得烂,项目后期就是一场噩梦。我见过太多项目,因为一开始随便选了个格式,结果到了量产阶段,翻译人员看不懂,工程师改不动,最后只能硬编码……嗯,那场面,别提多狼狈了。
今天咱们就掰开揉碎,聊聊三种最常见的资源文件格式:.ts、.po 和 .json。我会结合自己的实战经验,告诉你每种格式的优缺点,以及什么场景下该选谁。
3.1 .ts 文件:Qt 生态的“亲儿子”
先说说 .ts 文件。如果你用过 Qt 做 HMI,那对这个格式肯定不陌生。它是 Qt Linguist 工具链的原生格式,说白了就是给 Qt 项目量身定做的。
结构长什么样?
一个典型的 .ts 文件,本质上是 XML 格式。我截取一段实际项目中的例子:
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="zh_CN">
<context>
<name>MainWindow</name>
<message>
<source>Start</source>
<translation>启动</translation>
</message>
<message>
<source>Stop</source>
<translation>停止</translation>
</message>
</context>
</TS>
看到没?它用 <context> 来分组,每个 <message> 里包含 <source>(源文本)和 <translation>(翻译文本)。
我个人习惯用 .ts 的场景:
- 项目基于 Qt 框架(比如 QML 或 Qt Widgets)
- 团队里有专职的翻译人员,他们习惯用 Qt Linguist 工具
- 需要处理复数形式(比如英文的 "1 item" vs "2 items")
避坑指南:
我曾经在一个项目中,直接用文本编辑器手动修改 .ts 文件,结果不小心把 XML 标签搞乱了,导致整个翻译文件加载失败。后来我学乖了——永远用 Qt Linguist 工具编辑 .ts 文件,别手贱去改 XML。
3.2 .po 文件:GNU gettext 的“老兵”
接下来是 .po 文件。这个格式来自 GNU gettext 工具链,在 Linux 和开源社区里用得特别多。如果你做的是嵌入式 Linux 上的 HMI,大概率会碰到它。
结构长什么样?
看一个例子:
#: mainwindow.cpp:123
msgid "Start"
msgstr "启动"
#: mainwindow.cpp:124
msgid "Stop"
msgstr "停止"
#: mainwindow.cpp:130
msgid "Temperature: %d °C"
msgstr "温度:%d °C"
格式很简洁。msgid 是源文本,msgstr 是翻译文本。注释里还标明了源文件位置,方便定位。
我建议用 .po 的场景:
- 项目使用 C/C++ 开发,且集成了 gettext 库
- 需要支持复杂的复数规则(比如俄语、阿拉伯语有 3 种以上复数形式)
- 翻译团队熟悉 Poedit 等专业工具
一个让我印象深刻的坑:
我记得有一次,翻译人员把 msgstr 写成了空字符串,结果程序运行时直接显示空白。后来我加了个校验脚本,在编译前检查所有 msgstr 是否为空。嗯,这种小工具能省不少心。
3.3 .json 文件:Web 和轻量级方案的“万金油”
最后说说 .json 文件。这几年随着 Web 技术和轻量级 HMI 方案的流行,JSON 格式的资源文件越来越常见。它简单、直观,而且几乎所有编程语言都能解析。
结构长什么样?
{
"app": {
"title": "工业控制系统",
"version": "1.0.0"
},
"main": {
"start": "启动",
"stop": "停止",
"temperature": "温度:%d °C"
},
"alarm": {
"high_temp": "温度过高!",
"low_pressure": "压力过低!"
}
}
JSON 用键值对的方式组织,支持嵌套。你可以按模块、按页面来分组,非常灵活。
我推荐用 .json 的场景:
- HMI 基于 Web 技术(比如 React、Vue)或轻量级框架
- 需要快速原型开发,不想引入复杂的工具链
- 翻译人员懂一点技术,可以直接编辑 JSON 文件
但要注意:
JSON 文件没有原生的复数支持。如果你需要处理 "1 item" vs "2 items",得自己写代码逻辑。我曾经在一个项目中,因为没处理好复数,结果英文界面显示 "1 items"……那叫一个尴尬。
3.4 三种格式的对比与选择标准
好了,三种格式都介绍完了。你可能会问:到底该选哪个?
别急,我整理了一张对比表,一目了然:
| 特性 | .ts 文件 | .po 文件 | .json 文件 |
|---|---|---|---|
| 所属生态 | Qt | GNU gettext | 通用(Web/轻量级) |
| 格式类型 | XML | 纯文本 | JSON |
| 复数支持 | 原生支持 | 原生支持(强大) | 需自行实现 |
| 上下文分组 | 支持(context) | 支持(文件名) | 支持(嵌套对象) |
| 工具链 | Qt Linguist | Poedit, msgfmt | 任意文本编辑器 |
| 学习成本 | 中等 | 中等 | 低 |
| 适用场景 | Qt 项目 | Linux/C 项目 | Web/轻量级 HMI |
我的选择标准很简单:
- 如果项目用 Qt,别犹豫,直接上
.ts。Qt Linguist 工具链太成熟了,省心。 - 如果项目是嵌入式 Linux,且用 C/C++ 开发,
.po是首选。gettext 的复数支持无人能敌。 - 如果项目是 Web 技术栈,或者你想快速迭代,
.json最合适。简单就是力量。
核心原则:资源文件格式的选择,应该由你的技术栈和团队习惯决定,而不是凭个人喜好。我曾经见过一个团队,明明用 Qt,却非要用 JSON 做翻译文件,结果每次都要写额外的解析代码,得不偿失。
3.5 实战建议:从零开始搭建资源文件
最后,分享几个我踩过坑后总结的实战建议:
- 统一命名规范:比如
zh_CN.json、en_US.json,别用chinese.json、english.json,否则后期语言多了会乱。 - 保持键名稳定:一旦发布,尽量不要修改键名。否则所有翻译文件都得跟着改,工作量巨大。
- 预留占位符:比如
温度:%d °C中的%d,确保翻译人员知道这是变量,不要翻译成中文。 - 版本控制:资源文件一定要纳入 Git 管理。我见过有人把翻译文件放在共享文件夹里,结果被覆盖了都不知道。
好了,这一章就到这里。下一章咱们继续聊资源文件设计的下半部分——如何管理多语言文件、如何做增量更新,以及一些高级技巧。到时候见!