i18n 0.9.5 → 1.8.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +70 -32
  3. data/lib/i18n.rb +95 -36
  4. data/lib/i18n/backend.rb +3 -0
  5. data/lib/i18n/backend/base.rb +53 -23
  6. data/lib/i18n/backend/cache.rb +10 -11
  7. data/lib/i18n/backend/cache_file.rb +36 -0
  8. data/lib/i18n/backend/cascade.rb +3 -1
  9. data/lib/i18n/backend/chain.rb +38 -5
  10. data/lib/i18n/backend/fallbacks.rb +25 -14
  11. data/lib/i18n/backend/flatten.rb +10 -5
  12. data/lib/i18n/backend/gettext.rb +4 -0
  13. data/lib/i18n/backend/interpolation_compiler.rb +3 -1
  14. data/lib/i18n/backend/key_value.rb +31 -2
  15. data/lib/i18n/backend/memoize.rb +10 -2
  16. data/lib/i18n/backend/metadata.rb +5 -3
  17. data/lib/i18n/backend/pluralization.rb +3 -1
  18. data/lib/i18n/backend/simple.rb +26 -10
  19. data/lib/i18n/backend/transliterator.rb +2 -0
  20. data/lib/i18n/config.rb +20 -2
  21. data/lib/i18n/core_ext/hash.rb +54 -24
  22. data/lib/i18n/exceptions.rb +23 -16
  23. data/lib/i18n/gettext.rb +2 -0
  24. data/lib/i18n/gettext/helpers.rb +4 -2
  25. data/lib/i18n/gettext/po_parser.rb +7 -7
  26. data/lib/i18n/interpolate/ruby.rb +6 -4
  27. data/lib/i18n/locale.rb +2 -0
  28. data/lib/i18n/locale/fallbacks.rb +9 -6
  29. data/lib/i18n/locale/tag/parents.rb +8 -6
  30. data/lib/i18n/locale/tag/simple.rb +1 -1
  31. data/lib/i18n/middleware.rb +2 -0
  32. data/lib/i18n/tests.rb +2 -0
  33. data/lib/i18n/tests/interpolation.rb +9 -4
  34. data/lib/i18n/tests/link.rb +11 -1
  35. data/lib/i18n/tests/localization/date.rb +29 -7
  36. data/lib/i18n/tests/localization/date_time.rb +27 -6
  37. data/lib/i18n/tests/localization/procs.rb +6 -5
  38. data/lib/i18n/tests/localization/time.rb +26 -4
  39. data/lib/i18n/tests/lookup.rb +2 -2
  40. data/lib/i18n/tests/procs.rb +5 -0
  41. data/lib/i18n/version.rb +3 -1
  42. metadata +14 -60
  43. data/gemfiles/Gemfile.rails-3.2.x +0 -10
  44. data/gemfiles/Gemfile.rails-4.0.x +0 -10
  45. data/gemfiles/Gemfile.rails-4.1.x +0 -10
  46. data/gemfiles/Gemfile.rails-4.2.x +0 -10
  47. data/gemfiles/Gemfile.rails-5.0.x +0 -10
  48. data/gemfiles/Gemfile.rails-5.1.x +0 -10
  49. data/gemfiles/Gemfile.rails-master +0 -10
  50. data/lib/i18n/core_ext/kernel/suppress_warnings.rb +0 -8
  51. data/lib/i18n/core_ext/string/interpolate.rb +0 -9
  52. data/test/api/all_features_test.rb +0 -58
  53. data/test/api/cascade_test.rb +0 -28
  54. data/test/api/chain_test.rb +0 -24
  55. data/test/api/fallbacks_test.rb +0 -30
  56. data/test/api/key_value_test.rb +0 -24
  57. data/test/api/memoize_test.rb +0 -56
  58. data/test/api/override_test.rb +0 -42
  59. data/test/api/pluralization_test.rb +0 -30
  60. data/test/api/simple_test.rb +0 -28
  61. data/test/backend/cache_test.rb +0 -109
  62. data/test/backend/cascade_test.rb +0 -86
  63. data/test/backend/chain_test.rb +0 -122
  64. data/test/backend/exceptions_test.rb +0 -36
  65. data/test/backend/fallbacks_test.rb +0 -219
  66. data/test/backend/interpolation_compiler_test.rb +0 -118
  67. data/test/backend/key_value_test.rb +0 -61
  68. data/test/backend/memoize_test.rb +0 -79
  69. data/test/backend/metadata_test.rb +0 -48
  70. data/test/backend/pluralization_test.rb +0 -45
  71. data/test/backend/simple_test.rb +0 -103
  72. data/test/backend/transliterator_test.rb +0 -84
  73. data/test/core_ext/hash_test.rb +0 -36
  74. data/test/gettext/api_test.rb +0 -214
  75. data/test/gettext/backend_test.rb +0 -92
  76. data/test/i18n/exceptions_test.rb +0 -117
  77. data/test/i18n/gettext_plural_keys_test.rb +0 -20
  78. data/test/i18n/interpolate_test.rb +0 -91
  79. data/test/i18n/load_path_test.rb +0 -34
  80. data/test/i18n/middleware_test.rb +0 -24
  81. data/test/i18n_test.rb +0 -462
  82. data/test/locale/fallbacks_test.rb +0 -133
  83. data/test/locale/tag/rfc4646_test.rb +0 -143
  84. data/test/locale/tag/simple_test.rb +0 -32
  85. data/test/run_all.rb +0 -20
  86. data/test/test_data/locales/de.po +0 -82
  87. data/test/test_data/locales/en.rb +0 -3
  88. data/test/test_data/locales/en.yml +0 -3
  89. data/test/test_data/locales/invalid/empty.yml +0 -0
  90. data/test/test_data/locales/invalid/syntax.yml +0 -4
  91. data/test/test_data/locales/plurals.rb +0 -113
  92. data/test/test_helper.rb +0 -61
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '09643c444c40fe98042352f7f3e3e2df08e15ec5bee9f3d658b3e2fdeeb627a7'
4
- data.tar.gz: b924468907cca779632052d4ec166c79a9fc10f305a4a4f2c7be6ffeef6a5e47
3
+ metadata.gz: d85e42515b239906cad750dc259b33896b1af0f323bb0dfa76e00e25fffc4745
4
+ data.tar.gz: 9f7b02056b8d9efe5e753fa21c4d7e40abc1ac7ff5bb219b350d7424a9d8abc8
5
5
  SHA512:
