Skip to content

Commit

Permalink
Move I18n configuration to I18n.config.
Browse files Browse the repository at this point in the history
  • Loading branch information
josevalim committed Mar 10, 2010
1 parent 32aafb0 commit 4a7baea
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 28 deletions.
79 changes: 53 additions & 26 deletions lib/i18n.rb
Expand Up @@ -15,13 +15,18 @@ module I18n
autoload :Helpers, 'i18n/helpers'
autoload :Locale, 'i18n/locale'

@@backend = nil
@@load_path = nil
@@default_locale = :en
@@default_separator = '.'
@@exception_handler = :default_exception_handler
class Config
# The only configuration value that is not global and scoped to thread is :locale.
# It defaults to the default_locale.
def locale
@locale ||= default_locale
end

# Sets the current locale pseudo-globally, i.e. in the Thread.current hash.
def locale=(locale)
@locale = locale.to_sym rescue nil
end

class << self
# Returns the current backend. Defaults to +Backend::Simple+.
def backend
@@backend ||= Backend::Simple.new
Expand All @@ -34,24 +39,14 @@ def backend=(backend)

# Returns the current default locale. Defaults to :'en'
def default_locale
@@default_locale
@@default_locale ||= :en
end

# Sets the current default locale. Used to set a custom default locale.
def default_locale=(locale)
@@default_locale = locale.to_sym rescue nil
end

# Returns the current locale. Defaults to I18n.default_locale.
def locale
Thread.current[:locale] ||= default_locale
end

# Sets the current locale pseudo-globally, i.e. in the Thread.current hash.
def locale=(locale)
Thread.current[:locale] = locale.to_sym rescue nil
end

# Returns an array of locales for which translations are available.
# Unless you explicitely set the these through I18n.available_locales=
# the call will be delegated to the backend and memoized on the I18n module.
Expand All @@ -66,14 +61,19 @@ def available_locales=(locales)

# Returns the current default scope separator. Defaults to '.'
def default_separator
@@default_separator
@@default_separator ||= '.'
end

# Sets the current default scope separator.
def default_separator=(separator)
@@default_separator = separator
end

# Return the current exception handler. Defaults to :default_exception_handler.
def exception_handler
@@exception_handler ||= :default_exception_handler
end

# Sets the exception handler.
def exception_handler=(exception_handler)
@@exception_handler = exception_handler
Expand All @@ -96,12 +96,39 @@ def load_path
def load_path=(load_path)
@@load_path = load_path
end
end

class << self

# Gets I18n configuration object.
def config
Thread.current[:i18n_config] ||= I18n::Config.new
end

# Sets I18n configuration object.
def config=(value)
Thread.current[:i18n_config] = value
end

# Write methods which delegates to the configuration object
%w(locale backend default_locale available_locales default_separator
exception_handler load_path).each do |method|
module_eval <<-DELEGATORS, __FILE__, __LINE__ + 1
def #{method}
config.#{method}
end
def #{method}=(value)
config.#{method} = (value)
end
DELEGATORS
end

# Tells the backend to reload translations. Used in situations like the
# Rails development environment. Backends can implement whatever strategy
# is useful.
def reload!
backend.reload!
config.backend.reload!
end

# Translates, pluralizes and interpolates a given key using a given locale,
Expand Down Expand Up @@ -203,9 +230,9 @@ def reload!
def translate(*args)
options = args.pop if args.last.is_a?(Hash)
key = args.shift
locale = options && options.delete(:locale) || I18n.locale
locale = options && options.delete(:locale) || config.locale
raises = options && options.delete(:raise)
backend.translate(locale, key, options || {})
config.backend.translate(locale, key, options || {})
rescue I18n::ArgumentError => exception
raise exception if raises
handle_exception(exception, locale, key, options)
Expand All @@ -219,9 +246,9 @@ def translate!(key, options = {})

# Localizes certain objects, such as dates and numbers to local formatting.
def localize(object, options = {})
locale = options.delete(:locale) || I18n.locale
locale = options.delete(:locale) || config.locale
format = options.delete(:format) || :default
backend.localize(locale, object, format, options)
config.backend.localize(locale, object, format, options)
end
alias :l :localize

