i18n 1.8.3

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.
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