Javascriptの多言語対応について考えてみます。基本的には語り尽くされている気はしますが、自分好みの実装を説明してみたいと思います。
ちなみにここで説明している方法は、自前のライブラリに実装している方法です。
JavaScriptで多言語
単純に考えればメッセージを全てリテラルとして定義して切り替えれば良いわけです(切り替える方法は後述)。
英語なら
_M_ = {}
_M_.Hellow = 'Hellow, World!!";
alert(_M_.Hellow);
日本語なら
_M_ = {}
_M_.Hellow = "世界よこんにちは!!";
alert(_M_.Hellow);
ただし、このやりかたはメッセージを全てリテラルにしてやる必要があって、アドホックに作業を進めたいアマチュアな自分向きの方法ではありません。
その次の選択肢はせっかくJavaScriptの配列が連想配列なので、IDやキーワードで配列を参照する方法です。
_M_ = [];
_M_['ja']['Hellow, World!!'] = "世界よこんにちは!!";
_locale_ = 'ja'; //言語を設定しておきます(ページの言語取得方法は後述)
function gettext( text) {
return _M_[_locale_] && _M_[_locale_][text] || text;
}
alert(gettext("Hellow, World!!"));
getttextをエイリアスするとちょっと良い感じ(?)になります。
_ = gettext;
alert(_("Hellow, World!!");
後は、連想配列をIDで引く場合です。IDを指定した場合、対応するリソースが見つからなかった場合のデフォルトテキストを指定することにします。
function gettext( uid, text) {
if (text) {
return _M_[_locale_] && _M_[_locale_][uid] || text;
} else {
return _M_[_locale_] && _M_[_locale_][uid] || uid;
}
}
変数との組み合わせ
例えば表示したいテキストが「from + "を、" + to + "へコピーします。"」の様に変数を含んでいる場合、どうするのでしょうか。
C#なら以下の様に{0}等を含む書式付きの文字列にしてしまいます。基本的にはJavascriptでも同じアプローチをとりますが、Javascriptにはformatに相当する関数がありませんので、これも別途実装します。
string.format(gettext("copy from {0} to {1}"), from, to) ;
format関数は、ググればいっぱいコード出てきますが基本的に文字列置換を使用して実装しているようです。
format: function (fmt) {
if (!arguments.length) {
throw new Error("format:引数足りない");
}
if (arguments.length == 1) {
return fmt; //無変換
}
var res = arguments[0];
for (var i = 1; i < arguments.length; i++) {
var reg = new RegExp("\\{" + (i - 1) + "\\}", "g")
if (arguments[i] == null) {
res = res.replace(reg, "null");
}
else {
try {
res = res.replace(reg, arguments[i].toString());
} catch (e) {
res = res.replace(reg, "[object]");
}
}
}
return res;
}
変数を含むコードの例を示します。
_M_ = [];
_M_['ja']['copy from {0} to {1}'] = "{0} を {1} へコピーします。";
_locale_ = 'ja';
alert(format(_("copy from {0} to {1}"), from, to));
ロケールの取得
ここでは一つの方法だけを説明しておきます(自分の用途にはこれで十分なので・・・)。
表示するページのxml:lang属性を取得します。明示的に'xml:lang'を指定していなければnullが返されます。
_locale_ = document.getElementsByTagName('html')[0]
.getAttribute('xml:lang') || 'ja';
メッセージテキストの読み込み
それから、各言語用のメッセージテキストの読み込みですが、テキスト量が少なければ各言語のテキストを全て読み込んでおいても問題ありません(競合するわけでは無いので)。
が、データ量を絞り込むならロケールを特定してからメッセージファイルを読み込む方法があります。
ファイルはスクリプトファイルでもJSONでも良いでしょうね。
スクリプトファイルならscriptタグをDOMに追加で、JSONならXMLHttpRequestを使用した非同期受信を使用してバックグラウンドで読み込んでおきます。
var s = document.createElement('script');
s.src = '/message/' + _locale_ + '/message.js';
s.type = 'text/javascript';
s.defer = 'defer';
document.getElementsByTagName('head').item(0).appendChild(s);
XMLHttpRequestの例は、面倒なのでjQueryで記述しておきます。
$.get('/message/' + _locale_ + '/message.json', "", function (data) {
eval('var s=' + data);
_M_[_locale_] = _M_[_locale_] || [];
for (var i = 0; i < s.length; i++) {
_M_[_locale_][s[i].uid] = s[i].message;
}
});
補足
チューニング
全体に冗長な処理に感じますが、正直なコードを書いて遅くて且つ必要があればチューニングすれば良いと思っています。必要ならカリカリのコードに適宜変更してください。
ライブラリとの対応
上記の各キーワードをライブラリ内の各キーワードへマップしておきます。
| この記事 | ライブラリ |
|---|---|
| gettext() | DQ.gettext() |
| _M_ | DQ._message |
| _locale_ | DQ._locale |
| format() | DQ.format() |
| スクリプトの遅延ロード | DQ.lazyLoad() |