実装Note - 国際化

まともな国際化を。

minilang は, Unicodeベースです。

構文

識別子

ASCII文字だけを特別扱いするのではなく, 漢字・ひらがなを含め, 多様な文字を使えるようにします。

Rubyに合わせて, 大文字で始まるのは定数, 小文字はローカル変数です。とりあえず, 次のようにしてみました。\p{ } は, Unicodeプロパティ名 (Unicode property name) です。

CapitalLetter = \p{Lu} | \p{Lt}

NonCapitalLetter = \p{Ll} | \p{Lm} | \p{Lo} | "_" | [\x3400-\x4db5] | 
                   [\x4e00-\x9fcc] 

IdentifierPart = ( CapitalLetter | NonCapitalLetter | \p{Mn} | \p{Mc} | 
                   \p{Nd} | \p{Nl} )*

ConstIdentifier = CapitalLetter IdentifierPart

Identifier = NonCapitalLetter IdentifierPart ( "!" | "?" )?

ホワイトスペース

次の文字は空白として扱います。当然, 全角空白もホワイトスペースです。

WhiteSpace = "\t" | \x000B | \x000C | \p{Zs}

テキスト (文字列)

要求

まずどのぐらいのレベルを目指すか, です。

よくある状況として, 一枚のWebページに、いろいろな国の人が書き込んだ内容を表示することを考えてみてください。

1枚の、という時点で、一つの文字列にいろいろな国の文字を含められるようにしなければならない, と分かります。

現実的にこれを実現する方法は, 一つの文字列に複数の文字集合を含めるか, すべての文字を含む文字集合を使うか, のいずれかです。

Emacs MULE (MULtilingual Enhancement)

Emacs は, 古くから, 一つのテキストの中で実用的に多言語を扱えました。

この機能は, 当初は Emacs に対する拡張として提供されていて, 1995年には Emacs 19.28ベースの Mule 2.3 (末摘花) がリリースされていました。末摘花, ということは, その前に5つバージョンがあります。(整理されたページがありました; History of Emacs and Mule)

Mule は, それぞれの文字を, (文字集合を表すコード + その文字集合内のコード) という組で表現したようです。これにより, 一つのテキスト (文字列) 内で, 複数の文字集合の文字を同居させ, 多言語を実現していました。

その後, 1999年頃 (?) には UTF-8 などを扱う Mule-UCS が出ていたようです。

そこからだいぶ経って, Emacs23で, 上の (文字集合 + その中でのコード) というやり方を捨て, 内部コードが Unicode になりました。これが2009年7月.

ruby 1.9

ruby 1.9.1は2009年1月にリリースされました。くしくも Emacsがバージョン23でUnicodeベースになった年です。

ruby 1.9では, 文字列がその文字コード (エンコーディング) を保持するようになりました。

とても残念なことですが, 文字列が文字コードを持つ, ただそれだけ。多用字系ですらない。こんなものを多言語 (Multilingual) と呼ぶのは, どうかしているとしか思えません。

ruby 1.9 では, Encoding::Converter クラスが文字コードの変換を担当します。

これも, Citrus Project --- a Comprehensive I18n framework Towards Respectable Unix Systems. がはるか昔に通った道。これがいつ頃の話かクリアではありませんが, Solaris7 (1998年11月) よりは後で, 2003年時点には NetBSD-current にマージされていたようです。

minilangの実装

大きな文字集合にするか, 小さな文字集合を切り替えるか, ですが, 効率性の面から, 大きな文字集合のほうがいいでしょう。

2012-3年現在、上に書いた要求を実用的にこなせるのは、Unicodeだけです。Character Set Independence (CSI) でもいいのですが、でも、いつもUnicodeを選択している状態になります。

ということで、minilangでは、文字列をUnicodeベースにしてみました。

minilang における「文字」は、UTF-16 code unitで、文字列はその並びです。UTF-16はサロゲートペアがあるので, 1または2 code unitで Unicode code point になります。

サロゲートペアは、開発者 (minilangの利用者) が適切に対応してください。

本物の文字とUnicodeコードポイントは, 一致することもあれば, 一致しないこともあります。Forms of Unicode

ruby 1.9 は, String#[] が文字列を返すように変更されましたが, これもコンテナとのインターフェイスの整合性の観点から, code unitを返すべきです。

IO

どちらかというと、IOへのインパクトのほうが大きいです。

Ruby 1.9のStringはバイト列でもいいので、テキストファイルもバイナリファイルも、IOの各メソッドが同じ型を返せます。

minilangでは、StringをUnicode文字列と決めたので、バイナリファイルでは使えません。

バイト列はどのように表現すべきでしょうか。

ファイル (IO) の用途を考えると、次に分けれるでしょう。

  1. XMLファイルや対話的にファイルを開く場合など、あらかじめ文字コードが決まっているか外から与えられるテキストファイル
  2. テキストファイルだが、ファイルの文字コードが不明で、自動判定したい
  3. バイナリファイル

1番目のケースは、単純に、読み書きのときに変換すればよろし。読み込み、書き込みの型はString。

2番目、3番目のケースは、String は使えません。

minilang では, 型 (インターフェイス) が違うので、バイナリファイルを扱うIOと、テキストファイルを扱うTextReaderWriter の二つのクラスに分割しました。

File.open() がその引数に応じて、IO かTextReaderWriterのいずれかのオブジェクトを返します。

IOが軽くなる, という利点もあります。

ファイルを開くときに、文字コードを与えなかったとき

昔ならロケールにもとづく文字コードと見なすのがよかったでしょうが、現在では、文字コードが決め打ちのファイルも考えられるので、適切ではありません。

外部エンコードが指定されていないときは、バイナリファイルと見なすようにします。