1、背景
Qt的多语言文件是xml格式,且需要包含源文件名,行数等等信息才可以被正确识别(有点奇怪),如下为一段标准的Qt多语言格式
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="ts_ZA">
<context>
<name>HomeView</name>
<message>
<location filename="../../../src/UI/Home/HomeView.cpp" line="90"/>
<source>homepage_create</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MacWindowHelper</name>
<message>
<location filename="../../../native/Mac/Mainwindow_Mac.mm" line="56"/>
<source>About</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>
上述source字段即为key,translation对应的即为具体的多语言文案。
官方提供了工具将源文件名及行数信息写入xml中,也提供了一个GUI工具填写翻译文案(需要一个语言一个字段去填写),所以当项目中新增多语言,则需要调用工具更新多语言文件,然后再一个个更新对应多语言,非常麻烦。这里希望实现如下目标:编译期自动调用工具生成xml,自动调用脚本将多余文案填入translation字段。
2、Qt多语言实现流程
有两种运行机制。1、将xml多语言编译为一个cpp文件最终直接链接到二进制包中。2、程序运行期间动态加载解析xml。官方推荐第一种方式如下为第一种方式流程:
3、自动转换脚本实现流程
实现思路为当调用lupdate生成的多语言xml文件(空文案)后,调用填词脚本将多语言文案填入字段中。流程如下:
1、填词脚本
填词脚本代码为c++,它首先读解析现有项目ABS中(ios格式,也可以是其他格式,需自行实现)多语言文案文件,然后填入多语言文件.ts对应字段中。具体实现代码位于地址的tools/tsconvert目录下
2、多语言文案
上述填词脚本自动转换iOS多语言格式为Qt的xml格式。只需要将iOS多语言放入项目中即可
3、qrc文件
要实现从多语言文件.qm到多语言文件.cpp的转换,还需要借助qrc文件,它告诉cmake宏如找到qm文件和生成几种多语言。格式如下:
<RCC>
<qresource prefix="/">
<file>en.qm</file>
<file>es.qm</file>
<file>pt.qm</file>
<file>ru.qm</file>
</qresource>
</RCC>
file字段是qm文件与qrc文件的相对路径。
4、cmake配置
还需要cmake配置才能自动完成上述整个流程
- 添加填词脚本子任务
add_subdirectory(tools/tsconvert)
- 自动调用lupdate、-lrelease、填词脚本
set(languagesDir "${CMAKE_CURRENT_BINARY_DIR}/resources/languages")
file(MAKE_DIRECTORY ${languagesDir})
# 将qrc文件拷贝到指定目录;由于qrc指定了qm文件与其为同一目录,所以这里拷贝一下,不然会找不到
configure_file(resources/languages/language.qrc ${languagesDir} COPYONLY)
set(TS_FILES_DIR ${CMAKE_CURRENT_BINARY_DIR}/resources/languages)
set(TMP_TS_FILES
${TS_FILES_DIR}/en.ts.0.ts
${TS_FILES_DIR}/es.ts.0.ts
${TS_FILES_DIR}/pt.ts.0.ts
${TS_FILES_DIR}/ru.ts.0.ts
)
set(TS_FILES
${TS_FILES_DIR}/en.ts
${TS_FILES_DIR}/es.ts
${TS_FILES_DIR}/pt.ts
${TS_FILES_DIR}/ru.ts
)
# 该语句实际上通过add_custom_command调用lupdate指令;
qt5_add_lupdate("" ${TMP_TS_FILES} ${PROJECT_SOURCES})
foreach (lan_file tmp_ts_file ts_file IN ZIP_LISTS LAN_FILES TMP_TS_FILES TS_FILES)
add_custom_command(
OUTPUT ${ts_file}
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/tools/tsconvert/tsconvert "${lan_file}" "${tmp_ts_file}" "${ts_file}"
DEPENDS tsconvert ${lan_file} ${tmp_ts_file}
VERBATIM
)
endforeach ()
# 将源文件(ts文件)的编译结果输出到指定目录(默认为可执行文件路径)
set_source_files_properties(${TS_FILES} PROPERTIES OUTPUT_LOCATION ${languagesDir})
# 该语句将会构建出lrease指令,在执行build时调用这些指令
qt5_add_translation(QM_FILES ${TS_FILES})
add_custom_target(qmTranslations DEPENDS ${QM_FILES})
add_executable(CrossPlatform
${PROJECT_SOURCES}
${ICONS_SOURCES}
${CMAKE_CURRENT_BINARY_DIR}/resources/languages/language.qrc
)
add_dependencies(CrossPlatform qmTranslations)
4、使用
1、tr()
所有要实现多语言的字符串需要用QObject::tr()或者tr()(该类需继承于QObject且添加Q_OBJECT关键字),它是实现多语言的前提条件。
createButton->setText(QObject::tr("homepage_create"));
class MyClass:public QObject {
Q_OBJECT
public:
void func(){
// 如果所在类承于QObject且添加了Q_OBJECT关键字,可简写
createButton->setText(tr("homepage_create"));
}
}
2、多语言文案
直接将iOS格式的多语言文件放在resources/languages下(如果是其它格式也放在这个目录下,不过需要自行实现前面的填词脚本)
2、切换为对应语言
代码如下:
QTranslator translator;
// :/代表相对路径。相对路径默认构成为 前缀(qrc文件中prefix字段)+qm相对于qrc的路径
translator->load(":/es.qm");
qApp->installTranslator(mTranslator);
5、问题
由于lupdate指令是针对源文件的,所以每次源文件发生改变,即使没有产生新的多语言文案代码,最终都会调用填词脚本,它会对整个所有的key-value进行遍历,这样就产生了编译期间不必要的时间消耗。经过测试,自己电脑有一万条多语言文案,花费时间大概100ms,应该在可接受范围内。
改进方案:
增加一个宏定义,只有需要进行多语言翻译的时候在进行转换