admin管理员组

文章数量:1532357

前言

有的软件需要根据用户的设置来切换显示的语言,Qt 提供了一套用于 Internationalization 的机制来帮助我们实现语言切换。

大致的流程:首先用 lupdate 工具根据源码中标记的字符串生成 ts 文件,然后通过 Linguist(Qt语言家)工具进行编辑,再用 lrelease 工具发布 qm 翻译文件,最后代码中加载这个 qm 翻译文件。其中 lupdate/lrelease 工具的使用已经集成到 Qt Creator 和 Linguist,可以快速地生成相应的文件。

操作流程

在 cpp 代码中使用 QObject::tr() 包含待翻译的文本,QML 代码中使用 qsTr() 包含待翻译的文本:

QObject::tr("第一项") //任意cpp文件使用
tr("第二项") //QObject子类函数中使用
qsTr("第三项")   //QML中使用

在 pro 中加上 TRANSLATIONS 设置,如 TRANSLATIONS += trans_zh_CN.ts,可以设置多个,每个对应生成的 ts 文件名:

TRANSLATIONS += trans_zh_CN.ts \
    trans_en_US.ts

并在 Qt Creator 中点击菜单栏-工具-外部-更新翻译,默认在 pro 目录生成对应的 ts 文件。下图为 Qt Creator 6.0 截图:

如果使用的 VS 开发,点击 Qt 插件菜单创建翻译文件。创建的文件会自动添加到目录,文件右键菜单有更新和发布的选项。下图为 Visual Studio 2019 截图:

使用 Qt 安装目录下的 Linguist 工具或者记事本打开 ts 文件进行编辑,设置对应的翻译文本,下面第一行是翻译后的文本,第二行是翻译的注释(tr 函数也可以加注释,填在第二个参数)。同时,可以看到每个文件的翻译是独立的,所以更新一个文件的文本后,只需要重新对该文件翻译即可。

所有需要翻译的文本都翻译完成后,点击 Qt Linguist 文件-保存,再点发布,默认会在 ts 同级目录生成 qm 文件,这就是我们发布程序时需要带上的翻译文件。(也可以在编辑保存后,在 Qt Creator 点更新翻译下面的发布翻译按钮)

有了 qm 文件后就是在程序中加载了,主要是借助 QTranslator 类,也可以使用多个 QTranslator 来加载多个不同的 qm 文件。

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    QTranslator translator;
    translator.load("trans_zh_CN.qm");
    app.installTranslator(&translator);
    return app.exec();
}

要注意的是,如果加载翻译文件前,字符串已经赋值给变量,那是没法动态切换的,需要重新使用 tr 翻译的常量字符串赋值。

对于 QML,更新翻译后,需要调用 QQmlApplicationEngine 的 retranslate。更新的基本是 QML 中属性绑定的那些值,虽然 model-view 也会刷新,但如果字符串是保存在变量中的,那也没法被翻译了。对于那些没法动态切换的地方,可以通过全局的信号去重新加载数据。对于 QML 日历等组件,因为字符串是通过 QLocale 设置来生成的,所以切换翻译的时候需要重新设置 locale。

对于 QWidget,如果是 ui 文件中的文本,使用 ui->retranslateUi(this); 函数更新翻译,也可以参照该函数的实现,对其他的文本进行翻译:

    translator.load("trans_zh_CN.qm");
    qApp->installTranslator(&translator);
    ui->retranslateUi(this);

    //动态翻译
    label1->setText(QApplication::translate("MainWindow", "helllo", Q_NULLPTR));
    label2->setText(QApplication::translate("MainWindow", "qt", Q_NULLPTR));

其他,在 Qt5.15 版本,QTranslator 增加了 language() 函数。Qt Linguist 操作 ts 文件生成 qm 翻译文件后,是会携带 language 信息的,默认根据文件名中如 zh_CN 来区分,识别不了时会弹框让你自己选择,ts 就会保存这个语言信息,并带到 qm 文件中。QTranslator 读取 qm 文件后,如果有这个语言信息,就可以通过 QTranslator.language() 函数读取到,可以拿去设置 QLocale 等其他相关的。(如果是老版本 Qt 要使用到新的 qm 文件,并获取这个 language 属性,可以参照我下面的解析代码)

对于源文件中同一字符串需要不同翻译的情况,参照文档说明,可以在 tr() 函数中用第二个参数区分,第三个参数用于区分复数,详见文档

//通过tr第二第三个参数来消除歧义
QString Translator::getText(int i) const
{
    if(i<0){
        return tr("获取文本","<");
    }
    return tr("获取文本",">=");
}

封装一个语言切换单例类

语言切换一般是全局的,更新后的信号一般也是全局通知,所以直接作为单例即可。

代码链接及主要实现如下(QML  版本):

github 链接:https://github/gongjianbo/MyTestCode/tree/master/Qml/TestQml_20211215_Translator

#pragma once
#include <QObject>
#include <QTranslator>
#include <QLocale>
#include <QMap>

