i18n 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -3
  3. data/gemfiles/Gemfile.rails-3.2.x +1 -0
  4. data/gemfiles/Gemfile.rails-3.2.x.lock +7 -2
  5. data/gemfiles/Gemfile.rails-4.0.x +1 -0
  6. data/gemfiles/Gemfile.rails-4.0.x.lock +9 -5
  7. data/gemfiles/Gemfile.rails-4.1.x +1 -0
  8. data/gemfiles/Gemfile.rails-4.1.x.lock +9 -5
  9. data/gemfiles/Gemfile.rails-4.2.x +2 -1
  10. data/gemfiles/Gemfile.rails-4.2.x.lock +12 -8
  11. data/gemfiles/Gemfile.rails-5.0.x +9 -0
  12. data/gemfiles/Gemfile.rails-5.0.x.lock +37 -0
  13. data/gemfiles/Gemfile.rails-master +1 -0
  14. data/gemfiles/Gemfile.rails-master.lock +12 -9
  15. data/lib/i18n/backend/base.rb +61 -20
  16. data/lib/i18n/backend/cache.rb +23 -5
  17. data/lib/i18n/backend/chain.rb +3 -3
  18. data/lib/i18n/backend/fallbacks.rb +23 -9
  19. data/lib/i18n/backend/gettext.rb +25 -16
  20. data/lib/i18n/backend/metadata.rb +6 -2
  21. data/lib/i18n/backend/simple.rb +1 -1
  22. data/lib/i18n/backend/transliterator.rb +10 -4
  23. data/lib/i18n/config.rb +3 -1
  24. data/lib/i18n/core_ext/hash.rb +3 -3
  25. data/lib/i18n/exceptions.rb +3 -3
  26. data/lib/i18n/gettext/helpers.rb +9 -0
  27. data/lib/i18n/interpolate/ruby.rb +1 -1
  28. data/lib/i18n/tests/defaults.rb +8 -0
  29. data/lib/i18n/tests/interpolation.rb +20 -0
  30. data/lib/i18n/tests/localization/date.rb +4 -0
  31. data/lib/i18n/tests/localization/procs.rb +5 -5
  32. data/lib/i18n/tests/procs.rb +9 -9
  33. data/lib/i18n/version.rb +1 -1
  34. data/lib/i18n.rb +5 -3
  35. data/test/backend/cache_test.rb +23 -1
  36. data/test/backend/fallbacks_test.rb +46 -0
  37. data/test/core_ext/hash_test.rb +12 -0
  38. data/test/gettext/api_test.rb +7 -0
  39. data/test/i18n/exceptions_test.rb +9 -0
  40. data/test/i18n/interpolate_test.rb +11 -0
  41. data/test/i18n_test.rb +4 -0
  42. data/test/test_data/locales/plurals.rb +1 -1
  43. metadata +5 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 29c21c5ff9dda205f54c1156967092f364c009ec
4
- data.tar.gz: dd22c6d9398d5ab012e86d9e9e149158bc7d3731
3
+ metadata.gz: 9b6433e5959dd59f7cbc749fe2cf4ab553282d59
4
+ data.tar.gz: 56e4232a2598aacbfde0803866c8dbf0f7b65715
5
5
  SHA512:
6
- metadata.gz: 751f36baefacf168a3473f84d4b4f4a0a6c1da62acb51d78bfa4a1926dc6ba242bf4fa2ccd4263f07491a5c036f1eb37cc750efe5f8f227caf169d838bbfd87e
7
- data.tar.gz: 657f8618ea7c530a4ea77d250bda199b28eb7b1f3a7429156ee4f82de3b0481f94daf0b9833c2f31dffb8487e9355b90e0b123e00f173fc205331345322248ef
6
+ metadata.gz: 9f853f9dbcaf818c62134260296b9daad5885c5e68c5cacf87e13d5526d41ab982ff5387c9037d988b97bfec87b5854666088683157979af7dabcbf21f7e9714
7
+ data.tar.gz: 2baa498d60d117129803c04336c7f1bc77d11a497890d2e68035bfe080d5b8827b52d7f04973339e90d9fc80e8025ae480b8e5f32f111465e523c551dffcee63
data/README.md CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  Ruby Internationalization and localization 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.)
8
+
7
9
  Features:
8
10
 
9
11
  * translation and localization
@@ -22,7 +24,7 @@ Pluggable features:
22
24
  * Cache
23
25
  * Pluralization: lambda pluralizers stored as translation data
24
26
  * Locale fallbacks, RFC4647 compliant (optionally: RFC4646 locale validation)
