目录
需求说明
QT项目完成后,如果想给国外使用,势必要添加一个语言选择功能。选择中文时,页面展示中文,选择英语时,页面展示英文。那该如何实现呢?
方案1:手动设置控件文本。以QPushButton* btn为例,名称是"姓名"。设置中文时,btn->setText("姓名"),设置英文时,btn->setText("name")。
这个方案很容易想到,也很容易实现。那有没有问题呢?有。
从功能方面看,一点问题没有,需求能够实现。但这种方案过于繁琐,说直白些,就是这是个笨方法。试想下,如果我要翻译成十几个国家的语言,那是不是在代码中写十几次翻译逻辑,代码会变得很臃肿。同时也不好维护,因为每新增一个控件,都要去改动代码。
能不能进一步优化呢?可以。我们可以借鉴配置文件的功能。比如我们通常会把不确定的、未知的或变化的东西存在配置文件中,如ip、串口名等。程序启动后,读取配置文件,是什么就加载什么。
现在把语言功能抽象出来,一个国家语言对应一个配置文件。这个配置文件中存储所有需要翻译的控件,及其要展示的名称。当你切换语言时,我就加载对应的文件,遍历里面所有的控件,并将它们的名字重新设置为要翻译的名字。当新增控件或新增语言时,只需要修改配置文件即可。代码几乎不在需要改动。到这里,恭喜你,已经实现了一个更为通用的标准方案。也就是下面所讲的方案2。
方案2:使用QT框架自带的语言翻译功能。
概念说明
*.ts(zh_CN.ts/en_US.ts):
ts文件就是普通文本,里面存在翻译的内容。xml格式,人能看懂,可以编辑。如:
cpp
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="en">
<context>
<name>MainWindow</name>
<message>
<location filename="../mainwindow.ui" line="14"/>
<source>MainWindow</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="27"/>
<source>中文</source>
<translation>zhongwen</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="40"/>
<source>英语</source>
<translation>english1111</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="53"/>
<source>这是一个测试文本</source>
<translation>this is a text</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="66"/>
<source>数学</source>
<translation>math</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="10"/>
<source>姓名</source>
<translation>name</translation>
</message>
</context>
</TS>
*.qm(zh_CN.qm/en_US.qm):
qm文件不是普通文本,是二进制文件,给程序看的。它是由ts文件编译而来。所以两者一一对应。程序代码中加载的就是qm文件而不是ts文件。
lupdate/lrelease:
QT自带的工具。lupdate用来生成或更新ts文件。比如新增控件,或代码中用tr时。都需要重新lupdate下,这样ts文件更新后,就可以在里面查看和编辑新增内容。lrelease是用来把ts文件转成qm文件。页面位置如下:

