i18n 1.8.7 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ecfb802a53a50fa0b480295092adbe3064584ef69f06b81851b95bd15f49bca2
4
- data.tar.gz: 84e6ce2a82b2ce6f52ba8358b5c409c7fe48ff790ca7f3680d922557b06c7f56
3
+ metadata.gz: b92d195deaeca5e93f73cc62be2d2fb4c93d8a9787449e168b493773e5072458
4
+ data.tar.gz: f1172c9fac93f493c8a5b16cff4b27154ff917aed48cbc46a4a702363863111d
5
5
  SHA512:
6
- metadata.gz: c8cf31834170e4aea3f2b25cfad6930372f4ab5f781a9fb7c5f78e6ddabb5316f45a67c5258375d50b5b4ea7f7a97d3a44eda6c34d1a8e7b8c5e5a5f21a95042
7
- data.tar.gz: 49532d4362b42b2dcf5f8ef60afd677e64d23aece076933623f0e102f3a803383bff1f6c9add7d00097044459cbd609dd71ead49a965af1636c7a3452f3a885b
6
+ metadata.gz: 0174d10f7bac17e29cf7622d3ce69850e51755d53fd3d17d2f672bc9ca3c75823b851f6d04cbd367fea8ff5830c8c755fb9c80433bac9ec3c46590cbe9a874d0
7
+ data.tar.gz: ef22ed98cb7f223753a65b9d63d0f40e4b6821c93a41b1b41627e4c7b4e3de6b7bd411d8a6ed3d46dc6554432a776535c0461248e8d1fbfeee05dcda2ab89235
data/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # Ruby I18n
2
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/i18n.svg)](https://badge.fury.io/rb/i18n)
3
4
  [![Build Status](https://github.com/ruby-i18n/i18n/workflows/Ruby/badge.svg)](https://github.com/ruby-i18n/i18n/actions?query=workflow%3ARuby)
4
5
 
5
6
  Ruby internationalization and localization (i18n) solution.
@@ -2,12 +2,10 @@
2
2
 
3
3
  require 'yaml'
4
4
  require 'json'
5
- require 'i18n/core_ext/hash'
6
5
 
7
6
  module I18n
8
7
  module Backend
9
8
  module Base
10
- using I18n::HashRefinements
11
9
  include I18n::Backend::Transliterator
12
10
 
13
11
  # Accepts a list of paths to translation files. Loads translations from
@@ -15,7 +13,10 @@ module I18n
15
13
  # for details.
16
14
  def load_translations(*filenames)
17
15
  filenames = I18n.load_path if filenames.empty?
18
- filenames.flatten.each { |filename| load_file(filename) }
16
+ filenames.flatten.each do |filename|
17
+ loaded_translations = load_file(filename)
18
+ yield filename, loaded_translations if block_given?
19
+ end
19
20
  end
20
21
 
21
22
  # This method receives a locale, a data hash and options for storing translations.
@@ -34,7 +35,7 @@ module I18n
34
35
  if entry.nil? && options.key?(:default)
35
36
  entry = default(locale, key, options[:default], options)
36
37
  else
37
- entry = resolve(locale, key, entry, options)
38
+ entry = resolve_entry(locale, key, entry, options)
38
39
  end
39
40
 
40
41
  count = options[:count]
@@ -53,7 +54,7 @@ module I18n
53
54
  end
54
55
 
55
56
  deep_interpolation = options[:deep_interpolation]
56
- values = options.except(*RESERVED_KEYS)
57
+ values = Utils.except(options, *RESERVED_KEYS)
57
58
  if values
58
59
  entry = if deep_interpolation
59
60
  deep_interpolate(locale, entry, values)
@@ -153,6 +154,7 @@ module I18n
153
154
  end
154
155
  result unless result.is_a?(MissingTranslation)
155
156
  end
157
+ alias_method :resolve_entry, :resolve
156
158
 
157
159
  # Picks a translation from a pluralized mnemonic subkey according to English
158
160
  # pluralization rules :
@@ -223,24 +225,31 @@ module I18n
223
225
  def load_file(filename)
224
226
  type = File.extname(filename).tr('.', '').downcase
225
227
  raise UnknownFileType.new(type, filename) unless respond_to?(:"load_#{type}", true)
226
- data = send(:"load_#{type}", filename)
228
+ data, keys_symbolized = send(:"load_#{type}", filename)
227
229
  unless data.is_a?(Hash)
228
230
  raise InvalidLocaleData.new(filename, 'expects it to return a hash, but does not')
229
231
  end
230
- data.each { |locale, d| store_translations(locale, d || {}) }
232
+ data.each { |locale, d| store_translations(locale, d || {}, skip_symbolize_keys: keys_symbolized) }
233
+
234
+ data
231
235
  end
232
236
 
233
237
  # Loads a plain Ruby translations file. eval'ing the file must yield
234
238
  # a Hash containing translation data with locales as toplevel keys.
235
239
  def load_rb(filename)
236
- eval(IO.read(filename), binding, filename)
240
+ translations = eval(IO.read(filename), binding, filename)
241
+ [translations, false]
237
242
  end
238
243
 
239
244
  # Loads a YAML translations file. The data must have locales as
240
245
  # toplevel keys.
241
246
  def load_yml(filename)
242
247
  begin
243
- YAML.load_file(filename)
248
+ if YAML.respond_to?(:unsafe_load_file) # Psych 4.0 way
249
+ [YAML.unsafe_load_file(filename, symbolize_names: true, freeze: true), true]
250
+ else
251
+ [YAML.load_file(filename), false]
252
+ end
244
253
  rescue TypeError, ScriptError, StandardError => e
245
254
  raise InvalidLocaleData.new(filename, e.inspect)
246
255
  end
@@ -251,7 +260,12 @@ module I18n
251
260
  # toplevel keys.
252
261
  def load_json(filename)
253
262
  begin
254
- ::JSON.parse(File.read(filename))
263
+ # Use #load_file as a proxy for a version of JSON where symbolize_names and freeze are supported.
264
+ if ::JSON.respond_to?(:load_file)
265
+ [::JSON.load_file(filename, symbolize_names: true, freeze: true), true]
266
+ else
267
+ [::JSON.parse(File.read(filename)), false]
268
+ end
255
269
  rescue TypeError, StandardError => e
256
270
  raise InvalidLocaleData.new(filename, e.inspect)
257
271
  end
@@ -17,11 +17,11 @@
17
17
  #
18
18
  # The cache_key implementation by default assumes you pass values that return
19
19
  # a valid key from #hash (see
20
- # http://www.ruby-doc.org/core/classes/Object.html#M000337). However, you can
20
+ # https://www.ruby-doc.org/core/classes/Object.html#M000337). However, you can
21
21
  # configure your own digest method via which responds to #hexdigest (see
22
- # http://ruby-doc.org/stdlib/libdoc/digest/rdoc/index.html):
22
+ # https://ruby-doc.org/stdlib/libdoc/openssl/rdoc/OpenSSL/Digest.html):
23
23
  #
24
- # I18n.cache_key_digest = Digest::MD5.new
24
+ # I18n.cache_key_digest = OpenSSL::Digest::SHA256.new
25
25
  #
26
26
  # If you use a lambda as a default value in your translation like this:
27
27
  #
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'digest/sha2'
3
+ require 'openssl'
4
4
 
5
5
  module I18n
6
6
  module Backend
@@ -19,7 +19,7 @@ module I18n
19
19
  key = I18n::Backend::Flatten.escape_default_separator(normalized_path(filename))
20
20
  old_mtime, old_digest = initialized && lookup(:i18n, key, :load_file)
21
21
  return if (mtime = File.mtime(filename).to_i) == old_mtime ||
22
- (digest = Digest::SHA2.file(filename).hexdigest) == old_digest
22
+ (digest = OpenSSL::Digest::SHA256.file(filename).hexdigest) == old_digest
23
23
  super
24
24
  store_translations(:i18n, load_file: { key => [mtime, digest] })
25
25
  end
@@ -17,8 +17,6 @@ module I18n
17
17
  # The implementation assumes that all backends added to the Chain implement
18
18
  # a lookup method with the same API as Simple backend does.
19
19
  class Chain
20
- using I18n::HashRefinements
21
-
22
20
  module Implementation
23
21
  include Base
24
22
 
@@ -55,7 +53,7 @@ module I18n
55
53
 
56
54
  def translate(locale, key, default_options = EMPTY_HASH)
57
55
  namespace = nil
58
- options = default_options.except(:default)
56
+ options = Utils.except(default_options, :default)
59
57
 
60
58
  backends.each do |backend|
61
59
  catch(:exception) do
@@ -101,7 +99,7 @@ module I18n
101
99
  init_translations unless initialized?
102
100
  translations
103
101
  end
104
- memo.deep_merge!(partial_translations) { |_, a, b| b || a }
102
+ Utils.deep_merge!(memo, partial_translations) { |_, a, b| b || a }
105
103
  end
106
104
  end
107
105
 
@@ -16,11 +16,13 @@ module I18n
16
16
  # Returns the current fallbacks implementation. Defaults to +I18n::Locale::Fallbacks+.
17
17
  def fallbacks
18
18
  @@fallbacks ||= I18n::Locale::Fallbacks.new
19
+ Thread.current[:i18n_fallbacks] || @@fallbacks
19
20
  end
20
21
 
21
22
  # Sets the current fallbacks implementation. Use this to set a different fallbacks implementation.
22
23
  def fallbacks=(fallbacks)
23
24
  @@fallbacks = fallbacks.is_a?(Array) ? I18n::Locale::Fallbacks.new(fallbacks) : fallbacks
25
+ Thread.current[:i18n_fallbacks] = @@fallbacks
24
26
  end
25
27
  end
26
28
 
@@ -41,13 +43,13 @@ module I18n
41
43
  return super if options[:fallback_in_progress]
42
44
  default = extract_non_symbol_default!(options) if options[:default]
43
45
 
44
- fallback_options = options.merge(:fallback_in_progress => true)
46
+ fallback_options = options.merge(:fallback_in_progress => true, fallback_original_locale: locale)
45
47
  I18n.fallbacks[locale].each do |fallback|
46
48
  begin
47
49
  catch(:exception) do
48
50
  result = super(fallback, key, fallback_options)
49
51
  unless result.nil?
50
- on_fallback(locale, fallback, key, options) if locale != fallback
52
+ on_fallback(locale, fallback, key, options) if locale.to_s != fallback.to_s
51
53
  return result
52
54
  end
53
55
  end
@@ -62,6 +64,24 @@ module I18n
62
64
  throw(:exception, I18n::MissingTranslation.new(locale, key, options))
63
65
  end
64
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
+
65
85
  def extract_non_symbol_default!(options)
66
86
  defaults = [options[:default]].flatten
67
87
  first_non_symbol_default = defaults.detect{|default| !default.is_a?(Symbol)}
@@ -87,7 +107,7 @@ module I18n
87
107
  private
88
108
 
89
109
  # Overwrite on_fallback to add specified logic when the fallback succeeds.
90
- def on_fallback(_original_locale, _fallback_locale, _key, _optoins)
110
+ def on_fallback(_original_locale, _fallback_locale, _key, _options)
91
111
  nil
92
112
  end
93
113
  end
@@ -31,8 +31,6 @@ module I18n
31
31
  # Without it strings containing periods (".") will not be translated.
32
32
 
33
33
  module Gettext
34
- using I18n::HashRefinements
35
-
36
34
  class PoData < Hash
37
35
  def set_comment(msgid_or_sym, comment)
38
36
  # ignore
@@ -43,7 +41,7 @@ module I18n
43
41
  def load_po(filename)
44
42
  locale = ::File.basename(filename, '.po').to_sym
45
43
  data = normalize(locale, parse(filename))
46
- { locale => data }
44
+ [{ locale => data }, false]
47
45
  end
48
46
 
49
47
  def parse(filename)
@@ -61,7 +59,7 @@ module I18n
61
59
  { part => _normalized.empty? ? value : _normalized }
62
60
  end
63
61
 
64
- result.deep_merge!(normalized)
62
+ Utils.deep_merge!(result, normalized)
65
63
  end
66
64
  result
67
65
  end
@@ -67,8 +67,6 @@ module I18n
67
67
  #
68
68
  # This is useful if you are using a KeyValue backend chained to a Simple backend.
69
69
  class KeyValue
70
- using I18n::HashRefinements
71
-
72
70
  module Implementation
73
71
  attr_accessor :store
74
72
 
@@ -91,7 +89,7 @@ module I18n
91
89
  when Hash
92
90
  if @subtrees && (old_value = @store[key])
93
91
  old_value = JSON.decode(old_value)
94
- 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)
95
93
  end
96
94
  when Proc
97
95
  raise "Key-value stores cannot handle procs"
@@ -115,12 +113,12 @@ module I18n
115
113
  # them into a hash such as the one returned from loading the
116
114
  # haml files
117
115
  def translations
118
- @translations = @store.keys.clone.map do |main_key|
116
+ @translations = Utils.deep_symbolize_keys(@store.keys.clone.map do |main_key|
119
117
  main_value = JSON.decode(@store[main_key])
120
118
  main_key.to_s.split(".").reverse.inject(main_value) do |value, key|
121
119
  {key.to_sym => value}
122
120
  end
123
- end.inject{|hash, elem| hash.deep_merge!(elem)}.deep_symbolize_keys
121
+ end.inject{|hash, elem| Utils.deep_merge!(hash, elem)})
124
122
  end
125
123
 
126
124
  def init_translations
@@ -141,7 +139,7 @@ module I18n
141
139
  value = JSON.decode(value) if value
142
140
 
143
141
  if value.is_a?(Hash)
144
- value.deep_symbolize_keys
142
+ Utils.deep_symbolize_keys(value)
145
143
  elsif !value.nil?
146
144
  value
147
145
  elsif !@subtrees
@@ -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
@@ -19,8 +19,6 @@ module I18n
19
19
  #
20
20
  # I18n::Backend::Simple.include(I18n::Backend::Pluralization)
21
21
  class Simple
22
- using I18n::HashRefinements
23
-
24
22
  module Implementation
25
23
  include Base
26
24
 
@@ -35,14 +33,13 @@ module I18n
35
33
  def store_translations(locale, data, options = EMPTY_HASH)
36
34
  if I18n.enforce_available_locales &&
37
35
  I18n.available_locales_initialized? &&
38
- !I18n.available_locales.include?(locale.to_sym) &&
39
- !I18n.available_locales.include?(locale.to_s)
36
+ !I18n.locale_available?(locale)
40
37
  return data
41
38
  end
42
39
  locale = locale.to_sym
43
- translations[locale] ||= {}
44
- data = data.deep_symbolize_keys
45
- 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)
46
43
  end
47
44
 
48
45
  # Get available locales from the translations hash
@@ -71,7 +68,7 @@ module I18n
71
68
  # call `init_translations`
72
69
  init_translations if do_init && !initialized?
73
70
 
74
- @translations ||= {}
71
+ @translations ||= Concurrent::Hash.new { |h, k| h[k] = Concurrent::Hash.new }
75
72
  end
76
73
 
77
74
  protected
@@ -97,7 +94,7 @@ module I18n
97
94
  return nil unless result.has_key?(_key)
98
95
  end
99
96
  result = result[_key]
100
- 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)
101
98
  result
