关闭

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

标签: 数据库uiqt国际化多语言
934人阅读 评论(3) 收藏 举报
分类:

最近帮朋友使用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语言家打开,即可开始翻译工作。

语言家便捷入口 在语言家中对UI进行翻译
Qt语言家打开 开始翻译

代码中的字符串

在代码中的提示信息,如 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文件,拷贝到可执行文件所在文件夹即可。

1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:328356次
    • 积分:4593
    • 等级:
    • 排名:第7209名
    • 原创:96篇
    • 转载:3篇
    • 译文:0篇
    • 评论:336条
    最新评论