Next: , Previous: , Up: gettext   [Contents][Index]


11.2.6 複数形(plural forms)にたいする追加の関数

いままで説明してきた既存のアプローチでは完全に無視してきましたが、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によるプログラム記述を要請しているので、制限があるにもかかわらずこの解決策により要件を満足することができるのです。

Function: char * ngettext (const char *msgid1, const char *msgid2, unsigned long int n)

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 ("%2$d file removed from directory %1$s",
                  "%2$d files removed from directory %1$s",
                  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であるかを判定するためだけに使用されています。

Function: char * dngettext (const char *domain, const char *msgid1, const char *msgid2, unsigned long int n)

dngettextは、選択されたメッセージカタログにたいして、dgettextと同様な方法で使用します。異なるのは、正しいplural formのために、2つの余分なパラメーターを指定できる点です。この2つのパラメーターは、ngettextのときと同様に処理されます。

Function: char * dcngettext (const char *domain, const char *msgid1, const char *msgid2, unsigned long int n, int category)

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だけです。式で空白を使うことはできますが、バックスラッシュは使用できません。以下の例のうち、バックスラッシュと改行が使用されている例がありますが、これは表示を見やすくする目的のためだけに使用しています。この式はngettextdngettextdcngettextが呼び出されたときに評価されます。これらの関数に数値が渡されると、式中の変数nの値として、その数値が評価されます。結果は0以上、かつnpluralsに指定した値より小さくなければなりません。

複数形の使い分けについては、以下のルールが知られています。言語と言語のファミリーが記載されていますが、この情報を言語ファミリー全体に適用する必要があるという訳ではありません(見やすくするために併記しています)。5

1つの形式だけを使うもの:

1つの形式しか必要としない言語があります。これらの言語ではsingular formとplural formの区別はありません。この場合の適切なヘッダーエントリーは以下のようになるでしょう:

Plural-Forms: nplurals=1; plural=0;

このような特性をもつのは以下の言語です:

Asian family

Japanese, Vietnamese, Korean

Tai-Kadai family

Thai

2つの形式がありsingularは1にしか適用しないもの

この形式はEnglishで使われているもので、既存のプログラムでも一番多く使用されています。この場合のヘッダーエントリーは以下のようになるでしょう:

Plural-Forms: nplurals=2; plural=n != 1;

(注意: これはCの真偽値が0か1の2値をとる機能を使用しています。)

このような特性をもつのは以下の言語です:

Germanic family

English, German, Dutch, Swedish, Danish, Norwegian, Faroese

Romanic family

Spanish, Portuguese, Italian, Bulgarian

Latin/Greek family

Greek

Finno-Ugric family

Finnish, Estonian

Semitic family

Hebrew

Austronesian family

Bahasa Indonesian

Artificial

Esperanto

同じヘッダーエントリーを使う他の言語:

Finno-Ugric family

Hungarian

Turkic/Altaic family

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を区別します。

2つの形式がありsingularを0と1に適用するもの

言語ファミリーの例外的なケースです。ヘッダーエントリーは以下のようになります:

Plural-Forms: nplurals=2; plural=n>1;

このような特性をもつのは以下の言語です:

Romanic family

Brazilian Portuguese, French

3つの形式があり、0を特別なケースとして扱うもの

ヘッダーエントリーは以下のようになります:

Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2;

このような特性をもつのは以下の言語です:

Baltic family

Latvian

3つの形式があり、1と2を特別なケースとして扱うもの

ヘッダーエントリーは以下のようになります:

Plural-Forms: nplurals=3; plural=n==1 ? 0 : n==2 ? 1 : 2;

このような特性をもつのは以下の言語です:

Celtic

Gaeilge (Irish)

3つの形式があり、00または[2-9][0-9]で終わる数字を特別なケースとして扱うもの

ヘッダーエントリーは以下のようになります:

Plural-Forms: nplurals=3; \
    plural=n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2;

このような特性をもつのは以下の言語です:

Romanic family

Romanian

3つの形式があり、1[2-9]で終わる数字を特別なケースとして扱うもの

ヘッダーエントリーは以下のようになります:

Plural-Forms: nplurals=3; \
    plural=n%10==1 && n%100!=11 ? 0 : \
           n%10>=2 && (n%100<10 || n%100>=20) ? 1 : 2;

このような特性をもつのは以下の言語です:

Baltic family

Lithuanian

3つの形式があり、1、または2、3、4で終わる数字を特別なケースとして扱うもの、ただし1[1-4]で終わる数字を除く

ヘッダーエントリーは以下のようになります:

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;

このような特性をもつのは以下の言語です:

Slavic family

Russian, Ukrainian, Belarusian, Serbian, Croatian

3つの形式があり、1と、2、3、4を特別なケースとして扱うもの

ヘッダーエントリーは以下のようになります:

Plural-Forms: nplurals=3; \
    plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;

このような特性をもつのは以下の言語です:

Slavic family

Czech, Slovak

3つの形式があり1と、2、3、4で終わる数字を特別なケースとして扱うもの

ヘッダーエントリーは以下のようになります:

Plural-Forms: nplurals=3; \
    plural=n==1 ? 0 : \
           n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;

このような特性をもつのは以下の言語です:

Slavic family

Polish

4つの形式があり1と、02、03、04で終わるすべての数字を特別なケースとして扱うもの

ヘッダーエントリーは以下のようになります:

Plural-Forms: nplurals=4; \
    plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;

このような特性をもつのは以下の言語です:

Slavic family

Slovenian

6つの形式があります。1つ目は0、2つ目は1、3つ目は2、4つ目は03、...、10で終わるすべての数字、5つ目は11、...、99で終わるすべての数字、そして6つ目はそれ以外です。

ヘッダーエントリーは以下のようになります:

Plural-Forms: nplurals=6; \
    plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 \
    : n%100>=11 ? 4 : 5;

このような特性をもつのは以下の言語です:

Afroasiatic family

Arabic

ここまで読んで、あなたは思うかもしれません。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で説明しています。


Footnotes

(5)

追加の情報を歓迎します。bug-gnu-gettext@gnu.orgbug-glibc-manual@gnu.orgまでメールしてください。Unicode CLDR Project (http://cldr.unicode.org)は、異なるフォーマットによる、包括的なplural formsを提供しています。msginitプログラムは、このフォーマットにたいする予備的なサポートをもっているので、これを基本として使用することができます(msginit Invocationを参照してください)。


Next: , Previous: , Up: gettext   [Contents][Index]