Qt 编写应用支持多语言版本--一个GUI应用示例

释放双眼,带上耳机,听听看~!

简介

上一篇博文已经说过如何编写支持多语言的Qt 命令行应用,这一篇说说Qt GUI 应用多语言支持的坑。

本人喜欢用代码来写布局,而不是用 Qt Designer 来设计布局,手写布局比 Qt Desiner 布局有以下优点:

  1. 手工布局,所想即所见,
  2. 源代码方式修改布局非常方便,只需要拷贝代码、注释代码就行,如果用 Qt Designer 修改原有布局,简直要人老命,拖放一下 widget 就会打乱了原有的 layout;

本文示例程序是一个GUI应用,手写布局,在工具栏中添加两个 QAction,可以切换中文、英文界面,主界面一个标签显示文本,一个按钮用来退出应用。

英文界面如下:

Qt 编写应用支持多语言版本--一个GUI应用示例

中文界面如下:

Qt 编写应用支持多语言版本--一个GUI应用示例

开发环境及步骤

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 ,但是没有连接信号和槽函数。

布局完成调试看看是否布局合理。

第三步 翻译并添加到资源

  1. 新建翻译文件 helloqt_zh.ts
  2. 用工具 lupdate 提取字符串,并语言家来翻译;
  3. 用工具 lrelease 发布 QM 文件,即 helloqt_zh.qm 文件;
  4. 在资源文件 HelloQt.qrc 中添加 QM 文件,并把资源重命名为 :/translations/helloqt_zh
    如图所示:
    Qt 编写应用支持多语言版本--一个GUI应用示例
    上面的步骤只是准备了翻译字符串,实际上应用程序并不能切换语言,还需要执行下面的步骤:
  5. 新建一个成员变量 QTranslator *language_zh;,并在主窗口的构造方法中加载对应的QM文件, language_zh->load(\":/translations/helloqt_zh\");
  6. 加载了 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)));
  7. 分别实现 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);
    }
  8. 执行完上一步你就急着编译查看结果,点击两个工具栏你却发现并不能切换语言,为什么呢?因为切换语言的事件还没有处理呢。覆盖 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\") 那么就有两个上下文,分别是 QObjectHelloQt,这一点也可以从语言家看到。

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);
}

语言家界面的显示:

Qt 编写应用支持多语言版本--一个GUI应用示例

Qt 编写应用支持多语言版本--一个GUI应用示例

第二个参数 sourceText 就是源代码中以 tr() 括起来的字符串。

后面的两个参数暂时没有用到,忽略。

一张图说明上下文,源字符串二者的关系,如果代码中某个字符串被删除了但是 TS 文件中还存在,而且今后不需要这个翻译,就可以用记事本打开 TS 文件,直接删除以 vanished 标记的翻译:

Qt 编写应用支持多语言版本--一个GUI应用示例

回到 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 样式保留,只需要替换里面的 HelloQt! 即可。

源代码

只列出关键的源代码,首先是布局文件 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>&lt;h2&gt;&lt;i&gt;Hello&lt;/i&gt;&lt;font color=red&gt;Qt!&lt;/font&gt;&lt;/h2&gt;</source>
        <translation>&lt;h2&gt;&lt;i&gt;你好&lt;/i&gt;&lt;font color=red&gt;Qt!&lt;/font&gt;&lt;/h2&gt;</translation>
    </message>
    <message>
        <location filename=\"myUI_HelloQt.h\" line=\"59\"/>
        <location filename=\"myUI_HelloQt.h\" line=\"86\"/>
        <source>&amp;Quit</source>
        <translation>&amp;退出</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
本博客的文章如无特殊说明,均为原创,转载请注明出处。如未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

给TA打赏
共{{data.count}}人
人已打赏
随笔日记

深入理解Thread构造函数

2020-11-9 5:23:29

随笔日记

如何将Azure SQL 数据库还原到本地数据库实例中

2020-11-9 5:23:31

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索