actionview 4.2.11.1 → 5.2.7.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.

Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +118 -238
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +5 -6
  5. data/lib/action_view/base.rb +38 -28
  6. data/lib/action_view/buffers.rb +3 -1
  7. data/lib/action_view/context.rb +3 -3
  8. data/lib/action_view/dependency_tracker.rb +54 -20
  9. data/lib/action_view/digestor.rb +94 -83
  10. data/lib/action_view/flows.rb +11 -11
  11. data/lib/action_view/gem_version.rb +4 -2
  12. data/lib/action_view/helpers/active_model_helper.rb +17 -11
  13. data/lib/action_view/helpers/asset_tag_helper.rb +244 -62
  14. data/lib/action_view/helpers/asset_url_helper.rb +170 -67
  15. data/lib/action_view/helpers/atom_feed_helper.rb +19 -17
  16. data/lib/action_view/helpers/cache_helper.rb +105 -42
  17. data/lib/action_view/helpers/capture_helper.rb +16 -13
  18. data/lib/action_view/helpers/controller_helper.rb +15 -4
  19. data/lib/action_view/helpers/csp_helper.rb +24 -0
  20. data/lib/action_view/helpers/csrf_helper.rb +7 -5
  21. data/lib/action_view/helpers/date_helper.rb +170 -112
  22. data/lib/action_view/helpers/debug_helper.rb +7 -6
  23. data/lib/action_view/helpers/form_helper.rb +521 -127
  24. data/lib/action_view/helpers/form_options_helper.rb +109 -63
  25. data/lib/action_view/helpers/form_tag_helper.rb +110 -67
  26. data/lib/action_view/helpers/javascript_helper.rb +27 -12
  27. data/lib/action_view/helpers/number_helper.rb +77 -58
  28. data/lib/action_view/helpers/output_safety_helper.rb +36 -4
  29. data/lib/action_view/helpers/record_tag_helper.rb +14 -99
  30. data/lib/action_view/helpers/rendering_helper.rb +6 -5
  31. data/lib/action_view/helpers/sanitize_helper.rb +20 -15
  32. data/lib/action_view/helpers/tag_helper.rb +229 -73
  33. data/lib/action_view/helpers/tags/base.rb +134 -97
  34. data/lib/action_view/helpers/tags/check_box.rb +20 -18
  35. data/lib/action_view/helpers/tags/checkable.rb +4 -2
  36. data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -33
  37. data/lib/action_view/helpers/tags/collection_helpers.rb +70 -36
  38. data/lib/action_view/helpers/tags/collection_radio_buttons.rb +6 -11
  39. data/lib/action_view/helpers/tags/collection_select.rb +4 -2
  40. data/lib/action_view/helpers/tags/color_field.rb +3 -1
  41. data/lib/action_view/helpers/tags/date_field.rb +2 -0
  42. data/lib/action_view/helpers/tags/date_select.rb +38 -36
  43. data/lib/action_view/helpers/tags/datetime_field.rb +4 -2
  44. data/lib/action_view/helpers/tags/datetime_local_field.rb +2 -0
  45. data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
  46. data/lib/action_view/helpers/tags/email_field.rb +2 -0
  47. data/lib/action_view/helpers/tags/file_field.rb +2 -0
  48. data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
  49. data/lib/action_view/helpers/tags/hidden_field.rb +2 -0
  50. data/lib/action_view/helpers/tags/label.rb +3 -1
  51. data/lib/action_view/helpers/tags/month_field.rb +2 -0
  52. data/lib/action_view/helpers/tags/number_field.rb +2 -0
  53. data/lib/action_view/helpers/tags/password_field.rb +3 -1
  54. data/lib/action_view/helpers/tags/placeholderable.rb +3 -1
  55. data/lib/action_view/helpers/tags/radio_button.rb +7 -5
  56. data/lib/action_view/helpers/tags/range_field.rb +2 -0
  57. data/lib/action_view/helpers/tags/search_field.rb +14 -9
  58. data/lib/action_view/helpers/tags/select.rb +11 -9
  59. data/lib/action_view/helpers/tags/tel_field.rb +2 -0
  60. data/lib/action_view/helpers/tags/text_area.rb +4 -2
  61. data/lib/action_view/helpers/tags/text_field.rb +8 -7
  62. data/lib/action_view/helpers/tags/time_field.rb +2 -0
  63. data/lib/action_view/helpers/tags/time_select.rb +2 -0
  64. data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
  65. data/lib/action_view/helpers/tags/translator.rb +17 -13
  66. data/lib/action_view/helpers/tags/url_field.rb +2 -0
  67. data/lib/action_view/helpers/tags/week_field.rb +2 -0
  68. data/lib/action_view/helpers/tags.rb +3 -1
  69. data/lib/action_view/helpers/text_helper.rb +55 -36
  70. data/lib/action_view/helpers/translation_helper.rb +74 -32
  71. data/lib/action_view/helpers/url_helper.rb +159 -104
  72. data/lib/action_view/helpers.rb +5 -1
  73. data/lib/action_view/layouts.rb +65 -58
  74. data/lib/action_view/log_subscriber.rb +60 -8
  75. data/lib/action_view/lookup_context.rb +80 -65
  76. data/lib/action_view/model_naming.rb +3 -1
  77. data/lib/action_view/path_set.rb +30 -19
  78. data/lib/action_view/railtie.rb +39 -6
  79. data/lib/action_view/record_identifier.rb +53 -25
  80. data/lib/action_view/renderer/abstract_renderer.rb +21 -15
  81. data/lib/action_view/renderer/partial_renderer/collection_caching.rb +57 -0
  82. data/lib/action_view/renderer/partial_renderer.rb +218 -214
  83. data/lib/action_view/renderer/renderer.rb +8 -6
  84. data/lib/action_view/renderer/streaming_template_renderer.rb +50 -48
  85. data/lib/action_view/renderer/template_renderer.rb +67 -66
  86. data/lib/action_view/rendering.rb +19 -14
  87. data/lib/action_view/routing_url_for.rb +27 -17
  88. data/lib/action_view/tasks/cache_digests.rake +25 -0
  89. data/lib/action_view/template/error.rb +16 -16
  90. data/lib/action_view/template/handlers/builder.rb +10 -11
  91. data/lib/action_view/template/handlers/erb/erubi.rb +83 -0
  92. data/lib/action_view/template/handlers/erb.rb +9 -80
  93. data/lib/action_view/template/handlers/html.rb +11 -0
  94. data/lib/action_view/template/handlers/raw.rb +3 -3
  95. data/lib/action_view/template/handlers.rb +11 -7
  96. data/lib/action_view/template/html.rb +5 -5
  97. data/lib/action_view/template/resolver.rb +140 -115
  98. data/lib/action_view/template/text.rb +8 -9
  99. data/lib/action_view/template/types.rb +18 -18
  100. data/lib/action_view/template.rb +56 -31
  101. data/lib/action_view/test_case.rb +50 -29
  102. data/lib/action_view/testing/resolvers.rb +31 -31
  103. data/lib/action_view/version.rb +3 -1
  104. data/lib/action_view/view_paths.rb +28 -34
  105. data/lib/action_view.rb +8 -7
  106. data/lib/assets/compiled/rails-ujs.js +720 -0
  107. metadata +28 -27
  108. data/lib/action_view/tasks/dependencies.rake +0 -23
