Qt 編寫應用支援多語言版本--一個GUI應用示例
簡介
上一篇博文已經說過如何編寫支援多語言的Qt 命令列應用,這一篇說說Qt GUI 應用多語言支援的坑。
本人喜歡用程式碼來寫佈局,而不是用 Qt Designer 來設計佈局,手寫佈局比 Qt Desiner 佈局有以下優點:
- 手工佈局,所想即所見,
- 原始碼方式修改佈局非常方便,只需要拷貝程式碼、註釋程式碼就行,如果用 Qt Designer 修改原有佈局,簡直要人老命,拖放一下 widget 就會打亂了原有的 layout;
本文示例程式是一個GUI應用,手寫佈局,在工具欄中新增兩個 QAction
,可以切換中文、英文介面,主介面一個標籤顯示文字,一個按鈕用來退出應用。
英文介面如下:
中文介面如下:
開發環境及步驟
VS 版本: vs2017 社群版
Qt 版本: 5.11.1
Qt VS tools: 2.3.2
第一步 新建 HelloQt 工程
預設的會建立 UI 檔案,直接編譯工程,複製編譯產生的 ui_HelloQt.h
檔案,重新命名為 myUI_HelloQt.h
以這個檔案為模板,手工佈局。
第二步 在 setupUI()
中實現佈局
使用MVC程式設計模型,只在 myUI_HelloQt.h
檔案中實現GUI,這裡雖然新建了兩個 QAction
和一個 QPushButton
,但是沒有連線訊號和槽函式。
佈局完成除錯看看是否佈局合理。
第三步 翻譯並新增到資源
- 新建翻譯檔案 helloqt_zh.ts;
- 用工具
lupdate
提取字串,並語言家來翻譯; - 用工具
lrelease
釋出 QM 檔案,即 helloqt_zh.qm 檔案; - 在資原始檔
HelloQt.qrc
中新增 QM 檔案,並把資源重新命名為:/translations/helloqt_zh
如圖所示:
上面的步驟只是準備了翻譯字串,實際上應用程式並不能切換語言,還需要執行下面的步驟: - 新建一個成員變數
QTranslator *language_zh;
,並在主視窗的構造方法中載入對應的QM檔案,language_zh->load(":/translations/helloqt_zh");
載入了 QM 檔案還不能切換多語言,需要安裝才行。到底何時安裝呢?這時候想起了前面的兩個
QAction
,只有點選了對應的工具條才切換對應的語言,在主視窗的構造方法中連線訊號和槽函式:// signals and slots QObject::connect(ui.act_language_zh, SIGNAL(triggered(bool)), this, SLOT(on_trigger_language_zh(bool))); QObject::connect(ui.act_language_en, SIGNAL(triggered(bool)), this, SLOT(on_trigger_language_en(bool))); QObject::connect(ui.btn_quit, SIGNAL(clicked(bool)), this, SLOT(on_btn_quit_clicked(bool)));
分別實現 action 對應的槽函式,載入中文時安裝
QTranslation
,載入英文時解除安裝,如下面的程式碼所示:void HelloQt::on_trigger_language_zh(bool checked) { qDebug() << tr("Chinese") << endl; qApp->installTranslator(language_zh); } void HelloQt::on_trigger_language_en(bool checked) { qDebug() << tr("English") << endl; qApp->removeTranslator(language_zh); }
執行完上一步你就急著編譯檢視結果,點選兩個工具欄你卻發現並不能切換語言,為什麼呢?因為切換語言的事件還沒有處理呢。覆蓋
changeEvent()
即可:void HelloQt::changeEvent(QEvent *event) { if (event->type() == QEvent::LanguageChange) { ui.retranslateUi(this); } else { QMainWindow::changeEvent(event); } }
至此兩個工具欄終於可以正常地切換語言顯示了。
深入一點
你可以看到工程中至少出現了3次 retranslateUi()
函式,一次是定義,兩次是呼叫,第一次呼叫是在UI 初始化中,第二次呼叫是 changeEvent()
中切換語言,它所做的工作就是把UI widgets 上顯示的字串替換為特定語言的字串。例如給主視窗設定視窗標題 HelloQtClass->setWindowTitle(QApplication::translate("HelloQtClass", "HelloQt", nullptr));
最關鍵的是裡面的 [static] QString QCoreApplication::translate(const char *context, const char *sourceText, const char *disambiguation = nullptr, int n = -1)
,這個函式才是負責翻譯語言的。
第一個引數 context
理解為上下文,什麼是上下文,Qt 助手說它就是一個類名。不同的字串會分配到不同的上下文。分配規則是 tr()
是用哪個類名來限定的,如下面的程式碼,分別呼叫 QObject::tr("install Chinese")
,HelloQt::tr("install English")
那麼就有兩個上下文,分別是 QObject
和 HelloQt
,這一點也可以從語言家看到。
void HelloQt::on_trigger_language_zh(bool checked)
{
qDebug() << QObject::tr("install Chinese") << endl;
qApp->installTranslator(language_zh);
}
void HelloQt::on_trigger_language_en(bool checked)
{
qDebug() << HelloQt::tr("install English") << endl;
qApp->removeTranslator(language_zh);
}
語言家介面的顯示:
第二個引數 sourceText
就是原始碼中以 tr()
括起來的字串。
後面的兩個引數暫時沒有用到,忽略。
一張圖說明上下文,源字串二者的關係,如果程式碼中某個字串被刪除了但是 TS 檔案中還存在,而且今後不需要這個翻譯,就可以用記事本開啟 TS 檔案,直接刪除以 vanished 標記的翻譯:
回到 retranslateUi()
函式,這是一個自定義的函式,使用者在這裡翻譯GUI介面,如果發現GUI上某個 widget 語言沒有變化,就需要在這裡翻譯,如下面的程式碼翻譯標籤和按鈕的文字,以及兩個工具欄的文字:
```C++
// add your own translate here.
act_language_zh->setText(QApplication::translate("QObject", "Chinese", nullptr));
act_language_en->setText(QApplication::translate("QObject", "English", nullptr));
lbl_hello->setText(QApplication::translate("QObject", "<h2><i>Hello</i><font color=red>Qt!</font></h2>", nullptr));
btn_quit->setText(QApplication::translate("QObject", "&Quit", nullptr));
```
上面的程式碼中標籤文字是一個 HTML 程式碼,這個也可以翻譯的, HTML 樣式保留,只需要替換裡面的 Hello 和 Qt! 即可。
原始碼
只列出關鍵的原始碼,首先是佈局檔案 myUI_HelloQt.h
#pragma once
#ifndef MYUI_HELLOQT_H
#define MYUI_HELLOQT_H
#include <QtCore/QVariant>
#include <QtWidgets/QApplication>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QStatusBar>
#include <QtWidgets/QToolBar>
#include <QtWidgets/QWidget>
#include <QtWidgets/QMenu>
#include <QtWidgets/QAction>
#include <QtWidgets/QLabel>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QVBoxLayout>
QT_BEGIN_NAMESPACE
class Ui_HelloQtClass
{
public:
QMenuBar *menuBar;
QToolBar *mainToolBar;
QWidget *centralWidget;
QStatusBar *statusBar;
QAction *act_language_zh;
QAction *act_language_en;
QLabel *lbl_hello;
QPushButton *btn_quit;
void setupUi(QMainWindow *HelloQtClass)
{
if (HelloQtClass->objectName().isEmpty())
HelloQtClass->setObjectName(QStringLiteral("HelloQtClass"));
//HelloQtClass->resize(600, 400);
HelloQtClass->setFixedSize(400, 160);
menuBar = new QMenuBar(HelloQtClass);
menuBar->setObjectName(QStringLiteral("menuBar"));
HelloQtClass->setMenuBar(menuBar);
mainToolBar = new QToolBar(HelloQtClass);
mainToolBar->setObjectName(QStringLiteral("mainToolBar"));
HelloQtClass->addToolBar(mainToolBar);
centralWidget = new QWidget(HelloQtClass);
centralWidget->setObjectName(QStringLiteral("centralWidget"));
// add actions here.
act_language_zh = new QAction(QObject::tr("Chinese"), HelloQtClass);
act_language_en = new QAction(QObject::tr("English"), HelloQtClass);
mainToolBar->addAction(act_language_zh);
mainToolBar->addAction(act_language_en);
// add your widgets here.
lbl_hello = new QLabel(HelloQtClass);
lbl_hello->setText(QObject::tr("<h2><i>Hello</i><font color=red>Qt!</font></h2>"));
lbl_hello->setAlignment(Qt::AlignHCenter);
btn_quit = new QPushButton(QObject::tr("&Quit"), HelloQtClass);
QVBoxLayout *vLayout = new QVBoxLayout;
vLayout->addWidget(lbl_hello);
vLayout->addWidget(btn_quit);
centralWidget->setLayout(vLayout);
HelloQtClass->setCentralWidget(centralWidget);
statusBar = new QStatusBar(HelloQtClass);
statusBar->setObjectName(QStringLiteral("statusBar"));
HelloQtClass->setStatusBar(statusBar);
retranslateUi(HelloQtClass);
QMetaObject::connectSlotsByName(HelloQtClass);
} // setupUi
void retranslateUi(QMainWindow *HelloQtClass)
{
// NOTE: the first parameter `context` in translate() must match the `context` in Qt Linguist.
// [static] QString QCoreApplication::translate(const char *context, const char *sourceText, const char *disambiguation = nullptr, int n = -1)
HelloQtClass->setWindowTitle(QApplication::translate("HelloQtClass", "HelloQt", nullptr));
// add your own translate here.
act_language_zh->setText(QApplication::translate("QObject", "Chinese", nullptr));
act_language_en->setText(QApplication::translate("QObject", "English", nullptr));
lbl_hello->setText(QApplication::translate("QObject", "<h2><i>Hello</i><font color=red>Qt!</font></h2>", nullptr));
btn_quit->setText(QApplication::translate("QObject", "&Quit", nullptr));
} // retranslateUi
};
namespace Ui {
class HelloQtClass : public Ui_HelloQtClass {};
} // namespace Ui
QT_END_NAMESPACE
#endif // MYUI_HELLOQT_H
然後是 HelloQt.h
#pragma once
#include <QtWidgets/QMainWindow>
#include <QTranslator>
#include "myUI_HelloQt.h"
class HelloQt : public QMainWindow
{
Q_OBJECT
public:
HelloQt(QWidget *parent = Q_NULLPTR);
public slots:
void on_trigger_language_zh(bool checked);
void on_trigger_language_en(bool checked);
protected slots:
void on_btn_quit_clicked(bool clicked);
protected:
void changeEvent(QEvent *event) override;
private:
Ui::HelloQtClass ui;
QTranslator *language_zh;
};
然後是 HelloQt.c
#include <QDebug>
#include "HelloQt.h"
HelloQt::HelloQt(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
// load translation
language_zh = new QTranslator(this);
language_zh->load(":/translations/helloqt_zh");
// signals and slots
QObject::connect(ui.act_language_zh, SIGNAL(triggered(bool)), this, SLOT(on_trigger_language_zh(bool)));
QObject::connect(ui.act_language_en, SIGNAL(triggered(bool)), this, SLOT(on_trigger_language_en(bool)));
QObject::connect(ui.btn_quit, SIGNAL(clicked(bool)), this, SLOT(on_btn_quit_clicked(bool)));
}
void HelloQt::on_trigger_language_zh(bool checked)
{
qDebug() << QObject::tr("install Chinese") << endl;
qApp->installTranslator(language_zh);
}
void HelloQt::on_trigger_language_en(bool checked)
{
qDebug() << HelloQt::tr("install English") << endl;
qApp->removeTranslator(language_zh);
}
void HelloQt::on_btn_quit_clicked(bool clicked)
{
qApp->quit();
}
//************************************
// Method: changeEvent
// FullName: HelloQt::changeEvent
// Access: protected
// Returns: void
// Qualifier:
// Parameter: QEvent * event
// Description: change language.
//************************************
void HelloQt::changeEvent(QEvent *event)
{
if (event->type() == QEvent::LanguageChange) {
ui.retranslateUi(this);
} else {
QMainWindow::changeEvent(event);
}
}
然後是翻譯檔案 helloqt_zh.ts
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="zh_CN">
<context>
<name>HelloQt</name>
<message>
<location filename="HelloQt.cpp" line="27"/>
<source>install English</source>
<oldsource>English</oldsource>
<translation>安裝英文</translation>
</message>
</context>
<context>
<name>HelloQtClass</name>
<message>
<location filename="myUI_HelloQt.h" line="80"/>
<source>HelloQt</source>
<translation>你好Qt</translation>
</message>
</context>
<context>
<name>QObject</name>
<message>
<location filename="myUI_HelloQt.h" line="49"/>
<location filename="myUI_HelloQt.h" line="83"/>
<source>Chinese</source>
<translation>中文</translation>
</message>
<message>
<location filename="myUI_HelloQt.h" line="50"/>
<location filename="myUI_HelloQt.h" line="84"/>
<source>English</source>
<translation>英文</translation>
</message>
<message>
<location filename="myUI_HelloQt.h" line="56"/>
<location filename="myUI_HelloQt.h" line="85"/>
<source><h2><i>Hello</i><font color=red>Qt!</font></h2></source>
<translation><h2><i>你好</i><font color=red>Qt!</font></h2></translation>
</message>
<message>
<location filename="myUI_HelloQt.h" line="59"/>
<location filename="myUI_HelloQt.h" line="86"/>
<source>&Quit</source>
<translation>&退出</translation>
</message>
<message>
<location filename="HelloQt.cpp" line="21"/>
<source>install Chinese</source>
<translation>安裝中文</translation>
</message>
</context>
</TS>
然後是資原始檔 HelloQt.qrc
<RCC>
<qresource prefix="/translations">
<file alias="helloqt_zh">helloqt_zh.qm</file>
</qresource>
</RCC>
宣告
歡迎轉載,請註明出處和作者,同時保留宣告。
作者:LinTeX9527
出處:https://www.cnblogs.com/LinTeX9527/p/11015060.html
本部落格的文章如無特殊說明,均為原創,轉載請註明出處。如未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,否則保留追究法律責任的權利。