Railsアプリ開発の際、フロントをVue.jsで書いているのですが、
「i18nをフル活用し、ソースコードに日本語のベタ書きは限りなくゼロにしていこう!」
とした場合、
Rails側で使えるt('create')
のようなtメソッドを
「Vue.jsでも使いたい場合どうすればいいの」
となったので実装の記録を書き残します。
#前提
- macOS Mojave 10.14.6
- Ruby 2.5.5
- Rails 5.2.3
- Vue 2.6.10
- webpacker 4.0.7
※ ディレクトリ構成はフロントエンドとバックエンドが分離されているものではなく、Railsアプリケーション上にWebpackerを用いてVue.jsを使用可能な環境としています。予めご了承ください。
#gem「i18n-js」を導入
gem 'i18n-js'
$ bundle install
JavaScriptでI18nを利用する場合、config/locales/ja.yml
を直接使える訳ではなく、.js
に変換してそれを読み込んで利用します。
i18n-jsはその変換出力のためのgemです。
#i18n-js.ymlを作成
どのように変換出力するかのルールを設定するファイルであるi18n-js.yml
を作成します。
以下のコマンドでconfigディレクトリ
に新規作成されます。
rails generate i18n:js:config
translations:
- file: "app/javascript/packs/i18n/%{locale}.js"
prefix: "import I18n from './i18n';\n"
fileオプションで変換出力する場所を指定するパスと、言語別に出力されるよう設定します。
(公式などを見ると一般的に使われる設定ではtranslation.jsという1つのファイルに全ての言語の設定を詰め込むみたいですが、ファイルが重くなってしまうため今回は言語別にしています。)
webpackerを導入しているので、app/javascript/packs配下にi18nディレクトリを作成してその中で管理することとします。
さらにprefixオプションをつけると生成されるjsファイルの先頭に好きな文字列を追加できます。
付けた意味は後述します。
#許可するlocaleを限定
後述するrake i18n:js:export
をこのまま実行してしまうと、許可されている全ての言語のjsファイルが生成されてしまいます。(私の場合90個ぐらいでした)
日本で開発されている方であれば、日本語と英語だけで十分かと思いますので今回はその2言語のみ生成されるようにします。
# デフォルトのlocaleを日本語(:ja)にする
config.i18n.default_locale = :ja
# 許可するlocaleを日本語(:ja)と英語(:en)に限定する
config.i18n.available_locales = %i[ja en]
これでja.yml
はja.js
に、en.yml
はen.js
に変換出力されるようになります。
#rake i18n:js:exportで出力
gem「i18n-js」が用意するjs用localeファイルに変換するためのコマンドを実行します。
$ rake i18n:js:export
先ほどi18n-js.ymlファイルにfileオプションで指定したパスに対して出力されます
translations:
- file: "app/javascript/packs/i18n/%{locale}.js" # ←これ
prefix: "import I18n from './i18n';\n"
ja.js
とen.js
は確認できましたか?
import I18n from './i18n';
I18n.translations || (I18n.translations = {});
I18n.translations["ja"] = I18n.extend((I18n.translations["ja"] || {}), {"403_forbidden":"ページを表示できません", ......(中略)..... ,"zip":"郵便番号"});
中を見てみると
1行目でi18n-js.ymlファイルのprefixオプションで付け加えたものがあります。
3行目でRailsのi18nで使っているja.ymlファイルの中身が1行に凝縮されています。
最終的にtメソッド
はこの中からマッチするものを探してきます。
#i18n.jsを編集
同時にpublic/javascript/
にi18n.js
も出来ました。
機能の根幹となるメソッドがたくさん書いてあるファイルです。
この中からdefaultLocale
とlocale
を必要に応じて変更します。
railsのgemi18n
とは別物なので、別途定義が必要です。
日本語であれば"ja"
ですね。
#i18n.jsを移動
今編集したpublic/javascript/i18n.js
をapp/javascript/i18n/i18n.js
に移動してください。
rake i18n:js:export
コマンドを実行すると、毎回初期化されたi18n.jsがpublic/packs
に作成されてしまうため、中身を変更したi18n.jsは別の場所に移動しなければいけないのです。(本来そのような必要はないかもしれませんが解決策がわからずこのように運用しています。)
〜(2021/10/13 追記)〜
コメント欄にてi18n-jsが新たに作成されない方法を教えていただきました!
translations:
- file: "app/javascript/packs/i18n/%{locale}.js"
prefix: "import I18n from './i18n';\n"
export_i18n_js: false # ←これを追記
〜(追記終わり)〜
今回はja.jsたちと同じ場所に移動することで、管理しやすくします。
そうすることでprefixオプションでつけたja.jsの1行目の記述が活きてきます。
import I18n from './i18n';
I18n.translations || (I18n.translations = {});
I18n.translations["ja"] = I18n.extend((I18n.translations["ja"] || {}), {"403_forbidden":"ページを表示できません", ......(中略)..... ,"zip":"郵便番号"});
2行目からI18nというモジュールを呼び出そうとしていますが、importしていないものは呼び出すことができないので、先にimportさせているのです。
同じディレクトリに置いたのでパスも相対パスでOK。
webpackに%{locale}.jsを読み込ませる
方法は2通りあります。
1.エントリーポイントで%{locale}.jsファイルをimport
// 〜(中略)〜
import './i18n/ja'
webpackerのエントリーポイントで各localeごとのjsファイルをimportします。(enが必要な方はenもimportしてください)
webpackはimportしたファイルの中でさらにimportされているものは芋づる式にバンドル(まとめる)対象になるので、ここではメソッド本体であるi18n.js
はimportしなくて大丈夫です。
2.各Vueコンポーネント内でimport
<template>
</template>
<script>
import I18n from "../i18n/i18n";
import "../i18n/ja";
export default {
}
</script>
この方法はVueコンポーネントがを作る度に書き加えないといけないので面倒なのですが、
そもそもI18nを使いたいVueコンポーネントが全Vueファイルの中で一部しかない場合などはこの方法でいいでしょう。
先ほど1番では本体であるimport I18n from "../i18n/i18n";
は呼び出す必要はないと言いましたが、
後述する**おまけ2:script内でもI18nを使いたい場合は?**の中で
'I18n' is not defined
とLinterに怒られてしまう可能性があるため、便宜的に本体も呼び出しています。
(※2通りの方法を紹介しましたが、以下こちらの2番の方法で記述を進めていきます)
#consoleでI18nコマンドが使えるか確認
現時点でI18nが使えるかどうか、
rails serverを立ち上げてローカルでRailsアプリにアクセスします。
(2番の方法の場合、I18nをimportしてある.vueファイルが使われているページである必要があります。)
chromeのデベロッパーツールで画像のように打ち込んで確認してみましょう。
2つ目はご自身のアプリで定義してあるものを適当に呼び出してみてくださいね。
#vueファイルでI18nコマンドを省略するためのmixinを作成
今のままではI18n.t('hoge')
のように毎回先頭にI18n
を付けないと呼び出せないので面倒です。
そこでmixin
を作成し、t('hoge')
と省略形で呼び出せるようにします。
export const i18n = {
methods: {
t: (...args) => I18n.t(...args),
currentLocale: () => I18n.currentLocale()
}
};
#コンポーネントでmixinをimport
<template>
</template>
<script>
import I18n from "../i18n/i18n";
import "../i18n/ja";
import { i18n } from "../mixins/i18n";
export default {
mixins: [i18n]
}
</script>
これでvueファイル内で呼び出すt('hoge')
はI18n.t('hoge')
であると理解してくれます。
#vueファイルでtメソッドが使えるか確認
<template>
<p>{{ t('create') }}</p>
</template>
<script>
import I18n from "../i18n/i18n";
import "../i18n/ja";
import { i18n } from "../mixins/i18n";
export default {
mixins: [i18n]
}
</script>
上記vueファイルが使われているページにアクセスし、
I18nで定義された日本語(今回の場合は「登録」)が表示されます。
以上です。お疲れ様でした!
#おまけ1:Rails側のi18nのyamlファイルを修正した場合は?
$ rake i18n:js:export
修正を加える都度変換してください。
#おまけ2:script内でもI18nを使いたい場合は?
mixinはtemplate
内でt('hoge')
を使うことを可能にするのですが、
script
の中では省略ができません。
<template>
<p>{{ title }}</p>
</template>
<script>
import I18n from "../i18n/i18n"; // ←敢えて読み込んでいます
import "../i18n/ja";
import { i18n } from "../../mixins/i18n";
export default {
mixins: [i18n],
data() {
return {
title: I18n.t("title") // ←ここで使うために
};
}
}
</script>
#おまけ3:引数の渡し方は?
{}
(中括弧)で囲みます
ja:
mypage: "マイページ"
msg:
update_successful: "%{v}を更新しました。"
vueファイルなら
<template>
<p>{{ t('msg.update_successful', { v: t('mypage') }) }}</p>
<p>{{ message }}</p>
// どちらも出力結果は同じ
</template>
<script>
import { i18n } from "../mixins/i18n";
export default {
mixins: [i18n],
data() {
return {
message: I18n.t('msg.update_successful', { v: I18n.t('mypage') })
};
}
}
</script>
#参考記事
・i18n-js 公式ドキュメント
・[stackoverflow]単一のリポジトリでロケールを共有するRails + Vue.jsインスタンスにI18nがありますか?
・[Qiita]i18n-jsで必要な言語のみロードしてパフォーマンスUP
・[Qiita]react_railsとi18n-jsをwebpackerで動かす