Qt/C++ による GUI ソフト 8:国際化

このページでは,Qt6 翻訳ツールを使って GUI アプリケーションを国際化する練習をします.
材料となる国際化前のコードは,練習 2 で作成したメニューバーの「ファイル」を少し改変したものです.

まず,Qt6 翻訳ツールを使って翻訳用のソースファイルを作成します.
次いで,翻訳バイナリファイルを実行ファイルに埋め込む方法を試します.
さらにソースファイルを改変し,翻訳バイナリファイルを実行ファイルとは別に作成しておき,起動時にロードする方法を試します.

このページでは,翻訳用の定番 GUI ツール Linguist は使いません.
ソースファイルをどう改変してどのようなファイルが生成するかを学ぶのが目的なので,テキストエディタを使います.

Before : 6:レンダリング その2

インフォメーション

ドキュメント

Qt のドキュメント Qt 6 のなかにある,参考にしたページを紹介しておきます,

インストールした Debian パッケージ

パッケージ名(バージョン)
g++(14.2.0)GNU C++ コンパイラ
make(4.4.1)コンパイルを制御するユーティリティ
qt6-base-dev(6.8.2)Qt6 アプリケーションのビルドに使用されるヘッダ開発ファイル
qt6-3d-dev(6.8.2)Qt 3D ライブラリを使用した Qt6 アプリケーションのビルドに使用されるヘッダ開発ファイル
qt6-tools-dev-tools(6.8.2) Qt 6 開発ツール.Debian のパッケージ: qt6-tools-dev-tools (6.8.2-5) によると "translate applications" も含まれます

目次(ページ内リンク)


国際化前のソースファイル

メニューバーで作成したコードを改変して材料とします.
下がそのコードで,メインウィンドウにメニューバーを載せただけです.メニューは "File(F)" のみであり,何も機能しません.
ここでは "File(F)" を国際化します.

ソースファイル

これらは,tmp というディレクトリに置くとします.

main_window.h


#include <QMainWindow>
#include <QMenuBar>

class Main_Window : public QMainWindow {
 Q_OBJECT  //メニューの国際化には Q_OBJECT を宣言する必要があります.これは,Qt の C++ 拡張をおこなうためのマクロです

 private:
  QMenuBar *menu_bar;
  QMenu *file_menu;

 public:
  Main_Window(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags());
};

main_window.cc

国際化のソースコードを記述する方法は,Writing Source Code for Translation に記されています.

赤字で記したように,国際化すべき部分を tr() 関数の引数としてコーディングします.
tr() 関数は QObject Class のメンバ関数で,戻り値は引数としたソーステキストの翻訳版です.
あとは,戻り値となる翻訳版をどこかに作成しておけばいいわけです.


#include "main_window.h"

Main_Window::Main_Window(QWidget *parent, Qt::WindowFlags f)
 : QMainWindow(parent, f),
   menu_bar(menuBar())
{
 resize(320, 240);
 setWindowTitle("Main_Window");

 file_menu = new QMenu(tr("File(&F)"), this);  //国際化すべき部分を tr() 関数に渡す
 menu_bar->addMenu(file_menu);
}

main.cc


#include "main_window.h"
#include <QApplication>

int main(int argc, char *argv[]) {
 QApplication app(argc, argv);

 Main_Window window(nullptr, Qt::Window);
 window.show();

 return app.exec();
}

tmp.pro

qmake を使ったビルドのためのプロジェクトファイルです.
このページの主題とはあまり関係ないのですが,このページを作成している際に動作確認でインストールが必要になったので,インストールとアンインストールできるようにしました.


TEMPLATE = app
TARGET = my_window
INCLUDEPATH += .
QT += widgets
SOURCES += main.cc main_window.cc
HEADERS += main_window.h

target.path = /usr/local/bin  //インストール先
INSTALLS += target  //インストールされるファイル

ビルドと実行

国際化前のメインウィンドウ

1:メインウィンドウ以来同じ手順です.ここではインストールはしません.
~/tmp$ qmake6
~/tmp$ make
~/tmp$ ./my_window &

画像は実行イメージです.
メニューバーに "File(F)" と表示されています.


翻訳作業

翻訳用のファイルを置くためのサブディレクトリの追加

ここでは,ソースファイルを置いた tmp ディレクトリにサブディレクトリ i18n を追加します.
そして,tmp/i18n 内に翻訳用のファイルを置くわけです.
サブディレクトリは別の名前でも可能ですし,サブディレクトリを作成しないで,tmp ディレクトリ内に翻訳用のファイルを置くこともできます.

~/tmp$ mkdir i18n

プロジェクトファイルの改訂

qmake のプロジェクトファイル,tmp.pro に,3 行追加しました.赤字で示しています.


