Rails で config/locales/xxx/ja.yml を自動生成する gem を作った
リポジトリ
RubyGems
github
概要
gem 'i18n_factory', group: :development
でインストールすると、
$ bin/rails g model Post title content:text
を実行した際に、翻訳ファイルの雛形
ja:
activerecord:
models:
post: Post
attributes:
post:
title: Title
content: Content
を作ります。
また、既にDBにテーブルが存在する場合でも
$ bin/rails g i18n_factory:update Post
or
$ bin/railg g i18n_factory:update_all
を実行すると上記ファイルを作成します。
既にファイルが存在する場合は、追加した列のみ追記して元の定義は残すようにしています。(後述)
背景
Rails を触っていると、日本語のみに対応するシステムであっても翻訳ファイルを定義していると嬉しいことが多々あります。
- エラーメッセージを翻訳ファイルから生成できる
- 管理画面に ActiveAdmin を使用していると翻訳ファイルから自動的に画面名、画面項目名を生成してくれる
- ViewファイルやModelファイルから日本語を排除できて翻訳ファイルで一元管理できる
- DB設計書等を確認しなくても翻訳ファイルを見れば列が確認できる
翻訳ファイルで定義した値は下記で確認できます。(例: Post モデルの場合)
ja:
activerecord:
models:
post: 投稿
attributes:
post:
title: タイトル
content: 内容
Post.model_name.human # => '投稿'
Post.human_attribute_name :title # => 'タイトル'
そのため、自分が関わるプロジェクトは基本的に翻訳ファイルを作成して、
config/locales/<モデル名>/ja.yml
という形でモデルごとに1ファイルずつ定義しているのですが、
雛形を作成するのがだるいなと思ったので勉強のため gem にしてみました。
gem の作成方法について
gem を作成すること自体が初めてだったのでやり方がわかりませんでしたが、いろいろググると情報があって助かりました。
下記の手順で作成できます。
1. gem の雛形の作成
$ bundle gem ここにgemの名前
2. gemspec でTODOになっている箇所の修正
3. 開発
実際に動くかどうか試したかったので、既存のRailsプロジェクトの lib/i18n_factory
ディレクトリに上記で作成されたファイルを置いて、
gem 'i18n_factory', path: 'lib/i18n_factory'
でインストールして動作確認していました。
4. rubygems にアカウント登録
5. ビルド→デプロイ
ビルド
$ gem build xxx.gemspec
すると、xxx-0.1.0.gem
が作成されるのでそれをプッシュします。
$ gem signin
$ gem push xxx-0.1.0.gem
自分の場合はビルドを docker 上で行ない、できたファイルをホスト上でプッシュしました。
$ rails g model
にフックさせる
$ rails g model
で一緒にファイルを作成させたかったのですが、ググってもドンピシャの情報が出てこず。
わかったのは hook_for
を指定するとそいつが実行されたあとに指定されたジェネレータも一緒に実行してくれるということでした。
Rails::Generators::ModelGenerator.hook_for
で、作成した :i18n_factory
ジェネレータをフックさせることで $ rails g model
を実行した際に一緒に作成されるようにしました。
Rails::Generators::ModelGenerator.hook_for(
:i18n_factory,
default: true, type: :boolean
) do |model, i18n_factory|
model.invoke i18n_factory, [
model.name, model.attributes.map(&:name)
]
end
module I18nFactory
module Generators
class ModelGenerator < Rails::Generators::NamedBase
# ...
end
end
end
この辺は active_decorator などを参考にさせていただきました。
設定について
言語の判別
下記のようにロケールを設定すると思うので、そこから作成する翻訳ファイル名を自動的に決めています。
module YourApplicationName
class Application < Rails::Application
# ...
config.i18n.default_locale = :ja
end
end
複数の言語の翻訳ファイルを自動で作りたい場合は、I18nFactory.config.locales
を設定すれば動くようにしています。
Rails.application.configure do
# ...
config.after_initialize do
I18nFactory.configure do |factory|
factory.locales = [:ja, 'zh-TW']
end
end
end
出力不要な列について
I18nFactory.config.ignore_columns
を設定すれば翻訳ファイルに出力しない列名を指定できます。
デフォルトは、id
, updated_at
, created_at
です。
Rails.application.configure do
# ...
config.after_initialize do
I18nFactory.configure do |factory|
factory.ignore_columns = %w(id updated_at created_at)
end
end
end
出力不要なモデルについて
I18nFactory.config.ignore_paths
を設定すれば翻訳ファイルに出力しないモデルを指定できます。
Rails.application.configure do
# ...
config.after_initialize do
I18nFactory.configure do |factory|
factory.ignore_paths = [
'app/models/foo/bar.rb',
]
end
end
end
$ rails g i18n_factory:update
について
$ rails g model
以外でも翻訳ファイルを作成したいので、コマンドを用意しました。
$ bin/rails g i18n_factory:update モデル名
もしくは
$ bin/railg g i18n_factory:update_all
で、DBに接続して列名を取得し翻訳ファイルを作成します。
i18n_factory:update_all
コマンドは app/models
配下を舐めて、自動的に i18n_facotyr:update
を実行するので最終的に実行されるのは同じです。
module I18nFactory
module Generators
class UpdateAllGenerator < Rails::Generators::Base
def update_all_i18n_files
all_model_names.each do |name|
begin
if name.constantize.ancestors.include? ActiveRecord::Base
generate 'i18n_factory:update', name
end
end
end
end
# 略
既に翻訳ファイルが存在する場合
例えば、自分は enum_help という gem を使うので、enums.post.xxx
という定義を書くことがあります。
ja:
activerecord:
models:
post: 投稿
attributes:
post:
title: タイトル
content: 内容
status: ステータス
enums:
post:
status:
published: 公開
private: 非公開
のように定義されていたとして、Post#created_by
を追加した場合も元の定義を残して追加するようにしました。
また、インデントは揃っていてほしいのでキーの後ろの空白は最大長のキーの長さに自動で揃えるようにしています。
ja:
activerecord:
models:
post: 投稿
attributes:
post:
title: タイトル # ← 空白を挿入して縦列を揃えます
content: 内容
status: ステータス
created_by: CreatedBy # ←追加
enums:
post:
status:
published: 公開
private: 非公開
最後に
rails g i18n_factory:update
では元の定義を残したかったのと、空白で縦列を自動的に揃えたかったのでライブラリを使わず自前で生成しています。
そのため変なバグが発生する場合があるかもしれません。発見したら github の Issue で教えてほしいです。
Discussion