2    国際化ソフトウェアの開発

この章では,地域化の要件 (言語,コードセット,および文化習慣の違い) によって,基本的なコーディング作業がどのように変わるかを説明します。 この章で説明するプログラム開発技術を適用するサンプル・アプリケーションが,ディレクトリ /usr/examples/i18n/xpg4demo にあります。 このディレクトリにある README ファイルには,アプリケーションの紹介と,さまざまなロケールでこのアプリケーションをコンパイルして実行する方法についての説明が記載されています。 本書では,アプリケーション・プログラム xpg4demo の一部を例として使用します。

多くのコンピュータ・プログラムの主要な機能の 1 つとして,データの操作が挙げられます。 データ操作には,プログラムとコンピュータ・ユーザ間のやり取りが伴うことがあります。 商用アプリケーションでは,このようなやり取りを各ユーザの母国語で行うことが重要です。 文化的データも,適切な慣習に従っていなければなりません。

各国語でのデータ操作をサポートするプログラムを作成する場合,コンピュータ・システム内では,言語は 1 つまたは複数のコードセットで表現されることがある点を考慮しておかなければなりません。 言語ごとにその要件が違うため,コードセットの文字は,サイズ (8 ビット,16 ビットなど) とバイナリ表現の両方で異なることがあります。

言語や,文化的データ,文字のエンコーディングにまったく依存しないプログラムを作成することにより,上記のコードセットとデータの要件を満たすことができます。 そのようなプログラムは,国際化されてたプログラムと呼ばれます。 サポートされている言語,地域,およびコードセットの組み合わせに固有のデータは,プログラム・コードとは別に保持され,言語初期化関数を使用して実行時環境にバインドできます。

Tru64 UNIX は,国際化ソフトウェアの開発,地域化データの定義,および特定の言語要件の宣言に使用する,次の機能を備えています。

この章の説明と例では,標準 C ライブラリに含まれる関数に焦点を当てます。 curses ライブラリの関数の使用方法については,第 4 章を参照してください。 X および Motif ライブラリの関数の使用方法については,第 5 章を参照してください。

2.1    ロケールの使用

本オペレーティング・システムでは,Unicode ロケールと dense コード・ロケールがサポートされています。 Unicode ロケールは,/usr/i18n/lib/nls/ucsloc/ にインストールされます。 dense コード・ロケールは,/usr/i18n/lib/nls/loc にインストールされます。 省略時のロケールは,シンボリック・リンク /usr/i18n/lib/nls/dloc によって決まります。 たとえば,日本語のロケール・ファイル名 /usr/lib/nls/loc/ja_JP.eucJP は,/usr/i18n/lib/nls/dloc/ja_JP.eucJP へのシンボリック・リンクです。 ここで,/dloc は,日本語ロケールの Unicode バージョン用の /ucsloc と,dense コード・バージョン用の /loc のいずれかです。

スーパユーザの場合,シンボリック・リンクの設定を変えることで,Unicode ロケールと dense コード・ロケールの間で切り替えることができます。 l10n_intro(5) を参照するか,SysMan Menu から「国際化ソフトウェアの構成」ユーティリティを使用してください。 このユーティリティを使用して,省略時のシステム・ロケールを変更し,複数の入力システムをサポートするアジア系言語のロケールでの入力システムを指定することもできます。 詳細については,「国際化ソフトウェアの構成」ユーティリティのオンライン・ヘルプを参照してください。

Unicode ロケールは,Unicode と ISO/IEC 10646 標準に適合しており,UTF-32 をワイド文字エンコーディングとして使用します。 UTF-32 ワイド文字エンコーディングでは,wchar_t の値はロケールに関係なく同じ文字を表します。 これは,Unicode 標準では,プラットフォームが異なっても実装の一貫性が保たれるためです。

名前が .UTF-8 で終わるロケールでは,ファイル・コードと,ISO 10646 および Unicode 標準で規定された UTF-32 内部処理コード (wchar_t エンコーディング) を使用します。

その他の非 UTF-8 Unicode ロケールでは,内部処理コードとして UTF-32 を使用する一方,従来の UNIX コードセットや独自のコードセットをファイル・コード用に使用します。 これらの Unicode ロケールのサブセットには,@ucs4 という修飾子が付きます。 ただし,これらのサブセットは,@ucs4 修飾子が付かないロケールと同じです。 @ucs4 サブセットは,旧製品との互換性を保つために用意されており,将来は削除される可能性があります。 @ucs4 ロケールは,CDE のログイン・メニューから選択することはできません。 ロケール名を LANG 環境変数で指定しなければなりません。

universal.UTF-8 ロケールを利用することもできます (エンド・ユーザ用ではなく,アプリケーション用)。 このロケールは,UCS (Universal Character Set) の文字を完全にサポートしています。

エンコーディング・フォーマットについての詳細は, Unicode(5) のリファレンス・ページを参照してください。

.UTF-8 ロケールでは,1 バイトを超えるコードの文字がファイル・コードに含まれることがあります。 このため,これらのロケールは,マルチバイト・データを処理できるアプリケーションで使用してください。 新しいアプリケーションは,マルチバイトの .UTF-8 ロケールをベースにして設計してください。 このロケールには多くの種類の文字が盛り込まれているため,将来,アプリケーションは文字セットを変更することなく文字サポートを拡張することができます。

