Intlにおけるロケール識別子のマッチング
この記事では Intl.Segmenter
のような Intl
の下に存在しているコンストラクタの中で Intl.Locale
を除くものを「Intl
のコンストラクタ」と呼ぶ。
Intl のコンストラクタとロケール
Intl
のコンストラクタは、ロケールを表す文字列か Intl.Locale
オブジェクト(あるいはそれらのみを含む配列)を受け取る。これは省略することもでき(つまり undefined
を受けることができる)、その場合は規定のロケールが使われる。
const s1 = new Intl.Segmenter("en-US");
const s2 = new Intl.Segmenter(new Intl.Locale("en-US"));
const s3 = new Intl.Segmenter(["en-US", new Intl.Locale("en-US")]);
const s4 = new Intl.Segmenter();
Intl
のコンストラクタに、ロケール識別子として不正な文字列や Intl.Locale
ではないオブジェクトを渡すと RangeError を throw する。
try {
new Intl.Segmenter("invalid-locale-id");
} catch (e) {
console.log(e.name); // RangeError
}
try {
new Intl.Segmenter({});
} catch (e) {
console.log(e.name); // RangeError
}
ロケールのマッチング
Intl
のコンストラクタは受け取ったロケール情報が不正でないことを確認したら、受け取ったロケール情報とその環境で使えるロケールの情報をマッチングし、適切なロケールを選択する処理を行う。
このときに行われるロケールのマッチングのアルゴリズムを Intl
のコンストラクタの localeMatcher
オプションで指定できる。localeMatcher
オプションは "lookup"
もしくは "best fit"
という文字列を受け取る。"lookup"
と "best fit"
以外の値を受け取ったら RangeError を throw する。省略した場合は "best fit"
として扱われる。
const s1 = new Intl.Segmenter("en-US", { localeMatcher: "lookup" });
const s2 = new Intl.Segmenter("en-US", { localeMatcher: "bestfit" });
try {
new Intl.Segmenter("en-US", { localeMatcher: "invalid-matcher" });
} catch (e) {
console.log(e.name); // RangeError
}
Intl
のコンストラクタに配列を渡した場合はその中でもっともマッチしたものを一つだけ選ぶ。
仕様で動作が定められている lookup
localeMatcher
の値が "lookup"
のときは、ECMA402 の LookupMatchingLocaleByPrefix という abstract operation で規定されたアルゴリズムによってマッチングを行う。
具体的には以下の手順に従う:
- まず、利用可能なロケール情報の一覧
availableLocales
と、コンストラクタの引数として受け取ったrequestedLocales
の2つの値が存在する requestedLocales
を一つずつ見ていく(配列ではなく単体の文字列やオブジェクトを渡した場合は要素数が1の配列ようのように扱う)requestedLocales
の各ロケール情報に含まれるUnicode locale extension sequenceの部分と、拡張を含まないロケール識別子の部分を分ける[1]- 分けたあとのロケール識別子が
availableLocales
に含まれていたら、それと拡張の部分をくっつけて返す - ロケール識別子が
availableLocales
に含まれていなかった場合、ロケール識別子から一つサブタグを取り除いて、step 4 を繰り返す(たとえばen-US-California
がavailableLocales
に含まれていなかったら、-California
の部分を取り除いてen-US
がavailableLocales
に含まれているかを再度チェックする)。
詳細な動作は ECMA402 を参照してほしい。
実装依存の best fit
一方で localeMatcher
の値が "best fit"
のときにどのようにマッチングを行うかは実装依存である。仕様上は LookupMatchingLocaleByBestFit という abstract operation が呼び出されているが、冒頭で implementation-defined であることが明記されている。
JavaScriptCore[2]とSpiderMonkey[3]は、"lookup"
のときと全く同じ動きをする。これはあくまで暫定的にこうなっているだけで、より良い実装があればそちらが採用されるだろう。
V8でも "lookup"
と同じ動きをするが、harmony_intl_best_fit_matcher
フラグを付けると ICU 67.1 から実装されているicu::LocaleMatcher
が使われるようになる[4]。これは、言語的な近さや歴史的な背景を考慮したアルゴリズムらしい。たとえば en-AU
(オーストラリア英語) は en-US
(アメリカ英語)より en-GB
(イギリス英語)に近いものだと判定されるようだ。
Unicode locale extension sequence は簡単に言えば
en-US-u-ca-buddhist
のu-ca-buddhist
の部分のことだ。ca
以外にもcf
やfw
などたくさんの種類がある。詳細は UTS#35 を参照してほしい。 ↩︎https://github.com/WebKit/WebKit/blob/c994460e362bfdfa5706e5fb55b79eef38f51527/Source/JavaScriptCore/runtime/IntlObject.cpp#L928-L935 ↩︎
https://searchfox.org/mozilla-central/rev/dea459eb01a4c38b696ae3d31c1540e86365a937/js/src/builtin/intl/CommonFunctions.js#314-326 ↩︎