文章目录
一、概述
根据“对象模型(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类中。把更多的事情交给用户来处理,这也是不可避免的,同时也是非常明智的,毕竟在实际使用
过程中,语言翻译有很多歧义。
 
                    
                     
                    
                 
                    
                 
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号