dense コード・ロケールでは,テーブル・サイズを最小にするために,ワイド文字のエンコーディングに dense コードを使用します (つまり,コードポイントが連続して割り当てられ,空になる位置はありません)。 dense コード・ロケールでは,1 つのロケールの wchar_t 値は,他のロケールで同じ文字を表しているとは限らず,ロケールに固有です。 dense コード・ロケールは,内部処理コードに依存しないアプリケーションや,性能を上げることに重点を置いているアプリケーション (dense コード・ロケールは Unicode ロケールよりも多少効率的であるため) に適しています。

マルチバイト文字セットの有効なすべてのコードポイントは,Unicode のプライベート用領域のコードポイントにマッピングされる対応先なしのコードポイントも含め,Unicode 内の有効なコードポイントにマッピングされます。 このため,dense コード・ロケールは,Unicode ロケールと等価です。 一般的に,Unicode ロケールと dense コード・ロケールで,同じ文字マップとロケール・ソースを使用することができます。 ただし,LC_COLLATE セクションで定義されていない Unicode 文字と dense コード文字は,異なる方法でソートされることがあります。

各 dense コード・ロケールに対して,Unicode ロケールが 1 つ存在します (ただし,すべての Unicode ロケールに dense コード・バージョンが存在するわけではありません)。 Latin-1 ロケール (ISO8859-1) では,Latin-1 の文字が Unicode の最初の 256 文字と同じであるため,dense コード・ロケールと Unicode ロケールは全く同じです。 シンボリック・リンクの設定に従って,同じロケール名で Unicode ロケールを参照する場合と,dense コード・ロケールを参照する場合があることに注意してください。 このため,あるロケールでのアプリケーションの実行に問題がある場合,シンボリック・リンクをチェックしてください。

Unicode ロケールでは wchar_t フォームの文字に一貫した値を使用するため,Unicode ロケールにリンクすると,ロケールやプラットフォームの間で一貫性が増します。 ただし,ユーザによっては文字を wchar_t フォームに変換する独自のアルゴリズムを使用する古い dense コード・ロケールを好む場合や,アプリケーションが dense コードの wchar_t エンコーディングに依存している場合があります。

2.2    コードセットの使用

以前は,大半の UNIX システムが 7 ビットの ASCII コードセットに基づいていました。 しかし,英語以外のほとんどの言語は,ASCII コードセット以外の文字も含んでいます。

X/Open の UNIX 標準では,オペレーティング・システムが ASCII コードセットに加え,特定のコードセットもサポートすることは規定していません。 ただし,この仕様は,システム上で利用可能なコードセットが何であれ,プログラムがそのコードセットの文字を処理できるようにするための,文字操作用のインタフェースを必須要件として規定しています。

ISO (国際標準化機構) コードセットの 1 番目のグループは,主要なヨーロッパ言語だけをカバーしています。 このグループ内のいくつかのコードセットでは,主要な言語を単一のコードセットに統合することができます。 これらのコードセットはすべて,ASCII コードセットのスーパセットであるため,既存の国際化されていないソフトウェアで問題を生じることはなく,英語以外の言語もサポートできます。 Tru64 UNIX オペレーティング・システムは,ISO 8859-1 (ISO Latin-1) を使用する,米国英語ロケールを常にサポートします。

WLS (ワールドワイド言語サポート) の一部としてインストールされているサブセットは,地域化に対応しているオペレーティング・システムをサポートし,上記以外の ISO コードセットに基づくロケールを含むことがあります。 たとえば,チェコ語,ハンガリー語,ポーランド語,ロシア語,スロバキア語,およびスロベニア語をサポートする,Tru64 UNIX のオプションの言語別サブセットには,ISO 8859-2 (Latin-2) に基づくロケールが含まれています。

以下に,WLS が提供する ISO コードセットの全リスト,それらがサポートする言語,詳しい説明の記載されているリファレンス・ページを示します。

本オペレーティング・システムは,ISO 8859-3 (Latin-3) コードセットと ISO 8859-6 (Latin-6) コードセットはサポートしていません。

標準のオペレーティング・システム上のユーティリティがサポートする ISO コードセットには,他に ISO 6937: 1983 があります。 このコードセットは,7 ビットと 8 ビットの両方の文字に対応しており,通信ネットワークや交換メディア (磁気テープやディスクなど) 上でのテキスト通信に使用されます。

ここまでで説明したコードセットには,文字が 1 バイトに格納されるという要件があります。 このようなコードセットは,文字が複数バイトに格納されるアジア系言語の要件は満たしません。 本オペレーティング・システムでは,アジア系の言語および国をサポートするサブセットをインストールすることによって,次のコードセットが使用できます。

これらのコードセットは,オペレーティング・システム・ソフトウェアのアジア言語サブセットをインストールすると,使用できるようになります。 また,アジア言語文字の入力や表示に必要な専用の端末ドライバと関連ユーティリティも使用できるようになります。

PC システム用に開発されたコードセットは,一般にコード・ページと呼ばれます。 UNIX システム用に開発された大半の言語固有のコードセットには,対応する PC コード・ページがあります。 ほとんどの場合,Tru64 UNIX はファイル・データをあるエンコード方式から別のエンコード方式に変換するコンバータを介して,PC コードセットをサポートします。 CP850 コードセットは,英語/アメリカ合衆国をサポートします。 そして,文字のエンコーディングに CP850 コード・ページを使用して PC 上で生成されるアクセント付き文字を含むデータで使用されます。 ヨーロッパの MS-DOS および Windows オペレーティング・システムでは,特に指定しなければ,通常この文字エンコーディングが使用されます。 code_page(5) のリファレンス・ページを参照してください。

