C++ 国際化

C, C++ で使われている国際化(initialization, i18n)について書いてみたいと思います。
細かいところは結構はしょって書いたのですが、いろいろ書いてしまって結構長いです。

国際化というのは主に言語(地域)ごとにメッセージを切り替えることを言います。 他にも通貨記号($)や時間の表し方も言語ごとに違いますし、少数点などはヨーロッパでは, . の使い方が逆だったりしてそれにあわせることなども含みますが、 今回はメッセージの切り替え中心です。

Un*x


C ではこの国際化は標準ライブラリにローケルという名前で古くからあります。
これはメッセージファイルに番号ごとのメッセージを書いておいて、 コード中では最初に言語を設定し、この番号を指定すると設定した言語のメッセージが取得できる という仕組みです。 普通はコード中に番号を直接書くことはしないでしょうから、 番号に対して define や列挙型をヘッダに記述しておいてそれを使うことになります。

この標準ライブラリはそのまま使おうとするとすごくめんどくさいです。 そのため、せっかくあるのになかなか使われず、国際化が進みませんでした。 そこでで出てきたのが GetText です。
これはベースは標準ライブラリを使用しますが、使いやすくなります。
プログラムを作る側は今まで英語メッセージを文字リテラルとして書いていたところを次のように _で囲むだけで済みます。
printf(_("Helow World.!!\n"));
このように書いたソースに対してツールを通すとメッセージファイルを作ってくれます。
訳す人はツールでメッセージが抜き出されたものができるので、それに対応する訳を書いて、 また、ツールを通すとその言語用のメッセージファイルが出来ます。

これで国際化が進んで、普通に日本語でアプリケーションが使えるようにとても良かったのですが、 個人的にはこの GetText はあまり好きではありません。
というのも、メッセージやアイコンなどはリソースというのですが、 このリソースはソースと分けて一括で管理したいためです。
翻訳用のメッセージはまとまっているのですが、デフォルトの言語(大抵は英語) はソースコードのあちこちに散らばることになりますし、修正する度にビルドのしなおしが必要となります。

今は国際化するのが当たり前になってきたので、もう GetText はいいんじゃないかと思うのですが、 Linux ではこれが主流です。
統合デスクトップ環境である gnome で使われえている GTK+ は GetText を使っていますし、 KDE の Qt は GetText ではなく独自の国際化を持っているのですが、これも GetText 方式です。

Linux ではない商用 Un*x の世界はというと、こちらの GUI は Motif というライブラリが古くから使われていて、これで CDE (gnome や KDE のもとになっているもの) も作られています。 Motif はさらに古い GUI ツールキットである Xt ライブラリの上に出来ているのですが、 こちらはきちんとリソースが分けられています。
Xt ではテキストファイルであるリソースファイルに以下のような行を書きます。
FooApp.FooDialog.OkButton.label : 了解 
キーになる方は親子関係で書くことができ、これを Widget の部品の名前とあわせておきます。 そうすると、アプリケーションは起動時にリソースファイルをロードして言語にあわせたラベルになります。
ただ、これは GUI 専用のところがあって、 エラーメッセージのようなコード中で決める文字列は C の国際化を使用する必要があります。

他に有名な国際化ライブラリで ICU というものがあります。
これはリソースの国際化だけではなく、文字コード変換などの機能も含んだ大掛かりなもので、 クロスプラットホームで動作します。 しかし、文字コード変換などは Boost からも使われるようになって、広く使われているのですが、 リソース管理という面ではそれほど使われていない気がします。
これもリソースを分離するタイプでよく考えられていると思うのですが、 やはり、 GUI ツールキットとセットではないと広まりにくいのかも知れません。

Windows