TEMPLATE = app
TARGET = my_window
INCLUDEPATH += .
QT += widgets
SOURCES += main.cc main_window.cc
HEADERS += main_window.h

target.path = /usr/local/bin
INSTALLS += target

TRANSLATIONS += i18n/trans_ja.ts  #上で作成したサブディレクトリに翻訳ファイルを作成
CONFIG += lrelease  #翻訳バイナリファイル作成ツール
CONFIG += embed_translations  #作成した翻訳バイナリファイルを実行ファイルに埋め込む

翻訳ファイルの雛形

このページでは,翻訳ファイルのソースファイル の名前を trans_ja.ts とします.

trans_ja.ts の雛形を作成します.
プロジェクトファイルを引数にして lupdate コマンドを実行すると,trans_ja.ts が作成されます.
~/tmp$ /usr/lib/qt6/bin/lupdate tmp.pro
(パスが通っていませんでした,あまり使うものではないので,フルパスでコマンドを呼び出しています)
下がそのときの端末への出力です.

~/tmp$ /usr/lib/qt6/bin/lupdate tmp.pro
Updating 'i18n/trans_ja.ts'...
    Found 1 source text(s) (1 new and 0 already existing)

雛形ファイルの作成先は,プロジェクトファイル tmp.pro に記しておいた i18n/trans_ja.ts となります.
下が i18n/trans_ja.ts の内容です.赤字で示した箇所を書き換えればよさそうです.


<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="ja_JP">
<context>
    <name>Main_Window</name>
    <message>
        <location filename="../main_window.cc" line="10"/>
        <source>File(&amp;F)</source>
        <translation type="unfinished"></translation>
    </message>
</context>
</TS>

翻訳

以下のように改定しました.


<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="ja_JP">
<context>
    <name>Main_Window</name>
    <message>
        <location filename="../main_window.cc" line="10"/>
        <source>File(&amp;F)</source>
        <translation>ファイル(&amp;F)</translation>
    </message>
</context>
</TS>

ちなみに,&amp; は,マークアップ言語で & を表示するための記法で,文字実体参照といいます.


翻訳ファイルを実行ファイルに埋め込む方法

QTranslator Class を使えば,翻訳バイナリファイルを作成し,実行ファイルに組み込むことができます.
QTranslator Class のページに記されているサンプルコードでは,メンバ関数
bool load(const QLocale &locale, const QString &filename, const QString &prefix = QString(), const QString &directory = QString(), const QString &suffix = QString())
を使っています.

main.cc の改定


#include "main_window.h"
#include <QApplication>
#include <QTranslator>

int main(int argc, char *argv[]) {
 QApplication app(argc, argv);

 QTranslator translator;
 if(translator.load(QLocale(), QLatin1String("trans"), QLatin1String("_"), QLatin1String(":/i18n")))
 app.installTranslator(&translator);

 Main_Window window(nullptr, Qt::Window);
 window.show();

 return app.exec();
}

ここで使っている load() 関数は,引数を基にして.ファイル名 + プレフィックス + UI 言語名 + サフィックス (サフィックスが指定されていない場合は「.qm」) をロードすると書いてあります.
最初は分かりづらかったので,load() 関数の宣言での仮引数と main() 関数内での実引数を対応させて記してみました,

  1. const QLocale &locale ⇔ QLocale()
  2. const QString &filename ⇔ QLatin1String("trans") //"trans は,翻訳ファイルの "_" より前の部分"
  3. const QString &prefix = QString() ⇔ QLatin1String("_")
  4. const QString &directory = QString() ⇔ QLatin1String(":/i18n")
  5. const QString &suffix = QString() ⇔ 省略 //サフィックスは ".qm" が使われる

これらの引数を基にして,i18n/trans_ja.ts から作成される翻訳バイナリファイル i18n/trans_ja.qm がロードされ,実行ファイルに組み込まれるわけです.
UI 言語名というのは,ここでは "ja" のことだと判ります.

ビルドとインストール

定番のコマンドを実行します.
~/tmp$ qmake6
~/tmp$ make
~/tmp$ sudo make install

実行

国際化後のメインウィンドウ

実行ファイルに国際化リソースが組み込まれていることを確認します.
~/tmp$ my_window &
画像のように,メニューが日本語で表示されました.成功です.

アンインストールとクリーンアップをしておきます.
~/tmp$ sudo make uninstall
~/tmp$ make distclean


翻訳バイナリファイルを起動時にロードする方法

翻訳バイナリファイル *.qm を作成してロードする方法を試してみました.
翻訳先の言語が多くなるとこちらを使うのかもしれません.
ここでは練習ということで,手動で作成してインストールしました.

翻訳バイナリファイルの作成とインストール