Unicode と ISO/IEC 10646 標準は,ユニバーサル文字セット (UCS) を規定します。 UCS はアジア系言語も含め,すべての言語に同じ規則を適用することにより,文字ユニットを扱えるようにする文字セットです。 オペレーティング・システムは,この文字の UCS-4 (32 ビット) エンコーディングを処理コードでサポートします。

Unicode あるいは ISO/IEC 10646 標準 (または両方) で定義されている他のエンコーディング・フォーマットには,次のものがあります。

オペレーティング・システムでは,ロケールやコードセット・コンバータ (またはその両方) によって,これらの異なるフォーマットをサポートしています。 UCS-2 は UTF-16 のサブセットであるため,本オペレーティング・システムは,UCS-2 を UTF-16 コードセット・コンバータでサポートします。 本オペレーティング・システムは,コードセット変換とロケールの両方で UCS-4 をサポートします。

以下のロケールでは,UTF-32 を内部処理コードとして使用します。

LC_TIME などのロケール・カテゴリについては,2.5 節を参照してください。 データ処理でのロケールと比較については, Unicode(5) のリファレンス・ページと 2.1 節を参照してください。 ユーロ通貨記号についての詳細は, euro(5) のリファレンス・ページを参照してください。

UCS-2,UCS-4,UTF-8,UTF-16,および UTF-32 のサポートについての詳細は, Unicode(5) のリファレンス・ページを参照してください。 特定地域の言語でコードセットがどのようにサポートされるかについては,その言語のリファレンス・ページを参照してください。 言語のリファレンス・ページ,特にアジア系言語のリファレンス・ページには,ロケールではサポートされていないが,コードセット・コンバータが利用できる追加のコードセットについての説明も含まれています。

これ以降の項では,複数のコードセットの文字を処理するプログラムを作成する際に,ソース・コードの記述方法に影響する事柄について説明します。

2.2.1    データの透過性の確保

2.2 節で説明したように,国際化ソフトウェアは,さまざまな文字エンコード方式に対応しなければなりません。 プログラムは,X/Open UNIX CAE Specifications の必須要件に準拠するすべてのシステム上で特定のコードセットがサポートされていることや,個々の文字のビット数が固定されていることを前提にはできません。

UNIX システムでは 7 ビットの ASCII 文字を長年にわたって使用してきたため,バイトの最上位ビット (MSB) を独自の用途に使用するプログラムが存在します。 これは,基本コードセットの文字が常にバイトの下位 7 ビットにマップされている場合には安全な手法ですが,プログラミング手法としては好ましくありません。 国際化コードセットの世界では,バイトの最上位ビットをプログラム独自の用途に使用することは避けなければなりません。

2.2.2    コード内リテラルの使用

国際化ソフトウェアを作成するときは,コード内リテラルを使用しないでください。 例として,次の条件文について考えてみます。

if ((c = getchar()) == \141)
 

この条件文では,小文字 a が常に固定の 8 進値で表現されていますが,この前提がすべてのコードセットで正しいわけではありません。 コード内リテラルを使用する代わりに,関数を使用してください。 getchar() 関数を使用する次の文では,8 進値の代わりに文字定数を使用しています。

if ((c = getchar()) == 'a')
 

ただし,getchar() 関数はバイト単位で操作するため,入力ストリーム中の次の文字が複数バイトに渡っているときは正しく動作しません。 この問題を回避するには,getchar() 関数を getwchar() 関数に置き換えます。 getwchar() 関数は,例で使用されているように,どのコードセットでも正しく動作します。 これは,a がポータブル文字セットのメンバで,すべてのロケールで同じワイド文字値に変換されるためです。

if ((c = getwchar()) == L'a')
 

X/Open の UNIX 標準では,文字定数と文字列リテラル中の,ソース文字セットのすべてのメンバとエスケープ・シーケンスは,すべてのロケールで実行文字セットの同じメンバに変換されると規定しています。 そのため,ポータブル文字セット中の文字は,文字定数として,あるいは文字列リテラルで使用しても安全です。 英語以外の文字はポータブル文字セットに含まれていないため,リテラルとして使用すると正しく変換されないことがあります。 次に例を示します。

if ((c = getwchar()) == L' à ')

アクセント文字 à は,コードセットのソース文字セットまたは実行文字セットに含まれていない可能性があります。 また,アクセント文字のバイナリ値はコードセット間で変換できないこともあります。 ソース・ファイルで英語以外の文字が定数として使用されている場合,その結果は定義されていません。 このような場合,Unicode ロケールを一貫して使用すると効果があります。

次の例は,何らかの理由で英語以外の文字になる可能性がある定数との比較を行う方法を示します。 この定数は,メッセージ・カタログ中のシンボル識別子 MSG_ID で定義されています。 例に示されている文は,MSG_ID の値をメッセージ・カタログから取り出します。 この値は,ロケールに固有で,実行時にプログラムにバインドされます。


.
.
.
char *schar; [1] wchar_t wchar; [2]
.
.
.
schar = catgets(catd,NL_SETD,MSG_ID,"a"); [3] if (mbtowc (&wchar,schar,MB_CUR_MAX) == -1) [4] error(); if ((c = getwchar()) == wchar) [5]
.
.
.

  1. charschar へのポインタとして宣言します。 [例に戻る]

  2. 変数 wcharwchar_t 型として宣言します。 [例に戻る]

  3. catgets() 関数を呼び出して,ユーザのロケールのメッセージ・カタログから MSG_ID の値を取り出します。

    catgets() 関数は値をバイトの配列として返すため,値は schar 変数に返されます。 アクセント文字がロケールのコードセットに含まれていない場合,テストはアクセントの付かない基本文字 (a) に対して行われます。 [例に戻る]

  4. schar に含まれる値が有効なマルチバイト文字を表しているかどうかをテストします。 値が有効なマルチバイト文字の場合,プログラムはその文字をワイド文字値に変換し,結果を wchar に格納します。

    schar が有効なマルチバイト文字を含んでいない場合は,プログラムはエラーを発行します。 [例に戻る]

  5. wchar の値を定数として含む条件文です。 [例に戻る]

メッセージ・カタログと catgets() 関数の詳細については,第 3 章を参照してください。 マルチバイト文字とマルチバイト文字列を,プログラムで処理可能なワイド文字データに変換する方法については,2.2.4 項を参照してください。

2.2.3    マルチバイト文字の操作

Tru64 UNIX は,マルチバイト文字を含むコードセットをサポートするのに必要なすべてのインタフェース (putwc()getwc()fputws()fgetws() など) を備えています。 マルチバイト文字のサポートを可能にするロケールと機能を組み込むためには,オペレーティング・システムに言語別サブセットをインストールしなければなりません。 マルチバイト・ロケールがインストールされていないシステムや,インストールされているが実行時にプログラムにバインドされないシステムでは,*ws*()*wc*() 関数は,単に対応するシングルバイト関数 (putc()getc()fputs()fgets() など) と同じ動作をします。

2.2.4    マルチバイト文字データとワイド文字データ間の変換

国際化に対応しているシステムでは,データはマルチバイト文字とワイド文字のどちらでもエンコードできます。

一般に,マルチバイト・エンコーディングは,データをファイルに格納するとき,あるいはデータを外部用途やデータ交換のために生成するときに使用します。 マルチバイト・エンコーディングには,次のような欠点があります。

マルチバイト・エンコーディングにはこのような欠点があるため,プログラムの内部処理には,一般に 1 文字当たりのバイト数が固定されているワイド文字エンコーディングが使用されます。 実際,ワイド文字形式のデータを内部処理コードともいいます。 ワイド文字のサイズはシステムの実装よって異なります。 Tru64 UNIX システムでは,ワイド文字のサイズは,HP Alpha プロセッサの性能を最適化する,4 バイト (32 ビット) に設定されています。

テキストのプリントや,スキャン,入出力を行うライブラリ・ルーチンは,操作の種類に適した形でマルチバイト文字からワイド文字へ,あるいはワイド文字からマルチバイト文字へ自動的に変換する機能を備えています。 ただし,ほとんどのアプリケーションには,マルチバイト文字への変換やマルチバイト文字からの変換を明示的に指定する文や条件が含まれています。

次の例は,従業員データのデータベースからレコードを読み取るプログラム・モジュールの一部です。 この例では,プログラマは固定長のデータを扱いたいので,mbstowcs() 関数を使用して,従業員の名前と姓をマルチバイト文字からワイド文字のエンコーディングに変換します。

/*
 * The employee record is normalized with the following format, which
 * is locale independent:  Badge number, First Name, Surname,
 * Cost Center, Date of Join in the `yy/mm/dd' format. Each field is
 * separated by a TAB. The space character is allowed in the First
 * Name and Surname fields.
 */
static const char *dbOutFormat = "%ld\t%S\t%S\t%S\t%02d/%02d/%02d\n";
static const char *dbInFormat = "%ld %[^\t] %[^\t] %S %02d/%02d/%02d\n";

.
.
.
sscanf(record, dbInFormat, &emp->badge_num, firstname, surname, emp->cost_center, &emp->date_of_join.tm_year, &emp->date_of_join.tm_mon, &emp->date_of_join.tm_mday); (void) mbstowcs(emp->first_name, firstname, FIRSTNAME_MAX+1); (void) mbstowcs(emp->surname, surname, SURNAME_MAX+1);
.
.
.

マルチバイト・データを直接処理できる関数のリストについては,A.9 節を参照してください。

2.2.5    ソースおよび実行コードセットにおけるマルチバイト文字の規則

同じコードセットのソース文字セットと実行文字セットは,どちらもマルチバイト文字を含むことができます。 エンコーディングが同じである必要はありませんが,ソースと実行のどちらの文字セットも,X/Open の標準 UNIX 仕様を満たすコードセットの規則に従わなければなりません。 PC コードセットと UCS ベースのコードセットは,これらの規則のいくつか,またはほとんどを満たすだけでかまいませんが,X/Open の UNIX 標準に準拠する UNIX システムに固有のコードセットは,これらの規則をすべて満たさなければなりません。

コードセットのソース・バージョンは,次の規則にも従わなければなりません。

cc コマンド行で -std1 または -std フラグを指定すると,C 言語コンパイラは 3 文字表記 (trigraph sequence) をサポートします。 ANSI C 仕様で規定されている 3 文字表記を使用することにより,ソース・コードセットのすべての文字をサポートしていないキーボードからでも,すべての基本文字をプログラムに入力できます。 現在定義されている 3 文字表記を以下に示します。 各文字表記は,対応する 1 つの文字に置き換えられます。

3 文字表記 対応する文字
??= #
??( [
??/ \
??' ^
??< {
??) ]
??! |
??> }
??- ~

2.2.6    文字の分類

