#navi_header|C言語系| GNU gettext の自分用メモ - GNU gettext 本家 -- http://www.gnu.org/software/gettext/ - GNU gettext HTMLドキュメント(1ページ版) -- http://www.gnu.org/software/gettext/manual/gettext.html #more|| #outline|| ---- * 基本的な流れ ・最初のソースコード準備からpoファイル作成、moファイルへのコンパイルまで + ソースコードを準備する。 + "xgettext"で検出する為のマクロ定義を埋め込む。 + 翻訳したい文字列に対して、マクロを適用する。 + setlocale()など必要な前処理やヘッダファイルインクルードを組み込む。 + コンパイルして一旦Cロカールで動作確認 + xgettextでメッセージを抽出し、"*.pot"(poのテンプレートファイル)生成 + "*.pot"ファイルを"*.po"にリネームし、翻訳。 + msgfmtで"*.po"ファイルを"*.mo"ファイルにコンパイル。 + "*.mo"ファイルをbindtextdomain()やtextdomain()で指定したパッケージ名+".mo"にリネームし、ロカール用ディレクトリに置く。 ・ソースコードが変更された場合のメンテナンス作業 + xgettextでメッセージを抽出し、"*.pot"(poのテンプレートファイル)生成 + msgmergeで"*.po"ファイルにマージ、翻訳。 + msgfmtでコンパイルし、パッケージ名+".mo"にリネーム、ロカール用ディレクトリに配置。 GNU gettext HTMLドキュメント, "1.5 Overview of GNU gettext" よりファイル関連図を抜粋する(元の図から、"PO Compendium"という要素が分かりづらかったので取り除いてあります)(("*.gmo"ファイルというのは、"GNU gettextのサポートするmoファイルフォーマット"という意味。moファイルのバイナリフォーマットはシステムによって差異があり、GNU gettextがサポートしないフォーマットの場合もある。この図では特にGNU gettextのmoフォーマットである事を意味するため、".gmo"としている。))。 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: #code|C|> #include 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は次のようになる。 #code|C|> #include #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", メッセージカタログディレクトリは"."起点とした。 #code|C|> #include /* setlocale() */ #include /* bindtextdomain(), textdomain() */ #include #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を修正してみる。 #code|C|> #include #include #include #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: #pre||> ... #: 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" に詳細が載っている為、ここではポイントとなる手順だけを示す。 + m4ディレクトリを用意し、トップのMakefile.amで "ACLOCAL_AMFLAGS = -I ./m4" を指定しておく。 + Autoconfによるconfigureスクリプトまで生成させたら、"gettextize -c --intl" を実行する。poディレクトリ+ファイル群の作成、m4ディレクトリへのマクロファイルコピー、configure.ac,Makefile.amなどが自動調整される。 + po/POTFILES.in へメッセージ抽出対象のソースファイルを列挙する。 + po/Makevars.template を po/Makevars にリネーム or コピーする。 + configure.acに以下のマクロを追加 ++ AC_CANONICAL_HOST : AM_GNU_GETTEXTがconfigu.guess, config.subを必要としているため。 ++ AM_GNU_SOURCE, AM_GNU_GETTEXT ++ 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 を #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" 以下参照 #navi_footer|C言語系|