1. 程式人生 > 其它 >Qt實現多國語言

Qt實現多國語言

文章目錄

  • 一、概述

  • 二、工具集

  • 三、相關類解析

  • 四、語言程式碼表

  • 五、多國語言實現方案

  • 六、製作單一國語言方法

  • 七、應用單一國語言方法

  • 八、如何進行翻譯?

  • 九、總結
  • 一、概述

    根據“物件模型(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 進行翻譯工作。這樣的話切換目標國語言時就不會原始碼有任何的影響。


    圖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語言家 >> 更新翻譯


    圖6.2 更新翻譯檔案

    1. Qt Linguist 裡面開啟語言檔案進行編輯


    圖6.3 Qt Linguist編輯翻譯檔案介面

    4. 完成語言檔案的編輯後,在 `檔案` >> `釋出` 功能生成 `*.qm` 檔案,`*.qm` 檔案是翻譯檔案的釋出版本


    圖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類中。把更多的事情交給使用者來處理,這也是不可避免的,同時也是非常明智的,畢竟在實際使用
    過程中,語言翻譯有很多歧義。