Browse files

Custom handler for MissingInterpolationArgument exception

  • Loading branch information...
1 parent 53764cc commit c329ea2de319c817d3d7082c851eb6299663b80e @nashbridges nashbridges committed Mar 21, 2013
View
1 lib/i18n.rb
@@ -1,6 +1,7 @@
require 'i18n/version'
require 'i18n/exceptions'
require 'i18n/interpolate/ruby'
+require 'i18n/interpolate/missing_interpolation_argument_handler'
module I18n
autoload :Backend, 'i18n/backend'
View
2 lib/i18n/backend/interpolation_compiler.rb
@@ -77,7 +77,7 @@ def nil_key(key)
end
def missing_key(key)
- "raise(MissingInterpolationArgument.new(#{key}, {}, self))"
+ "I18n.config.missing_interpolation_argument_handler.call(#{key}, v, self)"
end
def reserved_key(key)
View
20 lib/i18n/config.rb
@@ -65,6 +65,26 @@ def exception_handler=(exception_handler)
@@exception_handler = exception_handler
end
+ # Return the current handler for situations when interpolation argument
+ # is missing. Defaults to MissingInterpolationArgumentHandler, which
+ # raises an exception.
+ def missing_interpolation_argument_handler
+ @@missing_interpolation_argument_handler ||= MissingInterpolationArgumentHandler.new
+ end
+
+ # Sets the missing interpolation argument handler. It can be any
+ # object that responds to #call.
+ #
+ # == Example:
+ # You can supress raising an exception by reassigning default handler
+ # with options:
+ #
+ # I18n.config.missing_interpolation_argument_handler =
+ # MissingInterpolationArgumentHandler.new(raise_exception: false)
+ def missing_interpolation_argument_handler=(exception_handler)
+ @@missing_interpolation_argument_handler = exception_handler
+ end
+
# Allow clients to register paths providing translation data sources. The
# backend defines acceptable sources.
#
View
4 lib/i18n/exceptions.rb
@@ -86,6 +86,10 @@ def initialize(key, values, string)
@key, @values, @string = key, values, string
super "missing interpolation argument #{key.inspect} in #{string.inspect} (#{values.inspect} given)"
end
+
+ def html_message
@josevalim
Collaborator

Why? I don't feel like an html_message should be part of I18n business.

html_message had been already introduced in the MissingTranslation, so I followed current implementation. However, looking back at my code, I understand that there was no need to introduce complex MissingInterpolationArgumentHandler at all, just provide default lambda which will raise MissingInterpolationArgument. One could replace it with his own implementation. How about that?

@josevalim
Collaborator

That sounds perfect to me. Thanks @nashbridges for following up.