102
99
  end
103
100
  end
data/lib/i18n/backend.rb CHANGED
@@ -3,7 +3,6 @@
3
3
  module I18n
4
4
  module Backend
5
5
  autoload :Base, 'i18n/backend/base'
6
- autoload :InterpolationCompiler, 'i18n/backend/interpolation_compiler'
7
6
  autoload :Cache, 'i18n/backend/cache'
8
7
  autoload :CacheFile, 'i18n/backend/cache_file'
9
8
  autoload :Cascade, 'i18n/backend/cascade'
@@ -11,7 +10,9 @@ module I18n
11
10
  autoload :Fallbacks, 'i18n/backend/fallbacks'
12
11
  autoload :Flatten, 'i18n/backend/flatten'
13
12
  autoload :Gettext, 'i18n/backend/gettext'
13
+ autoload :InterpolationCompiler, 'i18n/backend/interpolation_compiler'
14
14
  autoload :KeyValue, 'i18n/backend/key_value'
15
+ autoload :LazyLoadable, 'i18n/backend/lazy_loadable'
15
16
  autoload :Memoize, 'i18n/backend/memoize'
16
17
  autoload :Metadata, 'i18n/backend/metadata'
17
18
  autoload :Pluralization, 'i18n/backend/pluralization'
data/lib/i18n/config.rb CHANGED
@@ -38,7 +38,7 @@ module I18n
38
38
  end
