文章目录
一、概述
根据“对象模型(Object Model)”所述,Qt 中有而 C++ 没有的特性就包括翻译这一部分。你试想一下用纯 C++ 写一个“Hello world”然后把它翻译,是不是就懵逼了?
是不是不知道该怎么办了?Qt 已经为你提供了翻译的一条龙服务,使用起来非常的方便。本节的内容就和大家聊聊 Qt 中该如何进行翻译操作。
二、工具集
三、相关类解析
Qt 的翻译功能很简单,所用到的工具类就那么几个,最常用的就是 QTranslator、QTextCodec、QLocale 这三个类。所有关于翻译的类及其说明如下:
QTranslator
:存储翻译文件,执行翻译操作。QLocale
:存储本机的区域设置,还可以不同区域格式的转换。QTextCodec
:一个编/解码的小工具。QTextDecoder
:可以根据字节流的状态正确拼接字节流,从而进行解码操作,常用于网络。QTextEncoder
:可以根据字节流的状态正确拼接字节流,从而进行编码操作,常用于网络。QCollator
:基于不同区域来对比字符串的类。QCollatorSortKey
:用于加速一个字符串的排序。四、语言代码表
请跳转到目标链接查看语言代码表...
五、多国语言实现方案
*利用 Qt Linguist
制作单一国语言文件 *.ts
, 由翻译人员将 *.ts
文件内的源语言翻译为目标国语言, 在程序中根据用户需求加载对应国语言翻译文件 *.qm
, *
根据 Qt 的 QTranslator::translate
进行翻译工作。这样的话切换目标国语言时就不会源代码有任何的影响。
图4.1 Qt翻译加工流程
感觉有一点迭代的意思,其实不影响翻译。因为最后一步进行加载 qm翻译文件所写的代码已经没有和界面相关的字符串了
六、制作单一国语言方法
- 在
*.pro
文件内添加TRANSLATIONS += yourlanguage.ts
*.pro
...
CONFIG += c++11
TRANSLATIONS += language_ZH_CN.ts \
language_EN.ts
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
...
- 更新翻译文件,
工具
>>外部
>>Qt语言家
>>更新翻译
图6.2 更新翻译文件
- 在
Qt Linguist
里面打开语言文件进行编辑
图6.3 Qt Linguist编辑翻译文件界面
图6.4 发布翻译文件
七、应用单一国语言方法
应用大致分为两步骤
1. 加载语言*.qm文件并且应用到QApplication
QTranslator* translator = new QTranslator;
QString qm_filename = ":/qt_zh_CN.qm";
qDebug() << "try laod .qm " << qm_filename;
if(translator->load(qm_filename))
{
qDebug() << "try apply .qm " << qm_filename;
QApplication::instance()->installTranslator(translator);
}
2. 重写语言改变事件响应方法,因为我们在语言改变事件时要更新界面上的语言
void ExampleMultipleLanguageWidget::changeEvent(QEvent* event)
{
// In the case of events changing the application language
if (event->type() == QEvent::LanguageChange)
{
qDebug() << "update .qm ";
ui->retranslateUi(this); // translate the window again
}
return QWidget::changeEvent(event);
}
八、如何进行翻译?
翻译的前提主要有以下几点:
-
编写规范的代码
- 用 QString 包裹不需要翻译的文本。
- 因为 QString 内部采用 Unicode 编码格式,而 Unicode 几乎能表达世界上任何一个语言,并且很多 Qt 库函数的参数也是 - QString 类型,所以处理起来会比较方便。 - 当然用 char* 也可以,但是便宜的时候 Qt 内部还是会转换成 QString,这就会带来一定的系统开销。关于 char* -> QString 的 - 转换问题,Qt 默认会把 char* 当成 UTF-8 编码格式。因此如果 char* 中的内容是其他编码格式的,需要用 QTextCodec 类来转换。 - 参考 QTextCodec 类 - 编/解码小工具”。
- 用 tr() 包裹需要翻译的文本
- 那么凡是你要进行翻译的文本都要用 tr() 函数来包裹。这个 tr() 是 QObject 类的一个函数,用它包裹的文本会被 - Qt Linguist(Qt语言家)捕捉到从而进行翻译工作。或者你也可以这样理解,用 tr() 包裹的文本会添加到 ts 文件中。关于 ts 文件 - 在下文会说到。例如我们的示例工程就是这样写的 - \code - this->ui->label->setText(tr("Hello Wolrd")); - \endcode - QML 的翻译是用 qsTr() 来代替 tr() 函数
- 定义上下文
- 上下文一般指这个要翻译的文本属于哪个类。QObject 类及其子类只要使用了 Q_OBJECT 宏,默认是当前类作为上下文. - 当然你也可以显示的调用某个类的 tr() 函数来改变文本所属的上下文. - 如指定 QLabel 类作为上下文,代码如下 - this->ui->labelTranslator->setText(QObject::tr("Hello World"));
-
Qt多国语言翻译分析
根据不同的使用方法如QObject::tr()
、QCoreApplication::tr()
、QCoreApplication::translate()
还是其他宏定义用法, 其根本
上最后调用的翻译接口还是QTranslator::translate()
方法,这里我们直接解析QTranslator::translate()
方法。其他具体用法看看源代
码也就知道如何使用。
// QTranslator::translate 的实现代码, 参考自 Qt5.12.10
QString QTranslator::translate(const char *context, const char *sourceText, const char *disambiguation,
int n) const
{
Q_D(const QTranslator);
return d->do_translate(context, sourceText, disambiguation, n);
}
这里就不多说了,这是调到 QTranslator::do_translate的堆栈了。
// QTranslator::do_translate 的实现代码, 参考自 Qt5.12.10
// 这里代码略多,直接贴主要代码,不给人看的代码就不贴了。方法内部实现大概如下:
// >> 传入参数安全判断
// >> 检查上下文信息是否有效, True 的话进行提取,否则忽略
// >> 提取翻译文件内的消息体,提取消息体信息有两条执行路径
// >> 1. 通过直接 getMessage() 方法直接获取
// >> 2. 通过递归调用 Translator::translate() 接口提取
QString QTranslatorPrivate::do_translate(const char *context, const char *sourceText,
const char *comment, int n) const
{
// 省略一部分代码...
for (;;) {
quint32 h = 0;
elfHash_continue(sourceText, h);
elfHash_continue(comment, h);
elfHash_finish(h);
const uchar *start = offsetArray;
const uchar *end = start + ((numItems-1) << 3);
while (start <= end) {
const uchar *middle = start + (((end - start) >> 4) << 3);
uint hash = read32(middle);
if (h == hash) {
start = middle;
break;
} else if (hash < h) {
start = middle + 8;
} else {
end = middle - 8;
}
}
if (start <= end) {
// go back on equal key
while (start != offsetArray && read32(start) == read32(start-8))
start -= 8;
while (start < offsetArray + offsetLength) {
quint32 rh = read32(start);
start += 4;
if (rh != h)
break;
quint32 ro = read32(start);
start += 4;
QString tn = getMessage(messageArray + ro, messageArray + messageLength, context,
sourceText, comment, numerus);
if (!tn.isNull())
return tn;
}
}
if (!comment[0])
break;
comment = "";
}
searchDependencies:
for (QTranslator *translator : subTranslators) {
QString tn = translator->translate(context, sourceText, comment, n);
if (!tn.isNull())
return tn;
}
return QString();
}
那么到这里我们都知道只剩下最后一步了,因为递归最重要的方法就是从 getMessage()
方法提取出我们目标国语言的信息。
static QString getMessage(const uchar *m, const uchar *end, const char *context,
const char *sourceText, const char *comment, uint numerus)
{
const uchar *tn = 0;
uint tn_length = 0;
const uint sourceTextLen = uint(strlen(sourceText));
const uint contextLen = uint(strlen(context));
const uint commentLen = uint(strlen(comment));
for (;;) {
uchar tag = 0;
if (m < end)
tag = read8(m++);
switch((Tag)tag) {
case Tag_End:
goto end;
case Tag_Translation: {
int len = read32(m);
if (len % 1)
return QString();
m += 4;
if (!numerus--) {
tn_length = len;
tn = m;
}
m += len;
break;
}
case Tag_Obsolete1:
m += 4;
break;
case Tag_SourceText: {
quint32 len = read32(m);
m += 4;
if (!match(m, len, sourceText, sourceTextLen))
return QString();
m += len;
}
break;
case Tag_Context: {
quint32 len = read32(m);
m += 4;
if (!match(m, len, context, contextLen))
return QString();
m += len;
}
break;
case Tag_Comment: {
quint32 len = read32(m);
m += 4;
if (*m && !match(m, len, comment, commentLen))
return QString();
m += len;
}
break;
default:
return QString();
}
}
end:
if (!tn)
return QString();
QString str = QString((const QChar *)tn, tn_length/2);
if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
QChar *data = str.data();
qbswap<sizeof(QChar)>(data, str.length(), data);
}
return str;
}
根据以上代码,我们可以清楚的知道Qt是通过源语言的字符串、长度、上下文、上下文长度等信息提取*.qm
文件内的内容的
具体的可根据*.ts
文件结合理解。
九、总结
对于程序多国语言这个功能而言,C++相较于脚本语言来说是比较麻烦的,Qt的设计理念还是非常棒的,将所有的语言翻译代码
和使用解耦合在Translator类中。把更多的事情交给用户来处理,这也是不可避免的,同时也是非常明智的,毕竟在实际使用
过程中,语言翻译有很多歧义。