翻译过程简要为:
代码中 tr("姓名") → lupdate更新 → en_US.ts →把里面的姓名翻译成name → lrelease发布→代码加载en_US.qm→程序运行→显示英文。
原理介绍
翻译的文件有了,QT内部又是如何加载和调用呢?这就涉及到了ui_mainwindow.h。当我们创建一个UI(如mainwindow.ui),在上面添加控件,设置布局等等后。执行编译,QT便会自动创建一个ui_mainwindow.h文件。里面有两个主要函数setupUi和retranslateUi。
setupUi:
作用:初始化整个界面。如创建按钮、标签、输入框,并设置位置、大小、布局、图标、样式等。随后会调用 retranslateUi 设置文字,整个程序只调用 1 次(构造函数里)。
retranslateUi:
作用:只刷新所有文本翻译。不创建控件,不改变布局、大小、样式等。切换语言时必须调用,可以调用 N 次。ui_mainwindow.h内容如下:
cpp
/********************************************************************************
** Form generated from reading UI file 'mainwindow.ui'
**
** Created by: Qt User Interface Compiler version 5.12.2
**
** WARNING! All changes made in this file will be lost when recompiling UI file!
********************************************************************************/
#ifndef UI_MAINWINDOW_H
#define UI_MAINWINDOW_H
#include <QtCore/QVariant>
#include <QtWidgets/QApplication>
#include <QtWidgets/QLabel>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QStatusBar>
#include <QtWidgets/QTabWidget>
#include <QtWidgets/QToolBar>
#include <QtWidgets/QWidget>
QT_BEGIN_NAMESPACE
class Ui_MainWindow
{
public:
QWidget *centralWidget;
QPushButton *chBtn;
QPushButton *enBtn;
QLabel *label;
QLabel *label_2;
QLabel *name;
QPushButton *pushButton;
QTabWidget *tabWidget;
QWidget *tab;
QLineEdit *lineEdit;
QWidget *tab_2;
QLineEdit *lineEdit_2;
QMenuBar *menuBar;
QToolBar *mainToolBar;
QStatusBar *statusBar;
void setupUi(QMainWindow *MainWindow)
{
if (MainWindow->objectName().isEmpty())
MainWindow->setObjectName(QString::fromUtf8("MainWindow"));
MainWindow->resize(649, 499);
centralWidget = new QWidget(MainWindow);
centralWidget->setObjectName(QString::fromUtf8("centralWidget"));
chBtn = new QPushButton(centralWidget);
chBtn->setObjectName(QString::fromUtf8("chBtn"));
chBtn->setGeometry(QRect(50, 50, 75, 23));
enBtn = new QPushButton(centralWidget);
enBtn->setObjectName(QString::fromUtf8("enBtn"));
enBtn->setGeometry(QRect(180, 50, 75, 23));
label = new QLabel(centralWidget);
label->setObjectName(QString::fromUtf8("label"));
label->setGeometry(QRect(70, 140, 181, 31));
label_2 = new QLabel(centralWidget);
label_2->setObjectName(QString::fromUtf8("label_2"));
label_2->setGeometry(QRect(70, 180, 181, 31));
name = new QLabel(centralWidget);
name->setObjectName(QString::fromUtf8("name"));
name->setGeometry(QRect(70, 230, 181, 31));
pushButton = new QPushButton(centralWidget);
pushButton->setObjectName(QString::fromUtf8("pushButton"));
pushButton->setGeometry(QRect(370, 100, 75, 23));
tabWidget = new QTabWidget(centralWidget);
tabWidget->setObjectName(QString::fromUtf8("tabWidget"));
tabWidget->setGeometry(QRect(130, 190, 451, 181));
tab = new QWidget();
tab->setObjectName(QString::fromUtf8("tab"));
lineEdit = new QLineEdit(tab);
lineEdit->setObjectName(QString::fromUtf8("lineEdit"));
lineEdit->setEnabled(true);
lineEdit->setGeometry(QRect(10, 40, 131, 41));
lineEdit->setText(QString::fromUtf8("0"));
tabWidget->addTab(tab, QString());
tab_2 = new QWidget();
tab_2->setObjectName(QString::fromUtf8("tab_2"));
lineEdit_2 = new QLineEdit(tab_2);
lineEdit_2->setObjectName(QString::fromUtf8("lineEdit_2"));
lineEdit_2->setGeometry(QRect(10, 40, 131, 41));
tabWidget->addTab(tab_2, QString());
MainWindow->setCentralWidget(centralWidget);
menuBar = new QMenuBar(MainWindow);
menuBar->setObjectName(QString::fromUtf8("menuBar"));
menuBar->setGeometry(QRect(0, 0, 649, 23));
MainWindow->setMenuBar(menuBar);
mainToolBar = new QToolBar(MainWindow);
mainToolBar->setObjectName(QString::fromUtf8("mainToolBar"));
MainWindow->addToolBar(Qt::TopToolBarArea, mainToolBar);
statusBar = new QStatusBar(MainWindow);
statusBar->setObjectName(QString::fromUtf8("statusBar"));
MainWindow->setStatusBar(statusBar);
retranslateUi(MainWindow);
tabWidget->setCurrentIndex(0);
QMetaObject::connectSlotsByName(MainWindow);
} // setupUi
void retranslateUi(QMainWindow *MainWindow)
{
MainWindow->setWindowTitle(QApplication::translate("MainWindow", "MainWindow", nullptr));
chBtn->setText(QApplication::translate("MainWindow", "\344\270\255\346\226\207", nullptr));
enBtn->setText(QApplication::translate("MainWindow", "\350\213\261\350\257\255", nullptr));
label->setText(QApplication::translate("MainWindow", "\350\277\231\346\230\257\344\270\200\344\270\252\346\265\213\350\257\225\346\226\207\346\234\254", nullptr));
label_2->setText(QApplication::translate("MainWindow", "\346\225\260\345\255\246", nullptr));
name->setText(QString());
pushButton->setText(QApplication::translate("MainWindow", "PushButton", nullptr));
tabWidget->setTabText(tabWidget->indexOf(tab), QApplication::translate("MainWindow", "Tab 1", nullptr));
lineEdit_2->setText(QApplication::translate("MainWindow", "0", nullptr));
tabWidget->setTabText(tabWidget->indexOf(tab_2), QApplication::translate("MainWindow", "Tab 2", nullptr));
} // retranslateUi
};
namespace Ui {
class MainWindow: public Ui_MainWindow {};
} // namespace Ui
QT_END_NAMESPACE
#endif // UI_MAINWINDOW_H
从上面可以看出,文本显示靠的是retranslateUi函数。所以在切换语言时,只需要重新调用一次retranslateUi函数即可。
代码举例
1.创建一个简单项目Language

