Qt / Qml 中支持多國語言

【緣起】

最近找到一個看起來比較好用的開源工具( 然鵝不太會用 ),但整個界面都是英文的。

不過由於是 Qt 寫的,所以就嘗試自己做些漢化。

然後瞭解到不少實現多國語言相關的技術( 以及一些坑 (‾◡◝) )。

這裏寫一篇完整且具體的,「 如何在 Qt / Qml 中支持多國語言 & 動態翻譯 


【正文開始】

按例先上效果圖。

  • QtWidgets 的:

文本及翻譯如下:

    - MainWindow Title MainWindow  => "主窗口"」

    - Menu Title Language => "語言"」

    - Menu [ Action Text ] English => "英文"  Chinese => "中文"」

    - PushButton TextChange Language => "更改語言"」

    - Label TextThis is "Test Text" => "這是"測試文本""」

    - StatusBar MessageLanguage changed to English / Chinese => "語言變更爲英文 / 中文"」

其中,MainWidnow & Menu & Action & Label 是在 QtDesigner 中添加的,而 PushButton 爲手動編碼( 手動添加 ),而 StartusBar 來自 QMainWindow 本身,在運行時更改其文本

來看看關鍵代碼:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QPushButton>
#include <QTranslator>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    m_translator = new QTranslator(this);

    QCoreApplication::instance()->installTranslator(m_translator);

    m_changeBtn = new QPushButton(this);
    m_changeBtn->setText(tr("Change Language"));
    m_changeBtn->setGeometry(40, 100, 120, 40);

    connect(m_changeBtn, &QPushButton::clicked, this, [this]() {
        if (m_language == Language::English) {
            setLanguage(Language::Chinese);
        } else {
            setLanguage(Language::English);
        }
    });
    connect(ui->actionChinese, &QAction::triggered, this, [this]() {
        setLanguage(Language::Chinese);
    });
    connect(ui->actionEnglish, &QAction::triggered, this, [this]() {
        setLanguage(Language::English);
    });

    emit ui->actionEnglish->triggered();
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::setLanguage(MainWindow::Language lang)
{
    switch (lang) {
    case Language::Chinese:
        if (m_translator->load("./language/Translator_widget_zh_CN.qm")) {
            ui->actionChinese->setChecked(true);
            ui->actionEnglish->setChecked(false);
            m_language = Language::Chinese;
            statusBar()->showMessage(tr("--- Language changed to Chinese"), 1000);
        }
        break;
    case Language::English:
        m_translator->load("");
        ui->actionChinese->setChecked(false);
        ui->actionEnglish->setChecked(true);
        m_language = Language::English;
        statusBar()->showMessage(tr("--- Language changed to English"), 1000);
        break;
    }

    retranslateUi();
}

void MainWindow::retranslateUi()
{
    ui->retranslateUi(this);
    m_changeBtn->setText(QCoreApplication::translate("MainWindow", "Change Language", nullptr));
}

要想在 Qt 中實現多語言動態翻譯,大致分爲幾個步驟:

1、在 .pro 文件中加入 TRANSLATIONS += $${TARGET}_zh_CN.ts ( ts文件名可以任意,但建議如此,稍後解釋原因,zh_CN 代表簡體中文 )。

2、使用 tr() 函數將需要翻譯的字符串包裹起來,tr() 可以來自 QMetaObject::tr ( 非static,需要Q_OBJECT宏 ),或者來自 QObject::tr ( static函數 ),被包裹字符串將會被提取到翻譯文件( ts文件 )。

關於 *.ts *.qm 文件

*.ts 文件是從 Qt / C++ 或相關綁定 (如 Python 語言綁定 PyQt、PySide) 源代碼中,提取出來的 "Translate Source 翻譯資源" 文件

*.qm 文件是從 *.ts 文件,採用 Qt 自帶的 lrelease.exe 並運行相關 CMD 命令或由 Qt 自帶的 linguist.exe 應用生成的 "Qt Multi-language" 本地化文件。

關於 QMetaObject::tr() / QObject::tr() 函數

通過閱讀源碼,發現 tr() 函數的調用順序爲:

【 QMetaObject::tr() / QObject::tr() 】=> 【 QCoreApplication::translate() 】=> 【 QTranslator::translate() 

最終的 QTranslator::translate() 則會對安裝的 Translator ( 包含 qm 文件 ) 進行解析,然後返回相應語言的字符串。

3、使用 lupdate.exe 生成 / 更新 翻譯文件( .ts )。

4、使用 linguist.exe ( Qt 語言家 ) 打開 ts 文件並進行翻譯。

5、發佈翻譯文件( .qm ),使用 lrelease.exe

6、在代碼中安裝並載入翻譯文件:

    QCoreApplication::instance()->installTranslator(m_translator);
        .
        .
        .
    m_translator->load("./language/Translator_widget_zh_CN.qm")。

7、重新載入文本( 翻譯後的文本 ):retranslateUi()很重要,很多人不能動態翻譯就是缺少這一步

retranslateUi() 即重新設置 setText / setTitle 等等,然後使用 QCoreApplication::translate() 獲取翻譯後的對應字符串。

