前言
有的软件需要根据用户的设置来切换显示的语言,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.com/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