ロケールに依存するプログラムの動作のもう 1 つの特徴に,文字分類が挙げられます。 つまり,特定の文字コードがアルファベットの大文字,アルファベットの小文字,数字,句読点,制御文字,あるいはスペース文字のいずれを表しているのか判定する処理のことです。

従来,多くのプログラムは,文字の値が特定の数値範囲に収まっているかどうかを基準にして,文字の分類を行ってきました。 たとえば,次の文はすべてのアルファベットの大文字をテストします。

if (c >= 'A' && c <= 'Z')
 

この文は,すべての大文字が 0x41 から 0x5a (A から Z) までの範囲に収まっている ASCII コードセットでは有効です。 ただし,大文字が 0x41 から 0x5a まで,0xc0 から 0xd6 まで,および 0xd8 から 0xdf までの範囲にある ISO 8859-1 コードセットでは有効ではありません。 EBCDIC コードセットでも文字値は異なっており,英語の大文字のエンコーディングそのものが違います。

国際化プログラムを作成するときは,適切な国際化関数を呼び出して文字分類を行います。 次に例を示します。

if (iswupper (c))

国際化関数は,ユーザのロケールの ctype 情報に基づいてワイド文字のコード値を分類します。 文字分類関数のリストと説明については,A.2 節を参照してください。

2.2.7    文字の変換

国際化プログラムで行ってはいけないことの例として,次の文について考えてみます。 この文は,a_var 内の文字をまず小文字に変換し,次に大文字に変換することによって,ASCII 文字の変換を行います。

a_var |= 0x20;

.
.
.
a_var &= 0xdf;

ただし,上記の文は,ASCII コードの文字値を前提としていることと,入力が不正な値でも変換してしまうことから,国際化プログラムで使用するのは適切ではありません。

大文字と小文字の変換を正しく行うためには,小文字への変換には towlower() 関数を,大文字への変換には towupper() 関数を呼び出します。

a_var = towlower(a_var);

.
.
.
a_var = towupper(a_var);

これらの関数は,ユーザのロケールで指定されている情報を使用するため,文字が定義されているコードセットには依存しません。 また,これらの関数は,入力が無効であれば引数を変更せずに返します。 大文字と小文字の変換関数については,A.3 節を参照してください。

2.2.8    文字列の比較

UNIX システムでは,文字列比較のための関数が提供されています。 たとえば,次の文は文字列 s1s2 を比較し,マシンの照合順序で s1 の値が s2 よりも大きいか,等しいか,あるいは小さいかに応じて,それぞれ,ゼロよりも大きい値,等しい値,あるいは小さい値を返します。


.
.
.
int cmp_val; char *s1; char *s2;
.
.
.
cmp_val = strcmp(s1, s2);
.
.
.

ただしほとんどの言語では,単なる数値的なソートではなく,より複雑な照合アルゴリズムが必要になります。 複数回のソート・パスが必要になる理由を次に示します。

国際化環境における文字列比較はコードセットと言語に依存します。 そのため,ユーザのロケールの照合順序情報に従って文字列を比較するためには,別の関数が必要になります。 そのような関数の例を次に示します。

2.3    文化的データの処理

文化的データとは,言語や地域によって異なる可能性がある情報項目のことです。

次に例を示します。

国際化プログラムを作成するときは,文化的データについて前提を設けることはできません。 プログラムは,ユーザの地域固有の習慣に従って動作しなければなりません。 X/Open の UNIX 標準では,プログラムが実行時にアクセスできる文化的データ項目のデータベースと,それに関連するインタフェース群を利用して,この要件が満たされるように規定しています。 これ以降の項では,このデータベースと,そのデータ項目の抽出と処理に使用される関数について説明します。

2.3.1    langinfo データベース

langinfo と呼ばれる言語情報データベースには,システム上でサポートされている各ロケールの文化情報の詳細を表す項目が含まれています。 langinfo データベースは,X/Open の UNIX 標準の規定に従い,ロケールごとに次の情報を含みます。

2.3.2    langinfo データベースの照会

nl_langinfo() 関数を呼び出すことにより,langinfo データベースから文化的データ項目を抽出できます。 この関数は,ヘッダ・ファイル /usr/include/langinfo.h に定義されている定数の 1 つを item 引数として取ります。 この関数は,現在のロケールにおける item の値を含む文字列へのポインタを返します。

次の例は,日付と時刻情報を書式付けるための文字列を抽出する nl_langinfo() の呼び出しです。 この値は,定数 D_T_FMT に対応します。

nl_langinfo(D_T_FMT);

2.3.3    ローカルな習慣に合った日付および時刻文字列の生成と解釈

プログラムでは日付と時刻の文字列が生成されることがよくあります。 国際化プログラムはユーザのローカルな習慣に合った文字列を生成します。 この場合,要件を満たすには,strftime() または wcsftime() 関数を呼び出します。 どちらの関数も langinfo データベースを間接的に使用します。 さらに,wcsftime() 関数は,日付と時刻をワイド文字形式に変換します。

次の例では,strftime() 関数は,langinfo データベースの D_FMT 項目で定義されている日付文字列を生成します。


