i18n 0.9.5 → 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +71 -32
  3. data/lib/i18n/backend/base.rb +75 -31
  4. data/lib/i18n/backend/cache.rb +10 -11
  5. data/lib/i18n/backend/cache_file.rb +36 -0
  6. data/lib/i18n/backend/cascade.rb +3 -1
  7. data/lib/i18n/backend/chain.rb +37 -6
  8. data/lib/i18n/backend/fallbacks.rb +43 -14
  9. data/lib/i18n/backend/flatten.rb +10 -5
  10. data/lib/i18n/backend/gettext.rb +4 -2
  11. data/lib/i18n/backend/interpolation_compiler.rb +3 -1
  12. data/lib/i18n/backend/key_value.rb +31 -4
  13. data/lib/i18n/backend/lazy_loadable.rb +184 -0
  14. data/lib/i18n/backend/memoize.rb +10 -2
  15. data/lib/i18n/backend/metadata.rb +5 -3
  16. data/lib/i18n/backend/pluralization.rb +3 -1
  17. data/lib/i18n/backend/simple.rb +29 -16
  18. data/lib/i18n/backend/transliterator.rb +2 -0
  19. data/lib/i18n/backend.rb +5 -1
  20. data/lib/i18n/config.rb +20 -2
  21. data/lib/i18n/exceptions.rb +60 -17
  22. data/lib/i18n/gettext/helpers.rb +4 -2
  23. data/lib/i18n/gettext/po_parser.rb +7 -7
  24. data/lib/i18n/gettext.rb +2 -0
  25. data/lib/i18n/interpolate/ruby.rb +8 -6
  26. data/lib/i18n/locale/fallbacks.rb +21 -20
  27. data/lib/i18n/locale/tag/parents.rb +8 -6
  28. data/lib/i18n/locale/tag/simple.rb +1 -1
  29. data/lib/i18n/locale.rb +2 -0
  30. data/lib/i18n/middleware.rb +2 -0
  31. data/lib/i18n/tests/basics.rb +3 -5
  32. data/lib/i18n/tests/defaults.rb +1 -1
  33. data/lib/i18n/tests/interpolation.rb +12 -7
  34. data/lib/i18n/tests/link.rb +11 -1
  35. data/lib/i18n/tests/localization/date.rb +32 -10
  36. data/lib/i18n/tests/localization/date_time.rb +28 -7
  37. data/lib/i18n/tests/localization/procs.rb +7 -5
  38. data/lib/i18n/tests/localization/time.rb +27 -5
  39. data/lib/i18n/tests/lookup.rb +4 -4
  40. data/lib/i18n/tests/pluralization.rb +1 -1
  41. data/lib/i18n/tests/procs.rb +12 -1
  42. data/lib/i18n/tests.rb +2 -0
  43. data/lib/i18n/utils.rb +55 -0
  44. data/lib/i18n/version.rb +3 -1
  45. data/lib/i18n.rb +123 -50
  46. metadata +16 -61
  47. data/gemfiles/Gemfile.rails-3.2.x +0 -10
  48. data/gemfiles/Gemfile.rails-4.0.x +0 -10
  49. data/gemfiles/Gemfile.rails-4.1.x +0 -10
  50. data/gemfiles/Gemfile.rails-4.2.x +0 -10
  51. data/gemfiles/Gemfile.rails-5.0.x +0 -10
  52. data/gemfiles/Gemfile.rails-5.1.x +0 -10
  53. data/gemfiles/Gemfile.rails-master +0 -10
  54. data/lib/i18n/core_ext/hash.rb +0 -29
  55. data/lib/i18n/core_ext/kernel/suppress_warnings.rb +0 -8
  56. data/lib/i18n/core_ext/string/interpolate.rb +0 -9
  57. data/test/api/all_features_test.rb +0 -58
  58. data/test/api/cascade_test.rb +0 -28
  59. data/test/api/chain_test.rb +0 -24
  60. data/test/api/fallbacks_test.rb +0 -30
  61. data/test/api/key_value_test.rb +0 -24
  62. data/test/api/memoize_test.rb +0 -56
  63. data/test/api/override_test.rb +0 -42
  64. data/test/api/pluralization_test.rb +0 -30
  65. data/test/api/simple_test.rb +0 -28
  66. data/test/backend/cache_test.rb +0 -109
  67. data/test/backend/cascade_test.rb +0 -86
  68. data/test/backend/chain_test.rb +0 -122
  69. data/test/backend/exceptions_test.rb +0 -36
  70. data/test/backend/fallbacks_test.rb +0 -219
  71. data/test/backend/interpolation_compiler_test.rb +0 -118
  72. data/test/backend/key_value_test.rb +0 -61
  73. data/test/backend/memoize_test.rb +0 -79
  74. data/test/backend/metadata_test.rb +0 -48
  75. data/test/backend/pluralization_test.rb +0 -45
  76. data/test/backend/simple_test.rb +0 -103
  77. data/test/backend/transliterator_test.rb +0 -84
  78. data/test/core_ext/hash_test.rb +0 -36
  79. data/test/gettext/api_test.rb +0 -214
  80. data/test/gettext/backend_test.rb +0 -92
  81. data/test/i18n/exceptions_test.rb +0 -117
  82. data/test/i18n/gettext_plural_keys_test.rb +0 -20
  83. data/test/i18n/interpolate_test.rb +0 -91
  84. data/test/i18n/load_path_test.rb +0 -34
  85. data/test/i18n/middleware_test.rb +0 -24
  86. data/test/i18n_test.rb +0 -462
  87. data/test/locale/fallbacks_test.rb +0 -133
  88. data/test/locale/tag/rfc4646_test.rb +0 -143
  89. data/test/locale/tag/simple_test.rb +0 -32
  90. data/test/run_all.rb +0 -20
  91. data/test/test_data/locales/de.po +0 -82
  92. data/test/test_data/locales/en.rb +0 -3
  93. data/test/test_data/locales/en.yml +0 -3
  94. data/test/test_data/locales/invalid/empty.yml +0 -0
  95. data/test/test_data/locales/invalid/syntax.yml +0 -4
  96. data/test/test_data/locales/plurals.rb +0 -113
  97. data/test/test_helper.rb +0 -61
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # I18n locale fallbacks are useful when you want your application to use
2
4
  # translations from other locales when translations for the current locale are
