用CMake重新审视i18n

Revisited i18n with CMake

用CMake重新审视i18n

January 31, 2024 by Jörg Bornemann | Comments

​2024年1月31日 Jörg Bornemann |评论

With Qt 6.2, we introduced a new CMake API to handle internationalization (i18n) of Qt-based projects: qt_add_translationsqt_add_lupdate and qt_add_lrelease. These functions have shortcomings that we address in the upcoming Qt 6.7 release.

​在Qt 6.2中,我们引入了一个新的CMake API来处理基于Qt的项目的国际化(i18n):qt_add_translations、qt_add_lupdatee和qt_add_release。这些功能存在不足,我们将在即将发布的Qt 6.7中解决这些不足。

What's the problem?

怎么了?

The main entry point to i18n with CMake is qt_add_translations. The function takes a target as the first parameter. Source files from that target are then passed to lupdate, producing a .ts file.

使用CMake访问i18n的主要入口点是qt_add_translations。函数将目标作为第一个参数。然后将来自该目标的源文件传递给lupdate,生成一个.ts文件。

Now, projects usually have more than one target. We didn't have a good way to pass multiple targets to qt_add_translations or qt_add_lupdate so far. Even if you created separate .ts files per target, there is no convenient way to merge the resulting .qm files. The lconvert tool can do that, but you'll have to do the setup on CMake level.

现在,项目通常有多个目标。到目前为止,我们还没有一个好的方法将多个目标传递给qt_add_pranslations或qt_add_lupdate。即使为每个目标创建了单独的.ts文件,也没有方便的方法来合并生成的.qm文件。lconvert工具可以做到这一点,但必须在CMake级别上进行设置。

Then, there might be sources in a target that you don't want to pass to lupdate. You want to mark sources as "I don't want this to contribute to my project's .ts files." Our i18n functions didn't offer a way to exclude sources.

然后,目标中可能存在不想传递给lupdate的源。想将源标记为“我不希望这对我的项目的.ts文件有贡献。”我们的i18n函数没有提供排除源的方法。

The only reliable workaround for these shortcomings was to explicitly pass the list of source files to qt_add_translations / qt_add_lupdate.

针对这些缺点,唯一可靠的解决方法是将源文件列表显式传递给qt_add_translations/qt_add_lupdate。

Further, on iOS, projects need to announce what languages they support. We had code that extracted the languages from the .ts files to write it into the Info.plist file. That works, but it's a bit fragile at the edges.

此外,在iOS上,项目需要宣布他们支持哪些语言。我们有从.ts文件中提取语言的代码,将其写入Info.plist文件。这很有效,但它的边缘有点脆弱。

The revisited CMake i18n commands

重新审视的CMake i18n命令

The target-based view of i18n is way too fine-grained. We need a project-level view that allows us to collect translatable strings in the sources of the whole project. And there needs to be a way to exclude parts of the source tree conveniently.

i18n的基于目标的视图过于细粒度。我们需要一个项目级视图,允许我们在整个项目的源代码中收集可翻译的字符串。并且需要有一种方法来方便地排除源树的部分。

QMake has a project-wide view with its TRANSLATIONS variable, and TR_EXCLUDE is used to exclude sources. Projects that use gettext usually operate directly on the source tree. With Qt 6.7, we'll offer a project-wide view for qt_add_translations too. We've kept compatibility with the "one target API" to avoid breaking existing projects.

QMake具有TRANSLATIONS变量的项目范围视图,TR_EXCLUDE用于排除源。使用gettext的项目通常直接在源树上操作。使用Qt 6.7,我们还将提供qt_add_translations的项目范围视图。我们一直保持与“一个目标API”的兼容性,以避免破坏现有的项目。

Let's consider a medium-size project - a clone of the game classic frogger. The project consists of several parts:

​让我们考虑一个中等规模的项目——游戏经典蛙泳的克隆。该项目由几个部分组成:

  • frogger, the main executable target
  • frogger,主要的可执行目标
  • game_logic, a library target with the meat of the game logic
  • game_logic,一个拥有游戏逻辑核心的库目标
  • jump_sim, a third-party public domain library to realistically simulate the frog jumps
  • jump_sim,一个第三方公共域库,用于真实地模拟青蛙跳跃
  • a bunch of tests
  • 一堆测试

The top-level project file looks like this:

