Skip to content
Chris Salzberg edited this page Feb 13, 2018 · 16 revisions

The Table backend stores translation in a model-specific table. For a model Post with a table posts, the translation table would be post_translations. The translation table has columns for every translated attribute, as well as a column for the locale and a foreign key pointing to the model table (post_id).

A detailed description of this backend is provided as "Strategy #2" in this blog post.

Generating the Translation Table

Unlike the default KeyValue backend, for the Table backend you will need to generate and run a migration for each translated model. Mobility has a built-in generator for this. If your default backend is set to :table, and you want to translate a model Post to add translated columns title (string) and content (text), here is how you would do it:

rails generate mobility:translations post title:string content:text

(Pass --backend=table as an option if your default backend is something else.)

This will generate a migration to create the following table:

create_table "post_translations", force: :cascade do |t|
  t.string   "title"
  t.text     "content"
  t.string   "locale",     null: false
  t.integer  "post_id",    null: false
  t.datetime "created_at", null: false
  t.datetime "updated_at", null: false
  t.index ["locale"], name: "index_post_translations_on_locale"
  t.index ["post_id", "locale"], name: "index_post_translations_on_post_id_and_locale", unique: true
  t.index ["post_id"], name: "index_post_translations_on_post_id"
end

As can be seen, the translations table has two attribute columns, title and content, as well as a locale column, timestamps, and a reference to the post (post_id), as well as some indices to speed up translation retrieval. Note that the index on post_id and locale is unique, since normally you would never have more than one translation for a given locale on a given record.

Associations

In order to manage translations on a model, Mobility subclasses the Mobility::ActiveRecord::ModelTranslation (or Mobility::Sequel::ModelTranslation for Sequel) abstract class and assigns it to a class with a name Post::Translation (for a model Post). It then defines two associations: a has_many association from a model to its translations, and an inverse belongs_to association from the translation class back to the model, named translated_model.

The result looks something like this:

class Post < ApplicationRecord
  has_many :translations,
    class_name:  Post::Translation,
    foreign_key: :post_id,
    dependent:   :destroy,
    autosave:    true,
    inverse_of:  :translated_model
  # ...
end

and

class Post::Translation < ApplicationRecord
  belongs_to :post,
    class_name:  Post,
    foreign_key: :post_id,
    inverse_of:  :translations,
    touch: true

  # ...
end

When you get a value with post.title, the backend does roughly the following to get the value:

locale = Mobility.locale
translation = translations.find { |t| t.locale == locale.to_s }
translation ||= translations.build(locale: locale)
translation

So, Mobility looks for a matching translation, and if one does not exist, it builds on with the current locale. This is very similar to how the KeyValue backend fetches translations on a polymorphic association.

Querying

(coming soon)