Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

initial implementation of a Pluralization module

  • Loading branch information...
commit 9ca4c9ed52d4706566a6abeb2d78722dcc5d4764 1 parent 98bd63c
@svenfuchs authored
View
28 lib/i18n/backend/pluralization.rb
@@ -0,0 +1,28 @@
+module I18n
+ module Backend
+ module Pluralization
+ def pluralize(locale, entry, count)
+ return entry unless entry.is_a?(Hash) and count
+
+ pluralizer = pluralizer(locale)
+ if pluralizer.respond_to?(:call)
+ key = count == 0 && entry.has_key?(:zero) ? :zero : pluralizer.call(count)
+ raise InvalidPluralizationData.new(entry, count) unless entry.has_key?(key)
+ entry[key]
+ else
+ super
+ end
+ end
+
+ protected
+
+ def pluralizers
+ @pluralizers ||= {}
+ end
+
+ def pluralizer(locale)
+ pluralizers[locale] ||= lookup(locale, :"i18n.pluralize")
+ end
+ end
+ end
+end
View
72 test/backend/pluralization/api_test.rb
@@ -0,0 +1,72 @@
+require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
+require 'i18n/backend/pluralization'
+
+module PluralizationSetup
+ def setup
+ super
+ class << I18n.backend
+ include I18n::Backend::Pluralization
+ end
+ I18n.load_path << locales_dir + '/plurals.rb'
+ end
+end
+
+class I18nPluralizationBackendApiBasicsTest < Test::Unit::TestCase
+ include PluralizationSetup
+ include Tests::Backend::Api::Basics
+end
+
+class I18nPluralizationBackendApiTranslateTest < Test::Unit::TestCase
+ include Tests::Backend::Simple::Setup::Base
+ include PluralizationSetup
+ include Tests::Backend::Api::Translation
+end
+
+class I18nPluralizationBackendApiInterpolateTest < Test::Unit::TestCase
+ include Tests::Backend::Simple::Setup::Base
+ include PluralizationSetup
+ include Tests::Backend::Api::Interpolation
+end
+
+class I18nPluralizationBackendApiLambdaTest < Test::Unit::TestCase
+ include Tests::Backend::Simple::Setup::Base
+ include PluralizationSetup
+ include Tests::Backend::Api::Lambda
+end
+
+class I18nPluralizationBackendApiTranslateLinkedTest < Test::Unit::TestCase
+ include Tests::Backend::Simple::Setup::Base
+ include PluralizationSetup
+ include Tests::Backend::Api::Link
+end
+
+class I18nPluralizationBackendApiPluralizeTest < Test::Unit::TestCase
+ include PluralizationSetup
+ include Tests::Backend::Simple::Setup::Base
+ include Tests::Backend::Api::Pluralization
+end
+
+class I18nPluralizationBackendApiLocalizeDateTest < Test::Unit::TestCase
+ include PluralizationSetup
+ include Tests::Backend::Simple::Setup::Localization
+ include Tests::Backend::Api::Localization::Date
+end
+
+class I18nPluralizationBackendApiLocalizeDateTimeTest < Test::Unit::TestCase
+ include PluralizationSetup
+ include Tests::Backend::Simple::Setup::Localization
+ include Tests::Backend::Api::Localization::DateTime
+end
+
+class I18nPluralizationBackendApiLocalizeTimeTest < Test::Unit::TestCase
+ include PluralizationSetup
+ include Tests::Backend::Simple::Setup::Localization
+ include Tests::Backend::Api::Localization::Time
+end
+
+class I18nPluralizationBackendApiLocalizeLambdaTest < Test::Unit::TestCase
+ include PluralizationSetup
+ include Tests::Backend::Simple::Setup::Localization
+ include Tests::Backend::Api::Localization::Lambda
+end
+
View
39 test/backend/pluralization/pluralization_test.rb
@@ -0,0 +1,39 @@
+require File.expand_path(File.dirname(__FILE__) + '/../../test_helper')
+require 'i18n/backend/pluralization'
+
+class I18nPluralizationBackendTest < Test::Unit::TestCase
+ def setup
+ I18n.backend = I18n::Backend::Simple.new
+ class << I18n.backend
+ include I18n::Backend::Pluralization
+ end
+ @pluralizer = lambda { |n| n == 1 ? :one : n == 0 || (2..10).include?(n % 100) ? :few : (11..19).include?(n % 100) ? :many : :other }
+ backend_store_translations(:foo, :i18n => { :pluralize => @pluralizer })
+ @entry = { :zero => 'zero', :one => 'one', :few => 'few', :many => 'many', :other => 'other' }
+ end
+
+ define_method :"test: pluralization picks a pluralizer from :'i18n.pluralize'" do
+ assert_equal @pluralizer, I18n.backend.send(:pluralizer, :foo)
+ end
+
+ define_method :"test: pluralization picks :one for 1" do
+ assert_equal 'one', I18n.t(:count => 1, :default => @entry, :locale => :foo)
+ end
+
+ define_method :"test: pluralization picks :few for 2" do
+ assert_equal 'few', I18n.t(:count => 2, :default => @entry, :locale => :foo)
+ end
+
+ define_method :"test: pluralization picks :many for 11" do
+ assert_equal 'many', I18n.t(:count => 11, :default => @entry, :locale => :foo)
+ end
+
+ define_method :"test: pluralization picks zero for 0 if the key is contained in the data" do
+ assert_equal 'zero', I18n.t(:count => 0, :default => @entry, :locale => :foo)
+ end
+
+ define_method :"test: pluralization picks few for 0 if the key is not contained in the data" do
+ @entry.delete(:zero)
+ assert_equal 'few', I18n.t(:count => 0, :default => @entry, :locale => :foo)
+ end
+end
View
110 test/fixtures/locales/plurals.rb
@@ -0,0 +1,110 @@
+{
+ :af => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :am => { :i18n => { :pluralize => lambda { |n| (0..1).include?(n) ? :one : :other } } },
+ :ar => { :i18n => { :pluralize => lambda { |n| n == 0 ? :zero : n == 1 ? :one : n == 2 ? :two : (3..10).include?(n % 100) ? :few : (11..99).include?(n % 100) ? :many : :other } } },
+ :az => { :i18n => { :pluralize => lambda { |n| :other } } },
+ :be => { :i18n => { :pluralize => lambda { |n| n % 10 == 1 && n % 100 != 11 ? :one : (2..4).include?(n % 10) && !(12..14).include?(n % 100) ? :few : n % 10 == 0 || (5..9).include?(n % 10) || (11..14).include?(n % 100) ? :many : :other } } },
+ :bg => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :bh => { :i18n => { :pluralize => lambda { |n| (0..1).include?(n) ? :one : :other } } },
+ :bn => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :bo => { :i18n => { :pluralize => lambda { |n| :other } } },
+ :bs => { :i18n => { :pluralize => lambda { |n| n % 10 == 1 && n % 100 != 11 ? :one : (2..4).include?(n % 10) && !(12..14).include?(n % 100) ? :few : n % 10 == 0 || (5..9).include?(n % 10) || (11..14).include?(n % 100) ? :many : :other } } },
+ :ca => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :cs => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : (2..4).include?(n) ? :few : :other } } },
+ :cy => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : n == 2 ? :two : n == 8 || n == 11 ? :many : :other } } },
+ :da => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :de => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :dz => { :i18n => { :pluralize => lambda { |n| :other } } },
+ :el => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :en => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :eo => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :es => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :et => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :eu => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :fa => { :i18n => { :pluralize => lambda { |n| :other } } },
+ :fi => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :fil => { :i18n => { :pluralize => lambda { |n| (0..1).include?(n) ? :one : :other } } },
+ :fo => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :fr => { :i18n => { :pluralize => lambda { |n| n && n != 2 ? :one : :other } } },
+ :fur => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :fy => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :ga => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : n == 2 ? :two : :other } } },
+ :gl => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :gu => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :guw => { :i18n => { :pluralize => lambda { |n| (0..1).include?(n) ? :one : :other } } },
+ :ha => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :he => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :hi => { :i18n => { :pluralize => lambda { |n| (0..1).include?(n) ? :one : :other } } },
+ :hr => { :i18n => { :pluralize => lambda { |n| n % 10 == 1 && n % 100 != 11 ? :one : (2..4).include?(n % 10) && !(12..14).include?(n % 100) ? :few : n % 10 == 0 || (5..9).include?(n % 10) || (11..14).include?(n % 100) ? :many : :other } } },
+ :hu => { :i18n => { :pluralize => lambda { |n| :other } } },
+ :id => { :i18n => { :pluralize => lambda { |n| :other } } },
+ :is => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :it => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :iw => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :ja => { :i18n => { :pluralize => lambda { |n| :other } } },
+ :jv => { :i18n => { :pluralize => lambda { |n| :other } } },
+ :ka => { :i18n => { :pluralize => lambda { |n| :other } } },
+ :km => { :i18n => { :pluralize => lambda { |n| :other } } },
+ :kn => { :i18n => { :pluralize => lambda { |n| :other } } },
+ :ko => { :i18n => { :pluralize => lambda { |n| :other } } },
+ :ku => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :lb => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :ln => { :i18n => { :pluralize => lambda { |n| (0..1).include?(n) ? :one : :other } } },
+ :lt => { :i18n => { :pluralize => lambda { |n| n % 10 == 1 && !(11..19).include?(n % 100) ? :one : (2..9).include?(n % 10) && !(11..19).include?(n % 100) ? :few : :other } } },
+ :lv => { :i18n => { :pluralize => lambda { |n| n == 0 ? :zero : n % 10 == 1 && n % 100 != 11 ? :one : :other } } },
+ :mg => { :i18n => { :pluralize => lambda { |n| (0..1).include?(n) ? :one : :other } } },
+ :mk => { :i18n => { :pluralize => lambda { |n| n % 10 == 1 ? :one : :other } } },
+ :ml => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :mn => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :mo => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : n == 0 ? :few : :other } } },
+ :mr => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :ms => { :i18n => { :pluralize => lambda { |n| :other } } },
+ :mt => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : n == 0 || (2..10).include?(n % 100) ? :few : (11..19).include?(n % 100) ? :many : :other } } },
+ :my => { :i18n => { :pluralize => lambda { |n| :other } } },
+ :nah => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :nb => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :ne => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :nl => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :nn => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :no => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :nso => { :i18n => { :pluralize => lambda { |n| (0..1).include?(n) ? :one : :other } } },
+ :om => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :or => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :pa => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :pap => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :pl => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : (2..4).include?(n % 10) && !(12..14).include?(n % 100) && !(22..24).include?(n % 100) ? :few : :other } } },
+ :ps => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :pt => { :i18n => { :pluralize => lambda { |n| (0..1).include?(n) ? :one : :other } } },
+ :"pt-PT" => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },

@svenfuchs can you point to where in the CLDR they distinguish between :pt and :"pt-PT"? I'm not doubting the validity of this rule; I'm just trying to understand where it came from. I'm asking because I want to do a JS port of this logic and I want to get my sources right. I don't see the distinction in the Language Plural Rules table.

@svenfuchs Owner

@jamesarosen I actually don't remember where i got this from. I might have copied this file from somewhere else where a portuguese speaking person added this. The cldr table that you link specifies 0 as "other" for portuguese, so i would guess that maybe for portuguese as spoken in brazil etc it's different? I am sorry i can't give you a better answer :(

That's OK. I'll use the official CLDR source for now. If someone from Portugal chimes in on this topic I'm happy to change it. (They should also submit a bug to CLDR.) Thanks for the quick response :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ :ro => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : n == 0 ? :few : :other } } },
+ :ru => { :i18n => { :pluralize => lambda { |n| n % 10 == 1 && n % 100 != 11 ? :one : (2..4).include?(n % 10) && !(12..14).include?(n % 100) ? :few : n % 10 == 0 || (5..9).include?(n % 10) || (11..14).include?(n % 100) ? :many : :other } } },
+ :se => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : n == 2 ? :two : :other } } },
+ :sh => { :i18n => { :pluralize => lambda { |n| n % 10 == 1 && n % 100 != 11 ? :one : (2..4).include?(n % 10) && !(12..14).include?(n % 100) ? :few : n % 10 == 0 || (5..9).include?(n % 10) || (11..14).include?(n % 100) ? :many : :other } } },
+ :sk => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : (2..4).include?(n) ? :few : :other } } },
+ :sl => { :i18n => { :pluralize => lambda { |n| n % 100 == 1 ? :one : n % 100 == 2 ? :two : (3..4).include?(n % 100) ? :few : :other } } },
+ :sma => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : n == 2 ? :two : :other } } },
+ :smi => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : n == 2 ? :two : :other } } },
+ :smj => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : n == 2 ? :two : :other } } },
+ :smn => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : n == 2 ? :two : :other } } },
+ :sms => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : n == 2 ? :two : :other } } },
+ :so => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :sq => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :sr => { :i18n => { :pluralize => lambda { |n| n % 10 == 1 && n % 100 != 11 ? :one : (2..4).include?(n % 10) && !(12..14).include?(n % 100) ? :few : n % 10 == 0 || (5..9).include?(n % 10) || (11..14).include?(n % 100) ? :many : :other } } },
+ :sv => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :sw => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :ta => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :te => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :th => { :i18n => { :pluralize => lambda { |n| :other } } },
+ :ti => { :i18n => { :pluralize => lambda { |n| (0..1).include?(n) ? :one : :other } } },
+ :tk => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :tl => { :i18n => { :pluralize => lambda { |n| (0..1).include?(n) ? :one : :other } } },
+ :to => { :i18n => { :pluralize => lambda { |n| :other } } },
+ :tr => { :i18n => { :pluralize => lambda { |n| :other } } },
+ :uk => { :i18n => { :pluralize => lambda { |n| n % 10 == 1 && n % 100 != 11 ? :one : (2..4).include?(n % 10) && !(12..14).include?(n % 100) ? :few : n % 10 == 0 || (5..9).include?(n % 10) || (11..14).include?(n % 100) ? :many : :other } } },
+ :ur => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } },
+ :vi => { :i18n => { :pluralize => lambda { |n| :other } } },
+ :wa => { :i18n => { :pluralize => lambda { |n| (0..1).include?(n) ? :one : :other } } },
+ :yo => { :i18n => { :pluralize => lambda { |n| :other } } },
+ :zh => { :i18n => { :pluralize => lambda { |n| :other } } },
+ :zu => { :i18n => { :pluralize => lambda { |n| n == 1 ? :one : :other } } }
+}
@jamesarosen

@svenfuchs can you point to where in the CLDR they distinguish between :pt and :"pt-PT"? I'm not doubting the validity of this rule; I'm just trying to understand where it came from. I'm asking because I want to do a JS port of this logic and I want to get my sources right. I don't see the distinction in the Language Plural Rules table.

@svenfuchs

@jamesarosen I actually don't remember where i got this from. I might have copied this file from somewhere else where a portuguese speaking person added this. The cldr table that you link specifies 0 as "other" for portuguese, so i would guess that maybe for portuguese as spoken in brazil etc it's different? I am sorry i can't give you a better answer :(

@jamesarosen

That's OK. I'll use the official CLDR source for now. If someone from Portugal chimes in on this topic I'm happy to change it. (They should also submit a bug to CLDR.) Thanks for the quick response :)

Please sign in to comment.
Something went wrong with that request. Please try again.