25
- * Gettext support
27
+ * [Gettext support](https://github.com/svenfuchs/i18n/wiki/Gettext)
26
28
  * Translation metadata
27
29
 
28
30
  Alternative backends:
@@ -31,7 +33,7 @@ Alternative backends:
31
33
  * ActiveRecord (optionally: ActiveRecord::Missing and ActiveRecord::StoreProcs)
32
34
  * KeyValue (uses active_support/json and cannot store procs)
33
35
 
34
- For more information and lots of resources see: [http://ruby-i18n.org/wiki](http://ruby-i18n.org/wiki)
36
+ For more information and lots of resources see [the 'Resources' page on the wiki](https://github.com/svenfuchs/i18n/wiki/Resources).
35
37
 
36
38
  ## Installation
37
39
 
@@ -71,7 +73,7 @@ follow the usual test setup and should be easy to grok.
71
73
  * [Joshua Harvey](http://www.workingwithrails.com/person/759-joshua-harvey)
72
74
  * [Stephan Soller](http://www.arkanis-development.de)
73
75
  * [Saimon Moore](http://saimonmoore.net)
74
- * [Matt Aimonetti](http://railsontherun.com)
76
+ * [Matt Aimonetti](https://matt.aimonetti.net/)
75
77
 
76
78
  ## Contributors
77
79
 
@@ -6,3 +6,4 @@ gem 'activesupport', '~> 3.2.0'
6
6
  gem 'mocha'
7
7
  gem 'test_declarative'
8
8
  gem 'rake'
9
+ gem 'minitest'
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- i18n (0.7.0)
4
+ i18n (0.8.0.beta1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -10,9 +10,10 @@ GEM
10
10
  i18n (~> 0.6, >= 0.6.4)
11
11
  multi_json (~> 1.0)
12
12
  metaclass (0.0.4)
13
+ minitest (5.9.1)
13
14
  mocha (1.1.0)
14
15
  metaclass (~> 0.0.1)
15
- multi_json (1.10.1)
16
+ multi_json (1.11.0)
16
17
  rake (10.4.2)
17
18
  test_declarative (0.0.5)
18
19
 
@@ -22,6 +23,10 @@ PLATFORMS
22
23
  DEPENDENCIES
23
24
  activesupport (~> 3.2.0)
24
25
  i18n!
26
+ minitest
25
27
  mocha
26
28
  rake
27
29
  test_declarative
30
+
31
+ BUNDLED WITH
32
+ 1.13.7
@@ -6,3 +6,4 @@ gem 'activesupport', '~> 4.0.0'
6
6
  gem 'mocha'
7
7
  gem 'test_declarative'
8
8
  gem 'rake'
9
+ gem 'minitest'
@@ -1,12 +1,12 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- i18n (0.7.0)
4
+ i18n (0.8.0.beta1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- activesupport (4.0.12)
9
+ activesupport (4.0.13)
10
10
  i18n (~> 0.6, >= 0.6.9)
11
11
  minitest (~> 4.2)
12
12
  multi_json (~> 1.3)
@@ -16,11 +16,11 @@ GEM
16
16
  minitest (4.7.5)
17
17
  mocha (1.1.0)
18
18
  metaclass (~> 0.0.1)
19
- multi_json (1.10.1)
19
+ multi_json (1.11.0)
20
20
  rake (10.4.2)
21
21
  test_declarative (0.0.5)
22
- thread_safe (0.3.4)
23
- tzinfo (0.3.42)
22
+ thread_safe (0.3.5)
23
+ tzinfo (0.3.43)
24
24
 
25
25
  PLATFORMS
26
26
  ruby
@@ -28,6 +28,10 @@ PLATFORMS
28
28
  DEPENDENCIES
29
29
  activesupport (~> 4.0.0)
30
30
  i18n!
31
+ minitest
31
32
  mocha
32
33
  rake
33
34
  test_declarative
35
+
36
+ BUNDLED WITH
37
+ 1.13.7
@@ -6,3 +6,4 @@ gem 'activesupport', '~> 4.1.0'
6
6
  gem 'mocha'
7
7
  gem 'test_declarative'
8
8
  gem 'rake'
9
+ gem 'minitest'
@@ -1,25 +1,25 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- i18n (0.7.0)
4
+ i18n (0.8.0.beta1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- activesupport (4.1.8)
9
+ activesupport (4.1.10)
10
10
  i18n (~> 0.6, >= 0.6.9)
11
11
  json (~> 1.7, >= 1.7.7)
12
12
  minitest (~> 5.1)
13
13
  thread_safe (~> 0.1)
14
14
  tzinfo (~> 1.1)
15
- json (1.8.1)
15
+ json (1.8.2)
16
16
  metaclass (0.0.4)
17
- minitest (5.5.0)
17
+ minitest (5.5.1)
18
18
  mocha (1.1.0)
19
19
  metaclass (~> 0.0.1)
20
20
  rake (10.4.2)
21
21
  test_declarative (0.0.5)
22
- thread_safe (0.3.4)
22
+ thread_safe (0.3.5)
23
23
  tzinfo (1.2.2)
24
24
  thread_safe (~> 0.1)
25
25
 
@@ -29,6 +29,10 @@ PLATFORMS
29
29
  DEPENDENCIES
30
30
  activesupport (~> 4.1.0)
31
31
  i18n!
32
+ minitest
32
33
  mocha
33
34
  rake
34
35
  test_declarative
36
+
37
+ BUNDLED WITH
38
+ 1.13.7
@@ -2,7 +2,8 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec :path => '..'
4
4
 
5
- gem 'activesupport', '~> 4.2.0.rc3'
5
+ gem 'activesupport', '~> 4.2.0'
6
6
  gem 'mocha'
7
7
  gem 'test_declarative'
8
8
  gem 'rake'
9
+ gem 'minitest'
@@ -1,25 +1,25 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- i18n (0.7.0)
4
+ i18n (0.8.0.beta1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
- activesupport (4.2.0.rc3)
10
- i18n (>= 0.7.0.beta1, < 0.8)
9
+ activesupport (4.2.1)
10
+ i18n (~> 0.7)
11
11
  json (~> 1.7, >= 1.7.7)
12
12
  minitest (~> 5.1)
13
- thread_safe (~> 0.1)
13
+ thread_safe (~> 0.3, >= 0.3.4)
14
14
  tzinfo (~> 1.1)
15
- json (1.8.1)
15
+ json (1.8.2)
16
16
  metaclass (0.0.4)
17
- minitest (5.5.0)
17
+ minitest (5.5.1)
18
18
  mocha (1.1.0)
19
19
  metaclass (~> 0.0.1)
20
20
  rake (10.4.2)
21
21
  test_declarative (0.0.5)
22
- thread_safe (0.3.4)
22
+ thread_safe (0.3.5)
23
23
  tzinfo (1.2.2)
24
24
  thread_safe (~> 0.1)
25
25
 
@@ -27,8 +27,12 @@ PLATFORMS
27
27
  ruby
28
28
 
29
29
  DEPENDENCIES
30
- activesupport (~> 4.2.0.rc3)
30
+ activesupport (~> 4.2.0)
31
31
  i18n!
32
+ minitest
32
33
  mocha
33
34
  rake
34
35
  test_declarative
36
+
37
+ BUNDLED WITH
38
+ 1.13.7
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec :path => '..'
4
+
5
+ gem 'activesupport', '~> 5.0.0'
6
+ gem 'mocha'
7
+ gem 'test_declarative'
8
+ gem 'rake'
9
+ gem 'minitest'
@@ -0,0 +1,37 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ i18n (0.8.0.beta1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ activesupport (5.0.0.1)
10
+ concurrent-ruby (~> 1.0, >= 1.0.2)
11
+ i18n (~> 0.7)
12
+ minitest (~> 5.1)
13
+ tzinfo (~> 1.1)
14
+ concurrent-ruby (1.0.2)
15
+ metaclass (0.0.4)
16
+ minitest (5.9.1)
17
+ mocha (1.2.1)
18
+ metaclass (~> 0.0.1)
19
+ rake (11.3.0)
20
+ test_declarative (0.0.5)
21
+ thread_safe (0.3.5)
22
+ tzinfo (1.2.2)
23
+ thread_safe (~> 0.1)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ activesupport (~> 5.0.0)
30
+ i18n!
31
+ minitest
32
+ mocha
33
+ rake
34
+ test_declarative
35
+
36
+ BUNDLED WITH
37
+ 1.13.7
@@ -6,3 +6,4 @@ gem 'activesupport', github: 'rails/rails', branch: 'master'
6
6
  gem 'mocha'
7
7
  gem 'test_declarative'
8
8
  gem 'rake'
9
+ gem 'minitest'
@@ -1,31 +1,30 @@
1
1
  GIT
2
2
  remote: git://github.com/rails/rails.git
3
- revision: fe46f009be1ece58e45abc51195e2381a71bd023
3
+ revision: da23e125f8d755917b08f5cca1f7fe1ff38c8b7e
4
4
  branch: master
5
5
  specs:
6
- activesupport (5.0.0.alpha)
7
- i18n (>= 0.7.0.beta1, < 0.8)
8
- json (~> 1.7, >= 1.7.7)
6
+ activesupport (5.1.0.alpha)
7
+ concurrent-ruby (~> 1.0, >= 1.0.2)
8
+ i18n (~> 0.7)
9
9
  minitest (~> 5.1)
10
- thread_safe (~> 0.1)
11
10
  tzinfo (~> 1.1)
12
11
 
13
12
  PATH
14
13
  remote: ..
15
14
  specs:
16
- i18n (0.7.0)
15
+ i18n (0.8.0.beta1)
17
16
 
18
17
  GEM
19
18
  remote: https://rubygems.org/
20
19
  specs:
21
- json (1.8.1)
20
+ concurrent-ruby (1.0.2)
22
21
  metaclass (0.0.4)
23
- minitest (5.5.0)
22
+ minitest (5.5.1)
24
23
  mocha (1.1.0)
25
24
  metaclass (~> 0.0.1)
26
25
  rake (10.4.2)
27
26
  test_declarative (0.0.5)
28
- thread_safe (0.3.4)
27
+ thread_safe (0.3.5)
29
28
  tzinfo (1.2.2)
30
29
  thread_safe (~> 0.1)
31
30
 
@@ -35,6 +34,10 @@ PLATFORMS
35
34
  DEPENDENCIES
36
35
  activesupport!
37
36
  i18n!
37
+ minitest
38
38
  mocha
39
39
  rake
40
40
  test_declarative
41
+
42
+ BUNDLED WITH
43
+ 1.13.7
@@ -25,20 +25,32 @@ module I18n
25
25
  raise InvalidLocale.new(locale) unless locale
26
26
  entry = key && lookup(locale, key, options[:scope], options)
27
27
 
28
- if options.empty?
29
- entry = resolve(locale, key, entry, options)
28
+ if entry.nil? && options.key?(:default)
29
+ entry = default(locale, key, options[:default], options)
30
30
  else
31
- count, default = options.values_at(:count, :default)
32
- values = options.except(*RESERVED_KEYS)
33
- entry = entry.nil? && default ?
34
- default(locale, key, default, options) : resolve(locale, key, entry, options)
31
+ entry = resolve(locale, key, entry, options)
32
+ end
33
+
34
+ if entry.nil?
35
+ if (options.key?(:default) && !options[:default].nil?) || !options.key?(:default)
36
+ throw(:exception, I18n::MissingTranslation.new(locale, key, options))
37
+ end
35
38
  end
36
39
 
37
- throw(:exception, I18n::MissingTranslation.new(locale, key, options)) if entry.nil?
38
40
  entry = entry.dup if entry.is_a?(String)
39
41
 
42
+ count = options[:count]
40
43
  entry = pluralize(locale, entry, count) if count
41
- entry = interpolate(locale, entry, values) if values
44
+
45
+ deep_interpolation = options[:deep_interpolation]
46
+ values = options.except(*RESERVED_KEYS)
47
+ if values
48
+ entry = if deep_interpolation
49
+ deep_interpolate(locale, entry, values)
50
+ else
51
+ interpolate(locale, entry, values)
52
+ end
53
+ end
42
54
  entry
43
55
  end
44
56
 
@@ -50,6 +62,9 @@ module I18n
50
62
  # format string. Takes a key from the date/time formats translations as
51
63
  # a format argument (<em>e.g.</em>, <tt>:short</tt> in <tt>:'date.formats'</tt>).
52
64
  def localize(locale, object, format = :default, options = {})
65
+ if object.nil? && options.include?(:default)
66
+ return options[:default]
67
+ end
53
68
  raise ArgumentError, "Object must be a Date, DateTime or Time object. #{object.inspect} given." unless object.respond_to?(:strftime)
54
69
 
55
70
  if Symbol === format
@@ -59,18 +74,7 @@ module I18n
59
74
  format = I18n.t(:"#{type}.formats.#{key}", options)
60
75
  end
61
76
 
62
- # format = resolve(locale, object, format, options)
63
- format = format.to_s.gsub(/%[aAbBpP]/) do |match|
64
- case match
65
- when '%a' then I18n.t(:"date.abbr_day_names", :locale => locale, :format => format)[object.wday]
66
- when '%A' then I18n.t(:"date.day_names", :locale => locale, :format => format)[object.wday]
67
- when '%b' then I18n.t(:"date.abbr_month_names", :locale => locale, :format => format)[object.mon]
68
- when '%B' then I18n.t(:"date.month_names", :locale => locale, :format => format)[object.mon]
69
- when '%p' then I18n.t(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format).upcase if object.respond_to? :hour
70
- when '%P' then I18n.t(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format).downcase if object.respond_to? :hour
71
- end
72
- end
73
-
77
+ format = translate_localization_format(locale, object, format, options)
74
78
  object.strftime(format)
75
79
  end
76
80
 
@@ -155,6 +159,30 @@ module I18n
155
159
  end
156
160
  end
157
161
 
162
+ # Deep interpolation
163
+ #
164
+ # deep_interpolate { people: { ann: "Ann is %{ann}", john: "John is %{john}" } },
165
+ # ann: 'good', john: 'big'
166
+ # #=> { people: { ann: "Ann is good", john: "John is big" } }
167
+ def deep_interpolate(locale, data, values = {})
168
+ return data if values.empty?
169
+
170
+ case data
171
+ when ::String
172
+ I18n.interpolate(data, values)
173
+ when ::Hash
174
+ data.each_with_object({}) do |(k, v), result|
175
+ result[k] = deep_interpolate(locale, v, values)
176
+ end
177
+ when ::Array
178
+ data.map do |v|
179
+ deep_interpolate(locale, v, values)
180
+ end
181
+ else
182
+ data
183
+ end
184
+ end
185
+
158
186
  # Loads a single translations file by delegating to #load_rb or
159
187
  # #load_yml depending on the file extension and directly merges the
160
188
  # data to the existing translations. Raises I18n::UnknownFileType
@@ -184,6 +212,19 @@ module I18n
184
212
  raise InvalidLocaleData.new(filename, e.inspect)
185
213
  end
186
214
  end
215
+
216
+ def translate_localization_format(locale, object, format, options)
217
+ format.to_s.gsub(/%[aAbBpP]/) do |match|
218
+ case match
219
+ when '%a' then I18n.t(:"date.abbr_day_names", :locale => locale, :format => format)[object.wday]
220
+ when '%A' then I18n.t(:"date.day_names", :locale => locale, :format => format)[object.wday]
221
+ when '%b' then I18n.t(:"date.abbr_month_names", :locale => locale, :format => format)[object.mon]
222
+ when '%B' then I18n.t(:"date.month_names", :locale => locale, :format => format)[object.mon]
223
+ when '%p' then I18n.t(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format).upcase if object.respond_to? :hour
224
+ when '%P' then I18n.t(:"time.#{object.hour < 12 ? :am : :pm}", :locale => locale, :format => format).downcase if object.respond_to? :hour
225
+ end
226
+ end
227
+ end
187
228
  end
188
229
  end
189
230
  end
@@ -13,9 +13,13 @@
13
13
  # You can use any cache implementation you want that provides the same API as
14
14
  # ActiveSupport::Cache (only the methods #fetch and #write are being used).
15
15
  #
16
- # The cache_key implementation assumes that you only pass values to
17
- # I18n.translate that return a valid key from #hash (see
18
- # http://www.ruby-doc.org/core/classes/Object.html#M000337).
16
+ # The cache_key implementation by default assumes you pass values that return
17
+ # a valid key from #hash (see
18
+ # http://www.ruby-doc.org/core/classes/Object.html#M000337). However, you can
19
+ # configure your own digest method via which responds to #hexdigest (see
20
+ # http://ruby-doc.org/stdlib/libdoc/digest/rdoc/index.html):
21
+ #
22
+ # I18n.cache_key_digest = Digest::MD5.new
19
23
  #
20
24
  # If you use a lambda as a default value in your translation like this:
21
25
  #
@@ -37,6 +41,7 @@ module I18n
37
41
  class << self
38
42
  @@cache_store = nil
39
43
  @@cache_namespace = nil
44
+ @@cache_key_digest = nil
40
45
 
41
46
  def cache_store
42
47
  @@cache_store
@@ -54,6 +59,14 @@ module I18n
54
59
  @@cache_namespace = namespace
55
60
  end
56
61
 
62
+ def cache_key_digest
63
+ @@cache_key_digest
64
+ end
65
+
66
+ def cache_key_digest=(key_digest)
67
+ @@cache_key_digest = key_digest
68
+ end
69
+
57
70
  def perform_caching?
58
71
  !cache_store.nil?
59
72
  end
@@ -76,7 +89,8 @@ module I18n
76
89
  end
77
90
 
78
91
  def _fetch(cache_key, &block)
79
- result = I18n.cache_store.read(cache_key) and return result
92
+ result = I18n.cache_store.read(cache_key)
93
+ return result unless result.nil?
80
94
  result = catch(:exception, &block)
81
95
  I18n.cache_store.write(cache_key, result) unless result.is_a?(Proc)
82
96
  result
@@ -84,13 +98,17 @@ module I18n
84
98
 
85
99
  def cache_key(locale, key, options)
86
100
  # This assumes that only simple, native Ruby values are passed to I18n.translate.
87
- "i18n/#{I18n.cache_namespace}/#{locale}/#{key.hash}/#{USE_INSPECT_HASH ? options.inspect.hash : options.hash}"
101
+ "i18n/#{I18n.cache_namespace}/#{locale}/#{digest_item(key)}/#{USE_INSPECT_HASH ? digest_item(options.inspect) : digest_item(options)}"
88
102
  end
89
103
 
90
104
  private
91
105
  # In Ruby < 1.9 the following is true: { :foo => 1, :bar => 2 }.hash == { :foo => 2, :bar => 1 }.hash
92
106
  # Therefore we must use the hash of the inspect string instead to avoid cache key colisions.
93
107
  USE_INSPECT_HASH = RUBY_VERSION <= "1.9"
108
+
109
+ def digest_item(key)
110
+ I18n.cache_key_digest ? I18n.cache_key_digest.hexdigest(key.to_s) : key.hash
111
+ end
94
112
  end
95
113
  end
96
114
  end
@@ -17,7 +17,7 @@ module I18n
17
17
  class Chain
18
18
  module Implementation
19
19
  include Base
20
-
20
+
21
21
  attr_accessor :backends
22
22
 
23
23
  def initialize(*backends)
@@ -46,7 +46,7 @@ module I18n
46
46
  translation = backend.translate(locale, key, options)
47
47
  if namespace_lookup?(translation, options)
48
48
  namespace = _deep_merge(translation, namespace || {})
49
- elsif !translation.nil?
49
+ elsif !translation.nil? || (options.key?(:default) && options[:default].nil?)
50
50
  return translation
51
51
  end
52
52
  end
@@ -75,7 +75,7 @@ module I18n
75
75
  def namespace_lookup?(result, options)
76
76
  result.is_a?(Hash) && !options.has_key?(:count)
77
77
  end
78
-
78
+
79
79
  private
80
80
  # This is approximately what gets used in ActiveSupport.
81
81
  # However since we are not guaranteed to run in an ActiveSupport context
@@ -38,18 +38,21 @@ module I18n
38
38
  return super if options[:fallback]
39
39
  default = extract_non_symbol_default!(options) if options[:default]
40
40
 
41
- options[:fallback] = true
42
- I18n.fallbacks[locale].each do |fallback|
43
- begin
44
- catch(:exception) do
45
- result = super(fallback, key, options)
46
- return result unless result.nil?
41
+ begin
42
+ options[:fallback] = true
43
+ I18n.fallbacks[locale].each do |fallback|
44
+ begin
45
+ catch(:exception) do
46
+ result = super(fallback, key, options)
47
+ return result if (result.nil? && options.key?(:default) && options[:default].nil?) || !result.nil?
48
+ end
49
+ rescue I18n::InvalidLocale
50
+ # we do nothing when the locale is invalid, as this is a fallback anyways.
47
51
  end
48
- rescue I18n::InvalidLocale
49
- # we do nothing when the locale is invalid, as this is a fallback anyways.
50
52
  end
53
+ ensure
54
+ options.delete(:fallback)
51
55
  end
52
- options.delete(:fallback)
53
56
 
54
57
  return super(locale, nil, options.merge(:default => default)) if default
55
58
  throw(:exception, I18n::MissingTranslation.new(locale, key, options))
@@ -64,6 +67,17 @@ module I18n
64
67
  return first_non_symbol_default
65
68
  end
66
69
 
70
+ def exists?(locale, key)
71
+ I18n.fallbacks[locale].each do |fallback|
72
+ begin
73
+ return true if super(fallback, key)
74
+ rescue I18n::InvalidLocale
75
+ # we do nothing when the locale is invalid, as this is a fallback anyways.
76
+ end
77
+ end
78
+
79
+ false
80
+ end
67
81
  end
68
82
  end
69
83
  end
@@ -1,24 +1,33 @@
1
1
  require 'i18n/gettext'
2
2
  require 'i18n/gettext/po_parser'
3
3
 
4
- # Experimental support for using Gettext po files to store translations.
5
- #
6
- # To use this you can simply include the module to the Simple backend - or
7
- # whatever other backend you are using.
8
- #
9
- # I18n::Backend::Simple.include(I18n::Backend::Gettext)
10
- #
11
- # Now you should be able to include your Gettext translation (*.po) files to
12
- # the I18n.load_path so they're loaded to the backend and you can use them as
13
- # usual:
14
- #
15
- # I18n.load_path += Dir["path/to/locales/*.po"]
16
- #
17
- # Following the Gettext convention this implementation expects that your
18
- # translation files are named by their locales. E.g. the file en.po would
19
- # contain the translations for the English locale.
20
4
  module I18n
21
5
  module Backend
6
+ # Experimental support for using Gettext po files to store translations.
7
+ #
8
+ # To use this you can simply include the module to the Simple backend - or
9
+ # whatever other backend you are using.
10
+ #
11
+ # I18n::Backend::Simple.include(I18n::Backend::Gettext)
12
+ #
13
+ # Now you should be able to include your Gettext translation (*.po) files to
14
+ # the +I18n.load_path+ so they're loaded to the backend and you can use them as
15
+ # usual:
16
+ #
17
+ # I18n.load_path += Dir["path/to/locales/*.po"]
18
+ #
19
+ # Following the Gettext convention this implementation expects that your
20
+ # translation files are named by their locales. E.g. the file en.po would
21
+ # contain the translations for the English locale.
22
+ #
23
+ # To translate text <b>you must use</b> one of the translate methods provided by
24
+ # I18n::Gettext::Helpers.
25
+ #
26
+ # include I18n::Gettext::Helpers
27
+ # puts _("some string")
28
+ #
29
+ # Without it strings containing periods (".") will not be translated.
30
+
22
31
  module Gettext
23
32
  class PoData < Hash
24
33
  def set_comment(msgid_or_sym, comment)
@@ -21,11 +21,15 @@ module I18n
21
21
  def included(base)
22
22
  Object.class_eval do
23
23
  def translation_metadata
24
- @translation_metadata ||= {}
24
+ unless self.frozen?
25
+ @translation_metadata ||= {}
26
+ else
27
+ {}
28
+ end
25
29
  end
26
30
 
27
31
  def translation_metadata=(translation_metadata)
28
- @translation_metadata = translation_metadata
32
+ @translation_metadata = translation_metadata unless self.frozen?
29
33
  end
30
34
  end unless Object.method_defined?(:translation_metadata)
31
35
  end
@@ -63,7 +63,7 @@ module I18n
63
63
  end
64
64
 
65
65
  # Looks up a translation from the translations hash. Returns nil if
66
- # eiher key is nil, or locale, scope or key do not exist as a key in the
66
+ # either key is nil, or locale, scope or key do not exist as a key in the
67
67
  # nested translations hash. Splits keys or scopes containing dots
68
68
  # into multiple keys, i.e. <tt>currency.format</tt> is regarded the same as
69
69
  # <tt>%w(currency format)</tt>.
@@ -71,13 +71,13 @@ module I18n
71
71
 
72
72
  def initialize(rule = nil)
73
73
  @rule = rule
74
- add DEFAULT_APPROXIMATIONS.dup
74
+ add_default_approximations
75
75
  add rule if rule
76
76
  end
77
77
 
78
- def transliterate(string, replacement = nil)
78
+ def transliterate(string, replacement = DEFAULT_REPLACEMENT_CHAR)
79
79
  string.gsub(/[^\x00-\x7f]/u) do |char|
80
- approximations[char] || replacement || DEFAULT_REPLACEMENT_CHAR
80
+ approximations[char] || replacement
81
81
  end
82
82
  end
83
83
 
@@ -87,6 +87,12 @@ module I18n
87
87
  @approximations ||= {}
88
88
  end
89
89
 
90
+ def add_default_approximations
91
+ DEFAULT_APPROXIMATIONS.each do |key, value|
92
+ approximations[key] = value
93
+ end
94
+ end
95
+
90
96
  # Add transliteration rules to the approximations hash.
91
97
  def add(hash)
92
98
  hash.each do |key, value|
@@ -96,4 +102,4 @@ module I18n
96
102
  end
97
103
  end
98
104
  end
99
- end
105
+ end
data/lib/i18n/config.rb CHANGED
@@ -5,7 +5,7 @@ module I18n
5
5
  # The only configuration value that is not global and scoped to thread is :locale.
6
6
  # It defaults to the default_locale.
7
7
  def locale
8
- @locale ||= default_locale
8
+ defined?(@locale) && @locale ? @locale : default_locale
9
9
  end
10
10
 
11
11
  # Sets the current locale pseudo-globally, i.e. in the Thread.current hash.
@@ -124,6 +124,8 @@ module I18n
124
124
  # behave like a Ruby Array.
125
125
  def load_path=(load_path)
126
126
  @@load_path = load_path
127
+ @@available_locales_set = nil
128
+ backend.reload!
127
129
  end
128
130
 
129
131
  # Whether or not to verify if locales are in the list of available locales.
@@ -1,7 +1,7 @@
1
1
  class Hash
2
2
  def slice(*keep_keys)
3
- h = {}
4
- keep_keys.each { |key| h[key] = fetch(key) }
3
+ h = self.class.new
4
+ keep_keys.each { |key| h[key] = fetch(key) if has_key?(key) }
5
5
  h
6
6
  end unless Hash.method_defined?(:slice)
7
7
 
@@ -21,7 +21,7 @@ class Hash
21
21
  MERGER = proc do |key, v1, v2|
22
22
  Hash === v1 && Hash === v2 ? v1.merge(v2, &MERGER) : v2
23
23
  end
24
-
24
+
25
25
  def deep_merge!(data)
26
26
  merge!(data, &MERGER)
27
27
  end unless Hash.method_defined?(:deep_merge!)
@@ -38,12 +38,12 @@ module I18n
38
38
  end
39
39
  end
40
40
 
41
- class MissingTranslation
41
+ class MissingTranslation < ArgumentError
42
42
  module Base
43
43
  attr_reader :locale, :key, :options
44
44
 
45
- def initialize(locale, key, options = nil)
46
- @key, @locale, @options = key, locale, options.dup || {}
45
+ def initialize(locale, key, options = {})
46
+ @key, @locale, @options = key, locale, options.dup
47
47
  options.each { |k, v| self.options[k] = v.inspect if v.is_a?(Proc) }
48
48
  end
49
49
 
@@ -7,6 +7,15 @@ module I18n
7
7
  #
8
8
  # include I18n::Gettext::Helpers
9
9
  module Helpers
10
+ # Makes dynamic translation messages readable for the gettext parser.
11
+ # <tt>_(fruit)</tt> cannot be understood by the gettext parser. To help the parser find all your translations,
12
+ # you can add <tt>fruit = N_("Apple")</tt> which does not translate, but tells the parser: "Apple" needs translation.
13
+ # * msgid: the message id.
14
+ # * Returns: msgid.
15
+ def N_(msgsid)
16
+ msgsid
17
+ end
18
+
10
19
  def gettext(msgid, options = {})
11
20
  I18n.t(msgid, { :default => msgid, :separator => '|' }.merge(options))
12
21
  end
@@ -22,7 +22,7 @@ module I18n
22
22
  if match == '%%'
23
23
  '%'
24
24
  else
25
- key = ($1 || $2).to_sym
25
+ key = ($1 || $2 || match.tr("%{}", "")).to_sym
26
26
  value = if values.key?(key)
27
27
  values[key]
28
28
  else
@@ -24,6 +24,14 @@ module I18n
24
24
  assert_equal 'bar', I18n.t(:does_not_exist, :default => [:does_not_exist_2, :'foo.bar'])
25
25
  end
26
26
 
27
+ test "defaults: given false it returns false" do
28
+ assert_equal false, I18n.t(:does_not_exist, :default => false)
29
+ end
30
+
31
+ test "defaults: given nil it returns nil" do
32
+ assert_equal nil, I18n.t(:does_not_exist, :default => nil)
33
+ end
34
+
27
35
  test "defaults: given an array of missing keys it raises a MissingTranslationData exception" do
28
36
  assert_raise I18n::MissingTranslationData do
29
37
  I18n.t(:does_not_exist, :default => [:does_not_exist_2, :does_not_exist_3], :raise => true)
@@ -104,6 +104,26 @@ module I18n
104
104
  assert_raise(I18n::ReservedInterpolationKey) { interpolate(:default => '%{separator}', :foo => :bar) }
105
105
  end
106
106
 
107
+ test "interpolation: deep interpolation for default string" do
108
+ assert_equal 'Hi %{name}!', interpolate(:default => 'Hi %{name}!', :deep_interpolation => true)
109
+ end
110
+
111
+ test "interpolation: deep interpolation for interpolated string" do
112
+ assert_equal 'Hi Ann!', interpolate(:default => 'Hi %{name}!', :name => 'Ann', :deep_interpolation => true)
113
+ end
114
+
115
+ test "interpolation: deep interpolation for Hash" do
116
+ people = { :people => { :ann => 'Ann is %{ann}', :john => 'John is %{john}' } }
117
+ interpolated_people = { :people => { :ann => 'Ann is good', :john => 'John is big' } }
118
+ assert_equal interpolated_people, interpolate(:default => people, :ann => 'good', :john => 'big', :deep_interpolation => true)
119
+ end
120
+
121
+ test "interpolation: deep interpolation for Array" do
122
+ people = { :people => ['Ann is %{ann}', 'John is %{john}'] }
123
+ interpolated_people = { :people => ['Ann is good', 'John is big'] }
124
+ assert_equal interpolated_people, interpolate(:default => people, :ann => 'good', :john => 'big', :deep_interpolation => true)
125
+ end
126
+
107
127
  protected
108
128
 
109
129
  def capture(stream)
@@ -51,6 +51,10 @@ module I18n
51
51
  assert_nothing_raised { I18n.l(@date, options.freeze) }
52
52
  end
53
53
 
54
+ test "localize Date: given nil with default value it returns default" do
55
+ assert_equal 'default', I18n.l(nil, :default => 'default')
56
+ end
57
+
54
58
  test "localize Date: given nil it raises I18n::ArgumentError" do
55
59
  assert_raise(I18n::ArgumentError) { I18n.l(nil) }
56
60
  end
@@ -52,19 +52,19 @@ module I18n
52
52
  test "localize Time: given a format that resolves to a Proc it calls the Proc with the object" do
53
53
  setup_time_proc_translations
54
54
  time = ::Time.utc(2008, 3, 1, 6, 0)
55
- assert_equal inspect_args([time, {}]), I18n.l(time, :format => :proc, :locale => :ru)
55
+ assert_equal I18n::Tests::Localization::Procs.inspect_args([time, {}]), I18n.l(time, :format => :proc, :locale => :ru)
56
56
  end
57
57
 
58
58
  test "localize Time: given a format that resolves to a Proc it calls the Proc with the object and extra options" do
59
59
  setup_time_proc_translations
60
60
  time = ::Time.utc(2008, 3, 1, 6, 0)
61
61
  options = { :foo => 'foo' }
62
- assert_equal inspect_args([time, options]), I18n.l(time, options.merge(:format => :proc, :locale => :ru))
62
+ assert_equal I18n::Tests::Localization::Procs.inspect_args([time, options]), I18n.l(time, options.merge(:format => :proc, :locale => :ru))
63
63
  end
64
64
 
65
65
  protected
66
66
 
67
- def inspect_args(args)
67
+ def self.inspect_args(args)
68
68
  args = args.map do |arg|
69
69
  case arg
70
70
  when ::Time, ::DateTime
@@ -85,12 +85,12 @@ module I18n
85
85
  I18n.backend.store_translations :ru, {
86
86
  :time => {
87
87
  :formats => {
88
- :proc => lambda { |*args| inspect_args(args) }
88
+ :proc => lambda { |*args| I18n::Tests::Localization::Procs.inspect_args(args) }
89
89
  }
90
90
  },
91
91
  :date => {
92
92
  :formats => {
93
- :proc => lambda { |*args| inspect_args(args) }
93
+ :proc => lambda { |*args| I18n::Tests::Localization::Procs.inspect_args(args) }
94
94
  },
95
95
  :'day_names' => lambda { |key, options|
96
96
  (options[:format] =~ /^%A/) ?
@@ -4,28 +4,29 @@ module I18n
4
4
  module Tests
5
5
  module Procs
6
6
  test "lookup: given a translation is a proc it calls the proc with the key and interpolation values" do
7
- I18n.backend.store_translations(:en, :a_lambda => lambda { |*args| filter_args(*args) })
7
+ I18n.backend.store_translations(:en, :a_lambda => lambda { |*args| I18n::Tests::Procs.filter_args(*args) })
8
8
  assert_equal '[:a_lambda, {:foo=>"foo"}]', I18n.t(:a_lambda, :foo => 'foo')
9
9
  end
10
10
 
11
11
  test "defaults: given a default is a Proc it calls it with the key and interpolation values" do
12
- proc = lambda { |*args| filter_args(*args) }
12
+ proc = lambda { |*args| I18n::Tests::Procs.filter_args(*args) }
13
13
  assert_equal '[nil, {:foo=>"foo"}]', I18n.t(nil, :default => proc, :foo => 'foo')
14
14
  end
15
15
 
16
16
  test "defaults: given a default is a key that resolves to a Proc it calls it with the key and interpolation values" do
17
- I18n.backend.store_translations(:en, :a_lambda => lambda { |*args| filter_args(*args) })
17
+ the_lambda = lambda { |*args| I18n::Tests::Procs.filter_args(*args) }
18
+ I18n.backend.store_translations(:en, :a_lambda => the_lambda)
18
19
  assert_equal '[:a_lambda, {:foo=>"foo"}]', I18n.t(nil, :default => :a_lambda, :foo => 'foo')
19
20
  assert_equal '[:a_lambda, {:foo=>"foo"}]', I18n.t(nil, :default => [nil, :a_lambda], :foo => 'foo')
20
21
  end
21
22
 
22
23
  test "interpolation: given an interpolation value is a lambda it calls it with key and values before interpolating it" do
23
- proc = lambda { |*args| filter_args(*args) }
24
+ proc = lambda { |*args| I18n::Tests::Procs.filter_args(*args) }
24
25
  assert_match %r(\[\{:foo=>#<Proc.*>\}\]), I18n.t(nil, :default => '%{foo}', :foo => proc)
25
26
  end
26
27
 
27
28
  test "interpolation: given a key resolves to a Proc that returns a string then interpolation still works" do
28
- proc = lambda { |*args| "%{foo}: " + filter_args(*args) }
29
+ proc = lambda { |*args| "%{foo}: " + I18n::Tests::Procs.filter_args(*args) }
29
30
  assert_equal 'foo: [nil, {:foo=>"foo"}]', I18n.t(nil, :default => proc, :foo => 'foo')
30
31
  end
31
32
 
@@ -37,17 +38,16 @@ module I18n
37
38
  end
38
39
 
39
40
  test "lookup: given the option :resolve => false was passed it does not resolve proc translations" do
40
- I18n.backend.store_translations(:en, :a_lambda => lambda { |*args| filter_args(*args) })
41
+ I18n.backend.store_translations(:en, :a_lambda => lambda { |*args| I18n::Tests::Procs.filter_args(*args) })
41
42
  assert_equal Proc, I18n.t(:a_lambda, :resolve => false).class
42
43
  end
43
44
 
44
45
  test "lookup: given the option :resolve => false was passed it does not resolve proc default" do
45
- assert_equal Proc, I18n.t(nil, :default => lambda { |*args| filter_args(*args) }, :resolve => false).class
46
+ assert_equal Proc, I18n.t(nil, :default => lambda { |*args| I18n::Tests::Procs.filter_args(*args) }, :resolve => false).class
46
47
  end
47
48
 
48
- protected
49
49
 
50
- def filter_args(*args)
50
+ def self.filter_args(*args)
51
51
  args.map {|arg| arg.delete(:fallback) if arg.is_a?(Hash) ; arg }.inspect
52
52
  end
53
53
  end
data/lib/i18n/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module I18n
2
- VERSION = "0.7.0"
2
+ VERSION = "0.8.0"
3
3
  end
data/lib/i18n.rb CHANGED
@@ -9,10 +9,10 @@ module I18n
9
9
  autoload :Locale, 'i18n/locale'
10
10
  autoload :Tests, 'i18n/tests'
11
11
 
12
- RESERVED_KEYS = [:scope, :default, :separator, :resolve, :object, :fallback, :format, :cascade, :throw, :raise]
12
+ RESERVED_KEYS = [:scope, :default, :separator, :resolve, :object, :fallback, :format, :cascade, :throw, :raise, :deep_interpolation]
13
13
  RESERVED_KEYS_PATTERN = /%\{(#{RESERVED_KEYS.join("|")})\}/
14
14
 
15
- extend(Module.new {
15
+ module Base
16
16
  # Gets I18n configuration object.
17
17
  def config
18
18
  Thread.current[:i18n_config] ||= I18n::Config.new
@@ -337,5 +337,7 @@ module I18n
337
337
  def normalized_key_cache
338
338
  @normalized_key_cache ||= Hash.new { |h,k| h[k] = {} }
339
339
  end
340
- })
340
+ end
341
+
342
+ extend Base
341
343
  end
@@ -15,6 +15,7 @@ class I18nBackendCacheTest < I18n::TestCase
15
15
  I18n.backend = Backend.new
16
16
  super
17
17
  I18n.cache_store = ActiveSupport::Cache.lookup_store(:memory_store)
18
+ I18n.cache_key_digest = nil
18
19
  end
19
20
 
20
21
  def teardown
@@ -37,6 +38,12 @@ class I18nBackendCacheTest < I18n::TestCase
37
38
  assert_equal 'Bar', I18n.t(:bar)
38
39
  end
39
40
 
41
+ test "translate returns a cached false response" do
42
+ I18n.backend.expects(:lookup).never
43
+ I18n.cache_store.expects(:read).returns(false)
44
+ assert_equal false, I18n.t(:foo)
45
+ end
46
+
40
47
  test "still raises MissingTranslationData but also caches it" do
41
48
  assert_raise(I18n::MissingTranslationData) { I18n.t(:missing, :raise => true) }
42
49
  assert_raise(I18n::MissingTranslationData) { I18n.t(:missing, :raise => true) }
@@ -60,10 +67,19 @@ class I18nBackendCacheTest < I18n::TestCase
60
67
 
61
68
  test "adds locale and hash of key and hash of options" do
62
69
  options = { :bar=>1 }
63
- options_hash = I18n::Backend::Cache::USE_INSPECT_HASH ? options.inspect.hash : options.hash
70
+ options_hash = RUBY_VERSION <= "1.9" ? options.inspect.hash : options.hash
64
71
  assert_equal "i18n//en/#{:foo.hash}/#{options_hash}", I18n.backend.send(:cache_key, :en, :foo, options)
65
72
  end
66
73
 
74
+ test "cache_key uses configured digest method" do
75
+ md5 = Digest::MD5.new
76
+ options = { :bar=>1 }
77
+ options_hash = options.inspect
78
+ with_cache_key_digest(md5) do
79
+ assert_equal "i18n//en/#{md5.hexdigest(:foo.to_s)}/#{md5.hexdigest(options_hash)}", I18n.backend.send(:cache_key, :en, :foo, options)
80
+ end
81
+ end
82
+
67
83
  test "keys should not be equal" do
68
84
  interpolation_values1 = { :foo => 1, :bar => 2 }
69
85
  interpolation_values2 = { :foo => 2, :bar => 1 }
@@ -81,6 +97,12 @@ class I18nBackendCacheTest < I18n::TestCase
81
97
  yield
82
98
  I18n.cache_namespace = nil
83
99
  end
100
+
101
+ def with_cache_key_digest(digest)
102
+ I18n.cache_key_digest = digest
103
+ yield
104
+ I18n.cache_key_digest = nil
105
+ end
84
106
  end
85
107
 
86
108
  end # AS cache check
@@ -141,6 +141,10 @@ class I18nBackendFallbacksWithChainTest < I18n::TestCase
141
141
  assert_equal 'FOO', I18n.t(:foo, :locale => :'de-DE')
142
142
  end
143
143
 
144
+ test "falls back from de-DE to de when there is no translation for de-DE available when using arrays, too" do
145
+ assert_equal ['FOO', 'FOO'], I18n.t([:foo, :foo], :locale => :'de-DE')
146
+ end
147
+
144
148
  test "should not raise error when enforce_available_locales is true, :'pt' is missing and default is a Symbol" do
145
149
  I18n.enforce_available_locales = true
146
150
  begin
@@ -150,3 +154,45 @@ class I18nBackendFallbacksWithChainTest < I18n::TestCase
150
154
  end
151
155
  end
152
156
  end
157
+
158
+ class I18nBackendFallbacksExistsTest < I18n::TestCase
159
+ class Backend < I18n::Backend::Simple
160
+ include I18n::Backend::Fallbacks
161
+ end
162
+
163
+ def setup
164
+ super
165
+ I18n.backend = Backend.new
166
+ store_translations(:en, :foo => 'Foo in :en', :bar => 'Bar in :en')
167
+ store_translations(:de, :bar => 'Bar in :de')
168
+ store_translations(:'de-DE', :baz => 'Baz in :de-DE')
169
+ end
170
+
171
+ test "exists? given an existing key will return true" do
172
+ assert_equal true, I18n.exists?(:foo)
173
+ end
174
+
175
+ test "exists? given a non-existing key will return false" do
176
+ assert_equal false, I18n.exists?(:bogus)
177
+ end
178
+
179
+ test "exists? given an existing key and an existing locale will return true" do
180
+ assert_equal true, I18n.exists?(:foo, :en)
181
+ assert_equal true, I18n.exists?(:bar, :de)
182
+ end
183
+
184
+ test "exists? given a non-existing key and an existing locale will return false" do
185
+ assert_equal false, I18n.exists?(:bogus, :en)
186
+ assert_equal false, I18n.exists?(:bogus, :de)
187
+ end
188
+
189
+ test "exists? should return true given a key which is missing from the given locale and exists in a fallback locale" do
190
+ assert_equal true, I18n.exists?(:foo, :de)
191
+ assert_equal true, I18n.exists?(:foo, :'de-DE')
192
+ end
193
+
194
+ test "exists? should return false given a key which is missing from the given locale and all its fallback locales" do
195
+ assert_equal false, I18n.exists?(:baz, :de)
196
+ assert_equal false, I18n.exists?(:bogus, :'de-DE')
197
+ end
198
+ end
@@ -14,6 +14,18 @@ class I18nCoreExtHashInterpolationTest < I18n::TestCase
14
14
  assert_equal expected, hash.slice(:foo)
15
15
  end
16
16
 
17
+ test "#slice non-existent key" do
18
+ hash = { :foo => 'bar', :baz => 'bar' }
19
+ expected = { :foo => 'bar' }
20
+ assert_equal expected, hash.slice(:foo, :not_here)
21
+ end
22
+
23
+ test "#slice maintains subclasses of Hash" do
24
+ klass = Class.new(Hash)
25
+ hash = klass[:foo, 'bar', :baz, 'bar']
26
+ assert_instance_of klass, hash.slice(:foo)
27
+ end
28
+
17
29
  test "#except" do
18
30
  hash = { :foo => 'bar', :baz => 'bar' }
19
31
  expected = { :foo => 'bar' }
@@ -18,6 +18,13 @@ class I18nGettextApiTest < I18n::TestCase
18
18
  }, :separator => '|'
19
19
  end
20
20
 
21
+ # N_
22
+ def test_N_returns_original_msg
23
+ assert_equal 'foo|bar', N_('foo|bar')
24
+ I18n.locale = :de
25
+ assert_equal 'Hi Gettext!', N_('Hi Gettext!')
26
+ end
27
+
21
28
  # gettext
22
29
  def test_gettext_uses_msg_as_default
23
30
  assert_equal 'Hi Gettext!', _('Hi Gettext!')
@@ -13,6 +13,11 @@ class I18nExceptionsTest < I18n::TestCase
13
13
  end
14
14
  end
15
15
 
16
+ test "MissingTranslation can be initialized without options" do
17
+ exception = I18n::MissingTranslation.new(:en, 'foo')
18
+ assert_equal({}, exception.options)
19
+ end
20
+
16
21
  test "MissingTranslationData exception stores locale, key and options" do
17
22
  force_missing_translation_data do |exception|
18
23
  assert_equal 'de', exception.locale
@@ -66,6 +71,10 @@ class I18nExceptionsTest < I18n::TestCase
66
71
  assert_equal 'reserved key :scope used in "%{scope}"', exception.message
67
72
  end
68
73
  end
74
+
75
+ test "MissingTranslationData#new can be initialized with just two arguments" do
76
+ assert I18n::MissingTranslationData.new('en', 'key')
77
+ end
69
78
 
70
79
  private
71
80
 
@@ -57,6 +57,17 @@ class I18nInterpolateTest < I18n::TestCase
57
57
  def test_sprintf_mix_unformatted_and_formatted_named_placeholders
58
58
  assert_equal "foo 1.000000", I18n.interpolate("%{name} %<num>f", :name => "foo", :num => 1.0)
59
59
  end
60
+
61
+ class RailsSafeBuffer < String
62
+
63
+ def gsub(*args, &block)
64
+ to_str.gsub(*args, &block)
65
+ end
66
+
67
+ end
68
+ test "with String subclass that redefined gsub method" do
69
+ assert_equal "Hello mars world", I18n.interpolate(RailsSafeBuffer.new("Hello %{planet} world"), :planet => 'mars')
70
+ end
60
71
  end
61
72
 
62
73
  class I18nMissingInterpolationCustomHandlerTest < I18n::TestCase
data/test/i18n_test.rb CHANGED
@@ -269,6 +269,10 @@ class I18nTest < I18n::TestCase
269
269
  assert_raise(I18n::ArgumentError) { I18n.l nil }
270
270
  end
271
271
 
272
+ test "localize given nil and default returns default" do
273
+ assert_equal nil, I18n.l(nil, :default => nil)
274
+ end
275
+
272
276
  test "localize given an Object raises an I18n::ArgumentError" do
273
277
  assert_raise(I18n::ArgumentError) { I18n.l Object.new }
274
278
  end
@@ -74,7 +74,7 @@
74
74
  :or => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
75
75
  :pa => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
76
76
  :pap => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
77
- :pl => { :i18n => { :plural => { :keys => [:one, :few, :other], :rule => lambda { |n| n == 1 ? :one : [2, 3, 4].include?(n % 10) && ![12, 13, 14].include?(n % 100) ? :few : :other } } } },
77
+ :pl => { :i18n => { :plural => { :keys => [:one, :few, :many, :other], :rule => lambda { |n| n == 1 ? :one : [2, 3, 4].include?(n % 10) && ![12, 13, 14].include?(n % 100) ? :few : (n != 1 && [0, 1].include?(n % 10)) || [5, 6, 7, 8, 9].include?(n % 10) || [12, 13, 14].include?(n % 100) ? :many : :other } } } },
78
78
  :ps => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
79
79
  :pt => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| [0, 1].include?(n) ? :one : :other } } } },
80
80
  :"pt-PT" => { :i18n => { :plural => { :keys => [:one, :other], :rule => lambda { |n| n == 1 ? :one : :other } } } },
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: i18n
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sven Fuchs
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2014-12-19 00:00:00.000000000 Z
15
+ date: 2017-01-31 00:00:00.000000000 Z
16
16
  dependencies: []
17
17
  description: New wave Internationalization support for Ruby.
18
18
  email: rails-i18n@googlegroups.com
@@ -30,6 +30,8 @@ files:
30
30
  - gemfiles/Gemfile.rails-4.1.x.lock
31
31
  - gemfiles/Gemfile.rails-4.2.x
32
32
  - gemfiles/Gemfile.rails-4.2.x.lock
33
+ - gemfiles/Gemfile.rails-5.0.x
34
+ - gemfiles/Gemfile.rails-5.0.x.lock
33
35
  - gemfiles/Gemfile.rails-master
34
36
  - gemfiles/Gemfile.rails-master.lock
35
37
  - lib/i18n.rb
@@ -136,7 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
136
138
  version: 1.3.5
137
139
  requirements: []
138
140
  rubyforge_project: "[none]"
139
- rubygems_version: 2.4.3
141
+ rubygems_version: 2.6.8
140
142
  signing_key:
141
143
  specification_version: 4
142
144
  summary: New wave Internationalization support for Ruby