home ホーム search 検索 -  login ログイン  | reload edit datainfo version cmd icon diff delete  | help ヘルプ

C言語系/memos/gettext

C言語系/memos/gettext

C言語系 / memos / gettext
id: 488 所有者: msakamoto-sf    作成日: 2009-11-18 16:05:11
カテゴリ: C言語 Linux 

GNU gettext の自分用メモ


基本的な流れ

・最初のソースコード準備からpoファイル作成、moファイルへのコンパイルまで

  1. ソースコードを準備する。
  2. "xgettext"で検出する為のマクロ定義を埋め込む。
  3. 翻訳したい文字列に対して、マクロを適用する。
  4. setlocale()など必要な前処理やヘッダファイルインクルードを組み込む。
  5. コンパイルして一旦Cロカールで動作確認
  6. xgettextでメッセージを抽出し、"*.pot"(poのテンプレートファイル)生成
  7. "*.pot"ファイルを"*.po"にリネームし、翻訳。
  8. msgfmtで"*.po"ファイルを"*.mo"ファイルにコンパイル。
  9. "*.mo"ファイルをbindtextdomain()やtextdomain()で指定したパッケージ名+".mo"にリネームし、ロカール用ディレクトリに置く。

・ソースコードが変更された場合のメンテナンス作業

  1. xgettextでメッセージを抽出し、"*.pot"(poのテンプレートファイル)生成
  2. msgmergeで"*.po"ファイルにマージ、翻訳。
  3. msgfmtでコンパイルし、パッケージ名+".mo"にリネーム、ロカール用ディレクトリに配置。

GNU gettext HTMLドキュメント, "1.5 Overview of GNU gettext" よりファイル関連図を抜粋する(元の図から、"PO Compendium"という要素が分かりづらかったので取り除いてあります)*1

Original C Sources ─> Preparation ─> Marked C Sources ──┐
                                                            │
                 ┌─<── GNU gettext Library              │
┌── make <──┤                                         │
│               └─<─────────────┬─────┘
│                                              │
│        ┌─<─ PACKAGE.pot <─ xgettext <──┘
│        │         │
│        │         └─────>─────┐
│        │                                ├─> PO editor ─┐
│        ├─> msgmerge ──> LANG.po ─>─┘                │
│        │                                                  │
│        └───<───<───┐                            │
│                              ├─── New LANG.po <────┘
│  ┌─ LANG.gmo <─ msgfmt <─┘
│  │
│  └─> install ──> /.../LANG/PACKAGE.mo ──┐
│                                               ├───> "Hello world!"
└─> install ───> /.../bin/PROGRAM ─────┘

最初のソースコード準備からpoファイル作成、moファイルへのコンパイルと配置まで

下記のような簡単なサンプルコードに対して、徐々にgettext対応を肉付けしていく。
test.c:

#include <stdio.h>
 
char *msg = "GNU gettext excersize";
 
int main() {
    puts("Hello, World!");
    puts(msg);
    return 0;
}
$ gcc -Wall -o test test.c
$ ./test
Hello, World!
GNU gettext excersize
"xgettext"で検出する為のマクロ定義を埋め込み、翻訳対象文字列に適用する。

例えば次のようなメッセージであれば、

puts("Hello, World!");

このようにするのが基本となる。

puts(gettext("Hello, World!"));

ただしこのままだとキータイプ数が多い上、test.cにおける

char *msg = "...";

にも対応できない。グローバル変数に対して下記のようなコードは書けない。

char *msg = gettext("...");

このため、GNU gettext HTMLドキュメントでは次のマクロを定義する事を奨めている。
まず、普通に "puts(...)" に埋め込めるマクロを定義する。

#define _(String) gettext (String)

次にグローバル変数の初期化などに使うマクロを定義する。

#define gettext_noop(String) (String)
#define N_(String) gettext_noop (String)

このマクロを次のように埋め込む事で、グローバル変数の初期値に対してもマクロを適用できる。なぜなら、結局"N_"マクロは何も加工しないからである。

char *msg = N_("GNU gettext excersize");
...
puts(_(msg));

何も加工しない"N_"マクロをわざわざ定義したのは、続く"xgettext"でも検出できるようにする為である。

これで、test.cは次のようになる。

#include <stdio.h>
 
#define _(String) gettext(String)
#define N_(String) gettext_noop(String)
#define gettext_noop(String) (String)
 