.
.
.
setlocale(LC_ALL, ""); [1]
.
.
.
clock = time((time_t*)NULL); [2] tm = localtime(&clock); [3]
.
.
.
strftime(buf, size, "%x", tm); [4] puts(buf); [5]
.
.
.

  1. 実行時にプログラムを,システムまたは個々のユーザのロケール・セットにバインドします。 [例に戻る]

  2. time() サブルーチンを呼び出して,時刻値を clock 変数に返します。 返される時刻値は,協定世界時からの相対値です。 [例に戻る]

  3. localtime() 関数を呼び出して,clock に格納されている値を,tm 構造体に格納できる値に変換します。 tm 構造体は,年,月,日,時間,分などの値を表すメンバを含みます。 [例に戻る]

  4. strftime() 関数を呼び出して,tm 構造体に格納されている値から,ユーザのロケールで定義されている書式の日付文字列を生成します。

    buf 引数は,日付文字列が返される文字列変数へのポインタです。 size 引数は,buf の最大サイズを含みます。 "%x" 引数は,printf()scanf() 関数で使用される書式文字列と同様な変換指定を指定します。 "%x" 引数は出力文字列中で,ロケールでの適切な表現に置き換えられます。 [例に戻る]

  5. puts() 関数を呼び出して,buf に含まれている文字列を標準出力ストリーム (stdout) にコピーし,改行文字を付加します。 [例に戻る]

strftime()nl_langinfo() を組み合わせて,日付と時刻の文字列を生成する方法を示す,次の例を考えてみます。 前の例と同様に,setlocale()time(),および localtime() インタフェースの呼び出しが,ここでも行われているものとします。 ただし,次の例では,strftime() の呼び出しで,書式文字列引数が nl_langinfo() の呼び出しに置き換えられています。


.
.
.
strftime(buf, size, nl_langinfo(D_T_FMT), tm); puts(buf);
.
.
.

文字列から日付と時刻値への変換 (つまり,strftime() の逆の動作) には,strptime() 関数を使用します。 strptime() 関数では多数の変換指定子がサポートされていますが,その動作はロケールに依存します。

2.3.4    金額値の書式付け

strfmon() 関数は,実行時にプログラムにバインドされるロケール情報に従って,金額値を書式付けます。 次に例を示します。

strfmon(buf, size, "%n", value);

この文は,value 変数に含まれている倍精度浮動小数点値を書式付けます。 "%n" 引数は,実行時のロケールが定義する書式に置き換えられる書式指定です。 結果は buf 配列に返されます。 この配列の最大長は,size 変数に格納されています。

money プログラムは,strfmon() 関数がどのように動作するかを示します。 ワールドワイド言語サポート・サブセットをインストールする場合,このサンプル・プログラムのソース・ファイルは /usr/i18n/examples/money ディレクトリにインストールされます。

2.3.5    プログラム独自の形式による数値の書式付け

数値,金額値などの独自の変換を行う場合は,ユーザのロケール内の特定の書式情報を使用できます。 localeconv() 関数は引数を取らない関数であり,ロケールで定義されている数値の書式付けに関するすべての情報を,プログラム中で宣言されている構造体に返します。 次に例を示します。

struct lconv *app_conv;

プログラムで定義されているルーチンは,lconv 構造体に格納されている次の情報を使用できます。

2.3.6    他の処理における langinfo データベースの使用

これまでに説明した関数以外にも,langinfo データベースを使用して,特定の文化的データ項目の設定を判定する関数があります。 たとえば,wscanf()wprintf(),および wcstod() 関数は,langinfo データベースの情報から適切な小数点文字を決定します。

2.4    テキストの表示と入力の処理

アプリケーションを作成する場合,次の 3 つの事柄に関して,ユーザの言語を考慮しなければなりません。

2.4.1    メッセージの作成と使用

プログラムは,ユーザが使用する言語でユーザと対話しなければなりません。 そのために,プログラム・メッセージをどのように定義して,またどのようにアクセスするかについては,いくつかの制約が課せられています。 具体的には,メッセージはプログラム・ソース・コードとは別のファイルで定義されており,オブジェクト・ファイルにコンパイルされることはありません。 メッセージは別のファイルに含まれるため,メッセージを別の言語に翻訳し,実行時にプログラムにリンクされる形態で格納できます。 このようにしておけば,プログラムは,ユーザの言語に合わせて翻訳されたメッセージ・テキストを取り出すことができます。

X/Open UNIX 標準では,次のメッセージ関数が規定されています。

次に,国際化プログラムがカタログからメッセージを取り出す例を示します。

#include <stdio.h>     [1]
 
#include <locale.h>    [2]
#include <nl_types.h>  [3]
#include "prog_msg.h"      [4]
main()
{
      nl_catd catd;  [5]
      setlocale(LC_ALL, "");  [6]
      catd = catopen("prog.cat", NL_CAT_LOCALE);  [7]
      puts(catgets(catd, SETN, HELLO_MSG, "Hello, world!")); [8]
      catclose(catd);  [9]
}

