最近找到一个看起来比较好用的开源工具( 然鹅不太会用 ),但整个界面都是英文的。
不过由于是 Qt 写的,所以就尝试自己做些汉化。
然后了解到不少实现多国语言相关的技术( 以及一些坑 (‾◡◝) )。
这里写一篇完整且具体的,「 如何在 Qt / Qml 中支持多国语言 & 动态翻译 」。
按例先上效果图。
文本及翻译如下:
- 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
#include
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集成
有些类似,但也有很多不同,来瞅瞅效果图 :
当然,这里用的 QtQuick Controls 2.13,在桌面平台看起来稍微有些大了,不过也不影响。
整个界面与 QtWidgets 一致,main.cpp 关键代码:
#include
#include
#include
#include
#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("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