ok, just give me some time, I'll made PR for this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ %(<span class='interpolation_missing' title='#{message}'>%{#{key}}</span>)
+ end
end
class ReservedInterpolationKey < ArgumentError
View
30 lib/i18n/interpolate/missing_interpolation_argument_handler.rb
@@ -0,0 +1,30 @@
+module I18n
+ # It is responsible for raising an exception or providing default value in case
+ # of missing interpolation argument.
+ class MissingInterpolationArgumentHandler
+ module Implementation
+ attr_reader :defaults
+
+ # == Options:
+ # * :raise_exception - (default is true) Defines whether MissingInterpolationArgument
+ # should be raised or exception message should be returned instead.
+ # * :rescue_format - (:html (default), :text) Defines in what format exception message
+ # should be returned. Ignored if :raise_exception is true.
+ def initialize(options = {})
+ defaults = {:raise_exception => true, :rescue_format => :html}
+ @defaults = defaults.merge(options)
+ end
+
+ # Return String or raise MissingInterpolationArgument exception.
+ def call(missing_key, provided_hash, interpolated_string)
+ exception = MissingInterpolationArgument.new(missing_key, provided_hash, interpolated_string)
+ if defaults[:raise_exception]
+ raise exception
+ else
+ defaults[:rescue_format] == :html ? exception.html_message : "<#{exception.message}>"
+ end
+ end
+ end
+ include Implementation
+ end
+end
View
8 lib/i18n/interpolate/ruby.rb
@@ -9,6 +9,8 @@ module I18n
)
class << self
+ # Return String or raises MissingInterpolationArgument exception.
+ # Missing argument's logic is handled by I18n.config.missing_interpolation_argument_handler.
def interpolate(string, values)
raise ReservedInterpolationKey.new($1.to_sym, string) if string =~ RESERVED_KEYS_PATTERN
raise ArgumentError.new('Interpolation values must be a Hash.') unless values.kind_of?(Hash)
@@ -21,7 +23,11 @@ def interpolate_hash(string, values)
'%'
else
key = ($1 || $2).to_sym
- value = values.key?(key) ? values[key] : raise(MissingInterpolationArgument.new(key, values, string))
+ value = if values.key?(key)
+ values[key]
+ else
+ config.missing_interpolation_argument_handler.call(key, values, string)
+ end
value = value.call(values) if value.respond_to?(:call)
$3 ? sprintf("%#{$3}", value) : value
end
View
10 test/backend/exceptions_test.rb
@@ -27,4 +27,14 @@ def setup
end
assert_equal "translation missing: en.time.formats.foo", exception.message
end
+
+ test "exceptions: MissingInterpolationArgument message includes missing key, provided keys and full string" do
+ exception = I18n::MissingInterpolationArgument.new('key', {:this => 'was given'}, 'string')
+ assert_equal 'missing interpolation argument "key" in "string" ({:this=>"was given"} given)', exception.message
+ end
+
+ test "exceptions: MissingInterpolationArgument html message includes missing key, provided keys and full string" do
+ exception = I18n::MissingInterpolationArgument.new('key', {:this => 'was given'}, 'string')
+ assert_equal %|<span class='interpolation_missing' title='missing interpolation argument "key" in "string" ({:this=>"was given"} given)'>%{key}</span>|, exception.html_message
+ end
end
View
17 test/backend/interpolation_compiler_test.rb
@@ -76,6 +76,23 @@ def test_handles_weird_strings
assert_equal '\";eval("a")', compile_and_interpolate('\";eval("a")%{a}', :a => '' )
assert_equal "\na", compile_and_interpolate("\n%{a}", :a => 'a')
end
+
+ def test_raises_exception_when_argument_is_missing
+ assert_raise(I18n::MissingInterpolationArgument) do
+ compile_and_interpolate('%{first} %{last}', :first => 'first')
+ end
+ end
+
+ def test_custom_missing_interpolation_argument_handler
+ old_handler = I18n.config.missing_interpolation_argument_handler
+ I18n.config.missing_interpolation_argument_handler = lambda do |key, values, string|
+ "missing key is #{key}, values are #{values.inspect}, given string is '#{string}'"
+ end
+ assert_equal %|first missing key is last, values are {:first=>"first"}, given string is '%{first} %{last}'|,
+ compile_and_interpolate('%{first} %{last}', :first => 'first')
+ ensure
+ I18n.config.missing_interpolation_argument_handler = old_handler
+ end
end
class I18nBackendInterpolationCompilerTest < Test::Unit::TestCase
View
18 test/i18n/interpolate_test.rb
@@ -59,3 +59,21 @@ def test_sprintf_mix_unformatted_and_formatted_named_placeholders
assert_equal "foo 1.000000", I18n.interpolate("%{name} %<num>f", :name => "foo", :num => 1.0)
end
end
+
+class I18nMissingInterpolationCustomHandlerTest < Test::Unit::TestCase
+ def setup
+ @old_handler = I18n.config.missing_interpolation_argument_handler
+ I18n.config.missing_interpolation_argument_handler = lambda do |key, values, string|
+ "missing key is #{key}, values are #{values.inspect}, given string is '#{string}'"
+ end
+ end
+
+ def teardown
+ I18n.config.missing_interpolation_argument_handler = @old_handler
+ end
+
+ test "String interpolation can use custom missing interpolation handler" do
+ assert_equal %|Masao missing key is last, values are {:first=>"Masao"}, given string is '%{first} %{last}'|,
+ I18n.interpolate("%{first} %{last}", :first => 'Masao')
+ end
+end
View
30 test/i18n/missing_interpolate_handler_test.rb
@@ -0,0 +1,30 @@
+require 'test_helper'
+
+class I18nMissingInterpolationHandlerTest < Test::Unit::TestCase
+ test "#call raises an exception by default" do
+ subject = I18n::MissingInterpolationArgumentHandler.new
+ assert_raise(I18n::MissingInterpolationArgument) { subject.call(1, 2, 3) }
+ end
+
+ def mock_exception(method_name, return_value)
+ mock('exception').tap do |e|
+ e.expects(method_name).returns(return_value)
+ end
+ end
+
+ test "#call returns html message when :raise_exception option is false" do
+ exception = mock_exception(:html_message, 'missing!')
+ I18n::MissingInterpolationArgument.expects(:new).with(1, 2, 3).returns(exception)
+
+ subject = I18n::MissingInterpolationArgumentHandler.new(:raise_exception => false)
+ assert_equal 'missing!', subject.call(1, 2, 3)
+ end
+
+ test "#call returns plain text message when :rescue_format option is :text" do
+ exception = mock_exception(:message, 'missing!')
+ I18n::MissingInterpolationArgument.expects(:new).with(1, 2, 3).returns(exception)
+
+ subject = I18n::MissingInterpolationArgumentHandler.new(:raise_exception => false, :rescue_format => :text)
+ assert_equal '<missing!>', subject.call(1, 2, 3)
+ end
+end

0 comments on commit c329ea2

Please sign in to comment.