.
.
.

  1. 標準 C ライブラリ用のヘッダ・ファイルをインクルードします。 [例に戻る]

  2. setlocale() 関数と,関連する定数および変数を宣言するヘッダ・ファイル /usr/include/locale.h をインクルードします。 [例に戻る]

  3. catopen()catgets(),および catclose() 関数を宣言するヘッダ・ファイル /usr/include/nl_types.h をインクルードします。 [例に戻る]

  4. プログラム固有のヘッダ・ファイル prog_msg.h をインクルードします。 このヘッダ・ファイルには,このプログラム・モジュールが使用するメッセージ・セット (SETN) と,個々のメッセージ (この例の HELLO_MSG) を識別する定数が設定されています。

    メッセージ・カタログには,1 つまたは複数のメッセージ・セットを含めることができます。 各セットには,個々のメッセージが配置されています。 [例に戻る]

  5. メッセージ・カタログ記述子 catd を,nl_catd 型として宣言します。

    この記述子は,カタログをオープンする関数によって返されます。 またこの記述子は,カタログをクローズする関数への引数としても渡されます。 [例に戻る]

  6. setlocale() 関数を呼び出して,プログラムのロケール・カテゴリを,ユーザのロケール環境変数の設定にバインドします。

    LC_MESSAGES カテゴリに設定されているロケール名は,この例の catopen()catgets() 関数によって使用されるロケールです。 一般に,システム管理者やユーザは,LANG または LC_ALL 環境変数だけを特定のロケール名に設定します。 これにより,LC_MESSAGES 変数も暗黙的に設定されます。 [例に戻る]

  7. catopen() 関数を呼び出して,このプログラムが使用する prog.cat というメッセージ・カタログをオープンします。

    NL_CAT_LOCALE 引数は,LC_MESSAGES に設定されているロケール名をプログラムで使用することを指定します。 catopen() 関数は,NLSPATH 環境変数に設定されている値を使用して,メッセージ・カタログの位置を判別します。 この呼び出しは,catd 変数にメッセージ・カタログ記述子を返します。 [例に戻る]

  8. puts() 関数を呼び出して,メッセージを表示します。

    この呼び出しの第 1 引数は,catgets() 関数の呼び出しです。 この関数は,HELLO_MSG 識別子を持つメッセージのテキストを取り出します。 このメッセージは,SETN 定数によって識別されるメッセージ・セットに含まれています。 catgets() の最後の引数は,メッセージ呼び出しがカタログから翻訳テキストを取り出せなかった場合に使用される,省略時のテキストです。 一般に,省略時のテキストは英語です。 [例に戻る]

  9. catclose() 関数を呼び出して,記述子が catd 変数に格納されているメッセージ・カタログをクローズします。 [例に戻る]

メッセージ・カタログの作成と使用については,第 3 章を参照してください。

2.4.2    出力テキストの書式付け

メッセージをさまざまな言語に翻訳するためには,メッセージをプログラム・ソースから分離するだけでなく,プログラム中のメッセージ文字列を注意深く作成しなければなりません。

次の例を考えてみます。

printf(catgets(catd, set_id, WRONG_OWNER_MSG,
               "%s is owned by %s\n"),
               folder_name, user_name);

上記の文はメッセージ・カタログを使用していますが,特定の言語構造 (名詞の後に受動態の動詞があり,その後に名詞が続く) を想定しています。 動詞の受動態は,すべての言語にあるわけではありません。 そのため,翻訳されたメッセージでは,folder_name の前に user_name がプリントされる可能性があります。 つまり,表示されるメッセージが,"JULY_REVENUE is owned by John_Smith." ではなく,"John_Smith owns JULY_REVENUE" となるように,翻訳者がメッセージの語順を変更しなければならないことがあります。

メッセージ要素の順序が固定されていることから生じる問題を回避するために,printf() ルーチンの書式指定子では,次の未使用の引数に対して書式変換を行うだけでなく,引数リスト内の n 番目の引数に対して書式変換を行えるようになりました。 拡張された書式変換を利用するには,変換文字の % を,%digit $ というシーケンスに置き換えます。 digit は,引数リスト中での引数の位置を指定します。 この機能を書式文字列 "%s is owned by %s\n" に適用した例を,次に示します。

printf(catgets(catd, set_id, WRONG_OWNER_MSG,
               "%1$s is owned by %2$s\n"),
               folder_name, user_name);

翻訳者は,プログラムが使用するメッセージ・ファイル内の WRONG_OWNER_MSG エントリの省略値,つまり,文字列 "%1$s is owned by %2$s" を,次の語順を持つ英語以外の言語に置き換えることができます。

WRONG_OWNER_MSG        "%2$s owns %1$s\n"

2.4.3    入力テキストの走査

2.4.2 項で説明した,出力テキストの書式付けに関する問題は,入力テキストでも起こります。 たとえば,ユーザが日付の要素を指定する順序や,金額文字列の各部分を区切るために入力する文字は,国によって異なります。 そのため,scanf() ファミリの関数でも,ユーザが文字列の要素を入力する方法に柔軟性を持たせるために,拡張書式変換指定子がサポートされています。

次の例について考えてみます。


.
.
.
int day; int month; int year;
.
.
.
scanf("%d/%d/%d", &month, &day, &year);
.
.
.

この文の書式文字列は,すべてのユーザが日付の入力に米国英語の書式 (月/日/年) を使用するという前提に基づいています。 国際化プログラムでは,拡張書式指定子を使用して,文字列要素の順序に関する言語固有の要件をサポートするようにします。 次に例を示します。


.
.
.
scanf(catgets(catd, NL_SETD, DATE_STRING, "%1$d/%2$d/%3$d"), &month, &day, &year);
.
.
.

DATE_STRING メッセージの省略値である "%1$d/%2$d/%3$d" は,ユーザが日付の入力に月/日/年の書式を使用する国でのみ有効です。 文字列要素の順序や書式が異なる国では,翻訳者はプログラムのメッセージ・ファイルのエントリを変更できます。 次の言語について考えてみます。

2.5    実行時環境へのロケールのバインド

国際化プログラムが正しく動作するためには,ユーザの環境に適した地域化されたデータに実行時にバインドしなければなりません。 この作業は setlocale() 関数が行います。 setlocale() を呼び出すと,次の処理が実行されます。

この呼び出しは,categorylocale_name の 2 つの引数を取ります。