39
39
 
40
40
  # Returns an array of locales for which translations are available.
41
- # Unless you explicitely set these through I18n.available_locales=
41
+ # Unless you explicitly set these through I18n.available_locales=
42
42
  # the call will be delegated to the backend.
43
43
  def available_locales
44
44
  @@available_locales ||= nil
@@ -106,7 +106,7 @@ module I18n
106
106
  # if you don't care about arity.
107
107
  #
108
108
  # == Example:
109
- # You can supress raising an exception and return string instead:
109
+ # You can suppress raising an exception and return string instead:
110
110
  #
111
111
  # I18n.config.missing_interpolation_argument_handler = Proc.new do |key|
112
112
  # "#{key} is missing"
@@ -24,7 +24,7 @@ module I18n
24
24
  been set is likely to display text from the wrong locale to some users.
25
25
 
26
26
  If you have a legitimate reason to access i18n data outside of the user flow, you can do so by passing
27
- the desired locale explictly with the `locale` argument, e.g. `I18n.#{method}(..., locale: :en)`
27
+ the desired locale explicitly with the `locale` argument, e.g. `I18n.#{method}(..., locale: :en)`
28
28
  MESSAGE
29
29
  end
30
30
  end
@@ -47,10 +47,12 @@ module I18n
47
47
 
