Ryenski
Ryenski

Reputation: 9702

Rails i18n pluralization: Infinity

My app's pricing plan has some features that are unlimited. For example in the "Pro" version you get 10 forms, and in the "Unlimited" version you get unlimited forms.

This is represented in the Plan model by an integer or Float::Infinity.

pro = Plan.find_by name: 'Pro'
pro.forms_limit
#=> 10

unl = Plan.find_by name: 'Unlimited'
unl.forms_limit
#=> Float::INFINITY

In the view:

= t('.forms', count: plan.forms_limit)

I am trying to find an efficient way to interpolate this with I18n. What I'd like to do is:

plans:
  index:
    forms: 
      zero: No Forms
      one: 1 Form
      other: "%{count} Forms"
      infinity: Unlimited Forms

This will work, but it results in undesired output like:

"Infinity Forms"

Is there a way to structure this so that Infinity will interpolate "Unlimited" instead of "Infinity"?

Upvotes: 1

Views: 811

Answers (2)

James EJ
James EJ

Reputation: 2154

Define a custom I18n Backend in config/locales/locale.rb

module I18n
  module Backend
    module PluralizeInfinite
      def pluralize(local, entry, count)
        return super unless entry.is_a?(Hash) && entry.has_key?(:infinity)
        return super unless count&.infinite?

        entry[:infinity]
      end
    end
  end
end

I18n::Backend::Simple.send(:include, I18n::Backend::PluralizeInfinite)

This takes the same approach as I18n::Backend::Pluralization which is a Unicode CLDR backend.

Be sure to have tests that cover this behaviour if using this approach, as the I18n::Backend::Base#pluralize interface could change in future versions of Rails.

Upvotes: 0

Simple Lime
Simple Lime

Reputation: 11090

Create a file config/locales/plurals.rb with the following

{
  en: {
    i18n: {
      plural: {
        keys: [:one, :other, :infinity],
        rule: lambda do |n|
          if n == 1
            :one
          elsif n == Float::INFINITY
            :infinity
          else
            :other
          end
        end
      }
    }
  }
}

and then in my config/locales/en.yml, I have

en:
  forms:
    zero: 'No Forms'
    one: '1 Form'
    other: '%{count} Forms'
    infinity: 'Unlimited Forms'

added in config/initializers/locale.rb

I18n::Backend::Simple.send(:include, I18n::Backend::Pluralization)

and testing it in IRB

I18n.t(:forms, count: 0)
# => "No Forms"
I18n.t(:forms, count: 1)
# => "1 Form"
I18n.t(:forms, count: 10)
# => "10 Forms"
I18n.t(:forms, count: Float::INFINITY)
# => "Unlimited Forms"

What is this doing?

This isn't nearly as mysterious as I thought when. I first tried it (getting the idea from this related question.

When you include I18n::Backend::Pluralization it's going to start looking for a key i18n.plural.rule that should respond to call, anywhere in the load path. So the plurals.rb file name is unimportant, just need to make sure it's a file in I18n.load_path.

We make that file a ruby file with a hash so that we can set that i18n.plural.rule key to a lambda (so it responds to call) and then the lambda gets called with the count getting passed in. As pointed out there are

Upvotes: 3

Related Questions