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

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