i18n 1.6.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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +125 -0
  4. data/lib/i18n.rb +398 -0
  5. data/lib/i18n/backend.rb +21 -0
  6. data/lib/i18n/backend/base.rb +284 -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 +127 -0
  11. data/lib/i18n/backend/fallbacks.rb +84 -0
  12. data/lib/i18n/backend/flatten.rb +115 -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 +111 -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 +47 -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 +96 -0
  30. data/lib/i18n/locale/tag.rb +28 -0
  31. data/lib/i18n/locale/tag/parents.rb +22 -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 +159 -0
  39. data/lib/i18n/tests/link.rb +56 -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 +116 -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 +55 -0
  48. data/lib/i18n/version.rb +5 -0
  49. metadata +124 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 2b03b84d726d3cafa7ea2ecaa6f258cb911696c338cb32c769f71b6d62ca5fb5
4
+ data.tar.gz: 7a489bf447e9fedee8fd043cc8ba324c0afc0c34701fbfeff5f15b1c1f4fa6b4
5
+ SHA512:
6
+ metadata.gz: 534659c40a8df9eb3fd9a123b779de0f1b66471ecd3fb35e2083128193f51aeea95df6aad06039b6309932a6efb3426c525a8759e70c1104754fdd921732d6cd
7
+ data.tar.gz: caddebe447a797be3dde1607e74d826588ee13ee1317c6abfbb08cbd8a22d79a6aa10a84d3fd21fa45ca9348317aa0e0aafe1c408d4e1c989fbee7f09cd87606
@@ -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,125 @@
1
+ # Ruby I18n
2
+
3
+ [![Build Status](https://api.travis-ci.org/ruby-i18n/i18n.svg?branch=master)](https://travis-ci.org/ruby-i18n/i18n)
4
+
5
+ Ruby Internationalization and localization 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](http://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 (Ruby 1.9 compatible syntax)
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 defition (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/svenfuchs/i18n/wiki
110
+
111
+ ## Authors
112
+
113
+ * [Sven Fuchs](http://www.artweb-design.de)
114
+ * [Joshua Harvey](http://www.workingwithrails.com/person/759-joshua-harvey)
115
+ * [Stephan Soller](http://www.arkanis-development.de)
116
+ * [Saimon Moore](http://saimonmoore.net)
117
+ * [Matt Aimonetti](https://matt.aimonetti.net/)
118
+
119
+ ## Contributors
120
+
121
+ https://github.com/svenfuchs/i18n/graphs/contributors
122
+
123
+ ## License
124
+
125
+ MIT License. See the included MIT-LICENSE file.
@@ -0,0 +1,398 @@
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 singluar/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. Therefor your lambdas should
177
+ # always return the same translations/values per unique combination of argument
178
+ # values.
179
+ def translate(key = nil, *, throw: false, raise: false, locale: nil, **options) # TODO deprecate :raise
180
+ locale ||= config.locale
181
+ raise Disabled.new('t') if locale == false
182
+ enforce_available_locales!(locale)
183
+
184
+ backend = config.backend
185
+
186
+ result = catch(:exception) do
187
+ if key.is_a?(Array)
188
+ key.map { |k| backend.translate(locale, k, options) }
189
+ else
190
+ backend.translate(locale, key, options)
191
+ end
192
+ end
193
+
194
+ if result.is_a?(MissingTranslation)
195
+ handle_exception((throw && :throw || raise && :raise), result, locale, key, options)
196
+ else
197
+ result
198
+ end
199
+ end
200
+ alias :t :translate
201
+
202
+ # Wrapper for <tt>translate</tt> that adds <tt>:raise => true</tt>. With
203
+ # this option, if no translation is found, it will raise <tt>I18n::MissingTranslationData</tt>
204
+ def translate!(key, options = EMPTY_HASH)
205
+ translate(key, options.merge(:raise => true))
206
+ end
207
+ alias :t! :translate!
208
+
209
+ # Returns true if a translation exists for a given key, otherwise returns false.
210
+ def exists?(key, _locale = nil, locale: _locale)
211
+ locale ||= config.locale
212
+ raise Disabled.new('exists?') if locale == false
213
+ raise I18n::ArgumentError if key.is_a?(String) && key.empty?
214
+ config.backend.exists?(locale, key)
215
+ end
216
+
217
+ # Transliterates UTF-8 characters to ASCII. By default this method will
218
+ # transliterate only Latin strings to an ASCII approximation:
219
+ #
220
+ # I18n.transliterate("Ærøskøbing")
221
+ # # => "AEroskobing"
222
+ #
223
+ # I18n.transliterate("日本語")
224
+ # # => "???"
225
+ #
226
+ # It's also possible to add support for per-locale transliterations. I18n
227
+ # expects transliteration rules to be stored at
228
+ # <tt>i18n.transliterate.rule</tt>.
229
+ #
230
+ # Transliteration rules can either be a Hash or a Proc. Procs must accept a
231
+ # single string argument. Hash rules inherit the default transliteration
232
+ # rules, while Procs do not.
233
+ #
234
+ # *Examples*
235
+ #
236
+ # Setting a Hash in <locale>.yml:
237
+ #
238
+ # i18n:
239
+ # transliterate:
240
+ # rule:
241
+ # ü: "ue"
242
+ # ö: "oe"
243
+ #
244
+ # Setting a Hash using Ruby:
245
+ #
246
+ # store_translations(:de, :i18n => {
247
+ # :transliterate => {
248
+ # :rule => {
249
+ # "ü" => "ue",
250
+ # "ö" => "oe"
251
+ # }
252
+ # }
253
+ # )
254
+ #
255
+ # Setting a Proc:
256
+ #
257
+ # translit = lambda {|string| MyTransliterator.transliterate(string) }
258
+ # store_translations(:xx, :i18n => {:transliterate => {:rule => translit})
259
+ #
260
+ # Transliterating strings:
261
+ #
262
+ # I18n.locale = :en
263
+ # I18n.transliterate("Jürgen") # => "Jurgen"
264
+ # I18n.locale = :de
265
+ # I18n.transliterate("Jürgen") # => "Juergen"
266
+ # I18n.transliterate("Jürgen", :locale => :en) # => "Jurgen"
267
+ # I18n.transliterate("Jürgen", :locale => :de) # => "Juergen"
268
+ def transliterate(key, *, throw: false, raise: false, locale: nil, replacement: nil, **options)
269
+ locale ||= config.locale
270
+ raise Disabled.new('transliterate') if locale == false
271
+ enforce_available_locales!(locale)
272
+
273
+ config.backend.transliterate(locale, key, replacement)
274
+ rescue I18n::ArgumentError => exception
275
+ handle_exception((throw && :throw || raise && :raise), exception, locale, key, options)
276
+ end
277
+
278
+ # Localizes certain objects, such as dates and numbers to local formatting.
279
+ def localize(object, locale: nil, format: nil, **options)
280
+ locale ||= config.locale
281
+ raise Disabled.new('l') if locale == false
282
+ enforce_available_locales!(locale)
283
+
284
+ format ||= :default
285
+ config.backend.localize(locale, object, format, options)
286
+ end
287
+ alias :l :localize
288
+
289
+ # Executes block with given I18n.locale set.
290
+ def with_locale(tmp_locale = nil)
291
+ if tmp_locale == nil
292
+ yield
293
+ else
294
+ current_locale = self.locale
295
+ self.locale = tmp_locale
296
+ begin
297
+ yield
298
+ ensure
299
+ self.locale = current_locale
300
+ end
301
+ end
302
+ end
303
+
304
+ # Merges the given locale, key and scope into a single array of keys.
305
+ # Splits keys that contain dots into multiple keys. Makes sure all
306
+ # keys are Symbols.
307
+ def normalize_keys(locale, key, scope, separator = nil)
308
+ separator ||= I18n.default_separator
309
+
310
+ keys = []
311
+ keys.concat normalize_key(locale, separator)
312
+ keys.concat normalize_key(scope, separator)
313
+ keys.concat normalize_key(key, separator)
314
+ keys
315
+ end
316
+
317
+ # Returns true when the passed locale, which can be either a String or a
318
+ # Symbol, is in the list of available locales. Returns false otherwise.
319
+ def locale_available?(locale)
320
+ I18n.config.available_locales_set.include?(locale)
321
+ end
322
+
323
+ # Raises an InvalidLocale exception when the passed locale is not available.
324
+ def enforce_available_locales!(locale)
325
+ if locale != false && config.enforce_available_locales
326
+ raise I18n::InvalidLocale.new(locale) if !locale_available?(locale)
327
+ end
328
+ end
329
+
330
+ def available_locales_initialized?
331
+ config.available_locales_initialized?
332
+ end
333
+
334
+ private
335
+
336
+ # Any exceptions thrown in translate will be sent to the @@exception_handler
337
+ # which can be a Symbol, a Proc or any other Object unless they're forced to
338
+ # be raised or thrown (MissingTranslation).
339
+ #
340
+ # If exception_handler is a Symbol then it will simply be sent to I18n as
341
+ # a method call. A Proc will simply be called. In any other case the
342
+ # method #call will be called on the exception_handler object.
343
+ #
344
+ # Examples:
345
+ #
346
+ # I18n.exception_handler = :custom_exception_handler # this is the default
347
+ # I18n.custom_exception_handler(exception, locale, key, options) # will be called like this
348
+ #
349
+ # I18n.exception_handler = lambda { |*args| ... } # a lambda
350
+ # I18n.exception_handler.call(exception, locale, key, options) # will be called like this
351
+ #
352
+ # I18n.exception_handler = I18nExceptionHandler.new # an object
353
+ # I18n.exception_handler.call(exception, locale, key, options) # will be called like this
354
+ def handle_exception(handling, exception, locale, key, options)
355
+ case handling
356
+ when :raise
357
+ raise exception.respond_to?(:to_exception) ? exception.to_exception : exception
358
+ when :throw
359
+ throw :exception, exception
360
+ else
361
+ case handler = options[:exception_handler] || config.exception_handler
362
+ when Symbol
363
+ send(handler, exception, locale, key, options)
364
+ else
365
+ handler.call(exception, locale, key, options)
366
+ end
367
+ end
368
+ end
369
+
370
+ @@normalized_key_cache = I18n.new_double_nested_cache
371
+
372
+ def normalize_key(key, separator)
373
+ @@normalized_key_cache[separator][key] ||=
374
+ case key
375
+ when Array
376
+ key.map { |k| normalize_key(k, separator) }.flatten
377
+ else
378
+ keys = key.to_s.split(separator)
379
+ keys.delete('')
380
+ keys.map! do |k|
381
+ case k
382
+ when /\A[-+]?[1-9]\d*\z/ # integer
383
+ k.to_i
384
+ when 'true'
385
+ true
386
+ when 'false'
387
+ false
388
+ else
389
+ k.to_sym
390
+ end
391
+ end
392
+ keys
393
+ end
394
+ end
395
+ end
396
+
397
+ extend Base
398
+ end