i18n 0.5.0 → 0.5.2

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 (63) hide show
  1. data/CHANGELOG.textile +3 -12
  2. data/README.textile +17 -4
  3. data/lib/i18n.rb +37 -5
  4. data/lib/i18n/backend.rb +1 -0
  5. data/lib/i18n/backend/base.rb +32 -4
  6. data/lib/i18n/backend/cldr.rb +99 -0
  7. data/lib/i18n/backend/gettext.rb +3 -3
  8. data/lib/i18n/backend/interpolation_compiler.rb +1 -1
  9. data/lib/i18n/backend/metadata.rb +1 -1
  10. data/lib/i18n/config.rb +13 -1
  11. data/lib/i18n/core_ext/string/interpolate.rb +0 -9
  12. data/lib/i18n/exceptions.rb +31 -3
  13. data/lib/i18n/locale/fallbacks.rb +3 -3
  14. data/lib/i18n/tests/localization/procs.rb +24 -25
  15. data/lib/i18n/version.rb +1 -1
  16. metadata +16 -121
  17. data/ci/Gemfile.no-rails +0 -5
  18. data/ci/Gemfile.no-rails.lock +0 -14
  19. data/ci/Gemfile.rails-2.3.x +0 -9
  20. data/ci/Gemfile.rails-2.3.x.lock +0 -23
  21. data/ci/Gemfile.rails-3.x +0 -9
  22. data/ci/Gemfile.rails-3.x.lock +0 -23
  23. data/lib/i18n/core_ext/kernel/surpress_warnings.rb +0 -9
  24. data/lib/i18n/interpolate/ruby.rb +0 -31
  25. data/test/all.rb +0 -8
  26. data/test/api/all_features_test.rb +0 -58
  27. data/test/api/cascade_test.rb +0 -28
  28. data/test/api/chain_test.rb +0 -24
  29. data/test/api/fallbacks_test.rb +0 -30
  30. data/test/api/key_value_test.rb +0 -28
  31. data/test/api/memoize_test.rb +0 -60
  32. data/test/api/pluralization_test.rb +0 -30
  33. data/test/api/simple_test.rb +0 -28
  34. data/test/backend/cache_test.rb +0 -83
  35. data/test/backend/cascade_test.rb +0 -85
  36. data/test/backend/chain_test.rb +0 -67
  37. data/test/backend/exceptions_test.rb +0 -23
  38. data/test/backend/fallbacks_test.rb +0 -116
  39. data/test/backend/interpolation_compiler_test.rb +0 -102
  40. data/test/backend/key_value_test.rb +0 -46
  41. data/test/backend/memoize_test.rb +0 -47
  42. data/test/backend/metadata_test.rb +0 -67
  43. data/test/backend/pluralization_test.rb +0 -44
  44. data/test/backend/simple_test.rb +0 -79
  45. data/test/backend/transliterator_test.rb +0 -81
  46. data/test/core_ext/hash_test.rb +0 -30
  47. data/test/core_ext/string/interpolate_test.rb +0 -99
  48. data/test/gettext/api_test.rb +0 -206
  49. data/test/gettext/backend_test.rb +0 -93
  50. data/test/i18n/exceptions_test.rb +0 -116
  51. data/test/i18n/interpolate_test.rb +0 -61
  52. data/test/i18n/load_path_test.rb +0 -26
  53. data/test/i18n_test.rb +0 -236
  54. data/test/locale/fallbacks_test.rb +0 -124
  55. data/test/locale/tag/rfc4646_test.rb +0 -142
  56. data/test/locale/tag/simple_test.rb +0 -32
  57. data/test/run_all.rb +0 -21
  58. data/test/test_data/locales/de.po +0 -72
  59. data/test/test_data/locales/en.rb +0 -3
  60. data/test/test_data/locales/en.yml +0 -3
  61. data/test/test_data/locales/invalid/empty.yml +0 -1
  62. data/test/test_data/locales/plurals.rb +0 -113
  63. data/test/test_helper.rb +0 -56
@@ -1,14 +1,5 @@
1
1
  h1. Changelog
2
2
 
