react_railsとi18n-jsをwebpackerで動かす

# はじめに
先日、Railsのerbテンプレートのページに必要な部分だけ[react_rails](https://github.com/reactjs/react-rails)を使って、react化する機会がありました。

asset_pipelineを使用しないでwebpackerだけで`i18n-js`というライブラリを使用したかったのですが、案外まとまっていなかったのでまとめます。

少しでも、これからこの実装を行う人の助けになれば嬉しいです。また、この部分に関して、それほど知識があるわけではないので、フィードバックお待ちしております！

実際の動きはこんな感じになります。一番上の曜日の部分がreact_componentによるもの、仕様言語はという部分はRailsのerbによるものとなっております。
![](https://gyazo.com/c1f536204cc5b060c6ca17d740a27871.gif)

# 今回の環境(2019/05/09時点)
* Ruby 2.6.2
* Rails5.2.3
* Yarn1.6.0
* MySQL 5.7.25

翻訳で使用する言語は`日本語`と`英語`

といった構成になっています。

# 手順
## Rails new
rails new をします。

```shell:terminal
$ rails new i18n-sandbox -d mysql 
```

## 必要なGemの追加
Gemfileに `webpacker`,`i18n-js` を追加します。

```ruby:Gemfile
gem 'webpacker'
gem 'react-rails'
gem 'i18n-js'
```

## react_componentの作成
bundle installをして、react_railsの設定を行います。その後、言語切り替えを試すためのサンプルのコンポーネントを作成します。

```shell:terminal
$ bundle install
$ rails webpacker:install 
$ rails webpacker:install:react
$ rails g react:install 
$ rails g react:component HelloWorld greeting:string
```
これを行うと、次のようなファイルができます。

```javascript:app/javascript/components/HelloWorld.js
import React from "react"
import PropTypes from "prop-types"
class HelloWorld extends React.Component {
  render () {
    return (
      <React.Fragment>
        Greating: {this.props.greating}
      </React.Fragment>
    );
  }
}

HelloWorld.propTypes = {
  greating: PropTypes.string
};
export default HelloWorld
```

## React componentを表示
適当にコントローラとviewファイルを作成し、rootを設定します。

```shell:terminal
$ rails g controller posts
$ touch app/views/posts/index.html.erb
```

作成したviewファイルから、先ほどのreact componentを呼び出します。
```ruby:app/views/posts/index.html.erb
<%= react_component("HelloWorld", { greeting: "Hello" }) %>
```

ルーティングを設定します。

```ruby:config/routes.rb
root 'posts#index'
```

application.html.erbにwebpackerによってコンパイルされたJavascriptファイルを読み込むような設定を追加します。
```ruby:app/views/layouts/application.html.erb
<%= javascript_pack_tag 'application' %>
```

## サーバーの立ち上げ

```shell:terminal
$ rails s
$ ./bin/webpack-dev-server
```

これらを立ち上げて、 localhost:3000にアクセスすると、`Hello` の表示が確認できるはずです。


## i18n-js をwebpackerで動かす設定
さて、ここから、i18n-jsをwebpacker上で動かす設定をしていきます。[このGist](https://gist.github.com/bazzel/ecdff4718962e57c2d5569cf01d332fe)がとても参考になりました。

```shell:terminal
$ yarn add i18n-js
$ rails webpacker:install:erb
$ mkdir -p app/javascript/src/i18n-js
$ touch app/javascript/src/i18n-js/index.js.erb
```

上で作ったindex.js.erbファイルを次のように編集します。
コメントアウトの部分は、RailsのI18nと同様にconfig/locales/に配置した翻訳ファイルを参照するために追加しています。[i18n-js/issue514](https://github.com/fnando/i18n-js/issues/514)が参考になりました。

```javascript:app/javascript/src/i18n-js/index.js.erb
import I18n from "i18n-js"
/* rails-erb-loader-dependencies ./../config/locales/ */
I18n.translations = <%= I18n::JS.filtered_translations.to_json %>;
export default I18n
```

これで、react_component側で`I18n.t('翻訳ファイルのキー')`と呼び出すことができるようになりました。

この後、RailsのI18nとの連携をしていきます。

## I18nの設定(Rails側)
### defaultのlocaleと使用するlocaleを指定する
今回のサンプルでは、日本語と英語のみを使用し、基本の言語は、日本語という指定があったため、その設定をします。config/application.rbに次のように編集します。

```ruby:config/application.rb
require_relative 'boot'

require 'rails/all'

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)
module I18nSandbox
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 5.2

    # I18n　`追記部分`
    config.i18n.default_locale = :ja
    config.i18n.available_locales = %i(ja en)

    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration can go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded after loading
    # the framework and any gems in your application.
  end
end
```

本当に必要な言語のみが使用できるようになったか不安な方は、コンソールを立ち上げて確認することができます。

```ruby:rails_console
$ rails c 
Running via Spring preloader in process 92876
Loading development environment (Rails 5.2.3)
irb(main):001:0> I18n.default_locale
=> :ja
irb(main):002:0> I18n.available_locales
=> [:ja, :en]
```

## 言語の切り替え
[RailsGuides](https://railsguides.jp/i18n.html#url-params%E3%82%92%E5%85%83%E3%81%AB%E3%83%AD%E3%82%B1%E3%83%BC%E3%83%AB%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B)を参考にして、localeごとのURLを設定します。

```ruby:config/routes.rb
  scope '(:locale)', locale: /#{I18n.available_locales.map(&:to_s).join('|')}/ do
    root 'posts#index'
  end
```
この記述によって、`http://localhost:3000/ja/posts`と`http://localhost:3000/en/posts`というURLが生成され、URL単位で言語を管理することが可能になります。

コントローラで言語を切り替えるロジックを追加する。

```ruby:app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :set_locale

  def set_locale
    locale_param = Rails.configuration.i18n.available_locales&.include?(params[:locale].to_s.to_sym) && params[:locale]
    I18n.locale = locale_param || cookies[:locale] || I18n.default_locale
    cookies[:locale] = I18n.locale.to_s
    @locale_path = I18n.locale==I18n.default_locale ? nil : I18n.locale
end
```

実際に言語を切り替えるボタンを追加する

```ruby:app/views/posts/index.html.erb
    <% I18n.available_locales.reject{|l| l == I18n.locale.to_sym}.each do |locale| %>
      <p>使用言語は<%= I18n.locale %>です</p>
      <%= link_to "#{locale}に切り替える", url_for(params.permit().merge(locale: (locale == :ja ? :ja : locale))) %>
    <% end %>
```

## 翻訳ファイルを用意する
[ここ](https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/ja.yml)から、ja.ymlをダウンロードしてきて、`config/locales/ja.yml`に追記します。

実際にreact_componentにlocaleを渡して完成です。

```javascript:app/javascript/components/HelloWorld.js
import React from "react"
import PropTypes from "prop-types"
import I18n from 'src/i18n-js/index.js.erb'
class HelloWorld extends React.Component {
  constructor(props) {
    super(props);
    I18n.locale = this.props.locale;
  }
  render () {

    console.log(I18n.t('date.abbr_day_names'));
    return (
      <React.Fragment>
        <p>{I18n.t('date.abbr_day_names')}</p>
        Greating: {this.props.greating}
      </React.Fragment>
    );
  }
}

HelloWorld.propTypes = {
  greating: PropTypes.string
};
export default HelloWorld
```

```ruby:app/views/posts/index.html.erb
<%= react_component("HelloWorld", { locale: I18n.locale, greeting: "Hello" }) %>
```

# おわりに
i18n-jsをasset_pipelineに乗っ取らないでwebpackerだけで実装するのにはかなり苦労しました。そんな中、本文中の[このGist](https://gist.github.com/bazzel/ecdff4718962e57c2d5569cf01d332fe)と[i18n-js/issues514](https://github.com/fnando/i18n-js/issues/514)にはかなり助けられました。自分より前に詰まっていた先人たちに感謝をしつつ、この文章が今後誰かの助けになることを願っております。