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.