48
48
  class MissingTranslation < ArgumentError
49
49
  module Base
50
+ PERMITTED_KEYS = [:scope].freeze
51
+
50
52
  attr_reader :locale, :key, :options
51
53
 
52
54
  def initialize(locale, key, options = EMPTY_HASH)
53
- @key, @locale, @options = key, locale, options.dup
55
+ @key, @locale, @options = key, locale, options.slice(*PERMITTED_KEYS)
54
56
  options.each { |k, v| self.options[k] = v.inspect if v.is_a?(Proc) }
55
57
  end
56
58
 
@@ -108,4 +110,38 @@ module I18n
108
110
  super "can not load translations from #{filename}, the file type #{type} is not known"
109
111
  end
110
112
  end
113
+
114
+ class UnsupportedMethod < ArgumentError
115
+ attr_reader :method, :backend_klass, :msg
116
+ def initialize(method, backend_klass, msg)
117
+ @method = method
118
+ @backend_klass = backend_klass
119
+ @msg = msg
120
+ super "#{backend_klass} does not support the ##{method} method. #{msg}"
121
+ end
122
+ end
123
+
124
+ class InvalidFilenames < ArgumentError
125
+ NUMBER_OF_ERRORS_SHOWN = 20
126
+ def initialize(file_errors)
127
+ super <<~MSG
128
+ Found #{file_errors.count} error(s).
129
+ The first #{[file_errors.count, NUMBER_OF_ERRORS_SHOWN].min} error(s):
130
+ #{file_errors.map(&:message).first(NUMBER_OF_ERRORS_SHOWN).join("\n")}
131
+
132
+ To use the LazyLoadable backend:
133
+ 1. Filenames must start with the locale.
134
+ 2. An underscore must separate the locale with any optional text that follows.
135
+ 3. The file must only contain translation data for the single locale.
136
+
137
+ Example:
138
+ "/config/locales/fr.yml" which contains:
139
+ ```yml
140
+ fr:
141
+ dog:
142
+ chien
143
+ ```
144
+ MSG
145
+ end
146
+ end
111
147
  end
@@ -5,7 +5,7 @@ module I18n
5
5
  DEFAULT_INTERPOLATION_PATTERNS = [
6
6
  /%%/,
7
7
  /%\{([\w|]+)\}/, # matches placeholders like "%{foo} or %{foo|word}"
8
- /%<(\w+)>(.*?\d*\.?\d*[bBdiouxXeEfgGcps])/ # matches placeholders like "%<foo>.d"
8
+ /%<(\w+)>([^\d]*?\d*\.?\d*[bBdiouxXeEfgGcps])/ # matches placeholders like "%<foo>.d"
9
9
  ].freeze
10
10
  INTERPOLATION_PATTERN = Regexp.union(DEFAULT_INTERPOLATION_PATTERNS)
11
11
  deprecate_constant :INTERPOLATION_PATTERN
@@ -14,7 +14,7 @@ module I18n
14
14
  # Return String or raises MissingInterpolationArgument exception.
15
15
  # Missing argument's logic is handled by I18n.config.missing_interpolation_argument_handler.
16
16
  def interpolate(string, values)
17
- raise ReservedInterpolationKey.new($1.to_sym, string) if string =~ RESERVED_KEYS_PATTERN
17
+ raise ReservedInterpolationKey.new($1.to_sym, string) if string =~ I18n.reserved_keys_pattern
18
18
  raise ArgumentError.new('Interpolation values must be a Hash.') unless values.kind_of?(Hash)
19
19
  interpolate_hash(string, values)
20
20
  end
@@ -15,19 +15,12 @@
15
15
  # * all parent locales of a given locale (e.g. :es for :"es-MX") first,
16
16
  # * the current default locales and all of their parents second
17
17
  #
18
- # The default locales are set to [I18n.default_locale] by default but can be
19
- # set to something else.
18
+ # The default locales are set to [] by default but can be set to something else.
20
19
  #
21
20
  # One can additionally add any number of additional fallback locales manually.
22
21
  # These will be added before the default locales to the fallback chain. For
23
22
  # example:
24
23
  #
25
- # # using the default locale as default fallback locale
26
- #
27
- # I18n.default_locale = :"en-US"
28
- # I18n.fallbacks = I18n::Locale::Fallbacks.new(:"de-AT" => :"de-DE")
29
- # I18n.fallbacks[:"de-AT"] # => [:"de-AT", :de, :"de-DE"]
30
- #
31
24
  # # using a custom locale as default fallback locale
32
25
  #
