原文地址::https://zhuanlan.zhihu.com/p/40815590
Qt程序中实现多语言有Qt自己的一套机制,然而目前在5.9版本下该机制无法在程序运行期间动态切换语言。本文向大家介绍一种切实可行的walkaround方法,支持对普通文本以及在ListModel/ListElement
中的文本的多语言处理。
回顾Qt多语言机制
Qt自身的多语言机制分下面几个步骤:
- 在源码中用
QObject::tr()
(C++)或者qsTr()
(QML)将字符串包起来; - 用Qt自带的lupdate生成.ts文件;
- 用linguist工具翻译并发布为.qm文件;
- 程序刚启动还未加载任何界面元素之前,根据某些规则加载对应的.qm文件即可实现显示对应的语言的效果。
之所以该机制不支持动态加载语言就在于第四步。该步骤核心代码是:
QTranslator translator;
translator.load("zh.qm");
app.installTranslator(&translator);
installTranslator
函数执行完并不会重新加载或者刷新界面文本,所以新的多语言并不会起作用。 我们的解决办法也是设法能将改变程序语言的事件通知到界面。
动态加载处理方法
我们已经知道之所以界面文本没刷新是因为更换语言的事件没有通知到界面上各个qsTr()
。
解决办法首先是创建一个全局变量并加上:
- 一个属性,值是空的字符串,但是通过发送
changed
信号可以让和它绑定的属性(即我们的各种多语言文本)reevaluate一下,从而能够再次qsTr()
获得新语言下的文本。 - 一个信号,用于程序中需要切换语言时发送。
例如我的程序中都有个AppGlobal
的全局变量:
class AppGlobal : public QObject{
Q_OBJECT
QString m_langToken = "";
public:
Q_PROPERTY(QString langToken READ langToken NOTIFY langTokenChanged)
QString langToken() const{ return m_langToken;}
signals:
void langTokenChanged(QString langToken);
void changeLang(QString lang);
};
其中langToken
就是触发reevaluate
的属性,而changeLang
则是切换语言时发送的信号。
一般逻辑是:
- 编写程序的时候,所有需要支持多语言的文本末尾都加上
langToken
,例如原来是qsTr("Hello World")
,现在是qsTr("Hello World") + $app.langToken
; - 用户通过选择语言触发
changeLang
信号,参数带上具体选择的语言; - 事先连接的槽函数开始响应执行, 用
QTranslator
加载对应的.qm文件,然后installTranslator
装载; - 最后发送
langTokenChanged
信号,触发所有和langToken
属性绑定的文本刷新。
第3步的槽函数可以在任何C++部分编写连接,例如我们在main.cpp里写:
QGuiApplication app(argc, argv);
auto appGlobal = new AppGlobal;
QTranslator* translator = nullptr;
QObject::connect(appGlobal, &AppGlobal::changeLang, &app, [&app, translator](QString lang){
if(translator)
app.removeTranslator(translator);
translator = new QTranslator(&app);
if(lang == "en")
translator->load("en.qm");
else
translator->load("zh.qm");
app.installTranslator(translator);
emit appGlobal->langTokenChanged(lang);
});
最后,在合适的位置触发更换语言的信号,例如我们在一个按钮下做下面的处理:
Button{
id: chineseButton
text: "中文"
onClicked: $app.changeLang("zh");
}
该方法巧妙之处在于它不会改变界面的文本本身(因为langToken
是个空字符串),但又会引起qsTr()
重新执行(因为属性绑定、reevaluate机制)。
ListModel/ListElement
多语言问题解决
用过ListModel
的朋友应该都知道,ListElement
的字段是无法进行属性绑定的。也就是说,它里面的文本无法用我们上面的机制进行刷新。那怎么办呢?Qt为我们准备了一个宏:QT_TR_NOOP
,它可以解决这个问题。
首先在定义ListModel
的时候,需要多语言支持的文本一律用该宏包裹:
ListModel{
id: listModel
ListElement{
text: QT_TR_NOOP("First")
isValid: true
}
ListElement{
text: QT_TR_NOOP("Second")
isValid: false
}
ListElement{
text: QT_TR_NOOP("Third")
isValid: true
}
}
然后我们在使用该ListModel
的delegate
里,使用我们上面的绑定触发机制:
ListView{
model: listModel
delegate: Text{
text: qsTr(text) + $app.langToken
enabled: isValid
}
}
这样,当$app.changeLang
信号发出后,ListView
的多语言也能够得到刷新。
总结
本文给大家介绍了如何克服Qt无法动态刷新多语言问题的方法。笔者的环境是Qt5.9.6。注意,在Qt5.10之后,qsTr()
已经能够自动响应installTranslator
事件了,也就是我们上面的langToken
属性没必要加了。但是ListModel
中的QT_TR_NOOP
还是需要的。