Windows の場合、 .NET 以前は基本的に C の標準ライブラリのようなもので、 番号(ID)に対して文字列を指定し、 ID を指定して文字列を取得します。
ただ、 Visual Studio のような IDE で作るのが普通なので、 メッセージファイルの作成と番号の define 定義が同時にできるような感じになっていて、 そんなに使いづらくもないかなと思います。
.NET では番号を define したものではなく、 'Foo.Bar.Ok' のような文字列で取得できるようになっています。

Windows ではリソースは exe や dll などのバイナリファイルに埋め込んでしまいます。 なので、翻訳がない場合に問題が出てきます。
Un*x 系では言語の指定は環境変数 LANG で行いますが、 Windows では WIN32API などの OS が提供するライブラリでユーザーが設定している言語を取得してきます。
商用アプリなど売るところが決まっている場合は問題ないのですが、 フリーウェアなどの場合はリソース用の dll などで追加して、 言語の設定を自動で行うのではなく、アプリの設定でユーザーがどの言語を選択するかを指定するといった工夫が必要になります。

Un*x 系はというと翻訳ファイルやリソースは分かれているのが普通なので、 翻訳ファイルの置き場所に翻訳ファイルおいてなければデフォルト言語(英語)で表示して、 翻訳を追加したければ、そこにファイルを置くという方式が多いと思います。

ちなみに Qt はクロスプラットホームなので、アイコンなどのリソースを Un*x でも、がんばって取り込む仕組みを持っています。 ただ、翻訳ファイルは別ファイルになっていて、 Un*x のような追加方式にすることもできなくはありません。
できなくはないというのは、見つからなかったら英語にする仕組みはないので、 その辺はアプリ側で実装する必要があったりします。


といった感じが C++ での基本的な国際化の方法じゃないかと思います。
どれも一長一短といった感じで、これだっと言うものはないです。 強いて言えば、 .NET が良いと思うのですが、クロスプラットホームではないです。
(Linux でも動くんじゃないかって話はありますが)

それでクロスプラットホームの大規模のアプリの中には自前で国際化の仕組みをもったものもあります。
例えば、 firefox, thunderbird の Mozilla では テキストファイルである プロパティファイルや DTD を使った Java っぽい国際化ができるようになっています。

スポンサーサイト



 

国際化 - Unicode と文字列クラス

国際化の話、第 2 弾ということで Unicode とその限界、それと C++, Ruby での文字列の持ち方について書いてみたいと思います。

Unicode


国際化といえば Unicode なのですが、 登場までの流れを少し説明したいと思います。
C では文字列は基本は 1 バイトの char 型の配列です。
ただ、 1 バイトだと最大で 255 (0 は文字の末端として使います) 種類しか区別ができません。 英語だとそれで十分なのですが、日本語だとアルファベットもあわせるとひらがなぐらいしか入りません。 昔は文字の大きさもアルファベットに合わせる必要があったため、 ひらがなは表示が難しいので、カタカナにして、日本語はカタカナだけ使えるという時代がありました。 この名残が半角カタカナです。
英語などの文字の少ない言語しか対応できないようではだめだってことで、C では日本語などを入れるように 2 バイトの wchar_t 型を作って 日本語などは wchar_t 型の配列(ワイド文字列)に入れたらということになりました。 しかし、英語用に char 型できていたプログラムを wchar_t に変えるのは変更が大きすぎます。

そこで char 型の配列にいれるために 1 文字を複数のバイトで表すマルチバイト文字列が使われることになります。 これが Windows(MS-DOS)ではシフト JIS で、 Un*x では EUC です。あと JIS(ISO-2022-JP)もあります。
シフト JIS 、 EUC はそれぞれの種類の OS で使われているものですが、なぜ JIS があるかというと通信用で 7 bit の文字コードが必要だったためです。 英語は 255 種類も必要なく、 1 bit 減らした 125 種類で十分なので、 ASCII コードは 7 bit しか使いません。 昔は 1 ビットでも通信量を減らしたいということがあり、 8 ビット目を切り落として、詰めてデータを送る通信方式がありました。 このため 7 bit の文字コードが必要となっていました。 この JIS コードは 7 bit に切り詰めている分、かなり使いづらいコードなので、プログラム中では、シフト JIS や EUC を使って、通信する前に JIS コードに変えるというように使われていました。しかし、最近ではそんな方式は使わないようになっているので、 JIS コードはほとんど見かけなくなりました。

