- Published on
Qt多语言翻译(国际化)
- Authors
- Name
- realth000
如果想支持多语言,也就是添加翻译,需要使用qlinguist相关的内容。
在qt中实现的大体流程为:根据源代码生成.ts
文件,根据添加翻译后的.ts
文件生成.qm
文件,在源文件中setupUi
之前使用QTranslator
加载.qm
文件(翻译文件)。
源码中所有希望添加进翻译文件的字符串都需要使用tr()
括起来,.ui
文件中的不需要,qml文件中使用qsTr()
包裹。
由于与涉及到不同的build system以及.qm
文件的加载方式,具体的实现方法非常乱。
梳理花了很大一番功夫。
以下学习过程基于flameshot和qBittorrent两个项目。
由于缺包等原因,我本地的环境没法编译qBittorrent工程,仅看源码来学习。
实现方式
翻译文件的加载方式
在翻译文件.qm
的加载方式上,有两个选择:
- 可以选择把文件单独放到一个文件夹(如
/translations
),程序加载时使用相对路径加载(如/translation/appname_zh_CN.qm
)。 - 也可以选择放进资源文件
.qrc
里,使用qrc的路径加载(如:/translation/app_name_zh_CN.qm
)。
前者的好处是翻译文件独立,修改翻译文件时不需要重新编译程序,坏处是翻译文件暴露在外,可能会遇到丢失、乱码等情况。更常见的情况是修改并生成文件后,忘了把新版的文件放到translation
文件夹里。
后者的好处是可以使用qrc资源系统控制,不暴露在外,只要重新生成即可不需要手动放到translation文件夹,坏处是一旦修改文件就需要重新编译程序,而且包含所有翻译语言的翻译文件的话程序体积会略微大一点。
两套构建系统对翻译文件的处理
CMake
在CMake里,如果想加载翻译文件,需要LinguistTools
模块:
find_package(Qt5 CONFIG REQUIRED LinguistTools)
幸运的是LinguistTools
模块不是一个单独的动态库,应该是包含在Qt5Core内的。
生成并加载翻译文件需要使用qt5_add_translation
或者qt5_create_translation
两个function,且以来qrc系统,即要求CMake的AUTORCC
打开。
QMake
QMake需要使用Qt的lrelease
工具生成ts文件,人工填写ts文件内的翻译,再使用lrelease
生成.qm
文件,其实CMake底层也是调用的这个工具。
加载翻译文件需要用INSTALLATIONS变量 += 上所有的.ts
文件。
希望尽可能的自动化
编译过程需要手动操作的话非常恶星,总是希望整个构建过程能自动化就自动化。
需要自动化的点有:
- 自动根据
.ts
文件生成.qm
文件,这个步骤尽量每次都做,以保证翻译最新。至少在手动修改了.ts
文件后下一次编译时能自动生成.qm
。 - 自动加载
.qm
文件,这个既是指上一步中生成的.qm
能自动放在需要它的位置上,同时还包括程序加载翻译文件时选择当前系统语言。当然也可以加一个选择语言的配置,但能够选择性的加载翻译文件同样是必须的。
两个示例项目做了什么
flameshot
flameshot只支持CMake构建,对翻译文件的整个处理也比较简单。
- 在
src/CMakeLists.txt
中包含进.ts
:
set(FLAMESHOT_TS_FILES
${CMAKE_SOURCE_DIR}/data/translations/Internationalization_ca.ts
${CMAKE_SOURCE_DIR}/data/translations/Internationalization_cs.ts
...
${CMAKE_SOURCE_DIR}/data/translations/Internationalization_zh_TW.ts)
- 紧接着生成翻译文件:
if (GENERATE_TS)
qt5_create_translation(QM_FILES ${CMAKE_SOURCE_DIR} ${FLAMESHOT_TS_FILES})
else ()
qt5_add_translation(QM_FILES ${FLAMESHOT_TS_FILES})
endif ()
根据GENERATE_TS
状态的不同分别调用qt5_create_translation
和qt5_add_translation
,GENERATE_TS
本身只是一个手动控制的开关:
option(GENERATE_TS "Regenerate translation source files" OFF)
- 最后安装翻译文件到指定位置:
install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/flameshot/translations)
- 加载翻译文件的方式如下:
QTranslator translator, qtTranslator;
QStringList trPaths = PathInfo::translationsPaths();
for (const QString& path : trPaths) {
bool match = translator.load(QLocale(),
QStringLiteral("Internationalization"),
QStringLiteral("_"),
path);
if (match) {
break;
}
}
qtTranslator.load(
QLocale::system(),
"qt",
"_",
QLibraryInfo::location(QLibraryInfo::TranslationsPath));
app.installTranslator(&translator);
app.installTranslator(&qtTranslator);
用了两个translator,一个加载Qt自带语句的翻译,一个是加载项目自己加入的需要的翻译。
qBittorrent
qBittorrent同时支持使用QMake和Cmake构建,对翻译的处理较为复杂。
CMake
- 在
app/CMakeLists.txt
中包含.ts
文件并生成翻译文件:
# translations
include(QbtTranslations)
file(GLOB QBT_TS_FILES ../lang/*.ts)
qbt_add_translations(qBittorrent QRC_FILE "../lang/lang.qrc" TS_FILES ${QBT_TS_FILES})
qbt_add_translations
是qBittorrent自定义的一个函数,反正非常复杂就对了,杀鸡焉用牛刀,撤。
QMake
在src.pro
中,仅有以下几行代码即可:
isEmpty(QMAKE_LRELEASE) {
win32: QMAKE_LRELEASE = $$[QT_INSTALL_BINS]/lrelease.exe
else: QMAKE_LRELEASE = $$[QT_INSTALL_BINS]/lrelease
unix {
equals(QT_MAJOR_VERSION, 5) {
!exists($$QMAKE_LRELEASE): QMAKE_LRELEASE = lrelease-qt5
}
}
else {
!exists($$QMAKE_LRELEASE): QMAKE_LRELEASE = lrelease
}
}
lrelease.input = TS_SOURCES
lrelease.output = ${QMAKE_FILE_PATH}/${QMAKE_FILE_BASE}.qm
lrelease.commands = @echo "lrelease ${QMAKE_FILE_NAME}" && $$QMAKE_LRELEASE -silent ${QMAKE_FILE_NAME} -qm ${QMAKE_FILE_OUT}
lrelease.CONFIG += no_link target_predeps
QMAKE_EXTRA_COMPILERS += lrelease
TRANSLATIONS = $$files($$PWD/lang/qbittorrent_*.ts)
TS_SOURCES += $$TRANSLATIONS
最前面的一个大的if来设定生成翻译文件所用的lrelease
工具,中间lrelease
这个target用来配置生成翻译文件的动作,最后两行定义好TRANSLATIONS
和TS_SOURCES
两个变量。
非常简单明了。
最终实现
选择的实现方法
- 尽量想同时用QMake和CMake支持加载翻译文件,毕竟QMake有时候还挺好用,你Qt6的QMake停止更新关我Qt5的用户什么事。
- 选择将翻译文件放到
.qrc
里,原因嘛,单独放外面太蠢,QMake似乎没法通过在.pro
文件里写自定义命令的方式安装到translation文件夹里,调用起来也麻烦。
预备工作
首次生成翻译文件时,我们是连.ts
都没有的,所有需要先生成.ts
和.qm
:
- 在源码里配置好想加入翻译文件的文本,包括
.ui
文件内英文的控件文字、源码中所有可能显示在界面上的文字用tr()
包起来,qml文件中使用qsTr()
包裹。 - 在
.pro
文件里加上qBittorrent里翻译文件相关的配置 - 打开Qt自带的命令行,在这个命令行里才能用
lrelease
命令(仅限Windows,Linux大概直接find
一下就行)。 - 使用
lupdate app.pro -ts appname_zh_CN.ts
即可生成.ts
。 - 用文本编辑器打开
.ts
,添加翻译,添加后去掉type="unfinished"
。 - 在打开刚出的命令行,用
lrelease appname_zh_CN.ts -qm appname_zh_CN.qm
生成.qm
。 - 新建一个qrc文件(如
translation.qrc
),添加进刚才生成的.qm
,.ts
也可以加进去,不加也行但要记得加进git仓库。
CMake
- 在有
add_executable
命令的CMakeLists内加入以下代码,必须在这个CMakeLists内添加的原因是翻译文件无法使用target_sources
添加进编译的目标程序内(格式不支持):
# Generate translations
set(APPNAME_TS_FILES
${CMAKE_SOURCE_DIR}/src/resource/translation/appname_en.ts
${CMAKE_SOURCE_DIR}/src/resource/translation/appname_zh_CN.ts)
set_source_files_properties(${APPNAME_TS_FILES} PROPERTIES OUTPUT_LOCATION "${CMAKE_SOURCE_DIR}/src/resource/translation")
qt5_add_translation(QM_FILES ${APPNAME_TS_FILES})
注意:
- 以上语句需要在
add_subdirctory
添加了qrc文件之前。 set_source_files_properties
设置的输出目录必须是qrc文件内记录的qm文件目录,否则生成时无法更新qrc文件内包含的翻译。
- 之后在
add_executable
时加上生成的翻译文件:
add_executable(appname ${QM_FILES})
- 别忘了讲qrc文件加进来,并且打开AUTORCC开关:
# 打开AUTORCC需要在顶层的CMakeLists中尽早开启
set(AUTORCC ON)
target_sources(appname PRIVATE translation.qrc)
QMake
.pro
文件里加入以下配置:
isEmpty(QMAKE_LRELEASE) {
win32: QMAKE_LRELEASE = $$[QT_INSTALL_BINS]/lrelease.exe
else: QMAKE_LRELEASE = $$[QT_INSTALL_BINS]/lrelease
unix {
equals(QT_MAJOR_VERSION, 5) {
!exists($$QMAKE_LRELEASE): QMAKE_LRELEASE = lrelease-qt5
}
}
else {
!exists($$QMAKE_LRELEASE): QMAKE_LRELEASE = lrelease
}
}
lrelease.input = TS_SOURCES
lrelease.output = ${QMAKE_FILE_PATH}/${QMAKE_FILE_BASE}.qm
lrelease.commands = @echo "lrelease ${QMAKE_FILE_NAME}" && $$QMAKE_LRELEASE -silent ${QMAKE_FILE_NAME} -qm ${QMAKE_FILE_OUT}
lrelease.CONFIG += no_link target_predeps
QMAKE_EXTRA_COMPILERS += lrelease
TRANSLATIONS += $$files($$PWD/src/resource/translation/appname_*.ts)
TS_SOURCES += $$TRANSLATIONS
完事了,非常简单。
加载翻译文件
在setupUi
之前,一般在main.cpp里:
#include <QtCore/QTranslator>
QTranslator appTranslator;
QApplication a(argc, argv);
QLocale locale = QLocale::system();
QTranslator appTranslator;
switch (locale.script()) {
case QLocale::SimplifiedChineseScript:
appTranslator.load(QLatin1String(":/translation/appname_zh_CN.qm"));
break;
default:
appTranslator.load(QLatin1String(":/translation/appname_en.qm"));
}
a.installTranslator(&appTranslator);
- 我只制作了中文和英文两种翻译所以switch只有两条分支。
QTranslator
需要在声明application之后。