3. tr()函数详解:tr()的基本用法、上下文参数、复数处理、歧义消除
好,咱们今天来聊聊 tr() 这个函数。说实话,这是 Qt 国际化里最核心、也最容易踩坑的地方。我刚开始做国际化那会儿,以为就是简单地把字符串包一层 tr() 就完事了。结果呢?程序跑起来,该翻译的没翻译,不该翻译的反而被提取了……嗯,后来才明白,tr() 背后藏着不少门道。
3.1 tr() 的基本用法
先看最简单的场景。你有一个字符串,想让它在不同语言下显示不同的内容。那就用 tr() 包起来:
// 最简单的用法
ui->label->setText(tr("Hello"));
这行代码的意思是:告诉 Qt,「这个 "Hello" 需要被翻译」。lupdate 工具会扫描到它,生成待翻译条目。翻译人员把 "Hello" 翻译成 "你好",程序运行时就会自动显示中文。
我个人习惯,所有用户可见的字符串,哪怕你觉得「这个肯定不用翻译」,也最好用 tr() 包一下。为什么?因为需求会变。我遇到过项目上线后,客户突然说「我们要加日语支持」,这时候再回头改代码,那叫一个酸爽。
tr("Hello " + name) 这种写法,lupdate 提取不出来。正确做法是用占位符:tr("Hello %1").arg(name)。
3.2 上下文参数:为什么需要它?
你可能会问:tr() 不是已经够用了吗?为什么还要加个上下文参数?
来,我给你讲个真实案例。有一次我做了一个邮件客户端,界面里有「收件箱」这个词。同时,在另一个对话框里也有「收件箱」——但一个是名词(文件夹名称),一个是动词(正在收件)。翻译人员拿到翻译文件时,看到两个一模一样的 "Inbox",完全不知道哪个是哪个。
这时候,上下文参数就派上用场了:
// 上下文参数帮助区分歧义
// 在 MainWindow 类中
tr("Inbox", "folder name");
// 在 ReceiveDialog 类中
tr("Inbox", "receiving status");
你看,第二个参数就是上下文。它不会显示在界面上,但会出现在翻译文件中,帮助翻译人员理解。说白了,就是给翻译人员的一个提示。
我个人建议,只要你觉得某个词可能有歧义,就加上上下文参数。多写几个字,总比翻译错了强。
3.3 复数处理:不只是加个 "s"
英文里,复数就是加个 "s"。但中文呢?「1 个文件」、「2 个文件」——数量词变了,名词不变。俄语更复杂,有单数、双数、复数三种形式。
Qt 的 tr() 提供了复数处理机制:
// 复数处理
int fileCount = 3;
QString msg = tr("%n file(s) deleted", "", fileCount);
// 英文:3 file(s) deleted
// 中文:删除了 3 个文件
这里的关键是 %n 占位符。Qt 会根据 fileCount 的值,自动选择正确的复数形式。翻译人员在翻译时,需要为每种语言提供不同的复数规则。
if (count > 1) ... 这种逻辑,交给 tr() 处理最稳妥。
我曾经在一个项目中,自己手写了复数逻辑。结果呢?添加阿拉伯语支持时,整个代码都得重写。从那以后,我再也不敢手动处理复数了。
3.4 歧义消除:让翻译不再猜谜
除了上下文参数,还有几种常见的歧义消除手段:
- 使用 ID 而非原文: 有些团队喜欢用
tr("ID_SAVE_BUTTON")这种形式。好处是代码里不会出现用户可见的字符串,坏处是代码可读性差。我个人不太推荐。 - 注释辅助: 在
tr()上方加注释,lupdate 会把注释提取到翻译文件中。 - 统一术语表: 团队内部约定好,同一个概念用同一个英文词。比如「删除」统一用 "Delete",别一会儿 "Delete" 一会儿 "Remove"。
// 注释辅助示例
//: This is the button to save the current document
tr("Save");
嗯,这里要注意:注释必须以 //: 开头,lupdate 才会识别。普通注释 // 是不行的。
3.5 知识体系总览
说了这么多,咱们用一张图来总结一下 tr() 的核心要点:
这张图把 tr() 的四个核心维度都串起来了。你写代码时,可以对照着看看:这个字符串需要上下文吗?有复数问题吗?会不会有歧义?
tr() 的地方,就别用硬编码字符串。哪怕你现在只做中文版,也养成习惯。等哪天要出海了,你会发现这个习惯救了你一命。
好了,关于 tr() 的用法,咱们就聊到这儿。记住:上下文参数是给翻译人员看的,复数处理要交给 Qt 框架,歧义消除靠的是好习惯。下一节,咱们会聊聊更高级的翻译文件管理技巧。
可可精品资料库