Posted almost 2 years ago. Visible to the public. Repeats.

Dealing with I18n::InvalidPluralizationData errors

When localizing model attributes via I18n you may run into errors like this:

Copy
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:

Copy
class User < ApplicationRecord has_many :posts end
Copy
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:

Copy
# 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. When calling I18n.translate with a :count option, it will look for nested count instructions:

Copy
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à:

Copy
>> 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.

Copy
# 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:

Copy
# 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.

Copy
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):

Copy
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:

Copy
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:

Copy
en: activerecord: attributes: organisation/membership: user: one: User other: Users
Growing Rails Applications in Practice
Check out our new e-book:
Learn to structure large Ruby on Rails codebases with the tools you already know and love.

Owner of this card:

Avatar
Arne Hartherz
Last edit:
10 months ago
by Arne Hartherz
About this deck:
We are makandra and do test-driven, agile Ruby on Rails software development.
License for source code
Posted by Arne Hartherz to makandra dev
This website uses cookies to improve usability and analyze traffic.
Accept or learn more