i18n-js を importmap-rails で使う

## はじめに
Rails アプリで使用される [i18n](https://github.com/ruby-i18n/i18n) の JavaScript 版のライブラリとして [i18n-js](https://github.com/fnando/i18n-js) があります。
自分の Rails プロジェクトで Webpacker -> importmap-rails の移行をするにあたって、 i18n-js の移行にかなり試行錯誤したので、自分の使い方を記載しようと思います。

Webpacker -> importmap-rails の移行については別記事を書きました。

https://qiita.com/mishina2228/items/d4b9af22d0096ee451d7

## 前提
元々は、i18n-js のREADME の [Rails with webpacker](https://github.com/fnando/i18n-js/tree/v3#rails-with-webpacker) のセクションにある [gist のリンク](https://gist.github.com/bazzel/ecdff4718962e57c2d5569cf01d332fe)
を参考に使用していました。変更前のコードを一部分だけ記載しておきます。

```ruby:Gemfile
gem 'i18n-js'
```

```js:app/packs/src/i18n.js.erb
import I18n from 'i18n-js'
I18n.translations = <%= I18n::JS.filtered_translations.to_json %>
export default I18n
```

```js:app/packs/src/foo.js
import I18n from './i18n.js.erb'

console.log(I18n.t('helpers.notice.processing'))
```

importmap-rails では `js.erb` のようなサーバ側でビルドが必要な JavaScript ファイルを扱えないので、これを使わない方法で対応します。

## 手順
`config/application.rb` に以下を追記

```ruby:config/application.rb
module YourApp
  class Application < Rails::Application

  ...

  config.middleware.use I18n::JS::Middleware # ここを追加
  end
end
```

`config/i18n-js.yml` を作成

```yml:config/i18n-js.yml
translations:
  - file: "app/javascript/i18n-js/translations.js"
    prefix: "import I18n from 'i18n-js'\n"
    suffix: "export default I18n\n"
```

`bin/importmap pin i18n-js` を実行して `config/importmap.rb` に `i18n-js` を追記する

```ruby:config/importmap.rb
pin "i18n-js", to: "https://ga.jspm.io/npm:i18n-js@3.8.0/app/assets/javascripts/i18n.js"
```

`config/importmap.rb` に以下を追記

```ruby:config/importmap.rb
pin_all_from "app/javascript/i18n-js", under: "i18n-js"
```

`app/packs/src/i18n.js.erb` を削除し、呼び出し箇所も変更

```js:app/packs/src/foo.js
import I18n from 'i18n-js/translations'

console.log(I18n.t('helpers.notice.processing'))
```

## 解説

### `config/application.rb` 

`config.middleware.use I18n::JS::Middleware` によって、Rails サーバを起動中に、ページ更新などリクエストを行うタイミングで `translations.js` ファイルが自動的に生成されます。これは開発環境で役立つ機能で、 `config/locales` 配下の i18n ファイルを編集したあと、ページリロードするだけで、 i18n-js 側に反映してくれます。
これ以外に `translations.js` ファイルを生成する rake タスクが用意されており、`rake i18n:js:export` で生成されます。本番環境にデプロイする際などはこのタスクを使います。

### `config/i18n-js.yml`

```yml:config/i18n-js.yml
translations:
  - file: "app/javascript/i18n-js/translations.js"
    prefix: "import I18n from 'i18n-js'\n"
    suffix: "export default I18n\n"
```
`file` に指定したパスに変換した翻訳ファイルが出力されます。
`prefix`, `suffix` は [i18n-js の README](https://github.com/fnando/i18n-js/tree/v3#adding-prefix--suffix-to-the-translations-files) に説明がありますが、生成される `translations.js` の最初と最後に文字を差し込むことができます。
この設定から、次のようなファイルが生成されます。

```js:translations.js
import I18n from 'i18n-js'
I18n.translations || (I18n.translations = {});
I18n.translations["en"] = I18n.extend((I18n.translations["en"] || {}), JSON.parse('{"activemodel":{"attributes":{
...
'));
export default I18n
```

これで `import I18n from 'i18n-js/translations'` すれば `I18n.t()` でロケールごとに翻訳済みの文章が取得できます。
また、この例では全てのロケールを1つのファイルに出力していますが、ロケールごとに別のファイルに分割することも可能です。そちらについては i18n-js の GitHub レポジトリの README に記載があるのでそちらを参照ください。

https://github.com/fnando/i18n-js/tree/v3

### `config/importmap.rb` 

生成したファイルは `app/javascript/i18n-js/translations.js` なので、そのファイルを含むディレクトリ以下を読み込むようにします。

```ruby:config/importmap.rb
pin_all_from "app/javascript/i18n-js", under: "i18n-js"
```

ここが割と重要なのですが、ディレクトリ名を `i18n-js` としています。最初 `i18n` としたのですが、その場合 `import I18n from 'i18n/translations'` によって読み込むことができませんでした。しばらくハマったのですが、原因は i18n-js gem が全く同じパスで asset pipeline にファイルを配置しているため、そちらが読み込まれてしまっている、というものでした。
(ちなみにそっちのファイルの中身はこれ)

```js:i18n-js が配置している translations.js
//= require i18n/shims
//= require i18n
//= require i18n/filtered
```

なので `i18n` 以外のディレクトリにしましょう。

ここまでの対応で、各 JavaScript ファイルから `I18n.t()` が使えるようになったはずです。

## おまけ
`i18n-js/translations.js` ファイルは `config/locales` 以下のファイルから自動生成されるものなので、 git 管理配下から外しておいた方が良いでしょう。

1. `i18n-js` ディレクトリに `.keep` を作成
2. `.gitignore` に以下を追記

```:.gitignore
/app/javascript/i18n-js/*
!/app/javascript/i18n-js/.keep
```

本番環境にデプロイする際などは、`rake i18n:js:export` を実行して `translations.js` を生成しますが、毎回手で打つのは億劫なので、自分は `assets:precompile` タスクの直前に実行されるようにしました。

```ruby:lib/tasks/i18n.rake
Rake::Task['assets:precompile'].enhance ['i18n:js:export']
```

## 参考記事
- [【i18n-js】Railsアプリのi18n(tメソッド)をVue.jsでも使用する](https://qiita.com/yosaprog/items/3b2c509cf5de94f89cdb)