Skip to content
This repository
Browse code

Make ActiveSupport::Inflector locale aware and multilingual

The Inflector is currently not very supportive of internationalized
websites. If a user wants to singularize and/or pluralize words based on
any locale other than English, they must define each case in locale
files. Rather than create large locale files with mappings between
singular and plural words, why not allow the Inflector to accept a
locale?

This patch makes ActiveSupport::Inflector locale aware and uses `:en`` unless
otherwise specified. Users will still be provided a list of English (:en)
inflections, but they may additionally define inflection rules for other
locales. Each list is kept separately and permanently. There is no reason to
limit users to one list of inflections:

    ActiveSupport::Inflector.inflections(:es) do |inflect|
      inflect.plural(/$/, 's')
      inflect.plural(/([^aeéiou])$/i, '\1es')
      inflect.plural(/([aeiou]s)$/i, '\1')
      inflect.plural(/z$/i, 'ces')
      inflect.plural(/á([sn])$/i, 'a\1es')
      inflect.plural(/é([sn])$/i, 'e\1es')
      inflect.plural(/í([sn])$/i, 'i\1es')
      inflect.plural(/ó([sn])$/i, 'o\1es')
      inflect.plural(/ú([sn])$/i, 'u\1es')

      inflect.singular(/s$/, '')
      inflect.singular(/es$/, '')

      inflect.irregular('el', 'los')
    end

    'ley'.pluralize(:es)   # => "leyes"
    'ley'.pluralize(:en)   # => "leys"
    'avión'.pluralize(:es) # => "aviones"
    'avión'.pluralize(:en) # => "avións"

A multilingual Inflector should be of use to anybody that is tasked with
internationalizing their Rails application.

Signed-off-by: David Celis <david@davidcelis.com>
  • Loading branch information...
commit 7db0b073fec6bc3e6f213b58c76e7f43fcc2ab97 1 parent d4433a3
David Celis authored July 29, 2012
22  activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -13,6 +13,11 @@ class String
13 13
   # the singular form will be returned if <tt>count == 1</tt>.
14 14
   # For any other value of +count+ the plural will be returned.
15 15
   #
  16
+  # If the optional parameter +locale+ is specified,
  17
+  # the word will be pluralized as a word of that language.
  18
+  # By default, this parameter is set to <tt>:en</tt>.
  19
+  # You must define your own inflection rules for languages other than English.
  20
+  #
16 21
   #   'post'.pluralize             # => "posts"
17 22
   #   'octopus'.pluralize          # => "octopi"
18 23
   #   'sheep'.pluralize            # => "sheep"
@@ -21,15 +26,23 @@ class String
21 26
   #   'CamelOctopus'.pluralize     # => "CamelOctopi"
22 27
   #   'apple'.pluralize(1)         # => "apple"
23 28
   #   'apple'.pluralize(2)         # => "apples"
24  
-  def pluralize(count = nil)
  29
+  #   'ley'.pluralize(:es)         # => "leyes"
  30
+  #   'ley'.pluralize(1, :es)      # => "ley"
  31
+  def pluralize(count = nil, locale = :en)
  32
+    locale = count if count.is_a?(Symbol)
25 33
     if count == 1
26 34
       self
27 35
     else
28  
-      ActiveSupport::Inflector.pluralize(self)
  36
+      ActiveSupport::Inflector.pluralize(self, locale)
29 37
     end
30 38
   end
31 39
 
32 40
   # The reverse of +pluralize+, returns the singular form of a word in a string.
  41
+  # 
  42
+  # If the optional parameter +locale+ is specified,
  43
+  # the word will be singularized as a word of that language.
  44
+  # By default, this paramter is set to <tt>:en</tt>.
  45
+  # You must define your own inflection rules for languages other than English.
33 46
   #
34 47
   #   'posts'.singularize            # => "post"
35 48
   #   'octopi'.singularize           # => "octopus"
@@ -37,8 +50,9 @@ def pluralize(count = nil)
37 50
   #   'word'.singularize             # => "word"
38 51
   #   'the blue mailmen'.singularize # => "the blue mailman"
39 52
   #   'CamelOctopi'.singularize      # => "CamelOctopus"
40  
-  def singularize
41  
-    ActiveSupport::Inflector.singularize(self)
  53
+  #   'leyes'.singularize(:es)       # => "ley"
  54
+  def singularize(locale = :en)
  55
+    ActiveSupport::Inflector.singularize(self, locale)
42 56
   end
43 57
 
44 58
   # +constantize+ tries to find a declared constant with the name specified
2  activesupport/lib/active_support/inflections.rb
... ...
@@ -1,7 +1,7 @@
1 1
 require 'active_support/inflector/inflections'
2 2
 
3 3
 module ActiveSupport
4  
-  Inflector.inflections do |inflect|
  4
+  Inflector.inflections(:en) do |inflect|
5 5
     inflect.plural(/$/, 's')
6 6
     inflect.plural(/s$/i, 's')
7 7
     inflect.plural(/^(ax|test)is$/i, '\1es')
23  activesupport/lib/active_support/inflector/inflections.rb
... ...
@@ -1,13 +1,15 @@
1 1
 require 'active_support/core_ext/array/prepend_and_append'
  2
+require 'active_support/i18n'
2 3
 
3 4
 module ActiveSupport
4 5
   module Inflector
5 6
     extend self
6 7
 
7 8
     # A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional
8  
-    # inflection rules.
  9
+    # inflection rules. If passed an optional locale, rules for other languages can be specified. The default locale is
  10
+    # <tt>:en</tt>. Only rules for English are provided.
9 11
     #
10  
-    #   ActiveSupport::Inflector.inflections do |inflect|
  12
+    #   ActiveSupport::Inflector.inflections(:en) do |inflect|
11 13
     #     inflect.plural /^(ox)$/i, '\1\2en'
12 14
     #     inflect.singular /^(ox)en/i, '\1'
13 15
     #
@@ -20,8 +22,9 @@ module Inflector
20 22
     # pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may
21 23
     # already have been loaded.
22 24
     class Inflections
23  
-      def self.instance
24  
-        @__instance__ ||= new
  25
+      def self.instance(locale = :en)
  26
+        @__instance__ ||= Hash.new { |h, k| h[k] = new }
  27
+        @__instance__[locale]
25 28
       end
26 29
 
27 30
       attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
@@ -160,16 +163,18 @@ def clear(scope = :all)
160 163
     end
161 164
 
162 165
     # Yields a singleton instance of Inflector::Inflections so you can specify additional
163  
-    # inflector rules.
  166
+    # inflector rules. If passed an optional locale, rules for other languages can be specified.
  167
+    # If not specified, defaults to <tt>:en</tt>. Only rules for English are provided.
  168
+    # 
164 169
     #
165  
-    #   ActiveSupport::Inflector.inflections do |inflect|
  170
+    #   ActiveSupport::Inflector.inflections(:en) do |inflect|
166 171
     #     inflect.uncountable "rails"
167 172
     #   end
168  
-    def inflections
  173
+    def inflections(locale = :en)
169 174
       if block_given?
170  
-        yield Inflections.instance
  175
+        yield Inflections.instance(locale)
171 176
       else
172  
-        Inflections.instance
  177
+        Inflections.instance(locale)
173 178
       end
174 179
     end
175 180
   end
22  activesupport/lib/active_support/inflector/methods.rb
@@ -10,31 +10,41 @@ module ActiveSupport
10 10
   #
11 11
   # The Rails core team has stated patches for the inflections library will not be accepted
12 12
   # in order to avoid breaking legacy applications which may be relying on errant inflections.
13  
-  # If you discover an incorrect inflection and require it for your application, you'll need
14  
-  # to correct it yourself (explained below).
  13
+  # If you discover an incorrect inflection and require it for your application or wish to
  14
+  # define rules for languages other than English, please correct or add them yourself (explained below).
15 15
   module Inflector
16 16
     extend self
17 17
 
18 18
     # Returns the plural form of the word in the string.
19 19
     #
  20
+    # If passed an optional +locale+ parameter, the word will be
  21
+    # pluralized using rules defined for that language. By default,
  22
+    # this parameter is set to <tt>:en</tt>.
  23
+    #
20 24
     #   "post".pluralize             # => "posts"
21 25
     #   "octopus".pluralize          # => "octopi"
22 26
     #   "sheep".pluralize            # => "sheep"
23 27
     #   "words".pluralize            # => "words"
24 28
     #   "CamelOctopus".pluralize     # => "CamelOctopi"
25  
-    def pluralize(word)
26  
-      apply_inflections(word, inflections.plurals)
  29
+    #   "ley".pluralize(:es)         # => "leyes"
  30
+    def pluralize(word, locale = :en)
  31
+      apply_inflections(word, inflections(locale).plurals)
27 32
     end
28 33
 
29 34
     # The reverse of +pluralize+, returns the singular form of a word in a string.
30 35
     #
  36
+    # If passed an optional +locale+ parameter, the word will be
  37
+    # pluralized using rules defined for that language. By default,
  38
+    # this parameter is set to <tt>:en</tt>.
  39
+    #
31 40
     #   "posts".singularize            # => "post"
32 41
     #   "octopi".singularize           # => "octopus"
33 42
     #   "sheep".singularize            # => "sheep"
34 43
     #   "word".singularize             # => "word"
35 44
     #   "CamelOctopi".singularize      # => "CamelOctopus"
36  
-    def singularize(word)
37  
-      apply_inflections(word, inflections.singulars)
  45
+    #   "leyes".singularize(:es)       # => "ley"
  46
+    def singularize(word, locale = :en)
  47
+      apply_inflections(word, inflections(locale).singulars)
38 48
     end
39 49
 
40 50
     # By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+
31  activesupport/test/inflector_test.rb
@@ -354,6 +354,35 @@ def test_clear_#{inflection_type}
354 354
     RUBY
355 355
   end
356 356
 
  357
+  def test_inflector_locality
  358
+    ActiveSupport::Inflector.inflections(:es) do |inflect|
  359
+      inflect.plural(/$/, 's')
  360
+      inflect.plural(/z$/i, 'ces')
  361
+
  362
+      inflect.singular(/s$/, '')
  363
+      inflect.singular(/es$/, '')
  364
+      
  365
+      inflect.irregular('el', 'los')
  366
+    end
  367
+
  368
+    assert_equal('hijos', 'hijo'.pluralize(:es))
  369
+    assert_equal('luces', 'luz'.pluralize(:es))
  370
+    assert_equal('luzs', 'luz'.pluralize)
  371
+
  372
+    assert_equal('sociedad', 'sociedades'.singularize(:es))
  373
+    assert_equal('sociedade', 'sociedades'.singularize)
  374
+
  375
+    assert_equal('los', 'el'.pluralize(:es))
  376
+    assert_equal('els', 'el'.pluralize)
  377
+
  378
+    ActiveSupport::Inflector.inflections(:es) { |inflect| inflect.clear }
  379
+
  380
+    assert ActiveSupport::Inflector.inflections(:es).plurals.empty?
  381
+    assert ActiveSupport::Inflector.inflections(:es).singulars.empty?
  382
+    assert !ActiveSupport::Inflector.inflections.plurals.empty?
  383
+    assert !ActiveSupport::Inflector.inflections.singulars.empty?
  384
+  end
  385
+
357 386
   def test_clear_all
358 387
     with_dup do
359 388
       ActiveSupport::Inflector.inflections do |inflect|
@@ -467,7 +496,7 @@ def test_clear_with_default
467 496
   # there are module functions that access ActiveSupport::Inflector.inflections,
468 497
   # so we need to replace the singleton itself.
469 498
   def with_dup
470  
-    original = ActiveSupport::Inflector.inflections
  499
+    original = ActiveSupport::Inflector::Inflections.instance_variable_get(:@__instance__)
471 500
     ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, original.dup)
472 501
   ensure
473 502
     ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, original)
9  railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb
... ...
@@ -1,8 +1,9 @@
1 1
 # Be sure to restart your server when you modify this file.
2 2
 
3  
-# Add new inflection rules using the following format
4  
-# (all these examples are active by default):
5  
-# ActiveSupport::Inflector.inflections do |inflect|
  3
+# Add new inflection rules using the following format. Inflections
  4
+# are locale specific, and you may define rules for as many different
  5
+# locales as you wish. All of these examples are active by default:
  6
+# ActiveSupport::Inflector.inflections(:en) do |inflect|
6 7
 #   inflect.plural /^(ox)$/i, '\1en'
7 8
 #   inflect.singular /^(ox)en/i, '\1'
8 9
 #   inflect.irregular 'person', 'people'
@@ -10,6 +11,6 @@
10 11
 # end
11 12
 #
12 13
 # These inflection rules are supported but not enabled by default:
13  
-# ActiveSupport::Inflector.inflections do |inflect|
  14
+# ActiveSupport::Inflector.inflections(:en) do |inflect|
14 15
 #   inflect.acronym 'RESTful'
15 16
 # end

0 notes on commit 7db0b07

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