From 29ef126b9756360af5efd7b61f954c4fcabaca47 Mon Sep 17 00:00:00 2001 From: Markus Doits Date: Wed, 7 Sep 2016 18:25:16 +0200 Subject: [PATCH] Allow to set different globals per locale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This changes the globals that when it has have a `default` key it can have locale aware globals. For example: ``` I18n.config.globals = { salutation: 'Bye bye', hello: 'Hello', de: { salutation: 'Tschüss' }, fr: { salutation: 'Salut' } } ``` Depending on the locale, the global variable `salutation` will be different. The default value is used as a fallback, so that `hello` will be available in each language, too. --- lib/i18n/globals.rb | 79 +++++++++++++++++++++++++++++-- test/test_i18n_globals.rb | 98 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+), 5 deletions(-) diff --git a/lib/i18n/globals.rb b/lib/i18n/globals.rb index 6328f8f..fdf6958 100644 --- a/lib/i18n/globals.rb +++ b/lib/i18n/globals.rb @@ -3,21 +3,90 @@ module I18n class Config + class CachedGlobals < Hash + def []=(key, val) + clear_cache + annotate_hash(val) if val.is_a?(Hash) # see annotate hash below why whis must be done + super(key, val) + end + + def for_locale(locale) + if key?(locale) + globals_cache[locale] ||= merge(fetch(locale)).reject { |_, i| i.is_a?(Hash) } + else + globals_cache[:default] ||= reject { |_, i| i.is_a?(Hash) } + end + end + + def clear + clear_cache + super + end + + def merge!(val) + clear_cache + # see annotate hash below why whis must be done + val.select { |_, v| v.is_a?(Hash) }.each { |_, v| annotate_hash(v) } + super(val) + end + + private + + def globals_cache + @globals_cache ||= {} + end + + def clear_cache + @globals_cache = {} + end + + # This is a little bit cumbersome. It might happen that this is done: + # + # I18n.config.globals[:en][:welcome] = 'Hello' + # + # What this does is changing the locale dependent version of `welcome`. + # Unfortunately we only override `:[]=` for our globals hash so it + # does not detect that the globals have been changed. + # + # To overcome this we annotate every hash that might passed in with this + # method. So when the sub hash is changed like above, the whole cache + # is cleared like it should. + def annotate_hash(hash) + return if hash.instance_variable_defined?(:@cached_global) + hash.instance_variable_set(:@cached_global, self) + + def hash.[]=(key, value) + super(key, value) + @cached_global.send(:clear_cache) + end + + def hash.merge!(other_hash) + super(other_hash) + @cached_global.send(:clear_cache) + end + + def hash.clear + super + @cached_global.send(:clear_cache) + end + end + end + def globals - @@globals ||= {} + @@globals ||= CachedGlobals.new end - def globals=(globals) - @@globals = globals + def globals=(new_globals) + globals.clear.merge!(new_globals) # maybe there is something better than `clear` and `merge!` end end class << self def translate(*args) if args.last.is_a?(Hash) - args[-1] = config.globals.merge(args.last) + args[-1] = config.globals.for_locale(locale).merge(args.last) else - args << config.globals + args << config.globals.for_locale(locale) end super(*args) end diff --git a/test/test_i18n_globals.rb b/test/test_i18n_globals.rb index 8d0419e..33a288e 100644 --- a/test/test_i18n_globals.rb +++ b/test/test_i18n_globals.rb @@ -6,6 +6,8 @@ class TestI18nGlobals < Minitest::Test def setup I18n.backend.load_translations 'test/fixtures/translations.yml' I18n.config = I18n::Config.new + I18n.config.globals = {} # glear globals between runs + I18n.locale = :en end def test_that_simple_translations_work @@ -70,4 +72,100 @@ def test_that_global_variables_are_shared_between_config_instances assert_equal I18n.translate('greeting'), 'Hi there, Greg!' end + + def test_that_locale_dependent_variable_overrides_default_one + I18n.config.globals = { + name: 'Greg', + en: { name: 'Debby' } + } + + assert_equal 'Hi there, Debby!', I18n.translate('greeting') + end + + def test_that_default_variable_is_used_if_no_special_locale_version_is_present + I18n.config.globals = { + name: 'Greg', + fr: { name: 'Debora' } + } + + assert_equal 'Hi there, Greg!', I18n.translate('greeting') + end + + def test_that_cache_is_cleared_after_setting_a_new_locale_global + I18n.config.globals = { + name: 'Greg', + en: { name: 'Debby' } + } + + assert_equal 'Hi there, Debby!', I18n.translate('greeting') + + I18n.config.globals[:en][:name] = 'Elisa' + + assert_equal 'Hi there, Elisa!', I18n.translate('greeting') + end + + def test_that_cache_is_cleared_after_setting_a_new_locale_hash + I18n.config.globals = { + name: 'Greg', + en: { name: 'Debby' } + } + + assert_equal 'Hi there, Debby!', I18n.translate('greeting') + + I18n.config.globals[:en] = { + name: 'Elisa' + } + + assert_equal 'Hi there, Elisa!', I18n.translate('greeting') + end + + def test_that_cache_is_cleared_after_merging_a_new_locale_hash + I18n.config.globals = { + name: 'Greg', + en: { name: 'Debby' } + } + + assert_equal 'Hi there, Debby!', I18n.translate('greeting') + + I18n.config.globals[:en].merge!(name: 'Elisa') + + assert_equal 'Hi there, Elisa!', I18n.translate('greeting') + end + + def test_that_cache_is_cleared_after_clearing_locale_hash + I18n.config.globals = { + name: 'Greg', + en: { name: 'Debby' } + } + + assert_equal 'Hi there, Debby!', I18n.translate('greeting') + + I18n.config.globals[:en].clear + + assert_equal 'Hi there, Greg!', I18n.translate('greeting') + end + + def test_that_cache_is_cleared_after_setting_a_new_global + I18n.config.globals = { + name: 'Greg' + } + + assert_equal 'Hi there, Greg!', I18n.translate('greeting') + + I18n.config.globals[:name] = 'Dobby' + + assert_equal 'Hi there, Dobby!', I18n.translate('greeting') + end + + def test_that_cache_is_cleared_after_merging_a_new_global + I18n.config.globals = { + name: 'Greg' + } + + assert_equal 'Hi there, Greg!', I18n.translate('greeting') + + I18n.config.globals.merge!(name: 'Dobby') + + assert_equal 'Hi there, Dobby!', I18n.translate('greeting') + end end