Rails Translations I18n
Localisation i18n translations
Tips https://devhints.io/rails-i18n
Translate models using activerecord
https://guides.rubyonrails.org/i18n.html#translations-for-active-record-models
so you can use
User.model_name.human
# 'asd'.pluralize # asds so for english that is ok but if you use i18n, than use
# note that you have to write translations in yml.file if you use human(count)
User.model_name.human(count: 2)
# attribute
User.human_attribute_name(:email)
To translate active record messages for specific attributes, you can overwrite
messages for specific model and attributes (default ActiveRecord messages taken)
https://github.com/rails/rails/blob/master/activerecord/lib/active_record/locale/en.yml#L23
https://apidock.com/rails/v4.2.7/ActiveModel/Errors/generate_message
For submit buttons default title is defined on submit_default_value
https://github.com/rails/rails/blob/main/actionview/lib/action_view/helpers/form_helper.rb#L2573
(I found it using byebug and step) so to use in test
click_on "#{:update.to_s.humanize} #{User.model_name.human}"
Also you can change format errors.format: Polje "%{attribute}" %{message}
https://github.com/rails/rails/blob/master/activemodel/lib/active_model/locale/en.yml#L4
And you can change attribute name activerecord.attributes.user.email: имејл
To translate also plurals you can use User.model_name.human(count: 2)
. For
attributes you can use User.human_attribute_name("email")
link
For ApplicationRecord
translate activerecord
.
For form objects include ActiveModel::Model
you should translate
activemodel
. There you can use t('successfully')
instead
I18n.t('successfully')
if you include AbstractController::Translation
This will also translate error messages, for example
landing_signup.errors.add(:current_city) = 'x'
will result in message like
landing_signup.errors.full_messages.to_sentence # 'Који је твој град ? x'
# config/locales/activerecord_activemodels.en.yml
en:
activerecord:
models:
user:
zero: No dudes
one: Dude
other: Dudes
customer:
one: корисник
other: корисници
accusative: корисника
some_customer_message: Моја порука
activemodel:
attributes:
landing_signup:
current_city: Који је твој град ?
errors:
messages:
group_not_exists_for_age: Не постоји група (%{age}год) на овој локацији
models:
landing_signup:
attributes:
current_city:
blank: Не може бити празно ?
Separate translations into different files (for example
activerecord_activemodels.sr.yml
) include them with:
# config/application.rb
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]
# Whitelist locales available for the application
I18n.available_locales = [:en, :pt]
# Set default locale to something other than :en
I18n.default_locale = :pt
To raise error when translation is missing .translation_missing
class
# config/application.rb
config.action_view.raise_on_missing_translations = true
This way you can change default_locale and run system tests and you will get exception if some locale is missing.
No need to write quotes in yml unless you have:
- colon
:
, than simply escape it’s meaning and use quotesname: "name: Ime"
- start with
%{}
or,
or.
- have
answer: Yes
oranswer: No
, is somehow casts totrue
orfalse
- have double quotes inside
processing: <i class="fa fa-spinner fa-spin datatable-spinner"></i>Обрада...
. Than you need to switch to single quotes. Note that it does not help to wrap double quotes with single quote, this won’t work:processing: '<i class="c"></i>'
since you have double quote inside.
When debugging SyntaxError: [stdin]:60:33: unexpected identifier
you should
run rake tmp:clear
so all yml end .erb files are compiled again.
If you want to reuse same translation you can use alias, but only inside same file, so in case of form object, you can add to activerecord_models.yml
sr:
activerecord:
attributes:
subscriber: &subscriber_attributes
name: Назив
project: &project_attributes
name: Назив
activemodel:
attributes:
project_task_notification:
<<: *project_attributes
<<: *subscriber_attributes
project_name: Назив
For custom errors can be different for each attribute or same. Can also accept param, for example
errors.add :from_group_age, :group_not_exists_for_age, age: age
https://stackoverflow.com/questions/6166064/i18n-pluralization For serbian you can provide pluralization
# config/locales/plurals.rb
# https://github.com/svenfuchs/i18n/blob/master/test/test_data/locales/plurals.rb
serbian = {
i18n: {
plural: {
keys: %i[one few many other],
rule: lambda { |n|
if n % 10 == 1 && n % 100 != 11
:one
elsif [2, 3, 4].include?(n % 10) && ![12, 13, 14].include?(n % 100)
:few
# elsif (n % 10).zero? || [5, 6, 7, 8, 9].include?(n % 10) || [11, 12, 13, 14].include?(n % 100)
# :many
# there are no other integers, use :many if you need to differentiate
# with floats
else
:other
end
}
}
}
}
{
sr: serbian,
'sr-latin': serbian,
}
# config/initializers/pluralization.rb
require 'i18n/backend/pluralization'
I18n::Backend::Simple.send(:include, I18n::Backend::Pluralization)
If you send count
parameter t('sent_messages', count: 2)
it will try to pick
specific item based on number
# config/locales/sr.yml
sr:
sent_messages:
# 1, 21, 31 ...
one: %{count} порука је послата
# 2, 3, 4, 22, 23, 24, 32, 33, 34 ...
few: %{count} поруке су послате
# all other integers: 5, 6, ... 9, 10, 11, 12, 13, 14 ... 20, 25, ...
many: %{count} порука је послато
Note that you have to provide few
translation for all words, since it could
happend that count is 2 and translation is missing.
I18n.t 'sent_messages', count: 15
# or if you want to translate model
"#{chat.moves.size} #{Move.model_name.human count: chat.moves.size}"
You can translate to any language with
I18n.t 'sent_messages', locale: :sr
Example for Serbian localizations translations:
# config/locales/sr.yml
sr:
# https://github.com/rails/rails/blob/master/activemodel/lib/active_model/locale/en.yml#L8
errors:
format: Поље "%{attribute}" %{message}
messages:
blank: не сме бити празно
invalid: није исправно
neo4j:
errors:
messages:
required: мора постојати
taken: је већ заузет
models:
user: корисник
location:
one: локација
other: локације
attributes:
user:
email: Имејл
password: Лозинка
password_confirmation: Потврда лозинке
remember_me: Запамти ме
When you use .capitalize
, .titleize
or .upcase
than you need first to call
.mb_chars
. For example
'ž'.upcase
=> "ž"
'ž'.mb_chars.upcase.to_s
=> "Ž"
Some common words translations can be found https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/en.yml
To translate with accusative you need to joins strings or use param in translation
module TranslateHelper
# there are two ways of calling this helper:
# t_crud 'are_you_sure_to_remove_item', item: @move
# t_crud 'edit', User
def t_crud(action, model_class)
if model_class.class == Hash
t(action, item: t("neo4j.models.#{model_class[:item].name.downcase}.accusative"))
else
"#{t(action)} #{t("neo4j.models.#{model_class.name.downcase}.accusative")}"
end
end
end
Translate latin to cyrilic with https://github.com/dalibor/cyrillizer You need to set language in config
# Gemfile
# translate cyrillic
gem 'cyrillizer'
# config/initializers/cyrillizer.rb
Cyrillizer.language = :serbian
In console
'my string'.to_cyr
=> "мy стринг"
Note that some chars looks the same but are not when rendered on html page
# for example first line is not correct link a href
<a href='%{confirmation_url}'>Поново пошаљи упутство за потврду</а>"
<a href='%{confirmation_url}'>Поново пошаљи упутство за потврду</a>"
To check if word is using cyr you can use (note that it looks the same but are not same charcters)
Cyrillizer.alphabet.keys.include? 'a'
=> false
2.6.3 :026 > Cyrillizer.alphabet.keys.include? 'а'
=> true
Locale
When changing locale I18n.locale = :sr
in some methods, note that this is
global variable in thread, so when you have 5 puma threads than on GET requests
(simply refresh couple of times) you will get different locales.
Here is my Rails controbution to guide about it https://github.com/rails/rails/pull/34911
One way is to use I18n.with_locale
for example
class UserMailer < ActionMailer::Base
default from: '[email protected]'
def invitation(user)
I18n.with_locale(user.locale) do
mail subject: t('invitation'), to: user.email
end
end
end
For controller, you need to use around filters
around_action :set_locale_from_session
def set_locale_from_session
if session[:locale].present?
I18n.with_locale session[:locale].to_sym do
yield
end
else
yield
end
end
Translating user content
https://github.com/shioyama/mobility#quickstart
# Gemfile
# translation
gem 'mobility', '~> 0.8.6'
# this will generate config/initializers/mobility.rb
rails g mobility:install
# app/models/activity.rb
class Activity < ApplicationRecord
extend Mobility
translates :name
end
# in migration add default value
create_table :activities, id: :uuid do |t|
t.json :name, default: {}
For google translate look for two scripts, one for vim and one for whole yml. https://github.com/duleorlovic/config/tree/master/ruby
fallbacks
club.name fallback: false
club.name fallback: [:en]
If fallback is not false than longer translate will fallback to short
automatically ('sr-latin'
to :sr
)
Note that passing locale
options to reader or using locale accessors will
disable fallbacks
word.meaning(locale: :de)
#=> nil
word.meaning_de
#=> nil
Mobility.with_locale(:de) { word.meaning }
# if in model we have translate :meaning, fallbacks: { de: :ja}
#=> "(名詞):動きやすさ、可動性"
Global fallback
# config/initializers/mobility.rb
config.default_options[:fallbacks] = { sr: :en, en: :sr }
Dynamic fallback https://github.com/shioyama/mobility/pull/328 https://github.com/shioyama/mobility/issues/314
You can search find_by using @>
and passing json
Activity.where("name @> ?",{en: 'Climbing'}.to_json)
# or using i18n scope
Activity.i18n.where(name: 'Climing')
# or using specific locale
Mobility.with_locale(:en) do
Mobility.locale # => :en
Activity.i18n.find_by(name: 'Climbing')
end
Enums
# app/models/user.rb
class User < ApplicationRecord
enum status: [:active, :pending, :archived]
end
# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def self.human_enum_name(enum_name, enum_value)
I18n.t("activerecord.attributes.#{model_name.i18n_key}.#{enum_name.to_s.pluralize}.#{enum_value}")
end
end
# config/locales/activerecord.en.yml
en:
activerecord:
attributes:
user:
statuses:
active: "Active"
pending: "Pending"
archived: "Archived"
# usage:
User.human_enum_name(:status, :pending)
User.human_enum_name(:status, user.status)
Tips
- if you want to see which translation is used (if there is parent default value) you can try https://github.com/fphilipe/i18n-debug
- for big content pages, you can translate using
page.sr.html.erb
instead of yml translations - fallbacks, if can not find the key in curreny localy, it can search in
fallback locale. In this case you can translate only specific keys
# config/application.rb config.i18n.fallbacks = { en_GB: [:en] } # config/locales/en_GB.yml en_GB: soccer: Footbal en: soccer: Soccer words: Words # logs from i18n-debug en_GB.show.soccer => 'Footbal' en_GB.show.words => nil en.show.words => 'Words'
- To see Rails default datetime formats go to
https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/en.yml
to see current translation you can use
I18n.translate 'date.formats.default` => "%Y-%m-%d"
Use can check formats https://apidock.com/ruby/DateTime/strftime with
Time.zone.now.strftime '%e %B'
-
to localise date and timestamps you can use
I18n.l Time.now, format: :formal
en: time: formats: default: '%H:%M' formal: The time is %H:%M
helper.number_with_precision
,distance_of_time_in_words_to_now
,number_to_currency
,number_to_human
,number_to_human_size
are some helpers that uses translations- use
_html
suffix when you use tags in translated content. For exampletitle_html: Hi <b>man</b>
and<%= t('title_html') %>
will not escape and you do not need to use html_safe or raw - to add route instead of subdomain, use https://stackoverflow.com/a/8237800
# config/routes.rb devise_for :users, only: :omniauth_callbacks, controllers: { omniauth_callbacks: 'devise/my_omniauth_callbacks', } scope '(:locale)', locale: /#{I18n.available_locales.join("|")}/ do root 'pages#home' devise_for :users, skip: :omniauth_callbacks, controllers: { registrations: 'devise/my_registrations', } end # config/