Qt 的多语言支持的翻译机制

Qt 的多语言支持的翻译机制

来源 https://zhuanlan.zhihu.com/p/44536503

 

概述

根据“对象模型(Object Model)”所述,Qt 中有而 C++ 没有的特性就包括翻译这一部分。你试想一下用纯 C++ 写一个“Hello world”然后把它翻译,是不是就懵逼了?是不是不知道该怎么办了?Qt 已经为你提供了翻译的一条龙服务,使用起来非常的方便。本节的内容就和大家聊聊 Qt 中该如何进行翻译操作。

我们到底在翻译什么?

大多数情况我们可能只进行文字上的翻译,比如英语翻译成汉语、俄语、日语等等这些肉眼就可以看得到的东西。但是你有想过阿拉伯文可是从右往左书写的;各国的货币符号可是不一样的(人民币是¥,美元是$);各国的大数表示方式可是不一样的,比如我们写100万就是“100 0000”,美国人习惯于“1,000,000”,中间的逗号咋办呀?要是静态的数字还好办,那万一要显示的数字是个变量就没辙了。所以“区域”的设置也属于翻译的一部分。参考“QLocale 类 - 存储本机的区域设置”。

除了肉眼能看到的以外,还有就是眼睛看不到的字符编码转换问题。这部分不是给用户看的,而是给程序员看的。假设我们得到一段以“KIO8-R”为编码格式的俄文字节流,我要怎么把这段数据转换成比如 GBK18030 编码的中文啊?所以“编码的转换”也属于翻译的一部分。Qt 也同样提供了针对这个问题的类,参考“QTextCodec 类 - 编/解码小工具”。

综上所述,一个完整的翻译包括三部分:(1)文字的翻译;(2)区域格式的翻译;(3)语言编码的翻译。然鹅,绝大多数的新手可能只关注第一条而忽略了后两条。

和翻译相关的 Qt 类

Qt 的翻译功能很简单,所用到的工具类就那么几个,最常用的就是 QTranslator、QTextCodec、QLocale 这三个类。所有关于翻译的类及其说明如下:

  • QTranslator:存储翻译文件,执行翻译操作。
  • QLocale:存储本机的区域设置,还可以不同区域格式的转换。
  • QTextCodec:一个编/解码的小工具。
  • QTextDecoder:可以根据字节流的状态正确拼接字节流,从而进行解码操作,常用于网络。
  • QTextEncoder:可以根据字节流的状态正确拼接字节流,从而进行编码操作,常用于网络。
  • QCollator:基于不同区域来对比字符串的类。
  • QCollatorSortKey:用于加速一个字符串的排序。

翻译流程框架图

大致的流程是这样的:首先源代码产生 ts 文件,然后送给 Qt Linguist(Qt语言家)这个 Qt 自带的小工具进行处理产生 qm 翻译文件,最后源代码里加载这个 qm 翻译文件。

感觉有一点迭代的意思,其实不影响翻译。因为最后一步进行加载 qm 翻译文件所写的代码已经没有和界面相关的字符串了。

如何进行翻译?

从这部分开始我们详细的说下每一个步骤该如何做。为方便讲述,这里有一个示例工程,MainWindow 类中就添加一个 QLabel 对象。

第一步:写规范的代码

(1)用 QString 包裹不需要翻译的文本。

因为 QString 内部采用 Unicode 编码格式,而 Unicode 几乎能表达世界上任何一个语言,并且很多 Qt 库函数的参数也是 QString 类型,所以处理起来会比较方便。

当然用 char* 也可以,但是便宜的时候 Qt 内部还是会转换成 QString,这就会带来一定的系统开销。关于 char* -> QString 的转换问题,Qt 默认会把 char* 当成 UTF-8 编码格式。因此如果 char* 中的内容是其他编码格式的,需要用 QTextCodec 类来转换。参考“QTextCodec 类 - 编/解码小工具”。

(2)用 tr() 包裹需要翻译的文本

回顾本文最开头的概述,那么凡是你要进行翻译的文本都要用 tr() 函数来包裹。这个 tr() 是 QObject 类的一个函数,用它包裹的文本会被 Qt Linguist(Qt语言家)捕捉到从而进行翻译工作。或者你也可以这样理解,用 tr() 包裹的文本会添加到 ts 文件中。关于 ts 文件在下文会说到。例如我们的示例工程就是这样写的:

QML 的翻译是用 qsTr() 来代替 tr() 函数。

(3)定义上下文

什么叫上下文?请看下图,上下文一般指这个要翻译的文本属于哪个类。QObject 类及其子类只要使用了 Q_OBJECT 宏,默认是当前类作为上下文。

当然你也可以显示的调用某个类的 tr() 函数来改变文本所属的上下文。比如还是下图,如果直接写 tr("Hello"),那么打开 Qt Linguist 的话这一条会属于 MainWindow 这个上下文。但我这里写 QScroller::tr("Hello") 的话,图中显示就是属于 QScroller 这个类作为上下文。

(4)如何翻译非 Qt 类

QObject 类有现成的 tr() 函数可以方便使用,但是如果是非 QObject 类没有 tr() 函数怎么办呢?方法有四种:

第一种:利用 QCoreApplication::tr() 函数

用 QCoreApplication::tr() 这个函数来包裹要翻译的文本,只有这样才能被 Qt Linguist 捕捉到。例如:

第二种:利用 QCoreApplication::translate() 函数

用 QCoreApplication 类的 translate() 函数包裹要翻译的文本也能被 Qt Linguist 捕捉到。

第三种:利用 QCoreApplication 类的 Q_DECLARE_TR_FUNCTIONS 宏

使用这个宏当然要 #include <QCoreApplication> 啦。使用之后就可以用 tr() 函数了。

内部的原理依靠元对象系统,使用该宏后会自动在该类添加如下两个静态函数。这样就可以用啦。

static inline QString tr(const char *sourceText, const char *comment = 0);
static inline QString trUtf8(const char *sourceText, const char *comment = 0);

第四种:使用 QT_TR_NOOP() 宏和 QT_TRANSLATE_NOOP() 宏

示例代码如下,可以被捕捉到。但是这个宏一般不这样用,你也发现了在 Qt Linguist 的短语和表单这个窗口内没有“world”字符串了。

正确做法是用一个数组来存储用该宏包裹的字符串,这个数组在运行时存储的就会变为翻译后的文本。正确做法如下:

QString FriendlyConversation::greeting(int type)
{
    static const char *greeting_strings[] = {
    QT_TR_NOOP("world"),
    QT_TR_NOOP("hello")
    }
    return tr(greeting_strings[type]);
}

(5)给翻译添加个注释

添加注释可以更好的帮助翻译人员进行理解这个文本的含义,尤其是不同语境下有不同含义时。添加注释有两种方法,一种是采用固定的注释格式,另一种就是利用 tr() 的第二个参数。

第一种:添加注释的格式为:

//: ...
/*: ... */

第二种:tr() 函数的双参数

给翻译人员提供额外的信息来帮助理解不仅可以添加注释,在 Qt 4.4 之前最常用的方法是用 tr() 函数的第二个参数。

(6)如何翻译复数?

有时候会遇到这样一个场景,英语中有这样两句话:

I have 1 book.
I have 2 books.

此时就可以利用 tr() 的第三个参数,写代码就这样写:

int n = books.count();
showMessage(tr("I have %n book(s).", "", n));

上述代码的 tr() 函数,第一个参数是实际展示的文本,变量用 %n表示,就好比 %1、%2等之类的。book 的复数形式用括号括起来,翻译之后就会根据数来显示不同的形式;第二个参数是注释用的,这里面写不写看你自己,反正是给翻译人员看的;第三个就是这个变量 n。

第二步:转成 ts 文件

要想转成 ts 文件,一方面需要在 pro 文件中指定 ts 文件名,另一方面用 lupdate 功能生成。

在 pro 文件中指定 ts 文件

在 .pro 文件中添加如下代码:

TRANSLATIONS = app_zh.ts \
               app_de.ts

注意 TS 文件名,标明区域语言对运行时加载何种语言会很有用。Qt Linguist 会根据 TS 文件名自动设置区域。例如 app_de.ts 会将最终的翻译语言设置为德语,app_de_ch.ts 会将语言设置为德语,国家设置为瑞士。啥意思呢?其实是方便翻译人员的一个小技巧,看下图的不同点。看到没,它会根据本机的区域设置自动显示对应的语言。如果文件名不遵循该规范,也可以在 Qt Linguist 的“编辑”-“翻译文件设置”中显式的设置区域信息。

用 lupdate 生成 ts 文件

在 Qt Creator 的菜单栏中依次点击“工具”-“外部”-“Qt语言家”-“更新翻译(lupdate)”,就会在源代码文件所在的目录生成 ts 文件。

第三步:翻译并生成 qm 文件

用 Qt Linguist 打开 ts 文件并翻译后记得时刻保存,至此 ts 文件制作完毕,接下来就是 qm 文件的生成。

qm 文件生成可以直接在 Qt Linguist 里点击“文件”-“发布”,或者在 Qt Creator 中点击“工具”-“外部”-“Qt语言家”-“发布翻译(lrelease)”都可以。

至此,qm 文件生成完毕。

最后一步:源代码中加载、安装翻译文件

还记得 QTranslator 类吗?参考“QTranslator 类 - 翻译文件的容器”。示例代码如下,很简单吧。

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QTranslator translator;
    translator.load("app_zh");
    app.installTranslator(&translator);
    return app.exec();
}

多说两句,在上文第二步我们说过,翻译文件的命名如果遵循 Qt 的规范写的话会自动被识别这是哪个语言的翻译文件,比如“app_zh.ts”中的“zh”。所以高手们用 QTranslator:: load() 函数一般是这样的:

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QString locale = QLocale::system().name();
    QTranslator translator;
    translator.load(QString("app_") + locale);
    app.installTranslator(&translator);
    return app.exec();
}

你看,用 QLocale 直接就能获得计算机的区域设置“zh”了。如果这个程序在德语的操作系统环境下就会加载“app_de.qm”文件。

结语

整个有关 Qt 翻译的内容就是这么多了。笔者在写这篇文章时参考的官方内容有:

  • Internationalization with Qt
  • Writing Source Code for Translation
  • Qt Linguist Manual
  • Hello tr()示例
  • Arrow Pad示例
  • Troll Print示例

================= End

 

posted @ 2020-03-24 11:05  lsgxeva  阅读(4917)  评论(1编辑  收藏  举报