Qt多语言翻译(国际化)

如果想支持多语言,也就是添加翻译,需要使用qlinguist相关的内容。

在qt中实现的大体流程为:根据源代码生成.ts文件,根据添加翻译后的.ts文件生成.qm文件,在源文件中setupUi之前使用QTranslator加载.qm文件(翻译文件)。

源码中所有希望添加进翻译文件的字符串都需要使用tr()括起来,.ui文件中的不需要,qml文件中使用qsTr()包裹。

由于与涉及到不同的build system以及.qm文件的加载方式,具体的实现方法非常乱。

梳理花了很大一番功夫。

以下学习过程基于flameshotqBittorrent 两个项目。

由于缺包等原因,我本地的环境没法编译qBittorrent工程,仅看源码来学习。

实现方式

翻译文件的加载方式

在翻译文件.qm的加载方式上,有两个选择:

  • 可以选择把文件单独放到一个文件夹(如/translations),程序加载时使用相对路径加载(如/translation/appname_zh_CN.qm)。
  • 也可以选择放进资源文件.qrc里,使用qrc的路径加载(如:/translation/app_name_zh_CN.qm)。

前者的好处是翻译文件独立,修改翻译文件时不需要重新编译程序,坏处是翻译文件暴露在外,可能会遇到丢失、乱码等情况。更常见的情况是修改并生成文件后,忘了把新版的文件放到translation文件夹里。

后者的好处是可以使用qrc资源系统控制,不暴露在外,只要重新生成即可不需要手动放到translation文件夹,坏处是一旦修改文件就需要重新编译程序,而且包含所有翻译语言的翻译文件的话程序体积会略微大一点。

两套构建系统对翻译文件的处理

CMake

在CMake里,如果想加载翻译文件,需要LinguistTools模块:

1
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构建,对翻译文件的整个处理也比较简单。

  1. src/CMakeLists.txt中包含进.ts

    1
    2
    3
    4
    5
    
    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)
    
  2. 紧接着生成翻译文件:

    1
    2
    3
    4
    5
    
    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_translationqt5_add_translationGENERATE_TS本身只是一个手动控制的开关:

    1
    
    option(GENERATE_TS "Regenerate translation source files" OFF)
    
  3. 最后安装翻译文件到指定位置:

    1
    
    install(FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/flameshot/translations)
    
  4. 加载翻译文件的方式如下:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    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

  1. app/CMakeLists.txt中包含.ts文件并生成翻译文件:

    1
    2
    3
    4
    5
    
    # translations
    include(QbtTranslations)
    
    file(GLOB QBT_TS_FILES ../lang/*.ts)
    qbt_add_translations(qBittorrent QRC_FILE "../lang/lang.qrc" TS_FILES ${QBT_TS_FILES})
    
  2. qbt_add_translations是qBittorrent自定义的一个函数,反正非常复杂就对了,杀鸡焉用牛刀,撤。

QMake

src.pro中,仅有以下几行代码即可:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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用来配置生成翻译文件的动作,最后两行定义好TRANSLATIONSTS_SOURCES两个变量。

非常简单明了。

最终实现

选择的实现方法

  • 尽量想同时用QMake和CMake支持加载翻译文件,毕竟QMake有时候还挺好用,你Qt6的QMake停止更新关我Qt5的用户什么事
  • 选择将翻译文件放到.qrc里,原因嘛,单独放外面太蠢,QMake似乎没法通过在.pro文件里写自定义命令的方式安装到translation文件夹里,调用起来也麻烦。

预备工作

首次生成翻译文件时,我们是连.ts都没有的,所有需要先生成.ts.qm

  1. 在源码里配置好想加入翻译文件的文本,包括.ui文件内英文的控件文字、源码中所有可能显示在界面上的文字用tr()包起来,qml文件中使用qsTr()包裹。
  2. .pro文件里加上qBittorrent里翻译文件相关的配置
  3. 打开Qt自带的命令行,在这个命令行里才能用lrelease命令(仅限Windows,Linux大概直接find一下就行)。
  4. 使用lupdate app.pro -ts appname_zh_CN.ts即可生成.ts
  5. 用文本编辑器打开.ts,添加翻译,添加后去掉type="unfinished"
  6. 在打开刚出的命令行,用lrelease appname_zh_CN.ts -qm appname_zh_CN.qm生成.qm
  7. 新建一个qrc文件(如translation.qrc),添加进刚才生成的.qm.ts也可以加进去,不加也行但要记得加进git仓库。

CMake

  1. 在有add_executable命令的CMakeLists内加入以下代码,必须在这个CMakeLists内添加的原因是翻译文件无法使用target_sources添加进编译的目标程序内(格式不支持):

    1
    2
    3
    4
    5
    6
    
    # 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文件内包含的翻译。
  2. 之后在add_executable时加上生成的翻译文件:

    1
    
    add_executable(appname ${QM_FILES})
    
  3. 别忘了讲qrc文件加进来,并且打开AUTORCC开关:

    1
    2
    3
    
    # 打开AUTORCC需要在顶层的CMakeLists中尽早开启
    set(AUTORCC ON)
    target_sources(appname PRIVATE translation.qrc)
    

QMake

.pro文件里加入以下配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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里:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#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之后。
署名 - 非商业性使用 - 禁止演绎 4.0