33
26
  # I18n.fallbacks = I18n::Locale::Fallbacks.new(:"en-GB", :"de-AT" => :de, :"de-CH" => :de)
@@ -46,7 +39,7 @@
46
39
  # fallbacks[:"ar-PS"] # => [:"ar-PS", :ar, :"he-IL", :he, :"en-US", :en]
47
40
  # fallbacks[:"ar-EG"] # => [:"ar-EG", :ar, :"en-US", :en]
48
41
  #
49
- # # people speaking Sami as spoken in Finnland also speak Swedish and Finnish as spoken in Finnland
42
+ # # people speaking Sami as spoken in Finland also speak Swedish and Finnish as spoken in Finland
50
43
  # fallbacks.map(:sms => [:"se-FI", :"fi-FI"])
51
44
  # fallbacks[:sms] # => [:sms, :"se-FI", :se, :"fi-FI", :fi, :"en-US", :en]
52
45
 
@@ -71,13 +64,18 @@ module I18n
71
64
  super || store(locale, compute(locale))
72
65
  end
73
66
 
74
- def map(mappings)
75
- mappings.each do |from, to|
76
- from, to = from.to_sym, Array(to)
77
- to.each do |_to|
78
- @map[from] ||= []
79
- @map[from] << _to.to_sym
67
+ def map(*args, &block)
68
+ if args.count == 1 && !block_given?
69
+ mappings = args.first
70
+ mappings.each do |from, to|
71
+ from, to = from.to_sym, Array(to)
72
+ to.each do |_to|
73
+ @map[from] ||= []
74
+ @map[from] << _to.to_sym
75
+ end
80
76
  end
77
+ else
78
+ @map.map(*args, &block)
81
79
  end
82
80
  end
83
81
 
@@ -1,5 +1,5 @@
1
1
  # Simple Locale tag implementation that computes subtags by simply splitting
2
- # the locale tag at '-' occurences.
2
+ # the locale tag at '-' occurrences.
3
3
  module I18n
4
4
  module Locale
5
5
  module Tag
@@ -5,12 +5,11 @@ module I18n
5
5
  I18n.available_locales = nil
6
6
  end
7
7
 
8
- test "available_locales returns the locales stored to the backend by default" do
8
+ test "available_locales returns the available_locales produced by the backend, by default" do
9
9
  I18n.backend.store_translations('de', :foo => 'bar')
10
10
  I18n.backend.store_translations('en', :foo => 'foo')
11
11
 
12
- assert I18n.available_locales.include?(:de)
13
- assert I18n.available_locales.include?(:en)
12
+ assert_equal I18n.available_locales, I18n.backend.available_locales
14
13
  end
15
14
 
16
15
  test "available_locales can be set to something else independently from the actual locale data" do
@@ -24,11 +23,10 @@ module I18n
24
23
  assert_equal [:foo, :bar], I18n.available_locales
25
24
 
26
25
  I18n.available_locales = nil
27
- assert I18n.available_locales.include?(:de)
28
- assert I18n.available_locales.include?(:en)
26
+ assert_equal I18n.available_locales, I18n.backend.available_locales
29
27
  end
30
28
 
31
- test "available_locales memoizes when set explicitely" do
29
+ test "available_locales memoizes when set explicitly" do
32
30
  I18n.backend.expects(:available_locales).never
33
31
  I18n.available_locales = [:foo]
34
32
  I18n.backend.store_translations('de', :bar => 'baz')
@@ -36,7 +34,7 @@ module I18n
36
34
  assert_equal [:foo], I18n.available_locales
37
35
  end
38
36
 
39
- test "available_locales delegates to the backend when not set explicitely" do
37
+ test "available_locales delegates to the backend when not set explicitly" do
40
38
  original_available_locales_value = I18n.backend.available_locales
41
39
  I18n.backend.expects(:available_locales).returns(original_available_locales_value).twice
42
40
  assert_equal I18n.backend.available_locales, I18n.available_locales
@@ -37,7 +37,7 @@ module I18n
37
37
  end
38
38
 
39
39
  test "defaults: given an array of missing keys it raises a MissingTranslationData exception" do
40
- assert_raise I18n::MissingTranslationData do
40
+ assert_raises I18n::MissingTranslationData do
41
41
  I18n.t(:does_not_exist, :default => [:does_not_exist_2, :does_not_exist_3], :raise => true)
42
42
  end
43
43
  end
@@ -41,7 +41,7 @@ module I18n
41
41
  end
42
42
 
43
43
  test "interpolation: given values but missing a key it raises I18n::MissingInterpolationArgument" do
44
- assert_raise(I18n::MissingInterpolationArgument) do
44
+ assert_raises(I18n::MissingInterpolationArgument) do
45
45
  interpolate(:default => '%{foo}', :bar => 'bar')
46
46
  end
47
47
  end
@@ -77,13 +77,13 @@ module I18n
77
77
 
78
78
  if Object.const_defined?(:Encoding)
79
79
  test "interpolation: given a euc-jp translation and a utf-8 value it raises Encoding::CompatibilityError" do
80
- assert_raise(Encoding::CompatibilityError) do
80
+ assert_raises(Encoding::CompatibilityError) do
81
81
  interpolate(:default => euc_jp('こんにちは、%{name}さん!'), :name => 'ゆきひろ')
82
82
  end
83
83
  end
84
84
 
85
85
  test "interpolation: given a utf-8 translation and a euc-jp value it raises Encoding::CompatibilityError" do
86
- assert_raise(Encoding::CompatibilityError) do
86
+ assert_raises(Encoding::CompatibilityError) do
87
87
  interpolate(:default => 'こんにちは、%{name}さん!', :name => euc_jp('ゆきひろ'))
88
88
  end
89
89
  end