3
- h2. 0.5.0
4
-
5
- * "Extract Backend::ActiveRecord to a separate gem":https://github.com/svenfuchs/i18n/commit/197dacebad356b910d69fa69a719c2ad10cf49e6 (see "i18n-active_record":https://github.com/svenfuchs/i18n-active_record)
6
- * "Improve exception handling":https://github.com/svenfuchs/i18n/commit/2913ff9a7544f223f60e7d7b32c2a0e1af89812b (deprectates I18n.default_exception_handler)
7
- * "Change MissingTranslationData message to 'translation missing: foo.bar'":https://github.com/svenfuchs/i18n/commit/68fdfe47952325411afe5942e971ce10b2bdf900
8
- * "Expose MissingTranslationsData#keys method":https://github.com/svenfuchs/i18n/commit/3a37a389ecaac9670355b334e23e775549ee9822
9
- * "Improve Cascade#lookup (add default options)":https://github.com/svenfuchs/i18n/commit/0b9a1f2058a2be9543106cc19d08071c359511e1
10
- * "Finally remove deprecated interpolation syntax":https://github.com/svenfuchs/i18n/commit/2d43846d2b2a2e596f30fa58ea1c9ddb2243bb64
11
-
12
3
  h2. 0.4.2 (2010-10-26)
13
4
 
14
5
  * "Improve UTF8 handling":http://github.com/svenfuchs/i18n/commit/e8d5820a3b08eeca28de1a2b9c8a6ad2b9e6476c
@@ -53,7 +44,7 @@ h2. 0.3.6 (2010-03-23)
53
44
 
54
45
  h2. 0.3.5 (2010-02-26)
55
46
 
56
- * "Delegate I18n.normalize_translation_keys to I18n.normalize_keys and deprecate
47
+ * "Delegate I18n.normalize_translation_keys to I18n.normalize_keys and deprecate
57
48
  the former":http://github.com/svenfuchs/i18n/commit/7284b04d5f5dd9679cb68875515cdd0cdfc96fef
58
49
 
59
50
  h2. 0.3.4 (2010-02-25)
@@ -102,7 +93,7 @@ h2. 0.3.0 (2009-11-30)
102
93
  h2. 0.2.0 (2009-07-12)
103
94
 
104
95
  * "Allow using Ruby 1.9 syntax for string interpolation (API addition)":http://github.com/svenfuchs/i18n/commit/c6e0b06d512f2af57199a843a1d8a40241b32861
105
- * "Allow configuring the default scope separator, allow to pass a custom scope separator(API addition)":http://github.com/svenfuchs/i18n/commit/5b75bfbc348061adc11e3790187a187275bfd471 (e.g. I18n.t(:'foo|bar', :separator => '|')
96
+ * "Allow configuring the default scope separator, allow to pass a custom scope separator(API addition)":http://github.com/svenfuchs/i18n/commit/5b75bfbc348061adc11e3790187a187275bfd471 (e.g. I18n.t(:'foo|bar', :separator => '|')
106
97
  * "Pass :format option to #translate for #localize more useful lambda support":http://github.com/svenfuchs/i18n/commit/e277711b3c844fe7589b8d3f9af0f7d1b969a273
107
98
  * "Refactor Simple backend #resolve to #default and #resolve for more consistency. Now allows to pass lambdas as defaults and re-resolve Symbols":http://github.com/svenfuchs/i18n/commit/8c4ce3d923ce5fa73e973fe28217e18165549aba
108
99
  * "Add lambda support to #translate (API addition)":http://github.com/svenfuchs/i18n/commit/c90e62d8f7d3d5b78f34cfe328d871b58884f115
@@ -149,4 +140,4 @@ h2. More information
149
140
  * "Wiki":http://rails-i18n.org/wiki
150
141
  * "Mailinglist":http://groups.google.com/group/rails-i18n
151
142
  * "About the project/history":http://www.artweb-design.de/2008/7/18/finally-ruby-on-rails-gets-internationalized
