Rails 4 Active Record Enums are great, but what is the right pattern for translating with i18n?
I didn't find any specific pattern either, so I simply added:
en:
user_status:
active: Active
pending: Pending...
archived: Archived
to an arbitrary .yml file. Then in my views:
I18n.t :"user_status.#{user.status}"
Starting from Rails 5, all models will inherit from ApplicationRecord
.
class User < ApplicationRecord
enum status: [:active, :pending, :archived]
end
I use this superclass to implement a generic solution for translating enums:
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
Then I add the translations in my .yml
file:
en:
activerecord:
attributes:
user:
statuses:
active: "Active"
pending: "Pending"
archived: "Archived"
Finally, to get the translation I use:
User.human_enum_name(:status, :pending)
=> "Pending"
<%= f.select :status, User.statuses.keys.collect { |status| [User.human_enum_name(:status, status), status] } %>
. –
Sententious human_enum_name(@user, :status)
–
Osmunda self.human_enum_collection(enum_name)
. Code would be send(enum_name.to_s.pluralize).keys.collect { |val| [human_enum_name(enum_name, val), val] }
–
Saito self.human_enum_name2
and def self.human_enum_name3
and so on? –
Lockwood self.human_enum_name
you have to specify the enum name and the enum value. This means only one generic class method will be enough, doesn't matter how many enums your ActiveRecord class has. –
Sententious (enum_name, enum_value)
, if I have more than one enum name and enum value, how should I write this without confusion? –
Lockwood I didn't find any specific pattern either, so I simply added:
en:
user_status:
active: Active
pending: Pending...
archived: Archived
to an arbitrary .yml file. Then in my views:
I18n.t :"user_status.#{user.status}"
{locale}.activerecord.attributes.{model}.{attribute}
and wrote a t_enum(model, enum, value)
helper method so the enum translations would be adjacent to the label translation –
Suavity To keep the internationalization similar as any other attribute I followed the nested attribute way as you can see here.
If you have a class User
:
class User < ActiveRecord::Base
enum role: [ :teacher, :coordinator ]
end
And a yml
like this:
pt-BR:
activerecord:
attributes:
user/role: # You need to nest the values under model_name/attribute_name
coordinator: Coordenador
teacher: Professor
You can use:
User.human_attribute_name("role.#{@user.role}")
activerecord.attributes.<fieldname>
being the label
translation for form helpers –
Suavity role
key. You can nest coordinator
and teacher
directly under user
. –
Dineen Here is a view:
select_tag :gender, options_for_select(Profile.gender_attributes_for_select)
Here is a model (you can move this code into a helper or a decorator actually)
class Profile < ActiveRecord::Base
enum gender: {male: 1, female: 2, trans: 3}
# @return [Array<Array>]
def self.gender_attributes_for_select
genders.map do |gender, _|
[I18n.t("activerecord.attributes.#{model_name.i18n_key}.genders.#{gender}"), gender]
end
end
end
And here is locale file:
en:
activerecord:
attributes:
profile:
genders:
male: Male
female: Female
trans: Trans
.human_attribute_name('genders.male')
don't work –
Discord genders.map
? I keep getting undefined local variable or method `genders'
–
Lockwood Elaborating on user3647358's answer, you can accomplish that very closely to what you're used to when translating attributes names.
Locale file:
en:
activerecord:
attributes:
profile:
genders:
male: Male
female: Female
trans: Trans
Translate by calling I18n#t:
profile = Profile.first
I18n.t(profile.gender, scope: [:activerecord, :attributes, :profile, :genders])
Model:
enum stage: { starting: 1, course: 2, ending: 3 }
def self.i18n_stages(hash = {})
stages.keys.each { |key| hash[I18n.t("checkpoint_stages.#{key}")] = key }
hash
end
Locale:
checkpoint_stages:
starting: Saída
course: Percurso
ending: Chegada
And on the view (.slim):
= f.input_field :stage, collection: Checkpoint.i18n_stages, as: :radio_buttons
Combining the answers from Repolês and Aliaksandr, for Rails 5, we can build 2 methods that allow you to translate a single value or a collection of values from an enum attribute.
Set up the translations in your .yml
file:
en:
activerecord:
attributes:
user:
statuses:
active: "Active"
pending: "Pending"
archived: "Archived"
In the ApplicationRecord
class, from which all models inherit, we define a method that handles translations for a single value and another one that handles arrays by calling it:
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def self.translate_enum_name(enum_name, enum_value)
I18n.t("activerecord.attributes.#{model_name.i18n_key}.#{enum_name.to_s.pluralize}.#{enum_value}")
end
def self.translate_enum_collection(enum_name)
enum_values = self.send(enum_name.to_s.pluralize).keys
enum_values.map do |enum_value|
self.translate_enum_name enum_name, enum_value
end
end
end
In our views, we can then translate single values:
<p>User Status: <%= User.translate_enum_name :status, @user.status %></p>
Or the entire collection of enum values:
<%= f.select(:status, User.translate_enum_collection :status) %>
enum_values.each_with_object({}) do |enum_value, acc| acc[enum_value] = self.translate_enum_name(enum_name, enum_value) end
And then in the view add an invert: User.translate_enum_collection(:status).invert
–
Uboat I've created a gem for this.
http://rubygems.org/gems/translated_attribute_value
Add to your gemfile:
gem 'translated_attribute_value'
If you have a status field for user:
pt-BR:
activerecord:
attributes:
user:
status_translation:
value1: 'Translation for value1'
value2: 'Translation for value2'
And in your view you can call like this:
user.status_translated
It works with active record, mongoid or any other class with getter/setters:
Try using TranslateEnum gem for these purposes
class Post < ActiveRecord::Base
enum status: { published: 0, archive: 1 }
translate_enum :status
end
Post.translated_status(:published)
Post.translated_statuses
@post = Post.new(status: :published)
@post.translated_status
The model:
class User < ActiveRecord::Base
enum role: [:master, :apprentice]
end
The locale file:
en:
activerecord:
attributes:
user:
master: Master
apprentice: Apprentice
Usage:
User.human_attribute_name(:master) # => Master
User.human_attribute_name(:apprentice) # => Apprentice
@user.role
, because that is the main issue. –
Metanephros Try the enum_help gem. From its description:
Help ActiveRecord::Enum feature to work fine with I18n and simple_form.
Heres a t_enum
helper method that I use.
<%= t_enum(@user, :status) %>
enum_helper.rb:
module EnumHelper
def t_enum(inst, enum)
value = inst.send(enum);
t_enum_class(inst.class, enum, value)
end
def t_enum_class(klass, enum, value)
unless value.blank?
I18n.t("activerecord.enums.#{klass.to_s.demodulize.underscore}.#{enum}.#{value}")
end
end
end
user.rb:
class User < ActiveRecord::Base
enum status: [:active, :pending, :archived]
end
en.yml:
en:
activerecord:
enums:
user:
status:
active: "Active"
pending: "Pending..."
archived: "Archived"
Yet another way, I find it a bit more convenient using a concern in models
Concern :
module EnumTranslation
extend ActiveSupport::Concern
def t_enum(enum)
I18n.t "activerecord.attributes.#{self.class.name.underscore}.enums.#{enum}.#{self.send(enum)}"
end
end
YML:
fr:
activerecord:
attributes:
campaign:
title: Titre
short_description: Description courte
enums:
status:
failed: "Echec"
View :
<% @campaigns.each do |c| %>
<%= c.t_enum("status") %>
<% end %>
Don't forget to add concern in your model :
class Campaign < ActiveRecord::Base
include EnumTranslation
enum status: [:designed, :created, :active, :failed, :success]
end
I prefer a simple helper in application_helper
def translate_enum(object, enum_name)
I18n.t("activerecord.attributes.#{object.model_name.i18n_key}.#{enum_name.to_s.pluralize}.#{object.send(enum_name)}")
end
Then in my YML file :
fr:
activerecord:
attributes:
my_model:
my_enum_plural:
pending: "En cours"
accepted: "Accepté"
refused: "Refusé"
Model Order:
enum order_type: {normal: false, security: true}.freeze, _default: :normal
OrdersController
@order_type = Order.order_types.except(:security) unless secure_present # optional code
@order_type.transform_keys! { |key| I18n.t("orders.new.order_types.#{key}")}
Locales (Ukrainian)
uk:
orders:
new:
order_types:
normal: 'Звичайна'
security: 'Безпечна'
You can simply add a helper:
def my_something_list
modes = 'activerecord.attributes.mymodel.my_somethings'
I18n.t(modes).map {|k, v| [v, k]}
end
and set it up as usually:
en:
activerecord:
attributes:
mymodel:
my_somethings:
my_enum_value: "My enum Value!"
then use it with your select: my_something_list
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def self.enum(definitions)
defind_i18n_text(definitions) if definitions.delete(:_human)
super(definitions)
end
def self.defind_i18n_text(definitions)
scope = i18n_scope
definitions.each do |name, values|
next if name.to_s.start_with?('_')
define_singleton_method("human_#{name.to_s.tableize}") do
p values
values.map { |key, _value| [key, I18n.t("#{scope}.enums.#{model_name.i18n_key}.#{name}.#{key}")] }.to_h
end
define_method("human_#{name}") do
I18n.t("#{scope}.enums.#{model_name.i18n_key}.#{name}.#{send(name)}")
end
end
end
end
en:
activerecord:
enums:
mymodel:
my_somethings:
my_enum_value: "My enum Value!"
enum status: [:unread, :down], _human: true
Here is the simplest solution I have found.
Model file 'house.rb':
enum status: { unavailable: 0, available: 1 }
In the view, a simple_form select:
<%= simple_form_for(@house) do |f| %>
...
<%= f.input :status,
collection: House.statuses.keys.map { |s| [t("house_enum_status_#{s}"), s] }
...
<% end %>
The collection creates an array with [key, value] expected for the select, with the correct translation.
And here is both locales yml files used:
'fr.yml'
house_enum_status_unavailable: "Indisponible"
house_enum_status_available: "Disponible"
'en.yml'
house_enum_status_unavailable: "Not available"
house_enum_status_available: "Available"
© 2022 - 2024 — McMap. All rights reserved.
{locale}.activerecord.attributes.{model}.{attribute}
and wrote at_enum(model, enum, value)
helper method so the enum translations would be adjacent to the label translation – Suavity