actionview 4.2.11.1 → 7.0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of actionview might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +229 -215
- data/MIT-LICENSE +1 -1
- data/README.rdoc +9 -8
- data/lib/action_view/base.rb +116 -43
- data/lib/action_view/buffers.rb +20 -3
- data/lib/action_view/cache_expiry.rb +66 -0
- data/lib/action_view/context.rb +8 -12
- data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
- data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
- data/lib/action_view/dependency_tracker.rb +21 -122
- data/lib/action_view/digestor.rb +92 -85
- data/lib/action_view/flows.rb +15 -16
- data/lib/action_view/gem_version.rb +6 -4
- data/lib/action_view/helpers/active_model_helper.rb +17 -12
- data/lib/action_view/helpers/asset_tag_helper.rb +356 -101
- data/lib/action_view/helpers/asset_url_helper.rb +180 -74
- data/lib/action_view/helpers/atom_feed_helper.rb +21 -19
- data/lib/action_view/helpers/cache_helper.rb +156 -43
- data/lib/action_view/helpers/capture_helper.rb +21 -14
- data/lib/action_view/helpers/controller_helper.rb +16 -5
- 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 +288 -132
- data/lib/action_view/helpers/debug_helper.rb +9 -6
- data/lib/action_view/helpers/form_helper.rb +956 -173
- data/lib/action_view/helpers/form_options_helper.rb +178 -97
- data/lib/action_view/helpers/form_tag_helper.rb +220 -101
- data/lib/action_view/helpers/javascript_helper.rb +33 -19
- data/lib/action_view/helpers/number_helper.rb +88 -63
- data/lib/action_view/helpers/output_safety_helper.rb +38 -6
- data/lib/action_view/helpers/rendering_helper.rb +21 -10
- data/lib/action_view/helpers/sanitize_helper.rb +31 -32
- data/lib/action_view/helpers/tag_helper.rb +332 -71
- data/lib/action_view/helpers/tags/base.rb +123 -99
- data/lib/action_view/helpers/tags/check_box.rb +21 -20
- 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 +5 -3
- data/lib/action_view/helpers/tags/color_field.rb +4 -3
- data/lib/action_view/helpers/tags/date_field.rb +3 -2
- data/lib/action_view/helpers/tags/date_select.rb +38 -37
- data/lib/action_view/helpers/tags/datetime_field.rb +4 -3
- data/lib/action_view/helpers/tags/datetime_local_field.rb +3 -2
- 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 +18 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
- data/lib/action_view/helpers/tags/hidden_field.rb +6 -0
- data/lib/action_view/helpers/tags/label.rb +7 -2
- data/lib/action_view/helpers/tags/month_field.rb +3 -2
- 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 +12 -2
- 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 +3 -2
- data/lib/action_view/helpers/tags/weekday_select.rb +28 -0
- data/lib/action_view/helpers/tags.rb +5 -2
- data/lib/action_view/helpers/text_helper.rb +80 -51
- data/lib/action_view/helpers/translation_helper.rb +120 -69
- data/lib/action_view/helpers/url_helper.rb +398 -171
- data/lib/action_view/helpers.rb +29 -27
- data/lib/action_view/layouts.rb +68 -63
- data/lib/action_view/log_subscriber.rb +77 -10
- data/lib/action_view/lookup_context.rb +137 -113
- data/lib/action_view/model_naming.rb +4 -2
- data/lib/action_view/path_set.rb +28 -32
- data/lib/action_view/railtie.rb +74 -13
- data/lib/action_view/record_identifier.rb +53 -26
- data/lib/action_view/render_parser.rb +188 -0
- data/lib/action_view/renderer/abstract_renderer.rb +152 -15
- 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/collection_caching.rb +102 -0
- data/lib/action_view/renderer/partial_renderer.rb +51 -333
- data/lib/action_view/renderer/renderer.rb +68 -11
- data/lib/action_view/renderer/streaming_template_renderer.rb +60 -56
- data/lib/action_view/renderer/template_renderer.rb +87 -74
- data/lib/action_view/rendering.rb +73 -47
- data/lib/action_view/ripper_ast_parser.rb +198 -0
- data/lib/action_view/routing_url_for.rb +35 -24
- data/lib/action_view/tasks/cache_digests.rake +25 -0
- data/lib/action_view/template/error.rb +151 -41
- data/lib/action_view/template/handlers/builder.rb +12 -13
- data/lib/action_view/template/handlers/erb/erubi.rb +89 -0
- data/lib/action_view/template/handlers/erb.rb +29 -89
- 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 +14 -10
- data/lib/action_view/template/html.rb +12 -13
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/raw_file.rb +25 -0
- data/lib/action_view/template/renderable.rb +24 -0
- data/lib/action_view/template/resolver.rb +139 -300
- 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 +10 -12
- data/lib/action_view/template/types.rb +28 -26
- data/lib/action_view/template.rb +123 -91
- data/lib/action_view/template_details.rb +66 -0
- data/lib/action_view/template_path.rb +64 -0
- data/lib/action_view/test_case.rb +70 -53
- data/lib/action_view/testing/resolvers.rb +25 -35
- data/lib/action_view/unbound_template.rb +57 -0
- data/lib/action_view/version.rb +3 -1
- data/lib/action_view/view_paths.rb +73 -58
- data/lib/action_view.rb +16 -11
- data/lib/assets/compiled/rails-ujs.js +746 -0
- metadata +52 -32
- data/lib/action_view/helpers/record_tag_helper.rb +0 -108
- data/lib/action_view/tasks/dependencies.rake +0 -23
@@ -1,9 +1,15 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/string/filters"
|
4
|
+
require "active_support/core_ext/string/access"
|
5
|
+
require "active_support/core_ext/array/extract_options"
|
6
|
+
require "action_view/helpers/sanitize_helper"
|
7
|
+
require "action_view/helpers/tag_helper"
|
8
|
+
require "action_view/helpers/output_safety_helper"
|
3
9
|
|
4
10
|
module ActionView
|
5
11
|
# = Action View Text Helpers
|
6
|
-
module Helpers
|
12
|
+
module Helpers # :nodoc:
|
7
13
|
# The TextHelper module provides a set of methods for filtering, formatting
|
8
14
|
# and transforming strings, which can reduce the amount of inline Ruby code in
|
9
15
|
# your views. These helper methods extend Action View making them callable
|
@@ -11,9 +17,9 @@ module ActionView
|
|
11
17
|
#
|
12
18
|
# ==== Sanitization
|
13
19
|
#
|
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:
|
20
|
+
# Most text helpers that generate HTML output sanitize the given input by default,
|
21
|
+
# but do not escape it. This means HTML tags will appear in the page but all malicious
|
22
|
+
# code will be removed. Let's look at some examples using the +simple_format+ method:
|
17
23
|
#
|
18
24
|
# simple_format('<a href="http://example.com/">Example</a>')
|
19
25
|
# # => "<p><a href=\"http://example.com/\">Example</a></p>"
|
@@ -103,7 +109,9 @@ module ActionView
|
|
103
109
|
# Highlights one or more +phrases+ everywhere in +text+ by inserting it into
|
104
110
|
# a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt>
|
105
111
|
# as a single-quoted string with <tt>\1</tt> where the phrase is to be inserted (defaults to
|
106
|
-
#
|
112
|
+
# <tt><mark>\1</mark></tt>) or passing a block that receives each matched term. By default +text+
|
113
|
+
# is sanitized to prevent possible XSS attacks. If the input is trustworthy, passing false
|
114
|
+
# for <tt>:sanitize</tt> will turn sanitizing off.
|
107
115
|
#
|
108
116
|
# highlight('You searched for: rails', 'rails')
|
109
117
|
# # => You searched for: <mark>rails</mark>
|
@@ -122,7 +130,10 @@ module ActionView
|
|
122
130
|
#
|
123
131
|
# highlight('You searched for: rails', 'rails') { |match| link_to(search_path(q: match, match)) }
|
124
132
|
# # => You searched for: <a href="search?q=rails">rails</a>
|
125
|
-
|
133
|
+
#
|
134
|
+
# highlight('<a href="javascript:alert(\'no!\')">ruby</a> on rails', 'rails', sanitize: false)
|
135
|
+
# # => <a href="javascript:alert('no!')">ruby</a> on <mark>rails</mark>
|
136
|
+
def highlight(text, phrases, options = {}, &block)
|
126
137
|
text = sanitize(text) if options.fetch(:sanitize, true)
|
127
138
|
|
128
139
|
if text.blank? || phrases.blank?
|
@@ -130,10 +141,10 @@ module ActionView
|
|
130
141
|
else
|
131
142
|
match = Array(phrases).map do |p|
|
132
143
|
Regexp === p ? p.to_s : Regexp.escape(p)
|
133
|
-
end.join(
|
144
|
+
end.join("|")
|
134
145
|
|
135
146
|
if block_given?
|
136
|
-
text.gsub(/(#{match})(?![^<]*?>)/i)
|
147
|
+
text.gsub(/(#{match})(?![^<]*?>)/i, &block)
|
137
148
|
else
|
138
149
|
highlighter = options.fetch(:highlighter, '<mark>\1</mark>')
|
139
150
|
text.gsub(/(#{match})(?![^<]*?>)/i, highlighter)
|
@@ -146,7 +157,7 @@ module ActionView
|
|
146
157
|
# defined in <tt>:radius</tt> (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+,
|
147
158
|
# then the <tt>:omission</tt> option (which defaults to "...") will be prepended/appended accordingly. Use the
|
148
159
|
# <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.
|
160
|
+
# isn't found, +nil+ is returned.
|
150
161
|
#
|
151
162
|
# excerpt('This is an example', 'an', radius: 5)
|
152
163
|
# # => ...s is an exam...
|
@@ -181,8 +192,8 @@ module ActionView
|
|
181
192
|
|
182
193
|
unless separator.empty?
|
183
194
|
text.split(separator).each do |value|
|
184
|
-
if value.match(regex)
|
185
|
-
|
195
|
+
if value.match?(regex)
|
196
|
+
phrase = value
|
186
197
|
break
|
187
198
|
end
|
188
199
|
end
|
@@ -199,7 +210,12 @@ module ActionView
|
|
199
210
|
|
200
211
|
# Attempts to pluralize the +singular+ word unless +count+ is 1. If
|
201
212
|
# +plural+ is supplied, it will use that when count is > 1, otherwise
|
202
|
-
# it will use the Inflector to determine the plural form
|
213
|
+
# it will use the Inflector to determine the plural form for the given locale,
|
214
|
+
# which defaults to I18n.locale
|
215
|
+
#
|
216
|
+
# The word will be pluralized using rules defined for the locale
|
217
|
+
# (you must define your own inflection rules for languages other than English).
|
218
|
+
# See ActiveSupport::Inflector.pluralize
|
203
219
|
#
|
204
220
|
# pluralize(1, 'person')
|
205
221
|
# # => 1 person
|
@@ -207,16 +223,19 @@ module ActionView
|
|
207
223
|
# pluralize(2, 'person')
|
208
224
|
# # => 2 people
|
209
225
|
#
|
210
|
-
# pluralize(3, 'person', 'users')
|
226
|
+
# pluralize(3, 'person', plural: 'users')
|
211
227
|
# # => 3 users
|
212
228
|
#
|
213
229
|
# pluralize(0, 'person')
|
214
230
|
# # => 0 people
|
215
|
-
|
216
|
-
|
231
|
+
#
|
232
|
+
# pluralize(2, 'Person', locale: :de)
|
233
|
+
# # => 2 Personen
|
234
|
+
def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale)
|
235
|
+
word = if count == 1 || count.to_s.match?(/^1(\.0+)?$/)
|
217
236
|
singular
|
218
237
|
else
|
219
|
-
plural || singular.pluralize
|
238
|
+
plural || singular.pluralize(locale)
|
220
239
|
end
|
221
240
|
|
222
241
|
"#{count || 0} #{word}"
|
@@ -237,19 +256,23 @@ module ActionView
|
|
237
256
|
#
|
238
257
|
# word_wrap('Once upon a time', line_width: 1)
|
239
258
|
# # => Once\nupon\na\ntime
|
240
|
-
|
241
|
-
|
242
|
-
|
259
|
+
#
|
260
|
+
# You can also specify a custom +break_sequence+ ("\n" by default)
|
261
|
+
#
|
262
|
+
# word_wrap('Once upon a time', line_width: 1, break_sequence: "\r\n")
|
263
|
+
# # => Once\r\nupon\r\na\r\ntime
|
264
|
+
def word_wrap(text, line_width: 80, break_sequence: "\n")
|
243
265
|
text.split("\n").collect! do |line|
|
244
|
-
line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1
|
245
|
-
end *
|
266
|
+
line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").rstrip : line
|
267
|
+
end * break_sequence
|
246
268
|
end
|
247
269
|
|
248
270
|
# 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
|
271
|
+
# Two or more consecutive newlines(<tt>\n\n</tt> or <tt>\r\n\r\n</tt>) are
|
272
|
+
# considered a paragraph and wrapped in <tt><p></tt> tags. One newline
|
273
|
+
# (<tt>\n</tt> or <tt>\r\n</tt>) is considered a linebreak and a
|
274
|
+
# <tt><br /></tt> tag is appended. This method does not remove the
|
275
|
+
# newlines from the +text+.
|
253
276
|
#
|
254
277
|
# You can pass any HTML attributes into <tt>html_options</tt>. These
|
255
278
|
# will be added to all created paragraphs.
|
@@ -309,7 +332,7 @@ module ActionView
|
|
309
332
|
# <table>
|
310
333
|
# <% @items.each do |item| %>
|
311
334
|
# <tr class="<%= cycle("odd", "even") -%>">
|
312
|
-
# <td
|
335
|
+
# <td><%= item %></td>
|
313
336
|
# </tr>
|
314
337
|
# <% end %>
|
315
338
|
# </table>
|
@@ -334,7 +357,7 @@ module ActionView
|
|
334
357
|
# <% end %>
|
335
358
|
def cycle(first_value, *values)
|
336
359
|
options = values.extract_options!
|
337
|
-
name = options.fetch(:name,
|
360
|
+
name = options.fetch(:name, "default")
|
338
361
|
|
339
362
|
values.unshift(*first_value)
|
340
363
|
|
@@ -384,7 +407,7 @@ module ActionView
|
|
384
407
|
cycle.reset if cycle
|
385
408
|
end
|
386
409
|
|
387
|
-
class Cycle
|
410
|
+
class Cycle # :nodoc:
|
388
411
|
attr_reader :values
|
389
412
|
|
390
413
|
def initialize(first_value, *values)
|
@@ -403,22 +426,21 @@ module ActionView
|
|
403
426
|
def to_s
|
404
427
|
value = @values[@index].to_s
|
405
428
|
@index = next_index
|
406
|
-
|
429
|
+
value
|
407
430
|
end
|
408
431
|
|
409
432
|
private
|
433
|
+
def next_index
|
434
|
+
step_index(1)
|
435
|
+
end
|
410
436
|
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
def previous_index
|
416
|
-
step_index(-1)
|
417
|
-
end
|
437
|
+
def previous_index
|
438
|
+
step_index(-1)
|
439
|
+
end
|
418
440
|
|
419
|
-
|
420
|
-
|
421
|
-
|
441
|
+
def step_index(n)
|
442
|
+
(@index + n) % @values.size
|
443
|
+
end
|
422
444
|
end
|
423
445
|
|
424
446
|
private
|
@@ -427,7 +449,7 @@ module ActionView
|
|
427
449
|
# uses an instance variable of ActionView::Base.
|
428
450
|
def get_cycle(name)
|
429
451
|
@_cycles = Hash.new unless defined?(@_cycles)
|
430
|
-
|
452
|
+
@_cycles[name]
|
431
453
|
end
|
432
454
|
|
433
455
|
def set_cycle(name, cycle_object)
|
@@ -449,18 +471,25 @@ module ActionView
|
|
449
471
|
radius = options.fetch(:radius, 100)
|
450
472
|
omission = options.fetch(:omission, "...")
|
451
473
|
|
452
|
-
|
453
|
-
|
454
|
-
|
474
|
+
if separator != ""
|
475
|
+
part = part.split(separator)
|
476
|
+
part.delete("")
|
477
|
+
end
|
455
478
|
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
479
|
+
affix = part.length > radius ? omission : ""
|
480
|
+
|
481
|
+
part =
|
482
|
+
if part_position == :first
|
483
|
+
part.last(radius)
|
484
|
+
else
|
485
|
+
part.first(radius)
|
486
|
+
end
|
487
|
+
|
488
|
+
if separator != ""
|
489
|
+
part = part.join(separator)
|
461
490
|
end
|
462
491
|
|
463
|
-
return affix, part
|
492
|
+
return affix, part
|
464
493
|
end
|
465
494
|
end
|
466
495
|
end
|
@@ -1,99 +1,136 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_view/helpers/tag_helper"
|
4
|
+
require "active_support/html_safe_translation"
|
4
5
|
|
5
6
|
module ActionView
|
6
7
|
# = Action View Translation Helpers
|
7
|
-
module Helpers
|
8
|
+
module Helpers # :nodoc:
|
8
9
|
module TranslationHelper
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
9
12
|
include TagHelper
|
10
|
-
|
13
|
+
|
14
|
+
# Specify whether an error should be raised for missing translations
|
15
|
+
singleton_class.attr_accessor :raise_on_missing_translations
|
16
|
+
|
17
|
+
included do
|
18
|
+
mattr_accessor :debug_missing_translation, default: true
|
19
|
+
end
|
20
|
+
|
21
|
+
# Delegates to <tt>I18n#translate</tt> but also performs three additional
|
22
|
+
# functions.
|
11
23
|
#
|
12
|
-
# First, it will ensure that any thrown +MissingTranslation+ messages will
|
13
|
-
#
|
24
|
+
# First, it will ensure that any thrown +MissingTranslation+ messages will
|
25
|
+
# be rendered as inline spans that:
|
14
26
|
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
27
|
+
# * Have a <tt>translation-missing</tt> class applied
|
28
|
+
# * Contain the missing key as the value of the +title+ attribute
|
29
|
+
# * Have a titleized version of the last key segment as text
|
18
30
|
#
|
19
|
-
#
|
20
|
-
# <
|
21
|
-
# This way your views will display rather reasonable strings but it will still
|
22
|
-
# be easy to spot missing translations.
|
31
|
+
# For example, the value returned for the missing translation key
|
32
|
+
# <tt>"blog.post.title"</tt> will be:
|
23
33
|
#
|
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.
|
34
|
+
# <span
|
35
|
+
# class="translation_missing"
|
36
|
+
# title="translation missing: en.blog.post.title">Title</span>
|
31
37
|
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
38
|
+
# This allows for views to display rather reasonable strings while still
|
39
|
+
# giving developers a way to find missing translations.
|
40
|
+
#
|
41
|
+
# If you would prefer missing translations to raise an error, you can
|
42
|
+
# opt out of span-wrapping behavior globally by setting
|
43
|
+
# <tt>config.i18n.raise_on_missing_translations = true</tt> or
|
44
|
+
# individually by passing <tt>raise: true</tt> as an option to
|
45
|
+
# <tt>translate</tt>.
|
46
|
+
#
|
47
|
+
# Second, if the key starts with a period <tt>translate</tt> will scope
|
48
|
+
# the key by the current partial. Calling <tt>translate(".foo")</tt> from
|
49
|
+
# the <tt>people/index.html.erb</tt> template is equivalent to calling
|
50
|
+
# <tt>translate("people.index.foo")</tt>. This makes it less
|
51
|
+
# repetitive to translate many keys within the same partial and provides
|
52
|
+
# a convention to scope keys consistently.
|
53
|
+
#
|
54
|
+
# Third, the translation will be marked as <tt>html_safe</tt> if the key
|
55
|
+
# has the suffix "_html" or the last element of the key is "html". Calling
|
56
|
+
# <tt>translate("footer_html")</tt> or <tt>translate("footer.html")</tt>
|
57
|
+
# will return an HTML safe string that won't be escaped by other HTML
|
58
|
+
# helper methods. This naming convention helps to identify translations
|
59
|
+
# that include HTML tags so that you know what kind of output to expect
|
60
|
+
# when you call translate in a template and translators know which keys
|
61
|
+
# they can provide HTML values for.
|
62
|
+
#
|
63
|
+
# To access the translated text along with the fully resolved
|
64
|
+
# translation key, <tt>translate</tt> accepts a block:
|
65
|
+
#
|
66
|
+
# <%= translate(".relative_key") do |translation, resolved_key| %>
|
67
|
+
# <span title="<%= resolved_key %>"><%= translation %></span>
|
68
|
+
# <% end %>
|
69
|
+
#
|
70
|
+
# This enables annotate translated text to be aware of the scope it was
|
71
|
+
# resolved against.
|
72
|
+
#
|
73
|
+
def translate(key, **options)
|
74
|
+
return key.map { |k| translate(k, **options) } if key.is_a?(Array)
|
75
|
+
key = key&.to_s unless key.is_a?(Symbol)
|
46
76
|
|
47
|
-
|
48
|
-
|
49
|
-
# 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 || (options.key?(:rescue_format) && options[:rescue_format].nil?)
|
51
|
-
raise_error = false
|
52
|
-
i18n_raise = false
|
53
|
-
else
|
54
|
-
raise_error = options[:raise] || options[:rescue_format] || ActionView::Base.raise_on_missing_translations
|
55
|
-
i18n_raise = true
|
77
|
+
alternatives = if options.key?(:default)
|
78
|
+
options[:default].is_a?(Array) ? options.delete(:default).compact : [options.delete(:default)]
|
56
79
|
end
|
57
80
|
|
58
|
-
if
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
81
|
+
options[:raise] = true if options[:raise].nil? && TranslationHelper.raise_on_missing_translations
|
82
|
+
default = MISSING_TRANSLATION
|
83
|
+
|
84
|
+
translation = while key || alternatives.present?
|
85
|
+
if alternatives.blank? && !options[:raise].nil?
|
86
|
+
default = NO_DEFAULT # let I18n handle missing translation
|
64
87
|
end
|
65
|
-
translation = I18n.translate(scope_key_by_partial(key), html_safe_options.merge(raise: i18n_raise))
|
66
88
|
|
67
|
-
|
68
|
-
|
69
|
-
|
89
|
+
key = scope_key_by_partial(key)
|
90
|
+
|
91
|
+
translated = ActiveSupport::HtmlSafeTranslation.translate(key, **options, default: default)
|
92
|
+
|
93
|
+
break translated unless translated.equal?(MISSING_TRANSLATION)
|
94
|
+
|
95
|
+
if alternatives.present? && !alternatives.first.is_a?(Symbol)
|
96
|
+
break alternatives.first && I18n.translate(**options, default: alternatives)
|
97
|
+
end
|
98
|
+
|
99
|
+
first_key ||= key
|
100
|
+
key = alternatives&.shift
|
70
101
|
end
|
71
|
-
|
72
|
-
if
|
73
|
-
|
74
|
-
|
75
|
-
raise e if raise_error
|
76
|
-
|
77
|
-
keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
|
78
|
-
content_tag('span', keys.last.to_s.titleize, :class => 'translation_missing', :title => "translation missing: #{keys.join('.')}")
|
102
|
+
|
103
|
+
if key.nil? && !first_key.nil?
|
104
|
+
translation = missing_translation(first_key, options)
|
105
|
+
key = first_key
|
79
106
|
end
|
107
|
+
|
108
|
+
block_given? ? yield(translation, key) : translation
|
80
109
|
end
|
81
110
|
alias :t :translate
|
82
111
|
|
83
112
|
# Delegates to <tt>I18n.localize</tt> with no additional functionality.
|
84
113
|
#
|
85
|
-
# See
|
114
|
+
# See https://www.rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
|
86
115
|
# for more information.
|
87
|
-
def localize(
|
88
|
-
I18n.localize(
|
116
|
+
def localize(object, **options)
|
117
|
+
I18n.localize(object, **options)
|
89
118
|
end
|
90
119
|
alias :l :localize
|
91
120
|
|
92
121
|
private
|
122
|
+
MISSING_TRANSLATION = Object.new
|
123
|
+
private_constant :MISSING_TRANSLATION
|
124
|
+
|
125
|
+
NO_DEFAULT = [].freeze
|
126
|
+
private_constant :NO_DEFAULT
|
127
|
+
|
93
128
|
def scope_key_by_partial(key)
|
94
|
-
if key
|
129
|
+
if key&.start_with?(".")
|
95
130
|
if @virtual_path
|
96
|
-
@
|
131
|
+
@_scope_key_by_partial_cache ||= {}
|
132
|
+
@_scope_key_by_partial_cache[@virtual_path] ||= @virtual_path.gsub(%r{/_?}, ".")
|
133
|
+
"#{@_scope_key_by_partial_cache[@virtual_path]}#{key}"
|
97
134
|
else
|
98
135
|
raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
|
99
136
|
end
|
@@ -102,8 +139,22 @@ module ActionView
|
|
102
139
|
end
|
103
140
|
end
|
104
141
|
|
105
|
-
def
|
106
|
-
|
142
|
+
def missing_translation(key, options)
|
143
|
+
keys = I18n.normalize_keys(options[:locale] || I18n.locale, key, options[:scope])
|
144
|
+
|
145
|
+
title = +"translation missing: #{keys.join(".")}"
|
146
|
+
|
147
|
+
options.each do |name, value|
|
148
|
+
unless name == :scope
|
149
|
+
title << ", " << name.to_s << ": " << ERB::Util.html_escape(value)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
if ActionView::Base.debug_missing_translation
|
154
|
+
content_tag("span", keys.last.to_s.titleize, class: "translation_missing", title: title)
|
155
|
+
else
|
156
|
+
title
|
157
|
+
end
|
107
158
|
end
|
108
159
|
end
|
109
160
|
end
|