3
5
  # missing. E.g. you might want to use :en translations when translations in
@@ -14,11 +16,13 @@ module I18n
14
16
  # Returns the current fallbacks implementation. Defaults to +I18n::Locale::Fallbacks+.
15
17
  def fallbacks
16
18
  @@fallbacks ||= I18n::Locale::Fallbacks.new
19
+ Thread.current[:i18n_fallbacks] || @@fallbacks
17
20
  end
18
21
 
19
22
  # Sets the current fallbacks implementation. Use this to set a different fallbacks implementation.
20
23
  def fallbacks=(fallbacks)
21
- @@fallbacks = fallbacks
24
+ @@fallbacks = fallbacks.is_a?(Array) ? I18n::Locale::Fallbacks.new(fallbacks) : fallbacks
25
+ Thread.current[:i18n_fallbacks] = @@fallbacks
22
26
  end
23
27
  end
24
28
 
@@ -34,25 +38,24 @@ module I18n
34
38
  # The default option takes precedence over fallback locales only when
35
39
  # it's a Symbol. When the default contains a String, Proc or Hash
36
40
  # it is evaluated last after all the fallback locales have been tried.
37
- def translate(locale, key, options = {})
41
+ def translate(locale, key, options = EMPTY_HASH)
38
42
  return super unless options.fetch(:fallback, true)
39
43
  return super if options[:fallback_in_progress]
40
44
  default = extract_non_symbol_default!(options) if options[:default]
41
45
 
42
- begin
43
- options[:fallback_in_progress] = true
44
- I18n.fallbacks[locale].each do |fallback|
45
- begin
46
- catch(:exception) do
47
- result = super(fallback, key, options)
48
- return result unless result.nil?
46
+ fallback_options = options.merge(:fallback_in_progress => true, fallback_original_locale: locale)
47
+ I18n.fallbacks[locale].each do |fallback|
48
+ begin
49
+ catch(:exception) do
50
+ result = super(fallback, key, fallback_options)
51
+ unless result.nil?
52
+ on_fallback(locale, fallback, key, options) if locale.to_s != fallback.to_s
53
+ return result
49
54
  end
