actionview 6.0.3.3 → 6.1.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionview might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +177 -208
- data/MIT-LICENSE +1 -1
- data/lib/action_view.rb +4 -1
- data/lib/action_view/base.rb +19 -50
- data/lib/action_view/cache_expiry.rb +1 -2
- data/lib/action_view/dependency_tracker.rb +10 -4
- data/lib/action_view/digestor.rb +3 -2
- data/lib/action_view/gem_version.rb +3 -3
- data/lib/action_view/helpers/asset_tag_helper.rb +55 -15
- data/lib/action_view/helpers/asset_url_helper.rb +6 -4
- data/lib/action_view/helpers/atom_feed_helper.rb +2 -1
- data/lib/action_view/helpers/cache_helper.rb +10 -16
- data/lib/action_view/helpers/date_helper.rb +4 -4
- data/lib/action_view/helpers/form_helper.rb +66 -30
- data/lib/action_view/helpers/form_options_helper.rb +7 -16
- data/lib/action_view/helpers/form_tag_helper.rb +7 -7
- data/lib/action_view/helpers/javascript_helper.rb +3 -3
- data/lib/action_view/helpers/number_helper.rb +6 -6
- data/lib/action_view/helpers/rendering_helper.rb +11 -3
- data/lib/action_view/helpers/sanitize_helper.rb +2 -2
- data/lib/action_view/helpers/tag_helper.rb +92 -17
- data/lib/action_view/helpers/tags/base.rb +9 -5
- data/lib/action_view/helpers/tags/date_field.rb +1 -1
- data/lib/action_view/helpers/tags/date_select.rb +2 -2
- data/lib/action_view/helpers/tags/datetime_local_field.rb +1 -1
- data/lib/action_view/helpers/tags/label.rb +4 -0
- data/lib/action_view/helpers/tags/month_field.rb +1 -1
- data/lib/action_view/helpers/tags/select.rb +1 -1
- data/lib/action_view/helpers/tags/time_field.rb +1 -1
- data/lib/action_view/helpers/tags/week_field.rb +1 -1
- data/lib/action_view/helpers/text_helper.rb +1 -1
- data/lib/action_view/helpers/translation_helper.rb +87 -51
- data/lib/action_view/helpers/url_helper.rb +107 -13
- data/lib/action_view/layouts.rb +3 -2
- data/lib/action_view/log_subscriber.rb +26 -10
- data/lib/action_view/lookup_context.rb +3 -18
- data/lib/action_view/path_set.rb +0 -3
- data/lib/action_view/railtie.rb +39 -46
- data/lib/action_view/renderer/abstract_renderer.rb +93 -14
- data/lib/action_view/renderer/collection_renderer.rb +196 -0
- data/lib/action_view/renderer/object_renderer.rb +34 -0
- data/lib/action_view/renderer/partial_renderer.rb +20 -282
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +25 -26
- data/lib/action_view/renderer/renderer.rb +44 -1
- data/lib/action_view/renderer/streaming_template_renderer.rb +5 -1
- data/lib/action_view/renderer/template_renderer.rb +15 -12
- data/lib/action_view/rendering.rb +3 -1
- data/lib/action_view/routing_url_for.rb +1 -1
- data/lib/action_view/template.rb +9 -49
- data/lib/action_view/template/handlers.rb +0 -26
- data/lib/action_view/template/handlers/erb.rb +10 -14
- data/lib/action_view/template/handlers/erb/erubi.rb +9 -7
- data/lib/action_view/template/html.rb +1 -11
- data/lib/action_view/template/raw_file.rb +0 -3
- data/lib/action_view/template/renderable.rb +24 -0
- data/lib/action_view/template/resolver.rb +82 -40
- data/lib/action_view/template/text.rb +0 -3
- data/lib/action_view/test_case.rb +18 -25
- data/lib/action_view/testing/resolvers.rb +10 -31
- data/lib/action_view/unbound_template.rb +3 -3
- data/lib/action_view/view_paths.rb +34 -36
- metadata +18 -15
@@ -253,7 +253,7 @@ module ActionView
|
|
253
253
|
end
|
254
254
|
|
255
255
|
# Formats the bytes in +number+ into a more understandable
|
256
|
-
# representation (e.g., giving it 1500 yields 1.
|
256
|
+
# representation (e.g., giving it 1500 yields 1.46 KB). This
|
257
257
|
# method is useful for reporting file sizes to users. You can
|
258
258
|
# customize the format in the +options+ hash.
|
259
259
|
#
|
@@ -299,7 +299,7 @@ module ActionView
|
|
299
299
|
end
|
300
300
|
|
301
301
|
# Pretty prints (formats and approximates) a number in a way it
|
302
|
-
# is more readable by humans (
|
302
|
+
# is more readable by humans (e.g.: 1200000000 becomes "1.2
|
303
303
|
# Billion"). This is useful for numbers that can get very large
|
304
304
|
# (and too hard to read).
|
305
305
|
#
|
@@ -307,7 +307,7 @@ module ActionView
|
|
307
307
|
# size.
|
308
308
|
#
|
309
309
|
# You can also define your own unit-quantifier names if you want
|
310
|
-
# to use other decimal units (
|
310
|
+
# to use other decimal units (e.g.: 1500 becomes "1.5
|
311
311
|
# kilometers", 0.150 becomes "150 milliliters", etc). You may
|
312
312
|
# define a wide range of unit quantifiers, even fractional ones
|
313
313
|
# (centi, deci, mili, etc).
|
@@ -425,9 +425,9 @@ module ActionView
|
|
425
425
|
end
|
426
426
|
|
427
427
|
def escape_units(units)
|
428
|
-
|
429
|
-
|
430
|
-
end
|
428
|
+
units.transform_values do |v|
|
429
|
+
ERB::Util.html_escape(v)
|
430
|
+
end
|
431
431
|
end
|
432
432
|
|
433
433
|
def wrap_with_output_safety_handling(number, raise_on_invalid, &block)
|
@@ -22,8 +22,12 @@ module ActionView
|
|
22
22
|
# type of <tt>text/plain</tt> from <tt>ActionDispatch::Response</tt>
|
23
23
|
# object.
|
24
24
|
#
|
25
|
-
# If no options hash is passed or
|
26
|
-
#
|
25
|
+
# If no <tt>options</tt> hash is passed or if <tt>:update</tt> is specified, then:
|
26
|
+
#
|
27
|
+
# If an object responding to `render_in` is passed, `render_in` is called on the object,
|
28
|
+
# passing in the current view context.
|
29
|
+
#
|
30
|
+
# Otherwise, a partial is rendered using the second parameter as the locals hash.
|
27
31
|
def render(options = {}, locals = {}, &block)
|
28
32
|
case options
|
29
33
|
when Hash
|
@@ -35,7 +39,11 @@ module ActionView
|
|
35
39
|
end
|
36
40
|
end
|
37
41
|
else
|
38
|
-
|
42
|
+
if options.respond_to?(:render_in)
|
43
|
+
options.render_in(self, &block)
|
44
|
+
else
|
45
|
+
view_renderer.render_partial(self, partial: options, locals: locals, &block)
|
46
|
+
end
|
39
47
|
end
|
40
48
|
end
|
41
49
|
|
@@ -129,11 +129,11 @@ module ActionView
|
|
129
129
|
end
|
130
130
|
|
131
131
|
def sanitized_allowed_tags
|
132
|
-
safe_list_sanitizer.allowed_tags
|
132
|
+
sanitizer_vendor.safe_list_sanitizer.allowed_tags
|
133
133
|
end
|
134
134
|
|
135
135
|
def sanitized_allowed_attributes
|
136
|
-
safe_list_sanitizer.allowed_attributes
|
136
|
+
sanitizer_vendor.safe_list_sanitizer.allowed_attributes
|
137
137
|
end
|
138
138
|
|
139
139
|
# Gets the Rails::Html::FullSanitizer instance used by +strip_tags+. Replace with
|
@@ -13,19 +13,27 @@ module ActionView
|
|
13
13
|
include CaptureHelper
|
14
14
|
include OutputSafetyHelper
|
15
15
|
|
16
|
-
BOOLEAN_ATTRIBUTES = %w(allowfullscreen async autofocus
|
17
|
-
compact controls declare default
|
18
|
-
defaultmuted defaultselected defer
|
19
|
-
enabled formnovalidate hidden indeterminate
|
20
|
-
ismap itemscope loop multiple muted nohref
|
21
|
-
noresize noshade novalidate nowrap open
|
22
|
-
pauseonexit readonly required reversed
|
23
|
-
seamless selected sortable truespeed
|
24
|
-
visible).to_set
|
16
|
+
BOOLEAN_ATTRIBUTES = %w(allowfullscreen allowpaymentrequest async autofocus
|
17
|
+
autoplay checked compact controls declare default
|
18
|
+
defaultchecked defaultmuted defaultselected defer
|
19
|
+
disabled enabled formnovalidate hidden indeterminate
|
20
|
+
inert ismap itemscope loop multiple muted nohref
|
21
|
+
nomodule noresize noshade novalidate nowrap open
|
22
|
+
pauseonexit playsinline readonly required reversed
|
23
|
+
scoped seamless selected sortable truespeed
|
24
|
+
typemustmatch visible).to_set
|
25
25
|
|
26
26
|
BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym))
|
27
|
+
BOOLEAN_ATTRIBUTES.freeze
|
27
28
|
|
28
|
-
|
29
|
+
ARIA_PREFIXES = ["aria", :aria].to_set.freeze
|
30
|
+
DATA_PREFIXES = ["data", :data].to_set.freeze
|
31
|
+
|
32
|
+
TAG_TYPES = {}
|
33
|
+
TAG_TYPES.merge! BOOLEAN_ATTRIBUTES.index_with(:boolean)
|
34
|
+
TAG_TYPES.merge! DATA_PREFIXES.index_with(:data)
|
35
|
+
TAG_TYPES.merge! ARIA_PREFIXES.index_with(:aria)
|
36
|
+
TAG_TYPES.freeze
|
29
37
|
|
30
38
|
PRE_CONTENT_STRINGS = Hash.new { "" }
|
31
39
|
PRE_CONTENT_STRINGS[:textarea] = "\n"
|
@@ -41,6 +49,10 @@ module ActionView
|
|
41
49
|
@view_context = view_context
|
42
50
|
end
|
43
51
|
|
52
|
+
def p(*arguments, **options, &block)
|
53
|
+
tag_string(:p, *arguments, **options, &block)
|
54
|
+
end
|
55
|
+
|
44
56
|
def tag_string(name, content = nil, escape_attributes: true, **options, &block)
|
45
57
|
content = @view_context.capture(self, &block) if block_given?
|
46
58
|
if VOID_ELEMENTS.include?(name) && content.nil?
|
@@ -61,13 +73,31 @@ module ActionView
|
|
61
73
|
output = +""
|
62
74
|
sep = " "
|
63
75
|
options.each_pair do |key, value|
|
64
|
-
|
76
|
+
type = TAG_TYPES[key]
|
77
|
+
if type == :data && value.is_a?(Hash)
|
78
|
+
value.each_pair do |k, v|
|
79
|
+
next if v.nil?
|
80
|
+
output << sep
|
81
|
+
output << prefix_tag_option(key, k, v, escape)
|
82
|
+
end
|
83
|
+
elsif type == :aria && value.is_a?(Hash)
|
65
84
|
value.each_pair do |k, v|
|
66
85
|
next if v.nil?
|
86
|
+
|
87
|
+
case v
|
88
|
+
when Array, Hash
|
89
|
+
tokens = TagHelper.build_tag_values(v)
|
90
|
+
next if tokens.none?
|
91
|
+
|
92
|
+
v = safe_join(tokens, " ")
|
93
|
+
else
|
94
|
+
v = v.to_s
|
95
|
+
end
|
96
|
+
|
67
97
|
output << sep
|
68
98
|
output << prefix_tag_option(key, k, v, escape)
|
69
99
|
end
|
70
|
-
elsif
|
100
|
+
elsif type == :boolean
|
71
101
|
if value
|
72
102
|
output << sep
|
73
103
|
output << boolean_tag_option(key)
|
@@ -85,12 +115,14 @@ module ActionView
|
|
85
115
|
end
|
86
116
|
|
87
117
|
def tag_option(key, value, escape)
|
88
|
-
|
118
|
+
case value
|
119
|
+
when Array, Hash
|
120
|
+
value = TagHelper.build_tag_values(value) if key.to_s == "class"
|
89
121
|
value = escape ? safe_join(value, " ") : value.join(" ")
|
90
122
|
else
|
91
|
-
value = escape ? ERB::Util.unwrapped_html_escape(value)
|
123
|
+
value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s
|
92
124
|
end
|
93
|
-
value.gsub
|
125
|
+
value = value.gsub('"', """) if value.include?('"')
|
94
126
|
%(#{key}="#{value}")
|
95
127
|
end
|
96
128
|
|
@@ -152,8 +184,8 @@ module ActionView
|
|
152
184
|
# tag.input type: 'text', disabled: true
|
153
185
|
# # => <input type="text" disabled="disabled">
|
154
186
|
#
|
155
|
-
# HTML5 <tt>data-*</tt> attributes can be set with a
|
156
|
-
# pointing to a hash of sub-attributes.
|
187
|
+
# HTML5 <tt>data-*</tt> and <tt>aria-*</tt> attributes can be set with a
|
188
|
+
# single +data+ or +aria+ key pointing to a hash of sub-attributes.
|
157
189
|
#
|
158
190
|
# To play nicely with JavaScript conventions, sub-attributes are dasherized.
|
159
191
|
#
|
@@ -233,6 +265,9 @@ module ActionView
|
|
233
265
|
#
|
234
266
|
# tag("div", data: { name: 'Stephen', city_state: %w(Chicago IL) })
|
235
267
|
# # => <div data-name="Stephen" data-city-state="["Chicago","IL"]" />
|
268
|
+
#
|
269
|
+
# tag("div", class: { highlight: current_user.admin? })
|
270
|
+
# # => <div class="highlight" />
|
236
271
|
def tag(name = nil, options = nil, open = false, escape = true)
|
237
272
|
if name.nil?
|
238
273
|
tag_builder
|
@@ -260,6 +295,8 @@ module ActionView
|
|
260
295
|
# # => <div class="strong"><p>Hello world!</p></div>
|
261
296
|
# content_tag(:div, "Hello world!", class: ["strong", "highlight"])
|
262
297
|
# # => <div class="strong highlight">Hello world!</div>
|
298
|
+
# content_tag(:div, "Hello world!", class: ["strong", { highlight: current_user.admin? }])
|
299
|
+
# # => <div class="strong highlight">Hello world!</div>
|
263
300
|
# content_tag("select", options, multiple: true)
|
264
301
|
# # => <select multiple="multiple">...options...</select>
|
265
302
|
#
|
@@ -276,6 +313,24 @@ module ActionView
|
|
276
313
|
end
|
277
314
|
end
|
278
315
|
|
316
|
+
# Returns a string of tokens built from +args+.
|
317
|
+
#
|
318
|
+
# ==== Examples
|
319
|
+
# token_list("foo", "bar")
|
320
|
+
# # => "foo bar"
|
321
|
+
# token_list("foo", "foo bar")
|
322
|
+
# # => "foo bar"
|
323
|
+
# token_list({ foo: true, bar: false })
|
324
|
+
# # => "foo"
|
325
|
+
# token_list(nil, false, 123, "", "foo", { bar: true })
|
326
|
+
# # => "123 foo bar"
|
327
|
+
def token_list(*args)
|
328
|
+
tokens = build_tag_values(*args).flat_map { |value| value.to_s.split(/\s+/) }.uniq
|
329
|
+
|
330
|
+
safe_join(tokens, " ")
|
331
|
+
end
|
332
|
+
alias_method :class_names, :token_list
|
333
|
+
|
279
334
|
# Returns a CDATA section with the given +content+. CDATA sections
|
280
335
|
# are used to escape blocks of text containing characters which would
|
281
336
|
# otherwise be recognized as markup. CDATA sections begin with the string
|
@@ -306,6 +361,26 @@ module ActionView
|
|
306
361
|
end
|
307
362
|
|
308
363
|
private
|
364
|
+
def build_tag_values(*args)
|
365
|
+
tag_values = []
|
366
|
+
|
367
|
+
args.each do |tag_value|
|
368
|
+
case tag_value
|
369
|
+
when Hash
|
370
|
+
tag_value.each do |key, val|
|
371
|
+
tag_values << key.to_s if val && key.present?
|
372
|
+
end
|
373
|
+
when Array
|
374
|
+
tag_values.concat build_tag_values(*tag_value)
|
375
|
+
else
|
376
|
+
tag_values << tag_value.to_s if tag_value.present?
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
tag_values
|
381
|
+
end
|
382
|
+
module_function :build_tag_values
|
383
|
+
|
309
384
|
def tag_builder
|
310
385
|
@tag_builder ||= TagBuilder.new(self)
|
311
386
|
end
|
@@ -105,7 +105,7 @@ module ActionView
|
|
105
105
|
end
|
106
106
|
|
107
107
|
def tag_name(multiple = false, index = nil)
|
108
|
-
# a little duplication to construct
|
108
|
+
# a little duplication to construct fewer strings
|
109
109
|
case
|
110
110
|
when @object_name.empty?
|
111
111
|
"#{sanitized_method_name}#{multiple ? "[]" : ""}"
|
@@ -117,7 +117,7 @@ module ActionView
|
|
117
117
|
end
|
118
118
|
|
119
119
|
def tag_id(index = nil)
|
120
|
-
# a little duplication to construct
|
120
|
+
# a little duplication to construct fewer strings
|
121
121
|
case
|
122
122
|
when @object_name.empty?
|
123
123
|
sanitized_method_name.dup
|
@@ -129,11 +129,11 @@ module ActionView
|
|
129
129
|
end
|
130
130
|
|
131
131
|
def sanitized_object_name
|
132
|
-
@sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").
|
132
|
+
@sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").delete_suffix("_")
|
133
133
|
end
|
134
134
|
|
135
135
|
def sanitized_method_name
|
136
|
-
@sanitized_method_name ||= @method_name.
|
136
|
+
@sanitized_method_name ||= @method_name.delete_suffix("?")
|
137
137
|
end
|
138
138
|
|
139
139
|
def sanitized_value(value)
|
@@ -166,8 +166,11 @@ module ActionView
|
|
166
166
|
|
167
167
|
def add_options(option_tags, options, value = nil)
|
168
168
|
if options[:include_blank]
|
169
|
-
|
169
|
+
content = (options[:include_blank] if options[:include_blank].is_a?(String))
|
170
|
+
label = (" " unless content)
|
171
|
+
option_tags = tag_builder.content_tag_string("option", content, value: "", label: label) + "\n" + option_tags
|
170
172
|
end
|
173
|
+
|
171
174
|
if value.blank? && options[:prompt]
|
172
175
|
tag_options = { value: "" }.tap do |prompt_opts|
|
173
176
|
prompt_opts[:disabled] = true if options[:disabled] == ""
|
@@ -175,6 +178,7 @@ module ActionView
|
|
175
178
|
end
|
176
179
|
option_tags = tag_builder.content_tag_string("option", prompt_text(options[:prompt]), tag_options) + "\n" + option_tags
|
177
180
|
end
|
181
|
+
|
178
182
|
option_tags
|
179
183
|
end
|
180
184
|
|
@@ -13,7 +13,7 @@ module ActionView
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def render
|
16
|
-
error_wrapping(datetime_selector(@options, @html_options).
|
16
|
+
error_wrapping(datetime_selector(@options, @html_options).public_send("select_#{select_type}").html_safe)
|
17
17
|
end
|
18
18
|
|
19
19
|
class << self
|
@@ -58,7 +58,7 @@ module ActionView
|
|
58
58
|
time = Time.current
|
59
59
|
|
60
60
|
[:year, :month, :day, :hour, :min, :sec].each do |key|
|
61
|
-
default[key] ||= time.
|
61
|
+
default[key] ||= time.public_send(key)
|
62
62
|
end
|
63
63
|
|
64
64
|
Time.utc(
|
@@ -228,7 +228,7 @@ module ActionView
|
|
228
228
|
# pluralize(2, 'Person', locale: :de)
|
229
229
|
# # => 2 Personen
|
230
230
|
def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale)
|
231
|
-
word = if count == 1 || count.to_s
|
231
|
+
word = if count == 1 || count.to_s.match?(/^1(\.0+)?$/)
|
232
232
|
singular
|
233
233
|
else
|
234
234
|
plural || singular.pluralize(locale)
|
@@ -1,8 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "action_view/helpers/tag_helper"
|
4
|
-
require "active_support/core_ext/
|
5
|
-
require "i18n/exceptions"
|
4
|
+
require "active_support/core_ext/symbol/starts_ends_with"
|
6
5
|
|
7
6
|
module ActionView
|
8
7
|
# = Action View Translation Helpers
|
@@ -57,64 +56,56 @@ module ActionView
|
|
57
56
|
# that include HTML tags so that you know what kind of output to expect
|
58
57
|
# when you call translate in a template and translators know which keys
|
59
58
|
# they can provide HTML values for.
|
59
|
+
#
|
60
|
+
# To access the translated text along with the fully resolved
|
61
|
+
# translation key, <tt>translate</tt> accepts a block:
|
62
|
+
#
|
63
|
+
# <%= translate(".relative_key") do |translation, resolved_key| %>
|
64
|
+
# <span title="<%= resolved_key %>"><%= translation %></span>
|
65
|
+
# <% end %>
|
66
|
+
#
|
67
|
+
# This enables annotate translated text to be aware of the scope it was
|
68
|
+
# resolved against.
|
69
|
+
#
|
60
70
|
def translate(key, **options)
|
61
|
-
if
|
62
|
-
|
63
|
-
options[:default] = remaining_defaults unless remaining_defaults.first.kind_of?(Symbol)
|
64
|
-
end
|
71
|
+
return key.map { |k| translate(k, **options) } if key.is_a?(Array)
|
72
|
+
key = key&.to_s unless key.is_a?(Symbol)
|
65
73
|
|
66
|
-
|
67
|
-
|
68
|
-
# Note: `raise_error` refers to us re-raising the error in this method. I18n is forced to raise by default.
|
69
|
-
if options[:raise] == false
|
70
|
-
raise_error = false
|
71
|
-
i18n_raise = false
|
72
|
-
else
|
73
|
-
raise_error = options[:raise] || ActionView::Base.raise_on_missing_translations
|
74
|
-
i18n_raise = true
|
74
|
+
alternatives = if options.key?(:default)
|
75
|
+
options[:default].is_a?(Array) ? options.delete(:default).compact : [options.delete(:default)]
|
75
76
|
end
|
76
77
|
|
77
|
-
if
|
78
|
-
|
78
|
+
options[:raise] = true if options[:raise].nil? && ActionView::Base.raise_on_missing_translations
|
79
|
+
default = MISSING_TRANSLATION
|
79
80
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
end
|
81
|
+
translation = while key || alternatives.present?
|
82
|
+
if alternatives.blank? && !options[:raise].nil?
|
83
|
+
default = NO_DEFAULT # let I18n handle missing translation
|
84
84
|
end
|
85
85
|
|
86
|
-
|
87
|
-
|
88
|
-
translation = I18n.translate(scope_key_by_partial(key), **html_safe_options.merge(raise: i18n_raise))
|
86
|
+
key = scope_key_by_partial(key)
|
89
87
|
|
90
|
-
if
|
91
|
-
options
|
92
|
-
|
93
|
-
|
88
|
+
if html_safe_translation_key?(key)
|
89
|
+
html_safe_options ||= html_escape_translation_options(options)
|
90
|
+
translated = I18n.translate(key, **html_safe_options, default: default)
|
91
|
+
break html_safe_translation(translated) unless translated.equal?(MISSING_TRANSLATION)
|
94
92
|
else
|
95
|
-
|
96
|
-
|
97
|
-
else
|
98
|
-
I18n.translate(scope_key_by_partial(key), **options.merge(raise: i18n_raise))
|
99
|
-
end
|
100
|
-
rescue I18n::MissingTranslationData => e
|
101
|
-
if remaining_defaults.present?
|
102
|
-
translate remaining_defaults.shift, **options.merge(default: remaining_defaults)
|
103
|
-
else
|
104
|
-
raise e if raise_error
|
105
|
-
|
106
|
-
keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
|
107
|
-
title = +"translation missing: #{keys.join('.')}"
|
108
|
-
|
109
|
-
interpolations = options.except(:default, :scope)
|
110
|
-
if interpolations.any?
|
111
|
-
title << ", " << interpolations.map { |k, v| "#{k}: #{ERB::Util.html_escape(v)}" }.join(", ")
|
93
|
+
translated = I18n.translate(key, **options, default: default)
|
94
|
+
break translated unless translated.equal?(MISSING_TRANSLATION)
|
112
95
|
end
|
113
96
|
|
114
|
-
|
97
|
+
break alternatives.first if alternatives.present? && !alternatives.first.is_a?(Symbol)
|
115
98
|
|
116
|
-
|
99
|
+
first_key ||= key
|
100
|
+
key = alternatives&.shift
|
117
101
|
end
|
102
|
+
|
103
|
+
if key.nil? && !first_key.nil?
|
104
|
+
translation = missing_translation(first_key, options)
|
105
|
+
key = first_key
|
106
|
+
end
|
107
|
+
|
108
|
+
block_given? ? yield(translation, key) : translation
|
118
109
|
end
|
119
110
|
alias :t :translate
|
120
111
|
|
@@ -131,13 +122,19 @@ module ActionView
|
|
131
122
|
MISSING_TRANSLATION = Object.new
|
132
123
|
private_constant :MISSING_TRANSLATION
|
133
124
|
|
125
|
+
NO_DEFAULT = [].freeze
|
126
|
+
private_constant :NO_DEFAULT
|
127
|
+
|
128
|
+
def self.i18n_option?(name)
|
129
|
+
(@i18n_option_names ||= I18n::RESERVED_KEYS.to_set).include?(name)
|
130
|
+
end
|
131
|
+
|
134
132
|
def scope_key_by_partial(key)
|
135
|
-
|
136
|
-
if stringified_key.first == "."
|
133
|
+
if key&.start_with?(".")
|
137
134
|
if @virtual_path
|
138
135
|
@_scope_key_by_partial_cache ||= {}
|
139
136
|
@_scope_key_by_partial_cache[@virtual_path] ||= @virtual_path.gsub(%r{/_?}, ".")
|
140
|
-
"#{@_scope_key_by_partial_cache[@virtual_path]}#{
|
137
|
+
"#{@_scope_key_by_partial_cache[@virtual_path]}#{key}"
|
141
138
|
else
|
142
139
|
raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
|
143
140
|
end
|
@@ -146,8 +143,47 @@ module ActionView
|
|
146
143
|
end
|
147
144
|
end
|
148
145
|
|
146
|
+
def html_escape_translation_options(options)
|
147
|
+
return options if options.empty?
|
148
|
+
html_safe_options = options.dup
|
149
|
+
|
150
|
+
options.each do |name, value|
|
151
|
+
unless TranslationHelper.i18n_option?(name) || (name == :count && value.is_a?(Numeric))
|
152
|
+
html_safe_options[name] = ERB::Util.html_escape(value.to_s)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
html_safe_options
|
157
|
+
end
|
158
|
+
|
149
159
|
def html_safe_translation_key?(key)
|
150
|
-
/(?:_|\b)html\z/.match?(key
|
160
|
+
/(?:_|\b)html\z/.match?(key)
|
161
|
+
end
|
162
|
+
|
163
|
+
def html_safe_translation(translation)
|
164
|
+
if translation.respond_to?(:map)
|
165
|
+
translation.map { |element| element.respond_to?(:html_safe) ? element.html_safe : element }
|
166
|
+
else
|
167
|
+
translation.respond_to?(:html_safe) ? translation.html_safe : translation
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def missing_translation(key, options)
|
172
|
+
keys = I18n.normalize_keys(options[:locale] || I18n.locale, key, options[:scope])
|
173
|
+
|
174
|
+
title = +"translation missing: #{keys.join(".")}"
|
175
|
+
|
176
|
+
options.each do |name, value|
|
177
|
+
unless name == :scope
|
178
|
+
title << ", " << name.to_s << ": " << ERB::Util.html_escape(value)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
if ActionView::Base.debug_missing_translation
|
183
|
+
content_tag("span", keys.last.to_s.titleize, class: "translation_missing", title: title)
|
184
|
+
else
|
185
|
+
title
|
186
|
+
end
|
151
187
|
end
|
152
188
|
end
|
153
189
|
end
|