diff --git a/lib/rails_xss/action_view.rb b/lib/rails_xss/action_view.rb
index 95cb0d4..db22f2a 100644
--- a/lib/rails_xss/action_view.rb
+++ b/lib/rails_xss/action_view.rb
@@ -79,6 +79,50 @@ def link_to(*args, &block)
end
end
end
+
+ module TranslationHelper
+ HTML_SAFE_TRANSLATION_KEY_RE = /(\b|_|\.)html$/
+ ESCAPE_INTERPOLATIONS_RESERVED_KEYS = I18n::Backend::Base::RESERVED_KEYS + [:locale, :raise, :cascade]
+
+ # Replace translate to escape any interpolations when using keys ending
+ # with html. We don't use method chaining because it can't cover edge cases
+ # involving multiple keys.
+ #
+ # @see https://groups.google.com/group/rubyonrails-security/browse_thread/thread/2b61d70fb73c7cc5
+ def translate(keys, options = {})
+ if multiple_keys = keys.is_a?(Array)
+ ActiveSupport::Deprecation.warn "Giving an array to translate is deprecated, please give a symbol or a string instead", caller
+ end
+
+ options[:raise] = true
+ keys = scope_keys_by_partial(keys)
+ html_safe_options = nil
+
+ translations = keys.map do |key|
+ if key.to_s =~ HTML_SAFE_TRANSLATION_KEY_RE
+ unless html_safe_options
+ html_safe_options = options.dup
+ options.except(*ESCAPE_INTERPOLATIONS_RESERVED_KEYS).each do |name, value|
+ html_safe_options[name] = ERB::Util.html_escape(value.to_s)
+ end
+ end
+ I18n.translate(key, html_safe_options).html_safe
+ else
+ I18n.translate(key, options)
+ end
+ end
+
+ if multiple_keys || translations.size > 1
+ translations
+ else
+ translations.first
+ end
+ rescue I18n::MissingTranslationData => e
+ keys = I18n.send(:normalize_translation_keys, e.locale, e.key, e.options[:scope])
+ content_tag('span', keys.join(', '), :class => 'translation_missing')
+ end
+ alias :t :translate
+ end
end
end
diff --git a/test/translation_helper_test.rb b/test/translation_helper_test.rb
new file mode 100644
index 0000000..1c46d8e
--- /dev/null
+++ b/test/translation_helper_test.rb
@@ -0,0 +1,70 @@
+require 'test_helper'
+
+class TranslationHelperTest < ActionView::TestCase
+
+ include ActionView::Helpers::TagHelper
+ include ActionView::Helpers::TranslationHelper
+
+ def setup
+ I18n.backend.store_translations(:en,
+ :translations => {
+ :hello => 'Hello World',
+ :html => 'Hello World',
+ :hello_html => 'Hello World',
+ :interpolated_text => 'Hello %{word}',
+ :interpolated_html => 'Hello %{word}',
+ }
+ )
+ end
+
+ def test_translate_hello
+ assert_equal 'Hello World', translate(:'translations.hello')
+ end
+
+ def test_returns_missing_translation_message_wrapped_into_span
+ expected = 'en, translations, missing'
+ assert_equal expected, translate(:"translations.missing")
+ assert_equal true, translate(:"translations.missing").html_safe?
+ end
+
+ def test_with_array_of_keys_returns_missing_translation_message
+ expected = 'en, translations, missing'
+ assert_deprecated(/Giving an array to translate is deprecated/) do
+ assert_equal expected, translate([:"translations.missing", :"translations.interpolated_text"])
+ end
+ end
+
+ def test_translate_does_not_mark_plain_text_as_safe_html
+ assert !translate(:'translations.hello').html_safe?
+ end
+
+ def test_translate_marks_translations_named_html_as_safe_html
+ assert translate(:'translations.html').html_safe?
+ end
+
+ def test_translate_marks_translations_with_a_html_suffix_as_safe_html
+ assert translate(:'translations.hello_html').html_safe?
+ end
+
+ def test_translate_escapes_interpolations_in_translations_with_a_html_suffix
+ assert_equal 'Hello <World>', translate(:'translations.interpolated_html', :word => '')
+ string_stub = (Struct.new(:to_s)).new; string_stub.to_s = ''
+ assert_equal 'Hello <World>', translate(:'translations.interpolated_html', :word => string_stub)
+ end
+
+ def test_t_escapes_interpolations_in_translations_with_a_html_suffix
+ assert_equal 'Hello <World>', t(:'translations.interpolated_html', :word => '')
+ end
+
+ def test_translate_does_not_escape_interpolations_in_translations_without_a_html_suffix
+ assert_equal 'Hello ', translate(:'translations.interpolated_text', :word => '')
+ end
+
+ def test_translate_escapes_interpolations_with_multiple_keys
+ assert_deprecated(/Giving an array to translate is deprecated/) do
+ assert_equal ['Hello <World>', 'Hello '],
+ translate([:'translations.interpolated_html', :'translations.interpolated_text'], :word => '')
+ end
+ end
+
+end