Expand Down Expand Up @@ -266,11 +293,11 @@ def default_exception_handler(exception, locale, key, options)
# I18n.exception_handler = I18nExceptionHandler.new # an object
# I18n.exception_handler.call(exception, locale, key, options) # will be called like this
def handle_exception(exception, locale, key, options)
case @@exception_handler
case config.exception_handler
when Symbol
send(@@exception_handler, exception, locale, key, options)
send(config.exception_handler, exception, locale, key, options)
else
@@exception_handler.call(exception, locale, key, options)
config.exception_handler.call(exception, locale, key, options)
end
end

Expand Down
39 changes: 37 additions & 2 deletions test/i18n_test.rb
Expand Up @@ -21,6 +21,7 @@ def test_uses_simple_backend_set_by_default
def test_can_set_backend
assert_nothing_raised { I18n.backend = self }
assert_equal self, I18n.backend
ensure
I18n.backend = I18n::Backend::Simple.new
end

Expand All @@ -31,6 +32,7 @@ def test_uses_en_us_as_default_locale_by_default
def test_can_set_default_locale
assert_nothing_raised { I18n.default_locale = 'de' }
assert_equal :de, I18n.default_locale
ensure
I18n.default_locale = :en
end

Expand All @@ -41,16 +43,47 @@ def test_uses_default_locale_as_locale_by_default
def test_can_set_locale_to_thread_current
assert_nothing_raised { I18n.locale = 'de' }
assert_equal :de, I18n.locale
assert_equal :de, Thread.current[:locale]
assert_equal :de, Thread.current[:i18n_config].locale
I18n.locale = :en
end

def test_can_set_i18n_config
I18n.config = self
assert_equal self, I18n.config
assert_equal self, Thread.current[:i18n_config]
ensure
I18n.config = ::I18n::Config.new
end

def test_locale_is_not_shared_between_configurations
a = I18n::Config.new
b = I18n::Config.new
a.locale = :fr
b.locale = :es
assert_equal :fr, a.locale
assert_equal :es, b.locale
assert_equal :en, I18n.locale
end

def test_other_options_are_shared_between_configurations
a = I18n::Config.new
b = I18n::Config.new
a.default_locale = :fr
b.default_locale = :es
assert_equal :es, a.default_locale
assert_equal :es, b.default_locale
assert_equal :es, I18n.default_locale
ensure
I18n.default_locale = :en
end

def test_defaults_to_dot_as_separator
assert_equal '.', I18n.default_separator
end

def test_can_set_default_separator
assert_nothing_raised { I18n.default_separator = "\001" }
ensure
I18n.default_separator = '.' # revert it
end

Expand All @@ -73,14 +106,16 @@ def test_uses_passed_separator_to_normalize_keys

def test_can_set_exception_handler
assert_nothing_raised { I18n.exception_handler = :custom_exception_handler }
I18n.exception_handler = :default_exception_handler # revert it
ensure
I18n.exception_handler = :default_exception_handler
end

with_mocha do
def test_uses_custom_exception_handler
I18n.exception_handler = :custom_exception_handler
I18n.expects(:custom_exception_handler)
I18n.translate :bogus
ensure
I18n.exception_handler = :default_exception_handler # revert it
end

Expand Down

3 comments on commit 4a7baea

@clemens
Copy link
Collaborator

@clemens clemens commented on 4a7baea Sep 4, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@josevalim I know it's been ages but I just came across this and I was wondering whether there was/is a specific reason to introduce a dedicated Config object vs. just having the config on the class itself. Was it just a purely organizational refactoring or was there more to it?

@josevalim
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We wanted it to be thread local, so modifying in one thread does not affect the locale in another one, but that should apply to multiple configurations. So swapping a whole object is much easier/cheaper than doing it one by one.

@clemens
Copy link
Collaborator

@clemens clemens commented on 4a7baea Sep 4, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Of course that makes perfect sense!

Please sign in to comment.