50
- rescue I18n::InvalidLocale
51
- # we do nothing when the locale is invalid, as this is a fallback anyways.
52
55
  end
56
+ rescue I18n::InvalidLocale
57
+ # we do nothing when the locale is invalid, as this is a fallback anyways.
53
58
  end
54
- ensure
55
- options.delete(:fallback_in_progress)
56
59
  end
57
60
 
58
61
  return if options.key?(:default) && options[:default].nil?
@@ -61,6 +64,24 @@ module I18n
61
64
  throw(:exception, I18n::MissingTranslation.new(locale, key, options))
62
65
  end
63
66
 
67
+ def resolve_entry(locale, object, subject, options = EMPTY_HASH)
68
+ return subject if options[:resolve] == false
69
+ result = catch(:exception) do
70
+ options.delete(:fallback_in_progress) if options.key?(:fallback_in_progress)
71
+
72
+ case subject
73
+ when Symbol
74
+ I18n.translate(subject, **options.merge(:locale => options[:fallback_original_locale], :throw => true))
75
+ when Proc
76
+ date_or_time = options.delete(:object) || object
77
+ resolve_entry(options[:fallback_original_locale], object, subject.call(date_or_time, **options))
78
+ else
79
+ subject
80
+ end
81
+ end
82
+ result unless result.is_a?(MissingTranslation)
83
+ end
84
+
64
85
  def extract_non_symbol_default!(options)
65
86
  defaults = [options[:default]].flatten
66
87
  first_non_symbol_default = defaults.detect{|default| !default.is_a?(Symbol)}
@@ -70,7 +91,8 @@ module I18n
70
91
  return first_non_symbol_default
71
92
  end
72
93
 
73
- def exists?(locale, key)
94
+ def exists?(locale, key, options = EMPTY_HASH)
95
+ return super unless options.fetch(:fallback, true)
74
96
  I18n.fallbacks[locale].each do |fallback|
75
97
  begin
76
98
  return true if super(fallback, key)
@@ -81,6 +103,13 @@ module I18n
81
103
 
82
104
  false
83
105
  end
106
+
107
+ private
108
+
109
+ # Overwrite on_fallback to add specified logic when the fallback succeeds.
110
+ def on_fallback(_original_locale, _fallback_locale, _key, _optoins)
111
+ nil
112
+ end
84
113
  end
85
114
  end
86
115
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module I18n
2
4
  module Backend
3
5
  # This module contains several helpers to assist flattening translations.
@@ -16,14 +18,17 @@ module I18n
16
18
  # and creates way less objects than the one at I18n.normalize_keys.
17
19
  # It also handles escaping the translation keys.
18
20
  def self.normalize_flat_keys(locale, key, scope, separator)
19
- keys = [scope, key].flatten.compact
21
+ keys = [scope, key]
22
+ keys.flatten!
23
+ keys.compact!
24
+
20
25
  separator ||= I18n.default_separator
21
26
 
22
27
  if separator != FLATTEN_SEPARATOR
23
- keys.map! do |k|
24
- k.to_s.tr("#{FLATTEN_SEPARATOR}#{separator}",
25
- "#{SEPARATOR_ESCAPE_CHAR}#{FLATTEN_SEPARATOR}")
26
- end
28
+ from_str = "#{FLATTEN_SEPARATOR}#{separator}"
29
+ to_str = "#{SEPARATOR_ESCAPE_CHAR}#{FLATTEN_SEPARATOR}"
30
+
31
+ keys.map! { |k| k.to_s.tr from_str, to_str }
27
32
  end
28
33
 
29
34
  keys.join(".")
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'i18n/gettext'
2
4
  require 'i18n/gettext/po_parser'
3
5
 
@@ -39,7 +41,7 @@ module I18n
39
41
  def load_po(filename)