152
- * "Initial API Intro":http://www.artweb-design.de/2008/7/18/the-ruby-on-rails-i18n-core-api
143
+ * "Initial API Intro":http://www.artweb-design.de/2008/7/18/the-ruby-on-rails-i18n-core-api
@@ -64,12 +64,25 @@ h2. Tests
64
64
  You can run tests both with
65
65
 
66
66
  * `rake test` or just `rake`
67
- * run any test file directly, e.g. `ruby -Ilib -Itest test/api/simple_test.rb`
68
- * run all tests with `ruby -Ilib -Itest test/all.rb`
67
+ * run any test file directly, e.g. `ruby test/api/simple_test.rb`
68
+ * run all tests with `ruby test/all.rb`
69
69
 
70
- You can run all tests against all Gemfiles with
70
+ You can parametrize the test suite for using different sets of dependencies by
71
+ using:
71
72
 
72
- * `ruby test/run_all.rb`
73
+ .pre `ruby test/all.rb -w DEPENDENCIES`
74
+
75
+ ... where DEPENDENCIES is a comma-separated list of:
76
+
77
+ * r23 or rails-2.3.x
78
+ * r3 or rails-3.x
79
+ * no-rails
80
+ * sqlite
81
+ * mysql
82
+
83
+ So, e.g. this would run the test suite against Rails 2.3.x using mysql:
84
+
85
+ .pre `ruby test/all.rb -w r23,mysql`
73
86
 
74
87
  The structure of the test suite is a bit unusual as it uses modules to reuse
75
88
  particular tests in different test cases.
@@ -1,6 +1,13 @@
1
+ # Authors:: Sven Fuchs (http://www.artweb-design.de),
2
+ # Joshua Harvey (http://www.workingwithrails.com/person/759-joshua-harvey),
3
+ # Stephan Soller (http://www.arkanis-development.de/),
4
+ # Saimon Moore (http://saimonmoore.net),
5
+ # Matt Aimonetti (http://railsontherun.com/)
6
+ # Copyright:: Copyright (c) 2008 The Ruby i18n Team
7
+ # License:: MIT
1
8
  require 'i18n/version'
2
9
  require 'i18n/exceptions'
3
- require 'i18n/interpolate/ruby.rb'
10
+ require 'i18n/core_ext/string/interpolate'
4
11
 
5
12
  module I18n
6
13
  autoload :Backend, 'i18n/backend'
@@ -9,9 +16,6 @@ module I18n
9
16
  autoload :Locale, 'i18n/locale'
10
17
  autoload :Tests, 'i18n/tests'
11
18
 
