Skip to content
This repository has been archived by the owner on Jan 2, 2018. It is now read-only.

Commit

Permalink
Merge pull request #296 from julik/deep-merges-with-chain-lookup
Browse files Browse the repository at this point in the history
Perform deep merges when doing Backend::Chain#translate
  • Loading branch information
yaroslav committed Nov 12, 2014
2 parents c686d44 + 8db944b commit 5bda22e
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 10 deletions.
18 changes: 16 additions & 2 deletions lib/i18n/backend/chain.rb
Expand Up @@ -17,7 +17,7 @@ module Backend
class Chain
module Implementation
include Base

attr_accessor :backends

def initialize(*backends)
Expand Down Expand Up @@ -45,7 +45,7 @@ def translate(locale, key, default_options = {})
options = default_options if backend == backends.last
translation = backend.translate(locale, key, options)
if namespace_lookup?(translation, options)
namespace = translation.merge(namespace || {})
namespace = _deep_merge(translation, namespace || {})
elsif !translation.nil?
return translation
end
Expand Down Expand Up @@ -75,6 +75,20 @@ def localize(locale, object, format = :default, options = {})
def namespace_lookup?(result, options)
result.is_a?(Hash) && !options.has_key?(:count)
end

private
# This is approximately what gets used in ActiveSupport.
# However since we are not guaranteed to run in an ActiveSupport context
# it is wise to have our own copy. We underscore it
# to not pollute the namespace of the including class.
def _deep_merge(hash, other_hash)
copy = hash.dup
other_hash.each_pair do |k,v|
value_from_other = hash[k]
copy[k] = value_from_other.is_a?(Hash) && v.is_a?(Hash) ? _deep_merge(value_from_other, v) : v
end
copy
end
end

include Implementation
Expand Down
26 changes: 20 additions & 6 deletions test/backend/chain_test.rb
Expand Up @@ -4,10 +4,20 @@ class I18nBackendChainTest < I18n::TestCase
def setup
super
@first = backend(:en => {
:foo => 'Foo', :formats => { :short => 'short' }, :plural_1 => { :one => '%{count}' }, :dates => {:a => "A"}
:foo => 'Foo', :formats => {
:short => 'short',
:subformats => {:short => 'short'},
},
:plural_1 => { :one => '%{count}' },
:dates => {:a => "A"}
})
@second = backend(:en => {
:bar => 'Bar', :formats => { :long => 'long' }, :plural_2 => { :one => 'one' }, :dates => {:a => "B", :b => "B"}
:bar => 'Bar', :formats => {
:long => 'long',
:subformats => {:long => 'long'},
},
:plural_2 => { :one => 'one' },
:dates => {:a => "B", :b => "B"}
})
@chain = I18n.backend = I18n::Backend::Chain.new(@first, @second)
end
Expand Down Expand Up @@ -39,11 +49,11 @@ def setup
assert_equal({}, I18n.t(:'i18n.transliterate.rule', :locale => 'en', :default => {}))
end

test "namespace lookup collects results from all backends" do
assert_equal({ :short => 'short', :long => 'long' }, I18n.t(:formats))
test "namespace lookup collects results from all backends and merges deep hashes" do
assert_equal({:long=>"long", :subformats=>{:long=>"long", :short=>"short"}, :short=>"short"}, I18n.t(:formats))
end

test "namespace lookup collects results from all backends and does not overwrite" do
test "namespace lookup collects results from all backends and lets leftmost backend take priority" do
assert_equal({ :a => "A", :b => "B" }, I18n.t(:dates))
end

Expand All @@ -59,7 +69,11 @@ def setup
test "bulk lookup collects results from all backends" do
assert_equal ['Foo', 'Bar'], I18n.t([:foo, :bar])
assert_equal ['Foo', 'Bar', 'Bah'], I18n.t([:foo, :bar, :bah], :default => 'Bah')
assert_equal [{ :short => 'short', :long => 'long' }, { :one => 'one' }, 'Bah'], I18n.t([:formats, :plural_2, :bah], :default => 'Bah')
assert_equal [{
:long=>"long",
:subformats=>{:long=>"long", :short=>"short"},
:short=>"short"}, {:one=>"one"},
"Bah"], I18n.t([:formats, :plural_2, :bah], :default => 'Bah')
end

test "store_translations options are not dropped while transfering to backend" do
Expand Down
24 changes: 22 additions & 2 deletions test/i18n_test.rb
Expand Up @@ -388,9 +388,25 @@ def call(exception, locale, key, options); key; end
I18n.config.enforce_available_locales = false
end
end


def inspect_config
vars = I18n.config.class.class_variables
table = vars.inject({}) do | m, varname |
m.merge varname => I18n.config.class.class_variable_get(varname)
end
puts table.inspect
end

test 'I18n.reload! reloads the set of locales that are enforced' do
begin
# Clear the backend that affects the available locales and somehow can remain
# set from the last running test.
# For instance, it contains enough translations to cause a false positive with
# this test when ran with --seed=50992
I18n.backend = I18n::Backend::Simple.new

assert !I18n.available_locales.include?(:de), "Available locales should not include :de at this point"

I18n.enforce_available_locales = true

assert_raise(I18n::InvalidLocale) { I18n.default_locale = :de }
Expand All @@ -406,7 +422,11 @@ def call(exception, locale, key, options); key; end
store_translations(:en, :foo => 'Foo in :en')
store_translations(:de, :foo => 'Foo in :de')
store_translations(:pl, :foo => 'Foo in :pl')


assert I18n.available_locales.include?(:de), ":de should now be allowed"
assert I18n.available_locales.include?(:en), ":en should now be allowed"
assert I18n.available_locales.include?(:pl), ":pl should now be allowed"

assert_nothing_raised { I18n.default_locale = I18n.locale = :en }
assert_nothing_raised { I18n.default_locale = I18n.locale = :de }
assert_nothing_raised { I18n.default_locale = I18n.locale = :pl }
Expand Down

0 comments on commit 5bda22e

Please sign in to comment.