40
42
  locale = ::File.basename(filename, '.po').to_sym
41
43
  data = normalize(locale, parse(filename))
42
- { locale => data }
44
+ [{ locale => data }, false]
43
45
  end
44
46
 
45
47
  def parse(filename)
@@ -57,7 +59,7 @@ module I18n
57
59
  { part => _normalized.empty? ? value : _normalized }
58
60
  end
59
61
 
60
- result.deep_merge!(normalized)
62
+ Utils.deep_merge!(result, normalized)
61
63
  end
62
64
  result
63
65
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # The InterpolationCompiler module contains optimizations that can tremendously
2
4
  # speed up the interpolation process on the Simple backend.
3
5
  #
@@ -104,7 +106,7 @@ module I18n
104
106
  end
105
107
  end
106
108
 
107
- def store_translations(locale, data, options = {})
109
+ def store_translations(locale, data, options = EMPTY_HASH)
108
110
  compile_all_strings_in(data)
109
111
  super
110
112
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'i18n/backend/base'
2
4
 
3
5
  module I18n
@@ -74,7 +76,11 @@ module I18n
74
76
  @store, @subtrees = store, subtrees
75
77
  end
76
78
 
77
- def store_translations(locale, data, options = {})
79
+ def initialized?
80
+ !@store.nil?
81
+ end
82
+
83
+ def store_translations(locale, data, options = EMPTY_HASH)
78
84
  escape = options.fetch(:escape, true)
79
85
  flatten_translations(locale, data, escape, @subtrees).each do |key, value|
80
86
  key = "#{locale}.#{key}"
@@ -83,7 +89,7 @@ module I18n
83
89
  when Hash
84
90
  if @subtrees && (old_value = @store[key])
85
91
  old_value = JSON.decode(old_value)
86
- value = old_value.deep_symbolize_keys.deep_merge!(value) if old_value.is_a?(Hash)
92
+ value = Utils.deep_merge!(Utils.deep_symbolize_keys(old_value), value) if old_value.is_a?(Hash)
87
93
  end
88
94
  when Proc
89
95
  raise "Key-value stores cannot handle procs"
@@ -103,17 +109,37 @@ module I18n
103
109
 
104
110
  protected
105
111
 
112
+ # Queries the translations from the key-value store and converts
113
+ # them into a hash such as the one returned from loading the
114
+ # haml files
115
+ def translations
116
+ @translations = Utils.deep_symbolize_keys(@store.keys.clone.map do |main_key|
117
+ main_value = JSON.decode(@store[main_key])
118
+ main_key.to_s.split(".").reverse.inject(main_value) do |value, key|
119
+ {key.to_sym => value}
120
+ end
121
+ end.inject{|hash, elem| Utils.deep_merge!(hash, elem)})
122
+ end
123
+
124
+ def init_translations
125
+ # NO OP
126
+ # This call made also inside Simple Backend and accessed by
127
+ # other plugins like I18n-js and babilu and
128
+ # to use it along with the Chain backend we need to
129
+ # provide a uniform API even for protected methods :S
130
+ end
131
+
106
132
  def subtrees?
107
133
  @subtrees
108
134
  end
109
135
 
110
- def lookup(locale, key, scope = [], options = {})
136
+ def lookup(locale, key, scope = [], options = EMPTY_HASH)
111
137
  key = normalize_flat_keys(locale, key, scope, options[:separator])
112
138
  value = @store["#{locale}.#{key}"]
113
139
  value = JSON.decode(value) if value
114
140
 
115
141
  if value.is_a?(Hash)
116
- value.deep_symbolize_keys
142
+ Utils.deep_symbolize_keys(value)
117
143
  elsif !value.nil?
118
144
  value
119
145
  elsif !@subtrees
@@ -125,6 +151,7 @@ module I18n
125
151
  if subtrees?
126
152
  super
127
153
  else
154
+ return entry unless entry.is_a?(Hash)
128
155
  key = pluralization_key(entry, count)
129
156
  entry[key]
