i18n Pluralization

Ruby on-RailsInternationalizationRails I18nPluralizePlural

Ruby on-Rails Problem Overview


I want to be able to translate pluralized strings in i18n in rails. A string can be :

You have 2 kids

or

You have 1 kid

I know that I can use pluralize helper method, but I want to embed this in i18n translations so that I don't have to mess up with my views at any point in the future. I read that :count is somehow used in translations for plural, but I can't find any real resources on how it gets implemented.

Notice that I know that I can pass a variable in a translation string. I also tried something like :

<%= t 'misc.kids', :kids_num => pluralize(1, 'kid') %>

Which works fine, but has a fundamental problem of the same idea. I need to specify the string 'kid' in the pluralize helper. I don't want to do that because it will lead to view problems in the future. Instead I want to keep everything in the translation and nothing in the view.

How can I do that ?

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

Try this:

en.yml :

en:
  misc:
    kids:
      zero: no kids
      one: 1 kid
   	  other: %{count} kids

In a view:

You have <%= t('misc.kids', :count => 4) %>

Updated answer for languages with multiple pluralization (tested with Rails 3.0.7):

File config/initializers/pluralization.rb:

require "i18n/backend/pluralization" 
I18n::Backend::Simple.send(:include, I18n::Backend::Pluralization)

File config/locales/plurals.rb:

{:ru => 
  { :i18n => 
    { :plural => 
      { :keys => [:one, :few, :other],
        :rule => lambda { |n| 
          if n == 1
            :one
          else
            if [2, 3, 4].include?(n % 10) && 
               ![12, 13, 14].include?(n % 100) && 
               ![22, 23, 24].include?(n % 100)
               
              :few 
            else
              :other 
            end
          end
        } 
      } 
    } 
  } 
}

#More rules in this file: https://github.com/svenfuchs/i18n/blob/master/test/test_data/locales/plurals.rb
#(copy the file into `config/locales`)

File config/locales/en.yml:

en:
  kids:
    zero: en_zero
    one: en_one
    other: en_other
	

File config/locales/ru.yml:

ru:
  kids:
    zero: ru_zero
    one: ru_one
    few: ru_few
    other: ru_other

Test:

$ rails c
>> I18n.translate :kids, :count => 1
=> "en_one"
>> I18n.translate :kids, :count => 3
=> "en_other"
>> I18n.locale = :ru
=> :ru
>> I18n.translate :kids, :count => 1
=> "ru_one"
>> I18n.translate :kids, :count => 3
=> "ru_few"  #works! yay! 
>> I18n.translate :kids, :count => 5
=> "ru_other"  #works! yay! 




Solution 2 - Ruby on-Rails

I hope Russian-speaking Ruby on Rails programmers could find this. Just want to share my own very precise Russian pluralization formula. It based on Unicode Specs. Here is contents of config/locales/plurals.rb file only, everything else should be done as same as in answer above.

{:ru => 
  { :i18n => 
    { :plural => 
      { :keys => [:zero, :one, :few, :many],
        :rule => lambda { |n| 
          if n == 0
            :zero
          elsif
            ( ( n % 10 ) == 1 ) && ( ( n % 100 != 11 ) )
            # 1, 21, 31, 41, 51, 61...
            :one
          elsif
            ( [2, 3, 4].include?(n % 10) \
            && ![12, 13, 14].include?(n % 100) )
            # 2-4, 22-24, 32-34...
            :few
          elsif ( (n % 10) == 0 || \
            ![5, 6, 7, 8, 9].include?(n % 10) || \
            ![11, 12, 13, 14].include?(n % 100) )
            # 0, 5-20, 25-30, 35-40...
            :many
          end
        } 
      } 
    } 
  } 
}

Native speakers may enjoy cases such as 111 and 121. And here the test results:

  • zero: 0 запросов/куриц/яблок
  • one: 1 запрос/курица/яблоко
  • few: 3 запроса/курицы/яблока
  • many: 5 запросов/куриц/яблок
  • one: 101 запрос/курица/яблоко
  • few: 102 запроса/курицы/яблока
  • many: 105 запросов/куриц/яблок
  • many: 111 запросов/куриц/яблок
  • many: 119 запросов/куриц/яблок
  • one: 121 запрос/курица/яблоко
  • few: 122 запроса/курицы/яблока
  • many: 125 запросов/куриц/яблок

Thanks for initial answer!

Solution 3 - Ruby on-Rails

First, remember that number of plural forms depends on language, for English there are two, for Romanian there are 3 and for Arabic there are 6 !.

If you want to be able to properly use plural forms you have to use gettext.

For Ruby and rails you should check this http://www.yotabanana.com/hiki/ruby-gettext-howto-rails.html

Solution 4 - Ruby on-Rails

Rails 3 handles this robustly with CLDR consideration and count interpolation variable. See http://guides.rubyonrails.org/i18n.html#pluralization