OK!先告一段落,來說下步驟一中的原因:這是爲了稍後可以更方便的進行翻譯。

具體原因見上一篇:Qt Linguist(語言家)與QtCreator集成

  • QtWidgets 中很簡單,那麼 Qml 呢?

有些類似,但也有很多不同,來瞅瞅效果圖 : 

當然,這裏用的 QtQuick Controls 2.13,在桌面平臺看起來稍微有些大了,不過也不影響。

整個界面與 QtWidgets 一致,main.cpp 關鍵代碼:

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QTranslator>

#define q_invokalbe Q_INVOKABLE

class TranslateController : public QObject
{
    Q_OBJECT
    Q_ENUMS(Language)

public:
    enum class Language
    {
        English = 1,
        Chinese
    };

public:
    static TranslateController* instance(QQmlEngine *engine) {
        static TranslateController controller(engine);
        return &controller;
    }

    void retranslateUi() {
        m_engine->retranslate();
    }

    q_invokalbe void loadLanguage(Language lang) {
        switch (lang) {
        case Language::Chinese:
            if (m_translator->load("./language/Translator_qml_zh_CN.qm")) {
                emit message(tr("--- Language changed to Chinese"));
            }
            break;
        case Language::English:
            m_translator->load("");
            emit message(tr("--- Language changed to English"));
            break;
        }

        retranslateUi();
    }

signals:
    void message(const QString &msg);

private:
    TranslateController(QQmlEngine *engine) {
        m_engine = engine;
        m_translator = new QTranslator(this);
        QCoreApplication::installTranslator(m_translator);
    }

    QQmlEngine *m_engine = nullptr;
    QTranslator *m_translator = nullptr;
};

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    qmlRegisterUncreatableType<TranslateController>("an.translate", 1, 0, "Language", "不能創建TranslateController對象");

    QQmlApplicationEngine engine;
    auto translateController = TranslateController::instance(&engine);
    engine.rootContext()->setContextProperty("controller", translateController);
    const QUrl url(QStringLiteral("qrc:/main.qml"));
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
                     &app, [url](QObject *obj, const QUrl &objUrl) {
        if (!obj && url == objUrl)
            QCoreApplication::exit(-1);
    }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

#include "main.moc"

其中,關鍵點爲 QQmlEngine::retranslate() 函數,此函數刷新所有使用標記爲翻譯( qsTr() )的字符串的綁定表達式,注意此函數在 Qt 5.10 以後可用

main.qml代碼如下:

import QtQuick 2.13
import QtQuick.Window 2.13
import QtQuick.Controls 2.13
import an.translate 1.0

ApplicationWindow {
    id: root
    visible: true
    width: 640
    height: 480
    title: qsTr("MainWindow")

    property int language: Language.English

    ActionGroup {
        id: languageGroup
    }

    menuBar: MenuBar {
        Menu {
            id: languageMenu
            title: qsTr("Language")

            Action {
                checked: language == Language.English
                checkable: true
                text: qsTr("English")
                ActionGroup.group: languageGroup
                onTriggered: {
                    controller.loadLanguage(Language.English);
                    language = Language.English;
                }
            }

            Action {
                checked: language == Language.Chinese
                checkable: true
                text: qsTr("Chinese")
                ActionGroup.group: languageGroup
                onTriggered: {
                    controller.loadLanguage(Language.Chinese);
                    language = Language.Chinese;
                }
            }
        }
    }

    Connections {
        target: controller
        onMessage: {
            statusBar.text = msg;
        }
    }

    Text {
        id: statusBar
        visible: false
        color: "red"
        anchors {
            bottom: parent.bottom
            bottomMargin: 30
            left: parent.left
            leftMargin: 20
        }
        onTextChanged: {
            visible = true;
            textTimer.restart();
        }

        Timer {
            id: textTimer
            running: false
            interval: 1000
            onTriggered: statusBar.visible = false;
        }
    }

    Label {
        id: label
        anchors.centerIn: parent
        text: qsTr("This is \"Test Text\"")
        font { pointSize: 26 }
    }

    Button {
        id: button
        text: qsTr("Change Language")
        anchors {
            bottom: label.top
            bottomMargin: 30
            horizontalCenter: parent.horizontalCenter
        }

        onClicked: {
            if (language == Language.English){
                controller.loadLanguage(Language.Chinese);
                language = Language.Chinese;
            } else {
                controller.loadLanguage(Language.English);
                language = Language.English;
            }
        }
    }
}

實際上,與 Widgets 最大區別在於:qml 中翻譯的字符串需要用 qsTr() / qsTranslate() 包裹起來。


【結語】

終於寫完了。。爲此我還專門寫了兩個版本的示例。

本篇文章應當是最全面具體的「 如何在 Qt / Qml 中支持多國語言 & 動態翻譯 

如果有其他問題 & 錯誤,歡迎留言 / 評論 / 私信。

最後,附上項目鏈接:

Github的:https://github.com/mengps/QmlExamples

CSDN的:https://download.csdn.net/download/u011283226/12458172

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章