Rails gem approach, MySQL approach and a Multilingual Rails structure
Most of the world doesn’t speak English. That’s where internationalization and localization come in. Rails has a great i18n API.
Reference: http://guides.rubyonrails.org/i18n.html
It provides an easy-to-use and extensible framework for translating your application to a single custom language other than English or for providing multi-language support in our application.
I18n API:
The most important methods of the I18n API are:
translate # Lookup text translations
localize # Localize Date and Time objects to local formats
These have the aliases #t and #l so you can use them like this:
I18n.t 'app.title'
I18n.l Time.now
Rails-i18n gem:
Installation:
Add to the Gemfile:
gem 'rails-i18n', github: 'svenfuchs/rails-i18n', branch: 'master' # For 5.x
Configuration:
By default rails-i18n loads all locale files, pluralization and transliteration rules available in the gem. This behaviour can be changed, if we specify in config/environments/* the locales which have to be loaded via I18n.available_locales option:
config.i18n.available_locales = ['es-CO', :de]
or
config.i18n.available_locales = :nl
We can use another gem also:
Globalize gem:
Rails I18n de-facto standard library for ActiveRecord model/data translation. Globalize builds on the I18n API in Ruby on Rails to add model translations to ActiveRecord models.
Installation:
When using bundler put this in our Gemfile:
gem 'globalize', '~> 5.0.0'
To use globalize with Rails 5 add this in our Gemfile
gem 'activemodel-serializers-xml'
Documentation: https://github.com/globalize/globalize
DB Design for Multilingual App (English and Arabic):
Approach for supporting 2 or 3 languages:
1. Column Approach:
Create column approach model with language columns;
`title_en` varchar(255) NOT NULL,
`title_ar` varchar(255) NOT NULL,
Now, the way you would query it is also simple enough. We may do it by automatically selecting the right columns according to the chosen language.
Advantages:
- Simplicity – easy to implement
- Easy querying – no JOINs required
- No duplicates – doesn’t have duplicate content
Disadvantages:
- Hard to maintain – works in easy way for 2-3 languages, but it becomes a really hard when you have a lot of columns or a lot of languages
- Hard to add a new language
2. Translation table Approach:
Having a related translation table for each translatable table.
CREATE TABLE PRODUCT (id int, PRICE NUMBER(18, 2))
CREATE TABLE PRODUCT_tr (id int, product_id INT FK, languagecode varchar, name text, description text)
This way if we have multiple translatable column it would only require a single join to get it and it may be easier to import items together with their related translations.
- Doesn’t require altering the database schema for new languages (and thus limiting code changes)
- Doesn’t require a lot of space for unimplemented languages or translations of a particular item
Provides the most flexibility - We don’t end up with sparse tables
- We don’t have to worry about null keys and checking that we’re displaying an existing translation instead of some null entry.
Multilingual App with Rails-5 (English and Arabic):
The approach for multilingual Rails applications is very similar to the monolingual. Here we need to define YAML language files for all required languages and tell the Rails application which language it should currently use. We do this via I18n.locale.
Setting I18n.locale via URL Path Prefix
We want http://0.0.0.0:3000/ar to display the Arabic version and http://0.0.0.0:3000/en the English version.
config/routes.rb
Myapp::Application.routes.draw do
scope "(:locale)", :locale => /en|ar/ do
root :to => 'page#index'
get "page/index"
end
end
Set a before_filter in the app/controllers/application_controller.rb.
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :set_locale
private
def set_locale
I18n.locale = params[:locale] || I18n.default_locale
end
end
To test it, Go to the URL;
http://0.0.0.0:3000/ar
Or
http://0.0.0.0:3000/ar/page/index
Setting a default language:
app/controllers/application_controller.rb:
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :set_locale
private
def set_locale
I18n.locale = params[:locale] || I18n.default_locale
Rails.application.routes.default_url_options[:locale]= I18n.locale
end
end
As a result, We do not need to do anything else. All links generated via the scaffold generator are automatically changed accordingly.
Setting I18n.locale via Accept Language HTTP Header of Browser:
app/controllers/application_controller.rbclass
ApplicationController < ActionController::Base
protect_from_forgery
before_filter :set_locale
private
def extract_locale_from_accept_language_header
request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first
end
def set_locale
I18n.locale = extract_locale_from_accept_language_header || I18n.default_locale
end
end
Here we can change clean our routes file:
config/routes.rb:
Myapp::Application.routes.draw do
get "page/index"
root :to => 'page#index'
end
Storing I18n.locale in Session
app/controllers/set_mylanguage_controller.rb:
class SetMylanguageController < ApplicationController
#English
def en
I18n.locale = :en
set_session_and_redirect
end
#Arabic
def ar
I18n.locale = :ar
set_session_and_redirect
end
private
def set_session_and_redirect
session[:locale] = I18n.locale
redirect_to :back
rescue ActionController::RedirectBackError
redirect_to :root
end
end
Change in application controller:
app/controllers/application_controller.rb:
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :set_locale
private
def set_locale
I18n.locale = session[:locale] || I18n.default_locale
session[:locale] = I18n.locale
end
end
For setting Arabic:
http://0.0.0.0:3000/set_mylanguage/ar
For setting English:
http://0.0.0.0:3000/set_mylanguage/en
Text Blocks in YAML Format:
Create the below directories first:
$ mkdir -p config/locales/models/item
$ mkdir -p config/locales/views/item
Insert the following lines into the file config/application.rb:
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', 'models', '*', '*.yml').to_s]
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', 'views', '*', '*.yml').to_s]
config.i18n.default_locale = :en
We will have to create a file here; config/locales/models/item
config/locales/models/item/ar.yml for Arabic
and make sure that en.yml is present for English.
English YAML:
As most things are already present in the system for English
Insert the below into en.yml file.
# ruby encoding: utf-8
en:
views:
show: Show
edit: Edit
destroy: Delete
are_you_sure: Are you sure?
back: Back
item:
index:
title: List of all items
new:
title: New Item
flash_messages:
item_was_successfully_created: ‘Item was successfully created.'
Arabic YAML:
We will have to insert values/texts in Arabic into ar.yml like in en.yml
Even we can copy a ready-made default translation by Sven Fuchs from his github repository https://github.com/svenfuchs/rails-i18n:
$ cd config/locales
$ curl -O https://raw.github.com/svenfuchs/rails-i18n/master/rails/locale/ar.yml
Translating in View:
We can use human_attribute_name() for the attributes and the links need to be translated with I18n.t. We can use number_to_currency() to show the amount/price in formatted form:
Examples:
<% = t ‘views.item.index.listing_items’ %>
<% = link_to I18n.t('views.show'),@item %>
<%= Item.human_attribute_name(:name) %><%=Item.human_attribute_name(:description) %>
Translating Flash Messages in the Controller
if @item.save
format.html { redirect_to @item, notice:
I18n.t('views.item.flash_messages.item_was_successfully_created') }
format.json { render json: @item, status: :created, location: @item }
else
format.html { render action: "new" }
format.json { render json: @item.errors, status: :unprocessable_entity }
end
For other ready-made language translations:
https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale