diff --git a/.autotest b/.autotest new file mode 100644 index 00000000..5e739f46 --- /dev/null +++ b/.autotest @@ -0,0 +1,6 @@ +require 'autotest/restart' + +Autotest.add_hook :initialize do |at| + at.testlib = "minitest/autorun" +end + diff --git a/.gitignore b/.gitignore index b066d1ea..3dae10fe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .DS_Store test/rails/fixtures nbproject/ +gemfiles/vendor/**/* vendor/**/* *.swp pkg diff --git a/Gemfile b/Gemfile index 92f28620..0a135402 100644 --- a/Gemfile +++ b/Gemfile @@ -6,3 +6,5 @@ gem 'mocha' gem 'test_declarative' gem 'rake' gem 'minitest' +gem 'minitest-autotest' +gem 'autotest-suffix' diff --git a/Gemfile.lock b/Gemfile.lock index 005f916b..dc544349 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -6,8 +6,13 @@ PATH GEM remote: https://rubygems.org/ specs: + autotest-suffix (1.1.0) metaclass (0.0.4) - minitest (5.5.1) + minitest (5.9.0) + minitest-autotest (1.0.3) + minitest-server (~> 1.0) + minitest-server (1.0.4) + minitest (~> 5.0) mocha (1.1.0) metaclass (~> 0.0.1) rake (10.4.2) @@ -18,8 +23,13 @@ PLATFORMS ruby DEPENDENCIES + autotest-suffix i18n! minitest + minitest-autotest mocha rake test_declarative + +BUNDLED WITH + 1.11.2 diff --git a/lib/i18n.rb b/lib/i18n.rb index e3f7cbed..c36cae3b 100644 --- a/lib/i18n.rb +++ b/lib/i18n.rb @@ -285,6 +285,29 @@ def enforce_available_locales!(locale) end end + def deep_symbolize_keys(hash) + hash.inject({}) { |result, (key, value)| + value = deep_symbolize_keys(value) if value.is_a?(Hash) + the_key = (key.to_sym rescue key) || key + the_key = remove_ending_separator(the_key) + result[the_key] = value + result + } + end + + # If a key ends with the separator, then removes the separator. + # This consolidates the storage and lookup of keys ending in a separator. + def remove_ending_separator(key) + if key[-1] == I18n.default_separator + new_key = key[0..key.length - 2] + if key.is_a? Symbol + new_key = new_key.to_sym + end + key = new_key + end + key + end + private # Any exceptions thrown in translate will be sent to the @@exception_handler diff --git a/lib/i18n/backend/flatten.rb b/lib/i18n/backend/flatten.rb index c23f7c13..25593859 100644 --- a/lib/i18n/backend/flatten.rb +++ b/lib/i18n/backend/flatten.rb @@ -110,4 +110,4 @@ def escape_default_separator(key) #:nodoc: end end -end \ No newline at end of file +end diff --git a/lib/i18n/backend/key_value.rb b/lib/i18n/backend/key_value.rb index a79fc1aa..a5a4f35c 100644 --- a/lib/i18n/backend/key_value.rb +++ b/lib/i18n/backend/key_value.rb @@ -59,6 +59,7 @@ def initialize(store, subtrees=true) def store_translations(locale, data, options = {}) escape = options.fetch(:escape, true) + data = I18n.deep_symbolize_keys(data) flatten_translations(locale, data, escape, @subtrees).each do |key, value| key = "#{locale}.#{key}" @@ -66,7 +67,7 @@ def store_translations(locale, data, options = {}) when Hash if @subtrees && (old_value = @store[key]) old_value = ActiveSupport::JSON.decode(old_value) - value = old_value.deep_symbolize_keys.deep_merge!(value) if old_value.is_a?(Hash) + value = I18n.deep_symbolize_keys(old_value).deep_merge!(value) if old_value.is_a?(Hash) end when Proc raise "Key-value stores cannot handle procs" @@ -88,9 +89,11 @@ def available_locales def lookup(locale, key, scope = [], options = {}) key = normalize_flat_keys(locale, key, scope, options[:separator]) + key = I18n.remove_ending_separator key + value = @store["#{locale}.#{key}"] value = ActiveSupport::JSON.decode(value) if value - value.is_a?(Hash) ? value.deep_symbolize_keys : value + value.is_a?(Hash) ? I18n.deep_symbolize_keys(value) : value end end diff --git a/lib/i18n/backend/simple.rb b/lib/i18n/backend/simple.rb index 95ffb6ab..d80f635c 100644 --- a/lib/i18n/backend/simple.rb +++ b/lib/i18n/backend/simple.rb @@ -29,9 +29,12 @@ def initialized? # translations will be overwritten by new ones only at the deepest # level of the hash. def store_translations(locale, data, options = {}) + if options[:separator] + I18n.default_separator = options[:separator] + end locale = locale.to_sym translations[locale] ||= {} - data = data.deep_symbolize_keys + data = I18n.deep_symbolize_keys(data) translations[locale].deep_merge!(data) end diff --git a/lib/i18n/core_ext/hash.rb b/lib/i18n/core_ext/hash.rb index 895f41a4..a530d69a 100644 --- a/lib/i18n/core_ext/hash.rb +++ b/lib/i18n/core_ext/hash.rb @@ -9,19 +9,11 @@ def except(*less_keys) slice(*keys - less_keys) end unless Hash.method_defined?(:except) - def deep_symbolize_keys - inject({}) { |result, (key, value)| - value = value.deep_symbolize_keys if value.is_a?(Hash) - result[(key.to_sym rescue key) || key] = value - result - } - end unless Hash.method_defined?(:deep_symbolize_keys) - # deep_merge_hash! by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809 MERGER = proc do |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &MERGER) : v2 end - + def deep_merge!(data) merge!(data, &MERGER) end unless Hash.method_defined?(:deep_merge!) diff --git a/test/backend/key_value_test.rb b/test/backend/key_value_test.rb index 097780df..b3c905aa 100644 --- a/test/backend/key_value_test.rb +++ b/test/backend/key_value_test.rb @@ -40,4 +40,10 @@ def assert_flattens(expected, nested, escape=true, subtree=true) I18n.t("foo", :raise => true) end end + + test "key value store: can store and retrieve a translation which key ends with the escope separator character" do + setup_backend! + store_translations :'en', :"yyy." => "yyy value" + assert_equal("yyy value", I18n.t("yyy.")) + end end if I18n::TestCase.key_value? diff --git a/test/backend/memoize_test.rb b/test/backend/memoize_test.rb index 0fb9f904..2c42fe27 100644 --- a/test/backend/memoize_test.rb +++ b/test/backend/memoize_test.rb @@ -44,4 +44,10 @@ def test_resets_available_locales_on_store_translations assert I18n.available_locales.include?(:copa) assert_equal 1, I18n.backend.spy_calls end + + def test_memoize_can_store_and_retrieve_a_translation_ending_in_separator + store_translations :'en', :"yyy." => "yyy value" + assert_equal("yyy value", I18n.t("yyy.")) + end + end diff --git a/test/backend/simple_test.rb b/test/backend/simple_test.rb index 4d0c447f..61702434 100644 --- a/test/backend/simple_test.rb +++ b/test/backend/simple_test.rb @@ -70,6 +70,11 @@ def setup assert_equal Hash[:'en', {:foo => {:bar => 'bar', :baz => 'baz'}}], translations end + test "simple store and lookup: can store and retrieve a translation which key ends with the escope separator character" do + store_translations :'en', :"yyy." => "yyy value" + assert_equal("yyy value", I18n.t("yyy.")) + end + # reloading translations test "simple reload_translations: unloads translations" do diff --git a/test/core_ext/hash_test.rb b/test/core_ext/hash_test.rb index f7ebd6fe..cd56e687 100644 --- a/test/core_ext/hash_test.rb +++ b/test/core_ext/hash_test.rb @@ -5,7 +5,7 @@ class I18nCoreExtHashInterpolationTest < I18n::TestCase test "#deep_symbolize_keys" do hash = { 'foo' => { 'bar' => { 'baz' => 'bar' } } } expected = { :foo => { :bar => { :baz => 'bar' } } } - assert_equal expected, hash.deep_symbolize_keys + assert_equal expected, I18n.deep_symbolize_keys(hash) end test "#slice" do diff --git a/test/test_helper.rb b/test/test_helper.rb index dabb6a7b..e1b42b23 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -26,6 +26,7 @@ def self.key_value? def setup super I18n.enforce_available_locales = false + I18n.default_separator = nil end def teardown