Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Make ActiveSupport::Inflector locale aware and multilingual #7197

Merged
merged 1 commit into from

5 participants

David Celis Rafael Mendonça França José Valim Carlos Antonio da Silva Aaron Patterson
David Celis

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 the
application's I18n.default_locale 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

Rafael Mendonça França

This seems cool.

cc/ @NZKoz @fxn

...vesupport/lib/active_support/inflector/inflections.rb
@@ -20,8 +22,12 @@ module Inflector
# pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may
# already have been loaded.
class Inflections
- def self.instance
- @__instance__ ||= new
+ def self.instance(locale = I18n.default_locale)
+ unless instance_variable_defined?("@__#{locale}_instance__")
+ instance_variable_set("@__#{locale}_instance__", new)
+ end
+
+ instance_variable_get("@__#{locale}_instance__")
Aaron Patterson Owner

Can we just keep this in a hash or something? I'm really against dynamic class instance variables.

Yeah, I wasn't terribly happy about this either; this sounds much better. Done!

Actually, it looks like that's made a ton of tests fail. Investigating that now.

EDIT: Fixed. All tests passing!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
José Valim
Owner

:+1: I think this is a great idea, thanks! There are a few things to discuss though:

1) Today, we make the default I18n.default_locale, I think this is a bad idea because it can lead to your tables and other internal information to be inflected using another locale than :en. I believe we should default to :en, even more because I18n.default_locale doesn't mean anything, if your application has more than one locale, it will be at least half of the time the wrong one (and if it has only one locale, you simply don't care).

2) underscore, camelize, titleize, etc do not accept a locale. I am not sure if this is a deliberate decision, but I believe it should be discussed. Although I can see how some would never be used with a locale, like underscore, titleize and humanize seem expected to accept a locale.

3) We should probably update config/initializers/inflections.rb file that ships with Rails with the new info

thedarkone thedarkone referenced this pull request
Closed

Thread safety #6917

David Celis

Sure, I can go over my reasoning for those things:

1) I figured that this might actually be a nicety for Rails devs whose first languages aren't English or are working on a team that is primarily speaking another language. While it's true that the keywords in Ruby are all in English, I didn't really see any reason to restrict their class constants to be in English. Since the default_locale doesn't change after initialization, I saw it as a safe decision because the table mappings will be kept consistent. If you think that Rails internally should always use :en, however, I'm all ears.

2) Singularization/Pluralization were the only methods that really made sense to me to take a locale. Underscore, camelize and titleize seem to behave consistently based on casing rather than anything dependent on language. Actually, tableize does not take a locale and, depending on your answer to question 1, it should or shouldn't (since it uses pluralize internally). Only the singularization and pluralization rules seemed to differ between languages. I can certainly make those methods locale aware as well, I just wasn't sure why I should.

3) Definitely. I see a couple possibilities with this... Add (:en) to the commented out ActiveSupport::Inflector.inflections block, turn the existing block into a short declaration of rules for some other locale (such as the rules for :es that I gave above), or maybe just add a short block at the bottom for some other locale to give an example. Either way, I'll definitely update the commented note above it with an additional note that a locale can be specified and that multiple lists of inflections can be maintained.

José Valim
Owner

1) Except it isn't backwards compatible. Many people today have their default_locale set to something other than english while keeping their whole application in english. Also I18n.default_locale (and I18n as a whole) focuses mainly on view behavior, having it drastically affect how an application works internally is a bad idea. We could have another option for that, but I would rather see the need before adding it.

2) Well, I believe some people use titleize for such purpose but let's go with what I just said above: let's see the need before adding it. About different pluralization and singularization rules, I18n already tackles it. I don't think it is in scope with Rails (given their complexity)

3) Great.

David Celis

1) Okay, makes sense. I've made it default to :en as such.

2) Sounds good.

3) I went ahead and changed the commented block to rules for Spanish. It makes the comment a bit long, but I thought this would be the most useful option. It specifies how to define rules for another locale while also providing (what I believe to be) a good list of rules for Spanish, which would arguably be the most-used second locale. If you think the comment's too long, however, I can change it to one of the other options I mentioned.

4) Figured I'd ask because the contribution guides don't really mention it: should I also update the internationalization guide? I can also update ActiveSupport's CHANGELOG in this commit and 4.0 release notes, but I don't know if that's typically handled by Rails Core.

José Valim
Owner

Thanks!

About 3), I would say that passing :en as argument by default and a simple note should suffice. I would keep the examples in english, having them in spanish (and many examples) distracts from the main goal which is to show case the API.

/cc @fxn what is your take on 3) ?

David Celis davidcelis 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>
7db0b07
David Celis

Agreed; I reverted the initializer back and just added a note about locales (while showing :en being passed in). Not sure if you wanted @fxn's opinion on that or the updating of the guides. I have an update to the guide stashed, so if you guys decide you want that in, I can easily add it. If not, thanks for taking the time to look through all this!

