Skip to content

Commit

Permalink
Only deep_symbolize_keys when needed
Browse files Browse the repository at this point in the history
When loading certain translations from file, they can be parsed into their Symbol representation. It is wasteful to traverse the entire object graph in these cases.
  • Loading branch information
paarthmadan committed Dec 9, 2021
1 parent ad41bdc commit d7dd3dc
Show file tree
Hide file tree
Showing 5 changed files with 31 additions and 13 deletions.
16 changes: 10 additions & 6 deletions lib/i18n/backend/base.rb
Expand Up @@ -223,27 +223,28 @@ def deep_interpolate(locale, data, values = EMPTY_HASH)
def load_file(filename)
type = File.extname(filename).tr('.', '').downcase
raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}", true)
data = send(:"load_#{type}", filename)
data, keys_symbolized = send(:"load_#{type}", filename)
unless data.is_a?(Hash)
raise InvalidLocaleData.new(filename, 'expects it to return a hash, but does not')
end
data.each { |locale, d| store_translations(locale, d || {}) }
data.each { |locale, d| store_translations(locale, d || {}, skip_symbolize_keys: keys_symbolized) }
end

# Loads a plain Ruby translations file. eval'ing the file must yield
# a Hash containing translation data with locales as toplevel keys.
def load_rb(filename)
eval(IO.read(filename), binding, filename)
translations = eval(IO.read(filename), binding, filename)
[translations, false]
end

# Loads a YAML translations file. The data must have locales as
# toplevel keys.
def load_yml(filename)
begin
if YAML.respond_to?(:unsafe_load_file) # Psych 4.0 way
YAML.unsafe_load_file(filename)
[YAML.unsafe_load_file(filename), false]
else
YAML.load_file(filename)
[YAML.load_file(filename), false]
end
rescue TypeError, ScriptError, StandardError => e
raise InvalidLocaleData.new(filename, e.inspect)
Expand All @@ -255,7 +256,10 @@ def load_yml(filename)
# toplevel keys.
def load_json(filename)
begin
::JSON.parse(File.read(filename), symbolize_names: true, freeze: true)
[
::JSON.parse(File.read(filename), symbolize_names: true, freeze: true),
true,
]
rescue TypeError, StandardError => e
raise InvalidLocaleData.new(filename, e.inspect)
end
Expand Down
2 changes: 1 addition & 1 deletion lib/i18n/backend/gettext.rb
Expand Up @@ -43,7 +43,7 @@ def set_comment(msgid_or_sym, comment)
def load_po(filename)
locale = ::File.basename(filename, '.po').to_sym
data = normalize(locale, parse(filename))
{ locale => data }
[{ locale => data }, false]
end

def parse(filename)
Expand Down
2 changes: 1 addition & 1 deletion lib/i18n/backend/simple.rb
Expand Up @@ -40,7 +40,7 @@ def store_translations(locale, data, options = EMPTY_HASH)
end
locale = locale.to_sym
translations[locale] ||= Concurrent::Hash.new
data = data.deep_symbolize_keys
data = data.deep_symbolize_keys unless options.fetch(:skip_symbolize_keys, false)
translations[locale].deep_merge!(data)
end

Expand Down
20 changes: 17 additions & 3 deletions test/backend/simple_test.rb
Expand Up @@ -66,17 +66,17 @@ def setup
end

test "simple load_rb: loads data from a Ruby file" do
data = I18n.backend.send(:load_rb, "#{locales_dir}/en.rb")
data, _ = I18n.backend.send(:load_rb, "#{locales_dir}/en.rb")
assert_equal({ :en => { :fuh => { :bah => 'bas' } } }, data)
end

test "simple load_yml: loads data from a YAML file" do
data = I18n.backend.send(:load_yml, "#{locales_dir}/en.yml")
data, _ = I18n.backend.send(:load_yml, "#{locales_dir}/en.yml")
assert_equal({ 'en' => { 'foo' => { 'bar' => 'baz' } } }, data)
end

test "simple load_json: loads data from a JSON file" do
data = I18n.backend.send(:load_json, "#{locales_dir}/en.json")
data, _ = I18n.backend.send(:load_json, "#{locales_dir}/en.json")
assert_equal({ :en => { :foo => { :bar => 'baz' } } }, data)
end

Expand Down Expand Up @@ -140,6 +140,20 @@ def setup
assert_equal 'foo', I18n.t(:'1')
end

test "simple store_translations: store translations doesn't deep symbolize keys if skip_symbolize_keys is true" do
data = { :foo => {'bar' => 'barfr', 'baz' => 'bazfr'} }

# symbolized by default
store_translations(:fr, data)
assert_equal Hash[:foo, {:bar => 'barfr', :baz => 'bazfr'}], translations[:fr]

I18n.backend.reload!

# not deep symbolized when configured
store_translations(:fr, data, skip_symbolize_keys: true)
assert_equal Hash[:foo, {'bar' => 'barfr', 'baz' => 'bazfr'}], translations[:fr]
end

# reloading translations

test "simple reload_translations: unloads translations" do
Expand Down
4 changes: 2 additions & 2 deletions test/test_helper.rb
Expand Up @@ -45,8 +45,8 @@ def translations
I18n.backend.instance_variable_get(:@translations)
end

def store_translations(locale, data)
I18n.backend.store_translations(locale, data)
def store_translations(locale, data, options = I18n::EMPTY_HASH)
I18n.backend.store_translations(locale, data, options)
end

def locales_dir
Expand Down

0 comments on commit d7dd3dc

Please sign in to comment.