プログラムの出力メッセージの国際化(多言語対応)の手法について解説します。プラットフォームはUNIX(CUI)とMS-DOSです。ハードコーディングによる場合と、国際化による場合がある。複数の環境に対応する場合は多地域化(multilocalization)という。システムから分離・排除すること。(多)地域化の手法の一つでもある。その機能はおおむね以下のように分類されます。strcoll(3)など日付・時刻サポート(TZ,LC_TIME)strftime(3)などメッセージカタログ(LC_MESSAGES)catopen(3)などその他(LC_MONETARY,LC_NUMERIC)strfmon(3)など本稿では4.を扱います。localeとは、特定の言語や地域に依存する情報のことです。localeとは英語や日本語といった言語の区別のことであると考えて差し支えありません。別のlocaleとして扱います。localeはlocale名によって識別されます。使用可能なlocaleは、システムのlocaleデータベースに登録されています。プログラムが出力するメッセージの言語を実行時に選択できることをいいます。一般に、以下のような方法によって実現されています。言語ごとに別々のソースコード・実行ファイルを使用する。各言語のメッセージをコードに埋め込み、実行時にユーザーに選択させる。メッセージをプログラムの外部に持ち、実行時にユーザーに選択させる。コードの変更と再コンパイルが必要となります。再コンパイルなしで言語を選択・追加することができます。これを実現するメカニズムを、メッセージカタログといいます。文字列やアイコンなどの情報をプログラムの外部にもたせることができます。したがって、メッセージカタログは特に必要ありません。ここでは、CUI環境でのメッセージカタログに対象を絞ります。SunOS/GNUのgettextインターフェースがあります。これについて解説します。以下のような手順を踏みます。出力メッセージを集めたメッセージテキストファイル(.msg)を作成します。所定のディレクトリに置きます。指定されたlocaleに対応するメッセージカタログファイルを開きます。catgets(3)によりメッセージを取得し、出力します。catclose(3)によりメッセージカタログファイルを閉じます。環境変数LANGに希望のlocale名を設定し、プログラムを実行します。実際の手順を見ていきます。国際化されていない通常のプログラムは以下のようになっています。worldこれを国際化します。メッセージテキストファイル(.msg)を作成します。ここでは、日本語(ja_JP.EUC)とフランス語(fr_FR)をサポートすることにします。monde.\nこれをもとに、メッセージカタログファイル(.cat)を生成します。格納先ディレクトリは/usr/lib/nls/$LANG/とします。hello.fr.msg一方、プログラムの方は次のようになります。./hello世界よ、こんにちは。world詳しい説明は後述しますが、だいたい何をやっているかは想像がつくことと思います。実際に試す場合は注意してください(ディレクトリは変更可能です)。OSによってはlibxpg4などのライブラリをリンクする必要があります。/usr/lib/nls/msg/%L/%Lは$LANGのことです(後述)。IIここでは、メッセージカタログの使用法を詳しくみていきます。メッセージテキストファイルを作成します。拡張子は何でもかまいませんが、通常は.msgとします。"世界よ、こんにちは。\n"メッセージテキストファイルの各行は、コマンド行またはメッセージ行です。コマンド行には$に続いてコマンドを記述します。メッセージ行にはメッセージ番号とメッセージを記述します。空白やタブで始まる行、あるいは$と空白またはタブで始まる行はコメントとなります。メッセージ番号は、プログラムからメッセージを取得する際のIDとして使われます。これは1〜NL_MSGMAXの整数で、昇順に並んでいなければなりません。番号とメッセージの間は空白かタブで区切ります。引用文字でメッセージを囲みます。デフォルトでは引用文字は使わず、メッセージをそのまま記述します。\による継続行が使用できます。その他、C言語と同様のエスケープ文字が使用可能です。任意にグループ化することができます。これをセットといいます。各セットは1つ以上のメッセージを含みます。メッセージは、セット番号とメッセージ番号によって識別されます。セットが異なれば、メッセージ番号が同じでも違うメッセージとなります。メッセージ番号の増大を抑えることができ、管理が容易になります。コマンドは何種類かありますが、通常使うのは$quoteと$setです。[引用文字]引用文字を指定し、あるいは取り消します。メッセージ番号とメッセージとの間の空白文字の扱いが若干異なります。デフォルトでは、引用文字は指定されていません。セット番号セット番号を1〜NL_SETMAXの範囲で指定します。これ以降のメッセージは指定のセットに属します。デフォルトのセット番号は1(NL_SETD)です。メッセージカタログファイルの準備gencat(1)により、メッセージテキストファイルをメッセージカタログファイルに変換します。メッセージカタログファイルの拡張子は一般に.catです。メッセージテキストファイルの内容によって追加・更新します。所定のディレクトリ(OSによって異なります)に置きます。ディレクトリは一般にlocale名によって分かれています。環境変数NLSPATHによってディレクトリを指定することができます。ホームディレクトリにカタログを置きたい場合などに便利でしょう。NLSPATHに指定するディレクトリ名には、以下のような変数が使えます。OS・機種間の互換性はありません。メッセージカタログの利用メッセージカタログファイルをオープンするには、catopen(3)を使います。oflag);nameはメッセージカタログファイル名です。oflagは0を指定します。これは、$LANGによってlocaleを決定することを意味します。これにより、所定のディレクトリ(または$NLSPATHで指定されたディレクトリ)でメッセージカタログファイルを検索し、オープンします。拡張子.catが自動的に付加されるようです。成功した場合、カタログ記述子(ファイル記述子のようなもの)を返します。失敗した場合は、(nl_catd)-1を返します。エラーチェックをする必要は特にありません(後述)。メッセージカタログからメッセージを取得するには、catgets(3)を使います。そのメッセージへのポインタを返します。メッセージが取得できない場合に使われるデフォルトメッセージを指定します(通常これは英語です)。catgets()はdfl_msgを返します。失敗すればデフォルトメッセージが得られます。気にせずcatgets()を呼び出してかまいません。自動的にデフォルトメッセージを選択したことになります。メッセージカタログをクローズするには、catclose(3)を使います。明示的にクローズする必要があります。メッセージカタログによって国際化されている場合があります。$LANGによってlocale名を決定します。この場合LC_MESSAGESカテゴリによってlocale名を決定します。setlocale(3)を呼び出しておく必要があります。注意点について述べます。いくつか注意が必要です。静的領域に置かれている可能性があります。したがって、次のcatgets()の呼び出しの後は内容が保証されません。"write"));などとすると、期待した結果が得られないことがあります。いう場合は、以下のようにする必要があります。変更する必要があります。"第4"次のようにするとスマートです。将来メッセージが増えても安心です。メッセージカタログを使うかどうかをコンパイル時に選択できるのが好ましいといえます。そのためには、以下のようにするとよいでしょう。y/nの文字を入力させるのが一般的です。これもlocaleに従うほうが望ましいといえます。これを実現するには、そのlocaleでのyes/noを表す文字(列)を取得する必要があります。一般には、nl_langinfo(3)を使います。メッセージカタログで代用することもできます。nonこのようにlocaleに合ったメッセージカタログを作ります。語順が変わるために後続の引数を入れ替える必要が生じることがあります。offset);となります。コードの変更を余儀なくされることになります。このような状況に対応するため、printfの書式指定子が拡張されています。filename);となります。後続の引数のうちn番目のものを使うことを表しています。%1$dには1番目の引数であるoffsetが使われます。書式指定文字列をcatgets()で書き換えるだけですみます。上記の形式の書式指定子と通常の書式指定子を混在させることはできません。こちらはあまり使うことはないと思われるので割愛します。できればシェルスクリプトでも利用したいところです。dspmsg(1)というコマンドがあります。[引数...]]指定のメッセージをカタログから取得し、標準出力に出力します。対応する後続の引数で置き換えて表示されます。プログラマの負担を抑えるためには工夫が必要です。メッセージ番号の自動生成メッセージを番号で指定しなければならないというのはかなり大変です。記号定数を使いたいところです。これを実現するために、一部のOSにはmkcatdefs(1)というプリプロセッサがあります。記号定数を適当なメッセージ番号で置き換えて出力します。記号定数とメッセージ番号の対応をヘッダファイルとして出力します。メッセージテキスト/カタログを作成する必要は本来ありません。実際にはメッセージテキストは必要になります。localeに対してもメッセージカタログを作成することもありえます。メッセージ内容を同一に保たなくてはなりません。それには、一方から他方を自動生成するのが確実です。そこで、両方の場合を比較検討してみましょう。抜き出し、メッセージテキストを生成します。(ただし、catgets()の引数だけを抜き出すのであれば比較的容易です)。OSによってはそのようなツールが用意されていることもあります。メッセージの内容が一目でわかります。(ソースコードのデフォルトメッセージをすべて書き換えなくてはなりません)。メッセージテキストからソースコード(用のデフォルトメッセージ定義)を生成メッセージテキストの文字列をマクロ定義するヘッダファイルを生成します。ソースを見てもメッセージの内容がわかりません。目的のlocaleのメッセージテキストを使うようにします)。以下のような理由により、後者の方法を採用しています。デフォルトlocaleもなるべく他のlocaleと同等に扱いたい。生成ツールの作成が容易。ツールが付属していることがあります。作業の流れを見ていくことにします。既存のCソースをメッセージカタログに対応させる作業を支援します。ソースファイル名・行番号と共に出力します。catgets()のデフォルトメッセージは対象としません。これをinsertmsgの入力とします。新たなソース(nl_*.c)を生成します。メッセージ番号は自動で付加されます。gencat用のメッセージテキストを標準出力に出力します。これはデフォルトlocaleのメッセージカタログとして利用可能です。メッセージカタログに対応します。メッセージテキスト形式で出力します。*/なるコメントを従えた文字列定数を抜き出します(nはメッセージ番号)。出力はデフォルトlocaleのメッセージカタログとして利用可能です。抜き出す文字列のパターンを正規表現で指定できるところが大きく異なります。出力は、不要な文字列を除去した後、strmergeの入力とします。新たなソース(nl_*.c)を生成します。各ソースファイルごとにメッセージテキストファイル(.msg)を作ります。メッセージカタログに対応します。変換の可否などを画面上でインタラクティブに指示することができます。マルチウィンドウのスクリーンエディタです。メッセージテキストの記号定数をメッセージ番号に変換します。runcatは、mkcatdefsとgencatをパイプでつないで実行します。一般に、以下のようなハードコーディングによって実現されています。英語と他の言語とで別々の実行ファイルを提供する。実行時にオプションスイッチなどでユーザーに選択させる。実行時にコードページなどを判定して自動的に選択する。(DOS/V外部コマンドなど)筆者の知る限り、MS-DOS上でメッセージカタログなどの国際化の手法を用いて多言語化したアプリケーションの例はありません。そもそも、メッセージカタログを実装した例すらありません。UNIX版ではメッセージカタログを使用しました。UNIX版と同じ手法で国際化するのが正当な方法といえます。そのためには、メッセージカタログを実装する必要があります。他人がやっていないことをやるのはそれ自体において愉快なことです:-)。XPG仕様メッセージカタログのサブセットをMS-DOS上に実装しました。以下に実装上の特徴を述べます。MS-DOSの国番号とコードページ番号から暗黙のlocale名を決定します。DOS/Vで日本語環境と英語環境を行き来する場合を考慮した仕様です。locale名の別名が使えます。98で不幸になる場合があるのでやめました)。全localeカテゴリの設定状況を問い合わせたりできます。locale.ali)を生成します。国番号・コードページ番号・キーボードコードとlocale名の対応を定義したものです。暗黙のlocale名を決定するために使われます。locale.aliは、locale名の別名と標準名の対応を定義したものです。標準のlocale名を取得するために使われます。この実装では検索の効率化のためにバイナリに変換することにしました。locale(1)setlocale()の動作確認用です。メッセージテキストをそのままメッセージカタログとして使用します。そのため、catopen()はメッセージテキストを直接読み込んで処理します。すべてオンメモリで読み込むので、ラージデータモデルでのみ使用可能としています。catopen()はXPG4の仕様をサポートし、LC_MESSAGESカテゴリが使用可能です。NLSPATHが設定されていないときはcatopen()は失敗します。strerror(3)エラーメッセージをメッセージカタログからとるようになっています。strerror()の仕様が変だったりするので、それに対応しています。おまけ本稿では出力メッセージの国際化という高尚なテーマについて解説してきました。ユーザーがメッセージカタログを追加・修正することができるというところです。この手法は異なる方面への応用が考えられます。メッセージを特定の人物の口調に変更することが容易になるということです(笑)。バイナリパッチをあてたり、ソースを書き換えたりする必要はありません。メッセージカタログを用意するだけでいいのです。それもたいていは新規ディレクトリを作る程度で済みます。tcshの日本語メッセージカタログがあります。locale関連機能のサポート状況マルチバイト文字・ワイド文字関連は除く。XPGメッセージカタログのサポートが弱く使用には注意が必要です。/usr/xpg2lib/libxpg.aをリンクします。-lxpgで呼ぶのと等価です)。V仕様のライブラリが使われるなど不都合な場合があります。/usr/xpg2includeとします。明示的に補って使用します。JLEではXPGメッセージカタログはサポートされていません。リンクすることができません。非JLE環境でリンクしたバイナリの運用は可能なようです。Xの国際化プログラミングの基本を日本語で解説している、貴重なページです。gettextの使い方を簡潔に解説しています。UNIXのマニュアルのHTML版です。UNIXの各種マニュアル類がHTMLやPS、PDFでおいてあります。3DECの技術情報誌DTJの国際化特集号です。Drepper氏のホームページGNUにおける国際化プログラミングについて解説しています。UI的側面から国際化の問題について詳述しています。DictionaryUNIX/Cの国際化関連の用語について解説しています。