char *msg = N_("GNU gettext excersize");
 
int main() {
    puts(_("Hello, World!"));
    puts(_(msg));
    return 0;
}
setlocale()など必要な前処理の呼び出しと、コンパイルして一旦Cロカールで動作確認

setlocale()では環境変数からロカール名を取得するようにする。bindtextdomain(), textdomain()でドメイン名とメッセージカタログディレクトリを設定する。ドメインとは、gettext()の翻訳するmsgidのセットのこと。
今回はドメイン名は"test", メッセージカタログディレクトリは"."起点とした。

#include <stdio.h>
 
/* setlocale() */
#include <locale.h>
 
/* bindtextdomain(), textdomain() */
#include <libintl.h>
 
#define _(String) gettext(String)
#define N_(String) gettext_noop(String)
#define gettext_noop(String) (String)
 
char *msg = N_("GNU gettext excersize");
 
int main() {
    /* ロカール名を環境変数から取得 */
    setlocale(LC_ALL, "");
    /* ドメイン名"test"のメッセージカタログディレクトリをカレントディレクトリに設定 */
    bindtextdomain("test", ".");
    /* ドメイン名を"test"に設定 */
    textdomain("test");
 
    puts(_("Hello, World!"));
    puts(_(msg));
    return 0;
}
$ gcc -Wall -o test test.c
$ ./test
Hello, World!
GNU gettext excersize
xgettextでメッセージを抽出 → "*.pot" → "*.po"翻訳

基本:

xgettext -kキーワード -o XXYY.pot 抽出対象ファイル群

キーワードに"_", "N_"の二種類を指定することで、両方のマクロが適用された翻訳対象文字列を抽出できる。

$ xgettext -k"_" -k"N_" -o test.pot test.c
$ cp test.pot test.po

とりあえず"Content-Type"に文字コードを設定し、他、空になっているmsgstrに日本語翻訳文字列を埋め込む。

"Content-Type: text/plain; charset=CHARSET\n"
→ とりあえずUTF-8で埋め込むので、UTF-8にしておく。EUC-JPとかでもOK.
"Content-Type: text/plain; charset=UTF-8\n"
...
#: test.c:9
msgid "GNU gettext excersize"
msgstr "GNU gettext 練習サンプル"

#: test.c:16
msgid "Hello, World!"
msgstr "こんにちは、世界!"
msgfmtでコンパイル → "*.mo"ファイルを配置・動作確認

基本:

msgfmt -o XXYY.mo foo.po

今回は以下のようにコンパイルした。

$ msgfmt -o test.mo test.po

メッセージカタログディレクトリの起点は"."にしてあるので、ディレクトリを作成してmoファイルを配置する。

$ mkdir -p ja/LC_MESSAGES
$ cp test.mo ja/LC_MESSAGES/test.mo

途中の*.poや*.moファイルは偶々ドメイン名と同じにして作業していたが、最終的にLC_MESSAGES/以下に配置される時にドメイン名.moになっていればよい。途中ファイルについては好きなファイル名にしても問題ない。

動作確認:

$ LANG=C ./test
Hello, World!
GNU gettext excersize
$ LANG=ja_JP.UTF-8 ./test
こんにちは、世界!
GNU gettext 練習サンプル

ソースコードが変更された場合のメンテナンス作業

次のようにtest.cを修正してみる。

#include <stdio.h>
#include <locale.h>
#include <libintl.h>
 
#define _(String) gettext(String)
#define N_(String) gettext_noop(String)
#define gettext_noop(String) (String)
 
int main() {
    setlocale(LC_ALL, "");
    bindtextdomain("test", ".");
    textdomain("test");
 
    puts(_("Hello, World!!"));
    puts(_("How are you?"));
    return 0;
}

変更点:

  • "Hello, World"の末尾の"!"が、1個→2個に増えた。
  • "How are you?"メッセージの追加
  • "GNU gettext excersize" の削除
$ gcc -Wall -o test test.c
$ ./test
Hello, World!!
How are you?
xgettextでメッセージ抽出、msgmergeでマージ

基本:

msgmerge マージ先poファイル マージ元potファイル -o マージ結果出力poファイル
$ xgettext -k"_" -k"N_" -o test.pot test.c
$ msgmerge test.po test.pot -o new_test.po
.. 完了.

new_test.po:

...

#: test.c:14
#, fuzzy
msgid "Hello, World!!"
msgstr "こんにちは、世界!"