@@ -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
- :selected => @options.fetch(:selected) { value(@object) },
17
- :disabled => @options[:disabled]
18
+ selected: @options.fetch(:selected) { value },
19
+ disabled: @options[:disabled]
18
20
  }
19
21
 
20
22
  option_tags = if grouped_choices?
@@ -28,13 +30,13 @@ module ActionView
28
30
 
29
31
  private
30
32
 
31
- # Grouped choices look like this:
32
- #
33
- # [nil, []]
34
- # { nil => [] }
35
- def grouped_choices?
36
- !@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last
37
- end
33
+ # Grouped choices look like this:
34
+ #
35
+ # [nil, []]
36
+ # { nil => [] }
37
+ def grouped_choices?
38
+ !@choices.blank? && @choices.first.respond_to?(:last) && Array === @choices.first.last
39
+ end
38
40
  end
39
41
  end
40
42
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  module Helpers
3
5
  module Tags # :nodoc:
@@ -1,4 +1,6 @@
1
- require 'action_view/helpers/tags/placeholderable'
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(object) }, options)
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
- require 'action_view/helpers/tags/placeholderable'
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,22 @@ 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(object) } unless field_type == "file"
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 ||= self.name.split("::").last.sub("Field", "").downcase
22
+ @field_type ||= name.split("::").last.sub("Field", "").downcase
22
23
  end
23
24
  end
24
25
 
25
26
  private
26
27
 
