i18n 1.8.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +122 -0
  4. data/lib/i18n.rb +414 -0
  5. data/lib/i18n/backend.rb +21 -0
  6. data/lib/i18n/backend/base.rb +285 -0
  7. data/lib/i18n/backend/cache.rb +113 -0
  8. data/lib/i18n/backend/cache_file.rb +36 -0
  9. data/lib/i18n/backend/cascade.rb +56 -0
  10. data/lib/i18n/backend/chain.rb +130 -0
  11. data/lib/i18n/backend/fallbacks.rb +95 -0
  12. data/lib/i18n/backend/flatten.rb +118 -0
  13. data/lib/i18n/backend/gettext.rb +85 -0
  14. data/lib/i18n/backend/interpolation_compiler.rb +123 -0
  15. data/lib/i18n/backend/key_value.rb +206 -0
  16. data/lib/i18n/backend/memoize.rb +54 -0
  17. data/lib/i18n/backend/metadata.rb +71 -0
  18. data/lib/i18n/backend/pluralization.rb +55 -0
  19. data/lib/i18n/backend/simple.rb +109 -0
  20. data/lib/i18n/backend/transliterator.rb +108 -0
  21. data/lib/i18n/config.rb +165 -0
  22. data/lib/i18n/core_ext/hash.rb +59 -0
  23. data/lib/i18n/exceptions.rb +111 -0
  24. data/lib/i18n/gettext.rb +28 -0
  25. data/lib/i18n/gettext/helpers.rb +75 -0
  26. data/lib/i18n/gettext/po_parser.rb +329 -0
  27. data/lib/i18n/interpolate/ruby.rb +39 -0
  28. data/lib/i18n/locale.rb +8 -0
  29. data/lib/i18n/locale/fallbacks.rb +99 -0
  30. data/lib/i18n/locale/tag.rb +28 -0
  31. data/lib/i18n/locale/tag/parents.rb +24 -0
  32. data/lib/i18n/locale/tag/rfc4646.rb +74 -0
  33. data/lib/i18n/locale/tag/simple.rb +39 -0
  34. data/lib/i18n/middleware.rb +17 -0
  35. data/lib/i18n/tests.rb +14 -0
  36. data/lib/i18n/tests/basics.rb +60 -0
  37. data/lib/i18n/tests/defaults.rb +52 -0
  38. data/lib/i18n/tests/interpolation.rb +163 -0
  39. data/lib/i18n/tests/link.rb +66 -0
  40. data/lib/i18n/tests/localization.rb +19 -0
  41. data/lib/i18n/tests/localization/date.rb +117 -0
  42. data/lib/i18n/tests/localization/date_time.rb +103 -0
  43. data/lib/i18n/tests/localization/procs.rb +117 -0
  44. data/lib/i18n/tests/localization/time.rb +103 -0
  45. data/lib/i18n/tests/lookup.rb +81 -0
  46. data/lib/i18n/tests/pluralization.rb +35 -0
  47. data/lib/i18n/tests/procs.rb +60 -0
  48. data/lib/i18n/version.rb +5 -0
  49. metadata +128 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 461ee8866033d12fb5dd2daaa85703916b83ba58c225afd5bfe31e30c3231aa6