130
157
  end
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18n
4
+ module Backend
5
+ # Backend that lazy loads translations based on the current locale. This
6
+ # implementation avoids loading all translations up front. Instead, it only
7
+ # loads the translations that belong to the current locale. This offers a
8
+ # performance incentive in local development and test environments for
9
+ # applications with many translations for many different locales. It's
10
+ # particularly useful when the application only refers to a single locales'
11
+ # translations at a time (ex. A Rails workload). The implementation
12
+ # identifies which translation files from the load path belong to the
13
+ # current locale by pattern matching against their path name.
14
+ #
15
+ # Specifically, a translation file is considered to belong to a locale if:
16
+ # a) the filename is in the I18n load path
17
+ # b) the filename ends in a supported extension (ie. .yml, .json, .po, .rb)
18
+ # c) the filename starts with the locale identifier
19
+ # d) the locale identifier and optional proceeding text is separated by an underscore, ie. "_".
20
+ #
21
+ # Examples:
22
+ # Valid files that will be selected by this backend:
23
+ #
24
+ # "files/locales/en_translation.yml" (Selected for locale "en")
25
+ # "files/locales/fr.po" (Selected for locale "fr")
26
+ #
27
+ # Invalid files that won't be selected by this backend:
28
+ #
29
+ # "files/locales/translation-file"
30
+ # "files/locales/en-translation.unsupported"
31
+ # "files/locales/french/translation.yml"
32
+ # "files/locales/fr/translation.yml"
33
+ #
34
+ # The implementation uses this assumption to defer the loading of
35
+ # translation files until the current locale actually requires them.
36
+ #
37
+ # The backend has two working modes: lazy_load and eager_load.
38
+ #
39
+ # Note: This backend should only be enabled in test environments!
40
+ # When the mode is set to false, the backend behaves exactly like the
41
+ # Simple backend, with an additional check that the paths being loaded
42
+ # abide by the format. If paths can't be matched to the format, an error is raised.
43
+ #
44
+ # You can configure lazy loaded backends through the initializer or backends
45
+ # accessor:
46
+ #
47
+ # # In test environments
48
+ #
49
+ # I18n.backend = I18n::Backend::LazyLoadable.new(lazy_load: true)
50
+ #
51
+ # # In other environments, such as production and CI
52
+ #
53
+ # I18n.backend = I18n::Backend::LazyLoadable.new(lazy_load: false) # default
54
+ #
55
+ class LocaleExtractor
56
+ class << self
57
+ def locale_from_path(path)
58
+ name = File.basename(path, ".*")
59
+ locale = name.split("_").first
60
+ locale.to_sym unless locale.nil?
61
+ end
62
+ end
63
+ end
64
+
65
+ class LazyLoadable < Simple
66
+ def initialize(lazy_load: false)
67
+ @lazy_load = lazy_load
68
+ end
69
+
70
+ # Returns whether the current locale is initialized.
71
+ def initialized?
72
+ if lazy_load?
73
+ initialized_locales[I18n.locale]
74
+ else
75
+ super
76
+ end
77
+ end
78
+
79
+ # Clean up translations and uninitialize all locales.
80
+ def reload!
81
+ if lazy_load?
82
+ @initialized_locales = nil
83
+ @translations = nil
84
+ else
85
+ super
86
+ end
87
+ end
88
+
89
+ # Eager loading is not supported in the lazy context.
90
+ def eager_load!
91
+ if lazy_load?
92
+ raise UnsupportedMethod.new(__method__, self.class, "Cannot eager load translations because backend was configured with lazy_load: true.")
93
+ else
94
+ super
95
+ end
96
+ end
97
+
98
+ # Parse the load path and extract all locales.
99
+ def available_locales
100
+ if lazy_load?
101
+ I18n.load_path.map { |path| LocaleExtractor.locale_from_path(path) }
102
+ else
103
+ super
104
+ end
105
+ end
106
+
107
+ def lookup(locale, key, scope = [], options = EMPTY_HASH)
108
+ if lazy_load?
109
+ I18n.with_locale(locale) do
110
+ super
111
+ end
112
+ else
113
+ super
114
+ end
115
+ end
116
+
117
+ protected
118
+
119
+
120
+ # Load translations from files that belong to the current locale.
121
+ def init_translations
122
+ file_errors = if lazy_load?
123
+ initialized_locales[I18n.locale] = true
124
+ load_translations_and_collect_file_errors(filenames_for_current_locale)
125
+ else
126
+ @initialized = true
127
+ load_translations_and_collect_file_errors(I18n.load_path)
128
+ end
129
+
130
+ raise InvalidFilenames.new(file_errors) unless file_errors.empty?
131
+ end
132
+
133
+ def initialized_locales
134
+ @initialized_locales ||= Hash.new(false)
135
+ end
136
+
137
+ private
138
+
139
+ def lazy_load?
140
+ @lazy_load
141
+ end
142
+
143
+ class FilenameIncorrect < StandardError
144
+ def initialize(file, expected_locale, unexpected_locales)
145
+ super "#{file} can only load translations for \"#{expected_locale}\". Found translations for: #{unexpected_locales}."
146
+ end
147
+ end
148
+
149
+ # Loads each file supplied and asserts that the file only loads
150
+ # translations as expected by the name. The method returns a list of
151
+ # errors corresponding to offending files.
152
+ def load_translations_and_collect_file_errors(files)
153
+ errors = []
154
+
155
+ load_translations(files) do |file, loaded_translations|
156
+ assert_file_named_correctly!(file, loaded_translations)
157
+ rescue FilenameIncorrect => e
158
+ errors << e
159
+ end
160
+
161
+ errors
162
+ end
163
+
164
+ # Select all files from I18n load path that belong to current locale.
165
+ # These files must start with the locale identifier (ie. "en", "pt-BR"),
166
+ # followed by an "_" demarcation to separate proceeding text.
167
+ def filenames_for_current_locale
168
+ I18n.load_path.flatten.select do |path|
169
+ LocaleExtractor.locale_from_path(path) == I18n.locale
170
+ end
171
+ end
172
+
173
+ # Checks if a filename is named in correspondence to the translations it loaded.
174
+ # The locale extracted from the path must be the single locale loaded in the translations.
175
+ def assert_file_named_correctly!(file, translations)
176
+ loaded_locales = translations.keys.map(&:to_sym)
177
+ expected_locale = LocaleExtractor.locale_from_path(file)
178
+ unexpected_locales = loaded_locales.reject { |locale| locale == expected_locale }
179
+
180
+ raise FilenameIncorrect.new(file, expected_locale, unexpected_locales) unless unexpected_locales.empty?
181
+ end
182
+ end
183
+ end
184
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Memoize module simply memoizes the values returned by lookup using
2
4
  # a flat hash and can tremendously speed up the lookup process in a backend.