12
- RESERVED_KEYS = [:scope, :default, :separator, :resolve, :object, :fallback, :format, :cascade, :raise, :rescue_format]
13
- RESERVED_KEYS_PATTERN = /%\{(#{RESERVED_KEYS.join("|")})\}/
14
-
15
19
  class << self
16
20
  # Gets I18n configuration object.
17
21
  def config
@@ -25,7 +29,7 @@ module I18n
25
29
 
26
30
  # Write methods which delegates to the configuration object
27
31
  %w(locale backend default_locale available_locales default_separator
28
- exception_handler load_path).each do |method|
32
+ exception_handler load_path enforce_available_locales).each do |method|
29
33
  module_eval <<-DELEGATORS, __FILE__, __LINE__ + 1
30
34
  def #{method}
31
35
  config.#{method}
@@ -147,6 +151,7 @@ module I18n
147
151
  locale = options.delete(:locale) || config.locale
148
152
  raises = options.delete(:raise)
149
153
 
154
+ enforce_available_locales!(locale)
150
155
  raise I18n::ArgumentError if key.is_a?(String) && key.empty?
151
156
 
152
157
  if key.is_a?(Array)
@@ -222,6 +227,7 @@ module I18n
222
227
  locale = options && options.delete(:locale) || config.locale
223
228
  raises = options && options.delete(:raise)
224
229
  replacement = options && options.delete(:replacement)
230
+ enforce_available_locales!(locale)
225
231
  config.backend.transliterate(locale, key, replacement)
226
232
  rescue I18n::ArgumentError => exception
227
233
  raise exception if raises
@@ -232,6 +238,7 @@ module I18n
232
238
  def localize(object, options = {})
233
239
  locale = options.delete(:locale) || config.locale
234
240
  format = options.delete(:format) || :default
241
+ enforce_available_locales!(locale)
235
242
  config.backend.localize(locale, object, format, options)
236
243
  end
237
244
  alias :l :localize
@@ -260,6 +267,24 @@ module I18n
260
267
  keys
261
268
  end
262
269
 
270
+ # Returns true when the passed locale is in I18.available_locales.
271
+ # Returns false otherwise.
272
+ # Compare with Strings as `locale` may be coming from user input
273
+ def locale_available?(locale)
274
+ I18n.available_locales.map(&:to_s).include?(locale.to_s)
275
+ end
276
+
277
+ # Raises an InvalidLocale exception when the passed locale is not
278
+ # included in I18n.available_locales.
279
+ # Returns false otherwise
280
+ def enforce_available_locales!(locale)
281
+ handle_enforce_available_locales_deprecation
282
+
283
+ if config.enforce_available_locales
284
+ raise I18n::InvalidLocale.new(locale) if !locale_available?(locale)
285
+ end
286
+ end
287
+
263
288
  # making these private until Ruby 1.9.2 can send to protected methods again
264
289
  # see http://redmine.ruby-lang.org/repositories/revision/ruby-19?rev=24280
265
290
  private
@@ -290,6 +315,13 @@ module I18n
290
315
  end
291
316
  end
292
317
 
318
+ def handle_enforce_available_locales_deprecation
319
+ if config.enforce_available_locales.nil? && !@unenforced_available_locales_deprecation
320
+ $stderr.puts "[deprecated] I18n.enforce_available_locales will default to true in the future. If you really want to skip validation of your locale you can set I18n.enforce_available_locales = false to avoid this message."
321
+ @unenforced_available_locales_deprecation = true
322
+ end
323
+ end
324
+
293
325
  def normalize_key(key, separator)
294
326
  normalized_key_cache[separator][key] ||=
295
327
  case key
@@ -5,6 +5,7 @@ module I18n
5
5
  autoload :Cache, 'i18n/backend/cache'
6
6
  autoload :Cascade, 'i18n/backend/cascade'
7
7
  autoload :Chain, 'i18n/backend/chain'
8
+ autoload :Cldr, 'i18n/backend/cldr'
8
9
  autoload :Fallbacks, 'i18n/backend/fallbacks'
9
10
  autoload :Flatten, 'i18n/backend/flatten'
10
11
  autoload :Gettext, 'i18n/backend/gettext'
@@ -1,12 +1,14 @@
1
1
  require 'yaml'
2
2
  require 'i18n/core_ext/hash'
3
- require 'i18n/core_ext/kernel/surpress_warnings'
4
3
 
5
4
  module I18n
6
5
  module Backend
7
6
  module Base
8
7
  include I18n::Backend::Transliterator
9
8
 
9
+ RESERVED_KEYS = [:scope, :default, :separator, :resolve, :object, :fallback]
10
+ RESERVED_KEYS_PATTERN = /%\{(#{RESERVED_KEYS.join("|")})\}/
11
+
10
12
  # Accepts a list of paths to translation files. Loads translations from
11
13
  # plain Ruby (*.rb) or YAML files (*.yml). See #load_rb and #load_yml
12
14
  # for details.
@@ -138,14 +140,34 @@ module I18n
138
140
  #
139
141
  # interpolate "file %{file} opened by %%{user}", :file => 'test.txt', :user => 'Mr. X'
140
142
  # # => "file test.txt opened by %{user}"
143
+ #
144
+ # Note that you have to double escape the <tt>\\</tt> when you want to escape
145
+ # the <tt>{{...}}</tt> key in a string (once for the string and once for the
146
+ # interpolation).
141
147
  def interpolate(locale, string, values = {})
142
- if string.is_a?(::String) && !values.empty?
143
- I18n.interpolate(string, values)
148
+ return string unless string.is_a?(::String) && !values.empty?
149
+
150
+ values.each do |key, value|
151
+ value = value.call(values) if interpolate_lambda?(value, string, key)
152
+ value = value.to_s unless value.is_a?(::String)
153
+ values[key] = value
154
+ end
155
+
156
+ string % values
157
+ rescue KeyError => e
158
+ if string =~ RESERVED_KEYS_PATTERN
159
+ raise ReservedInterpolationKey.new($1.to_sym, string)
144
160
  else
145
- string
161
+ raise MissingInterpolationArgument.new(values, string)
146
162
  end
147
163
  end
148
164
 
165
+ # returns true when the given value responds to :call and the key is
166
+ # an interpolation placeholder in the given string
167
+ def interpolate_lambda?(object, string, key)
168
+ object.respond_to?(:call) && string =~ /%\{#{key}\}|%\<#{key}>.*?\d*\.?\d*[bBdiouxXeEfgGcps]\}/
169
+ end
170
+
149
171
  # Loads a single translations file by delegating to #load_rb or
150
172
  # #load_yml depending on the file extension and directly merges the
151
173
  # data to the existing translations. Raises I18n::UnknownFileType
@@ -169,6 +191,12 @@ module I18n
169
191
  def load_yml(filename)
170
192
  YAML.load_file(filename)
171
193
  end
194
+
195
+ def warn_syntax_deprecation!(locale, string) #:nodoc:
196
+ return if @skip_syntax_deprecation
197
+ warn "The {{key}} interpolation syntax in I18n messages is deprecated. Please use %{key} instead.\n#{locale} - #{string}\n"
198
+ @skip_syntax_deprecation = true
199
+ end
172
200
  end
173
201
  end
174
202
  end
@@ -0,0 +1,99 @@
1
+ require 'cldr'
2
+
3
+ module I18n
4
+ module Backend
5
+ module Cldr
6
+ include ::Cldr::Format
7
+
8
+ def localize(locale, object, format = :default, options = {})
9
+ options[:as] ||= detect_type(object, options)
10
+ send(:"format_#{options[:as]}", locale, object, format, options)
11
+ end
12
+
13
+ def format_decimal(locale, object, format = :default, options = {})
14
+ formatter(locale, :decimal, format).apply(object, options)
15
+ end
16
+
17
+ def format_integer(locale, object, format = :default, options = {})
18
+ format_object(number, options.merge(:precision => 0))
19
+ end
20
+
21
+ def format_currency(locale, object, format = :default, options = {})
22
+ options.merge!(:currency => lookup_currency(locale, options[:currency], object)) if options[:currency].is_a?(Symbol)
23
+ formatter(locale, :currency, format).apply(object, options)
24
+ end
25
+
26
+ def format_percent(locale, object, format = :default, options = {})
27
+ formatter(locale, :percent, format).apply(object, options)
28
+ end
29
+
30
+ def format_date(locale, object, format = :default, options = {})
31
+ formatter(locale, :date, format).apply(object, options)
32
+ end
33
+
34
+ def format_time(locale, object, format = :default, options = {})
35
+ formatter(locale, :time, format).apply(object, options)
36
+ end
37
+
38
+ def format_datetime(locale, object, format = :default, options = {})
39
+ key = :"calendars.gregorian.formats.datetime.#{format}.pattern"
40
+ date = I18n.l(object, :format => options[:date_format] || format, :locale => locale, :as => :date)
41
+ time = I18n.l(object, :format => options[:time_format] || format, :locale => locale, :as => :time)
42
+ I18n.t(key, :date => date, :time => time, :locale => locale, :raise => true)
43
+ end
44
+
45
+ protected
46
+
47
+ def detect_type(object, options)
48
+ options.has_key?(:currency) ? :currency : case object
49
+ when ::Numeric
50
+ :decimal
51
+ when ::Date, ::DateTime, ::Time
52
+ object.class.name.downcase.to_sym
53
+ else
54
+ raise_unspecified_format_type!
55
+ end
56
+ end
57
+
58
+ def formatter(locale, type, format)
59
+ (@formatters ||= {})[:"#{locale}.#{type}.#{format}"] ||= begin
60
+ format = lookup_format(locale, type, format)
61
+ data = lookup_format_data(locale, type)
62
+ ::Cldr::Format.const_get(type.to_s.camelize).new(format, data)
63
+ end
64
+ end
65
+
66
+ def lookup_format(locale, type, format)
67
+ key = case type
68
+ when :date, :time, :datetime
69
+ :"calendars.gregorian.formats.#{type}.#{format}.pattern"
70
+ else
71
+ :"numbers.formats.#{type}.patterns.#{format || :default}"
72
+ end
73
+ I18n.t(key, :locale => locale, :raise => true)
74
+ end
75
+
76
+ def lookup_format_data(locale, type)
77
+ key = case type
78
+ when :date, :time, :datetime
79
+ :'calendars.gregorian'
80
+ else
81
+ :'numbers.symbols'
82
+ end
83
+ I18n.t(key, :locale => locale, :raise => true)
84
+ end
85
+
86
+ def lookup_currency(locale, currency, count)
87
+ I18n.t(:"currencies.#{currency}", :locale => locale, :count => count)
88
+ end
89
+
90
+ def raise_unspecified_format_type!
91
+ raise ArgumentError.new("You have to specify a format type, e.g. :as => :number.")
92
+ end
93
+
94
+ def raise_unspecified_currency!
95
+ raise ArgumentError.new("You have to specify a currency, e.g. :currency => 'EUR'.")
96
+ end
97
+ end
98
+ end
99
+ end
@@ -43,8 +43,8 @@ module I18n
43
43
  key, value = normalize_pluralization(locale, key, value) if key.index("\000")
44
44
 
45
45
  parts = key.split('|').reverse
46
- normalized = parts.inject({}) do |_normalized, part|
47
- { part => _normalized.empty? ? value : _normalized }
46
+ normalized = parts.inject({}) do |normalized, part|
47
+ normalized = { part => normalized.empty? ? value : normalized }
48
48
  end
49
49
 
50
50
  result.deep_merge!(normalized)
@@ -62,7 +62,7 @@ module I18n
62
62
  raise "invalid number of plurals: #{values.size}, keys: #{keys.inspect}" if values.size != keys.size
63
63
 
64
64
  result = {}
65
- values.each_with_index { |_value, ix| result[keys[ix]] = _value }
65
+ values.each_with_index { |value, ix| result[keys[ix]] = value }
66
66
  [key, result]
67
67
  end
68
68
 
@@ -61,7 +61,7 @@ module I18n
61
61
 
62
62
  def interpolate_or_raise_missing(key)
63
63
  escaped_key = escape_key_sym(key)
64
- RESERVED_KEYS.include?(key) ? reserved_key(escaped_key) : interpolate_key(escaped_key)
64
+ Base::RESERVED_KEYS.include?(key) ? reserved_key(escaped_key) : interpolate_key(escaped_key)
65
65
  end
66
66
 
67
67
  def interpolate_key(key)
@@ -38,7 +38,7 @@ module I18n
38
38
  :scope => options[:scope],
39
39
  :default => options[:default],
40
40
  :separator => options[:separator],
41
- :values => options.reject { |name, value| RESERVED_KEYS.include?(name) }
41
+ :values => options.reject { |name, value| Base::RESERVED_KEYS.include?(name) }
42
42
  }
43
43
  with_metadata(metadata) { super }
44
44
  end
@@ -8,6 +8,7 @@ module I18n
8
8
 
9
9
  # Sets the current locale pseudo-globally, i.e. in the Thread.current hash.
10
10
  def locale=(locale)
11
+ I18n.enforce_available_locales!(locale)
11
12
  @locale = locale.to_sym rescue nil
12
13
  end
13
14
 
@@ -28,6 +29,7 @@ module I18n
28
29
 
29
30
  # Sets the current default locale. Used to set a custom default locale.
30
31
  def default_locale=(locale)
32
+ I18n.enforce_available_locales!(locale)
31
33
  @@default_locale = locale.to_sym rescue nil
32
34
  end
33
35
 
@@ -41,7 +43,7 @@ module I18n
41
43
 
42
44
  # Sets the available locales.
43
45
  def available_locales=(locales)
44
- @@available_locales = Array(locales).map { |locale| locale.to_sym }
46
+ @@available_locales = Array(locales).map {|locale| locale.to_sym}
45
47
  @@available_locales = nil if @@available_locales.empty?
46
48
  end
47
49
 
@@ -82,5 +84,15 @@ module I18n
82
84
  def load_path=(load_path)
83
85
  @@load_path = load_path
84
86
  end
87
+
88
+ # [Deprecated] this will default to true in the future
89
+ # Defaults to nil so that it triggers the deprecation warning
90
+ def enforce_available_locales
91
+ @@enforce_available_locales ||= nil
92
+ end
93
+
94
+ def enforce_available_locales=(enforce_available_locales)
95
+ @@enforce_available_locales = enforce_available_locales
96
+ end
85
97
  end
86
98
  end
@@ -1,12 +1,3 @@
1
- # This backports the Ruby 1.9 String interpolation syntax to Ruby 1.8.
2
- #
3
- # This backport has been shipped with I18n for a number of versions. Meanwhile
4
- # Rails has started to rely on it and we are going to move it to ActiveSupport.
5
- # See https://rails.lighthouseapp.com/projects/8994/tickets/6013-move-19-string-interpolation-syntax-backport-from-i18n-to-activesupport
6
- #
7
- # Once the above patch has been applied to Rails the following code will be
8
- # removed from I18n.
9
-
10
1
  =begin
11
2
  heavily based on Masao Mutoh's gettext String interpolation extension
12
3
  http://github.com/mutoh/gettext/blob/f6566738b981fe0952548c421042ad1e0cdfb31e/lib/gettext/core_ext/string.rb
@@ -1,3 +1,11 @@
1
+ require 'cgi'
2
+
3
+ class KeyError < IndexError
4
+ def initialize(message = nil)
5
+ super(message || "key not found")
6
+ end
7
+ end unless defined?(KeyError)
8
+
1
9
  module I18n
2
10
  # Handles exceptions raised in the backend. All exceptions except for
3
11
  # MissingTranslationData exceptions are re-raised. When a MissingTranslationData
@@ -7,7 +15,19 @@ module I18n
7
15
  include Module.new {
8
16
  def call(exception, locale, key, options)
9
17
  if exception.is_a?(MissingTranslationData)
10
- options[:rescue_format] == :html ? exception.html_message : exception.message
18
+ #
19
+ # TODO: this block is to be replaced by `exception.message` when
20
+ # rescue_format is removed
21
+ if options[:rescue_format] == :html
22
+ if @rescue_format_deprecation
23
+ $stderr.puts "[DEPRECATED] I18n's :recue_format option will be removed from a future release. All exception messages will be plain text. If you need the exception handler to return an html format please set or pass a custom exception handler."
24
+ @rescue_format_deprecation = true
25
+ end
26
+ exception.html_message
27
+ else
28
+ exception.message
29
+ end
30
+
11
31
  else
12
32
  raise exception
13
33
  end
@@ -43,8 +63,9 @@ module I18n
43
63
  end
44
64
 
45
65
  def html_message
46
- key = keys.last.to_s.gsub('_', ' ').gsub(/\b('?[a-z])/) { $1.capitalize }
47
- %(<span class="translation_missing" title="translation missing: #{keys.join('.')}">#{key}</span>)
66
+ key = CGI.escapeHTML titleize(keys.last)
67
+ path = CGI.escapeHTML keys.join('.')
68
+ %(<span class="translation_missing" title="translation missing: #{path}">#{key}</span>)
48
69
  end
49
70
 
50
71
  def keys
@@ -52,6 +73,13 @@ module I18n
52
73
  keys << 'no key' if keys.size < 2
53
74
  end
54
75
  end
76
+
77
+ protected
78
+
79
+ # TODO : remove when #html_message is removed
80
+ def titleize(key)
81
+ key.to_s.gsub('_', ' ').gsub(/\b('?[a-z])/) { $1.capitalize }
82
+ end
55
83
  end
56
84
 
57
85
  class InvalidPluralizationData < ArgumentError