Next: Optimized gettext, Previous: Contexts, Up: gettext [Contents][Index]
いままで説明してきた既存のアプローチでは完全に無視してきましたが、gettext
ファミリーの関数(すべてのcatgets
関数も同様)を実世界で使用する場合は、1つの問題があります。それはplural
form(複数形書式)の取り扱いです。
インターナショナリゼーション以前のUnixのソースコード(そして悲しいことにインターナショナリゼーション以降のソースコードさえも)を見てみると、以下のようなコードを目にすることがあります:
printf ("%d file%s deleted", n, n == 1 ? "" : "s");
最初にコードをインターナショナライズする人々から不具合が報告された後は、このような複数形についての場合分けを無視するか、"file(s)"
という文字列を使うようになりました。これはどちらも不自然で解決法とはなりませんでした。最初の試みとして、以下のようにすることで解決が図られました:
if (n == 1) printf ("%d file deleted", n); else printf ("%d files deleted", n);
しかし、これは問題の解決になっていません。この方法は、単純に名詞に
‘s’
を追加することによりplural
formを表現するだけではない言語をも対象としていましたが、結局それだけのことでした。人々は自分が使っている言語のルールが、他の言語にも適用できる普遍的なものだと信じるという罠に陥っていたのです。実際には言語ファミリー間でplural
formの取り扱いかたは大きく異なっていたのです。たとえば、Rafal Maszkowski
<rzm@mat.uni.torun.pl>
による以下のレポートを見てください:
わたしたちはPolishでplik(fileのこと)を以下ように表現します:
1 plik 2,3,4 pliki 5-21 pliko'w 22-24 pliki 25-31 pliko'w以下同様にカウントしていきます(o’は8859-2 oacuteを意味しており、okreskaより、むしろaogonekと似ています。
言語間(そして言語ファミリー内部においてさえも)には、2つの異なる事象が存在しました:
単一の形式しかもたない言語ファミリーや、多数の形式をもつ言語ファミリーがあります。これに関する更なる情報は、このセクションの範囲を超えています。
これらの理由により、アプリケーションを記述する人は、これらの問題をコードで解決するべきではないという結論になります。これは言語環境にたいしてハードコードされた状態においてのみ有用なローカリゼーションでしょう。かわりに拡張されたgettext
のインターフェースを使うべきです。
これらの追加の関数は、単一のキー文字列ではなく、2つの文字列と、数値の引数をとります。この考え方の背景には、数値の引数と最初の文字列をキー文字列として使用することにより、翻訳者が指定した正しいplural
formを実装が選択できるようにするというアイデアがあります。それから、2つの文字列引数は、メッセージカタログが見つからなかったときの戻り値を提供するために使用されます(これは通常のgettext
の振る舞いと同じです)。このケースではGermanic言語のルールが適用され、最初の文字列引数はsingular
form、2番目の文字列引数はplural formとみなされます。
この結果、言語カタログをもたないプログラムは、それがGermanic言語のルールにしたがって記述された場合のみ正しい文字列を表示できるということになります。これは確かに制限なのですが、GNU
C library(とGNU
gettext
パッケージ)がGNUパッケージの一部として記述されていること、そしてGNUプロジェクトのコーディング規約がEnglishによるプログラム記述を要請しているので、制限があるにもかかわらずこの解決策により要件を満足することができるのです。
ngettext
関数は、gettext
関数と似ており、メッセージカタログを検索する方法は同じですが、2つの追加の引数をとります。パラメーターmsgid1には、変換する必要のある文字列のsingular
formを指定します。このパラメーターはカタログを検索するキーとしても使用されます。パラメーターmsgid2にはplural
formを指定します。パラメーターnは、plural
formの適用を決定するのに使用されます。メッセージカタログが見つからなかったとき、n ==
1
ならmsgid1
が戻され、それ以外のときはmsgid2
が戻されます。
以下はこの関数の使用例です:
printf (ngettext ("%d file removed", "%d files removed", n), n);
nの数値は、printf
関数にも引き渡されることに注意してください。ngettext
だけに渡したい場合、この例は不適切です。
Englishのsingular caseの場合、常に1であるような数値は、"one"に置き換えることができます:
printf (ngettext ("One file removed", "%d files removed", n), n);
‘printf’関数は、format stringが与えられていない余分な引数を無視するので、この例は問題なく動作します。
ここで2つ以上の引数を要求するようなformat stringを関数で使う場合、以下の例のような使い方はできません:
printf (ngettext ("%d file removed from directory %s", "%d files removed from directory %s", n), n, dir);
Englishでsingular caseのときに‘%d’を“one”に置き換えたように、他の言語の翻訳者も特定の単語に置き換えたいと望むかもしれません。しかしCのformat stringでは、1番目の引数をスキップして2番目の引数を使用するようなことはできません。このような場合は引数の順番を変えて、‘n’が最後にくるようにする必要があります:
printf (ngettext ("%$2d file removed from directory %$1s", "%$2d files removed from directory %$1s", n), dir, n);
このように引数の配置を指定する文法についての詳細は、c-formatを参照してください。
n
の値がとる範囲がわかっている場合には、xgettext
ツールのためのコメントを指定することができます。以下の例のような情報は、翻訳者が適切な翻訳を行う助けとなるでしょう:
if (days > 7 && days < 14) /* xgettext: range: 1..6 */ printf (ngettext ("one week and one day", "one week and %d days", days - 7), days - 7);
以下の例のように、文字列に数値が含まれないようなときに、この関数を使うこともできます:
puts (ngettext ("Delete the selected file?", "Delete the selected files?", n));
この例で、nはplural formであるかを判定するためだけに使用されています。
dngettext
は、選択されたメッセージカタログにたいして、dgettext
と同様な方法で使用します。異なるのは、正しいplural
formのために、2つの余分なパラメーターを指定できる点です。この2つのパラメーターは、ngettext
のときと同様に処理されます。
dcngettext
は、選択されたメッセージカタログにたいして、dcgettext
と同様な方法で使用します。異なるのは、正しいplural
formのために、2つの余分なパラメーターを指定できる点です。この2つのパラメーターは、ngettext
のときと同様に処理されます。
では、これらの関数はどのようにしてplural formsの問題を解決しているのでしょうか? 言語学の情報(そして、それは利用可能ではありません)がないかぎり、少しの差異しかないplural formのうちからどれを使用すればよいのか、そして新たにサポートされる言語ごとに数を増やせるかを決定することはできません。
したがって、plural formを選択するルールを翻訳者が指定するという解決策が実装されました。各言語ごとに方式が異なる以上、これはコード内に情報をハードコーディングする以外の、唯一可能な解決策なのです(それでもまだ新しい言語の使用を妨げない拡張可能性を満たすという要件は残されています)。
plural
form選択のための情報は、以下のようにPOファイルのヘッダーエントリー(msgid
が空文字列のエントリーのうちの1つ)に保存されています:
Plural-Forms: nplurals=2; plural=n == 1 ? 0 : 1;
nplurals
には、その言語では何種類のplural
formがあるのかを数字で指定します。plural
に続く文字列には、C言語で使用できる評価式です。負の数値は使用できません。数値には正の整数を指定します。また、変数として使用できるのはn
だけです。式で空白を使うことはできますが、バックスラッシュは使用できません。以下の例のうち、バックスラッシュと改行が使用されている例がありますが、これは表示を見やすくする目的のためだけに使用しています。この式はngettext
、dngettext
、dcngettext
が呼び出されたときに評価されます。これらの関数に数値が渡されると、式中の変数n
の値として、その数値が評価されます。結果は0以上、かつnplurals
に指定した値より小さくなければなりません。
複数形の使い分けについては、以下のルールが知られています。言語と言語のファミリーが記載されていますが、この情報を言語ファミリー全体に適用する必要があるという訳ではありません(見やすくするために併記しています)。5
1つの形式しか必要としない言語があります。これらの言語ではsingular formとplural formの区別はありません。この場合の適切なヘッダーエントリーは以下のようになるでしょう:
Plural-Forms: nplurals=1; plural=0;
このような特性をもつのは以下の言語です:
Japanese, Vietnamese, Korean
この形式はEnglishで使われているもので、既存のプログラムでも一番多く使用されています。この場合のヘッダーエントリーは以下のようになるでしょう:
Plural-Forms: nplurals=2; plural=n != 1;
(注意: これはCの真偽値が0か1の2値をとる機能を使用しています。)
このような特性をもつのは以下の言語です:
English, German, Dutch, Swedish, Danish, Norwegian, Faroese
Spanish, Portuguese, Italian, Bulgarian
Greek
Finnish, Estonian
Hebrew
Esperanto
同じヘッダーエントリーを使う他の言語:
Hungarian
Turkish
Hungarianは、数字を含んだセンテンスでは複数形を使いません。たとえば“1 apple”は “1 alma” で、“123
apples”も“123 alma”です。しかし数が明確でない場合には“the apple”は“az alma”、“the
apples”は“az
almák”のようにsingularとpluralを区別します。このようなセンテンスをngettext
がサポートするようになってから、Hungarianも2つの形式をもつクラスに分類されるようになりました。
Turkish も同様です: “1 apple”は“1 elma”で、“123 apples”も“123 elma”です。しかし数字が省略された場合には、“the apple”は“elma”、“the apples”は“elmalar”のようにsingularとpluralを区別します。
言語ファミリーの例外的なケースです。ヘッダーエントリーは以下のようになります:
Plural-Forms: nplurals=2; plural=n>1;
このような特性をもつのは以下の言語です:
Brazilian Portuguese, French
ヘッダーエントリーは以下のようになります:
Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2;
このような特性をもつのは以下の言語です:
Latvian
ヘッダーエントリーは以下のようになります:
Plural-Forms: nplurals=3; plural=n==1 ? 0 : n==2 ? 1 : 2;
このような特性をもつのは以下の言語です:
Gaeilge (Irish)
ヘッダーエントリーは以下のようになります:
Plural-Forms: nplurals=3; \ plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2;
このような特性をもつのは以下の言語です:
Romanian
ヘッダーエントリーは以下のようになります:
Plural-Forms: nplurals=3; \ plural=n%10==1 && n%100!=11 ? 0 : \ n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2;
このような特性をもつのは以下の言語です:
Lithuanian
ヘッダーエントリーは以下のようになります:
Plural-Forms: nplurals=3; \ plural=n%10==1 && n%100!=11 ? 0 : \ n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;
このような特性をもつのは以下の言語です:
Russian, Ukrainian, Belarusian, Serbian, Croatian
ヘッダーエントリーは以下のようになります:
Plural-Forms: nplurals=3; \ plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;
このような特性をもつのは以下の言語です:
Czech, Slovak
ヘッダーエントリーは以下のようになります:
Plural-Forms: nplurals=3; \ plural=n==1 ? 0 : \ n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;
このような特性をもつのは以下の言語です:
Polish
ヘッダーエントリーは以下のようになります:
Plural-Forms: nplurals=4; \ plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;
このような特性をもつのは以下の言語です:
Slovenian
ここまで読んで、あなたは思うかもしれません。ngettext
が扱うnの型は‘unsigned
long’だ。では、もっと大きな整数型の場合はどうだろうか? 負の数値については? 浮動小数値の場合は?
‘uintmax_t’や‘unsigned long
long’のようなより大きな数値のための整数型の場合、これらの数値は‘unsigned
long’の範囲に適合するように値を小さくして処理できます。この場合、単に値を‘unsigned
long’にキャストするのは正しくありません(キャストではULONG_MAX + 1
は0、ULONG_MAX +
2
は1、...のようにキャストされます)。あなたは、いままで紹介してきたすべてのplural
formで、100(または1000や1000000)で除する方法によって数式を間接的に評価できるという事実を見てきたでしょう。もし大きな数値を、下6桁を保持して[1000000,
1999999]という範囲の他の数値に置き換えられれば、同じplural formの選択方法で取り扱えます。この場合のコードは以下のようになるでしょう:
#include <inttypes.h> uintmax_t nbytes = ...; printf (ngettext ("The file has %"PRIuMAX" byte.", "The file has %"PRIuMAX" bytes.", (nbytes > ULONG_MAX ? (nbytes % 1000000) + 1000000 : nbytes)), nbytes);
負の数や小数については、通常はsingularかpluralが明解でない物質量に適用されます。このようなケースでは、ngettext
を使う必要はなく、単にすべての値に適切な形式を指定してgettext
を呼び出します:
printf (gettext ("Time elapsed: %.3f seconds"), num_milliseconds * 0.001);
num_millisecondsが1000の倍数のようなときでも、出力は以下のようになります
Time elapsed: 1.000 seconds
これは、Englishや他の言語でも許容できる出力です。
plural formにたいする翻訳者の考え方については、Translating plural formsで説明しています。
Next: Optimized gettext, Previous: Contexts, Up: gettext [Contents][Index]