Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix anonymous objects #393

Merged
merged 3 commits into from Mar 9, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 4 additions & 2 deletions lib/i18n.rb
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require 'concurrent/map'

require 'i18n/version'
Expand All @@ -14,7 +16,7 @@ module I18n

RESERVED_KEYS = [:scope, :default, :separator, :resolve, :object, :fallback, :fallback_in_progress, :format, :cascade, :throw, :raise, :deep_interpolation]
RESERVED_KEYS_PATTERN = /%\{(#{RESERVED_KEYS.join("|")})\}/

EMPTY_HASH = {}.freeze

def self.new_double_nested_cache # :nodoc:
Concurrent::Map.new { |h,k| h[k] = Concurrent::Map.new }
Expand Down Expand Up @@ -175,7 +177,7 @@ def translate(*args)

# Wrapper for <tt>translate</tt> that adds <tt>:raise => true</tt>. With
# this option, if no translation is found, it will raise <tt>I18n::MissingTranslationData</tt>
def translate!(key, options={})
def translate!(key, options = EMPTY_HASH)
translate(key, options.merge(:raise => true))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling merge on a frozen hash will not work:

irb(main):001:0> a = {}.freeze
=> {}
irb(main):002:0> a.merge!(raise: true)
RuntimeError: can't modify frozen Hash
	from (irb):2:in `merge!'
	from (irb):2
	from /Users/ryanbigg/.rubies/ruby-2.4.2/bin/irb:11:in `<main>'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huh this means that it is not covered with tests, need to fix this

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait a second. Yes .merge! will raise an error, because you can not mutate frozen object. But this code uses .merge without exclamation mark

end
alias :t! :translate!
Expand Down
2 changes: 2 additions & 0 deletions lib/i18n/backend.rb
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module I18n
module Backend
autoload :Base, 'i18n/backend/base'
Expand Down
18 changes: 10 additions & 8 deletions lib/i18n/backend/base.rb
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require 'yaml'
require 'i18n/core_ext/hash'
require 'i18n/core_ext/kernel/suppress_warnings'
Expand All @@ -17,11 +19,11 @@ def load_translations(*filenames)

# This method receives a locale, a data hash and options for storing translations.
# Should be implemented
def store_translations(locale, data, options = {})
def store_translations(locale, data, options = EMPTY_HASH)
raise NotImplementedError
end

def translate(locale, key, options = {})
def translate(locale, key, options = EMPTY_HASH)
raise I18n::ArgumentError if (key.is_a?(String) || key.is_a?(Symbol)) && key.empty?
raise InvalidLocale.new(locale) unless locale
return nil if key.nil? && !options.key?(:default)
Expand Down Expand Up @@ -68,7 +70,7 @@ def exists?(locale, key)
# Acts the same as +strftime+, but uses a localized version of the
# format string. Takes a key from the date/time formats translations as
# a format argument (<em>e.g.</em>, <tt>:short</tt> in <tt>:'date.formats'</tt>).
def localize(locale, object, format = :default, options = {})
def localize(locale, object, format = :default, options = EMPTY_HASH)
if object.nil? && options.include?(:default)
return options[:default]
end
Expand Down Expand Up @@ -97,7 +99,7 @@ def reload!
protected

# The method which actually looks up for the translation in the store.
def lookup(locale, key, scope = [], options = {})
def lookup(locale, key, scope = [], options = EMPTY_HASH)
raise NotImplementedError
end

Expand All @@ -109,7 +111,7 @@ def subtrees?
# If given subject is an Array, it walks the array and returns the
# first translation that can be resolved. Otherwise it tries to resolve
# the translation directly.
def default(locale, object, subject, options = {})
def default(locale, object, subject, options = EMPTY_HASH)
options = options.dup.reject { |key, value| key == :default }
case subject
when Array
Expand All @@ -126,7 +128,7 @@ def default(locale, object, subject, options = {})
# If the given subject is a Symbol, it will be translated with the
# given options. If it is a Proc then it will be evaluated. All other
# subjects will be returned directly.
def resolve(locale, object, subject, options = {})
def resolve(locale, object, subject, options = EMPTY_HASH)
return subject if options[:resolve] == false
result = catch(:exception) do
case subject
Expand Down Expand Up @@ -168,7 +170,7 @@ def pluralize(locale, entry, count)
# each element of the array is recursively interpolated (until it finds a string)
# method interpolates ["yes, %{user}", ["maybe no, %{user}, "no, %{user}"]], :user => "bartuz"
# # => "["yes, bartuz",["maybe no, bartuz", "no, bartuz"]]"
def interpolate(locale, subject, values = {})
def interpolate(locale, subject, values = EMPTY_HASH)
return subject if values.empty?

case subject
Expand All @@ -184,7 +186,7 @@ def interpolate(locale, subject, values = {})
# deep_interpolate { people: { ann: "Ann is %{ann}", john: "John is %{john}" } },
# ann: 'good', john: 'big'
# #=> { people: { ann: "Ann is good", john: "John is big" } }
def deep_interpolate(locale, data, values = {})
def deep_interpolate(locale, data, values = EMPTY_HASH)
return data if values.empty?

case data
Expand Down
10 changes: 6 additions & 4 deletions lib/i18n/backend/cache.rb
@@ -1,3 +1,5 @@
# frozen_string_literal: true

# This module allows you to easily cache all responses from the backend - thus
# speeding up the I18n aspects of your application quite a bit.
#
Expand All @@ -14,9 +16,9 @@
# ActiveSupport::Cache (only the methods #fetch and #write are being used).
#
# The cache_key implementation by default assumes you pass values that return
# a valid key from #hash (see
# http://www.ruby-doc.org/core/classes/Object.html#M000337). However, you can
# configure your own digest method via which responds to #hexdigest (see
# a valid key from #hash (see
# http://www.ruby-doc.org/core/classes/Object.html#M000337). However, you can
# configure your own digest method via which responds to #hexdigest (see
# http://ruby-doc.org/stdlib/libdoc/digest/rdoc/index.html):
#
# I18n.cache_key_digest = Digest::MD5.new
Expand Down Expand Up @@ -75,7 +77,7 @@ def perform_caching?
module Backend
# TODO Should the cache be cleared if new translations are stored?
module Cache
def translate(locale, key, options = {})
def translate(locale, key, options = EMPTY_HASH)
I18n.perform_caching? ? fetch(cache_key(locale, key, options)) { super } : super
end

Expand Down
4 changes: 3 additions & 1 deletion lib/i18n/backend/cascade.rb
@@ -1,3 +1,5 @@
# frozen_string_literal: true

# The Cascade module adds the ability to do cascading lookups to backends that
# are compatible to the Simple backend.
#
Expand Down Expand Up @@ -31,7 +33,7 @@
module I18n
module Backend
module Cascade
def lookup(locale, key, scope = [], options = {})
def lookup(locale, key, scope = [], options = EMPTY_HASH)
return super unless cascade = options[:cascade]

cascade = { :step => 1 } unless cascade.is_a?(Hash)
Expand Down
8 changes: 5 additions & 3 deletions lib/i18n/backend/chain.rb
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module I18n
module Backend
# Backend that chains multiple other backends and checks each of them when
Expand Down Expand Up @@ -28,15 +30,15 @@ def reload!
backends.each { |backend| backend.reload! }
end

def store_translations(locale, data, options = {})
def store_translations(locale, data, options = EMPTY_HASH)
backends.first.store_translations(locale, data, options)
end

def available_locales
backends.map { |backend| backend.available_locales }.flatten.uniq
end

def translate(locale, key, default_options = {})
def translate(locale, key, default_options = EMPTY_HASH)
namespace = nil
options = default_options.except(:default)

Expand All @@ -62,7 +64,7 @@ def exists?(locale, key)
end
end

def localize(locale, object, format = :default, options = {})
def localize(locale, object, format = :default, options = EMPTY_HASH)
backends.each do |backend|
catch(:exception) do
result = backend.localize(locale, object, format, options) and return result
Expand Down
24 changes: 11 additions & 13 deletions lib/i18n/backend/fallbacks.rb
@@ -1,3 +1,5 @@
# frozen_string_literal: true

# I18n locale fallbacks are useful when you want your application to use
# translations from other locales when translations for the current locale are
# missing. E.g. you might want to use :en translations when translations in
Expand Down Expand Up @@ -34,25 +36,21 @@ module Fallbacks
# The default option takes precedence over fallback locales only when
# it's a Symbol. When the default contains a String, Proc or Hash
# it is evaluated last after all the fallback locales have been tried.
def translate(locale, key, options = {})
def translate(locale, key, options = EMPTY_HASH)
return super unless options.fetch(:fallback, true)
return super if options[:fallback_in_progress]
default = extract_non_symbol_default!(options) if options[:default]

begin
options[:fallback_in_progress] = true
I18n.fallbacks[locale].each do |fallback|
begin
catch(:exception) do
result = super(fallback, key, options)
return result unless result.nil?
end
rescue I18n::InvalidLocale
# we do nothing when the locale is invalid, as this is a fallback anyways.
fallback_options = options.merge(:fallback_in_progress => true)
I18n.fallbacks[locale].each do |fallback|
begin
catch(:exception) do
result = super(fallback, key, fallback_options)
return result unless result.nil?
end
rescue I18n::InvalidLocale
# we do nothing when the locale is invalid, as this is a fallback anyways.
end
ensure
options.delete(:fallback_in_progress)
end

return if options.key?(:default) && options[:default].nil?
Expand Down
2 changes: 2 additions & 0 deletions lib/i18n/backend/flatten.rb
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module I18n
module Backend
# This module contains several helpers to assist flattening translations.
Expand Down
2 changes: 2 additions & 0 deletions lib/i18n/backend/gettext.rb
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require 'i18n/gettext'
require 'i18n/gettext/po_parser'

Expand Down
4 changes: 3 additions & 1 deletion lib/i18n/backend/interpolation_compiler.rb
@@ -1,3 +1,5 @@
# frozen_string_literal: true

# The InterpolationCompiler module contains optimizations that can tremendously
# speed up the interpolation process on the Simple backend.
#
Expand Down Expand Up @@ -104,7 +106,7 @@ def interpolate(locale, string, values)
end
end

def store_translations(locale, data, options = {})
def store_translations(locale, data, options = EMPTY_HASH)
compile_all_strings_in(data)
super
end
Expand Down
6 changes: 4 additions & 2 deletions lib/i18n/backend/key_value.rb
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require 'i18n/backend/base'

module I18n
Expand Down Expand Up @@ -74,7 +76,7 @@ def initialize(store, subtrees=true)
@store, @subtrees = store, subtrees
end

def store_translations(locale, data, options = {})
def store_translations(locale, data, options = EMPTY_HASH)
escape = options.fetch(:escape, true)
flatten_translations(locale, data, escape, @subtrees).each do |key, value|
key = "#{locale}.#{key}"
Expand Down Expand Up @@ -107,7 +109,7 @@ def subtrees?
@subtrees
end

def lookup(locale, key, scope = [], options = {})
def lookup(locale, key, scope = [], options = EMPTY_HASH)
key = normalize_flat_keys(locale, key, scope, options[:separator])
value = @store["#{locale}.#{key}"]
value = JSON.decode(value) if value
Expand Down
6 changes: 4 additions & 2 deletions lib/i18n/backend/memoize.rb
@@ -1,3 +1,5 @@
# frozen_string_literal: true

# Memoize module simply memoizes the values returned by lookup using
# a flat hash and can tremendously speed up the lookup process in a backend.
#
Expand All @@ -14,7 +16,7 @@ def available_locales
@memoized_locales ||= super
end

def store_translations(locale, data, options = {})
def store_translations(locale, data, options = EMPTY_HASH)
reset_memoizations!(locale)
super
end
Expand All @@ -26,7 +28,7 @@ def reload!

protected

def lookup(locale, key, scope = nil, options = {})
def lookup(locale, key, scope = nil, options = EMPTY_HASH)
flat_key = I18n::Backend::Flatten.normalize_flat_keys(locale,
key, scope, options[:separator]).to_sym
flat_hash = memoized_lookup[locale.to_sym]
Expand Down
6 changes: 4 additions & 2 deletions lib/i18n/backend/metadata.rb
@@ -1,3 +1,5 @@
# frozen_string_literal: true

# I18n translation metadata is useful when you want to access information
# about how a translation was looked up, pluralized or interpolated in
# your application.
Expand Down Expand Up @@ -35,7 +37,7 @@ def translation_metadata=(translation_metadata)
end
end

def translate(locale, key, options = {})
def translate(locale, key, options = EMPTY_HASH)
metadata = {
:locale => locale,
:key => key,
Expand All @@ -47,7 +49,7 @@ def translate(locale, key, options = {})
with_metadata(metadata) { super }
end

def interpolate(locale, entry, values = {})
def interpolate(locale, entry, values = EMPTY_HASH)
metadata = entry.translation_metadata.merge(:original => entry)
with_metadata(metadata) { super }
end
Expand Down
2 changes: 2 additions & 0 deletions lib/i18n/backend/pluralization.rb
@@ -1,3 +1,5 @@
# frozen_string_literal: true

# I18n Pluralization are useful when you want your application to
# customize pluralization rules.
#
Expand Down
6 changes: 4 additions & 2 deletions lib/i18n/backend/simple.rb
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module I18n
module Backend
# A simple backend that reads translations from YAML files and stores them in
Expand Down Expand Up @@ -28,7 +30,7 @@ def initialized?
# This uses a deep merge for the translations hash, so existing
# translations will be overwritten by new ones only at the deepest
# level of the hash.
def store_translations(locale, data, options = {})
def store_translations(locale, data, options = EMPTY_HASH)
if I18n.enforce_available_locales &&
I18n.available_locales_initialized? &&
!I18n.available_locales.include?(locale.to_sym) &&
Expand Down Expand Up @@ -73,7 +75,7 @@ def translations
# nested translations hash. Splits keys or scopes containing dots
# into multiple keys, i.e. <tt>currency.format</tt> is regarded the same as
# <tt>%w(currency format)</tt>.
def lookup(locale, key, scope = [], options = {})
def lookup(locale, key, scope = [], options = EMPTY_HASH)
init_translations unless initialized?
keys = I18n.normalize_keys(locale, key, scope, options[:separator])

Expand Down
2 changes: 2 additions & 0 deletions lib/i18n/backend/transliterator.rb
@@ -1,4 +1,6 @@
# encoding: utf-8
# frozen_string_literal: true

module I18n
module Backend
module Transliterator
Expand Down
4 changes: 3 additions & 1 deletion lib/i18n/config.rb
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require 'set'

module I18n
Expand Down Expand Up @@ -57,7 +59,7 @@ def available_locales=(locales)
@@available_locales = nil if @@available_locales.empty?
@@available_locales_set = nil
end

# Returns true if the available_locales have been initialized
def available_locales_initialized?
( !!defined?(@@available_locales) && !!@@available_locales )
Expand Down
4 changes: 3 additions & 1 deletion lib/i18n/exceptions.rb
@@ -1,3 +1,5 @@
# frozen_string_literal: true

require 'cgi'

module I18n
Expand Down Expand Up @@ -42,7 +44,7 @@ class MissingTranslation < ArgumentError
module Base
attr_reader :locale, :key, :options

def initialize(locale, key, options = {})
def initialize(locale, key, options = EMPTY_HASH)
@key, @locale, @options = key, locale, options.dup
options.each { |k, v| self.options[k] = v.inspect if v.is_a?(Proc) }
end
Expand Down
2 changes: 2 additions & 0 deletions lib/i18n/gettext.rb
@@ -1,3 +1,5 @@
# frozen_string_literal: true

module I18n
module Gettext
PLURAL_SEPARATOR = "\001"
Expand Down