When localizing model attributes via I18n you may run into errors like this:
I18n::InvalidPluralizationData: translation data { ... } can not be used with :count => 1. key 'one' is missing.
They seem to appear out of the blue and the error message is more confusing than helpful.
TL;DR A model (e.g. Post
) is lacking an attribute (e.g. thread
) translation.
Fix it by adding a translation for that model's attribute (attributes.post.thread
). The error message reveals the (wrongly) located I18n data (from attributes.thread
).
Background
When you run into this, you probably introduced a new model that has attributes like these:
class User < ApplicationRecord
has_many :posts
end
class Post < ApplicationRecord
belongs_to :user
validates :user, presence: true # Default for belongs_to on Rails 5+
end
I18n has the feature of falling back one level to look up translations, if it can't find one. This allows you to define common attributes like updated_at
for all models on the activerecord.attributes
level (or activemodel.attributes
) instead of having the same translation string in each model's attribute block:
# I18n file like en.yml
en:
activerecord:
attributes:
updated_at: Last change
user:
name: Name
role: Access level
# updated_at not needed here, Rails will use the definition from above
Another feature of I18n is
optional pluralization
Show archive.org snapshot
. When calling I18n.translate
with a :count
option, it will look for nested count instructions:
en:
thing:
zero: Things
one: Thing
other: Things
The problem
Now imagine creating a post without a user. It is invalid, and Rails will display an error message.
For generating that message, Rails will look up the post's user
attribute at the attributes.post
level first, and fall back to the generic attributes
, thus querying attributes.user
. Note that this is not the user attribute, but the user model!
Now it invokes the translation with count: 1
. I18n tries to pluralize the derived key (i.e. the user model, and fails. Instead of the pluralization keys one
, other
etc, it finds name
and role
.
Voilà:
>> Post.new(user: nil).valid?
I18n::InvalidPluralizationData: translation data {:name=>"Name", :role=>"Access level"} can not be used with :count => 1. Key 'one' is missing.
In this example, the validating the user
attribute fails and Rails tries to generate localized error messages on the Post
instance. The error message generator mistakenly attempts to incorporate the user model's attribute localization data and fails.
Solutions
Option A: No magic (preferred)
Define translations for associations to avoid Rails' fallback to allegedly "generic". In the above example, you need to add the key attributes.post.user
. Note that this is all about association names, so you must not use user_id
.
# I18n file like en.yml
en:
activerecord:
attributes:
updated_at: Last change
user:
name: Name
role: Access level
post:
user: Author
Option B: Use I18n internals
If applicable, you may define a "one
" key for each model inside attributes
, like so:
# I18n file like en.yml
en:
activerecord:
attributes:
updated_at: Last change
user:
one: Author
name: Name
role: Access level
post:
body: Message
While that would be possible, I am not convinced about that approach's readability.
Note that in both cases model_name.human
uses the models
I18n scope for resolution. You need to define your model names there.
Option C: A little magic
It might be an idea to use a Yaml reference and load all models into each model's attributes list. It will pollute your translation data with unneeded fields, though.
en:
models: &models
user:
one: Author
other: Authors
post:
one: Post
other: Posts
attributes: &attributes
user:
<<: *models
name: Name
role: Access level
post:
<<: *models
body: Message
activerecord:
models: *models
attributes: *attributes
activemodel:
models: *models
attributes: *attributes
Hints for debugging
Should you still encounter the error after following the instructions above, you probably did something wrong and want to debug this further.
Place a debugger in activemodel-x.x.x/lib/active_model/errors.rb#generate_message
(inside i18n you will find upper level keys only which aren't helpful anymore):
def generate_message(attribute, type = :invalid, options = {})
# many things happen here
I18n.translate(key, options)
rescue
binding.pry
end
You can then inspect the key
attribute which will tell you something like:
activerecord.errors.models.organisation/membership.attributes.user.required
This means the attribute user
is missing for organisation/membership
and can be solved by adding these lines to your translation files:
en:
activerecord:
attributes:
organisation/membership:
user:
one: User
other: Users