2.手动创建lang目录,用来存储后续的ts、qm文件

3.修改Language.pro文件,添加翻译文件
cpp
# 生成翻译文件
TRANSLATIONS += \
lang/zh_CN.ts \
lang/en_US.ts
4.点击lupdate更新,lang目录下便会生成两个ts文件

5.编辑en_US.ts文件,填写翻译结果。用linguist打开。

6.编辑里面的内容,介绍如下

需要翻译的源文,翻译后要点击上面的对号确认,点击后文本前面会由问号变为对号。表示已经生效。全部改为后如下:

然后点击保存按钮,最后退出。

7.点击发布lrelease,生成qm文件


8.核心代码实现
mainWindow.h
cpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QTranslator>
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
void switchLanguage(bool isEnglish);
private slots:
void on_chBtn_clicked();
void on_enBtn_clicked();
private:
Ui::MainWindow *ui;
QTranslator *g_translator;
int val=100;
};
#endif // MAINWINDOW_H
mainWindow.cpp
cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "QDebug"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->lineEdit->setText(QString::number(100));
// 默认加载中文
g_translator = new QTranslator(this);
g_translator->load("E:/code/Qt/Language/lang/zh_CN.qm");
qApp->installTranslator(g_translator);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_chBtn_clicked()
{
switchLanguage(false);
}
void MainWindow::on_enBtn_clicked()
{
switchLanguage(true);
}
void MainWindow::switchLanguage(bool isEnglish)
{
bool res;
if (isEnglish) {
res =g_translator->load("E:/code/Qt/Language/lang/en_US.qm");
} else {
res = g_translator->load("E:/code/Qt/Language/lang/zh_CN.qm");
}
qDebug() << "加载是否成功:" << res;
// 关键:刷新界面
ui->retranslateUi(this);
}
9.运行效果如下:

lineEdit控件特别说明
从上面的演示结果看 ,语言翻译功能基本实现。不过上面也预留了一个"坑",就是lineEdit初始值是100,但在语言切换后,居然变成0了。这显然不对,因为我只想修改文本,不想改动lineEdit数值。
导致这个现象的原因,可以在retranslateUi中找到。

我们看到,每次执行retranslateUi后,lineEdit也会设置下,结果就是0。实际上,在UI中lineEdit值设置多少,执行retranslateUi后,不管原来展示多少,最终就会展示初始设置的值。也就是,初始值0,切换后展示0,初始值100,切换后展示100,初始值空,切换后展示空。
知道了原因,就好解决了。既然翻译时候,会调用lineEdit的setText,那我不让他设置不就可以了。答案是很容易做到。只需将lineEdit属性的text的可翻译取消勾选即可。
原来页面:

取消勾选:

重新编译后我们再次查看ui_mainwindow.h,可以看到刚才lineEdit->setText代码已经没有了。也就是说,再次调用retranslateUi后,lineEdit结果不会再改变。展示如下:

添加资源
上面加载qm使用的是绝对路径,生产部署时候肯定不行,现在改为加载资源方式。
1.右键项目,添加一个新的资源文件,如命名lang.qrc。路径选择lang目录


此时会发现,lang目录下已经生成了lang.qrc
2.添加资源前缀和资源文件
添加→添加前缀→改为/lang

添加→添加文件→选择lang目录下的en_US.qm和zh_CN.qm两个文件

最后ctrl s保存
3.修改代码,将其中的绝对路径地址改为资源路径地址
mainWindow.cpp
cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "QDebug"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->lineEdit->setText(QString::number(100));
// 默认加载中文
g_translator = new QTranslator(this);
g_translator->load(":/lang/zh_CN.qm");
qApp->installTranslator(g_translator);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_chBtn_clicked()
{
switchLanguage(false);
}
void MainWindow::on_enBtn_clicked()
{
switchLanguage(true);
}
void MainWindow::switchLanguage(bool isEnglish)
{
bool res;
if (isEnglish) {
res =g_translator->load(":/lang/en_US.qm");
} else {
res = g_translator->load(":/lang/zh_CN.qm");
}
qDebug() << "加载是否成功:" << res;
// 关键:刷新界面
ui->retranslateUi(this);
}
4.最后编译、运行,效果和使用绝对路径一样。