Gnu gettextをつかって時々うまく翻訳文字列が得られないことがありました。まず、gettextをinfoで読みました。php でgettextを使っていたので、phpのドキュメントにも目を通しました。余談ですが、php上のgettextは、c言語のapiのgettext libraryのwrapperになっています。なぜgettextが動作期待したとおりに処理されないのか、理解できていません。
gettextを調べるためのプログラム
gettext libraryを調べるためのプログラムのディレクトリ構成です。
bin
|-- i18n
| |-- en
| | `-- LC_MESSAGES
| | `-- messages.mo
| |-- en_US
| | `-- LC_MESSAGES
| | `-- messages.mo
| |-- ja
| | `-- LC_MESSAGES
| | `-- messages.mo
| `-- ja_JP
| `-- LC_MESSAGES
| `-- messages.mo
`-- i18n-msg
このソフトウェアはここから入手できます。
このプログラムはコンソールにメッセージを出すだけのプログラムです。 "-l ja_JP -c UTF-8
“というコマンド引数(オプション)を指定して実行すると、日本語変換されたメッセージがコンソールに出力されます。次のようなオプションでは、翻訳されたメッセージを得ることができません。
-l ja
-l ja -c UTF-8
-l ja_JP
以下がgettextに関する呼び出しシーケンスです。
/* locale はコマンド引数 */
setloale(LC_MESSAGES, locale);
/* domain_dir は "i18n ディレクトリまでの絶対パス" */
bindtextdomain("messages", domain_dir);
/* codesetはコマンド引数 */
bind_textdomain_codeset("messages", codeset);
setlocaleが成功しない
上に書いたコマンド引数のいくつかでは、setlocaleが失敗します。
$> bin/i18n-msg -l ja
failed to bind text domain with ja
上にあるとおり、ロケールjaは存在しますが、setlocaleは失敗します。
文字化けする
以下のようなオプションでは、文字化けします。
$> bin/i18n-msg -l ja_JP
gettext????????????
用意したmessage.moのファイルはUTF-8です。そして、環境変数のLC_CTYPEは、en.UTF-8になっています。man 7 によると、LC_CTYPEが表示する文字列のために使用されると記載があります。
setlocaleが失敗する理由
ロケールに関するmanページを読みました。setlocaleを呼ぶ前にlocaleを用意しておく必要があることを知りました。私のlinux環境では、以下のlocaleが用意されていました。
$> locale -a
aa_DJ
aa_DJ.iso88591
aa_DJ.utf8en_US
...
en_US.iso88591
en_US.iso885915
en_US.utf8
en_ZA
...
ja_JP
ja_JP.eucjp
ja_JP.ujis
ja_JP.utf8
japanese
japanese.euc
...
zu_ZA
zu_ZA.iso88591
zu_ZA.utf8
‘ja’が上のリストにありません。これが、setlocaleが成功しない理由でした。
setlocale(“ja”)を成功させるために
setlocale(“ja”)を成功させるには、”ja” のロケールを用意しなければなりません。これは、”localedef”を実行することで、達成できます。ロケールはディレクトリ構造で、その配下のファイルは、ロケール関連の関数のバイナリデータになってます。デフォルトでは、ロケールデータはsystemディレクトリにあります。新しいカスタムロケールを作る場合などには、簡単な方法とはなっていないと感じました。そこで、新しい”ja”をログインユーザのディレクトリに用意して、環境変数’LOCPATH’を設定して、’ja’ロケールが得られるようにしました。
“ja”を私のlinux環境で用意するために、以下のコマンドを使いました。
$> localedef -i ja_JP -f UTF-8 locales/ja
以下はsetlocaleを成功させるための処理の一例です。
char* locale_res;
int result;
locale_res = setlocale(LC_MESSAGES, locale);
if (!locale_res) {
char* exec_dir;
exec_dir = get_executable_dir();
if (exec_dir) {
size_t exec_dir_len;
size_t locales_dir_len;
size_t loc_path_buffer_size;
const char* locales_dir = "locales";
char* loc_path_buffer;
char* saved_loc_path;
locales_dir_len = strlen(locales_dir);
exec_dir_len = strlen(exec_dir);
loc_path_buffer_size = exec_dir_len;
loc_path_buffer_size += locales_dir_len;
loc_path_buffer_size += 1;
loc_path_buffer = (char*)malloc(loc_path_buffer_size);
/* 設定済のlocpath環境変数 */
saved_loc_path = getenv("LOCPATH");
if (loc_path_buffer) {
snprintf(loc_path_buffer, loc_path_buffer_size,
"%s%s", exec_dir, locales_dir);
/* loc_path_buffer は"locales" ディレクトリ */
/* "locales"ディレクトリは、実行ファイルがあるディレクトリ直下にある。*/
setenv("LOCPATH", loc_path_buffer, 1);
}
/* setlocaleは成功するでしょう */
locale_res = setlocale(LC_MESSAGES, locale);
result = locale_res ? 0 : -1;
if (saved_loc_path) {
/* LOCPATH 元に戻す */
setenv("LOCPATH", saved_loc_path, 1);
}
if (saved_loc_path) {
free_str(saved_loc_path);
}
if (loc_path_buffer) {
free_str(loc_path_buffer);
}
}
if (exec_dir) {
free_str(exec_dir);
}
} else {
result = 0;
}
return result;
gettextから文字化けしない文字列を取得する
gettextから文字化けしない文字列を取得するためには、setlocale(LC_CTYPE, <locale.codeset>)または、 bind_textdomain_codeset(domain, <codeset>)を呼ぶ必要があります。
setlocale(LC_CTYPE, <locale.codeset>) を呼ぶ
gettextのソースコードを読みました。戻りの文字列は、iconvによって変換されることが判りました。iconvは変換先のcodesetにメッセージdomainにと紐ついたものを使用します。もし紐ついたものがない場合は、nl_langinfo(CODESET)の戻り値を使用します。nl_langinfo(CODESET)は、setlocale(LC_TYPE, <locale.codeset>)によって設定されます。man 3 setlocaleによると、linux programの初期のlocaleは”C”です。環境変数は、異なる設定になっているかもしれません。locale関連の環境変数は、プログラムの初期の状態では影響しないのです。
こちらで確認した、locale関連の環境変数です。
LANG=en_US.UTF-8
LC_CTYPE=en_US.UTF-8
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=
locale “C”では、 nl_langinfo(CODESET) は”ANSI_X3.4-1968″を返しました。これが、文字化けする理由でした。
bind_textdomain_codesetを呼ぶこと
bind_textdomain_codesetを呼ぶことは、直線的で判りやすい方法です。man bind_textdomain_codesetを読めば、理解できると思います。少し上で述べたとおり、この関数はあるドメインに、codesetを紐つけます。そして、iconvの出力パラメータとして用いられます。
最後に
gettextを使うにあたって、次のことを考慮に入れる必要あるでしょう。
- 使いたいlocaleが見つからない場合、localedefを使用してLOCPATH 環境変数を設定する。
- gettextを使用して文字化けしない文字列得るには、setlocale(LC_CTYPE, <locale.codeset>)または、 bind_textdomain_codeset(<domain>, <codeset>) を呼び出す。