修正を減らすためのマルチバイト文字列だったのですが、これはこれで扱いが難しいです。
よくある問題が漢字を含む Windows のパスでパス区切りの \ を探そうとすると 2 バイト構成されている漢字の中の 1 バイトに \ がひっかかって ちゃんと検索できないといったものです。 EUC はシフト JIS より後発のため、多少改善されてますが、 同じような問題はあります。

シェアを広げるためには、国際化は必須です。 しかし、やっぱり 1 文字は 1 つのデータとして扱わないとプログラミングのコストがかかります。
そこで Microsoft をはじめ、大手企業が乗り出してきて、登場したのが Unicode です。 これで 1 文字を 2 バイトに格納する仕様が決まり、最初の wchar_t に脚光がもどってきました。

UTF-8


文字を 2 バイトで扱うようにしたことによって、新たな問題も生じます。 2 バイトだとファイルや通信などで OS をまたぐ際にはエンディアンを考慮する必要が出てきます。
Unicode をそのままファイル落とすとビックエンディアンかリトルエンディアンか分からないので、 先頭にエンディアンを区別のための BOM を付けたものが UTF-16 で UTF-16BE と UTF-16LE があります。
ただ、これをテキストファイルの標準にしようとすると今までのプログラムをほとんど変える必要があります。 英語だけの文字の場合には ASCII コードと同じものとして扱えるようにしたものが UTF-8 です。英語を優遇した結果どこかにしわ寄せがくるもので、日本語などは今まで 2 バイトだったものが文字によって 3, 4 バイト使うことになります。

いまだに 7 bit 通信を考慮した UTF-7 や日本語のデータ量をそんなに増やさずにすむ UTF-9 といった規格もあるようですが、使われていないようです。

日本語を保存したときのデータ量が増えるとはいえ、 Unicode を使うと英語などの言語は 文字のメモリ使用量が 2 倍になるんだし、それに比べれば UTF-8 が使われていくのは、仕方ないかなと思います。
しかし、本来 1 バイトのため、エンディアンを考慮する必要のない UTF-8 ですが、 UTF-8 を使っているという識別のために BOM 付きの UTF-8 というものがあります。
しかも今は BOM 付きだと動かないものと BOM なしだとちゃんと認識してくれないものが混在していて、なんとかしてよという気がします。


Un*x


Unicode と UTF-8 の説明が終わったところで、実際にプログラムではどういった感じで文字列データを持っているのか という話に移りたいと思います。

QtQString という文字列クラスを使っていて、これには 2 バイトの Unicode が格納されています。 ファイルなどの外部から文字を取り込むときに Unicode にして、プログラム中では Unicode で処理するという一番、一般的なタイプだと思います。
Java や Perl などもこのタイプです。

GLib と glib を使っている GTK+ は、プログラム中は Unicode ではなく、 UTF-8 を使います。
文字列を取り込むときに UTF-8 に変換して、各関数は UTF-8 のマルチバイト処理だけ対応している感じです。データの型は一応 GString という文字列を格納する型はあるのですが、 全部これに入れてから使うという感じでもなく、 C の char 型配列が主に使われています。

Motif はというと、私が知らないだけかも知れませんが、 Unicode で統一しようという動きはなく、 いまだに EUC が使われている気がします。

Windows