27
- def field_type
28
- self.class.field_type
29
- end
28
+ def field_type
29
+ self.class.field_type
30
+ end
30
31
  end
31
32
  end
32
33
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  module Helpers
3
5
  module Tags # :nodoc:
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  module Helpers
3
5
  module Tags # :nodoc:
@@ -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(@object) || @options[:default], @priority_zones, @options[:model] || ActiveSupport::TimeZone), @options, @html_options
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,28 @@ module ActionView
14
16
  translated_attribute || human_attribute_name
15
17
  end
16
18
 
19
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
20
+ # Workaround for Ruby 2.2 "private attribute?" warning.
17
21
  protected
18
22
 
19
- attr_reader :object_name, :method_and_value, :scope, :model
23
+ attr_reader :object_name, :method_and_value, :scope, :model
20
24
 
21
25
  private
22
26
 
23
- def i18n_default
24
- if model
25
- key = model.model_name.i18n_key
26
- ["#{key}.#{method_and_value}".to_sym, ""]
27
- else
28
- ""
27
+ def i18n_default
28
+ if model
29
+ key = model.model_name.i18n_key
30
+ ["#{key}.#{method_and_value}".to_sym, ""]
31
+ else
32
+ ""
33
+ end
29
34
  end
30
- end
31
35
 
32
- def human_attribute_name
33
- if model && model.class.respond_to?(:human_attribute_name)
34
- model.class.human_attribute_name(method_and_value)
36
+ def human_attribute_name
37
+ if model && model.class.respond_to?(:human_attribute_name)
38
+ model.class.human_attribute_name(method_and_value)
39
+ end
35
40
  end
36
- end
37
41
  end
38
42
  end
39
43
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  module Helpers
3
5
  module Tags # :nodoc:
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
4
  module Helpers
3
5
  module Tags # :nodoc:
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActionView
2
- module Helpers
4
+ module Helpers #:nodoc:
3
5
  module Tags #:nodoc:
4
6
  extend ActiveSupport::Autoload
5
7
 
@@ -1,5 +1,7 @@
1
- require 'active_support/core_ext/string/filters'
2
- require 'active_support/core_ext/array/extract_options'
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 by default sanitize the given content, but do not escape it.
15
- # This means HTML tags will appear in the page but all malicious code will be removed.
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...
@@ -182,7 +189,7 @@ module ActionView
182
189
  unless separator.empty?
183
190
  text.split(separator).each do |value|
184
191
  if value.match(regex)
185
- regex = phrase = value
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
- def pluralize(count, singular, plural = nil)
216
- word = if (count == 1 || count =~ /^1(\.0+)?$/)
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
- def word_wrap(text, options = {})
241
- line_width = options.fetch(:line_width, 80)
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\n").strip : line
245
- end * "\n"
262
+ line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").strip : 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 considered as a
250
- # paragraph and wrapped in <tt><p></tt> tags. One newline (<tt>\n</tt>) is
251
- # considered as a linebreak and a <tt><br /></tt> tag is appended. This
252
- # method does not remove the newlines from the +text+.
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>item</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, 'default')
356
+ name = options.fetch(:name, "default")
338
357
 
339
358
  values.unshift(*first_value)
340
359
 
@@ -403,22 +422,22 @@ module ActionView
403
422
  def to_s
404
423
  value = @values[@index].to_s
405
424
  @index = next_index
406
- return value
425
+ value
407
426
  end
408
427
 
409
428
  private
410
429
 
411
- def next_index
412
- step_index(1)
413
- end
430
+ def next_index
431
+ step_index(1)
432
+ end
414
433
 
415
- def previous_index
416
- step_index(-1)
417
- end
434
+ def previous_index
435
+ step_index(-1)
436
+ end
418
437
 
419
- def step_index(n)
420
- (@index + n) % @values.size
421
- end
438
+ def step_index(n)
439
+ (@index + n) % @values.size
440
+ end
422
441
  end
423
442
 
424
443
  private
@@ -427,7 +446,7 @@ module ActionView
427
446
  # uses an instance variable of ActionView::Base.
428
447
  def get_cycle(name)
429
448
  @_cycles = Hash.new unless defined?(@_cycles)
430
- return @_cycles[name]
449
+ @_cycles[name]
431
450
  end
432
451
 
433
452
  def set_cycle(name, cycle_object)
