CMakeで国際化を再検討

本稿は「Revisited i18n with CMake」の抄訳です。
 

Qt 6.2では、Qtベースのプロジェクトの国際化(i18n)を処理するための新しいCMake APIである qt_add_translationsqt_add_lupdateqt_add_lreleaseを導入しました。これらの関数には欠点があり、次期Qt 6.7リリースで対応する予定です。

更新:Qt 6.7 API レビューでは、i18n CMake API の一部の名称を変更しました。この投稿もそれに合わせて更新しました。

問題は何でしょうか?

CMake を使用した国際化への主なエントリーポイントは、qt_add_translations です。この関数は、最初のパラメータとして CMake ターゲットを受け取ります。そのターゲットのソースファイルは lupdate に渡され、.ts ファイルが生成されます。

通常、プロジェクトには複数のターゲットが存在します。 これまで、複数のターゲットをqt_add_translationsまたはqt_add_lupdateに渡す良い方法がありませんでした。 ターゲットごとに個別の.tsファイルを作成しても、結果として生成される.qmファイルをマージする便利な方法はありません。 lconvertツールを使用すれば可能ですが、CMakeレベルでセットアップを行う必要があります。

また、ターゲットの中には lupdate に渡したくないソースがあるかもしれません。ソースに「プロジェクトの .ts ファイルに含めない」というマークを付けたい場合もあるでしょう。しかし、当社の国際化機能ではソースを除外する方法がありませんでした。

これらの欠点を回避する確実な方法は、ソースファイルのリストを明示的に qt_add_translations / qt_add_lupdate に渡すことしかありませんでした。

さらに、iOSでは、プロジェクトがサポートする言語を通知する必要があります。.tsファイルから言語を抽出してInfo.plistファイルに書き込むコードはありましたが、少し不安定な部分があります。

見直された CMake の国際化コマンド

国際化のターゲットベースのビューはあまりにも細かすぎます。プロジェクト全体のソースで翻訳可能な文字列を収集できるプロジェクトレベルのビューが必要です。また、ソースツリーの一部を簡単に除外できる方法も必要です。

QMakeには、プロジェクト全体をカバーするTRANSLATIONS変数があります。また、TR_EXCLUDEはソースを除外するために使用されます。gettextを使用するプロジェクトでは、通常ソースツリーを直接操作します。Qt 6.7では、qt_add_translationsについてもプロジェクト全体をカバーするビューを提供します。既存のプロジェクトを壊さないように、「1つのターゲットAPI」との互換性を維持しています。

中規模のプロジェクト、例えばゲームの古典であるfroggerのクローンを考えてみましょう。このプロジェクトはいくつかの部分から構成されています。

  • メインの実行可能ターゲットであるfrogger
  • ゲームロジックの主要部分であるgame_logicライブラリターゲット
  • カエルのジャンプをリアルにシミュレートするサードパーティのパブリックドメインライブラリjump_sim
  • 多数のテスト

トップレベルのプロジェクトファイルは次のようになります。

cmake_minimum_required(VERSION 3.28)
project(frogger)
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

このプロジェクトにはノルウェー語とドイツ語の翻訳がありますので、セットアップコールを以下のように調整します。

qt_standard_project_setup(I18N_TRANSLATED_LANGUAGES nb de)

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

froggerターゲットが作成された後、どこかで qt_add_translations を呼び出します。

qt_add_translations(frogger)

そして、完了です!これは以下のことを行います。

  • プロジェクトのすべてのターゲットからすべてのソースファイルを集めます。
  • プロジェクト名と、qt_standard_project_setupコールで渡した言語のリストから、frogger_nb.tsfrogger_de.tsを作成します。
  • 複数形のみを含むfrogger_en.tsを作成します。複数形については、下記説明を参照してください。
  • 収集したソースファイルから翻訳可能な文字列を抽出するためのupdate_translationsターゲットを作成します
  • release_translations ターゲットを作成し、.ts ファイルから .qm ファイルを作成します。.
  • qm ファイルを含む Qt リソースを作成し、それを frogger ターゲットに埋め込みます。
  • iOS 向けにビルドすると、アプリには frogger が英語、ノルウェー語、ドイツ語をサポートしているという情報が含まれます。

この例は、qt_add_translations の最も単純な使用法を示しています。自動化のさまざまな側面をカスタマイズしたり、ターゲット、ソース、または .ts ファイルを明示的に指定したりすることも可能です。その方法を示すことは、この記事の目的の範囲を超えています。より詳細な情報にご興味をお持ちの場合は、ドキュメントをご覧ください。

