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 实战建议:从零开始搭建资源文件

最后,分享几个我踩过坑后总结的实战建议:

  1. 统一命名规范:比如 zh_CN.jsonen_US.json,别用 chinese.jsonenglish.json,否则后期语言多了会乱。
  2. 保持键名稳定:一旦发布,尽量不要修改键名。否则所有翻译文件都得跟着改,工作量巨大。
  3. 预留占位符:比如 温度:%d °C 中的 %d,确保翻译人员知道这是变量,不要翻译成中文。
  4. 版本控制:资源文件一定要纳入 Git 管理。我见过有人把翻译文件放在共享文件夹里,结果被覆盖了都不知道。

好了,这一章就到这里。下一章咱们继续聊资源文件设计的下半部分——如何管理多语言文件、如何做增量更新,以及一些高级技巧。到时候见!