Skip to content
This repository was archived by the owner on Dec 28, 2023. It is now read-only.

Commit c329ea2

Browse files
committed
Custom handler for MissingInterpolationArgument exception
1 parent 53764cc commit c329ea2

10 files changed

+138
-2
lines changed

lib/i18n.rb

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require 'i18n/version'
22
require 'i18n/exceptions'
33
require 'i18n/interpolate/ruby'
4+
require 'i18n/interpolate/missing_interpolation_argument_handler'
45

56
module I18n
67
autoload :Backend, 'i18n/backend'

lib/i18n/backend/interpolation_compiler.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def nil_key(key)
7777
end
7878

7979
def missing_key(key)
80-
"raise(MissingInterpolationArgument.new(#{key}, {}, self))"
80+
"I18n.config.missing_interpolation_argument_handler.call(#{key}, v, self)"
8181
end
8282

8383
def reserved_key(key)

lib/i18n/config.rb

+20
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,26 @@ def exception_handler=(exception_handler)
6565
@@exception_handler = exception_handler
6666
end
6767

68+
# Return the current handler for situations when interpolation argument
69+
# is missing. Defaults to MissingInterpolationArgumentHandler, which
70+
# raises an exception.
71+
def missing_interpolation_argument_handler
72+
@@missing_interpolation_argument_handler ||= MissingInterpolationArgumentHandler.new
73+
end
74+
75+
# Sets the missing interpolation argument handler. It can be any
76+
# object that responds to #call.
77+
#
78+
# == Example:
79+
# You can supress raising an exception by reassigning default handler
80+
# with options:
81+
#
82+
# I18n.config.missing_interpolation_argument_handler =
83+
# MissingInterpolationArgumentHandler.new(raise_exception: false)
84+
def missing_interpolation_argument_handler=(exception_handler)
85+
@@missing_interpolation_argument_handler = exception_handler
86+
end
87+
6888
# Allow clients to register paths providing translation data sources. The
6989
# backend defines acceptable sources.
7090
#

lib/i18n/exceptions.rb