@@ -1,40 +1,62 @@
1
- require 'action_view/helpers/tag_helper'
2
- require 'active_support/core_ext/string/access'
3
- require 'i18n/exceptions'
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
- # Delegates to <tt>I18n#translate</tt> but also performs three additional functions.
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 be turned
13
- # into inline spans that:
22
+ # First, it will ensure that any thrown +MissingTranslation+ messages will
23
+ # be rendered as inline spans that:
14
24
  #
15
- # * have a "translation-missing" class set,
16
- # * contain the missing key as a title attribute and
17
- # * a titleized version of the last key segment as a text.
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
- # E.g. the value returned for a missing translation key :"blog.post.title" will be
20
- # <span class="translation_missing" title="translation missing: en.blog.post.title">Title</span>.
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
- # Second, it'll scope the key by the current partial if the key starts
25
- # with a period. So if you call <tt>translate(".foo")</tt> from the
26
- # <tt>people/index.html.erb</tt> template, you'll actually be calling
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
- # Third, it'll mark the translation as safe HTML if the key has the suffix
33
- # "_html" or the last element of the key is the word "html". For example,
34
- # calling translate("footer_html") or translate("footer.html") will return
35
- # a safe HTML string that won't be escaped by other HTML helper methods. This
36
- # naming convention helps to identify translations that include HTML tags so that
37
- # you know what kind of output to expect when you call translate in a template.
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.
38
60
  def translate(key, options = {})
39
61
  options = options.dup
40
62
  has_default = options.has_key?(:default)
@@ -47,24 +69,32 @@ module ActionView
47
69
  # If the user has explicitly decided to NOT raise errors, pass that option to I18n.
48
70
  # Otherwise, tell I18n to raise an exception, which we rescue further in this method.
49
71
  # 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?)
72
+ if options[:raise] == false
51
73
  raise_error = false
52
74
  i18n_raise = false
53
75
  else
54
- raise_error = options[:raise] || options[:rescue_format] || ActionView::Base.raise_on_missing_translations
76
+ raise_error = options[:raise] || ActionView::Base.raise_on_missing_translations
55
77
  i18n_raise = true
56
78
  end
57
79
 
58
80
  if html_safe_translation_key?(key)
59
81
  html_safe_options = options.dup
82
+
60
83
  options.except(*I18n::RESERVED_KEYS).each do |name, value|
61
84
  unless name == :count && value.is_a?(Numeric)
62
85
  html_safe_options[name] = ERB::Util.html_escape(value.to_s)
63
86
  end
64
87
  end
88
+
89
+ html_safe_options[:default] = MISSING_TRANSLATION unless html_safe_options[:default].blank?
90
+
65
91
  translation = I18n.translate(scope_key_by_partial(key), html_safe_options.merge(raise: i18n_raise))
66
92
 
67
- translation.respond_to?(:html_safe) ? translation.html_safe : translation
93
+ if translation.equal?(MISSING_TRANSLATION)
94
+ options[:default].first
95
+ else
96
+ translation.respond_to?(:html_safe) ? translation.html_safe : translation
97
+ end
68
98
  else
69
99
  I18n.translate(scope_key_by_partial(key), options.merge(raise: i18n_raise))
70
100
  end
@@ -75,7 +105,16 @@ module ActionView
75
105
  raise e if raise_error
76
106
 
77
107
  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('.')}")
108
+ title = "translation missing: #{keys.join('.')}".dup
109
+
110
+ interpolations = options.except(:default, :scope)
111
+ if interpolations.any?
112
+ title << ", " << interpolations.map { |k, v| "#{k}: #{ERB::Util.html_escape(v)}" }.join(", ")
113
+ end
114
+
115
+ return title unless ActionView::Base.debug_missing_translation
116
+
117
+ content_tag("span", keys.last.to_s.titleize, class: "translation_missing", title: title)
79
118
  end
80
119
  end
81
120
  alias :t :translate
@@ -90,6 +129,9 @@ module ActionView
90
129
  alias :l :localize
91
130
 
92
131
  private
132
+ MISSING_TRANSLATION = Object.new
133
+ private_constant :MISSING_TRANSLATION
134
+
93
135
  def scope_key_by_partial(key)
94
136
  if key.to_s.first == "."
95
137
  if @virtual_path
@@ -103,7 +145,7 @@ module ActionView
103
145
  end
104
146
 
105
147
  def html_safe_translation_key?(key)
106
- key.to_s =~ /(\b|_|\.)html$/
148
+ /(\b|_|\.)html$/.match?(key.to_s)
107
149
  end
108
150
  end
109
151
  end