【緣起】
最近找到一個看起來比較好用的開源工具( 然鵝不太會用 ),但整個界面都是英文的。
不過由於是 Qt 寫的,所以就嘗試自己做些漢化。
然後瞭解到不少實現多國語言相關的技術( 以及一些坑 (‾◡◝) )。
這裏寫一篇完整且具體的,「 如何在 Qt / Qml 中支持多國語言 & 動態翻譯 」。
【正文開始】
按例先上效果圖。
- QtWidgets 的:
文本及翻譯如下:
- MainWindow Title 「 MainWindow => "主窗口"」
- Menu Title「 Language => "語言"」
- Menu [ Action Text ] 「 English => "英文" Chinese => "中文"」
- PushButton Text「 Change Language => "更改語言"」
- Label Text「 This is "Test Text" => "這是"測試文本""」
- StatusBar Message「 Language 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