顶级项目文件如下所示:

cmake_minimum_required(VERSION 3.28)
project()
find_package(Qt6 COMPONENTS OpenGLWidgets)
qt_standard_project_setup()

add_subdirectory(3rdparty) # adds target jump_sim

qt_add_library(game_logic src/game_logic/stuff.cpp ...)
target_link_libraries(game_logic PRIVATE jump_sim)

qt_add_executable(frogger src/frogger/main.cpp ...)
target_link_libraries(frogger PRIVATE game_logic)

add_subdirectory(tests) # adds several targets

Our project has Norwegian and German translations, so we adjust the setup call as follows:

我们的项目有挪威语和德语翻译,因此我们调整设置调用如下:

qt_standard_project_setup(I18N_LANGUAGES nb de)

Somewhere after the creation of the frogger target, we call qt_add_translations.

在创建frogger目标之后的某个地方,我们调用qt_add_translations。

qt_add_translations(frogger)

And that's it! This will

就这样!这将

  • collect all source files from all targets of the project
  • 从项目的所有目标收集所有源文件
  • create frogger_nb.ts and frogger_de.ts from the project name and the list of languages we passed in the qt_standard_project_setup call
  • 根据项目名称和我们在qt_standard_project_setup调用中传递的语言列表创建froggernb.ts和frogger_de.ts
  • create an update_translations target to extract the translatable strings from the collected source files
  • 创建update_translations目标以从收集的源文件中提取可翻译字符串
  • create a release_translations target to create the .qm files from the .ts files
  • 创建一个release_translations目标,从.ts文件创建.qm文件
  • create a Qt resource that contains the .qm files and embed it into the frogger target
  • 创建一个包含.qm文件的Qt资源,并将其嵌入到frogger目标中
  • when building for iOS, the app will contain the information that frogger supports the languages English, Norwegian and German
  • 当为iOS构建时,该应用程序将包含frogger支持英语、挪威语和德语的信息

The example above shows the most straightforward use of qt_add_translations. Customizing various aspects of the automatisms or explicitly specifying targets, sources or .ts files is possible. To show how to do that is out of the scope of this post. Please take a look at the documentation if you're interested in more details.

​上面的示例显示了qt_add_translations最直接的用法。可以自定义自动化的各个方面,也可以显式指定目标、源或.ts文件。展示如何做到这一点超出了本文的范围。如果对更多细节感兴趣,请查看文档。

Excluding targets and directories

排除目标和目录

As it happens, the jump_sim target and the tests of our project contain translatable strings that we don't need in frogger's .qm files.

碰巧的是,我们项目的jump_sim目标和测试包含了frogger的.qm文件中不需要的可翻译字符串。

To exclude the jump_sim target from translation, we set the target property EXCLUDED_FROM_TRANSLATION to ON:

​要从翻译中排除jump_sim目标,我们将目标属性EXCLUDED_FROM_TRANSLATION设置为ON:

set_property(TARGET jump_sim PROPERTY EXCLUDED_FROM_TRANSLATION ON)

For the tests we want to exclude every target below the tests directory. To do that, we set the directory property EXCLUDED_FROM_TRANSLATIONS in tests/CMakeLists.txt to ON.

​对于测试,我们希望排除测试目录下的每个目标。为此,我们将tests/CMakeLists.txt中的目录属性EXCLUDED_FROM_TRANSLATIONS设置为ON。

# tests/CMakeLists.txt
set_directory_properties(PROPERTIES EXCLUDED_FROM_TRANSLATION ON)

With this setup, we don't pass source files from jump_sim or the tests.

使用此设置,我们不会从jump_sim或测试传递源文件。

Specifying source targets explicitly

明确指定源目标

For this simple project, it would be actually easier just to say that the translatable source files are in the two targets, frogger and game_logic. Instead of excluding parts of the project, we could call qt_add_translations like this:

对于这个简单的项目,实际上更容易说可翻译的源文件在两个目标中,frogger和game_logic。我们可以这样调用qt_add_translations,而不是排除项目的部分内容:

qt_add_translations(frogger
    SOURCE_TARGETS frogger game_logic
)

Remember to adjust the SOURCE_TARGETS list if you extend your project with more targets containing translatable strings! For more complex projects, automatically collecting targets and excluding unwanted parts would be advisable.