@@ -108,10 +108,10 @@ module I18n
108
108
  end
109
109
 
110
110
  test "interpolation: given a translations containing a reserved key it raises I18n::ReservedInterpolationKey" do
111
- assert_raise(I18n::ReservedInterpolationKey) { interpolate(:foo => :bar, :default => '%{exception_handler}') }
112
- assert_raise(I18n::ReservedInterpolationKey) { interpolate(:foo => :bar, :default => '%{default}') }
113
- assert_raise(I18n::ReservedInterpolationKey) { interpolate(:foo => :bar, :default => '%{separator}') }
114
- assert_raise(I18n::ReservedInterpolationKey) { interpolate(:foo => :bar, :default => '%{scope}') }
111
+ assert_raises(I18n::ReservedInterpolationKey) { interpolate(:foo => :bar, :default => '%{exception_handler}') }
112
+ assert_raises(I18n::ReservedInterpolationKey) { interpolate(:foo => :bar, :default => '%{default}') }
113
+ assert_raises(I18n::ReservedInterpolationKey) { interpolate(:foo => :bar, :default => '%{separator}') }
114
+ assert_raises(I18n::ReservedInterpolationKey) { interpolate(:foo => :bar, :default => '%{scope}') }
115
115
  end
116
116
 
117
117
  test "interpolation: deep interpolation for default string" do
@@ -78,15 +78,15 @@ module I18n
78
78
  end
79
79
 
80
80
  test "localize Date: given nil it raises I18n::ArgumentError" do
81
- assert_raise(I18n::ArgumentError) { I18n.l(nil) }
81
+ assert_raises(I18n::ArgumentError) { I18n.l(nil) }
82
82
  end
83
83
 
84
84
  test "localize Date: given a plain Object it raises I18n::ArgumentError" do
85
- assert_raise(I18n::ArgumentError) { I18n.l(Object.new) }
85
+ assert_raises(I18n::ArgumentError) { I18n.l(Object.new) }
86
86
  end
87
87
 
88
88
  test "localize Date: given a format is missing it raises I18n::MissingTranslationData" do
89
- assert_raise(I18n::MissingTranslationData) { I18n.l(@date, :format => :missing) }
89
+ assert_raises(I18n::MissingTranslationData) { I18n.l(@date, :format => :missing) }
90
90
  end
91
91
 
92
92
  test "localize Date: it does not alter the format string" do
@@ -78,7 +78,7 @@ module I18n
78
78
  end
79
79
 
80
80
  test "localize DateTime: given a format is missing it raises I18n::MissingTranslationData" do
81
- assert_raise(I18n::MissingTranslationData) { I18n.l(@datetime, :format => :missing) }
81
+ assert_raises(I18n::MissingTranslationData) { I18n.l(@datetime, :format => :missing) }
82
82
  end
83
83
 
84
84
  protected
@@ -74,6 +74,7 @@ module I18n
74
74
  arg.strftime('%a, %d %b %Y')
75
75
  when Hash
76
76
  arg.delete(:fallback_in_progress)
77
+ arg.delete(:fallback_original_locale)
77
78
  arg.inspect
78
79
  else
79
80
  arg.inspect
@@ -79,7 +79,7 @@ module I18n
79
79
  end
80
80
 
81
81
  test "localize Time: given a format is missing it raises I18n::MissingTranslationData" do
82
- assert_raise(I18n::MissingTranslationData) { I18n.l(@time, :format => :missing) }
82
+ assert_raises(I18n::MissingTranslationData) { I18n.l(@time, :format => :missing) }
83
83
  end
84
84
 
85
85
  protected
@@ -34,7 +34,7 @@ module I18n
34
34
  end
35
35
 
36
36
  test "lookup: given a missing key, no default and the raise option it raises MissingTranslationData" do
37
- assert_raise(I18n::MissingTranslationData) { I18n.t(:missing, :raise => true) }
37
+ assert_raises(I18n::MissingTranslationData) { I18n.t(:missing, :raise => true) }
38
38
  end
39
39
 
40
40
  test "lookup: does not raise an exception if no translation data is present for the given locale" do
@@ -61,7 +61,7 @@ module I18n
61
61
  # In fact it probably *should* fail but Rails currently relies on using the default locale instead.
62
62
  # So we'll stick to this for now until we get it fixed in Rails.
63
63
  test "lookup: given nil as a locale it does not raise but use the default locale" do
64
- # assert_raise(I18n::InvalidLocale) { I18n.t(:bar, :locale => nil) }
64
+ # assert_raises(I18n::InvalidLocale) { I18n.t(:bar, :locale => nil) }
65
65
  assert_nothing_raised { I18n.t(:bar, :locale => nil) }
66
66
  end
67
67
 
@@ -28,7 +28,7 @@ module I18n
28
28
  end
29
29
 
30
30
  test "pluralization: given incomplete pluralization data it raises I18n::InvalidPluralizationData" do
31
- assert_raise(I18n::InvalidPluralizationData) { I18n.t(:default => { :one => 'bar' }, :count => 2) }
31
+ assert_raises(I18n::InvalidPluralizationData) { I18n.t(:default => { :one => 'bar' }, :count => 2) }
32
32
  end
33
33
  end
34
34
  end
@@ -53,7 +53,13 @@ module I18n
53
53
 
54
54
 
55
55
  def self.filter_args(*args)
56
- args.map {|arg| arg.delete(:fallback_in_progress) if arg.is_a?(Hash) ; arg }.inspect
56
+ args.map do |arg|
57
+ if arg.is_a?(Hash)
58
+ arg.delete(:fallback_in_progress)
59
+ arg.delete(:fallback_original_locale)
60
+ end
61
+ arg
62
+ end.inspect
57
63
  end
58
64
  end
