-
Notifications
You must be signed in to change notification settings - Fork 87
KeyValue Backend
The KeyValue backend stores translations on a set of two shared tables, one for string-valued translations and one for text-valued translations. These tables are generated when you run the default install generator (rails generate mobility:install
) and then migrate your database (rake db:migrate
).
A detailed description of the backend is described as "Strategy #3" in this blog post.
The tables generated by the default migration look like this:
create_table "mobility_string_translations", force: :cascade do |t|
t.string "locale"
t.string "key"
t.string "value"
t.integer "translatable_id"
t.string "translatable_type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["translatable_id", "translatable_type", "key"], name: "index_mobility_string_translations_on_translatable_attribute"
t.index ["translatable_id", "translatable_type", "locale", "key"], name: "index_mobility_string_translations_on_keys", unique: true
t.index ["translatable_type", "key", "value", "locale"], name: "index_mobility_string_translations_on_query_keys"
end
create_table "mobility_text_translations", force: :cascade do |t|
t.string "locale"
t.string "key"
t.text "value"
t.integer "translatable_id"
t.string "translatable_type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["translatable_id", "translatable_type", "key"], name: "index_mobility_text_translations_on_translatable_attribute"
t.index ["translatable_id", "translatable_type", "locale", "key"], name: "index_mobility_text_translations_on_keys", unique: true
end
Both tables have a translatable_type
string column, because these will be used in a polymorphic relationship with translated models.
To understand how this works, suppose we have a model Post
:
class Post < ApplicationRecord
extend Mobility
translates :title, backend: :key_value, type: :string
translates :content, backend: :key_value, type: :text
end
The backend will create associations on the class roughly resulting in:
class Post < ApplicationRecord
has_many :mobility_string_translations, ->{ where key: [:title] },
as: :translatable,
class_name: Mobility::ActiveRecord::StringTranslation,
dependent: :destroy,
inverse_of: :translatable,
autosave: true
has_many :mobility_text_translations, ->{ where key: [:content] },
as: :translatable,
class_name: Mobility::ActiveRecord::TextTranslation,
dependent: :destroy,
inverse_of: :translatable,
autosave: true
end
So the backend adds associations for each type
, scoped to only include translations whose keys are :title
and :content
, respectively.
The classes Mobility::ActiveRecord::StringTranslation
and Mobility::ActiveRecord::TextTranslation
both inherit from Mobility::ActiveRecord::Translation
, which is an abstract class:
module Mobility
module ActiveRecord
class Translation < ::ActiveRecord::Base
self.abstract_class = true
belongs_to :translatable, polymorphic: true
validates :key, presence: true, uniqueness: { scope: [:translatable_id, :translatable_type, :locale] }
validates :translatable, presence: true
validates :locale, presence: true
end
end
end
Since the class itself is abstract, it has no table; the table is set in the descendants to mobility_string_translations
and mobility_text_translations
, respectively. The different tables is actually the only difference between the string and text translation classes, and the only difference between the tables is that their value
columns are of different types (string/text) (and that the string table has one additional index on value).
When you get a value with post.title
, the backend does roughly the following to get the value:
locale = Mobility.locale
translation = mobility_string_translations.find { |t| t.key == "title" && t.locale == locale.to_s }
translation ||= translations.build(locale: locale, key: "title")
translation.value
So it finds the translation from all translations associated with the model which has a key "title" and a locale equal to Mobility.locale
. If such a translation does not exist, it builds one. It then returns the value. Setting is much the same, with the value of the translation updated to a new value.