i18n 0.7.0 → 0.8.0.beta1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of i18n might be problematic. Click here for more details.

Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -3
  3. data/gemfiles/Gemfile.rails-3.2.x +1 -0
  4. data/gemfiles/Gemfile.rails-3.2.x.lock +6 -1
  5. data/gemfiles/Gemfile.rails-4.0.x +1 -0
  6. data/gemfiles/Gemfile.rails-4.0.x.lock +8 -4
  7. data/gemfiles/Gemfile.rails-4.1.x +1 -0
  8. data/gemfiles/Gemfile.rails-4.1.x.lock +8 -4
  9. data/gemfiles/Gemfile.rails-4.2.x +2 -1
  10. data/gemfiles/Gemfile.rails-4.2.x.lock +11 -7
  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 +11 -8
  15. data/lib/i18n.rb +5 -3
  16. data/lib/i18n/backend/base.rb +61 -20
  17. data/lib/i18n/backend/cache.rb +23 -5
  18. data/lib/i18n/backend/chain.rb +3 -3
  19. data/lib/i18n/backend/fallbacks.rb +23 -9
  20. data/lib/i18n/backend/gettext.rb +25 -16
  21. data/lib/i18n/backend/metadata.rb +6 -2
  22. data/lib/i18n/backend/simple.rb +1 -1
  23. data/lib/i18n/backend/transliterator.rb +10 -4
  24. data/lib/i18n/config.rb +3 -1
  25. data/lib/i18n/core_ext/hash.rb +3 -3
  26. data/lib/i18n/exceptions.rb +3 -3
  27. data/lib/i18n/gettext/helpers.rb +9 -0
  28. data/lib/i18n/interpolate/ruby.rb +1 -1
  29. data/lib/i18n/tests/defaults.rb +8 -0
  30. data/lib/i18n/tests/interpolation.rb +20 -0
  31. data/lib/i18n/tests/localization/date.rb +4 -0
  32. data/lib/i18n/tests/localization/procs.rb +5 -5
  33. data/lib/i18n/tests/procs.rb +9 -9
  34. data/lib/i18n/version.rb +1 -1
  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: '06029cd6886cf5d01ec5c508ee83ec2e5f3ef8c8'
4
+ data.tar.gz: 8b359de40823d4a14757157320c3496d671e8900
5
5
  SHA512:
6
- metadata.gz: 751f36baefacf168a3473f84d4b4f4a0a6c1da62acb51d78bfa4a1926dc6ba242bf4fa2ccd4263f07491a5c036f1eb37cc750efe5f8f227caf169d838bbfd87e
7
- data.tar.gz: 657f8618ea7c530a4ea77d250bda199b28eb7b1f3a7429156ee4f82de3b0481f94daf0b9833c2f31dffb8487e9355b90e0b123e00f173fc205331345322248ef
6
+ metadata.gz: 1bf61e5f2cd5d4af1f4665eb61fcb67b15313480248697fd15a54a314ab512c0880d785c3b3b8ddcbd8d8d5b6af1b1d8cbe65e797f22fabc8821aa661bb871a1
7
+ data.tar.gz: '06319f0d7005c3db6cdc12885f9f319cdde28dec8c132f8746ac03cf180df5c85590d3dd34057de1cbd60874c3192c96120dbb15cc0d1dd7e551dc5024947c01'
data/README.md CHANGED
@@ -22,7 +22,7 @@ Pluggable features:
22
22
  * Cache
23
23
  * Pluralization: lambda pluralizers stored as translation data
24
24
  * Locale fallbacks, RFC4647 compliant (optionally: RFC4646 locale validation)
25
- * Gettext support
25
+ * [Gettext support](https://github.com/svenfuchs/i18n/wiki/Gettext)
26
26
  * Translation metadata
27
27
 
28
28
  Alternative backends:
@@ -31,7 +31,7 @@ Alternative backends:
31
31
  * ActiveRecord (optionally: ActiveRecord::Missing and ActiveRecord::StoreProcs)
32
32
  * KeyValue (uses active_support/json and cannot store procs)
33
33
 
34
- For more information and lots of resources see: [http://ruby-i18n.org/wiki](http://ruby-i18n.org/wiki)
34
+ For more information and lots of resources see [the 'Resources' page on the wiki](https://github.com/svenfuchs/i18n/wiki/Resources).
35
35
 
36
36
  ## Installation
37
37
 
@@ -71,7 +71,7 @@ follow the usual test setup and should be easy to grok.
71
71
  * [Joshua Harvey](http://www.workingwithrails.com/person/759-joshua-harvey)
72
72
  * [Stephan Soller](http://www.arkanis-development.de)
73
73
  * [Saimon Moore](http://saimonmoore.net)
74
- * [Matt Aimonetti](http://railsontherun.com)
74
+ * [Matt Aimonetti](https://matt.aimonetti.net/)
75
75
 
76
76
  ## Contributors
77
77
 
@@ -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'
@@ -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.5
@@ -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'
@@ -6,7 +6,7 @@ PATH
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.5
@@ -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'
@@ -6,20 +6,20 @@ PATH
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.5
@@ -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'
@@ -6,20 +6,20 @@ PATH
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.5
@@ -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.7.0)
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.6
@@ -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,13 +1,12 @@
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
@@ -18,14 +17,14 @@ PATH
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.6
@@ -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
@@ -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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module I18n
2
- VERSION = "0.7.0"
2
+ VERSION = "0.8.0.beta1"
3
3
  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
@@ -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.beta1
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: 2016-11-21 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.5.1
140
142
  signing_key:
141
143
  specification_version: 4
142
144
  summary: New wave Internationalization support for Ruby