3
5
  #
@@ -14,7 +16,7 @@ module I18n
14
16
  @memoized_locales ||= super
15
17
  end
16
18
 
17
- def store_translations(locale, data, options = {})
19
+ def store_translations(locale, data, options = EMPTY_HASH)
18
20
  reset_memoizations!(locale)
19
21
  super
20
22
  end
@@ -24,9 +26,15 @@ module I18n
24
26
  super
25
27
  end
26
28
 
29
+ def eager_load!
30
+ memoized_lookup
31
+ available_locales
32
+ super
33
+ end
34
+
27
35
  protected
28
36
 
29
- def lookup(locale, key, scope = nil, options = {})
37
+ def lookup(locale, key, scope = nil, options = EMPTY_HASH)
30
38
  flat_key = I18n::Backend::Flatten.normalize_flat_keys(locale,
31
39
  key, scope, options[:separator]).to_sym
32
40
  flat_hash = memoized_lookup[locale.to_sym]
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # I18n translation metadata is useful when you want to access information
2
4
  # about how a translation was looked up, pluralized or interpolated in
3
5
  # your application.
@@ -35,19 +37,19 @@ module I18n
35
37
  end
36
38
  end
37
39
 
38
- def translate(locale, key, options = {})
40
+ def translate(locale, key, options = EMPTY_HASH)
39
41
  metadata = {
40
42
  :locale => locale,
41
43
  :key => key,
42
44
  :scope => options[:scope],
43
45
  :default => options[:default],
44
46
  :separator => options[:separator],
45
- :values => options.reject { |name, value| RESERVED_KEYS.include?(name) }
47
+ :values => options.reject { |name, _value| RESERVED_KEYS.include?(name) }
46
48
  }