ターゲットとディレクトリを除外

偶然にも、jump_simターゲットとプロジェクトのテストには、froggerの.qmファイルでは必要のない翻訳可能な文字列が含まれています。

jump_simターゲットを翻訳対象から除外するには、ターゲットプロパティQT_EXCLUDE_FROM_TRANSLATIONをONに設定します。

set_property(TARGET jump_sim PROPERTY QT_EXCLUDE_FROM_TRANSLATION ON)

テストについては、tests ディレクトリ以下のすべてのターゲットを除外します。そのためには、tests/CMakeLists.txt ファイルで ディレクトリプロパティ QT_EXCLUDE_FROM_TRANSLATIONSON に設定します。

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

この設定により、jump_sim またはテストからソースファイルを渡さないようにします。

ソースターゲットを明示的に指定

この単純なプロジェクトでは、翻訳可能なソースファイルが存在するのは froggergame_logic の2つのターゲットのみ、と指定する方があれば簡単です。プロジェクトの一部を除外する代わりに、次のようにqt_add_translations を呼び出すことができます。

qt_add_translations(frogger
SOURCE_TARGETS frogger game_logic
)

翻訳対象の文字列を含むターゲットをプロジェクトに追加する場合は、SOURCE_TARGETS リストを調整することを忘れないでください。より複雑なプロジェクトの場合は、ターゲットを自動的に収集し、不要な部分を除外することをお勧めします。

ソース言語における複数形の処理

メインのゲーム画面では、すでに何匹のカエルが家に帰ったかを表示します。 このような文字列です。

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

この翻訳可能な文字列は複数形であり、ノルウェー語とドイツ語の翻訳ではカエルの数に応じて異なる文字列が表示されます(例えば、ドイツ語では「1 Frosch」と「2 Frösche」)。また、英語では「1 frog」と「2 frogs」と表示されるのが望ましいでしょう。これを実現するには、「ソースコード英語」から「人間が読む英語」への翻訳を作成し、複数形のみを含める必要があります。

翻訳されていないソース文字列が書かれている言語は、プロジェクトのソース言語と呼ばれます。 もう一つのよく使われる用語は開発言語です。 そして、qt_standard_project_setup では、その事実を次のように示します。

qt_standard_project_setup(
I18N_SOURCE_LANGUAGE en
I18N_TRANSLATED_LANGUAGES nb de
)

qt_add_translationsコマンドは、複数形の文字列のみを含むfrogger_en.tsファイルを自動的に作成します。Qt Linguistを起動して少数の複数形を「翻訳」し、froggerのゲーム進行表示に「1 frog is home」や「2 frogs are home」と表示できるようになりました。

ソース言語が英語の場合は、I18N_SOURCE_LANGUAGE を指定する必要はありません。これはデフォルト値だからです。qt_add_translationsNO_GENERATE_PLURALS_TS_FILE を渡すことで、複数形のみのファイルの作成を防ぐことができます。

まとめ

完全な例のCMakeソースは次のようになります。

cmake_minimum_required(VERSION 3.28)
project(frogger)
find_package(Qt6 COMPONENTS OpenGLWidgets)
qt_standard_project_setup(
  I18N_TRANSLATED_LANGUAGES nb de
)

add_subdirectory(3rdparty) # adds target jump_sim
set_property(TARGET jump_sim PROPERTY QT_EXCLUDE_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 QT_EXCLUDE_FROM_TRANSLATION ON)
add_subdirectory(tests) # adds several targets

qt_add_translations()

 

本記事では Qt 6.2 で導入した i18n CMake API の欠点と、Qt 6.7 で再検討した i18n CMake API でどのように対処したかについて説明しました。 重要なポイントは次のとおりです。

  • 単一ターゲットのビューではなく、プロジェクト全体の翻訳ビューが利用できるようになりました。
  • プロジェクトレベルでサポートする言語を指定することが可能(かつ推奨)になりました。
  • qt_add_translationsは、lupdateの入力として使用されるソースファイルのターゲットを自動的に収集できるようになりました。
  • ターゲットとディレクトリは、この自動収集プロセスから除外することができます。
  • 自動ターゲット収集が不要な場合は、ターゲットを明示的に渡すことができます。
  • qt_add_translationsは、.tsファイルの名前を自動的に生成することができます。この機能も無効にすることができます。
  • プロジェクトのネイティブ言語用の複数形 .tsファイルの作成がサポートされるようになりました。

この新しいAPIをぜひお試しください。ご意見をお待ちしております。


Blog Topics:

Comments