59
65
  end
data/lib/i18n/utils.rb ADDED
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module I18n
4
+ module Utils
5
+ class << self
6
+ if Hash.method_defined?(:except)
7
+ def except(hash, *keys)
8
+ hash.except(*keys)
9
+ end
10
+ else
11
+ def except(hash, *keys)
12
+ hash = hash.dup
13
+ keys.each { |k| hash.delete(k) }
14
+ hash
15
+ end
16
+ end
17
+
18
+ def deep_merge(hash, other_hash, &block)
19
+ deep_merge!(hash.dup, other_hash, &block)
20
+ end
21
+
22
+ def deep_merge!(hash, other_hash, &block)
23
+ hash.merge!(other_hash) do |key, this_val, other_val|
24
+ if this_val.is_a?(Hash) && other_val.is_a?(Hash)
25
+ deep_merge(this_val, other_val, &block)
26
+ elsif block_given?
27
+ yield key, this_val, other_val
28
+ else
29
+ other_val
30
+ end
31
+ end
32
+ end
33
+
34
+ def deep_symbolize_keys(hash)
35
+ hash.each_with_object({}) do |(key, value), result|
36
+ result[key.respond_to?(:to_sym) ? key.to_sym : key] = deep_symbolize_keys_in_object(value)
37
+ result
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def deep_symbolize_keys_in_object(value)
44
+ case value
45
+ when Hash
46
+ deep_symbolize_keys(value)
47
+ when Array
48
+ value.map { |e| deep_symbolize_keys_in_object(e) }
49
+ else
50
+ value
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
data/lib/i18n/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module I18n
4
- VERSION = "1.8.7"
4
+ VERSION = "1.12.0"
5
5
  end
data/lib/i18n.rb CHANGED
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'concurrent/map'
4
+ require 'concurrent/hash'
4
5
 
5
6
  require 'i18n/version'
7
+ require 'i18n/utils'
6
8
  require 'i18n/exceptions'
7
9
  require 'i18n/interpolate/ruby'
8
10
 
@@ -21,6 +23,7 @@ module I18n
21
23
  exception_handler
22
24
  fallback
23
25
  fallback_in_progress
26
+ fallback_original_locale
24
27
  format
25
28
  object
26
29
  raise
@@ -28,12 +31,24 @@ module I18n
28
31
  scope
29
32
  separator
30
33
  throw
