diff --git a/lib/i18n/backend/pluralization.rb b/lib/i18n/backend/pluralization.rb index 381ac13f..b6026571 100644 --- a/lib/i18n/backend/pluralization.rb +++ b/lib/i18n/backend/pluralization.rb @@ -16,51 +16,26 @@ module Backend module Pluralization # Overwrites the Base backend translate method so that it will check the # translation meta data space (:i18n) for a locale specific pluralization - # rule and use it to pluralize the given entry. I.e., the library expects + # rule and use it to pluralize the given entry. I.e. the library expects # pluralization rules to be stored at I18n.t(:'i18n.plural.rule') # # Pluralization rules are expected to respond to #call(count) and - # return a pluralization key. Valid keys depend on the pluralization - # rules for the locale, as defined in the CLDR. - # As of v41, 6 locale-specific plural categories are defined: - # :few, :many, :one, :other, :two, :zero + # return a pluralization key. Valid keys depend on the translation data + # hash (entry) but it is generally recommended to follow CLDR's style, + # i.e., return one of the keys :zero, :one, :few, :many, :other. # - # n.b., The :one plural category does not imply the number 1. - # Instead, :one is a category for any number that behaves like 1 in - # that locale. For example, in some locales, :one is used for numbers - # that end in "1" (like 1, 21, 151) but that don't end in - # 11 (like 11, 111, 10311). - # Similar notes apply to the :two, and :zero plural categories. - # - # If you want to have different strings for the categories of count == 0 - # (e.g. "I don't have any cars") or count == 1 (e.g. "I have a single car") - # use the explicit `"0"` and `"1"` keys. - # https://unicode-org.github.io/cldr/ldml/tr35-numbers.html#Explicit_0_1_rules + # The :zero key is always picked directly when count equals 0 AND the + # translation data has the key :zero. This way translators are free to + # either pick a special :zero translation even for languages where the + # pluralizer does not return a :zero key. def pluralize(locale, entry, count) return entry unless entry.is_a?(Hash) && count pluralizer = pluralizer(locale) if pluralizer.respond_to?(:call) - # "0" and "1" are special cases - # https://unicode-org.github.io/cldr/ldml/tr35-numbers.html#Explicit_0_1_rules - if count == 0 || count == 1 - value = entry[symbolic_count(count)] - return value if value - end - - # Lateral Inheritance of "count" attribute (http://www.unicode.org/reports/tr35/#Lateral_Inheritance): - # > If there is no value for a path, and that path has a [@count="x"] attribute and value, then: - # > 1. If "x" is numeric, the path falls back to the path with [@count=«the plural rules category for x for that locale»], within that the same locale. - # > 2. If "x" is anything but "other", it falls back to a path [@count="other"], within that the same locale. - # > 3. If "x" is "other", it falls back to the path that is completely missing the count item, within that the same locale. - # Note: We don't yet implement #3 above, since we haven't decided how lateral inheritance attributes should be represented. - plural_rule_category = pluralizer.call(count) - - value = if entry.has_key?(plural_rule_category) || entry.has_key?(:other) - entry[plural_rule_category] || entry[:other] - else - raise InvalidPluralizationData.new(entry, count, plural_rule_category) - end + key = count == 0 && entry.has_key?(:zero) ? :zero : pluralizer.call(count) + raise InvalidPluralizationData.new(entry, count, key) unless entry.has_key?(key) + entry[key] else super end @@ -68,23 +43,13 @@ def pluralize(locale, entry, count) protected - def pluralizers - @pluralizers ||= {} - end - - def pluralizer(locale) - pluralizers[locale] ||= I18n.t(:'i18n.plural.rule', :locale => locale, :resolve => false) - end - - private + def pluralizers + @pluralizers ||= {} + end - # Normalizes categories of 0.0 and 1.0 - # and returns the symbolic version - def symbolic_count(count) - count = 0 if count == 0 - count = 1 if count == 1 - count.to_s.to_sym - end + def pluralizer(locale) + pluralizers[locale] ||= I18n.t(:'i18n.plural.rule', :locale => locale, :resolve => false) + end end end end diff --git a/test/backend/pluralization_test.rb b/test/backend/pluralization_test.rb index c49b492c..d955818e 100644 --- a/test/backend/pluralization_test.rb +++ b/test/backend/pluralization_test.rb @@ -9,22 +9,16 @@ class Backend < I18n::Backend::Simple def setup super I18n.backend = Backend.new - @rule = lambda { |n| n % 10 == 1 && n % 100 != 11 ? :one : n == 0 || (2..10).include?(n % 100) ? :few : (11..19).include?(n % 100) ? :many : :other } + @rule = lambda { |n| n == 1 ? :one : n == 0 || (2..10).include?(n % 100) ? :few : (11..19).include?(n % 100) ? :many : :other } store_translations(:xx, :i18n => { :plural => { :rule => @rule } }) - @entry = { :"0" => 'none', :"1" => 'single', :one => 'one', :few => 'few', :many => 'many', :other => 'other' } + @entry = { :zero => 'zero', :one => 'one', :few => 'few', :many => 'many', :other => 'other' } end test "pluralization picks a pluralizer from :'i18n.pluralize'" do assert_equal @rule, I18n.backend.send(:pluralizer, :xx) end - test "pluralization picks the explicit 1 rule for count == 1, the explicit rule takes priority over the matching :one rule" do - assert_equal 'single', I18n.t(:count => 1, :default => @entry, :locale => :xx) - assert_equal 'single', I18n.t(:count => 1.0, :default => @entry, :locale => :xx) - end - - test "pluralization picks :one for 1, since in this case that is the matching rule for 1 (when there is no explicit 1 rule)" do - @entry.delete(:"1") + test "pluralization picks :one for 1" do assert_equal 'one', I18n.t(:count => 1, :default => @entry, :locale => :xx) end @@ -36,22 +30,15 @@ def setup assert_equal 'many', I18n.t(:count => 11, :default => @entry, :locale => :xx) end - test "pluralization picks explicit 0 rule for count == 0, since the explicit rule takes priority over the matching :few rule" do - assert_equal 'none', I18n.t(:count => 0, :default => @entry, :locale => :xx) - assert_equal 'none', I18n.t(:count => 0.0, :default => @entry, :locale => :xx) - assert_equal 'none', I18n.t(:count => -0, :default => @entry, :locale => :xx) + 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 => :xx) end - test "pluralization picks :few for 0 (when there is no explicit 0 rule)" do - @entry.delete(:"0") + 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 => :xx) end - test "pluralization does Lateral Inheritance to :other to cover missing data" do - @entry.delete(:many) - assert_equal 'other', I18n.t(:count => 11, :default => @entry, :locale => :xx) - end - test "pluralization picks one for 1 if the entry has attributes hash on unknown locale" do @entry[:attributes] = { :field => 'field', :second => 'second' } assert_equal 'one', I18n.t(:count => 1, :default => @entry, :locale => :pirate)