如果使用更多包含可翻译字符串的目标来扩展项目,请记住调整SOURCE_TARGETS列表!对于更复杂的项目,建议自动收集目标并排除不需要的部分。

Handling plural forms in the native language

处理母语中的复数形式

In our game, we display how many frogs already reached home. It's a string like this:

在我们的游戏中,我们展示了有多少只青蛙已经到家了。这是一条像这样的字符串:

int numberOfFrogsAtHome = countFrogsAtHome();
tr("%n frog(s) are home", "", numberOfFrogsAtHome);

This translatable string is a plural form, and our Norwegian and German translations will display different strings depending on the number of frogs ("1 Frosch" vs. "2 Frösche" in German). It would also be nice to display "1 frog" and "2 frogs" in English. To do that, we need to create a translation from "source code English" to "human English" that only contains the plural forms.

​这个可翻译字符串是复数形式,我们的挪威语和德语翻译将根据青蛙的数量显示不同的字符串(德语中的“1 Frosch”与“2 Frösche”)。用英语显示“1只青蛙”和“2只青蛙”也很好。要做到这一点,我们需要创建一个从“源代码英语”到“人类英语”的翻译,只包含复数形式。

The language our untranslated source strings are written in is called the native language of the project. And we denote that fact in qt_standard_project_setup like so:

未翻译的源字符串所使用的语言称为项目的母语。我们在qt_standard_project_setup中这样表示这个事实:

qt_standard_project_setup(
    I18N_NATIVE_LANGUAGE en
    I18N_LANGUAGES nb de
)

This will automatically create a frogger_en.ts file that only contains plural form strings. We fire up Qt Linguist to "translate" the handful of plural forms, and now frogger's game progress display can show "2 frogs are home".

这将自动创建一个只包含多个表单字符串的frogger_en.ts文件。我们启动Qt语言学家来“翻译”少数复数形式,现在青蛙的游戏进度显示可以显示“2只青蛙在家”。

Conclusion

结论

The full example listing looks like this:

完整的示例列表如下所示:

cmake_minimum_required(VERSION 3.28)
project()
find_package(Qt6 COMPONENTS OpenGLWidgets)
qt_standard_project_setup(
    I18N_NATIVE_LANGUAGE en
    I18N_LANGUAGES nb de
)

add_subdirectory(3rdparty) # adds target jump_sim
set_property(TARGET jump_sim PROPERTY EXCLUDED_FROM_TRANSLATION ON)

qt_add_library(game_logic src/game_logic/stuff.cpp ...)
target_link_libraries(game_logic PRIVATE jump_sim)

qt_add_executable(frogger src/frogger/main.cpp ...)
target_link_libraries(frogger PRIVATE game_logic)

# in tests/CMakeLists.txt we have
# set_directory_properties(PROPERTIES EXCLUDED_FROM_TRANSLATION ON)
add_subdirectory(tests) # adds several targets

We've discussed the shortcomings of the i18n CMake API we introduced in Qt 6.2 and how we addressed this with the revisited i18n CMake API in Qt 6.7. The key takeaways are

我们已经讨论了我们在Qt 6.2中介绍的i18n CMake API的缺点,以及我们如何通过Qt 6.7中重新讨论的i18n CMake API来解决这一问题。关键的收获是

  • We now have a project-wide view of translations instead of a single-target view.
  • 我们现在有一个项目范围的翻译视图,而不是单一的目标视图。
  • It's possible (and advisable) to specify the supported languages on the project level.
  • 在项目级别指定支持的语言是可能的(也是可取的)。
  • qt_add_translations can automatically collect targets from which source files are used as input for lupdate.
  • qt_add_translations可以自动收集源文件用作lupdate输入的目标。
  • Targets and directories can be excluded from this automatic collection process.
  • 可以从此自动收集过程中排除目标和目录。
  • If no automatic target collection is desired, targets can be explicitly passed.
  • 如果不需要自动目标集合,则可以显式传递目标。
  • qt_add_translations can automatically generate the names of .ts files. This can also be turned off.
  • qt_add_translations可以自动生成.ts文件的名称。这也可以关闭。
  • There's now direct support for creating a plural-form .ts file for the native language of the project.
  • 现在直接支持为项目的母语创建复数形式的.ts文件。

Please play with this new API. We're happy to receive your feedback.

请使用这个新的API。我们很高兴收到您的反馈。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值