+4
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ def initialize(key, values, string)
8686
@key, @values, @string = key, values, string
8787
super "missing interpolation argument #{key.inspect} in #{string.inspect} (#{values.inspect} given)"
8888
end
89+
90+
def html_message
91+
%(<span class='interpolation_missing' title='#{message}'>%{#{key}}</span>)
92+
end
8993
end
9094

9195
class ReservedInterpolationKey < ArgumentError
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
module I18n
2+
# It is responsible for raising an exception or providing default value in case
3+
# of missing interpolation argument.
4+
class MissingInterpolationArgumentHandler
5+
module Implementation
6+
attr_reader :defaults
7+
8+
# == Options:
9+
# * :raise_exception - (default is true) Defines whether MissingInterpolationArgument
10+
# should be raised or exception message should be returned instead.
11+
# * :rescue_format - (:html (default), :text) Defines in what format exception message
12+
# should be returned. Ignored if :raise_exception is true.
13+
def initialize(options = {})
14+
defaults = {:raise_exception => true, :rescue_format => :html}
15+
@defaults = defaults.merge(options)
16+
end
17+
18+
# Return String or raise MissingInterpolationArgument exception.
19+
def call(missing_key, provided_hash, interpolated_string)
20+
exception = MissingInterpolationArgument.new(missing_key, provided_hash, interpolated_string)
21+
if defaults[:raise_exception]
22+
raise exception
23+
else
24+
defaults[:rescue_format] == :html ? exception.html_message : "<#{exception.message}>"
25+
end
26+
end
27+
end
28+
include Implementation
29+
end
30+
end

lib/i18n/interpolate/ruby.rb

+7-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ module I18n
99
)
1010

1111
class << self
12+
# Return String or raises MissingInterpolationArgument exception.
13+
# Missing argument's logic is handled by I18n.config.missing_interpolation_argument_handler.
1214
def interpolate(string, values)
1315
raise ReservedInterpolationKey.new($1.to_sym, string) if string =~ RESERVED_KEYS_PATTERN
1416
raise ArgumentError.new('Interpolation values must be a Hash.') unless values.kind_of?(Hash)
@@ -21,7 +23,11 @@ def interpolate_hash(string, values)
2123
'%'
2224
else
2325
key = ($1 || $2).to_sym
24-
value = values.key?(key) ? values[key] : raise(MissingInterpolationArgument.new(key, values, string))
26+
value = if values.key?(key)
27+
values[key]
28+
else
29+
config.missing_interpolation_argument_handler.call(key, values, string)
30+
end
2531
value = value.call(values) if value.respond_to?(:call)
2632
$3 ? sprintf("%#{$3}", value) : value
2733
end

test/backend/exceptions_test.rb

+10
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,14 @@ def setup
2727
end
2828
assert_equal "translation missing: en.time.formats.foo", exception.message
2929
end
30+
31+
test "exceptions: MissingInterpolationArgument message includes missing key, provided keys and full string" do
32+
exception = I18n::MissingInterpolationArgument.new('key', {:this => 'was given'}, 'string')
33+
assert_equal 'missing interpolation argument "key" in "string" ({:this=>"was given"} given)', exception.message
34+
end
35+
36+
test "exceptions: MissingInterpolationArgument html message includes missing key, provided keys and full string" do
37+
exception = I18n::MissingInterpolationArgument.new('key', {:this => 'was given'}, 'string')
38+
assert_equal %|<span class='interpolation_missing' title='missing interpolation argument "key" in "string" ({:this=>"was given"} given)'>%{key}</span>|, exception.html_message
39+
end
3040
end

test/backend/interpolation_compiler_test.rb

+17
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,23 @@ def test_handles_weird_strings
7676
assert_equal '\";eval("a")', compile_and_interpolate('\";eval("a")%{a}', :a => '' )
7777
assert_equal "\na", compile_and_interpolate("\n%{a}", :a => 'a')
7878
end
79+
80+
def test_raises_exception_when_argument_is_missing
81+
assert_raise(I18n::MissingInterpolationArgument) do
82+
compile_and_interpolate('%{first} %{last}', :first => 'first')
83+
end
84+
end
85+
86+
def test_custom_missing_interpolation_argument_handler
87+
old_handler = I18n.config.missing_interpolation_argument_handler
88+
I18n.config.missing_interpolation_argument_handler = lambda do |key, values, string|
89+
"missing key is #{key}, values are #{values.inspect}, given string is '#{string}'"
90+
end
91+
assert_equal %|first missing key is last, values are {:first=>"first"}, given string is '%{first} %{last}'|,
92+
compile_and_interpolate('%{first} %{last}', :first => 'first')
93+
ensure
94+
I18n.config.missing_interpolation_argument_handler = old_handler
95+
end
7996
end
8097

8198
class I18nBackendInterpolationCompilerTest < Test::Unit::TestCase

test/i18n/interpolate_test.rb

+18
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,21 @@ def test_sprintf_mix_unformatted_and_formatted_named_placeholders
5959
assert_equal "foo 1.000000", I18n.interpolate("%{name} %<num>f", :name => "foo", :num => 1.0)
6060
end
6161
end
62+
63+
class I18nMissingInterpolationCustomHandlerTest < Test::Unit::TestCase
64+
def setup
65+
@old_handler = I18n.config.missing_interpolation_argument_handler
66+
I18n.config.missing_interpolation_argument_handler = lambda do |key, values, string|
67+
"missing key is #{key}, values are #{values.inspect}, given string is '#{string}'"
68+
end
69+
end
70+
71+
def teardown
72+
I18n.config.missing_interpolation_argument_handler = @old_handler
73+
end
74+
75+
test "String interpolation can use custom missing interpolation handler" do
76+
assert_equal %|Masao missing key is last, values are {:first=>"Masao"}, given string is '%{first} %{last}'|,
77+
I18n.interpolate("%{first} %{last}", :first => 'Masao')
78+
end
79+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
require 'test_helper'
2+
3+
class I18nMissingInterpolationHandlerTest < Test::Unit::TestCase
4+
test "#call raises an exception by default" do
5+
subject = I18n::MissingInterpolationArgumentHandler.new
6+
assert_raise(I18n::MissingInterpolationArgument) { subject.call(1, 2, 3) }
7+
end
8+
9+
def mock_exception(method_name, return_value)
10+
mock('exception').tap do |e|
11+
e.expects(method_name).returns(return_value)
12+
end
13+
end
14+
15+
test "#call returns html message when :raise_exception option is false" do
16+
exception = mock_exception(:html_message, 'missing!')
17+
I18n::MissingInterpolationArgument.expects(:new).with(1, 2, 3).returns(exception)
18+
19+
subject = I18n::MissingInterpolationArgumentHandler.new(:raise_exception => false)
20+
assert_equal 'missing!', subject.call(1, 2, 3)
21+
end
22+
23+
test "#call returns plain text message when :rescue_format option is :text" do
24+
exception = mock_exception(:message, 'missing!')
25+
I18n::MissingInterpolationArgument.expects(:new).with(1, 2, 3).returns(exception)
26+
27+
subject = I18n::MissingInterpolationArgumentHandler.new(:raise_exception => false, :rescue_format => :text)
28+
assert_equal '<missing!>', subject.call(1, 2, 3)
29+
end
30+
end

0 commit comments

Comments
 (0)