47
49
  with_metadata(metadata) { super }
48
50
  end
49
51
 
50
- def interpolate(locale, entry, values = {})
52
+ def interpolate(locale, entry, values = EMPTY_HASH)
51
53
  metadata = entry.translation_metadata.merge(:original => entry)
52
54
  with_metadata(metadata) { super }
53
55
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # I18n Pluralization are useful when you want your application to
2
4
  # customize pluralization rules.
3
5
  #
@@ -27,7 +29,7 @@ module I18n
27
29
  # either pick a special :zero translation even for languages where the
28
30
  # pluralizer does not return a :zero key.
29
31
  def pluralize(locale, entry, count)
30
- return entry unless entry.is_a?(Hash) and count
32
+ return entry unless entry.is_a?(Hash) && count
31
33
 
32
34
  pluralizer = pluralizer(locale)
33
35
  if pluralizer.respond_to?(:call)
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'i18n/backend/base'
4
+
1
5
  module I18n
2
6
  module Backend
3
7
  # A simple backend that reads translations from YAML files and stores them in
@@ -15,8 +19,6 @@ module I18n
15
19
  #
16
20
  # I18n::Backend::Simple.include(I18n::Backend::Pluralization)
17
21
  class Simple
18
- (class << self; self; end).class_eval { public :include }
19
-
20
22
  module Implementation
21
23
  include Base
22
24
 
@@ -28,17 +30,16 @@ module I18n
28
30
  # This uses a deep merge for the translations hash, so existing
29
31
  # translations will be overwritten by new ones only at the deepest
30
32
  # level of the hash.
31
- def store_translations(locale, data, options = {})
33
+ def store_translations(locale, data, options = EMPTY_HASH)
32
34
  if I18n.enforce_available_locales &&
33
35
  I18n.available_locales_initialized? &&
34
- !I18n.available_locales.include?(locale.to_sym) &&
35
- !I18n.available_locales.include?(locale.to_s)
36
+ !I18n.locale_available?(locale)
36
37
  return data
37
38
  end
38
39
  locale = locale.to_sym
39
- translations[locale] ||= {}
40
- data = data.deep_symbolize_keys
41
- translations[locale].deep_merge!(data)
40
+ translations[locale] ||= Concurrent::Hash.new
41
+ data = Utils.deep_symbolize_keys(data) unless options.fetch(:skip_symbolize_keys, false)
42
+ Utils.deep_merge!(translations[locale], data)
42
43
  end
43
44
 
44
45
  # Get available locales from the translations hash
@@ -57,6 +58,19 @@ module I18n
57
58
  super
58
59
  end
59
60
 
61
+ def eager_load!
62
+ init_translations unless initialized?
63
+ super
64
+ end
65
+
66
+ def translations(do_init: false)
67
+ # To avoid returning empty translations,
68
+ # call `init_translations`
69
+ init_translations if do_init && !initialized?
70
+
71
+ @translations ||= Concurrent::Hash.new { |h, k| h[k] = Concurrent::Hash.new }
72
+ end
73
+
60
74
  protected
61
75
 
62
76
  def init_translations
@@ -64,24 +78,23 @@ module I18n
64
78
  @initialized = true
65
79
  end
66
80
 
67
- def translations
68
- @translations ||= {}
69
- end
70
-
71
81
  # Looks up a translation from the translations hash. Returns nil if
72
82
  # either key is nil, or locale, scope or key do not exist as a key in the
73
83
  # nested translations hash. Splits keys or scopes containing dots
74
84
  # into multiple keys, i.e. <tt>currency.format</tt> is regarded the same as
75
85
  # <tt>%w(currency format)</tt>.
76
- def lookup(locale, key, scope = [], options = {})
86
+ def lookup(locale, key, scope = [], options = EMPTY_HASH)
77
87
  init_translations unless initialized?
