Qt 程序不重启实现自动翻译 -- 多语言切换

在做应用程序的过程中,多语言的切换是必不可少的功能,今天看一看怎么用Qt自带的翻译类 QTranslator 进行多语言之间的无缝切换,并且不会重启程序。

首先我们看下实现效果:
Qt 程序不重启实现自动翻译 -- 多语言切换_第1张图片

1、传统的设置语言的方法

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 对象创建之后再进行设置,则会存在已经创建的界面不会被翻译的情况。

2、qm文件的由来

通过上面的代码我们看到,首先我们定义了一个 QTranslator 的对象,然后这个对象通过load 方法加载了一个 .qm 文件,这个文件其实就是翻译文件。那么这个文件是怎么得来的呢?

下面我们看看这个文件的由来。

1、生成翻译文件 .ts 文件

生成方法比较简单,如果使用 QtCreator 进行代码开发,则直接在工程文件(.pro)中添加如下代码:

TRANSLATIONS += \
	simple_cn.ts   \
	english.ts

点击保存之后会在工程目录下生成对应的文件。

如果使用的是VS + qt 进行代码开发,则在右键工程目录下面的Qt子菜单中选择创建新的翻译文件,如下图所示:
Qt 程序不重启实现自动翻译 -- 多语言切换_第2张图片

2、利用linguist工具翻译.ts文件

linguist工具打开上面生成的翻译文件 (.ts),对界面上一些并未被翻译的语句进行翻译。如下图所示:
Qt 程序不重启实现自动翻译 -- 多语言切换_第3张图片

从上面的图中我们可以看出,标记为 1 的这样语句并没有被翻译,因此我们需要在标记为 2 的地方对这句话进行手动翻译,完成之后点击 标记为 3 的按钮,就会看到,界面已经被翻译完成了,如下图所示。

Qt 程序不重启实现自动翻译 -- 多语言切换_第4张图片

3、发布翻译文件(.qm)

发布翻译文件的方法也有很多种,QtCreator 中有独立的工具,如下图所示。上面的更新翻译则会生成.ts文件。
Qt 程序不重启实现自动翻译 -- 多语言切换_第5张图片

VS + qt 在生成翻译文件 .ts 文件的截图中也能够看出来,菜单选项。

linguist工具的文件菜单中也有发布的选项,发布之后,我们就会生成 .qm 文件。
如果我们使用传统的方式来设置程序的语言,那么,生成 .qm 文件之后就已经足够进行代码开发了。

3、多语言切换

如果我们想进行语言之间的自由切换,并且能够保持程序一直运行,那么传统的方式肯定是不能用的。我们需要另辟蹊径。网上也看到很多中方法,其实都是差不多的,都能够完整的进行切换,我们今天主要是对一些没有的方法进行查漏补缺。

申明一下,我下面的例子,是将生成的翻译文件(.qm) 加载进了 Qt 的资源树中,方便调用和维护。

1、重写QApplication类

为什么重写该类,主要的考虑目的是,在整个程序中,我们甚至不知道会在哪个界面进行语言的切换,并且我们一般都会通过配置文件的方式保存上一次的语言,那么在程序启动的 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);
}

2、调用

对上面这个函数的调用是非常简单的,首先我们看下它在 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));

3、如何进行翻译

按照上面的方法写完之后,那么软件究竟是如何进行翻译的呢?
其实,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); 来完成翻译。

4、多窗口语言翻译

上面我们也看到了,翻译需要手动调用 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);
}

这一点是很重要的,如果某个窗口漏掉的话,那么这个窗口也就不会在自动语言切换的范围之内。

写在这儿,就在我信心满满,大呼大功告成的时候,我运行了下程序,发现是这样的。

翻译前:
Qt 程序不重启实现自动翻译 -- 多语言切换_第6张图片
翻译后:
Qt 程序不重启实现自动翻译 -- 多语言切换_第7张图片
这不是我想要的,这肯定是在回来的路上,踩上了狗屎,走的却不是狗屎运。

但无论如何,问题还是需要解决的。注意看上面两张图标注的地方,然后我分析了下这两个地方和其他地方有啥不一样。通过多方位对比,终于知道了,原来这两个控件在我的程序里面都有通过使用代码进行了 setText

5、通过代码设置控件的text的翻译

上面看到的结果,如果我们使用 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的翻译。

测试代码。

你可能感兴趣的:(Qt,Qt,QTranslator,中英文切换,不重启程序,多语言设置)