Windows は .NET 以前は複雑です。
コンパイル時に _UNICODE の定義があるかないかで char と wchar_t が切り替わる TCHAR 型というものを使っています。
文字と文字列リテラルはワイド文字列の場合、前に L を付けるのですが、これも _UNICODE で切り替わるようにリテラルは _T のマクロで囲む必要があります。
char *str = "Hello";
wchar_t *wstr = L"Hello";
TCHAR *tstr = _T("Hello");
また、 MFC には CString という文字列クラスがあるのですが、 これも中に格納するデータは定義で char と wchar_t が切り替わっています。
さらに COM を使うということになると BSTR というデータ構造を使う必要があります。

.NET では文字列は System::String クラスで、これは 2 バイトの Unicode が格納されます。
ただ、これも値を変更できない変わったクラスではあります。

ちなみに Qt でも文字列リテラルを囲む _T は使用します。
MFC からの移植を簡単にするため、同じ名前にしたのでしょうが、 MFC の _T とは別ものです。 これは GetText の N_ や _ の役割と文字列リテラル(char *)を QString に変える役割をもっています。

C++ 標準ライブラリ


C++ の標準ライブラリでは std::string とワイド文字列用の std::wstring があります。 これはテンプレートでクラスで定義されたものに char と wchar_t をテンプレートの引数に渡した型を typedef したものです。 string と iostream はテンプレートを使って実装されていますが、 テンプレートとして使っているわけでは無いので、標準ライブラリではありますが、 STL には含まれないそうです。

string は C++ での char 配列の代わりとしてよく使われますが、 wstring は使われる場面は限られます。
GLib, Qt, MFC, .NET が使えない環境で日本語の文字列処理が必要な場合です。
それってどこだよという感じですが、私はクロスプラットホームで動作する コマンドラインプログラムを作ったときに使ったことがあります。 (Qt はラインセンスの関係で使いませんでした)

Unicode の限界


広く使われるようになった Unicode ですが、実は問題を含んでいます。
それは世界で使われてる文字を入れるのに 2 バイトでは足りないということです。
32 bit の大部分を使っているのは、中国、台湾、日本の漢字圏です。 そこで、 3 つの国で似ている漢字を一緒にしちゃえってことで、 無理やり 2 バイトに押し込めました。
(アルファベットに関しては、フォントで分けるべきものじゃないかというのまで、分けられているのですが...)
それでも、日本語だけ使うのであれば、特に問題はありません。
問題は日本語と中国語などを同時に使う場合です。 Unicode を使っていると 一部の文字がどちらの言語か判別できず、正確に表示されないということになってしまいます。 つまり、 Unicode では国際化はできるけど、多言語化はできません

4 バイトの Unicode を使えばいいのですが、さすがに文字データが 4 倍のメモリを使用するのは、なかなか採用されないでしょう。
多言語化はされないのか、多言語化したとき漢字が少し変になるぐらいかまわないと思われているのか 分かりませんが、世の中はどんどん 2 バイトの Unicode で統一されていっています。

Ruby の多言語化


そんな中、日本発のプログラミング言語はやっぱり違います。
Ruby はちゃんと多言語化に対応してしまいました。
標準に採用されている XML パーサーや Rails が UTF-8 で統一されているので、 GLib のような UTF-8 で統一する方針なのかなと思いきや、 Version 1.9 で多言語化(Multilingualization, M17N)が導入されました。

どうやっているかというと、 文字列クラス String のインスタンス 1 つ 1 つで自分が今どの文字コードなのか という文字コード情報を持っています。検索などの文字列処理関数は文字列のその情報をみて、 それにあった方式で処理することになります。
この方式では 1 文字で 1 単位というわけではないので、マルチバイト文字と同じ問題が発生するため C のようにポインタでまわして自分で中を見ていくということはできず、文字列には必ず文字列処理用の関数を通して扱う必要があります。
ただ、 Ruby ではもともと EUC やシフト JIS に対応していて、 C と違ってすでにずっとそういう方針だったので、特に違和感はありません。

また、文字コード情報は 8 バイト程度でしょうから、それが付いているだけで、 ASCII 文字では 1 バイト、日本語も 2 バイトと文字データのメモリ使用量が減ります。