//管理语言翻译
class Translator : public QObject
{
    Q_OBJECT
    Q_PROPERTY(Translator::Language language READ getLanguage WRITE setLanguage NOTIFY languageChanged)
    Q_PROPERTY(QLocale locale READ getLocale NOTIFY languageChanged)
public:
    enum Language {
        AnyLanguage = 0, //未设置
        ZH_CN, //中文
        EN_US //英文
    };
    Q_ENUM(Language)
private:
    explicit Translator(QObject *parent = nullptr);
public:
    ~Translator();
    static Translator *getInstance();

    Translator::Language getLanguage() const;
    void setLanguage(Translator::Language type);

    //用于一些组件属性绑定
    QLocale getLocale() const;

private:
    //QTranslator在Qt5.15才有language接口,低版本可以自己解析
    QString parseLanguage(const QString &qmpath);

signals:
    void languageChanged();

private:
    //当前语言设置
    Language lang{AnyLanguage};
    //加载翻译文件
    QTranslator trans;
    //语言枚举与language name的映射表
    QMap<Language,QString> langMap;
    //当前locale设置
    QLocale locale;
};
#include "Translator.h"
#include <QCoreApplication>
#include <QLocale>
#include <QMetaEnum>
#include <QtEndian>
#include <QFile>
#include <QDebug>

Translator::Translator(QObject *parent)
    : QObject{parent}
{
    langMap = {
        {ZH_CN,"zh_CN"},
        {EN_US,"en_US"}
    };
    qApp->installTranslator(&trans);
}

Translator::~Translator()
{
}

Translator *Translator::getInstance()
{
    static Translator instance;
    return &instance;
}

Translator::Language Translator::getLanguage() const
{
    return lang;
}

void Translator::setLanguage(Translator::Language type)
{
    if(type == lang || !langMap.contains(type))
        return;
    lang = type;
    //枚举名对应文件名,便于查找对应文件
    //但是对于可扩展的翻译,可以遍历目录下的文件,通过文件名来切换
    QString lang_str = langMap.value(type);
    QString trans_path = QString("%1/trans_%2.qm")
            .arg(qApp->applicationDirPath())
            .arg(lang_str);

    trans.load(trans_path);
    //qDebug()<<QLocale("zh_CN")<<QLocale("zh_TW");
    //qDebug()<<__FUNCTION__<<trans.language()<<trans_path<<QLocale::system().name();
    //可以根据翻译文件中的语言设置来设置locale
    //5.15才提供了language接口
#if (QT_VERSION < QT_VERSION_CHECK(5,15,0))
    locale = QLocale(parseLanguage(trans_path));
#else
    locale = QLocale(trans.language());
#endif
    QLocale::setDefault(locale);
    emit languageChanged();
}

QLocale Translator::getLocale() const
{
    return locale;
}

QString Translator::parseLanguage(const QString &qmpath)
{
    QString result;
    QFile file(qmpath);
    if(file.size() > 21 && file.open(QIODevice::ReadOnly))
    {
        file.read(16); //MagicLength,16个字节的标识忽略
        uchar temp[5]{}; //每个段开头个一个字节标识和四个字节长度位
        file.read((char *)temp, 5);
        quint8 tag = qFromBigEndian<quint8>(temp);
        quint32 block_len = qFromBigEndian<quint32>(temp + 1);
        //0xA7表示Language标记,为Qt5.15新增
        if(tag == 0xA7 && block_len > 0 && file.size() >= 21 + block_len)
        {
            result = file.read(block_len);
        }
        file.close();
    }
    return result;
}
    QGuiApplication app(argc, argv);

    Translator::getInstance()->setLanguage(Translator::ZH_CN);

    QQmlApplicationEngine engine;
    QObject::connect(Translator::getInstance(), &Translator::languageChanged,
                     &engine, &QQmlEngine::retranslate);
import QtQuick 2.12
import QtQml 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import QtQuick.Controls 1.4 as Ctrl1
import QtQuick.Layouts 1.12
import Trans 1.0

Window {
    width: 820
    height: 640
    visible: true
    title: qsTr("Qt Translator")

    Column {
        spacing: 10
        Row {
            spacing: 20
            RadioButton {
                text: "中文"
                checked: translator.language === Translator.ZH_CN
                onClicked: {
                    translator.language = Translator.ZH_CN
                }
            }
            RadioButton {
                text: "English"
                checked: translator.language === Translator.EN_US
                onClicked: {
                    translator.language = Translator.EN_US
                }
            }
        }
        Text {
            text: qsTr("翻译")
        }
        Ctrl1.Calendar {
            //默认值为Qt.locale()不能动态切换
            locale: translator.locale
        }
    }
}

参考

文档:https://doc.qt.io/qt-5/i18n-source-translation.html

文档:https://doc.qt.io/qt-5/qtlinguist-index.html

文档:https://doc.qt.io/qt-5/qtquick-internationalization.html

博客:Qt中,软件多语言国际化翻译的方法与步骤 - 知乎

博客:Qt的多语言翻译功能及步骤_MJZ0508-CSDN博客

本文标签: 多语言QT