78
88
  keys = I18n.normalize_keys(locale, key, scope, options[:separator])
79
89
 
80
90
  keys.inject(translations) do |result, _key|
81
- _key = _key.to_sym
82
- return nil unless result.is_a?(Hash) && result.has_key?(_key)
91
+ return nil unless result.is_a?(Hash)
92
+ unless result.has_key?(_key)
93
+ _key = _key.to_s.to_sym
94
+ return nil unless result.has_key?(_key)
95
+ end
83
96
  result = result[_key]
84
- result = resolve(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol)
97
+ result = resolve_entry(locale, _key, result, options.merge(:scope => nil)) if result.is_a?(Symbol)
85
98
  result
86
99
  end
87
100
  end
@@ -1,4 +1,6 @@
1
1
  # encoding: utf-8
2
+ # frozen_string_literal: true
3
+
2
4
  module I18n
3
5
  module Backend
4
6
  module Transliterator
data/lib/i18n/backend.rb CHANGED
@@ -1,14 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module I18n
2
4
  module Backend
3
5
  autoload :Base, 'i18n/backend/base'
4
- autoload :InterpolationCompiler, 'i18n/backend/interpolation_compiler'
5
6
  autoload :Cache, 'i18n/backend/cache'
7
+ autoload :CacheFile, 'i18n/backend/cache_file'
6
8
  autoload :Cascade, 'i18n/backend/cascade'
7
9
  autoload :Chain, 'i18n/backend/chain'
8
10
  autoload :Fallbacks, 'i18n/backend/fallbacks'
9
11
  autoload :Flatten, 'i18n/backend/flatten'
10
12
  autoload :Gettext, 'i18n/backend/gettext'
13
+ autoload :InterpolationCompiler, 'i18n/backend/interpolation_compiler'
11
14
  autoload :KeyValue, 'i18n/backend/key_value'
15
+ autoload :LazyLoadable, 'i18n/backend/lazy_loadable'
12
16
  autoload :Memoize, 'i18n/backend/memoize'
13
17
  autoload :Metadata, 'i18n/backend/metadata'
14
18
  autoload :Pluralization, 'i18n/backend/pluralization'
data/lib/i18n/config.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'set'
2
4
 
3
5
  module I18n
@@ -5,7 +7,7 @@ module I18n
5
7
  # The only configuration value that is not global and scoped to thread is :locale.
6
8
  # It defaults to the default_locale.
7
9
  def locale
8
- defined?(@locale) && @locale ? @locale : default_locale
10
+ defined?(@locale) && @locale != nil ? @locale : default_locale
9
11
  end
10
12
 
11
13
  # Sets the current locale pseudo-globally, i.e. in the Thread.current hash.
@@ -57,7 +59,7 @@ module I18n
57
59
  @@available_locales = nil if @@available_locales.empty?
58
60
  @@available_locales_set = nil
59
61
  end
60
-
62
+
61
63
  # Returns true if the available_locales have been initialized
62
64
  def available_locales_initialized?
63
65
  ( !!defined?(@@available_locales) && !!@@available_locales )
@@ -143,5 +145,21 @@ module I18n
143
145
  def enforce_available_locales=(enforce_available_locales)
144
146
  @@enforce_available_locales = enforce_available_locales
145
147
  end
148
+
149
+ # Returns the current interpolation patterns. Defaults to
150
+ # I18n::DEFAULT_INTERPOLATION_PATTERNS.
151
+ def interpolation_patterns
152
+ @@interpolation_patterns ||= I18n::DEFAULT_INTERPOLATION_PATTERNS.dup
153
+ end
154
+
155
+ # Sets the current interpolation patterns. Used to set a interpolation
156
+ # patterns.
157
+ #
158
+ # E.g. using {{}} as a placeholder like "{{hello}}, world!":
159
+ #
160
+ # I18n.config.interpolation_patterns << /\{\{(\w+)\}\}/
161
+ def interpolation_patterns=(interpolation_patterns)
162
+ @@interpolation_patterns = interpolation_patterns
163
+ end
146
164
  end
147
165
  end