6
- metadata.gz: 338556073a2c02752c1d3e5e3ba5ed70cb692d814ab05aca5a3d70ef3ee013aa3bb9d5cfc6aecbbd2a93e4732105a4c05c70968ea7f955c169cf69ad97462796
7
- data.tar.gz: a0a4eff7b73ac32730dae8b77f609c4cc6fca9b6c2f46d81f42d992e10dd8d1467dbc28985b38769f9cab2f6ac1d67e4309eb8a8df65d9fc38baa39b65343f18
6
+ metadata.gz: 20ec433c6d838fcbb62815541827634e38740ceb02c9e8c2f020a72da6d41316016ff89399fc4baaea23150ae6be020a77c8dc7855d1d1da7a5d72f7a3834b30
7
+ data.tar.gz: 27300973f400a0804fed69ab7e522bf2f3e60718a541245f8d6874c244b9a5aff67dbebfc5c503dc82336b962349e23e4d046ee688e2af03840bd9d7528e2a8b
data/README.md CHANGED
@@ -1,45 +1,82 @@
1
1
  # Ruby I18n
2
2
 
3
- [![Build Status](https://api.travis-ci.org/svenfuchs/i18n.svg?branch=master)](https://travis-ci.org/svenfuchs/i18n)
3
+ [![Build Status](https://github.com/ruby-i18n/i18n/workflows/Ruby/badge.svg)](https://github.com/ruby-i18n/i18n/actions?query=workflow%3ARuby)
4
4
 
5
- Ruby Internationalization and localization solution.
5
+ Ruby internationalization and localization (i18n) solution.
6
6
 
7
- [See the Rails Guide](http://guides.rubyonrails.org/i18n.html) for an example of its usage. (Note: This library can be used independently from Rails.)
7
+ Currently maintained by @radar.
8
8
 
9
- Features:
9
+ ## Usage
10
10
 
11
- * translation and localization
12
- * interpolation of values to translations (Ruby 1.9 compatible syntax)
13
- * pluralization (CLDR compatible)
14
- * customizable transliteration to ASCII
15
- * flexible defaults
16
- * bulk lookup
17
- * lambdas as translation data
18
- * custom key/scope separator
19
- * custom exception handlers
20
- * extensible architecture with a swappable backend
11
+ ### Rails
21
12
 
22
- Pluggable features:
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
23
66
 
24
67
  * Cache
25
68
  * Pluralization: lambda pluralizers stored as translation data
26
69
  * Locale fallbacks, RFC4647 compliant (optionally: RFC4646 locale validation)
27
- * [Gettext support](https://github.com/svenfuchs/i18n/wiki/Gettext)
70
+ * [Gettext support](https://github.com/ruby-i18n/i18n/wiki/Gettext)
28
71
  * Translation metadata
29
72
 
30
- Alternative backends:
73
+ ## Alternative Backend
31
74
 
32
75
  * Chain
33
76
  * ActiveRecord (optionally: ActiveRecord::Missing and ActiveRecord::StoreProcs)
34
77
  * KeyValue (uses active_support/json and cannot store procs)
35
78
 
36
- For more information and lots of resources see [the 'Resources' page on the wiki](https://github.com/svenfuchs/i18n/wiki/Resources).
37
-
38
- ## Installation
39
-
40
- ```
41
- gem install i18n
42
- ```
79
+ For more information and lots of resources see [the 'Resources' page on the wiki](https://github.com/ruby-i18n/i18n/wiki/Resources).
43
80
 
44
81
  ## Tests
45
82
 
@@ -58,7 +95,7 @@ particular tests in different test cases.
58
95
  The reason for this is that we need to enforce the I18n API across various
59
96
  combinations of extensions. E.g. the Simple backend alone needs to support
60
97
  the same API as any combination of feature and/or optimization modules included
61
- to the Simple backend. We test this by reusing the same API defition (implemented
98
+ to the Simple backend. We test this by reusing the same API definition (implemented
62
99
  as test methods) in test cases with different setups.
63
100
 
64
101
  You can find the test cases that enforce the API in test/api. And you can find
@@ -67,17 +104,18 @@ the API definition test methods in test/api/tests.
67
104
  All other test cases (e.g. as defined in test/backend, test/core_ext) etc.
68
105
  follow the usual test setup and should be easy to grok.
69
106
 
70
- ## Authors
107
+ ## More Documentation
71
108
 
72
- * [Sven Fuchs](http://www.artweb-design.de)
73
- * [Joshua Harvey](http://www.workingwithrails.com/person/759-joshua-harvey)
74
- * [Stephan Soller](http://www.arkanis-development.de)
75
- * [Saimon Moore](http://saimonmoore.net)
76
- * [Matt Aimonetti](https://matt.aimonetti.net/)
109
+ Additional documentation can be found here: https://github.com/ruby-i18n/i18n/wiki
77
110
 
78
111
  ## Contributors
79
112
 
80
- https://github.com/svenfuchs/i18n/graphs/contributors
113
+ * @radar
114
+ * @carlosantoniodasilva
115
+ * @josevalim
116
+ * @knapo
117
+ * @tigrish
118
+ * [and many more](https://github.com/ruby-i18n/i18n/graphs/contributors)
81
119
 
82
120
  ## License
83
121
 
data/lib/i18n.rb CHANGED
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'concurrent/map'
4
+ require 'concurrent/hash'
2
5
 
3
6
  require 'i18n/version'
4
7
  require 'i18n/exceptions'
@@ -12,12 +15,26 @@ module I18n
12
15
  autoload :Tests, 'i18n/tests'
13
16
  autoload :Middleware, 'i18n/middleware'
14
17
 
15
- RESERVED_KEYS = [:scope, :default, :separator, :resolve, :object, :fallback, :fallback_in_progress, :format, :cascade, :throw, :raise, :deep_interpolation]
18
+ RESERVED_KEYS = %i[
19
+ cascade
20
+ deep_interpolation
21
+ default
22
+ exception_handler
23
+ fallback
24
+ fallback_in_progress
25
+ format
26
+ object
27
+ raise
28
+ resolve
29
+ scope
30
+ separator
31
+ throw
32
+ ].freeze
16
33
  RESERVED_KEYS_PATTERN = /%\{(#{RESERVED_KEYS.join("|")})\}/
17
-
34
+ EMPTY_HASH = {}.freeze
18
35
 
19
36
  def self.new_double_nested_cache # :nodoc:
20
- Concurrent::Map.new { |h,k| h[k] = Concurrent::Map.new }
37
+ Concurrent::Map.new { |h, k| h[k] = Concurrent::Map.new }
21
38
  end
22
39
 
23
40
  module Base
@@ -53,6 +70,13 @@ module I18n
53
70
  config.backend.reload!
54
71
  end
55
72
 
73
+ # Tells the backend to load translations now. Used in situations like the
74
+ # Rails production environment. Backends can implement whatever strategy
75
+ # is useful.
76
+ def eager_load!
77
+ config.backend.eager_load!
78
+ end
79
+
56
80
  # Translates, pluralizes and interpolates a given key using a given locale,
57
81
  # scope, and default, as well as interpolation values.
58
82
  #
@@ -92,7 +116,7 @@ module I18n
92
116
  # *PLURALIZATION*
93
117
  #
94
118
  # Translation data can contain pluralized translations. Pluralized translations
95
- # are arrays of singluar/plural versions of translations like <tt>['Foo', 'Foos']</tt>.
119
+ # are arrays of singular/plural versions of translations like <tt>['Foo', 'Foos']</tt>.
96
120
  #
97
121
  # Note that <tt>I18n::Backend::Simple</tt> only supports an algorithm for English
98
122
  # pluralization rules. Other algorithms can be supported by custom backends.
@@ -150,18 +174,32 @@ module I18n
150
174
  #
151
175
  # It is recommended to use/implement lambdas in an "idempotent" way. E.g. when
152
176
  # a cache layer is put in front of I18n.translate it will generate a cache key
153
- # from the argument values passed to #translate. Therefor your lambdas should
177
+ # from the argument values passed to #translate. Therefore your lambdas should
154
178
  # always return the same translations/values per unique combination of argument
155
179
  # values.
156
- def translate(*args)
157
- options = args.last.is_a?(Hash) ? args.pop.dup : {}
158
- key = args.shift
159
- backend = config.backend
160
- locale = options.delete(:locale) || config.locale
161
- handling = options.delete(:throw) && :throw || options.delete(:raise) && :raise # TODO deprecate :raise
162
-
180
+ #
181
+ # *Ruby 2.7+ keyword arguments warning*
182
+ #
183
+ # This method uses keyword arguments.
184
+ # There is a breaking change in ruby that produces warning with ruby 2.7 and won't work as expected with ruby 3.0
185
+ # The "hash" parameter must be passed as keyword argument.
186
+ #
187
+ # Good:
188
+ # I18n.t(:salutation, :gender => 'w', :name => 'Smith')
189
+ # I18n.t(:salutation, **{ :gender => 'w', :name => 'Smith' })
190
+ # I18n.t(:salutation, **any_hash)
191
+ #
192
+ # Bad:
193
+ # I18n.t(:salutation, { :gender => 'w', :name => 'Smith' })
194
+ # I18n.t(:salutation, any_hash)
195
+ #
196
+ def translate(key = nil, throw: false, raise: false, locale: nil, **options) # TODO deprecate :raise
197
+ locale ||= config.locale
198
+ raise Disabled.new('t') if locale == false
163
199
  enforce_available_locales!(locale)
164
200
 
201
+ backend = config.backend
202
+
165
203
  result = catch(:exception) do
166
204
  if key.is_a?(Array)
167
205
  key.map { |k| backend.translate(locale, k, options) }
@@ -169,21 +207,28 @@ module I18n
169
207
  backend.translate(locale, key, options)
170
208
  end
171
209
  end
172
- result.is_a?(MissingTranslation) ? handle_exception(handling, result, locale, key, options) : result
210
+
211
+ if result.is_a?(MissingTranslation)
212
+ handle_exception((throw && :throw || raise && :raise), result, locale, key, options)
213
+ else
214
+ result
215
+ end
173
216
  end
174
217
  alias :t :translate
175
218
 
176
219
  # Wrapper for <tt>translate</tt> that adds <tt>:raise => true</tt>. With
177
220
  # this option, if no translation is found, it will raise <tt>I18n::MissingTranslationData</tt>
178
- def translate!(key, options={})
179
- translate(key, options.merge(:raise => true))
221
+ def translate!(key, **options)
222
+ translate(key, **options, raise: true)
180
223
  end
181
224
  alias :t! :translate!
182
225
 
183
226
  # Returns true if a translation exists for a given key, otherwise returns false.
184
- def exists?(key, locale = config.locale)
227
+ def exists?(key, _locale = nil, locale: _locale, **options)
228
+ locale ||= config.locale
229
+ raise Disabled.new('exists?') if locale == false
185
230
  raise I18n::ArgumentError if key.is_a?(String) && key.empty?
186
- config.backend.exists?(locale, key)
231
+ config.backend.exists?(locale, key, options)
187
232
  end
188
233
 
189
234
  # Transliterates UTF-8 characters to ASCII. By default this method will
@@ -237,37 +282,40 @@ module I18n
237
282
  # I18n.transliterate("Jürgen") # => "Juergen"
238
283
  # I18n.transliterate("Jürgen", :locale => :en) # => "Jurgen"
239
284
  # I18n.transliterate("Jürgen", :locale => :de) # => "Juergen"
240
- def transliterate(*args)
241
- options = args.pop.dup if args.last.is_a?(Hash)
242
- key = args.shift
243
- locale = options && options.delete(:locale) || config.locale
244
- handling = options && (options.delete(:throw) && :throw || options.delete(:raise) && :raise)
245
- replacement = options && options.delete(:replacement)
285
+ def transliterate(key, throw: false, raise: false, locale: nil, replacement: nil, **options)
286
+ locale ||= config.locale
287
+ raise Disabled.new('transliterate') if locale == false
246
288
  enforce_available_locales!(locale)
289
+
247
290
  config.backend.transliterate(locale, key, replacement)
248
291
  rescue I18n::ArgumentError => exception
249
- handle_exception(handling, exception, locale, key, options || {})
292
+ handle_exception((throw && :throw || raise && :raise), exception, locale, key, options)
250
293
  end
251
294
 
252
295
  # Localizes certain objects, such as dates and numbers to local formatting.
253
- def localize(object, options = nil)
254
- options = options ? options.dup : {}
255
- locale = options.delete(:locale) || config.locale
256
- format = options.delete(:format) || :default
296
+ def localize(object, locale: nil, format: nil, **options)
297
+ locale ||= config.locale
298
+ raise Disabled.new('l') if locale == false
257
299
  enforce_available_locales!(locale)
300
+
301
+ format ||= :default
258
302
  config.backend.localize(locale, object, format, options)
259
303
  end
260
304
  alias :l :localize
261
305
 
262
306
  # Executes block with given I18n.locale set.
263
307
  def with_locale(tmp_locale = nil)
264
- if tmp_locale
308
+ if tmp_locale == nil
309
+ yield
310
+ else
265
311
  current_locale = self.locale
266
- self.locale = tmp_locale
312
+ self.locale = tmp_locale
313
+ begin
314
+ yield
315
+ ensure
316
+ self.locale = current_locale
317
+ end
267
318
  end
268
- yield
269
- ensure
270
- self.locale = current_locale if tmp_locale
271
319
  end
272
320
 
273
321
  # Merges the given locale, key and scope into a single array of keys.
@@ -291,7 +339,7 @@ module I18n
291
339
 
292
340
  # Raises an InvalidLocale exception when the passed locale is not available.
293
341
  def enforce_available_locales!(locale)
294
- if config.enforce_available_locales
342
+ if locale != false && config.enforce_available_locales
295
343
  raise I18n::InvalidLocale.new(locale) if !locale_available?(locale)
296
344
  end
297
345
  end
@@ -342,11 +390,22 @@ module I18n
342
390
  @@normalized_key_cache[separator][key] ||=
343
391
  case key
344
392
  when Array
345
- key.map { |k| normalize_key(k, separator) }.flatten
393
+ key.flat_map { |k| normalize_key(k, separator) }
346
394
  else
347
395
  keys = key.to_s.split(separator)
348
396
  keys.delete('')
349
- keys.map! { |k| k.to_sym }
397
+ keys.map! do |k|
398
+ case k
399
+ when /\A[-+]?[1-9]\d*\z/ # integer
400
+ k.to_i
401
+ when 'true'
402
+ true
403
+ when 'false'
404
+ false
405
+ else
406
+ k.to_sym
407
+ end
408
+ end
350
409
  keys
351
410
  end
352
411
  end
data/lib/i18n/backend.rb CHANGED
@@ -1,8 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module I18n
2
4
  module Backend
3
5
  autoload :Base, 'i18n/backend/base'
4
6
  autoload :InterpolationCompiler, 'i18n/backend/interpolation_compiler'
5
7
  autoload :Cache, 'i18n/backend/cache'
8
+ autoload :CacheFile, 'i18n/backend/cache_file'
6
9
  autoload :Cascade, 'i18n/backend/cascade'
7
10
  autoload :Chain, 'i18n/backend/chain'
8
11
  autoload :Fallbacks, 'i18n/backend/fallbacks'
@@ -1,14 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yaml'
4
+ require 'json'
2
5
  require 'i18n/core_ext/hash'
3
- require 'i18n/core_ext/kernel/suppress_warnings'
4
6
 
5
7
  module I18n
6
8
  module Backend
7
9
  module Base
10
+ using I18n::HashRefinements
8
11
  include I18n::Backend::Transliterator
9
12
 
10
13
  # Accepts a list of paths to translation files. Loads translations from
11
- # plain Ruby (*.rb) or YAML files (*.yml). See #load_rb and #load_yml
14
+ # plain Ruby (*.rb), YAML files (*.yml), or JSON files (*.json). See #load_rb, #load_yml, and #load_json
12
15
  # for details.
13
16
  def load_translations(*filenames)
14
17
  filenames = I18n.load_path if filenames.empty?
@@ -17,11 +20,11 @@ module I18n
17
20
 
18
21
  # This method receives a locale, a data hash and options for storing translations.
19
22
  # Should be implemented
20
- def store_translations(locale, data, options = {})
23
+ def store_translations(locale, data, options = EMPTY_HASH)
21
24
  raise NotImplementedError
22
25
  end
23
26
 
24
- def translate(locale, key, options = {})
27
+ def translate(locale, key, options = EMPTY_HASH)
25
28
  raise I18n::ArgumentError if (key.is_a?(String) || key.is_a?(Symbol)) && key.empty?
26
29
  raise InvalidLocale.new(locale) unless locale
27
30
  return nil if key.nil? && !options.key?(:default)
@@ -61,14 +64,14 @@ module I18n
61
64
  entry
62
65
  end
63
66
 
64
- def exists?(locale, key)
67
+ def exists?(locale, key, options = EMPTY_HASH)
65
68
  lookup(locale, key) != nil
66
69
  end
67
70
 
68
71
  # Acts the same as +strftime+, but uses a localized version of the
69
72
  # format string. Takes a key from the date/time formats translations as
70
73
  # a format argument (<em>e.g.</em>, <tt>:short</tt> in <tt>:'date.formats'</tt>).
71
- def localize(locale, object, format = :default, options = {})
74
+ def localize(locale, object, format = :default, options = EMPTY_HASH)
72
75
  if object.nil? && options.include?(:default)
73
76
  return options[:default]
74
77
  end
@@ -78,7 +81,7 @@ module I18n
78
81
  key = format
79
82
  type = object.respond_to?(:sec) ? 'time' : 'date'
80
83
  options = options.merge(:raise => true, :object => object, :locale => locale)
81
- format = I18n.t(:"#{type}.formats.#{key}", options)
84
+ format = I18n.t(:"#{type}.formats.#{key}", **options)
82
85
  end
83
86
 
84
87
  format = translate_localization_format(locale, object, format, options)
@@ -92,12 +95,21 @@ module I18n
92
95
  end
93
96
 
94
97
  def reload!
98
+ eager_load! if eager_loaded?
99
+ end
100
+
101
+ def eager_load!
102
+ @eager_loaded = true
95
103
  end
96
104
 
97
105
  protected
98
106
 
107
+ def eager_loaded?
108
+ @eager_loaded ||= false
109
+ end
110
+
99
111
  # The method which actually looks up for the translation in the store.
100
- def lookup(locale, key, scope = [], options = {})
112
+ def lookup(locale, key, scope = [], options = EMPTY_HASH)
101
113
  raise NotImplementedError
102
114
  end
103
115
 
@@ -109,8 +121,8 @@ module I18n
109
121
  # If given subject is an Array, it walks the array and returns the
110
122
  # first translation that can be resolved. Otherwise it tries to resolve
111
123
  # the translation directly.
112
- def default(locale, object, subject, options = {})
113
- options = options.dup.reject { |key, value| key == :default }
124
+ def default(locale, object, subject, options = EMPTY_HASH)
125
+ options = options.reject { |key, value| key == :default }
114
126
  case subject
115
127
  when Array
116
128
  subject.each do |item|
@@ -126,15 +138,15 @@ module I18n
126
138
  # If the given subject is a Symbol, it will be translated with the
127
139
  # given options. If it is a Proc then it will be evaluated. All other
128
140
  # subjects will be returned directly.
129
- def resolve(locale, object, subject, options = {})
141
+ def resolve(locale, object, subject, options = EMPTY_HASH)
130
142
  return subject if options[:resolve] == false
131
143
  result = catch(:exception) do
132
144
  case subject
133
145
  when Symbol
134
- I18n.translate(subject, options.merge(:locale => locale, :throw => true))
146
+ I18n.translate(subject, **options.merge(:locale => locale, :throw => true))
135
147
  when Proc
136
148
  date_or_time = options.delete(:object) || object
137
- resolve(locale, object, subject.call(date_or_time, options))
149
+ resolve(locale, object, subject.call(date_or_time, **options))
138
150
  else
139
151
  subject
140
152
  end
@@ -151,7 +163,8 @@ module I18n
151
163
  # not standard with regards to the CLDR pluralization rules.
152
164
  # Other backends can implement more flexible or complex pluralization rules.
153
165
  def pluralize(locale, entry, count)
154
- return entry unless entry.is_a?(Hash) && count
166
+ entry = entry.reject { |k, _v| k == :attributes } if entry.is_a?(Hash)
167
+ return entry unless entry.is_a?(Hash) && count && entry.values.none? { |v| v.is_a?(Hash) }
155
168
 
156
169
  key = pluralization_key(entry, count)
157
170
  raise InvalidPluralizationData.new(entry, count, key) unless entry.has_key?(key)
@@ -168,7 +181,7 @@ module I18n
168
181
  # each element of the array is recursively interpolated (until it finds a string)
169
182
  # method interpolates ["yes, %{user}", ["maybe no, %{user}, "no, %{user}"]], :user => "bartuz"
170
183
  # # => "["yes, bartuz",["maybe no, bartuz", "no, bartuz"]]"
171
- def interpolate(locale, subject, values = {})
184
+ def interpolate(locale, subject, values = EMPTY_HASH)
172
185
  return subject if values.empty?
173
186
 
174
187
  case subject
@@ -184,7 +197,7 @@ module I18n
184
197
  # deep_interpolate { people: { ann: "Ann is %{ann}", john: "John is %{john}" } },
185
198
  # ann: 'good', john: 'big'
186
199
  # #=> { people: { ann: "Ann is good", john: "John is big" } }
187
- def deep_interpolate(locale, data, values = {})
200
+ def deep_interpolate(locale, data, values = EMPTY_HASH)
188
201
  return data if values.empty?
189
202
 
190
203
  case data
@@ -232,18 +245,35 @@ module I18n
232
245
  raise InvalidLocaleData.new(filename, e.inspect)
233
246
  end
234
247
  end
248
+ alias_method :load_yaml, :load_yml
249
+
250
+ # Loads a JSON translations file. The data must have locales as
251
+ # toplevel keys.
252
+ def load_json(filename)
253
+ begin
254
+ ::JSON.parse(File.read(filename))
255
+ rescue TypeError, StandardError => e
256
+ raise InvalidLocaleData.new(filename, e.inspect)
257
+ end
258
+ end
235
259
 
236
260
  def translate_localization_format(locale, object, format, options)
237
- format.to_s.gsub(/%[aAbBpP]/) do |match|
261
+ format.to_s.gsub(/%(|\^)[aAbBpP]/) do |match|
238
262
  case match
239
- when '%a' then I18n.t(:"date.abbr_day_names", :locale => locale, :format => format)[object.wday]
240
- when '%A' then I18n.t(:"date.day_names", :locale => locale, :format => format)[object.wday]
241
- when '%b' then I18n.t(:"date.abbr_month_names", :locale => locale, :format => format)[object.mon]
242
- when '%B' then I18n.t(:"date.month_names", :locale => locale, :format => format)[object.mon]
243
- when '%p' then I18n.t(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format).upcase if object.respond_to? :hour
244
- when '%P' then I18n.t(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format).downcase if object.respond_to? :hour
263
+ when '%a' then I18n.t!(:"date.abbr_day_names", :locale => locale, :format => format)[object.wday]
264
+ when '%^a' then I18n.t!(:"date.abbr_day_names", :locale => locale, :format => format)[object.wday].upcase
265
+ when '%A' then I18n.t!(:"date.day_names", :locale => locale, :format => format)[object.wday]
266
+ when '%^A' then I18n.t!(:"date.day_names", :locale => locale, :format => format)[object.wday].upcase
267
+ when '%b' then I18n.t!(:"date.abbr_month_names", :locale => locale, :format => format)[object.mon]
268
+ when '%^b' then I18n.t!(:"date.abbr_month_names", :locale => locale, :format => format)[object.mon].upcase
269
+ when '%B' then I18n.t!(:"date.month_names", :locale => locale, :format => format)[object.mon]
270
+ when '%^B' then I18n.t!(:"date.month_names", :locale => locale, :format => format)[object.mon].upcase
271
+ when '%p' then I18n.t!(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format).upcase if object.respond_to? :hour
272
+ when '%P' then I18n.t!(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format).downcase if object.respond_to? :hour
245
273
  end
246
274
  end
275
+ rescue MissingTranslationData => e
276
+ e.message
247
277
  end
248
278
 
249
279
  def pluralization_key(entry, count)