Not only is the Internet dead, it's starting to smell really bad.:2010年06月01日分

2010/06/01(Tue)

[C][i18n] ワイド文字(列)リテラル

ゆにこーどがあらわれた、ソースコードをUTF-8で保存して、実行時はUTF-8 localeのみで動作させるなら
gcc(1) がアレなせいで動きますが、実際には移植性のないコードなんですよね。

詳しいことは以前書いた記事を参照してくだしあ。

ポイントとしては

あたりですかね。

清く正しく i18n 化するにはリテラルなんぞ使わずにすべてメッセージカタログ化します。
今時は libc の catgets(3) よりも gettext(3) を使う事の方が多いので例題はそちらを使ってみませう。
まー gettext(3) はコロコロ互換性の無い変更をツッコみやがるのであまり係わり合いになりたくないのですが
デファクトだから仕方が無い。

以下のソースを -lintl つきでコンパイル。

$ cat >foo.c
/* このファイルは US-ASCII で保存 */
#include <locale.h>
#include <libintl.h>
#include <stdlib.h>
#include <wchar.h>

#define DOMAINNAME      "foo"
#define LOCALEDIR       "/usr/local/share/locale"

int
main(void)
{
	char *s;
	wchar_t *ws;
	size_t len;

	setlocale(LC_CTYPE, "");
	setlocale(LC_MESSAGES, "");

	bindtextdomain(DOMAINNAME, LOCALEDIR);

	s = dgettext(DOMAINNAME, "AIU\n");
	len = mbstowcs(NULL, s, 0);
	if (len == (size_t)-1)
		abort();
	++len;
	ws = malloc(sizeof(*ws) * len);
	if (ws == NULL)
		abort();
	mbstowcs(ws, s, len);
	wprintf(ws);

	return 0;
}
^D
$ gcc -lintl -o foo foo.c

xgettext(1) でソースから文字列をぶっこ抜いてメッセージファイル(.po)の雛形を作成します。

$ xgettext -d foo foo.c

xgettext(1) はデフォルトでは {d, n, dn, dc, dcn}?gettext(3) の引数のみ探すので、もし

#define _(msg)	dgettext(DOMAINNAME, msg)

のようにマクロ組んだりした場合はメッセージファイルには出力されません。
そんな場合は --extract-all を指定しましょう。

メッセージファイルを編集します。

# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2010-05-31 17:00+0900\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=EUC-JP\n"
"Content-Transfer-Encoding: 8bit\n"

#: foo.c:20
msgid "AIU\n"
msgstr "あいう\n"

charset を変更するのと、msgid に対応する訳文を入力すればおk。

メッセージカタログ(.mo)を作成します。

$ msgfmt -o foo.mo foo.po

メッセージカタログを bindtextdomain(3) の引数で指定したディレクトリに配備します。

# install -d /usr/local/share/locale/ja_JP.eucJP/LC_MESSAGES
# install -m 444 -o root -g wheel foo.mo /usr/local/share/locale/ja_JP.eucJP/LC_MESSAGES

指定ディレクトリの下に ${locale 名}/LC_MESSAGES/ というサブディレクトリが必要です。

んでこのバイナリを実行すると

が表示されます。

$ LANG=ja_JP.eucJP ./test
あいう
$ ./test
AIU

まぁワイド文字に変換する必要もあんまり無いので

#include <locale.h>
#include <libintl.h>
#include <stdlib.h>

#define DOMAINNAME      "foo"
#define LOCALEDIR       "/usr/local/share/locale"
#define _N(msg)          dgettext(DOMAINNAME,msg)
int
main(void)
{
	setlocale(LC_CTYPE, "");
	setlocale(LC_MESSAGES, "");

	bindtextdomain(DOMAINNAME, LOCALEDIR);

	printf(_N("AIU\n"));

	return 0;
}

とする方が普通ですが。
文字列を全部 _T() に変えろ地獄 → _N() に変えろ地獄ですな:D

あ、元コードですが _T() を使うなら wprintf ではなく _tprintf ですね。