i18n 1.6.0

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