category 引数は,照会したい,変更したい,あるいは使用したいロケールのすべての,または特定のセクションを指定します。 category の値と,その意味を次に示します。

locale_name 引数は,次の値のいずれかです。

2.5.1    システムまたはユーザのロケール・セットへのバインド

一般に,システム管理者やユーザは,LANG または LC_ALL 環境変数にロケール名を設定します。 これらの環境変数のいずれかを設定すると,ロケールのすべてのロケール・カテゴリ変数に同じロケール名が自動的に設定されます。

LC_ALL を使用してすべてのロケール・カテゴリに同じロケール名を設定した場合以外は,システム管理者やユーザは,個々のロケール・カテゴリ変数に異なるロケール名を設定できます。 一般に国際化プログラムには,プログラム内のすべてのロケール・カテゴリを,そのユーザに対してすでに有効になっている環境変数設定で初期化するための,LC_ALL 呼び出しが含まれています。 例を次に示します。

setlocale(LC_ALL, "");

標準のロケール名は,language_TERRITORY.codeset@modifier からなります。 たとえば,zh_CN.dechanzi@radical です。 意味は,次のとおりです。

ロケールには通常,複数のバリエーションがあります。 各バリエーションの名前はベースのロケールと同じですが,アット記号 (@) で始まるファイル名サフィックスが付いています。 本来 UNIX にないコードセット (UCS-4 や CP850 など) をサポートするロケール・バリエーションは,LANGLC_ALL に設定できます。

ただし,1 つのロケール・カテゴリだけベース・ロケールと異なっているロケール・バリエーションは,適切なロケール・カテゴリにのみ割り当ててください。 たとえば,@radical などの特定の照合シーケンスをサポートするためのロケール・バリエーションは,LC_COLLATE に設定します。 ユーロ通貨記号 (@euro) をサポートするためのロケール・バリエーションは,LC_MONETARY に設定します。 LANG 環境変数の設定には,これらのバリエーションではなく,ベース・ロケール名を使用してください。

さらに,ベース・ロケール名をすべてのロケール・カテゴリに設定するのでなければ,LC_ALL 環境変数は使用しないでください。 この環境変数に値を設定すると,LANG と,個別のロケール・カテゴリの環境変数の設定が無効になります。

多くのロケール固有ファイルは,ロケール名の言語,地域,およびコードセット部分からなる名前のディレクトリ内に置かれます。 コマンドやその他のシステム・アプリケーションは,ディレクトリ・ノードの 1 つとして %L を含む検索パスに,LANG 変数の設定値を挿入します。 これにより,ソフトウェア・プログラムは,現在のロケールで使用されるべき,フォント,リソース・ファイル,ユーザ定義文字ファイル,翻訳済みのリファレンス・ページなどの,正しいセットを見つけることができます。 LANG 変数に設定する値に,照合に関連する @ サフィックスが含まれていると,ロケール固有のファイルの一部がアプリケーションから見つからなくなることがあります。

2.5.2    プログラム実行時のロケールの変更

国際化プログラムの中には,ユーザからのロケール名の入力を受け付けたり,プログラムの実行中にロケールを変更しなければならないものがあります。 次の例は,setlocale() を呼び出して,すべてのロケール・カテゴリを同じロケール名で明示的に初期化または再初期化します。


.
.
.
nl_catd catd; [1] char buf[BUFSIZ]; [2]
.
.
.
setlocale(LC_ALL, ""); [3] catd = catopen(CAT_NAME, NL_CAT_LOCALE); [4]
.
.
.
printf(catgets(catd, NL_SETD, LOCALE_PROMPT_MSG, "Enter locale name: ")); [5] gets(buf); [6] setlocale(LC_ALL, buf); [7]
.
.
.

  1. カタログ記述子 catdnl_catd 型 として宣言します。 [例に戻る]

  2. 後でロケール名が格納される buf 変数を宣言します。

    プログラムを実行するシステムに関係なく,この変数がロケール名を格納できる十分な大きさになるように,最大サイズを BUFSIZ に設定します。 この定数は,システム・ベンダが /usr/include/stdio.h で定義しています。 [例に戻る]

  3. setlocale() を呼び出して,プログラムのロケール設定を,プログラムを実行するユーザに対して有効になっているロケール設定に初期化します。 [例に戻る]

  4. catopen() を呼び出して,プログラムのメッセージが格納されているメッセージ・カタログをオープンします。 この関数は,カタログ記述子を catd 変数に返します。

    CAT_NAME 定数は,プログラム自体のヘッダ・ファイルで定義されています。 [例に戻る]

  5. ユーザに新しいロケール名の入力を求めます。

    NL_SETD 定数は,メッセージ・カタログ中の省略時のメッセージ・セット番号を指定するもので,/usr/include/nl_types.h で定義されています。 LOCALE_PROMPT_MSG 識別子は,省略時のメッセージ・セット内の翻訳されたプロンプト文字列を指定します。 [例に戻る]

  6. gets() 関数を呼び出して,ユーザが入力したロケール名を読み取り,buf 変数に格納します。 [例に戻る]

  7. locale_name 引数に buf を指定して setlocale() を呼び出し,ロケール全体を再初期化します。 [例に戻る]

場合によっては,特定のカテゴリのデータについてのみロケールを変更しなければならないことがあります。 たとえば,金額値を含んでいる,いくつかの国固有のファイルを処理するプログラムは,各ファイルのデータを処理する前に,プログラム変数を新しいロケール名で再初期化し,その変数値を使用してロケールの LC_MONETARY カテゴリだけを再設定できます。