# in view
t('actors', :count => @movie.actors.size)

# locales file, i.e. config/locales/en.yml
en:
  actors:
    one: Actor
    other: Actors

Solution 5 - Ruby on-Rails

English

It just works out of the box

en.yml:

en:
  kid:
    one: '1 kid'
    other: '%{count} kids'

Usage (you can skip I18n in a view file, of course):

> I18n.t :kid, count: 1
 => "1 kid"

> I18n.t :kid, count: 3
 => "3 kids"
Russian (and other languages with multiple plural forms)

Install rails-18n gem and add translations to your .yml files as in the example:

ru.yml:

ru:
  kid:
    zero: 'нет детей'
    one: '%{count} ребенок'
    few: '%{count} ребенка'
    many: '%{count} детей'
    other: 'дети'

Usage:

> I18n.t :kid, count: 0
 => "нет детей"

> I18n.t :kid, count: 1
 => "1 ребенок"

> I18n.t :kid, count: 3
 => "3 ребенка"

> I18n.t :kid, count: 5
 => "5 детей"

> I18n.t :kid, count: 21
 => "21 ребенок"

> I18n.t :kid, count: 114
 => "114 детей"

> I18n.t :kid, count: ''
 => "дети"

Solution 6 - Ruby on-Rails

There is actually an alternative to the cumbersome i18n approach. The solution is called Tr8n.

Your above code would simply be:

 <%= tr("You have {num || kid}", num: 1) %>

That's it. No need to extract your keys from your code and maintain them in resource bundles, no need to implement pluralization rules for each language. Tr8n comes with numeric context rules for all language. It also comes with gender rules, list rules and language cases.

The full definition of the above translation key would actually look like this:

 <%= tr("You have {num:number || one: kid, other: kids}", num: 1) %>

But since we want to save space and time, num is automatically mapped to numeric rules and there is no need to provide all options for the rule values. Tr8n comes with pluralizers and inflectors that will do the work for you on the fly.

The translation for your key in Russian, would simply be:

 "У вас есть {num || ребенок, ребенка, детей}"

By the way, your translation would be inaccurate in languages that have gender specific rules. For example, in Hebrew, you would actually have to specify at least 2 translations for your example, as "You" would be different based on the gender of the viewing user. Tr8n handles it very well. Here is a transliteration of Hebrew translations:

 "Yesh leha yeled ahad" with {context: {viewing_user: male, num: one}}
 "Yesh leha {num} yeladim" with {context: {viewing_user: male, num: other}}
 "Yesh lah yeled ahad" with {context: {viewing_user: female, num: one}}
 "Yesh lah {num} yeladim" with {context: {viewing_user: female, num: other}}

So your single English key, in this case, needs 4 translations. All translations are done in context - you don't have to break the sentence. Tr8n has a mechanism to map one key to multiple translations based on the language and context - all done on the fly.

One last thing. What if you had to make the count part bold? It would simply be:

<%= tr("You have [bold: {num || kid}]", num: 1, bold: "<strong>{$0}</strong>") %>

Just in case you want to redefine your "bold" later - it would be very easy - you won't have to go through all your YAML files and change them - you just do it in one place.

To learn more, please take a look here:

https://github.com/tr8n/tr8n_rails_clientsdk

Disclosure: I am the developer and the maintainer of Tr8n framework and all its libraries.

Solution 7 - Ruby on-Rails

About Redmine. If you copy pluralization file rules in config/locales/ as plurals.rb and other not same as locale name (ru.rb, pl.rb .. etc) these not work. You must rename file rules to 'locale'.rb or change method in file /lib/redmine/i18n.rb

def init_translations(locale)
  locale = locale.to_s
  paths = ::I18n.load_path.select {|path| File.basename(path, '.*') == locale}
  load_translations(paths)
  translations[locale] ||= {}
end

and if you have older redmine, add

module Implementation
        include ::I18n::Backend::Base
        **include ::I18n::Backend::Pluralization**

Solution 8 - Ruby on-Rails

I've found a very good resource with description of locales http://translate.sourceforge.net/wiki/l10n/pluralforms ( alt link ).

For example, for ukrainian, russian, belarusian and few other languages the expression will look like this plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSpyrosView Question on Stackoverflow
Solution 1 - Ruby on-RailsZabbaView Answer on Stackoverflow
Solution 2 - Ruby on-RailssashaegorovView Answer on Stackoverflow
Solution 3 - Ruby on-RailssorinView Answer on Stackoverflow
Solution 4 - Ruby on-RailsLuke WView Answer on Stackoverflow
Solution 5 - Ruby on-RailsinstalleroView Answer on Stackoverflow
Solution 6 - Ruby on-RailsMichael BerkovichView Answer on Stackoverflow
Solution 7 - Ruby on-RailsVladimir KovalevView Answer on Stackoverflow
Solution 8 - Ruby on-RailscuddlemeisterView Answer on Stackoverflow