コンテンツを複数言語で表示できるウェブアプリケーションを作ってみましょう。この記事では、次の手順で翻訳機能を実装してみます。
- データベースで翻訳を扱う方法を検討する
- アプリケーションの言語に合わせて、適切な翻訳を表示する機能を作る
データベースで翻訳を扱う方法
はじめに、データベースで翻訳を扱う方法を検討するために、翻訳がどのようなデータか確認しましょう。
翻訳は、1つのコンテンツをある言語で表したデータです。扱う言語の数だけ翻訳を用意する必要があります。
言語 | 挨拶 |
---|---|
英語 | Hello |
日本語 | こんにちは |
中国語 | 你好 |
例えば、「挨拶」というコンテンツを英語と日本語、中国語に翻訳するためには、上記の表で表されるデータを用意します。
データベースで翻訳を扱う方法
データベースで翻訳を扱う方法は、いくつか考えられます。簡単に思いついた方法を、3つ挙げてみました。それぞれに長所や短所があります。翻訳のデータ型や利用頻度などに応じて適切な方法を選択すると良いと思います。
方法 | カラム | 長所 | 短所 |
---|---|---|---|
翻訳を別テーブルに保存する | greeting_id,locale, greeting | 言語が増えてもデータベース構造を変更する必要がない | 翻訳が必要なコンテンツごとに翻訳用テーブルが必要である |
翻訳を言語ごとのカラムに保存する | id, greeting_en, greeting_ja, greeting_zh | 一つのテーブルで翻訳を扱える | 言語が増えると翻訳用のカラムを追加する必要がある |
翻訳をJSONでシリアライズして保存する | id, serialized_greeting | 一つのカラムで複数の翻訳を扱える | 言語を指定して翻訳をselectできない |
翻訳機能を実装する
翻訳を別テーブルに保存する方法を採用した上で、翻訳機能を実装してみました。投稿とその翻訳を表すモデルクラスを、次のソースコードで示します。
class Post
validates :permalink, uniqueness: true
# Has current locale translation
has_one :translation, -> { locale: I18n.locale },
class_name: 'PostTranslation'
# Has many translations
has_many :translations, class_name: 'PostTranslation'
# Define reader methods that returns the translation of current locale
# If the translation is not exists, it returns nil
%i(title excerpt body).each do |translation_attribute|
define_method(translation_attribute) do
translation.try(translation_attribute)
end
end
end
class PostTranslation
belongs_to :post, validate: true
validates :locale, inclusion: { in: I18n.available_locales.map(&:to_s) }
end
投稿は、パーマリンクとタイトル、抜粋、本文の4つの属性を持ちます。
そのうち、タイトル、抜粋、本文はアプリケーションの言語に合わせた内容を返します。もし、翻訳が存在しない場合は、nil
を返します。
post = Post.create(permalink: "greeting-post")
post.translations.create(locale: "en", title: "Hello")
post.translations.create(locale: "ja", title: "こんにちは")
post.translations.create(locale: "zh", title: "你好")
I18n.locale = :en
post.title
=> Hello
I18n.locale = :ja
post.title
=> こんにちは
I18n.locale = :sw
post.title
=> nil
ActiveSupport::Concernを使って、機能を分離する
翻訳機能は、様々なモデルで利用します。そこで、ActiveSupport::Concernを使い、翻訳機能を分離します。
module Translatable
extend ActiveSupport::Concern
included do
# has current locale translation
has_one :translation, -> { locale: I18n.locale },
class_name: translation_class_name
# has many translations
has_many :translations, class_name: translation_class_name
# define reader methods that returns the translation of current locale
translation_attribute_names.each do |translation_attribute|
define_method(translation_attribute) do
translation.try(translation_attribute)
end
end
end
module ClassMethods
def translation_class_name
"#{self.name}Translation"
end
def translation_class
Object.const_get(translation_class_name)
end
def translation_attribute_names
self.attribute_names - translation_class.attribute_names
end
end
end
翻訳機能を利用するには、Translatable
モジュールをモデルクラスでインクルードします。翻訳用クラスは、インクルードしたクラス名を元に決めました。
class Post < ActiveRecord::Base
include Translatable
end
Post.translation_class
=> PostTranslation
まとめ
別テーブルに保存した翻訳を言語に合わせて表示することができました。また、ActiveSupport::Concernを使うことで、翻訳機能を汎用的にすることができました。
ウェブアプリケーションに翻訳機能を実装することで、気軽にコンテンツを多言語で表示することができます。
ではでは。