4
+ data.tar.gz: 9bd10034e9629c3fb72c07974fef0b069047b269dbab165ecbd5b8c38c9c5567
5
+ SHA512:
6
+ metadata.gz: feb783a73598b1ab9e319f5569d71b14c061816bdb8e7d887262fed6531ec0a3c77afa36a4257080bbc7a1ec5239963a08b2f37a3dd449db82f9e402c5f57fbc
7
+ data.tar.gz: fdb2f914e53484a8cf925b71de1004a4d0c1bfa3da4b7d50bd14fcda1ab036ae12d773e692cbf85ade5e78906af24fcc05b22156e280a4615e26430f8929eb75
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 The Ruby I18n team
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,122 @@
1
+ # Ruby I18n
2
+
3
+ [![Build Status](https://github.com/ruby-i18n/i18n/workflows/Ruby/badge.svg)](https://github.com/ruby-i18n/i18n/actions?query=workflow%3ARuby)
4
+
5
+ Ruby internationalization and localization (i18n) solution.
6
+
7
+ Currently maintained by @radar.
8
+
9
+ ## Usage
10
+
11
+ ### Rails
12
+
13
+ You will most commonly use this library within a Rails app.
14
+
15
+ [See the Rails Guide](https://guides.rubyonrails.org/i18n.html) for an example of its usage.
16
+
17
+ ### Ruby (without Rails)
18
+
19
+ If you want to use this library without Rails, you can simply add `i18n` to your `Gemfile`:
20
+
21
+ ```ruby
22
+ gem 'i18n'
23
+ ```
24
+
25
+ Then configure I18n with some translations, and a default locale:
26
+
27
+ ```ruby
28
+ I18n.load_path << Dir[File.expand_path("config/locales") + "/*.yml"]
29
+ I18n.default_locale = :en # (note that `en` is already the default!)
30
+ ```
31
+
32
+ A simple translation file in your project might live at `config/locales/en.yml` and look like:
33
+
34
+ ```yml
35
+ en:
36
+ test: "This is a test"
37
+ ```
38
+
39
+ You can then access this translation by doing:
40
+
41
+ ```ruby
42
+ I18n.t(:test)
43
+ ```
44
+
45
+ You can switch locales in your project by setting `I18n.locale` to a different value:
46
+
47
+ ```ruby
48
+ I18n.locale = :de
49
+ I18n.t(:test) # => "Dies ist ein Test"
50
+ ```
51
+
52
+ ## Features
53
+
54
+ * Translation and localization
55
+ * Interpolation of values to translations
56
+ * Pluralization (CLDR compatible)
57
+ * Customizable transliteration to ASCII
58
+ * Flexible defaults
59
+ * Bulk lookup
60
+ * Lambdas as translation data
61
+ * Custom key/scope separator
62
+ * Custom exception handlers
63
+ * Extensible architecture with a swappable backend
64
+
65
+ ## Pluggable Features
66
+
67
+ * Cache
68
+ * Pluralization: lambda pluralizers stored as translation data
69
+ * Locale fallbacks, RFC4647 compliant (optionally: RFC4646 locale validation)
70
+ * [Gettext support](https://github.com/ruby-i18n/i18n/wiki/Gettext)
71
+ * Translation metadata
72
+
73
+ ## Alternative Backend
74
+
75
+ * Chain
76
+ * ActiveRecord (optionally: ActiveRecord::Missing and ActiveRecord::StoreProcs)
77
+ * KeyValue (uses active_support/json and cannot store procs)
78
+
79
+ For more information and lots of resources see [the 'Resources' page on the wiki](https://github.com/ruby-i18n/i18n/wiki/Resources).
80
+
81
+ ## Tests
82
+
83
+ You can run tests both with
84
+
85
+ * `rake test` or just `rake`
86
+ * run any test file directly, e.g. `ruby -Ilib:test test/api/simple_test.rb`
87
+
88
+ You can run all tests against all Gemfiles with
89
+
90
+ * `ruby test/run_all.rb`
91
+
92
+ The structure of the test suite is a bit unusual as it uses modules to reuse
93
+ particular tests in different test cases.
94
+
95
+ The reason for this is that we need to enforce the I18n API across various
96
+ combinations of extensions. E.g. the Simple backend alone needs to support
97
+ the same API as any combination of feature and/or optimization modules included
98
+ to the Simple backend. We test this by reusing the same API definition (implemented
99
+ as test methods) in test cases with different setups.
100
+
101
+ You can find the test cases that enforce the API in test/api. And you can find
102
+ the API definition test methods in test/api/tests.
103
+
104
+ All other test cases (e.g. as defined in test/backend, test/core_ext) etc.
105
+ follow the usual test setup and should be easy to grok.
106
+
107
+ ## More Documentation
108
+
109
+ Additional documentation can be found here: https://github.com/ruby-i18n/i18n/wiki
110
+
111
+ ## Contributors
112
+
113
+ * @radar
114
+ * @carlosantoniodasilva
115
+ * @josevalim
116
+ * @knapo
117
+ * @tigrish
118
+ * [and many more](https://github.com/ruby-i18n/i18n/graphs/contributors)
119
+
120
+ ## License
121
+
122
+ MIT License. See the included MIT-LICENSE file.
@@ -0,0 +1,414 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent/map'
4
+
5
+ require 'i18n/version'
6
+ require 'i18n/exceptions'
7
+ require 'i18n/interpolate/ruby'
8
+
9
+ module I18n
10
+ autoload :Backend, 'i18n/backend'
11
+ autoload :Config, 'i18n/config'
12
+ autoload :Gettext, 'i18n/gettext'
13
+ autoload :Locale, 'i18n/locale'
14
+ autoload :Tests, 'i18n/tests'
15
+ autoload :Middleware, 'i18n/middleware'
16
+
17
+ RESERVED_KEYS = %i[
18
+ cascade
19
+ deep_interpolation
20
+ default
21
+ exception_handler
22
+ fallback
23
+ fallback_in_progress
24
+ format
25
+ object
26
+ raise
27
+ resolve
28
+ scope
29
+ separator
30
+ throw
31
+ ].freeze
32
+ RESERVED_KEYS_PATTERN = /%\{(#{RESERVED_KEYS.join("|")})\}/
33
+ EMPTY_HASH = {}.freeze
34
+
35
+ def self.new_double_nested_cache # :nodoc:
36
+ Concurrent::Map.new { |h,k| h[k] = Concurrent::Map.new }
37
+ end
38
+
39
+ module Base
40
+ # Gets I18n configuration object.
41
+ def config
42
+ Thread.current[:i18n_config] ||= I18n::Config.new
43
+ end
44
+
45
+ # Sets I18n configuration object.
46
+ def config=(value)
47
+ Thread.current[:i18n_config] = value
48
+ end
49
+
50
+ # Write methods which delegates to the configuration object
51
+ %w(locale backend default_locale available_locales default_separator
52
+ exception_handler load_path enforce_available_locales).each do |method|
53
+ module_eval <<-DELEGATORS, __FILE__, __LINE__ + 1
54
+ def #{method}
55
+ config.#{method}
56
+ end
57
+
58
+ def #{method}=(value)
59
+ config.#{method} = (value)
60
+ end
61
+ DELEGATORS
62
+ end
63
+
64
+ # Tells the backend to reload translations. Used in situations like the
65
+ # Rails development environment. Backends can implement whatever strategy
66
+ # is useful.
67
+ def reload!
68
+ config.clear_available_locales_set
69
+ config.backend.reload!
70
+ end
71
+
72
+ # Tells the backend to load translations now. Used in situations like the
73
+ # Rails production environment. Backends can implement whatever strategy
74
+ # is useful.
75
+ def eager_load!
76
+ config.backend.eager_load!
77
+ end
78
+
79
+ # Translates, pluralizes and interpolates a given key using a given locale,
80
+ # scope, and default, as well as interpolation values.
81
+ #
82
+ # *LOOKUP*
83
+ #
84
+ # Translation data is organized as a nested hash using the upper-level keys
85
+ # as namespaces. <em>E.g.</em>, ActionView ships with the translation:
86
+ # <tt>:date => {:formats => {:short => "%b %d"}}</tt>.
87
+ #
88
+ # Translations can be looked up at any level of this hash using the key argument
89
+ # and the scope option. <em>E.g.</em>, in this example <tt>I18n.t :date</tt>
90
+ # returns the whole translations hash <tt>{:formats => {:short => "%b %d"}}</tt>.
91
+ #
92
+ # Key can be either a single key or a dot-separated key (both Strings and Symbols
93
+ # work). <em>E.g.</em>, the short format can be looked up using both:
94
+ # I18n.t 'date.formats.short'
95
+ # I18n.t :'date.formats.short'
96
+ #
97
+ # Scope can be either a single key, a dot-separated key or an array of keys
98
+ # or dot-separated keys. Keys and scopes can be combined freely. So these
99
+ # examples will all look up the same short date format:
100
+ # I18n.t 'date.formats.short'
101
+ # I18n.t 'formats.short', :scope => 'date'
102
+ # I18n.t 'short', :scope => 'date.formats'
103
+ # I18n.t 'short', :scope => %w(date formats)
104
+ #
105
+ # *INTERPOLATION*
106
+ #
107
+ # Translations can contain interpolation variables which will be replaced by
108
+ # values passed to #translate as part of the options hash, with the keys matching
109
+ # the interpolation variable names.
110
+ #
111
+ # <em>E.g.</em>, with a translation <tt>:foo => "foo %{bar}"</tt> the option
112
+ # value for the key +bar+ will be interpolated into the translation:
113
+ # I18n.t :foo, :bar => 'baz' # => 'foo baz'
114
+ #
115
+ # *PLURALIZATION*
116
+ #
117
+ # Translation data can contain pluralized translations. Pluralized translations
118
+ # are arrays of singular/plural versions of translations like <tt>['Foo', 'Foos']</tt>.
119
+ #
120
+ # Note that <tt>I18n::Backend::Simple</tt> only supports an algorithm for English
121
+ # pluralization rules. Other algorithms can be supported by custom backends.
122
+ #
123
+ # This returns the singular version of a pluralized translation:
124
+ # I18n.t :foo, :count => 1 # => 'Foo'
125
+ #
126
+ # These both return the plural version of a pluralized translation:
127
+ # I18n.t :foo, :count => 0 # => 'Foos'
128
+ # I18n.t :foo, :count => 2 # => 'Foos'
129
+ #
130
+ # The <tt>:count</tt> option can be used both for pluralization and interpolation.
131
+ # <em>E.g.</em>, with the translation
132
+ # <tt>:foo => ['%{count} foo', '%{count} foos']</tt>, count will
133
+ # be interpolated to the pluralized translation:
134
+ # I18n.t :foo, :count => 1 # => '1 foo'
135
+ #
136
+ # *DEFAULTS*
137
+ #
138
+ # This returns the translation for <tt>:foo</tt> or <tt>default</tt> if no translation was found:
139
+ # I18n.t :foo, :default => 'default'
140
+ #
141
+ # This returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt> if no
142
+ # translation for <tt>:foo</tt> was found:
143
+ # I18n.t :foo, :default => :bar
144
+ #
145
+ # Returns the translation for <tt>:foo</tt> or the translation for <tt>:bar</tt>
146
+ # or <tt>default</tt> if no translations for <tt>:foo</tt> and <tt>:bar</tt> were found.
147
+ # I18n.t :foo, :default => [:bar, 'default']
148
+ #
149
+ # *BULK LOOKUP*
150
+ #
151
+ # This returns an array with the translations for <tt>:foo</tt> and <tt>:bar</tt>.
152
+ # I18n.t [:foo, :bar]
153
+ #
154
+ # Can be used with dot-separated nested keys:
155
+ # I18n.t [:'baz.foo', :'baz.bar']
156
+ #
157
+ # Which is the same as using a scope option:
158
+ # I18n.t [:foo, :bar], :scope => :baz
159
+ #
160
+ # *LAMBDAS*
161
+ #
162
+ # Both translations and defaults can be given as Ruby lambdas. Lambdas will be
163
+ # called and passed the key and options.
164
+ #
165
+ # E.g. assuming the key <tt>:salutation</tt> resolves to:
166
+ # lambda { |key, options| options[:gender] == 'm' ? "Mr. #{options[:name]}" : "Mrs. #{options[:name]}" }
167
+ #
168
+ # Then <tt>I18n.t(:salutation, :gender => 'w', :name => 'Smith') will result in "Mrs. Smith".
169
+ #
170
+ # Note that the string returned by lambda will go through string interpolation too,
171
+ # so the following lambda would give the same result:
172
+ # lambda { |key, options| options[:gender] == 'm' ? "Mr. %{name}" : "Mrs. %{name}" }
173
+ #
174
+ # It is recommended to use/implement lambdas in an "idempotent" way. E.g. when
175
+ # a cache layer is put in front of I18n.translate it will generate a cache key
176
+ # from the argument values passed to #translate. Therefore your lambdas should
177
+ # always return the same translations/values per unique combination of argument
178
+ # values.
179
+ #
180
+ # *Ruby 2.7+ keyword arguments warning*
181
+ #
182
+ # This method uses keyword arguments.
183
+ # There is a breaking change in ruby that produces warning with ruby 2.7 and won't work as expected with ruby 3.0
184
+ # The "hash" parameter must be passed as keyword argument.
185
+ #
186
+ # Good:
187
+ # I18n.t(:salutation, :gender => 'w', :name => 'Smith')
188
+ # I18n.t(:salutation, **{ :gender => 'w', :name => 'Smith' })
189
+ # I18n.t(:salutation, **any_hash)
190
+ #
191
+ # Bad:
192
+ # I18n.t(:salutation, { :gender => 'w', :name => 'Smith' })
193
+ # I18n.t(:salutation, any_hash)
194
+ #
195
+ def translate(key = nil, *, throw: false, raise: false, locale: nil, **options) # TODO deprecate :raise
196
+ locale ||= config.locale
197
+ raise Disabled.new('t') if locale == false
198
+ enforce_available_locales!(locale)
199
+
200
+ backend = config.backend
201
+
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)
207
+ end
208
+ end
209
+
210
+ if result.is_a?(MissingTranslation)
211
+ handle_exception((throw && :throw || raise && :raise), result, locale, key, options)
212
+ else
213
+ result
214
+ end
215
+ end
216
+ alias :t :translate
217
+
218
+ # Wrapper for <tt>translate</tt> that adds <tt>:raise => true</tt>. With
219
+ # this option, if no translation is found, it will raise <tt>I18n::MissingTranslationData</tt>
220
+ def translate!(key, options = EMPTY_HASH)
221
+ translate(key, **options.merge(:raise => true))
222
+ end
223
+ alias :t! :translate!
224
+
225
+ # Returns true if a translation exists for a given key, otherwise returns false.
226
+ def exists?(key, _locale = nil, locale: _locale, **options)
227
+ locale ||= config.locale
228
+ raise Disabled.new('exists?') if locale == false
229
+ raise I18n::ArgumentError if key.is_a?(String) && key.empty?
230
+ config.backend.exists?(locale, key, options)
231
+ end
232
+
233
+ # Transliterates UTF-8 characters to ASCII. By default this method will
234
+ # transliterate only Latin strings to an ASCII approximation:
235
+ #
236
+ # I18n.transliterate("Ærøskøbing")
237
+ # # => "AEroskobing"
238
+ #
239
+ # I18n.transliterate("日本語")
240
+ # # => "???"
241
+ #
242
+ # It's also possible to add support for per-locale transliterations. I18n
243
+ # expects transliteration rules to be stored at
244
+ # <tt>i18n.transliterate.rule</tt>.
245
+ #
246
+ # Transliteration rules can either be a Hash or a Proc. Procs must accept a
247
+ # single string argument. Hash rules inherit the default transliteration
248
+ # rules, while Procs do not.
249
+ #
250
+ # *Examples*
251
+ #
252
+ # Setting a Hash in <locale>.yml:
253
+ #
254
+ # i18n:
255
+ # transliterate:
256
+ # rule:
257
+ # ü: "ue"
258
+ # ö: "oe"
259
+ #
260
+ # Setting a Hash using Ruby:
261
+ #
262
+ # store_translations(:de, :i18n => {
263
+ # :transliterate => {
264
+ # :rule => {
265
+ # "ü" => "ue",
266
+ # "ö" => "oe"
267
+ # }
268
+ # }
269
+ # )
270
+ #
271
+ # Setting a Proc:
272
+ #
273
+ # translit = lambda {|string| MyTransliterator.transliterate(string) }
274
+ # store_translations(:xx, :i18n => {:transliterate => {:rule => translit})
275
+ #
276
+ # Transliterating strings:
277
+ #
278
+ # I18n.locale = :en
279
+ # I18n.transliterate("Jürgen") # => "Jurgen"
280
+ # I18n.locale = :de
281
+ # I18n.transliterate("Jürgen") # => "Juergen"
282
+ # I18n.transliterate("Jürgen", :locale => :en) # => "Jurgen"
283
+ # I18n.transliterate("Jürgen", :locale => :de) # => "Juergen"
284
+ def transliterate(key, *, throw: false, raise: false, locale: nil, replacement: nil, **options)
285
+ locale ||= config.locale
286
+ raise Disabled.new('transliterate') if locale == false
287
+ enforce_available_locales!(locale)
288
+
289
+ config.backend.transliterate(locale, key, replacement)
290
+ rescue I18n::ArgumentError => exception
291
+ handle_exception((throw && :throw || raise && :raise), exception, locale, key, options)
292
+ end
293
+
294
+ # Localizes certain objects, such as dates and numbers to local formatting.
295
+ def localize(object, locale: nil, format: nil, **options)
296
+ locale ||= config.locale
297
+ raise Disabled.new('l') if locale == false
298
+ enforce_available_locales!(locale)
299
+
300
+ format ||= :default
301
+ config.backend.localize(locale, object, format, options)
302
+ end
303
+ alias :l :localize
304
+
305
+ # Executes block with given I18n.locale set.
306
+ def with_locale(tmp_locale = nil)
307
+ if tmp_locale == nil
308
+ yield
309
+ else
310
+ current_locale = self.locale
311
+ self.locale = tmp_locale
312
+ begin
313
+ yield
314
+ ensure
315
+ self.locale = current_locale
316
+ end
317
+ end
318
+ end
319
+
320
+ # Merges the given locale, key and scope into a single array of keys.
321
+ # Splits keys that contain dots into multiple keys. Makes sure all
322
+ # keys are Symbols.
323
+ def normalize_keys(locale, key, scope, separator = nil)
324
+ separator ||= I18n.default_separator
325
+
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
331
+ end
332
+
333
+ # Returns true when the passed locale, which can be either a String or a
334
+ # Symbol, is in the list of available locales. Returns false otherwise.
335
+ def locale_available?(locale)
336
+ I18n.config.available_locales_set.include?(locale)
337
+ end
338
+
339
+ # Raises an InvalidLocale exception when the passed locale is not available.
340
+ def enforce_available_locales!(locale)
341
+ if locale != false && config.enforce_available_locales
342
+ raise I18n::InvalidLocale.new(locale) if !locale_available?(locale)
343
+ end
344
+ end
345
+
346
+ def available_locales_initialized?
347
+ config.available_locales_initialized?
348
+ end
349
+
350
+ private
351
+
352
+ # Any exceptions thrown in translate will be sent to the @@exception_handler
353
+ # which can be a Symbol, a Proc or any other Object unless they're forced to
354
+ # be raised or thrown (MissingTranslation).
355
+ #
356
+ # If exception_handler is a Symbol then it will simply be sent to I18n as
357
+ # a method call. A Proc will simply be called. In any other case the
358
+ # method #call will be called on the exception_handler object.
359
+ #
360
+ # Examples:
361
+ #
362
+ # I18n.exception_handler = :custom_exception_handler # this is the default
363
+ # I18n.custom_exception_handler(exception, locale, key, options) # will be called like this
364
+ #
365
+ # I18n.exception_handler = lambda { |*args| ... } # a lambda
366
+ # I18n.exception_handler.call(exception, locale, key, options) # will be called like this
367
+ #
368
+ # I18n.exception_handler = I18nExceptionHandler.new # an object
369
+ # I18n.exception_handler.call(exception, locale, key, options) # will be called like this
370
+ def handle_exception(handling, exception, locale, key, options)
371
+ case handling
372
+ when :raise
373
+ raise exception.respond_to?(:to_exception) ? exception.to_exception : exception
374
+ when :throw
375
+ throw :exception, exception
376
+ else
377
+ case handler = options[:exception_handler] || config.exception_handler
378
+ when Symbol
379
+ send(handler, exception, locale, key, options)
380
+ else
381
+ handler.call(exception, locale, key, options)
382
+ end
383
+ end
384
+ end
385
+
386
+ @@normalized_key_cache = I18n.new_double_nested_cache
387
+
388
+ def normalize_key(key, separator)
389
+ @@normalized_key_cache[separator][key] ||=
390
+ case key
391
+ when Array
392
+ key.flat_map { |k| normalize_key(k, separator) }
393
+ else
394
+ keys = key.to_s.split(separator)
395
+ keys.delete('')
396
+ keys.map! do |k|
397
+ case k
398
+ when /\A[-+]?[1-9]\d*\z/ # integer
399
+ k.to_i
400
+ when 'true'
401
+ true
402
+ when 'false'
403
+ false
404
+ else
405
+ k.to_sym
406
+ end
407
+ end
408
+ keys
409
+ end
410
+ end
411
+ end
412
+
413
+ extend Base
414
+ end