31
- ].freeze
32
- RESERVED_KEYS_PATTERN = /%\{(#{RESERVED_KEYS.join("|")})\}/
34
+ ]
33
35
  EMPTY_HASH = {}.freeze
34
36
 
35
37
  def self.new_double_nested_cache # :nodoc:
36
- Concurrent::Map.new { |h,k| h[k] = Concurrent::Map.new }
38
+ Concurrent::Map.new { |h, k| h[k] = Concurrent::Map.new }
39
+ end
40
+
41
+ # Marks a key as reserved. Reserved keys are used internally,
42
+ # and can't also be used for interpolation. If you are using any
43
+ # extra keys as I18n options, you should call I18n.reserve_key
44
+ # before any I18n.translate (etc) calls are made.
45
+ def self.reserve_key(key)
46
+ RESERVED_KEYS << key.to_sym
47
+ @reserved_keys_pattern = nil
48
+ end
49
+
50
+ def self.reserved_keys_pattern # :nodoc:
51
+ @reserved_keys_pattern ||= /%\{(#{RESERVED_KEYS.join("|")})\}/
37
52
  end
38
53
 
39
54
  module Base
@@ -199,18 +214,12 @@ module I18n
199
214
 
200
215
  backend = config.backend
201
216
 
202
- result = catch(:exception) do
203
- if key.is_a?(Array)
204
- key.map { |k| backend.translate(locale, k, options) }
205
- else
206
- backend.translate(locale, key, options)
217
+ if key.is_a?(Array)
218
+ key.map do |k|
219
+ translate_key(k, throw, raise, locale, backend, options)
207
220
  end
208
- end
209
-
210
- if result.is_a?(MissingTranslation)
211
- handle_exception((throw && :throw || raise && :raise), result, locale, key, options)
212
221
  else
213
- result
222
+ translate_key(key, throw, raise, locale, backend, options)
214
223
  end
215
224
  end
216
225
  alias :t :translate
@@ -259,14 +268,14 @@ module I18n
259
268
  #
260
269
  # Setting a Hash using Ruby:
261
270
  #
262
- # store_translations(:de, :i18n => {
263
- # :transliterate => {
264
- # :rule => {
265
- # "ü" => "ue",
266
- # "ö" => "oe"
267
- # }
268
- # }
269
- # )
271
+ # store_translations(:de, i18n: {
272
+ # transliterate: {
273
+ # rule: {
274
+ # 'ü' => 'ue',
275
+ # 'ö' => 'oe'
276
+ # }
277
+ # }
278
+ # })
270
279
  #
271
280
  # Setting a Proc:
272
281
  #
@@ -323,11 +332,11 @@ module I18n
323
332
  def normalize_keys(locale, key, scope, separator = nil)
324
333
  separator ||= I18n.default_separator
325
334
 
326
- keys = []
327
- keys.concat normalize_key(locale, separator)
328
- keys.concat normalize_key(scope, separator)
329
- keys.concat normalize_key(key, separator)
330
- keys
335
+ [
336
+ *normalize_key(locale, separator),
337
+ *normalize_key(scope, separator),
338
+ *normalize_key(key, separator)
339
+ ]
331
340
  end
332
341
 
333
342
  # Returns true when the passed locale, which can be either a String or a
@@ -349,6 +358,18 @@ module I18n
349
358
 
350
359
  private
351
360
 
361
+ def translate_key(key, throw, raise, locale, backend, options)
362
+ result = catch(:exception) do
363
+ backend.translate(locale, key, options)
364
+ end
365
+
366
+ if result.is_a?(MissingTranslation)
367
+ handle_exception((throw && :throw || raise && :raise), result, locale, key, options)
368
+ else
369
+ result
370
+ end
371
+ end
372
+
352
373
  # Any exceptions thrown in translate will be sent to the @@exception_handler
353
374
  # which can be a Symbol, a Proc or any other Object unless they're forced to
354
375
  # be raised or thrown (MissingTranslation).
@@ -395,7 +416,7 @@ module I18n
395
416
  keys.delete('')
396
417
  keys.map! do |k|
397
418
  case k
398
- when /\A[-+]?[1-9]\d*\z/ # integer
419
+ when /\A[-+]?([1-9]\d*|0)\z/ # integer
399
420
  k.to_i
400
421
  when 'true'
401
422
  true
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: i18n
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.7
4
+ version: 1.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sven Fuchs
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2021-01-04 00:00:00.000000000 Z
16
+ date: 2022-07-13 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: concurrent-ruby
@@ -49,13 +49,13 @@ files:
49
49
  - lib/i18n/backend/gettext.rb
50
50
  - lib/i18n/backend/interpolation_compiler.rb
51
51
  - lib/i18n/backend/key_value.rb
52
+ - lib/i18n/backend/lazy_loadable.rb
52
53
  - lib/i18n/backend/memoize.rb
53
54
  - lib/i18n/backend/metadata.rb
54
55
  - lib/i18n/backend/pluralization.rb
55
56
  - lib/i18n/backend/simple.rb
56
57
  - lib/i18n/backend/transliterator.rb
57
58
  - lib/i18n/config.rb
58
- - lib/i18n/core_ext/hash.rb
59
59
  - lib/i18n/exceptions.rb
60
60
  - lib/i18n/gettext.rb
61
61
  - lib/i18n/gettext/helpers.rb
@@ -81,6 +81,7 @@ files:
81
81
  - lib/i18n/tests/lookup.rb
82
82
  - lib/i18n/tests/pluralization.rb
83
83
  - lib/i18n/tests/procs.rb
84
+ - lib/i18n/utils.rb
84
85
  - lib/i18n/version.rb
85
86
  homepage: https://github.com/ruby-i18n/i18n
86
87
  licenses:
@@ -90,23 +91,7 @@ metadata:
90
91
  changelog_uri: https://github.com/ruby-i18n/i18n/releases
91
92
  documentation_uri: https://guides.rubyonrails.org/i18n.html
92
93
  source_code_uri: https://github.com/ruby-i18n/i18n
93
- post_install_message: |2+
94
-
95
- HEADS UP! i18n 1.1 changed fallbacks to exclude default locale.
96
- But that may break your application.
97
-
98
- If you are upgrading your Rails application from an older version of Rails:
99
-
100
- Please check your Rails app for 'config.i18n.fallbacks = true'.
101
- If you're using I18n (>= 1.1.0) and Rails (< 5.2.2), this should be
102
- 'config.i18n.fallbacks = [I18n.default_locale]'.
103
- If not, fallbacks will be broken in your app by I18n 1.1.x.
104
-
105
- If you are starting a NEW Rails application, you can ignore this notice.
106
-
107
- For more info see:
108
- https://github.com/svenfuchs/i18n/releases/tag/v1.1.0
109
-
94
+ post_install_message:
110
95
  rdoc_options: []
111
96
  require_paths:
112
97
  - lib
@@ -121,9 +106,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
106
  - !ruby/object:Gem::Version
122
107
  version: 1.3.5
123
108
  requirements: []
124
- rubygems_version: 3.2.3
109
+ rubygems_version: 3.3.16
125
110
  signing_key:
126
111
  specification_version: 4
127
112
  summary: New wave Internationalization support for Ruby
128
113
  test_files: []
129
- ...
@@ -1,59 +0,0 @@
1
- module I18n
2
- module HashRefinements
3
- refine Hash do
4
- using I18n::HashRefinements
5
- def except(*keys)
6
- dup.except!(*keys)
7
- end
8
-
9
- def except!(*keys)
10
- keys.each { |key| delete(key) }
11
- self
12
- end
13
-
14
- def deep_symbolize_keys
15
- each_with_object({}) do |(key, value), result|
16
- result[symbolize_key(key)] = deep_symbolize_keys_in_object(value)
17
- result
18
- end
19
- end
20
-
21
- # deep_merge from activesupport 5
22
- # Copyright (c) 2005-2019 David Heinemeier Hansson
23
- def deep_merge(other_hash, &block)
24
- dup.deep_merge!(other_hash, &block)
25
- end
26
-
27
- # deep_merge! from activesupport 5
28
- # Copyright (c) 2005-2019 David Heinemeier Hansson
29
- def deep_merge!(other_hash, &block)
30
- merge!(other_hash) do |key, this_val, other_val|
31
- if this_val.is_a?(Hash) && other_val.is_a?(Hash)
32
- this_val.deep_merge(other_val, &block)
33
- elsif block_given?
34
- block.call(key, this_val, other_val)
35
- else
36
- other_val
37
- end
38
- end
39
- end
40
-
41
- def symbolize_key(key)
42
- key.respond_to?(:to_sym) ? key.to_sym : key
43
- end
44
-
45
- private
46
-
47
- def deep_symbolize_keys_in_object(value)
48
- case value
49
- when Hash
50
- value.deep_symbolize_keys
51
- when Array
52
- value.map { |e| deep_symbolize_keys_in_object(e) }
53
- else
54
- value
55
- end
56
- end
57
- end
58
- end
59
- end