大規模なQtアプリケーションの翻訳は、特にIDベースの翻訳を使用する場合に困難を伴います。テキストベースの翻訳はコンテキスト(翻訳が行われるクラスや名前空間)ごとに整理されるのに対し、IDベースの翻訳には従来、この組織構造が欠けていました。数百から数千ものIDベースの翻訳文字列を含む大規模プロジェクトでは、翻訳者は「<unnamed context>」という単一の膨大なリストを操作せざるを得ませんでした。
この課題に対処する新機能「IDベース翻訳用ラベル」をQt 6.11で提供開始します。ラベルはIDベース翻訳エントリを論理的なグループに整理するシンプルかつ強力な手段であり、翻訳者のワークフロー効率化を実現します。
Qt は国際化 (i18n) に対して2つのアプローチを提供します。
テキストベースの翻訳では、翻訳を検索するキーとしてソーステキストとコンテキストの組み合わせを使用します。コンテキストは、コード構造の中で翻訳が発生する場所を特定します。C++では、クラス内で tr("Open File") を呼び出すとき、コンテキストはクラス名とその名前空間です。QMLでは、qsTr("Open File") を呼び出すと、コンテキストは通常QMLファイル名、またはpragma Translator の記法を使って定義した場合はカスタマイズしたコンテキスト名となります。
翻訳システムは原文("Open File")とコンテキストをルックアップのキーとして使用します。これらの翻訳は自動的に整理され、Qt Linguist のコンテキストごとにグループ化されるため、翻訳者は各文字列がアプリケーションのどの部分に属しているかを簡単に理解することができます。
IDベースの翻訳では、ソーステキストの代わりに一意の識別子を使用します。C++ で qtTrId("msg.open")、QML で qsTrId("msg.open") を呼び出すと、翻訳システ ムはルックアップ・キーとして "msg.open "を使用します。この方法にはいくつかの利点があります:
しかし、IDベースの翻訳には固有のコンテキストがありません。 翻訳者がQt LinguistでIDベースの翻訳を含むTSファイルを開くと、すべてのエントリが1つのグループに表示され、ナビゲートや翻訳作業の構成を理解するのが難しくなります。
ラベルは、アプリケーションの実行時の動作に影響を与えることなく、IDベースの翻訳を意味のあるグループに分類する方法を提供します。テキストベースの翻訳がコンテキストによってグループ化されるように、IDベースの翻訳もラベルによってグループ化することができます。それでも、翻訳のルックアップは以前と同じようにIDだけを使用して実行されます。ラベルは、翻訳者が翻訳作業をナビゲートし、構造を理解するための、純粋に組織的な目的のためのものです。
IDベースの翻訳にラベルを追加するのは簡単です。翻訳の前に//@ LabelName コメントを追加するだけです。
C++の例
//% "Open file"
//@ FileOperations
qtTrId("msg.open");
//% "Save file"
//@ FileOperations
qtTrId("msg.save");
//% "Connection timeout"
//@ NetworkErrors
qtTrId("err.timeout");
QMLの例
//% "Open file"
//@ FileOperations
qsTrId("msg.open")
//% "Save file"
//@ FileOperations
qsTrId("msg.save")
//% "Connection timeout"
//@ NetworkErrors
qsTrId("err.timeout")
lupdateがこれらの翻訳を抽出するとき、IDベースの各エントリーにラベルを関連付けます。Qt Linguistでは、同じラベルを持つエントリは一緒にグループ化されます(コンテキストでグループ化されたテキストベースの翻訳に似ています)。
ラベルは、ID ベースの翻訳をサポートするすべての Qt ファイルタイプで一貫して機能します。
qtTrId()呼び出しの前に //@ LabelNameコメントを使用。qsTrId()呼び出しの前に //@ LabelNameコメントを使用。PythonのQtはIDベースの翻訳をサポートしていないので、Pythonのファイルには同等のものがないことに注意してください。
手動でラベル名を指定するのは多くのプロジェクトでうまくいきますが、大規模なコードベース全体で一貫したラベル名を維持するのは面倒になることがあります。コード構造からラベルを派生させるプレースホルダを使用することで、ラベルを自動生成することができます。
<context> - 完全なコンテキストパス(C++ではnamespace::class、QMLではコンポーネント名)を使用します。<class> - 名前空間プレフィックスを除いたクラス名のみを使用します(C++のみ)。<file> - ソースファイル名を使用します。例えば、C++の場合
namespace MyApp {
class FileHandler : QObject {
Q_OBJECT
void open() {
//% "Open file"
//@ <context>
qtTrId("msg.open"); // Label becomes: MyApp::FileHandler
//% "Save file"
//@ <class>
qtTrId("msg.save"); // Label becomes: FileHandler
//% "Export"
//@ <file>
qtTrId("msg.export"); // Label becomes: filehandler.cpp
}
};
}
またはQMLでは
// main.qml
Item {
id: mainView
Component.onCompleted: {
//% "Loading"
//@ <context>
qsTrId("msg.loading") // Label becomes: mainView
//% "Ready"
//@ <file>
qsTrId("msg.ready") // Label becomes: main.qml
}
}
プレースホルダーはカスタムテキストと組み合わせて、より明確なラベルを作成することができます。
//% "Open file"
//@ <file>:<class>
qtTrId("msg.open"); // Label: filehandler.cpp:FileHandler
//% "Network error"
//@ module_<context>
qtTrId("err.network"); // Label: module_MyApp::ErrorHandler
//% "Status update"
//@ <file>_<class>-state
qtTrId("status.update"); // Label: statusbar.cpp_StatusBar-state
自動ラベルは、手動でのラベル管理が現実的でない大規模なプロジェクトで特に役立ちます。開発者がラベル名を手動で管理することなく、コード構造に基づいた一貫性のあるグループ化を保証し、各翻訳がどこで使用されているかについてのコンテキストヒントを翻訳者に自動的に提供します。
(qtTrId,qsTrId) でのみ機能します。<class> を使用すると警告が表示されますので、代わりに <unnamed> が表示されます。新しいラベル機能をサポートするために、Qt Linguist は混合された翻訳タイプを扱うための、より直感的なインターフェイスに強化されました。コンテキストドックウィジェットには、翻訳者が切り替えられるタブが追加されました:
この分離により、一度に 1 つの翻訳アプローチに集中しやすくなりました。
ID ベースの翻訳では、列のヘッダーに関連情報が表示されます。
ラベルのないエントリは、"<unnamed label>"の下に表示されます。
ラベルは純粋に整理のためのものです。翻訳者が翻訳作業の構造を理解し、作業を進めるための補助として存在します。実行時の動作には一切影響しません。
qtTrId("msg.open")を呼び出します。これにより、アプリケーションの機能に影響を与えることなく、ラベルを追加、変更、削除することができます。
lupdateは、ラベルを正しく使うための検証を行います。
無効:テキストベースの翻訳のラベル
//@ MyLabel // Warning: labels cannot be used with text-based translation
tr("Open File");
警告:同じIDのラベルの衝突
//% "Open file"
//@ FileOperations
qtTrId("msg.open");
// コードの後半で...
//% "Open file"
//@ UserInterface // Warning: contradicting labels for id "msg.open"
qtTrId("msg.open");
同じIDが異なるラベルで利用されると、lupdateは警告を発します。
コードベースでは、特定のIDのラベルを一度定義すれば十分です。しかし、コードをわかりやすくするために、複数の場所でラベルを繰り返すこともできます。ラベルは、最初に定義された場所に関係なく、翻訳ファイルのIDに関連付けられます。
ラベルが実際のアプリケーションの翻訳ワークフローをどのように改善するか見てみましょう。数百のIDベースの翻訳を持つ医療機器のインターフェイスを考えてみましょう。
ラベルを使う前は、500以上の翻訳がすべて「<unnamed context>」という単一のリストに表示されていました。翻訳者は、どの文字列がどの機能に属するかを理解するのが難しく、文脈の混乱や翻訳の遅れにつながっていました。
ラベルを使用すると、翻訳は論理的なグループに整理されます。便利なパターンは、QT_TRID_NOOP を使用して、すべての ID ベースの翻訳をソーステキストとラベルとともに専用の場所(ヘッダーファイルなど)に一度定義することです。
// translations.h - Central definition of all ID-based translations
// Patient Management
//% "Patient Name"
//@ PatientManagement
QT_TRID_NOOP("patient.name");
//% "Date of Birth"
//@ PatientManagement
QT_TRID_NOOP("patient.dob");
//% "Patient ID"
//@ PatientManagement
QT_TRID_NOOP("patient.id");
// Vital Signs Monitoring
//% "Heart Rate"
//@ VitalSigns
QT_TRID_NOOP("vitals.heartrate");
//% "Blood Pressure"
//@ VitalSigns
QT_TRID_NOOP("vitals.bloodpressure");
//% "Temperature"
//@ VitalSigns
QT_TRID_NOOP("vitals.temperature");
// Device Errors
//% "Sensor Disconnected"
//@ DeviceErrors
QT_TRID_NOOP("err.sensor.disconnected");
//% "Battery Low"
//@ DeviceErrors
QT_TRID_NOOP("err.battery.low");
//% "Calibration Required"
//@ DeviceErrors
QT_TRID_NOOP("err.calibration.required");
一度一元的に定義すれば、ソース文字列やラベルを繰り返すことなく、コードベース全体でこれらのIDを使用することができます。
// patientview.cpp
void PatientView::displayPatient(const Patient &patient)
{
nameLabel->setText(qtTrId("patient.name"));
dobLabel->setText(qtTrId("patient.dob"));
idLabel->setText(qtTrId("patient.id"));
}
// vitalsmonitor.cpp
void VitalsMonitor::updateDisplay()
{
heartRateLabel->setText(qtTrId("vitals.heartrate"));
bpLabel->setText(qtTrId("vitals.bloodpressure"));
tempLabel->setText(qtTrId("vitals.temperature"));
}
// errorhandler.cpp
void ErrorHandler::showError(ErrorType type)
{
QString message;
if (type == SensorError)
message = qtTrId("err.sensor.disconnected");
else if (type == BatteryError)
message = qtTrId("err.battery.low");
// ...
}
これにより、翻訳者は論理的なセクションごとに作業を進められるようになります。まず患者管理関連の文字列をまとめて翻訳し、次にバイタルサイン関連の文字列をまとめて翻訳するといった具合です。グループ化された構成により文脈が明確になり、翻訳作業がより直感的で効率的になります。この手法は、クリーンで読みやすいコードを維持するとともに、全ての翻訳定義を管理しやすい一箇所に集約します。
当社の実装とテストに基づき、効果的なラベル使用に関する推奨事項を以下にご紹介します。
ラベルは Qt 6.11 から利用できます。プロジェクトで使用するには
LabelNameまたは//@ <placeholder> を使用して、ID ベースの翻訳にラベルコメントを追加しますQt Widgets Designer UI ファイルの場合、フォーム設定ダイアログでラベルを設定すると、そのフォーム内のすべての ID ベースの翻訳が自動的にラベルを継承します。
ラベルは既存のプロジェクトに統合できます。ラベルのないTSファイルは、Linguistの「<unnamed label>」の下にラベルのないエントリが表示され、完全に機能し続けます。一度にすべての翻訳にラベルを付ける必要はなく、少しずつラベルを追加することができます。旧バージョンの Qt を使用しているユーザーのために、最小限の後方互換性サポートにより、ラベル付きの UI ファイルが旧バージョンでエラーを生成しないようにします。
実装に興味のある方のために、ラベルは以下の通りです。
//@ LabelNameコメントからlupdate によって抽出されます。ラベルは Qt における ID ベースの翻訳に整理をもたらし、大規模なプロジェクトに携わる翻訳者の課題に対処します。関連する翻訳をグループ化するためのシンプルな構文を提供することで、ラベル名が手動割り当てであれ、コード構造からの自動生成であれ、ラベルは翻訳ワークフローをより効率的で直感的なものにします。
新しいプロジェクトを開始する場合でも、既存のプロジェクトを保守する場合でも、ラベルを使用することで、国際化ワークフローを低労力で効果的に改善することができます。この機能は既存の Qt 翻訳ツールとよく統合でき、開発プロセスへの変更も最小限で済みます。ラベルは Qt 6.11 で利用可能になります。
Qt 6.11 以降のプロジェクトでラベルを試し、フィードバックを Qt コミュニティと共有することをお勧めします。Qt の国際化機能の詳細については、Qt Linguist Manual を参照してください。