FromClauseTranslate
This gem extends ActiveRecord models for easy manipulations with multilanguage data.
The name of gem stands for way that it is doing its magic - SQL's FROM statement.
The main idea is that you can have database columns name_en, name_ru, name_fr and you will still able to filter data with WHERE name LIKE '%some name%', all SQL statements will be working with just name column.
It allows making of complex SQL queries without need of interpolation of I18n.locale and without joining separate translation tables.
Requirements
Activerecord.
Postgresql, other databases were not tested.
Usage
Lets have translatable model Post:
class CreatePosts < ActiveRecord::Migration[5.2]
def change
create_table :posts do |t|
%i[en ru uk de fr it es].each do |locale|
t.string "slug_#{locale}"
t.string "name_#{locale}"
t.string "title_#{locale}"
t.string "text_#{locale}"
t.boolean "translated_#{locale}", null: false, default: false
end
end
end
end
In migration array of locales should be hardcoded, because when new language will be added in future, this migration will still be reversable.
Then this FromClauseTranslate should be included in AplicationRecord or in the required model:
class ApplicationRecord < ActiveRecord::Base
include FromClauseTranslate
self.abstract_class = true
end
Make columns in the model translatable:
class Post < ApplicationRecord
translates :name, :title, :text, :slug, :translated, plurals: %i[slugs translateds]
end
About plurals will be down below, now this model supports getting and setting values:
post = Post.new(name: 'Name for current locale')
post.name == 'Name for current locale' # true
post.name = 'New name'
post.name_fr = 'French name of post' # real columns are still accessible
Such ActiveRecord column methods are supported (star for column name):
save_change_to_*, *_changed?, *_before_last_save, *_change_to_be_saved, *_in_database, saved_change_to_*?, will_save_change_to_*?
Querying
Following code will get posts just like they are in DB, with all of name_en, title_de columns:
post = Post.take
posts = Post.all
For receiving columns for current locale use .translated method with the list of required columns:
post = Post.translated(:title, :name, :text)
It accepts symbols and SQL strings that will go to SELECT statement.
After invoking .translated passed columns becomes available in querying methods:
Post.translated(:title, :name)
.where(title: 'smth')
.where("name ILIKE '%name%'")
.order('title DESC, name ASC')
Fallbacks
Set up I18n.fallbacks in application.rb:
I18n.fallbacks = {uk: [:ru, :en]}
Now ukrainian will fallback to russian and then to english.
Lets take post record:
Post.translated(:name).take
Fallback rule will be present in produced SQL statement:
COALESCE(COALESCE("posts"."name_uk", name_ru), name_en) AS name
Fallbacks could be set through option of .translated method in model:
class Post
translates :name, fallback: {_: %i[name_en title_en]}
end
Here _ key means any locale.
If current locale is ru and name_ru is NULL then name_en will be used.
If it is NULL too then title_en will be received.
If current locale is en then name_en fallback rule will be ignored and title_en will be received.
Plurals
Described Post model has slug columns for human readable urls.
Also it has translated boolean columns for hiding not translated posts from users.
class Post < ApplicationRecord
translates :name, ..., plurals: %i[slugs translateds]
end
Query for show page loads slug and translated columns for all locales:
post = Post.translated(:slug, :slugs, :translateds).find_by(slug: params[:slug])
Now at the show page you can provide links to same post in different languages filtering out not translated:
<% I18n.available_locales.each do |locale| %>
<% if @post.send("translated_#{locale}") %>
<% link_to "Read in #{locale}", post_url(@post.send("slug_#{locale}")) %>
<% end %>
<% end %>
Installation
Add this line to your application's Gemfile:
gem 'from_clause_translate'
And then execute:
$ bundle
Or install it yourself as:
$ gem install from_clause_translate
License
The gem is available as open source under the terms of the MIT License.