actionview 4.2.11.1 → 6.0.4
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 +201 -192
- data/MIT-LICENSE +1 -1
- data/README.rdoc +9 -8
- data/lib/action_view/base.rb +144 -37
- data/lib/action_view/buffers.rb +18 -1
- data/lib/action_view/cache_expiry.rb +53 -0
- data/lib/action_view/context.rb +8 -12
- data/lib/action_view/dependency_tracker.rb +54 -20
- data/lib/action_view/digestor.rb +88 -85
- data/lib/action_view/flows.rb +11 -12
- data/lib/action_view/gem_version.rb +6 -4
- data/lib/action_view/helpers/active_model_helper.rb +16 -11
- data/lib/action_view/helpers/asset_tag_helper.rb +241 -82
- data/lib/action_view/helpers/asset_url_helper.rb +171 -67
- data/lib/action_view/helpers/atom_feed_helper.rb +19 -17
- data/lib/action_view/helpers/cache_helper.rb +112 -42
- data/lib/action_view/helpers/capture_helper.rb +20 -13
- data/lib/action_view/helpers/controller_helper.rb +15 -4
- data/lib/action_view/helpers/csp_helper.rb +26 -0
- data/lib/action_view/helpers/csrf_helper.rb +8 -6
- data/lib/action_view/helpers/date_helper.rb +230 -129
- data/lib/action_view/helpers/debug_helper.rb +7 -6
- data/lib/action_view/helpers/form_helper.rb +755 -129
- data/lib/action_view/helpers/form_options_helper.rb +130 -75
- data/lib/action_view/helpers/form_tag_helper.rb +116 -71
- data/lib/action_view/helpers/javascript_helper.rb +30 -14
- data/lib/action_view/helpers/number_helper.rb +84 -59
- data/lib/action_view/helpers/output_safety_helper.rb +36 -4
- data/lib/action_view/helpers/rendering_helper.rb +11 -8
- data/lib/action_view/helpers/sanitize_helper.rb +30 -31
- data/lib/action_view/helpers/tag_helper.rb +201 -75
- data/lib/action_view/helpers/tags/base.rb +138 -98
- data/lib/action_view/helpers/tags/check_box.rb +20 -19
- data/lib/action_view/helpers/tags/checkable.rb +4 -2
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -34
- data/lib/action_view/helpers/tags/collection_helpers.rb +69 -36
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +6 -12
- data/lib/action_view/helpers/tags/collection_select.rb +4 -2
- data/lib/action_view/helpers/tags/color_field.rb +4 -3
- data/lib/action_view/helpers/tags/date_field.rb +2 -1
- data/lib/action_view/helpers/tags/date_select.rb +37 -36
- data/lib/action_view/helpers/tags/datetime_field.rb +4 -3
- data/lib/action_view/helpers/tags/datetime_local_field.rb +2 -1
- data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
- data/lib/action_view/helpers/tags/email_field.rb +2 -0
- data/lib/action_view/helpers/tags/file_field.rb +2 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
- data/lib/action_view/helpers/tags/hidden_field.rb +2 -0
- data/lib/action_view/helpers/tags/label.rb +3 -2
- data/lib/action_view/helpers/tags/month_field.rb +2 -1
- data/lib/action_view/helpers/tags/number_field.rb +2 -0
- data/lib/action_view/helpers/tags/password_field.rb +3 -1
- data/lib/action_view/helpers/tags/placeholderable.rb +3 -1
- data/lib/action_view/helpers/tags/radio_button.rb +7 -6
- data/lib/action_view/helpers/tags/range_field.rb +2 -0
- data/lib/action_view/helpers/tags/search_field.rb +14 -9
- data/lib/action_view/helpers/tags/select.rb +11 -10
- data/lib/action_view/helpers/tags/tel_field.rb +2 -0
- data/lib/action_view/helpers/tags/text_area.rb +4 -2
- data/lib/action_view/helpers/tags/text_field.rb +8 -8
- data/lib/action_view/helpers/tags/time_field.rb +2 -1
- data/lib/action_view/helpers/tags/time_select.rb +2 -0
- data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
- data/lib/action_view/helpers/tags/translator.rb +15 -16
- data/lib/action_view/helpers/tags/url_field.rb +2 -0
- data/lib/action_view/helpers/tags/week_field.rb +2 -1
- data/lib/action_view/helpers/tags.rb +3 -1
- data/lib/action_view/helpers/text_helper.rb +56 -38
- data/lib/action_view/helpers/translation_helper.rb +91 -47
- data/lib/action_view/helpers/url_helper.rb +160 -105
- data/lib/action_view/helpers.rb +5 -3
- data/lib/action_view/layouts.rb +65 -61
- data/lib/action_view/log_subscriber.rb +61 -10
- data/lib/action_view/lookup_context.rb +147 -89
- data/lib/action_view/model_naming.rb +3 -1
- data/lib/action_view/path_set.rb +28 -23
- data/lib/action_view/railtie.rb +62 -6
- data/lib/action_view/record_identifier.rb +53 -26
- data/lib/action_view/renderer/abstract_renderer.rb +71 -13
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +103 -0
- data/lib/action_view/renderer/partial_renderer.rb +239 -225
- data/lib/action_view/renderer/renderer.rb +22 -8
- data/lib/action_view/renderer/streaming_template_renderer.rb +54 -54
- data/lib/action_view/renderer/template_renderer.rb +79 -73
- data/lib/action_view/rendering.rb +68 -44
- data/lib/action_view/routing_url_for.rb +33 -22
- data/lib/action_view/tasks/cache_digests.rake +25 -0
- data/lib/action_view/template/error.rb +44 -29
- data/lib/action_view/template/handlers/builder.rb +12 -13
- data/lib/action_view/template/handlers/erb/erubi.rb +87 -0
- data/lib/action_view/template/handlers/erb.rb +24 -86
- data/lib/action_view/template/handlers/html.rb +11 -0
- data/lib/action_view/template/handlers/raw.rb +4 -4
- data/lib/action_view/template/handlers.rb +38 -8
- data/lib/action_view/template/html.rb +19 -10
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/raw_file.rb +28 -0
- data/lib/action_view/template/resolver.rb +217 -193
- data/lib/action_view/template/sources/file.rb +17 -0
- data/lib/action_view/template/sources.rb +13 -0
- data/lib/action_view/template/text.rb +11 -10
- data/lib/action_view/template/types.rb +18 -18
- data/lib/action_view/template.rb +146 -90
- data/lib/action_view/test_case.rb +52 -32
- data/lib/action_view/testing/resolvers.rb +46 -34
- data/lib/action_view/unbound_template.rb +31 -0
- data/lib/action_view/version.rb +3 -1
- data/lib/action_view/view_paths.rb +48 -31
- data/lib/action_view.rb +11 -8
- data/lib/assets/compiled/rails-ujs.js +746 -0
- metadata +38 -29
- data/lib/action_view/helpers/record_tag_helper.rb +0 -108
- data/lib/action_view/tasks/dependencies.rake +0 -23
@@ -1,20 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActionView
|
2
4
|
module Helpers
|
3
5
|
module Tags # :nodoc:
|
4
6
|
class SearchField < TextField # :nodoc:
|
5
7
|
def render
|
6
|
-
|
7
|
-
if options["autosave"]
|
8
|
-
if options["autosave"] == true
|
9
|
-
options["autosave"] = request.host.split(".").reverse.join(".")
|
10
|
-
end
|
11
|
-
options["results"] ||= 10
|
12
|
-
end
|
8
|
+
options = @options.stringify_keys
|
13
9
|
|
14
|
-
|
15
|
-
|
10
|
+
if options["autosave"]
|
11
|
+
if options["autosave"] == true
|
12
|
+
options["autosave"] = request.host.split(".").reverse.join(".")
|
16
13
|
end
|
14
|
+
options["results"] ||= 10
|
17
15
|
end
|
16
|
+
|
17
|
+
if options["onsearch"]
|
18
|
+
options["incremental"] = true unless options.has_key?("incremental")
|
19
|
+
end
|
20
|
+
|
21
|
+
@options = options
|
22
|
+
super
|
18
23
|
end
|
19
24
|
end
|
20
25
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActionView
|
2
4
|
module Helpers
|
3
5
|
module Tags # :nodoc:
|
@@ -13,8 +15,8 @@ module ActionView
|
|
13
15
|
|
14
16
|
def render
|
15
17
|
option_tags_options = {
|
16
|
-
:
|
17
|
-
:
|
18
|
+
selected: @options.fetch(:selected) { value },
|
19
|
+
disabled: @options[:disabled]
|
18
20
|
}
|
19
21
|
|
20
22
|
option_tags = if grouped_choices?
|
@@ -27,14 +29,13 @@ module ActionView
|
|
27
29
|
end
|
28
30
|
|
29
31
|
private
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
end
|
32
|
+
# Grouped choices look like this:
|
33
|
+
#
|
34
|
+
# [nil, []]
|
35
|
+
# { nil => [] }
|
36
|
+
def grouped_choices?
|
37
|
+
!@choices.blank? && @choices.first.respond_to?(:last) && Array === @choices.first.last
|
38
|
+
end
|
38
39
|
end
|
39
40
|
end
|
40
41
|
end
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_view/helpers/tags/placeholderable"
|
2
4
|
|
3
5
|
module ActionView
|
4
6
|
module Helpers
|
@@ -14,7 +16,7 @@ module ActionView
|
|
14
16
|
options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split)
|
15
17
|
end
|
16
18
|
|
17
|
-
content_tag("textarea", options.delete("value") { value_before_type_cast
|
19
|
+
content_tag("textarea", options.delete("value") { value_before_type_cast }, options)
|
18
20
|
end
|
19
21
|
end
|
20
22
|
end
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_view/helpers/tags/placeholderable"
|
2
4
|
|
3
5
|
module ActionView
|
4
6
|
module Helpers
|
@@ -10,23 +12,21 @@ module ActionView
|
|
10
12
|
options = @options.stringify_keys
|
11
13
|
options["size"] = options["maxlength"] unless options.key?("size")
|
12
14
|
options["type"] ||= field_type
|
13
|
-
options["value"] = options.fetch("value") { value_before_type_cast
|
14
|
-
yield options if block_given?
|
15
|
+
options["value"] = options.fetch("value") { value_before_type_cast } unless field_type == "file"
|
15
16
|
add_default_name_and_id(options)
|
16
17
|
tag("input", options)
|
17
18
|
end
|
18
19
|
|
19
20
|
class << self
|
20
21
|
def field_type
|
21
|
-
@field_type ||=
|
22
|
+
@field_type ||= name.split("::").last.sub("Field", "").downcase
|
22
23
|
end
|
23
24
|
end
|
24
25
|
|
25
26
|
private
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
end
|
27
|
+
def field_type
|
28
|
+
self.class.field_type
|
29
|
+
end
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActionView
|
2
4
|
module Helpers
|
3
5
|
module Tags # :nodoc:
|
@@ -11,7 +13,7 @@ module ActionView
|
|
11
13
|
|
12
14
|
def render
|
13
15
|
select_content_tag(
|
14
|
-
time_zone_options_for_select(value
|
16
|
+
time_zone_options_for_select(value || @options[:default], @priority_zones, @options[:model] || ActiveSupport::TimeZone), @options, @html_options
|
15
17
|
)
|
16
18
|
end
|
17
19
|
end
|
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActionView
|
2
4
|
module Helpers
|
3
5
|
module Tags # :nodoc:
|
4
6
|
class Translator # :nodoc:
|
5
|
-
def initialize(object, object_name, method_and_value, scope)
|
7
|
+
def initialize(object, object_name, method_and_value, scope:)
|
6
8
|
@object_name = object_name.gsub(/\[(.*)_attributes\]\[\d+\]/, '.\1')
|
7
9
|
@method_and_value = method_and_value
|
8
10
|
@scope = scope
|
@@ -14,26 +16,23 @@ module ActionView
|
|
14
16
|
translated_attribute || human_attribute_name
|
15
17
|
end
|
16
18
|
|
17
|
-
protected
|
18
|
-
|
19
|
-
attr_reader :object_name, :method_and_value, :scope, :model
|
20
|
-
|
21
19
|
private
|
20
|
+
attr_reader :object_name, :method_and_value, :scope, :model
|
22
21
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
22
|
+
def i18n_default
|
23
|
+
if model
|
24
|
+
key = model.model_name.i18n_key
|
25
|
+
["#{key}.#{method_and_value}".to_sym, ""]
|
26
|
+
else
|
27
|
+
""
|
28
|
+
end
|
29
29
|
end
|
30
|
-
end
|
31
30
|
|
32
|
-
|
33
|
-
|
34
|
-
|
31
|
+
def human_attribute_name
|
32
|
+
if model && model.class.respond_to?(:human_attribute_name)
|
33
|
+
model.class.human_attribute_name(method_and_value)
|
34
|
+
end
|
35
35
|
end
|
36
|
-
end
|
37
36
|
end
|
38
37
|
end
|
39
38
|
end
|
@@ -1,5 +1,7 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/string/filters"
|
4
|
+
require "active_support/core_ext/array/extract_options"
|
3
5
|
|
4
6
|
module ActionView
|
5
7
|
# = Action View Text Helpers
|
@@ -11,9 +13,9 @@ module ActionView
|
|
11
13
|
#
|
12
14
|
# ==== Sanitization
|
13
15
|
#
|
14
|
-
# Most text helpers
|
15
|
-
# This means HTML tags will appear in the page but all malicious
|
16
|
-
# Let's look at some examples using the +simple_format+ method:
|
16
|
+
# Most text helpers that generate HTML output sanitize the given input by default,
|
17
|
+
# but do not escape it. This means HTML tags will appear in the page but all malicious
|
18
|
+
# code will be removed. Let's look at some examples using the +simple_format+ method:
|
17
19
|
#
|
18
20
|
# simple_format('<a href="http://example.com/">Example</a>')
|
19
21
|
# # => "<p><a href=\"http://example.com/\">Example</a></p>"
|
@@ -103,7 +105,9 @@ module ActionView
|
|
103
105
|
# Highlights one or more +phrases+ everywhere in +text+ by inserting it into
|
104
106
|
# a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt>
|
105
107
|
# as a single-quoted string with <tt>\1</tt> where the phrase is to be inserted (defaults to
|
106
|
-
# '<mark>\1</mark>') or passing a block that receives each matched term.
|
108
|
+
# '<mark>\1</mark>') or passing a block that receives each matched term. By default +text+
|
109
|
+
# is sanitized to prevent possible XSS attacks. If the input is trustworthy, passing false
|
110
|
+
# for <tt>:sanitize</tt> will turn sanitizing off.
|
107
111
|
#
|
108
112
|
# highlight('You searched for: rails', 'rails')
|
109
113
|
# # => You searched for: <mark>rails</mark>
|
@@ -122,6 +126,9 @@ module ActionView
|
|
122
126
|
#
|
123
127
|
# highlight('You searched for: rails', 'rails') { |match| link_to(search_path(q: match, match)) }
|
124
128
|
# # => You searched for: <a href="search?q=rails">rails</a>
|
129
|
+
#
|
130
|
+
# highlight('<a href="javascript:alert(\'no!\')">ruby</a> on rails', 'rails', sanitize: false)
|
131
|
+
# # => <a href="javascript:alert('no!')">ruby</a> on <mark>rails</mark>
|
125
132
|
def highlight(text, phrases, options = {})
|
126
133
|
text = sanitize(text) if options.fetch(:sanitize, true)
|
127
134
|
|
@@ -130,7 +137,7 @@ module ActionView
|
|
130
137
|
else
|
131
138
|
match = Array(phrases).map do |p|
|
132
139
|
Regexp === p ? p.to_s : Regexp.escape(p)
|
133
|
-
end.join(
|
140
|
+
end.join("|")
|
134
141
|
|
135
142
|
if block_given?
|
136
143
|
text.gsub(/(#{match})(?![^<]*?>)/i) { |found| yield found }
|
@@ -146,7 +153,7 @@ module ActionView
|
|
146
153
|
# defined in <tt>:radius</tt> (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+,
|
147
154
|
# then the <tt>:omission</tt> option (which defaults to "...") will be prepended/appended accordingly. Use the
|
148
155
|
# <tt>:separator</tt> option to choose the delimitation. The resulting string will be stripped in any case. If the +phrase+
|
149
|
-
# isn't found, nil is returned.
|
156
|
+
# isn't found, +nil+ is returned.
|
150
157
|
#
|
151
158
|
# excerpt('This is an example', 'an', radius: 5)
|
152
159
|
# # => ...s is an exam...
|
@@ -181,8 +188,8 @@ module ActionView
|
|
181
188
|
|
182
189
|
unless separator.empty?
|
183
190
|
text.split(separator).each do |value|
|
184
|
-
if value.match(regex)
|
185
|
-
|
191
|
+
if value.match?(regex)
|
192
|
+
phrase = value
|
186
193
|
break
|
187
194
|
end
|
188
195
|
end
|
@@ -199,7 +206,12 @@ module ActionView
|
|
199
206
|
|
200
207
|
# Attempts to pluralize the +singular+ word unless +count+ is 1. If
|
201
208
|
# +plural+ is supplied, it will use that when count is > 1, otherwise
|
202
|
-
# it will use the Inflector to determine the plural form
|
209
|
+
# it will use the Inflector to determine the plural form for the given locale,
|
210
|
+
# which defaults to I18n.locale
|
211
|
+
#
|
212
|
+
# The word will be pluralized using rules defined for the locale
|
213
|
+
# (you must define your own inflection rules for languages other than English).
|
214
|
+
# See ActiveSupport::Inflector.pluralize
|
203
215
|
#
|
204
216
|
# pluralize(1, 'person')
|
205
217
|
# # => 1 person
|
@@ -207,16 +219,19 @@ module ActionView
|
|
207
219
|
# pluralize(2, 'person')
|
208
220
|
# # => 2 people
|
209
221
|
#
|
210
|
-
# pluralize(3, 'person', 'users')
|
222
|
+
# pluralize(3, 'person', plural: 'users')
|
211
223
|
# # => 3 users
|
212
224
|
#
|
213
225
|
# pluralize(0, 'person')
|
214
226
|
# # => 0 people
|
215
|
-
|
216
|
-
|
227
|
+
#
|
228
|
+
# pluralize(2, 'Person', locale: :de)
|
229
|
+
# # => 2 Personen
|
230
|
+
def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale)
|
231
|
+
word = if count == 1 || count.to_s =~ /^1(\.0+)?$/
|
217
232
|
singular
|
218
233
|
else
|
219
|
-
plural || singular.pluralize
|
234
|
+
plural || singular.pluralize(locale)
|
220
235
|
end
|
221
236
|
|
222
237
|
"#{count || 0} #{word}"
|
@@ -237,19 +252,23 @@ module ActionView
|
|
237
252
|
#
|
238
253
|
# word_wrap('Once upon a time', line_width: 1)
|
239
254
|
# # => Once\nupon\na\ntime
|
240
|
-
|
241
|
-
|
242
|
-
|
255
|
+
#
|
256
|
+
# You can also specify a custom +break_sequence+ ("\n" by default)
|
257
|
+
#
|
258
|
+
# word_wrap('Once upon a time', line_width: 1, break_sequence: "\r\n")
|
259
|
+
# # => Once\r\nupon\r\na\r\ntime
|
260
|
+
def word_wrap(text, line_width: 80, break_sequence: "\n")
|
243
261
|
text.split("\n").collect! do |line|
|
244
|
-
line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1
|
245
|
-
end *
|
262
|
+
line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").rstrip : line
|
263
|
+
end * break_sequence
|
246
264
|
end
|
247
265
|
|
248
266
|
# Returns +text+ transformed into HTML using simple formatting rules.
|
249
|
-
# Two or more consecutive newlines(<tt>\n\n</tt>) are
|
250
|
-
# paragraph and wrapped in <tt><p></tt> tags. One newline
|
251
|
-
#
|
252
|
-
# method does not remove the
|
267
|
+
# Two or more consecutive newlines(<tt>\n\n</tt> or <tt>\r\n\r\n</tt>) are
|
268
|
+
# considered a paragraph and wrapped in <tt><p></tt> tags. One newline
|
269
|
+
# (<tt>\n</tt> or <tt>\r\n</tt>) is considered a linebreak and a
|
270
|
+
# <tt><br /></tt> tag is appended. This method does not remove the
|
271
|
+
# newlines from the +text+.
|
253
272
|
#
|
254
273
|
# You can pass any HTML attributes into <tt>html_options</tt>. These
|
255
274
|
# will be added to all created paragraphs.
|
@@ -309,7 +328,7 @@ module ActionView
|
|
309
328
|
# <table>
|
310
329
|
# <% @items.each do |item| %>
|
311
330
|
# <tr class="<%= cycle("odd", "even") -%>">
|
312
|
-
# <td
|
331
|
+
# <td><%= item %></td>
|
313
332
|
# </tr>
|
314
333
|
# <% end %>
|
315
334
|
# </table>
|
@@ -334,7 +353,7 @@ module ActionView
|
|
334
353
|
# <% end %>
|
335
354
|
def cycle(first_value, *values)
|
336
355
|
options = values.extract_options!
|
337
|
-
name = options.fetch(:name,
|
356
|
+
name = options.fetch(:name, "default")
|
338
357
|
|
339
358
|
values.unshift(*first_value)
|
340
359
|
|
@@ -403,22 +422,21 @@ module ActionView
|
|
403
422
|
def to_s
|
404
423
|
value = @values[@index].to_s
|
405
424
|
@index = next_index
|
406
|
-
|
425
|
+
value
|
407
426
|
end
|
408
427
|
|
409
428
|
private
|
429
|
+
def next_index
|
430
|
+
step_index(1)
|
431
|
+
end
|
410
432
|
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
def previous_index
|
416
|
-
step_index(-1)
|
417
|
-
end
|
433
|
+
def previous_index
|
434
|
+
step_index(-1)
|
435
|
+
end
|
418
436
|
|
419
|
-
|
420
|
-
|
421
|
-
|
437
|
+
def step_index(n)
|
438
|
+
(@index + n) % @values.size
|
439
|
+
end
|
422
440
|
end
|
423
441
|
|
424
442
|
private
|
@@ -427,7 +445,7 @@ module ActionView
|
|
427
445
|
# uses an instance variable of ActionView::Base.
|
428
446
|
def get_cycle(name)
|
429
447
|
@_cycles = Hash.new unless defined?(@_cycles)
|
430
|
-
|
448
|
+
@_cycles[name]
|
431
449
|
end
|
432
450
|
|
433
451
|
def set_cycle(name, cycle_object)
|
@@ -1,99 +1,143 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_view/helpers/tag_helper"
|
4
|
+
require "active_support/core_ext/string/access"
|
5
|
+
require "i18n/exceptions"
|
4
6
|
|
5
7
|
module ActionView
|
6
8
|
# = Action View Translation Helpers
|
7
|
-
module Helpers
|
9
|
+
module Helpers #:nodoc:
|
8
10
|
module TranslationHelper
|
11
|
+
extend ActiveSupport::Concern
|
12
|
+
|
9
13
|
include TagHelper
|
10
|
-
|
14
|
+
|
15
|
+
included do
|
16
|
+
mattr_accessor :debug_missing_translation, default: true
|
17
|
+
end
|
18
|
+
|
19
|
+
# Delegates to <tt>I18n#translate</tt> but also performs three additional
|
20
|
+
# functions.
|
11
21
|
#
|
12
|
-
# First, it will ensure that any thrown +MissingTranslation+ messages will
|
13
|
-
#
|
22
|
+
# First, it will ensure that any thrown +MissingTranslation+ messages will
|
23
|
+
# be rendered as inline spans that:
|
14
24
|
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
25
|
+
# * Have a <tt>translation-missing</tt> class applied
|
26
|
+
# * Contain the missing key as the value of the +title+ attribute
|
27
|
+
# * Have a titleized version of the last key segment as text
|
18
28
|
#
|
19
|
-
#
|
20
|
-
# <
|
21
|
-
# This way your views will display rather reasonable strings but it will still
|
22
|
-
# be easy to spot missing translations.
|
29
|
+
# For example, the value returned for the missing translation key
|
30
|
+
# <tt>"blog.post.title"</tt> will be:
|
23
31
|
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
# <tt>I18n.translate("people.index.foo")</tt>. This makes it less repetitive
|
28
|
-
# to translate many keys within the same partials and gives you a simple framework
|
29
|
-
# for scoping them consistently. If you don't prepend the key with a period,
|
30
|
-
# nothing is converted.
|
32
|
+
# <span
|
33
|
+
# class="translation_missing"
|
34
|
+
# title="translation missing: en.blog.post.title">Title</span>
|
31
35
|
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
36
|
+
# This allows for views to display rather reasonable strings while still
|
37
|
+
# giving developers a way to find missing translations.
|
38
|
+
#
|
39
|
+
# If you would prefer missing translations to raise an error, you can
|
40
|
+
# opt out of span-wrapping behavior globally by setting
|
41
|
+
# <tt>ActionView::Base.raise_on_missing_translations = true</tt> or
|
42
|
+
# individually by passing <tt>raise: true</tt> as an option to
|
43
|
+
# <tt>translate</tt>.
|
44
|
+
#
|
45
|
+
# Second, if the key starts with a period <tt>translate</tt> will scope
|
46
|
+
# the key by the current partial. Calling <tt>translate(".foo")</tt> from
|
47
|
+
# the <tt>people/index.html.erb</tt> template is equivalent to calling
|
48
|
+
# <tt>translate("people.index.foo")</tt>. This makes it less
|
49
|
+
# repetitive to translate many keys within the same partial and provides
|
50
|
+
# a convention to scope keys consistently.
|
51
|
+
#
|
52
|
+
# Third, the translation will be marked as <tt>html_safe</tt> if the key
|
53
|
+
# has the suffix "_html" or the last element of the key is "html". Calling
|
54
|
+
# <tt>translate("footer_html")</tt> or <tt>translate("footer.html")</tt>
|
55
|
+
# will return an HTML safe string that won't be escaped by other HTML
|
56
|
+
# helper methods. This naming convention helps to identify translations
|
57
|
+
# that include HTML tags so that you know what kind of output to expect
|
58
|
+
# when you call translate in a template and translators know which keys
|
59
|
+
# they can provide HTML values for.
|
60
|
+
def translate(key, **options)
|
61
|
+
if options.has_key?(:default)
|
62
|
+
remaining_defaults = Array.wrap(options.delete(:default)).compact
|
63
|
+
options[:default] = remaining_defaults unless remaining_defaults.first.kind_of?(Symbol)
|
45
64
|
end
|
46
65
|
|
47
66
|
# If the user has explicitly decided to NOT raise errors, pass that option to I18n.
|
48
67
|
# Otherwise, tell I18n to raise an exception, which we rescue further in this method.
|
49
68
|
# Note: `raise_error` refers to us re-raising the error in this method. I18n is forced to raise by default.
|
50
|
-
if options[:raise] == false
|
69
|
+
if options[:raise] == false
|
51
70
|
raise_error = false
|
52
71
|
i18n_raise = false
|
53
72
|
else
|
54
|
-
raise_error = options[:raise] ||
|
73
|
+
raise_error = options[:raise] || ActionView::Base.raise_on_missing_translations
|
55
74
|
i18n_raise = true
|
56
75
|
end
|
57
76
|
|
58
77
|
if html_safe_translation_key?(key)
|
59
78
|
html_safe_options = options.dup
|
79
|
+
|
60
80
|
options.except(*I18n::RESERVED_KEYS).each do |name, value|
|
61
81
|
unless name == :count && value.is_a?(Numeric)
|
62
82
|
html_safe_options[name] = ERB::Util.html_escape(value.to_s)
|
63
83
|
end
|
64
84
|
end
|
65
|
-
translation = I18n.translate(scope_key_by_partial(key), html_safe_options.merge(raise: i18n_raise))
|
66
85
|
|
67
|
-
|
86
|
+
html_safe_options[:default] = MISSING_TRANSLATION unless html_safe_options[:default].blank?
|
87
|
+
|
88
|
+
translation = I18n.translate(scope_key_by_partial(key), **html_safe_options.merge(raise: i18n_raise))
|
89
|
+
|
90
|
+
if translation.equal?(MISSING_TRANSLATION)
|
91
|
+
options[:default].first
|
92
|
+
elsif translation.respond_to?(:map)
|
93
|
+
translation.map { |element| element.respond_to?(:html_safe) ? element.html_safe : element }
|
94
|
+
else
|
95
|
+
translation.respond_to?(:html_safe) ? translation.html_safe : translation
|
96
|
+
end
|
68
97
|
else
|
69
|
-
I18n.translate(scope_key_by_partial(key), options.merge(raise: i18n_raise))
|
98
|
+
I18n.translate(scope_key_by_partial(key), **options.merge(raise: i18n_raise))
|
70
99
|
end
|
71
100
|
rescue I18n::MissingTranslationData => e
|
72
101
|
if remaining_defaults.present?
|
73
|
-
translate remaining_defaults.shift, options.merge(default: remaining_defaults)
|
102
|
+
translate remaining_defaults.shift, **options.merge(default: remaining_defaults)
|
74
103
|
else
|
75
104
|
raise e if raise_error
|
76
105
|
|
77
106
|
keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
|
78
|
-
|
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(", ")
|
112
|
+
end
|
113
|
+
|
114
|
+
return title unless ActionView::Base.debug_missing_translation
|
115
|
+
|
116
|
+
content_tag("span", keys.last.to_s.titleize, class: "translation_missing", title: title)
|
79
117
|
end
|
80
118
|
end
|
81
119
|
alias :t :translate
|
82
120
|
|
83
121
|
# Delegates to <tt>I18n.localize</tt> with no additional functionality.
|
84
122
|
#
|
85
|
-
# See
|
123
|
+
# See https://www.rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
|
86
124
|
# for more information.
|
87
|
-
def localize(
|
88
|
-
I18n.localize(
|
125
|
+
def localize(object, **options)
|
126
|
+
I18n.localize(object, **options)
|
89
127
|
end
|
90
128
|
alias :l :localize
|
91
129
|
|
92
130
|
private
|
131
|
+
MISSING_TRANSLATION = Object.new
|
132
|
+
private_constant :MISSING_TRANSLATION
|
133
|
+
|
93
134
|
def scope_key_by_partial(key)
|
94
|
-
|
135
|
+
stringified_key = key.to_s
|
136
|
+
if stringified_key.first == "."
|
95
137
|
if @virtual_path
|
96
|
-
@
|
138
|
+
@_scope_key_by_partial_cache ||= {}
|
139
|
+
@_scope_key_by_partial_cache[@virtual_path] ||= @virtual_path.gsub(%r{/_?}, ".")
|
140
|
+
"#{@_scope_key_by_partial_cache[@virtual_path]}#{stringified_key}"
|
97
141
|
else
|
98
142
|
raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
|
99
143
|
end
|
@@ -103,7 +147,7 @@ module ActionView
|
|
103
147
|
end
|
104
148
|
|
105
149
|
def html_safe_translation_key?(key)
|
106
|
-
|
150
|
+
/(?:_|\b)html\z/.match?(key.to_s)
|
107
151
|
end
|
108
152
|
end
|
109
153
|
end
|