#: test.c:15
msgid "How are you?"
msgstr ""

#~ msgid "GNU gettext excersize"
#~ msgstr "GNU gettext 練習サンプル"
#, fuzzy

→元のmsgidから少しだけ変更された場合に付く特殊なフラグコメント。"#, fuzzy"付のエントリはmsgfmtからは「翻訳済」とは見なされない。

#~ msgid ...
#~ msgstr ...

→obsoleteなメッセージで、使われなくなったmsgid, msgstrを意味する。

とりあえず以下のように書き換えておく。

#: test.c:14
msgid "Hello, World!!"
msgstr "こんにちは、世界!!"

#: test.c:15
msgid "How are you?"
msgstr "御機嫌如何?"
msgfmtでコンパイル→動作確認
$ msgfmt new_test.po -o test.mo
$ cp test.mo ja/LC_MESSAGES/
cp: `ja/LC_MESSAGES/test.mo' を上書きしてもよろしいですか(yes/no)? yes
$ gcc -Wall -o test test.c
$ LANG=C ./test
Hello, World!!
How are you?
$ LANG=ja_JP.UTF-8 ./test
こんにちは、世界!!
御機嫌如何?

Autoconf/Automakeに組み込みたい

GNU gettext HTMLドキュメントの "13 The Maintainer's View" に詳細が載っている為、ここではポイントとなる手順だけを示す。

  1. m4ディレクトリを用意し、トップのMakefile.amで "ACLOCAL_AMFLAGS = -I ./m4" を指定しておく。
  2. Autoconfによるconfigureスクリプトまで生成させたら、"gettextize -c --intl" を実行する。poディレクトリ+ファイル群の作成、m4ディレクトリへのマクロファイルコピー、configure.ac,Makefile.amなどが自動調整される。
  3. po/POTFILES.in へメッセージ抽出対象のソースファイルを列挙する。
  4. po/Makevars.template を po/Makevars にリネーム or コピーする。
  5. configure.acに以下のマクロを追加
    1. AC_CANONICAL_HOST : AM_GNU_GETTEXTがconfigu.guess, config.subを必要としているため。
    2. AM_GNU_SOURCE, AM_GNU_GETTEXT
    3. AC_CONFIG_FILESに intl/Makefile, po/Makefile.in を追加

メンテナンス:

  • テンプレートの更新:"cd po/; make (POT名).pot-update"
  • メッセージカタログ(ロカール)の追加:po/LINGUASにロカール名を追加
  • カタログの更新とコンパイル:"make update-po" or "make"

gettextの無効化:

  • configure時に "--disable-nls" オプションを指定する。
  • ENABLE_NLSマクロが未定義になる為、#ifndefなどで #include <libintl.h> を #include "gettext.h" にする。
  • gettext.hはシステムに入っているlibディレクトリなどからコピーしてくる。ただし、gettext.h自体はシステムディレクトリにインストールしないように注意する。

以下、GNU Libtool HTMLドキュメントでの主な見出しをポイントしておく。(見出しの番号については2009/11時点のものなので、将来変更される可能性がある)

  • "xgettext"によるPOテンプレートファイルの生成機能を詳しく知りたい
    • "5 Making the PO Template File" 以下参照
  • "msgfmt", "msgmerge" の他にどんな"msgXXXX"ツールがあるか知りたい
    • "9 Manipulating PO Files" 以下参照
  • "msgfmt"の詳細を知りたい、逆方向の変換ツール"msgunfmt"を知りたい、GNU MOファイルのフォーマットを知りたい
    • "10 Producing Binary MO Files" 以下参照
  • Autoconf/Automakeとの連携の詳細を知りたい
    • "13 The Maintainer's View" 以下参照

*1: "*.gmo"ファイルというのは、"GNU gettextのサポートするmoファイルフォーマット"という意味。moファイルのバイナリフォーマットはシステムによって差異があり、GNU gettextがサポートしないフォーマットの場合もある。この図では特にGNU gettextのmoフォーマットである事を意味するため、".gmo"としている。

プレーンテキスト形式でダウンロード
現在のバージョン : 1
更新者: msakamoto-sf
更新日: 2009-11-18 20:22:15
md5:74ea506a746e9d32c6008bc4f52940f4
sha1:851a459dc995c628642d6b4c2b1673acd9043ac6
コメント
コメントを投稿するにはログインして下さい。