文章目录

  • 一、概述

  • 二、工具集

  • 三、相关类解析

  • 四、语言代码表

  • 五、多国语言实现方案

  • 六、制作单一国语言方法

  • 七、应用单一国语言方法

  • 八、如何进行翻译?

  • 九、总结
  • 一、概述

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

    二、工具集

  • Qt Linguist

  • Qt Creator 4.13.1 (Community)
  • 三、相关类解析

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

  • QTranslator:存储翻译文件,执行翻译操作。

  • QLocale:存储本机的区域设置,还可以不同区域格式的转换。

  • QTextCodec:一个编/解码的小工具。

  • QTextDecoder:可以根据字节流的状态正确拼接字节流,从而进行解码操作,常用于网络。

  • QTextEncoder:可以根据字节流的状态正确拼接字节流,从而进行编码操作,常用于网络。

  • QCollator:基于不同区域来对比字符串的类。

  • QCollatorSortKey:用于加速一个字符串的排序。
  • 四、语言代码表

    请跳转到目标链接查看语言代码表...

    五、多国语言实现方案

    *利用 Qt Linguist 制作单一国语言文件 *.ts, 由翻译人员将 *.ts 文件内的源语言翻译为目标国语言, 在程序中根据用户需求加载对应国语言翻译文件 *.qm, *
    根据 Qt 的 QTranslator::translate 进行翻译工作。这样的话切换目标国语言时就不会源代码有任何的影响。

    Qt翻译加工流程图
    图4.1 Qt翻译加工流程

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

    六、制作单一国语言方法
    1. *.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
    ...
    
    1. 更新翻译文件,工具 >> 外部 >> Qt语言家 >> 更新翻译

    image
    图6.2 更新翻译文件

    1. Qt Linguist 里面打开语言文件进行编辑

    image
    图6.3 Qt Linguist编辑翻译文件界面

    4. 完成语言文件的编辑后,在 `文件` >> `发布` 功能生成 `*.qm` 文件,`*.qm` 文件是翻译文件的发布版本

    image
    图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);
    }
    
    八、如何进行翻译?

    翻译的前提主要有以下几点:

    1. 编写规范的代码

      • 用 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"));
      
    2. 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类中。把更多的事情交给用户来处理,这也是不可避免的,同时也是非常明智的,毕竟在实际使用
    过程中,语言翻译有很多歧义。

    posted on 2022-05-19 18:41  怪小子  阅读(528)  评论(0编辑  收藏  举报