WebExtensionsによるFirefox用の拡張機能で組み込みのページのローカライズを容易にするライブラリ：l10n.js

（この記事は、Firefoxの従来型アドオン（XULアドオン）の開発経験がある人向けに、WebExtensionsでの拡張機能開発でのノウハウを紹介する物で、[所属会社のブログの2018年7月10日の記事](http://www.clear-code.com/blog/2018/7/10.html)の再掲です。）

XULアドオンでは、表示文字列のローカライズには「DTDファイルで`<!ENTITY menu.open.label "開く">`といった形式でロケールを定義し、XULファイルの中に`<label value="&menu.open.label;">`のように書いておくと自動的に適切なロケールの内容に置き換わる」「propertiesファイルで`menuLabelOpen=開く`といった形式でロケールを定義し、JavaScriptから`stringbundle.getString('menuLabelOpen')`といった形で参照する（String Bundle）」という2つの方法がありました。

[WebExtensionsの国際化対応の仕組み](https://developer.mozilla.org/ja/Add-ons/WebExtensions/Internationalization)はそれよりももっと単純です。ロケールはJSON形式のみで定義し、[`browser.i18n.getMessage()`](https://developer.mozilla.org/ja/Add-ons/WebExtensions/API/i18n/getMessage)でキーを指定すると適切な言語のロケールの内容が文字列として取得できるという物で、XULアドオンでのString Bundleに近い形式です。

この方法はテンプレートエンジンやVirtual DOMなどでUIを構築する場合は特に支障にならないのですが、静的なHTMLファイルで設定画面のページなどを作成する場合には、「参照されるべきロケールのキーを書いておくだけで表示時に適切なロケールの内容が反映される」というXULファイルで使っていた方法のような仕組みが欲しくなる所です。実際、`manifest.json`の中では`__MSG_menuLabelOpen__`のように書くだけでFirefoxが表示時に自動的に適切なロケールの内容を反映してくれるので、これと同じ事がHTMLファイルではできないというのはもどかしいです。

そこで、静的なHTMLファイルの中にロケールを埋め込む使い方を可能する軽量ライブラリとして、[`l10n.js`](https://github.com/piroor/webextensions-lib-l10n)という物を開発しました[^l10n]。

[^l10n]: 「l10n」は「localization（ローカライズ、地域化）」の略としてよく使われる、先頭と末尾の文字、およびその間の文字数を組み合わせた表現です。ちなみに、同様に「i18n」は「internationalization（国際化）」の略です。

# 基本的な使い方

## 読み込みと初期化

このライブラリを使う事自体には、特別な権限は必要ありません。最も単純な使い方では、国際化するページからファイルを読み込むだけで機能します。

```html
<script type="application/javascript" src="path/to/l10n.js"></script>
```

このライブラリを使う時は、HTMLのページ中のテキストや属性値に`__MSG_（ロケールのキー）__`と書いておきます。例えば以下の要領です。

```html
<p title="__MSG_config_enabled_tooltip__">
  <label><input type="checkbox">
         __MSG_config_enabled_label__</label></p>
<p title="__MSG_config_advanced_tooltip__">
  <label><input type="checkbox">
         __MSG_config_advanced__</label></p>
<p title="__MSG_config_attributes_tooltip__">
  <label>__MSG_config_attributes_label_before__
         <input type="text">
         __MSG_config_attributes_label_after__</label></p>
```

これだけで、ページの読み込み時に自動的に各部分が対応するロケールの内容で置き換わります。

![英語での表示](https://qiita-image-store.s3.amazonaws.com/0/61387/1ba42b42-1c38-fd5e-cc5c-0704749799f9.png)
![日本語での表示](https://qiita-image-store.s3.amazonaws.com/0/61387/9229c907-42ca-def2-d579-c672674b71c4.png)

## 任意のタイミングでの反映

このライブラリは、動的に挿入されたDOM要素の内容テキストや属性値に対しては作用しません。ページの読み込み完了後に追加された内容に対してもロケールの反映を行いたい場合は、それらがDOMツリー内に組み込まれた後のタイミングで`l10n.updateDocument()`を実行して下さい。例えば以下の要領です。

```javascript
var fragment = range.createContextualFragment(`
  <p>__MSG_errorDescription__
     <label><input type="checkbox">__MSG_errorCheckLabel__</label></p>
`);
document.body.appendChild(fragment);
l10n.updateDocument();
```

## 明示的に空文字列を使う場合の注意点

「See（リンク）」と「（リンク）を参照して下さい」のように、言語によって要素の前や後に何もテキストを設けない方が自然になる場合があります。このようなケースでは、あらかじめ要素の前後にテキストを埋め込めるようにしておき、言語によってその内容を変えるというやり方が使われる事があります。

```html
<p>__MSG_before_link__
   <a href="...">__MSG_link_text__</a>
   __MSG_after_link__</p>
```

```javascript
{ // 英語
  { "before_link": { "message": "For more details, see " } },
  { "link_text":   { "message": "the API document." } },
  { "after_link":  { "message": "" } },
}

{ // 日本語
  { "before_link": { "message": "" } },
  { "link_text":   { "message": "APIドキュメント" } },
  { "after_link":  { "message": "に詳しい情報があります。" } }
}
```

XULではこのような場合空文字列は空文字列として埋め込まれていましたが、`l10n.js`では対応するロケールが空だった場合は`__MSG_after_link__`という参照のための文字列がそのままUI上に残ります。これは、`browser.i18n`において「参照したロケールが未定義だった場合」と「参照したロケールの値が明示的に空文字に設定されていた場合」を区別できないことから、ミスの検出を容易にするために敢えてそのような仕様としているためです。

このようなケースでは、明示的に空にしたい部分には`\u200b`（ゼロ幅スペース）と書いて下さい。上記の例であれば、訂正後は以下のようになります。

```javascript
{ // 英語
  { "before_link": { "message": "For more details, see " } },
  { "link_text":   { "message": "the API document." } },
  { "after_link":  { "message": "\u200b" } },
}

{ // 日本語
  { "before_link": { "message": "\u200b" } },
  { "link_text":   { "message": "APIドキュメント" } },
  { "after_link":  { "message": "に詳しい情報があります。" } }
}
```



# まとめ

以上、Firefox用のWebExtensionsベースのアドオンにおける静的なHTMLファイルのローカライズを容易にするライブラリである`l10n.js`の使い方を解説しました。

XULアドオンでの感覚に近い開発を支援する軽量ライブラリは他にもいくつかあります。以下の解説も併せてご覧下さい。

 * [設定画面の構築を支援する`Options.js`](https://qiita.com/drafts/9aff1161d5307b57a5de)
 * [設定の読み書きを簡単にする`Configs.js`](https://qiita.com/piroor/items/a56946c327dcc93a8ce4)
 * [キーボードショートカットの変更用UIを提供する`ShortcutCustomizeUIMenuUI.js`](https://qiita.com/piroor/items/a9f0a1bb5cebb7f3ad42)
 * [メニューやポップアップパネル風のUIを提供する`MenuUI.js`](https://qiita.com/piroor/items/b7a3a983ba4bdd22bdb9)
 * [「以後確認しない」というチェックボックス付きの確認ダイアログ風UIを提供する`RichConfirm.js`](https://qiita.com/piroor/items/588fcb9ef94f913a849d)