はっさんブログ

技術的なまとめ・話題になっているもの・やっていきを配信する

【Ruby gem】翻訳用のライブラリ、globalizeを使ってみた

対象読者

  • Ruby / Railsを扱っている人
  • 自分のサービスを多言語対応させたい人

globalizeについて

globalizeのリポジトリ https://github.com/globalize/globalize

Rails I18n de-facto standard library for ActiveRecord model/data translation.

ActiveRecordのモデル/データ翻訳用のデファクトスタンダードなライブラリだよ!!とのことです。早速、使ってみましょう。

Gemfileにglobalizeを追加

# Gemfile
gem 'globalize', git: 'https://github.com/globalize/globalize'

該当モデルに翻訳する属性名を記述する

今回はvideosテーブルがもつtitle, descriptionを翻訳対象とします。
はじめに、Videoモデルに翻訳したい属性を以下のように指定します。

class Video < ApplicationRecord
  translates :title, :description #=> 追加
end

翻訳用のテーブルを作成する

次に翻訳テーブルを追加します。

$ rails g migration video_translatable

globalizeが用意しているcreate_translation_table!メソッドで翻訳用のテーブルを作成します。
このメソッドはモデルにtranslatesを追加することで生えるメソッドのようです。

rollback時にはdrop_translation_tableでテーブルを削除するように指定します。

class VideoTranslateable < ActiveRecord::Migration[5.1]
  def change
    reversible do |dir|
      dir.up do
        Video.create_translation_table! title: { type: :string, null: false }, description: { type: :text, null: false }
      end

      dir.down do
        Video.drop_translation_table!
      end
    end
  end
end

スキーマの確認

作成されたスキーマ

create_table "video_translations", force: :cascade do |t|
    t.integer "video_id", null: false
    t.string "locale", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "title", null: false
    t.text "description", null: false
    t.index ["locale"], name: "index_video_translations_on_locale"
    t.index ["video_id"], name: "index_video_translations_on_video_id"
  end

翻訳テーブルの方にカラムを移すことから重複を避けるため、元となるテーブルにはtitledescription等のカラムは持たないようにします。

create_table "videos", force: :cascade do |t|
    t.text "file", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
end

global-accessorsを利用する

global-accessorsというgemを使います。
https://github.com/globalize/globalize-accessors

# Gemfile

gem 'globalize', git: 'https://github.com/globalize/globalize'
gem 'globalize-accessors' #=> 追加

このgemは該当するモデルで以下のように指定すると、attributename_locales でアクセスできるアクセサメソッドが追加されるgemです。

form内で以下のように言語ごとにフィールドを指定してあげたい時に便利です。

.field
  = f.label :繁体字タイトル
  = f.text_field :title_cht
.field
  = f.label :簡体字タイトル
  = f.text_field :title_chs

該当するモデルに以下のようにglobalize_accessorsを追加します。

class Video < ApplicationRecord
  translates :title, :description
  globalize_accessors locales: %i[chs cht], attributes: %i[title description]
end

また、プロジェクト内で扱いたいlocalesキーは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 App
  class Application < Rails::Application
    config.load_defaults 5.1

    config.i18n.default_locale = :ja
    I18n.config.available_locales = %i[ja chs cht] #=> 追加
  end
end

以上で基本的な設定は終了です。

コンソールからDBに多言語データが入る / 取得時に切り替わることを確認

最低限のデータを追加してください。
次にglobal-accessorsによって追加されたアクセサメソッドが実際に動作するか、言語の切り替え後に取得するデータが適切に切り変わるかを確認します。

pry(main)> Video.first.title
  Video Load (0.6ms)  SELECT  "videos".* FROM "videos" ORDER BY "videos"."id" DESC LIMIT $1  [["LIMIT", 1]]
  Video::Translation Load (0.4ms)  SELECT "video_translations".* FROM "video_translations" WHERE "video_translations"."video_id" = $1  [["video_id", 1]]
=> "日本語タイトル"

追加されたアクセサメソッドを試してみる。

pry(main)> Video.first.title_cht
  Video Load (0.3ms)  SELECT  "videos".* FROM "videos" ORDER BY "videos"."id" DESC LIMIT $1  [["LIMIT", 1]]
  Video::Translation Load (0.3ms)  SELECT "video_translations".* FROM "video_translations" WHERE "video_translations"."video_id" = $1  [["video_id", 1]]
=> nil

データはないけど、メソッドの認識はしているようですね。
データを追加してみます。

pry(main)> Video.first.update(title_cht: '繁体字タイトル', description_cht: '繁体字説明')
  SQL (0.4ms)  UPDATE "videos" SET "updated_at" = $1 WHERE "videos"."id" = $2  [["updated_at", "2017-08-28 08:53:07.435762"], ["id", 1]]
  SQL (0.4ms)  INSERT INTO "video_translations" ("video_id", "locale", "created_at", "updated_at", "title", "description") VALUES ($1, $2, $3, $4, $5, $6) RETURNING "id"  [["video_id", 1], ["locale", "cht"], ["created_at", "2017-08-28 08:53:07.438655"], ["updated_at", "2017-08-28 08:53:07.438655"], ["title", "繁体字タイトル"], ["description", "繁体字説明"]]
   (1.6ms)  COMMIT
=> true

おっ。
扱う言語を変更します。

pry(main)> I18n.locale = :cht
=> :cht
pry(main)> Video.first.title
  Video Load (0.6ms)  SELECT  "videos".* FROM "videos" ORDER BY "videos"."id" DESC LIMIT $1  [["LIMIT", 1]]
  Video::Translation Load (0.4ms)  SELECT "video_translations".* FROM "video_translations" WHERE "video_translations"."video_id" = $1  [["video_id", 1]]
=> "繁体字タイトル"

おおーっ!
プロジェクト内のlocaleを変更したところ、取得するデータが指定の言語に切り変わりましたね!!

Strong Parametersで翻訳パラメータを許可する

実際にWeb上からユーザの入力によってパラメータを受け取る場合には、Strong Parametersで許可するパラメータに言語ごとのパラメータを追加してあげます。

def video_create_params
  permitted = Product.globalize_attribute_names + %i(title description file)
  params.require(:product).permit(*permitted)
end

あとはよくあるResourcesController#createでパラメータを受け付け、データを作成してあげればOKです!
うまく保存されたでしょうか?

後は、ユーザーの言語情報を判断・切り替えをしてあげることによって、様々な国のユーザーに適切な言語のデータを表示させることができますね!

まとめ

globalizeglobal-accessorsを用いることで、多言語なデータの管理もスマートに行うことができました。

実際に運用をする際はユーザーの言語情報をどこで判断するか等の考えどころが多く、面白みが深いなと思います。

その際に参考になるリンク: i18nについて