Ruby の多言語化は素晴らしいのは素晴らしいのですが、 スクリプトの結果を表示するターミナルや GUI が Unicode で統一されていっているので、 結局、ちゃんと表示できるようにはならないでしょう。
Ruby の GUI ライブラリはいろいろあるのですが、基本的に C++ のライブラリを Ruby で使えるようにしたものなので、 XLib や WIN32API からごりごり作った Ruby 用のクロスプラットホームで使える GUI ツールキットができたらいいなと思います。
これは Ruby アソシエーションが開発プロジェクトを公募しているとき、やろうかとちょっと頭をよぎったのですが、 個人で 1 年でできるようなレベルのものじゃないなということでやめました。

 

文字コードの変換ライブラリ

日本語ではシフト JIS, EUC, UTF-8, JIS といったいろんな文字コードが使用されています。 前に、こういった文字コードの読み取って、 プログラム中では Unicode として統一して扱うことが多いと書きました。
今度はこの文字コード変換にどういったものが使われているのかについて記述したいと思います。

Windows


.NET では System.Text.Encoding というクラスがあります。これで、各コードの文字データを Unicode に変換して、 System.String 型に格納します。
ただ、これは文字コードの判別までは行ってくれません。

.NET を使用しない場合は IE が使用しているライブラリ mlang.dll がよく使われていると思います。
また、これは文字コードの判別もできるので、 .NET を使う場合も自動判別が必要な場合はこちらを使用する必要があります。

また、 Windows の日本語環境でよく使われる Shift-JIS(OEM), UTF-8 であれば WIN32API に Unicode との変換関数が用意されています。


Un*x


Un*x 系では GNU の iconv が標準いってもいいかも知れません。
これはライブラリとコマンドラインのツールがあります。
ただ、 iconv は文字コードの判別は行ってくれません。

文字コード判定のライブラリとしては iconv みたいに浸透はしてませんが、libguess というライブラリがあります。


クロスプラットホーム


Qt の場合は 自前で文字コード変換機能を持っています。
これを使って、各文字コードから QString に変換します。
文字コード判別機能は持っていないのですが、 HTML のヘッダや BOM から文字コードを判定するメソッドならあります。

Unicode 用の国際化ライブラリである ICU も各文字コードと Unicode 間の変換が出来ます。
こちらは文字コードの判別機能もあります。

Boostでは正規表現の拡張などで ICU が取り入れられています。
また、 iostream が簡単に作れる boost::iostreams というモジュールには入出力するとき文字コードを変換してくれる code_converter というクラスもあります。

その他には日本語文字コードだけであれば、次のような文字コードの判定と変換ができるライブラリ(ツール)が有名だと思います。

Ruby


Ruby の場合は iconv, nkf のラッパーライブラリと Kconv という判定変換してくれるライブラリが 標準で添付されています
Ruby 1.9 からは独自のエンコーディング変換エンジン(transcode)を搭載して、 言語レベルでエンコーディングの変換ができるようになっています。

 
このページをシェア
アクセスカウンター
アクセスランキング
[ジャンルランキング]
コンピュータ
23位
アクセスランキングを見る>>

[サブジャンルランキング]
プログラミング
7位
アクセスランキングを見る>>
カレンダー(アーカイブ)
プルダウン 降順 昇順 年別

05月 | 2023年06月 | 07月
- - - - 1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 -


はてな新着記事
はてな人気記事
ブロとも申請フォーム
プロフィール

yohshiy

Author:yohshiy
職業プログラマー。
仕事は主に C++ ですが、軽い言語マニアなので、色々使っています。

はてブ:yohshiy のブックマーク
Twitter:@yohshiy

サイト紹介
プログラミング好きのブログです。プログラミング関連の話題や公開ソフトの開発記などを雑多に書いてます。ただ、たまに英語やネット系の話になることも。