在做应用程序的过程中,多语言的切换是必不可少的功能,今天看一看怎么用Qt自带的翻译类 QTranslator
进行多语言之间的无缝切换,并且不会重启程序。
Qt的语言翻译主要是针对使用 Qtdesigner 设计的UI界面,对其上的一些语言进行翻译的过程。在这样的情况下,我们设置程序的语言一般都是在 main
函数里面实现。设置语言实现的顺序是有区别的。是在 QApplication
对象创建之后,主窗口创建之前进行设置。代码如下所示:
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTranslator translate;
translate.load("./simple_cn.qm");
qApp->installTranslator(&translate);
MainWindow w;
w.show();
return a.exec();
}
为什么需要在 QApplication
对象创建之后,但要在 MainWindow
对象创建之前设置语言,主要是因为,语言翻译对象需要用全局对象 qApp
进行设置。前面已经说了,Qt的语言翻译主要是针对使用 Qtdesigner
设计的UI界面,如果在 MainWindow
对象创建之后再进行设置,则会存在已经创建的界面不会被翻译的情况。
通过上面的代码我们看到,首先我们定义了一个 QTranslator
的对象,然后这个对象通过load
方法加载了一个 .qm
文件,这个文件其实就是翻译文件。那么这个文件是怎么得来的呢?
下面我们看看这个文件的由来。
生成方法比较简单,如果使用 QtCreator
进行代码开发,则直接在工程文件(.pro)中添加如下代码:
TRANSLATIONS += \
simple_cn.ts \
english.ts
点击保存之后会在工程目录下生成对应的文件。
如果使用的是VS + qt 进行代码开发,则在右键工程目录下面的Qt子菜单中选择创建新的翻译文件,如下图所示:
用 linguist工具打开上面生成的翻译文件 (.ts),对界面上一些并未被翻译的语句进行翻译。如下图所示:
从上面的图中我们可以看出,标记为 1 的这样语句并没有被翻译,因此我们需要在标记为 2 的地方对这句话进行手动翻译,完成之后点击 标记为 3 的按钮,就会看到,界面已经被翻译完成了,如下图所示。
发布翻译文件的方法也有很多种,QtCreator 中有独立的工具,如下图所示。上面的更新翻译则会生成.ts
文件。
VS + qt 在生成翻译文件 .ts 文件的截图中也能够看出来,菜单选项。
linguist工具的文件菜单中也有发布的选项,发布之后,我们就会生成 .qm 文件。
如果我们使用传统的方式来设置程序的语言,那么,生成 .qm 文件之后就已经足够进行代码开发了。
如果我们想进行语言之间的自由切换,并且能够保持程序一直运行,那么传统的方式肯定是不能用的。我们需要另辟蹊径。网上也看到很多中方法,其实都是差不多的,都能够完整的进行切换,我们今天主要是对一些没有的方法进行查漏补缺。
申明一下,我下面的例子,是将生成的翻译文件(.qm) 加载进了 Qt 的资源树中,方便调用和维护。
为什么重写该类,主要的考虑目的是,在整个程序中,我们甚至不知道会在哪个界面进行语言的切换,并且我们一般都会通过配置文件的方式保存上一次的语言,那么在程序启动的 main
函数中也一定会有初始的语言设置。也就是说,会有多个地方进行语言设置。
那么重写该类的方便就体现出来了,我们可以将切换语言的功能在该类中实现,然后在需要切换语言的地方进行语言切换。
重写该类的方法我们在前面的文章中已经介绍过了,如果不明白可以看下前面的文章–《Qt 程序中QApplication对象的重写》。下面我们主要看下语言切换代码的实现:
void GlobalApplication::switchApplicationLanguage(LanguageType type)
{
if (Q_NULLPTR != m_pTranslator)
{
qApp->removeTranslator(m_pTranslator);
delete m_pTranslator;
m_pTranslator = Q_NULLPTR;
}
constexpr auto SIMPLE_CN = (":/translation/simple_cn.qm"); //#define 也是可以的,但是问了程序的可测试行,推荐使用const对象
constexpr auto ENGLISH = (":/translation/english.qm");
m_pTranslator = new QTranslator;
bool flag = false;
switch (type)
{
case GlobalApplication::LANGHAGE_SIMPLE_CN:
{
flag = m_pTranslator->load(SIMPLE_CN);
break;
}
case GlobalApplication::LANGUAGE_ENGLISH:
{
flag = m_pTranslator->load(ENGLISH);
break;
}
default:
break;
}
if (!flag)
{
return;
}
qApp->installTranslator(m_pTranslator);
}
对上面这个函数的调用是非常简单的,首先我们看下它在 main
函数中的调用,因为在 main
函数中, 先创建了 GlobalApplication
对象,所以我们就可以直接进行调用 :
a.switchApplicationLanguage(GlobalApplication::LANGUAGE_ENGLISH);
其次,在其他界面进行切换语言时,我们知道Qt里面有个全局对象 qApp
,qApp
对象的定义如下
#define qApp (static_cast<QApplication *>(QCoreApplication::instance()))
而在这个程序里面,我们实现了自定义的QApplication
类,因此我们可以参考qApp对象的定义方式,定义我们自定义类 GlobalApplication
类的全局对象。如下:
class GlobalApplication;
//下面两种选择都行
#define app static_cast<GlobalApplication*>(QCoreApplication::instance())
//#define app static_cast(QApplication::instance())
定义好全局对象之后,调用也就会非常简单,只需要在需要的地方执行下面的调用:
app->switchApplicationLanguage(static_cast<GlobalApplication::LanguageType>(index));
按照上面的方法写完之后,那么软件究竟是如何进行翻译的呢?
其实,Qt中的 .ui
文件在编译的过程中, uic 工具在生成的 ui_xxx.h
文件中会生成一个方法,retranslateUi(QWidget* wdg);
下面是我们上面这个这个ui文件生成的retranslateUi
方法。
void retranslateUi(QMainWindow *MainWindow)
{
MainWindow->setWindowTitle(QApplication::translate("MainWindow", "MainWindow", nullptr));
pushButton->setText(QString());
label_3->setText(QString());
label_2->setText(QApplication::translate("MainWindow", "Hello Qt, this is a test program for Multilingual switching", nullptr));
label->setText(QApplication::translate("MainWindow", "language change", nullptr));
comboBox->setItemText(0, QApplication::translate("MainWindow", "English", nullptr));
comboBox->setItemText(1, QApplication::translate("MainWindow", "simple_cn", nullptr));
}
上面的函数我们能够看到,在这个方法中,重新翻译界面,都是重新对需要翻译的每个对象进行设置text操作。参数是通过 QApplication::translate
这个方法来获取。这个方法的作用也是能够在翻译文件中找到源文件对应的翻译数据。如果找不到,会返回空。
上面这个函数我们需要手动去调用才能完成翻译,因此在上面第二步之后,我们需要调用 ui->retranslateUi(this);
来完成翻译。
上面我们也看到了,翻译需要手动调用 ui->retranslateUi(QWidget* wdg)
方法来完成翻译,那么如果有多个窗口,甚至有几十个窗口的时候,我们该怎么通知每个界面去调用函数进行翻译?通过信号槽的方式去触发吗?
那是不可能的。
Qt的核心是什么,是事件循环,那么我们是不是可以从事件循环的这方面想一想办法。
其实,我们在调用 qApp->installTranslator(m_pTranslator);
之后,Qt底层会发起一个窗口事件,去通知窗口进行相应的响应,事件类型为 QEvent::LanguageChange
,所以,我们只需要在每个窗口界面重写event
事件函数就能过滤到语言切换的事件。代码如下:
bool MainWindow::event(QEvent *event)
{
if(event->type() == QEvent::LanguageChange)
{
ui->retranslateUi(this);
}
return QWidget::event(event);
}
这一点是很重要的,如果某个窗口漏掉的话,那么这个窗口也就不会在自动语言切换的范围之内。
写在这儿,就在我信心满满,大呼大功告成的时候,我运行了下程序,发现是这样的。
翻译前:
翻译后:
这不是我想要的,这肯定是在回来的路上,踩上了狗屎,走的却不是狗屎运。
但无论如何,问题还是需要解决的。注意看上面两张图标注的地方,然后我分析了下这两个地方和其他地方有啥不一样。通过多方位对比,终于知道了,原来这两个控件在我的程序里面都有通过使用代码进行了 setText
。
上面看到的结果,如果我们使用 setText 方法对控件进行了文本的设置之后,在调用 ui->retranslateUi(this);
之后并不会对我们的文本进行正常的翻译。那么为什么呢?
其实原因在 如何进行翻译 这一小节中已经有提到了,因为我们每次调用 ui->retranslateUi(this);
的时候,其实都是对每一个需要翻译的控件做了重新的设置文本(setText
)的操作,但是那些我们通过代码手动设置文本的控件他们在retranslateUi
方法中的代码是这样的。
pushButton->setText(QString());
label_3->setText(QString());
能够正常翻译的是这样的。
label_2->setText(QApplication::translate("MainWindow", "Hello Qt, this is a test program for Multilingual switching", nullptr));
区别是什么,区别就是重新设置的时候并没有从翻译文件中获取正确的内容,而是直接设置了空值。这个时候,就需要我们手动进行设置。代码如下:
bool MainWindow::event(QEvent *event)
{
if(event->type() == QEvent::LanguageChange)
{
ui->retranslateUi(this);
QString text = QApplication::translate("MainWindow", ui->pushButton->property("translator").toString().toStdString().c_str(), nullptr);
ui->pushButton->setText(text);
text = QApplication::translate("MainWindow", ui->label_3->property("translator").toString().toStdString().c_str(), nullptr);
ui->label_3->setText(text);
}
return QWidget::event(event);
}
但是,需要注意的是,我们自己手动设置的代码一定要在 ui->retranslateUi(this);
之后,否则会被 retranslateUi
方法里面的设置冲掉。
我们再来看下下面这两行代码,有什么不一样,为什么我们手动设置的在translate
的第二个参数和界面的不一样。
label_2->setText(QApplication::translate("MainWindow", "Hello Qt, this is a test program for Multilingual switching", nullptr));
ui->label_3->setText(QApplication::translate("MainWindow", ui->label_3->property("translator").toString().toStdString().c_str(), nullptr));
其实,我们使用这种方法的目的是为了,如果我们通过英文切换到中文,这个时候 label_3 的文本是中文,然后,你遇到了一个比较难缠的客户,他又想从中文切换回英文。如果我们使用 ui->label_3->text()
方法时,获取到的是中文,但是我们的翻译文件里面一般是只有一种源文件的,那我们的选择肯定是直接将目前的翻译直接delete
掉。软件就会重新显示回英文。使用上面的这种方法你会发现在翻译文件中找不到翻译,那么界面就会显示空。
所以为了避免这种有几种不同的值去查找翻译文件的问题,我们在起始设置文本的时候,给这些控件添加了一个属性,通过属性值来更改会比较方便。同样的,也可以通过生成翻译文件的方式来实现,但这种方式比较麻烦你,就是你得手动来编写 .ts 文件了。
通过设置属性的代码如下:
ui->pushButton->setText(tr("test"));
ui->pushButton->setProperty("translator", "test");
ui->label_3->setText(tr("test finish"));
ui->label_3->setProperty("translator", "test finish");
通过上面的学习,但愿你学会了Qt的翻译。
测试代码。