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 13, 2021
1 parent 2ab9b37 commit cbca32e
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 14 deletions.
15 changes: 8 additions & 7 deletions lib/i18n/backend/base.rb
Expand Up @@ -221,27 +221,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,9 +256,9 @@ def load_json(filename)
begin
# Use #load_file as a proxy for a version of JSON where symbolize_names and freeze are supported.
if JSON.respond_to?(:load_file)
::JSON.load_file(filename, symbolize_names: true, freeze: true)
[::JSON.load_file(filename, symbolize_names: true, freeze: true), true]
else
::JSON.parse(File.read(filename))
[::JSON.parse(File.read(filename)), false]
end
rescue TypeError, StandardError => e
raise InvalidLocaleData.new(filename, e.inspect)
Expand Down
2 changes: 1 addition & 1 deletion lib/i18n/backend/gettext.rb
Expand Up @@ -41,7 +41,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 @@ -38,7 +38,7 @@ def store_translations(locale, data, options = EMPTY_HASH)
end
locale = locale.to_sym
translations[locale] ||= Concurrent::Hash.new
data = Utils.deep_symbolize_keys(data)
data = Utils.deep_symbolize_keys(data) unless options.fetch(:skip_symbolize_keys, false)
Utils.deep_merge!(translations[locale], 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)

if JSON.respond_to?(:load_file)
Expand Down Expand Up @@ -144,6 +144,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 cbca32e

Please sign in to comment.