From 49ee522c1da2edd28341fe8f9f3394b244da5a10 Mon Sep 17 00:00:00 2001 From: William Lorentson Date: Fri, 6 Nov 2009 14:59:42 -0800 Subject: [PATCH] adding nested hash returns --- lib/i18n_backend_database/database.rb | 44 +++++++++++++++++++++++++++ lib/i18n_util.rb | 2 +- spec/translate_spec.rb | 37 ++++++++++++++++++++++ 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/lib/i18n_backend_database/database.rb b/lib/i18n_backend_database/database.rb index eccd9c7..cf8c0ba 100644 --- a/lib/i18n_backend_database/database.rb +++ b/lib/i18n_backend_database/database.rb @@ -63,6 +63,17 @@ def translate(locale, key, options = {}) return translate(@locale.code, default, options.dup) end + # The requested key might not be a parent node in a hierarchy of keys instead of a regular 'leaf' node + # that would simply result in a string return. If so, check the database for possible children + # and return them in a nested hash if we find them. + # We can safely ignore pluralization indeces here since they should never apply to a hash return + if !entry && (key.is_a?(String) || key.is_a?(Symbol)) + #We need to escape % and \. Rails will handle the rest. + escaped_key = key.to_s.gsub('\\', '\\\\\\\\').gsub(/%/, '\%') + children = @locale.translations.find :all, :conditions => ["raw_key like ?", "#{escaped_key}.%"] + return hashify_record_array(key.to_s, children) if children.size > 0 + end + # we check the database before creating a translation as we can have translations with nil values # if we still have no blasted translation just go and create one for the current locale! unless entry @@ -210,6 +221,39 @@ def interpolate(locale, string, values = {}) result end + def strip_root_key(root_key, key) + return nil if key.nil? + return key.gsub(/^#{root_key}\./, '') + end + + def hashify_record_array(root_key, record_array) + return nil if record_array.nil? || record_array.empty? + + #Make sure that all of our records have raw_keys + record_array.reject! {|record| record.raw_key.nil?} + + # Start building our return hash + result = {} + record_array.each { |record| + key = strip_root_key(root_key, record.raw_key) + next unless key.present? + + # If we contain a period delimiter, we need to add a sub-hash. + # Otherwise, we just insert the value at this level. + if key.index(".") + internal_node = key.slice(0, key.index('.')) + new_root = root_key + '.' + internal_node + new_record_array = record_array.select {|record| record.raw_key.starts_with? new_root} + result[internal_node.to_sym] = hashify_record_array(new_root, new_record_array) + else + value = record.value + value = value.to_i if value == "0" || value.to_i != 0 #simple integer cast + result[key.to_sym] = value + end + } + result + end + end end end diff --git a/lib/i18n_util.rb b/lib/i18n_util.rb index 5ee3c87..42fb3c6 100644 --- a/lib/i18n_util.rb +++ b/lib/i18n_util.rb @@ -50,7 +50,7 @@ def self.extract_i18n_keys(hash, parent_keys = []) if value.is_a?(Hash) # Nested hash keys += extract_i18n_keys(value, full_key) - elsif value.present? + elsif !value.nil? # String leaf node keys << full_key.join(".") end diff --git a/spec/translate_spec.rb b/spec/translate_spec.rb index 95b4ed3..3b22098 100644 --- a/spec/translate_spec.rb +++ b/spec/translate_spec.rb @@ -23,6 +23,43 @@ def write(key, value, options = nil) @backend.cache_store.clear end + describe "returning hashes for non-leaves" do + before do + #Set up a translation hierarchy + @locale = Locale.create!(:code => "en") + I18n.default_locale = "en" + end + + it "should return a hash when asked for a non-leaf node" do + format_hash = {:separator => ".", :precision => 3, :delimiter => ","} + @locale.translations.create!(:key => "number.format.separator", :value => format_hash[:separator]) + @locale.translations.create!(:key => "number.format.precision", :value => format_hash[:precision]) + @locale.translations.create!(:key => "number.format.delimiter", :value => format_hash[:delimiter]) + @backend.translate("en", "number.format").should == format_hash + end + + it "should return a nested hash" do + nested_hash = {:a => "b", :c => {:d => "e", :f => "g"}} + @locale.translations.create!(:key => "nested.a", :value => "b") + @locale.translations.create!(:key => "nested.c.d", :value => "e") + @locale.translations.create!(:key => "nested.c.f", :value => "g") + @backend.translate("en", "nested").should == nested_hash + end + + it "should not be fooled by SQL injection" do + #for bad_string in ["number.format%", "number.format\%", "number.format\\"] do + for bad_string in ["number.format\\"] do + @backend.translate("en", bad_string).should == bad_string + end + end + + it "should not be fooled by keys ending in a delimiter" do + @locale.translations.create!(:key => "number.format.", :value => 'this should be a normal leaf') + @backend.translate('en', 'number.format.').should == 'this should be a normal leaf' + end + + end + describe "with default locale en" do before(:each) do I18n.default_locale = "en"