○[i18n] setlocale(3) で指定するロケール名の移植性
kbkさんとこ。
setlocale(3)でどうやって
- 言語(language)
- 地域(territory)
- 文字符号化手法(encoding)
- おまけ(modifiers)
を文字列にして指定するかは、
仕様では決まってないのですよ。
The locale argument is a pointer to a character string containing the required
setting of category. The contents of this string are implementation-defined.
とある通り、"C", "POSIX", "", NULL以外は実装依存です。
まあUnix方面だと
言語 + "_" + 地域 + "." 文字符号化手法 ( + "@" おまけ)
というフォーマットで
となってる実装が多いのですが
これHTTPのAccept-Languageのように、
RFC4646 Tags for Identifying Languages
の仕様で縛られてる訳ではないです(そもそも言語タグだと"_"じゃなくて"-"ですな)。
混同されてる方は要注意。
さらに文字符号化手法については
- eucJP
- EUC-JP
- ujis(テラナツカシス)
- Extended_UNIX_Code_Packed_Format_for_Japanese (IANA charset registry儲向け)
- Japanese-EUC (XFree86/xorgのlocale.alias参照)
と、まあ表記の揺れが激しいわけです、glibc2ではloose matchingとか導入したくらいで。
HTTPのAccept-CharsetはいちおIANA charset registry縛りがあるのだったっけ?
あれはPOSIX localeには何の強制もないですし、そもそも
setlocale(LC_ALL, "ja_JP.Extended_UNIX_Code_Packed_Format_for_Japanese");
なんて嫌すぐる
*1。
それと、複数のカテゴリをいっぺんにsetlocale(3)に指定する場合も
やりかたは実装によってバラバラです、いくつか確認してみましょ。
Solarisの場合
$ cat >hoge.c
#include <locale.h>
#include <stdio.h>
#if defined(_WIN32)
#define JPLOCALE "Japanese_Japan.932"
#elif defined(__linux__)
#define JPLOCALE "ja_JP.EUC-JP"
#else
#define JPLOCALE "ja_JP.eucJP"
#endif
main(void)
{
setlocale(LC_ALL, "C");
setlocale(LC_CTYPE, JPLOCALE);
printf("%s\n", setlocale(LC_ALL, NULL));
}
^D
$ make hoge
cc -o hoge hoge.c
$ ./hoge
/ja_JP.eucJP/C/C/C/C/C
各カテゴリをスラッシュ区切りで表示します(順序はLC_*の定数値の小さい方から)。
なのですが、"/"ではじまる場合はpathnameと解釈するちゅう仕様に反してる気が。
つかそもそもスラッシュ区切りだと、LC_ALLの場合pathnameが使えなくなりますな。
/usr/local/share/locale/ww_WW.wwwWW/LC_COLLATE;/usr/local/share/locale/ww_WW.wwwWW/LC_CTYPE;...
とか、せめて;とか:なんかにしとけばよかったのに。
glibc2の場合は
$ ./hoge
LC_CTYPE=ja_JP.EUC-JP;LC_NUMERIC=C;...
となります、これはこれでウザい(注:単に個人差です)。
われらがNetBSDは↓になってます、FreeBSDが4.4BSD runeを拡張したコードが元ネタ。
$ ./hoge
C/ja_JP.eucJP/C/C/C/C
Solarisと似てますが、先頭が"/"ではじまる場合はpathnameと解釈するPOSIX仕様とはいちお整合性あります。
まあLC_ALLの場合pathname使えないのはSolarisと一緒です(汗)。
んで最後、MSVCはglibc2と一緒っすね。
C:\> hoge.exe
LC_COLLATE=C;LC_CTYPE=Japanese_Japan.932;...
んで気づいてしまったのだけど
これ、将来的にカテゴリを追加する(
ISO/IEC TR 14652にはある)
場合を考えると、カテゴリが足りない場合エラーになる今のNetBSD setlocale(3)の
実装では
glibc2のLC_*_MASK問題と同様に、やっぱりバイナリの後方互換性失われるのよなぁ。
仕様にopaqueとは書かれてないので、移植性を諦めさえすれば
ハードコードすることは別に禁じられてないし…うーむ頭痛い。
どーせ互換性無くなるならglibc2/MSVC風の方がキモイけどリーズナブル。
この問題の別解として
setlocale(LC_ALL, "ja_JP.eucJP");
として、LC_ALLを指定した場合は全部同じロケールをセットすること以外は
禁止してしまえばいいようにも思えるのだけど
char *tmp, *s;
setlocale(LC_ALL, "C");
setlocale(LC_CTYPE, "ja_JP.eucJP");
...
tmp = setlocale(LC_ALL, NULL);
s = strdup(tmp); /* save current locale */
...
setlocale(LC_ALL, s); /* restore previous locale */
free(s);
というcurrent localeのsave/restoreを行うコードが動作しなくなっちまうよな。
まあアプリ側はそもそも
この通り
そもそも、/ で区切る指定ができるということを POSIX その他は
全く規定していないですし、そういうことをやりたい人は LC_xxx で
明示的に指定するべきでしょう。
なのよな。
つーわけでパラノイアなアプリ実装者は、とことん安全側に倒して
char *tmp, *collate, *ctype, ...;
setlocale(LC_ALL, "C");
setlocale(LC_CTYPE, "ja_JP.eucJP");
...
tmp = setlocale(LC_COLLATE, NULL);
collate = strdup(tmp); /* save current LC_COLLATE */
tmp = setlocale(LC_CTYPE, NULL);
ctype = strdup(tmp); /* save current LC_CTYPE */
...
setlocale(LC_COLLATE, collate); /* restore previous LC_COLLATE */
setlocale(LC_CTYPE, ctype); /* restore previous LC_CTYPE */
free(collate);
free(ctype);
...
とした方がいいかもしれない、save/restore目的にはLC_ALLは使うなと。
ああ、やっぱりPOSIX localeは設計が腐(以下略
あと XFree86/xorg のlocale.aliasにある
POSIX-UTF2 C
なんじゃこれ(UTF2ってのはUTF-8の古い名前ね)。
いちお
仕 様 ではC/POSIX localeのnl_langinfo(CODESET)がUTF-8でも構わない
*2のだけど
XFree86/xorgがこのaliasをつけちゃうのは、あんまりよろしくない。
*1:NetBSDの場合、実装の都合上ロケール名は32byteまでだったり。
*2:これ、えんがちょの理由はUTF-8だからではなく、わざわざUCS2までの限定サポートにしてること。