Qt 数据库操作与多语言支持的解决途径探讨

最近帮朋友使用Qt开发了一个毕业设计,自动化成绩系统的PC端。其实,对与消费用户发生交互的应用来说,使用Web APP是最合适的了。无奈,题目要求如此,也只能照办。由于需要做繁体、简体转换,考虑到使用Qt內建的国际化支持。对UI的元素进行国际化,大致牵扯到以下几个部分。
UI元素:如按钮、各种控件。
代码中的字符串:比如一些弹出消息等。
数据库内容:包括字段名、字段值。
这三个部分由易到难。

翻译之前的准备

Qt国际化的原理是通过加载字典文件(qm文件),并对上下文(Context) 中的对应原文进行替换。这个字典文件是由翻译脚本(ts文件)发布生成的,执行这个工作的工具是Qt语言家。
翻译前的第一步,是为项目加入ts文件。打开 .pro 工程,加入如下两行:

TRANSLATIONS += XXXX_zh_CN.ts \
		XXXX_zh_HK.ts
OTHER_FILES +=	XXXX_zh_CN.ts \
		XXXX_zh_HK.ts 

其中,XXXX代表工程的TARGET,如果直接用向导生成,这个名字也是工程文件的名字。TRANSLATIONS 入口告诉Qt,这两个文件是翻译脚本文件。一旦有这个入口,lupdate 工具即可根据该设置,自动扫描工程包含的所有元素,并更新ts文件。
翻译前的第二步,即执行lupdate.exe,自动扫描生成ts 文件。该入口在
“工具”–>“外部”–>“Qt语言家”–>“更新翻译”。

UI元素的国际化

首先得到解决的是UI元素。因为UI元素在设计师中默认开启了翻译的功能。所有没有额外关闭该属性的界面元素,都会出现在ts文件中。
右键单击工程树的翻译脚本,选择用Qt语言家打开,即可开始翻译工作。

img-Vq6wZ1iJ-1657683030433img-ZXxpj1vH-1657683030435

代码中的字符串

在代码中的提示信息,如 QMessageBox中的提示,可直接使用tr()宏进行包含,以告诉Qt语言家,这个提示是需要翻译的。如动态插入三列名称:

	m_pLayerDispMod = new QStandardItemModel(this);
	m_pLayerDispMod->setColumnCount(3);
	m_pLayerDispMod->setHeaderData(0,Qt::Horizontal,QString(tr("name")));
	m_pLayerDispMod->setHeaderData(1,Qt::Horizontal,QString(tr("active")));
	m_pLayerDispMod->setHeaderData(2,Qt::Horizontal,QString(tr("visible")));
	ui->tableView_QTV_layers->setModel(m_pLayerDispMod);

在ts文件中,把他们翻译为名称、活动、可见即可。
比较奇怪的一点是,QMessageBox::information\ warning等静态方法,尽管弹出的内容可以使用tr()包裹,但标准按钮却一直是“Ok”“Yes”。这个问题笔者一直没有找到很好的方法,最终只能自己写了一个类似功能的类完成了按钮的汉化。

数据库字段名

为了使数据库中选择的列名是跟随语言变化而变化的,有很多截然不同的方法。

方法1,准备多个视图

这个方法根据语言环境的后缀,准备多个视图,使用

create view view_stu_zh_CN as
select 
    stu_name as '学生姓名',
    stu_age  as '学生年龄',
    ...;
create view view_stu_zh_HK as
select 
    stu_name as '學生姓名',
    stu_age  as '學生年齡',
    ...

而后,在数据库接口代码中,根据不同的语境,选择不同的视图。
**缺点:**在代码中的条件查询,也要跟上语境。如果想从视图中选择年龄>20岁的条件,则需要区分语境,使用不同的字段名。

方法2,在接口文件中使用tr()

这个方法,思路是在接口文件中,统一进行显式tr包裹。如:

QSqlQuery queryStudentByAge(const int ageMin, const int ageMax)
{
	...
	try{
		...
		QString sql_main =
					"select "
					" stu_name as " + tr("StudentName") +
					",stu_age as " + tr("StudentAge") +
					" from student where stu_age >= ? and stu_age <=?; ";
		...
	}
	...
}

此时,只要保证所有引用学生名称的位置全部都用tr(“StudentName”)包裹,界面元素全用StudentName作为Text,就能达到适应语言的效果。

数据库中的数据记录

翻译中,最难的就是数据库中的数据记录。数据记录里,学生的姓名是动态录入的,在程序完成编译后,这些信息会不断变更。

方法1 冗余字段/翻译表

这个方法就是直接添加两个姓名字段,分别存储繁体、简体。或者稍微高明一些,建立一个字典表,而后根据语言做联合键,left join 过去。
这种方法,每次需要录入两个姓名,而且,需要一个视图,来统一接口。目前的工程,采用的就是这个方法。

方法2 PostgreSQL json /hstore 字段

SELECT stuname->'zh_CN' FROM student ;

这个方法是比较灵活的方法,把stuname定义为json字段,则可以使用键选择不同的语言了。不过缺点与方法一类似,就是仍然需要录入好几种字符。详见 PostgreSQL Json字段
PostgreSQL还有一个类似的字段类型是 HStore,存储了单层键-值序列,有异曲同工之妙。有兴趣的可以看看这里

方法3 动态显示转换

可以重载QSqlQueryModel,对它的 data()方法进行重载,动态切换显示语言。此外,为显示控件提供一个自定义代理,即QItemDelegate 的派生类,在其中实现繁简体转换也是可以的。繁简体转换其实就是逐个字符查表。

缺点:如果需要从程序中动态获取条件,如姓名=张三,则要搞清楚,数据库中究竟存储的是繁体还是简体,否则,字符串比较肯定不对。

这三个方法都不好,操作起来不是数据库端要费尽,就是末端显示要大把的代码。不知道各位高手有木有傻瓜化的解决方案,好指导指导楼猪,不胜感激。

发布翻译

最后一步,是在主函数中加入翻译功能的代码,直接拷贝Qt文档即可。

#include "XXXX.h"
#include <QApplication>
#include <QLibraryInfo>
#include <QTranslator>
int main(int argc, char *argv[])
{
	QApplication app(argc, argv);
	QTranslator qtTranslator;
	qtTranslator.load("qt_" + QLocale::system().name(),
					  QLibraryInfo::location(QLibraryInfo::TranslationsPath));
	app.installTranslator(&qtTranslator);

	QTranslator appTranslator;
	QString strTransLocalFile =
			QCoreApplication::applicationDirPath()+"/" +
			QCoreApplication::applicationName()+"_"+
			QLocale::system().name()+".qm";
	appTranslator.load(strTransLocalFile );
	app.installTranslator(&appTranslator);

	XXXX w;
	w.show();
	return app.exec();
}

当然,这段代码会根据当前操作系统的语言环境自动选择 zh_CN或者 zh_HK,或者 zh_TW等。如果想让用户动态切换,需要额外添加手工切换、刷新(调用 retranslateUi(this))的代码。

用Qt预言家发布翻译为.QM文件,拷贝到可执行文件所在文件夹即可。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丁劲犇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值