まず,翻訳バイナリファイル trans_ja.qm を作成します.
元になるファイルは trans_ja.ts で,この作成法は上と同じで,サブディレクトリ i18n に作成します.
~/tmp$ /usr/lib/qt6/bin/lrelease i18n/trans_ja.ts -qm i18n/trans_ja.qm
パスが通っていなかったので,フルパスで lrelease を使っています.
i18n 内に trans_ja.qm というファイルが生成していました.

次にインストールです.
ここでは,/usr/local/bin/i18n というディレクトリを作成してコピーしました.
~/tmp$ sudo mkdir /usr/local/bin/i18n
~/tmp$ sudo cp i18n/trans_ja.qm /usr/local/bin/i18n/

プロジェクトファイルの改変

翻訳バイナリファイルを手動でインストールしたので,実行ファイルに組み込む指示は不要です.
すなわち,tmp.pro から,CONFIG += lrelease 行と CONFIG += embed_translations 行をコメントアウトしました.


TEMPLATE = app
TARGET = my_window
INCLUDEPATH += .
QT += widgets
SOURCES += main.cc main_window.cc
HEADERS += main_window.h

target.path = /usr/local/bin
INSTALLS += target

TRANSLATIONS += i18n/trans_ja.ts
#CONFIG += lrelease
#CONFIG += embed_translations 

main() 関数の改変:QLocale から情報取得

上のコードでは,無条件に trans_ja.qm をロードしています.これでは日本語化です.
国際化と称するためには,ローケールを取得して,適切な .qm ファイルをロードする必要があります.
ここでは,QLocale Class をインスタンス化し,ローケール情報を取得してみます.

main() 関数を以下のように改変します.
コードでは,QLocale インスタンスから情報を取得し,端末に出力しています.情報を取得する関数は,QLocale Class に書いてあるので,その一部を利用しました.


#include <QMainWindow>
#include <QApplication>
#include <QTranslator>
#include "main_window.h"
#include <iostream>

int main(int argc, char *argv[]) {
 QApplication app(argc, argv);

 QLocale locale;
 //どんな情報が得られるか練習している
 std::cout << locale.amText().toStdString() << std::endl;
 std::cout << locale.bcp47Name().toStdString() << std::endl;  //IETF(Internet Engineering Task Force) 言語タグ
 std::cout << locale.currencySymbol().toStdString() << std::endl;  //通貨記号
 std::cout << locale.dateFormat().toStdString() << std::endl;
 std::cout << locale.dateTimeFormat().toStdString() << std::endl;
 std::cout << locale.dayName(1).toStdString() << std::endl;
 std::cout << locale.language() << std::endl;  //戻り値は enum Qlocale::Language.日本語は 120

 QTranslator translator;
 if(locale.language() == QLocale::Japanese && translator.load("/usr/local/bin/i18n/trans_ja"))
  app.installTranslator(&translator);
 //if(locale.bcp47Name() == "ja" …… でもよさそう

 Main_Window window(nullptr, Qt::Window);
 window.show();

 return app.exec();
}

ビルドと実行

定番のコマンドを実行します.
~/tmp$ qmake6
~/tmp$ make
~/tmp$ sudo make install
インストールされた実行ファイルと翻訳バイナリファイルが利用されるのを確認するために,カレントディレクトリにある実行ファイルと翻訳バイナリファイルを削除します.
~/tmp$ make distclean
~/tmp$ rm i18n/trans_ja.qm
実行
~/tmp$ my_window &
国際化されたウィンドウが表示されるとともに,下記の内容が端末に出力されました.

午前
ja
¥
yyyy年M月d日dddd
yyyy年M月d日dddd H時mm分ss秒 tttt
月曜日
120

確認

作業ディレクトリとインストールしたファイル,ディレクトリを削除するついでに,翻訳バイナリがなければどうなるかテストします.
~/tmp$ sudo rm -rf /usr/local/bin/i18n
とやって翻訳バイナリを削除して
~/tmp$ my_window &
とすると,国際化されていないウィンドウが表示されました.

後片付け

あとはファイルとディレクトリを作事しておきます.
~/tmp$ sudo make uninstall(または sudo rm /usr/local/bin/window)
~/tmp$ cd
~$ rm -rf tmp

日本語化

インストールされた日本語翻訳バイナリファイルと load() 関数を使って日本語化するなら,main() 関数内の該当箇所を下のように書いてもよさそうです.


 QTranslator translator;
 if(translator.load("/usr/local/bin/i18n/trans_ja"))  //これでは日本語化.国際化は,ローケールを取得して適切な .qm ファイルをロードする
  app.installTranslator(&translator);

それならいっそ,ソースファイルのメニューなどを日本語で書けば充分.
国際化は,ローケールを取得して適切な .qm ファイルをロードする作業です.