José Valim
Owner

For me this is ready to go. /cc @tenderlove @rafaelfranca @fxn go?

Carlos Antonio da Silva

Looks good :+1:.

I'm just thinking a little bit about how the API looks like:

pluralize('foo', :en)
#vs
pluralize('foo', locale: :en)

Anyway, :shipit:

José Valim josevalim merged commit 13af5ac into from
David Celis davidcelis referenced this pull request in davidcelis/inflections
Closed

default locale not detected when loaded normally #11

David Celis davidcelis deleted the branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jul 31, 2012
  1. David Celis

    Make ActiveSupport::Inflector locale aware and multilingual

    davidcelis authored
    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>
This page is out of date. Refresh to see the latest.
22 activesupport/lib/active_support/core_ext/string/inflections.rb
View
@@ -13,6 +13,11 @@ class String
# the singular form will be returned if <tt>count == 1</tt>.
# For any other value of +count+ the plural will be returned.
#
+ # If the optional parameter +locale+ is specified,
+ # the word will be pluralized as a word of that language.
+ # By default, this parameter is set to <tt>:en</tt>.
+ # You must define your own inflection rules for languages other than English.
+ #
# 'post'.pluralize # => "posts"
# 'octopus'.pluralize # => "octopi"
# 'sheep'.pluralize # => "sheep"
@@ -21,15 +26,23 @@ class String
# 'CamelOctopus'.pluralize # => "CamelOctopi"
# 'apple'.pluralize(1) # => "apple"
# 'apple'.pluralize(2) # => "apples"
- def pluralize(count = nil)
+ # 'ley'.pluralize(:es) # => "leyes"
+ # 'ley'.pluralize(1, :es) # => "ley"
+ def pluralize(count = nil, locale = :en)
+ locale = count if count.is_a?(Symbol)
if count == 1
self
else
- ActiveSupport::Inflector.pluralize(self)
+ ActiveSupport::Inflector.pluralize(self, locale)
end
end
# The reverse of +pluralize+, returns the singular form of a word in a string.
+ #
+ # If the optional parameter +locale+ is specified,
+ # the word will be singularized as a word of that language.
+ # By default, this paramter is set to <tt>:en</tt>.
+ # You must define your own inflection rules for languages other than English.
#
# 'posts'.singularize # => "post"
# 'octopi'.singularize # => "octopus"
@@ -37,8 +50,9 @@ def pluralize(count = nil)
# 'word'.singularize # => "word"
# 'the blue mailmen'.singularize # => "the blue mailman"
# 'CamelOctopi'.singularize # => "CamelOctopus"
- def singularize
- ActiveSupport::Inflector.singularize(self)
+ # 'leyes'.singularize(:es) # => "ley"
+ def singularize(locale = :en)
+ ActiveSupport::Inflector.singularize(self, locale)
end
# +constantize+ tries to find a declared constant with the name specified
2  activesupport/lib/active_support/inflections.rb
View
@@ -1,7 +1,7 @@
require 'active_support/inflector/inflections'
module ActiveSupport
- Inflector.inflections do |inflect|
+ Inflector.inflections(:en) do |inflect|
inflect.plural(/$/, 's')
inflect.plural(/s$/i, 's')
inflect.plural(/^(ax|test)is$/i, '\1es')
23 activesupport/lib/active_support/inflector/inflections.rb
View
@@ -1,13 +1,15 @@
require 'active_support/core_ext/array/prepend_and_append'
+require 'active_support/i18n'
module ActiveSupport
module Inflector
extend self
# A singleton instance of this class is yielded by Inflector.inflections, which can then be used to specify additional
- # inflection rules.
+ # inflection rules. If passed an optional locale, rules for other languages can be specified. The default locale is
+ # <tt>:en</tt>. Only rules for English are provided.
#
- # ActiveSupport::Inflector.inflections do |inflect|
+ # ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.plural /^(ox)$/i, '\1\2en'
# inflect.singular /^(ox)en/i, '\1'
#
@@ -20,8 +22,9 @@ module Inflector
# pluralization and singularization rules that is runs. This guarantees that your rules run before any of the rules that may
# already have been loaded.
class Inflections
- def self.instance
- @__instance__ ||= new
+ def self.instance(locale = :en)
+ @__instance__ ||= Hash.new { |h, k| h[k] = new }
+ @__instance__[locale]
end
attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
@@ -160,16 +163,18 @@ def clear(scope = :all)
end
# Yields a singleton instance of Inflector::Inflections so you can specify additional
- # inflector rules.
+ # inflector rules. If passed an optional locale, rules for other languages can be specified.
+ # If not specified, defaults to <tt>:en</tt>. Only rules for English are provided.
+ #
#
- # ActiveSupport::Inflector.inflections do |inflect|
+ # ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.uncountable "rails"
# end
- def inflections
+ def inflections(locale = :en)
if block_given?
- yield Inflections.instance
+ yield Inflections.instance(locale)
else
- Inflections.instance
+ Inflections.instance(locale)
end
end
end
22 activesupport/lib/active_support/inflector/methods.rb
View
@@ -10,31 +10,41 @@ module ActiveSupport
#
# The Rails core team has stated patches for the inflections library will not be accepted
# in order to avoid breaking legacy applications which may be relying on errant inflections.
- # If you discover an incorrect inflection and require it for your application, you'll need
- # to correct it yourself (explained below).
+ # If you discover an incorrect inflection and require it for your application or wish to
+ # define rules for languages other than English, please correct or add them yourself (explained below).
module Inflector
extend self
# Returns the plural form of the word in the string.
#
+ # If passed an optional +locale+ parameter, the word will be
+ # pluralized using rules defined for that language. By default,
+ # this parameter is set to <tt>:en</tt>.
+ #
# "post".pluralize # => "posts"
# "octopus".pluralize # => "octopi"
# "sheep".pluralize # => "sheep"
# "words".pluralize # => "words"
# "CamelOctopus".pluralize # => "CamelOctopi"
- def pluralize(word)
- apply_inflections(word, inflections.plurals)
+ # "ley".pluralize(:es) # => "leyes"
+ def pluralize(word, locale = :en)
+ apply_inflections(word, inflections(locale).plurals)
end
# The reverse of +pluralize+, returns the singular form of a word in a string.
#
+ # If passed an optional +locale+ parameter, the word will be
+ # pluralized using rules defined for that language. By default,
+ # this parameter is set to <tt>:en</tt>.
+ #
# "posts".singularize # => "post"
# "octopi".singularize # => "octopus"
# "sheep".singularize # => "sheep"
# "word".singularize # => "word"
# "CamelOctopi".singularize # => "CamelOctopus"
- def singularize(word)
- apply_inflections(word, inflections.singulars)
+ # "leyes".singularize(:es) # => "ley"
+ def singularize(word, locale = :en)
+ apply_inflections(word, inflections(locale).singulars)
end
# By default, +camelize+ converts strings to UpperCamelCase. If the argument to +camelize+
31 activesupport/test/inflector_test.rb
View
@@ -354,6 +354,35 @@ def test_clear_#{inflection_type}
RUBY
end
+ def test_inflector_locality
+ ActiveSupport::Inflector.inflections(:es) do |inflect|
+ inflect.plural(/$/, 's')
+ inflect.plural(/z$/i, 'ces')
+
+ inflect.singular(/s$/, '')
+ inflect.singular(/es$/, '')
+
+ inflect.irregular('el', 'los')
+ end
+
+ assert_equal('hijos', 'hijo'.pluralize(:es))
+ assert_equal('luces', 'luz'.pluralize(:es))
+ assert_equal('luzs', 'luz'.pluralize)
+
+ assert_equal('sociedad', 'sociedades'.singularize(:es))
+ assert_equal('sociedade', 'sociedades'.singularize)
+
+ assert_equal('los', 'el'.pluralize(:es))
+ assert_equal('els', 'el'.pluralize)
+
+ ActiveSupport::Inflector.inflections(:es) { |inflect| inflect.clear }
+
+ assert ActiveSupport::Inflector.inflections(:es).plurals.empty?
+ assert ActiveSupport::Inflector.inflections(:es).singulars.empty?
+ assert !ActiveSupport::Inflector.inflections.plurals.empty?
+ assert !ActiveSupport::Inflector.inflections.singulars.empty?
+ end
+
def test_clear_all
with_dup do
ActiveSupport::Inflector.inflections do |inflect|
@@ -467,7 +496,7 @@ def test_clear_with_default
# there are module functions that access ActiveSupport::Inflector.inflections,
# so we need to replace the singleton itself.
def with_dup
- original = ActiveSupport::Inflector.inflections
+ original = ActiveSupport::Inflector::Inflections.instance_variable_get(:@__instance__)
ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, original.dup)
ensure
ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, original)
9 railties/lib/rails/generators/rails/app/templates/config/initializers/inflections.rb
View
@@ -1,8 +1,9 @@
# Be sure to restart your server when you modify this file.
-# Add new inflection rules using the following format
-# (all these examples are active by default):
-# ActiveSupport::Inflector.inflections do |inflect|
+# Add new inflection rules using the following format. Inflections
+# are locale specific, and you may define rules for as many different
+# locales as you wish. All of these examples are active by default:
+# ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.plural /^(ox)$/i, '\1en'
# inflect.singular /^(ox)en/i, '\1'
# inflect.irregular 'person', 'people'
@@ -10,6 +11,6 @@
# end
#
# These inflection rules are supported but not enabled by default:
-# ActiveSupport::Inflector.inflections do |inflect|
+# ActiveSupport::Inflector.inflections(:en) do |inflect|
# inflect.acronym 'RESTful'
# end
Something went wrong with that request. Please try again.