Skip to content

Commit fee61e3

Browse files
authored
Merge pull request rails#43415 from rails/rm-translate-on-controllers
Treat html suffix in controller translation
2 parents 5e2ce5c + 28bc4f8 commit fee61e3

File tree

5 files changed

+96
-38
lines changed

5 files changed

+96
-38
lines changed

actionpack/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
* Treat html suffix in controller translation.
2+
3+
*Rui Onodera*, *Gavin Miller*
4+
15
* Allow permitting numeric params.
26

37
Previously it was impossible to permit different fields on numeric parameters.

actionpack/lib/abstract_controller/translation.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# frozen_string_literal: true
22

3+
require "active_support/html_safe_translation"
4+
35
module AbstractController
46
module Translation
57
mattr_accessor :raise_on_missing_translations, default: false
@@ -22,7 +24,8 @@ def translate(key, **options)
2224
end
2325

2426
i18n_raise = options.fetch(:raise, self.raise_on_missing_translations)
25-
I18n.translate(key, **options, raise: i18n_raise)
27+
28+
ActiveSupport::HtmlSafeTranslation.translate(key, **options, raise: i18n_raise)
2629
end
2730
alias :t :translate
2831

actionpack/test/abstract/translation_test.rb

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ def setup
2020
translation: {
2121
index: {
2222
foo: "bar",
23+
hello: "<a>Hello World</a>",
24+
hello_html: "<a>Hello World</a>",
25+
interpolated_html: "<a>Hello %{word}</a>",
26+
nested: { html: "<a>nested</a>" }
2327
},
2428
no_action: "no_action_tr",
2529
},
@@ -95,6 +99,43 @@ def test_localize
9599
assert_equal expected, @controller.l(time)
96100
end
97101
end
102+
103+
def test_translate_does_not_mark_plain_text_as_safe_html
104+
@controller.stub :action_name, :index do
105+
translation = @controller.t(".hello")
106+
assert_equal "<a>Hello World</a>", translation
107+
assert_equal false, translation.html_safe?
108+
end
109+
end
110+
111+
def test_translate_marks_translations_with_a_html_suffix_as_safe_html
112+
@controller.stub :action_name, :index do
113+
translation = @controller.t(".hello_html")
114+
assert_equal "<a>Hello World</a>", translation
115+
assert_equal true, translation.html_safe?
116+
end
117+
end
118+
119+
def test_translate_marks_translation_with_nested_html_key
120+
@controller.stub :action_name, :index do
121+
translation = @controller.t(".nested.html")
122+
assert_equal "<a>nested</a>", translation
123+
assert_equal true, translation.html_safe?
124+
end
125+
end
126+
127+
def test_translate_escapes_interpolations_in_translations_with_a_html_suffix
128+
word_struct = Struct.new(:to_s)
129+
@controller.stub :action_name, :index do
130+
translation = @controller.t(".interpolated_html", word: "<World>")
131+
assert_equal "<a>Hello &lt;World&gt;</a>", translation
132+
assert_equal true, translation.html_safe?
133+
134+
translation = @controller.t(".interpolated_html", word: word_struct.new("<World>"))
135+
assert_equal "<a>Hello &lt;World&gt;</a>", translation
136+
assert_equal true, translation.html_safe?
137+
end
138+
end
98139
end
99140
end
100141
end

actionview/lib/action_view/helpers/translation_helper.rb

Lines changed: 4 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22

33
require "action_view/helpers/tag_helper"
4+
require "active_support/html_safe_translation"
45

56
module ActionView
67
# = Action View Translation Helpers
@@ -84,14 +85,9 @@ def translate(key, **options)
8485

8586
key = scope_key_by_partial(key)
8687

87-
if html_safe_translation_key?(key)
88-
html_safe_options ||= html_escape_translation_options(options)
89-
translated = I18n.translate(key, **html_safe_options, default: default)
90-
break html_safe_translation(translated) unless translated.equal?(MISSING_TRANSLATION)
91-
else
92-
translated = I18n.translate(key, **options, default: default)
93-
break translated unless translated.equal?(MISSING_TRANSLATION)
94-
end
88+
translated = ActiveSupport::HtmlSafeTranslation.translate(key, **options, default: default)
89+
90+
break translated unless translated.equal?(MISSING_TRANSLATION)
9591

9692
if alternatives.present? && !alternatives.first.is_a?(Symbol)
9793
break alternatives.first && I18n.translate(**options, default: alternatives)
@@ -126,10 +122,6 @@ def localize(object, **options)
126122
NO_DEFAULT = [].freeze
127123
private_constant :NO_DEFAULT
128124

129-
def self.i18n_option?(name)
130-
(@i18n_option_names ||= I18n::RESERVED_KEYS.to_set).include?(name)
131-
end
132-
133125
def scope_key_by_partial(key)
134126
if key&.start_with?(".")
135127
if @virtual_path
@@ -144,31 +136,6 @@ def scope_key_by_partial(key)
144136
end
145137
end
146138

147-
def html_escape_translation_options(options)
148-
return options if options.empty?
149-
html_safe_options = options.dup
150-
151-
options.each do |name, value|
152-
unless TranslationHelper.i18n_option?(name) || (name == :count && value.is_a?(Numeric))
153-
html_safe_options[name] = ERB::Util.html_escape(value.to_s)
154-
end
155-
end
156-
157-
html_safe_options
158-
end
159-
160-
def html_safe_translation_key?(key)
161-
/(?:_|\b)html\z/.match?(key)
162-
end
163-
164-
def html_safe_translation(translation)
165-
if translation.respond_to?(:map)
166-
translation.map { |element| element.respond_to?(:html_safe) ? element.html_safe : element }
167-
else
168-
translation.respond_to?(:html_safe) ? translation.html_safe : translation
169-
end
170-
end
171-
172139
def missing_translation(key, options)
173140
keys = I18n.normalize_keys(options[:locale] || I18n.locale, key, options[:scope])
174141

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# frozen_string_literal: true
2+
3+
module ActiveSupport
4+
module HtmlSafeTranslation # :nodoc:
5+
extend self
6+
7+
def translate(key, **options)
8+
if html_safe_translation_key?(key)
9+
html_safe_options = html_escape_translation_options(options)
10+
translation = I18n.translate(key, **html_safe_options)
11+
html_safe_translation(translation)
12+
else
13+
I18n.translate(key, **options)
14+
end
15+
end
16+
17+
private
18+
def html_safe_translation_key?(key)
19+
/(?:_|\b)html\z/.match?(key)
20+
end
21+
22+
def html_escape_translation_options(options)
23+
options.each do |name, value|
24+
unless i18n_option?(name) || (name == :count && value.is_a?(Numeric))
25+
options[name] = ERB::Util.html_escape(value.to_s)
26+
end
27+
end
28+
end
29+
30+
def i18n_option?(name)
31+
(@i18n_option_names ||= I18n::RESERVED_KEYS.to_set).include?(name)
32+
end
33+
34+
35+
def html_safe_translation(translation)
36+
if translation.respond_to?(:map)
37+
translation.map { |element| element.respond_to?(:html_safe) ? element.html_safe : element }
38+
else
39+
translation.respond_to?(:html_safe) ? translation.html_safe : translation
40+
end
41+
end
42+
end
43+
end

0 commit comments

Comments
 (0)