Rails 4 Active Record Enums are great, but what is the right pattern for translating with i18n?
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) %>
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:
https://github.com/viniciusoyama/translated_attribute_value
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
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
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"
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