L10n, つまり Localization をやる段階になった。
再度各ブラウザに用意されている l10n(あるいは i18n)のリンクを引いておく。
- Chrome: Internationalization (i18n)
http://code.google.com/chrome/extensions/i18n.html - Opera: Creating multilingual extensions
http://dev.opera.com/articles/view/creating-multilingual-extensions/ - Firefox: Localization
https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/tutorials/l10n.html
l10n といってもやることはいろいろあると思うけど、wasavi においては、wasavi からのメッセージをユーザが求める言語に変換した上で出力することとしよう。ソース上では、l10n の対象になりそうなメッセージの文字列リテラルは、すべて関数 _ の呼び出しにラップしてある。したがって、変換処理は _ の内部に閉じ込めればいい。
- Chrome
まず Chrome では、エクステンションのアーカイブ内に _locales/[localeCode]/messages.json を置く。messages.json はソース内のメッセージ ID とその各言語への変換結果を並べたデータベースだ。messages.json に納められているデータの参照は、API chrome.i18n.getMessage() によって動的に行うほかに、スタイルシートや html ファイルといった静的なファイルでも行われる(メッセージ ID ‘foo’ であれば、静的なファイル内で ‘__MSG_foo__’ と記述した箇所が自動的に変換される。へーおもしろい。
実際に使用することを考える。メッセージ ID として使用できる文字は、http://code.google.com/chrome/extensions/i18n-messages.html によると
- A-Z
- a-z
- 0-9
- _ (underscore)
- @
とのことだ。一方で、wasavi のソース内ではメッセージは例えば
var item = getItem(name);
if (!item) {
return _('Unknown option: {0}', name);
}
こんな感じになる。関数 _ に渡している引数がメッセージ。当然メッセージごとにユニークなので、そのまま ID になり得る。ただし空白や { といった記号を含んでいるので適当に変換して、例えば messages.json での記述は
{
"Unknown_option_0": {
message:"そんなオプションないんですけど: {0}"
}
}
のようになると思う。気になる点:
- いわゆる sprintf() 的な機能をどうするか。関数 _ 自身にも持たせているが、chrome.i18n.getMessage() にもパラメータをバインドする機能は存在する。どちらを使うか?
- 名詞の複数形をどう取り扱うかが Chrome の提供する仕組みでは示されていないような。どうするんだろう?
- Opera
Opera のエクステンションは w3c の規格のようなそうでもないような widgets に基づいていて、l10n の仕様も記述されている。Opera でも、やはりエクステンションのアーカイブ内の locales/[localeCode]/ に必要なものを置くのだが、Chrome ではそこはまさにメッセージカタログの置き場所であって他の何でもなかったのに対し、Opera ではなんとコンテンツ本体を置くようになっている。つまり、en_US 用の wasavi.js、ja_JP 用の wasavi.js、みたいに。少なくとも仕様書に書いてある例ではそういう使い方になっている。
それはそれで 1 つのソリューションだけど、うーん、ちょっと、ダメだな。Chrome のように言語ごとにことなるメッセージカタログだけを置いといて、javascript でそれを参照しつつ動的に変換するのが現実的な解ではないか。じゃないとエクステンションのアーカイブのサイズだけが膨れ上がるだけだ。
また、Chrome では __MSG_ 形式の自動変換は manifest.json も対象になる。なので、エクステンションをインストール際の説明なんかも l10n することができる。一方、Opera の config.xml では、config.xml 内で完結する l10n の仕組みはある(要素に lang 属性をつけて指定する)が、上記のフォルダベースの l10n とは分断されている。したがって、エクステンションのアーカイブをビルドする時点で特別な一手間(メッセージカタログ側のエクステンションの説明に対応するメッセージを取り出し、config.xml へ書き込むようなスクリプトを走らせる)が必要になる、かも知れない。
うーん、なんか、ダメな仕様だなこれ!
この、エクステンションのアーカイブ内のファイルを参照する際に現在のロケールによって locales/ 以下のファイルが優先的に使用されるという仕組み以外には、特に何か用意されていはいない。javascript の API などもない。
- Firefox (Add-on SDK)
Firefox では、l10n モジュールが用意されている。このモジュールは get() 関数を備えていて、これにメッセージ ID を渡すと変換後の文字列を返す。メッセージカタログは、locale/en_US.property といったファイルに置いておく。フォーマットは、その拡張子が示す通り。ただ……
The SDK supports basic localization of strings appearing in your main add-on’s JavaScript code. It doesn’t, yet, support localization of HTML, CSS, or content scripts.
おい!
ただ、リファレンスで紹介されている Language Plural Rules は面白くて、いろいろ参考になる。
現在 wasavi では、名詞の複数形が必要になる場合はメッセージを
_('Shifted {0} {line:0}.', rowCount);
のようにしている。{ ~ } で囲まれた数値は、パラメータのインデックス。これが、{[word]:index} の形式であった場合は、パラメータの数値によって word を変化させるようにしている。英語だとパラメータが 1 ならそのまま、それ以外なら後ろに s を付ける、みたいな。もちろん複数形への変化はもっと複雑怪奇なので、この辺もメッセージカタログを参照する方がいい。このときどう変化させるかは上記の Language Plural Rules に従うことにしよう。
まとめ
と、このように見てみると、最も実践的なのは Chrome のシステムに思える。Chrome のそれを基本としたい。
Opera と Firefox でも Chrome と同じ messages.json を使う。background でそれを読み込んでおき、wasavi 起動時に送出する。メッセージ ID から変換後のメッセージを取り出す部分は、wasavi.js 内で自前で行う。Firefox については組み込みの l10n モジュールは使用しない。