2017-07-23 11:36:41 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2016-08-06 12:50:17 -04:00
|
|
|
require "abstract_unit"
|
2008-07-06 13:00:55 -04:00
|
|
|
|
2014-11-19 09:08:56 -05:00
|
|
|
module I18n
|
|
|
|
class CustomExceptionHandler
|
|
|
|
def self.call(exception, locale, key, options)
|
2016-08-06 12:50:17 -04:00
|
|
|
"from CustomExceptionHandler"
|
2014-11-19 09:08:56 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2010-01-26 14:43:35 -05:00
|
|
|
class TranslationHelperTest < ActiveSupport::TestCase
|
2008-07-06 13:00:55 -04:00
|
|
|
include ActionView::Helpers::TranslationHelper
|
2010-01-26 14:43:35 -05:00
|
|
|
|
2010-11-14 12:46:40 -05:00
|
|
|
attr_reader :request, :view
|
|
|
|
|
2014-06-14 04:32:36 -04:00
|
|
|
setup do
|
2010-11-14 12:46:40 -05:00
|
|
|
I18n.backend.store_translations(:en,
|
2016-08-06 13:36:34 -04:00
|
|
|
translations: {
|
|
|
|
templates: {
|
|
|
|
found: { foo: "Foo" },
|
Extend `ActionView::Helpers#translate` to yield
This commit extends the `ActionView::Helpers#translate` (and by way of
alias, `#t`) helper methods to accept blocks.
When invoked with a block, the `translate` call will yield the
translated text as its first block argument, along with the resolved
translation key as its second:
```erb
<%= translate(".key") do |translation, resolved_key| %>
<span data-i18n-key="<%= resolved_key %>"><%= translation %></span>
<% end %>
```
In cases where relative translation keys are foregone in lieu of fully
qualified keys, or if the caller is not interested in the resolved key,
the second block argument can be omitted:
```erb
<%= translate("action.template.key") do |translation| %>
<p><%= translation %></p>
<p><%= translation %>, but a second time</p>
<% end %>
```
A benefit of yielding the translation is that it enabled template-local
variable re-use. Alternatively, [`Object#tap`][tap] could be used.
Prior to this commit, however, the resolution of the translation key was
internal to `ActionView`, and unavailable to the caller (unless they
were willing to explicitly determine the resolved key themselves). By
making it available as a block parameter, it could be used to annotate
the translated value in the resulting elements.
[tap]: https://ruby-doc.org/core-2.7.0/Object.html#method-i-tap
2020-03-06 10:34:26 -05:00
|
|
|
found_yield_single_argument: { foo: "Foo" },
|
|
|
|
found_yield_block: { foo: "Foo" },
|
2016-08-06 13:36:34 -04:00
|
|
|
array: { foo: { bar: "Foo Bar" } },
|
2020-12-30 06:50:03 -05:00
|
|
|
default: { foo: "Foo" },
|
|
|
|
partial: { foo: "Partial foo" }
|
2010-11-14 12:46:40 -05:00
|
|
|
},
|
2016-08-06 13:36:34 -04:00
|
|
|
foo: "Foo",
|
|
|
|
hello: "<a>Hello World</a>",
|
|
|
|
html: "<a>Hello World</a>",
|
|
|
|
hello_html: "<a>Hello World</a>",
|
|
|
|
interpolated_html: "<a>Hello %{word}</a>",
|
|
|
|
array_html: %w(foo bar),
|
|
|
|
array: %w(foo bar),
|
|
|
|
count_html: {
|
|
|
|
one: "<a>One %{count}</a>",
|
|
|
|
other: "<a>Other %{count}</a>"
|
2011-11-19 08:19:20 -05:00
|
|
|
}
|
2010-11-14 12:46:40 -05:00
|
|
|
}
|
|
|
|
)
|
2019-01-23 19:11:54 -05:00
|
|
|
view_paths = ActionController::Base.view_paths
|
2019-01-23 17:19:50 -05:00
|
|
|
view_paths.each(&:clear_cache)
|
|
|
|
@view = ::ActionView::Base.with_empty_template_cache.with_view_paths(view_paths, {})
|
2009-02-03 21:25:37 -05:00
|
|
|
end
|
2010-08-14 01:13:00 -04:00
|
|
|
|
2014-06-14 04:32:36 -04:00
|
|
|
teardown do
|
|
|
|
I18n.backend.reload!
|
|
|
|
end
|
|
|
|
|
2015-05-04 17:46:25 -04:00
|
|
|
def test_delegates_setting_to_i18n
|
Improve Action View `translate` helper
This disentangles the control flow between Action View's `translate` and
I18n's `translate`. In doing so, it fixes a handful of corner cases,
for which tests have now been added. It also reduces memory
allocations, and improves speed when using a default:
**Memory**
```ruby
require "benchmark/memory"
Benchmark.memory do |x|
x.report("warmup") { translate(:"translations.foo"); translate(:"translations.html") }
x.report("text") { translate(:"translations.foo") }
x.report("html") { translate(:"translations.html") }
x.report("text 1 default") { translate(:"translations.missing", default: :"translations.foo") }
x.report("html 1 default") { translate(:"translations.missing", default: :"translations.html") }
x.report("text 2 defaults") { translate(:"translations.missing", default: [:"translations.missing", :"translations.foo"]) }
x.report("html 2 defaults") { translate(:"translations.missing", default: [:"translations.missing", :"translations.html"]) }
end
```
Before:
```
text 1.240k memsize ( 0.000 retained)
13.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
html 1.600k memsize ( 0.000 retained)
19.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
text 1 default 4.728k memsize ( 1.200k retained)
39.000 objects ( 4.000 retained)
5.000 strings ( 0.000 retained)
html 1 default 5.056k memsize ( 1.160k retained)
41.000 objects ( 3.000 retained)
4.000 strings ( 0.000 retained)
text 2 defaults 7.464k memsize ( 2.392k retained)
54.000 objects ( 6.000 retained)
4.000 strings ( 0.000 retained)
html 2 defaults 7.944k memsize ( 2.384k retained)
60.000 objects ( 6.000 retained)
4.000 strings ( 0.000 retained)
```
After:
```
text 952.000 memsize ( 0.000 retained)
9.000 objects ( 0.000 retained)
1.000 strings ( 0.000 retained)
html 1.008k memsize ( 0.000 retained)
10.000 objects ( 0.000 retained)
1.000 strings ( 0.000 retained)
text 1 default 2.400k memsize ( 40.000 retained)
24.000 objects ( 1.000 retained)
4.000 strings ( 0.000 retained)
html 1 default 2.464k memsize ( 0.000 retained)
22.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
text 2 defaults 3.232k memsize ( 0.000 retained)
30.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
html 2 defaults 3.456k memsize ( 0.000 retained)
32.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
```
**Speed**
```ruby
require "benchmark/ips"
Benchmark.ips do |x|
x.report("text") { translate(:"translations.foo") }
x.report("html") { translate(:"translations.html") }
x.report("text 1 default") { translate(:"translations.missing", default: :"translations.foo") }
x.report("html 1 default") { translate(:"translations.missing", default: :"translations.html") }
x.report("text 2 defaults") { translate(:"translations.missing", default: [:"translations.missing", :"translations.foo"]) }
x.report("html 2 defaults") { translate(:"translations.missing", default: [:"translations.missing", :"translations.html"]) }
end
```
Before:
```
text 35.685k (± 0.7%) i/s - 179.050k in 5.017773s
html 28.569k (± 3.1%) i/s - 143.871k in 5.040128s
text 1 default 13.953k (± 2.0%) i/s - 70.737k in 5.071651s
html 1 default 12.507k (± 0.4%) i/s - 63.546k in 5.080908s
text 2 defaults 9.103k (± 0.3%) i/s - 46.308k in 5.087323s
html 2 defaults 8.570k (± 4.3%) i/s - 43.071k in 5.034322s
```
After:
```
text 36.694k (± 2.0%) i/s - 186.864k in 5.094367s
html 30.415k (± 0.5%) i/s - 152.900k in 5.027226s
text 1 default 18.095k (± 2.7%) i/s - 91.086k in 5.036857s
html 1 default 15.934k (± 1.7%) i/s - 80.223k in 5.036085s
text 2 defaults 12.179k (± 0.6%) i/s - 61.659k in 5.062910s
html 2 defaults 11.193k (± 2.1%) i/s - 56.406k in 5.041433s
```
2020-08-05 11:27:50 -04:00
|
|
|
matcher_called = false
|
|
|
|
matcher = ->(key, options) do
|
|
|
|
matcher_called = true
|
|
|
|
assert_equal :foo, key
|
|
|
|
assert_equal "en", options[:locale]
|
|
|
|
end
|
|
|
|
|
|
|
|
I18n.stub(:translate, matcher) do
|
2016-08-06 13:36:34 -04:00
|
|
|
translate :foo, locale: "en"
|
2015-08-22 00:03:53 -04:00
|
|
|
end
|
Improve Action View `translate` helper
This disentangles the control flow between Action View's `translate` and
I18n's `translate`. In doing so, it fixes a handful of corner cases,
for which tests have now been added. It also reduces memory
allocations, and improves speed when using a default:
**Memory**
```ruby
require "benchmark/memory"
Benchmark.memory do |x|
x.report("warmup") { translate(:"translations.foo"); translate(:"translations.html") }
x.report("text") { translate(:"translations.foo") }
x.report("html") { translate(:"translations.html") }
x.report("text 1 default") { translate(:"translations.missing", default: :"translations.foo") }
x.report("html 1 default") { translate(:"translations.missing", default: :"translations.html") }
x.report("text 2 defaults") { translate(:"translations.missing", default: [:"translations.missing", :"translations.foo"]) }
x.report("html 2 defaults") { translate(:"translations.missing", default: [:"translations.missing", :"translations.html"]) }
end
```
Before:
```
text 1.240k memsize ( 0.000 retained)
13.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
html 1.600k memsize ( 0.000 retained)
19.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
text 1 default 4.728k memsize ( 1.200k retained)
39.000 objects ( 4.000 retained)
5.000 strings ( 0.000 retained)
html 1 default 5.056k memsize ( 1.160k retained)
41.000 objects ( 3.000 retained)
4.000 strings ( 0.000 retained)
text 2 defaults 7.464k memsize ( 2.392k retained)
54.000 objects ( 6.000 retained)
4.000 strings ( 0.000 retained)
html 2 defaults 7.944k memsize ( 2.384k retained)
60.000 objects ( 6.000 retained)
4.000 strings ( 0.000 retained)
```
After:
```
text 952.000 memsize ( 0.000 retained)
9.000 objects ( 0.000 retained)
1.000 strings ( 0.000 retained)
html 1.008k memsize ( 0.000 retained)
10.000 objects ( 0.000 retained)
1.000 strings ( 0.000 retained)
text 1 default 2.400k memsize ( 40.000 retained)
24.000 objects ( 1.000 retained)
4.000 strings ( 0.000 retained)
html 1 default 2.464k memsize ( 0.000 retained)
22.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
text 2 defaults 3.232k memsize ( 0.000 retained)
30.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
html 2 defaults 3.456k memsize ( 0.000 retained)
32.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
```
**Speed**
```ruby
require "benchmark/ips"
Benchmark.ips do |x|
x.report("text") { translate(:"translations.foo") }
x.report("html") { translate(:"translations.html") }
x.report("text 1 default") { translate(:"translations.missing", default: :"translations.foo") }
x.report("html 1 default") { translate(:"translations.missing", default: :"translations.html") }
x.report("text 2 defaults") { translate(:"translations.missing", default: [:"translations.missing", :"translations.foo"]) }
x.report("html 2 defaults") { translate(:"translations.missing", default: [:"translations.missing", :"translations.html"]) }
end
```
Before:
```
text 35.685k (± 0.7%) i/s - 179.050k in 5.017773s
html 28.569k (± 3.1%) i/s - 143.871k in 5.040128s
text 1 default 13.953k (± 2.0%) i/s - 70.737k in 5.071651s
html 1 default 12.507k (± 0.4%) i/s - 63.546k in 5.080908s
text 2 defaults 9.103k (± 0.3%) i/s - 46.308k in 5.087323s
html 2 defaults 8.570k (± 4.3%) i/s - 43.071k in 5.034322s
```
After:
```
text 36.694k (± 2.0%) i/s - 186.864k in 5.094367s
html 30.415k (± 0.5%) i/s - 152.900k in 5.027226s
text 1 default 18.095k (± 2.7%) i/s - 91.086k in 5.036857s
html 1 default 15.934k (± 1.7%) i/s - 80.223k in 5.036085s
text 2 defaults 12.179k (± 0.6%) i/s - 61.659k in 5.062910s
html 2 defaults 11.193k (± 2.1%) i/s - 56.406k in 5.041433s
```
2020-08-05 11:27:50 -04:00
|
|
|
|
|
|
|
assert matcher_called
|
2009-02-03 21:25:37 -05:00
|
|
|
end
|
2010-08-14 01:13:00 -04:00
|
|
|
|
2010-11-14 12:46:40 -05:00
|
|
|
def test_delegates_localize_to_i18n
|
|
|
|
@time = Time.utc(2008, 7, 8, 12, 18, 38)
|
2020-02-05 04:49:44 -05:00
|
|
|
assert_called_with(I18n, :localize, [@time, locale: "en"]) do
|
|
|
|
localize @time, locale: "en"
|
2015-08-22 00:03:53 -04:00
|
|
|
end
|
2020-02-05 04:49:44 -05:00
|
|
|
assert_equal "Tue, 08 Jul 2008 12:18:38 +0000", localize(@time, locale: "en")
|
2010-11-14 12:46:40 -05:00
|
|
|
end
|
|
|
|
|
2020-12-09 12:49:29 -05:00
|
|
|
def test_converts_key_to_string_as_necessary
|
|
|
|
key = Struct.new(:to_s).new("translations.foo")
|
|
|
|
assert_equal "Foo", translate(key)
|
|
|
|
assert_equal key, translate(:"translations.missing", default: key)
|
|
|
|
end
|
|
|
|
|
2020-12-10 12:38:11 -05:00
|
|
|
def test_returns_nil_for_nil_key_without_default
|
|
|
|
assert_nil translate(nil)
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_returns_default_for_nil_key_with_default
|
|
|
|
assert_equal "Foo", translate(nil, default: "Foo")
|
|
|
|
assert_equal "Foo", translate(nil, default: :"translations.foo")
|
|
|
|
assert_predicate translate(nil, default: :"translations.html"), :html_safe?
|
|
|
|
end
|
|
|
|
|
2015-12-30 00:12:50 -05:00
|
|
|
def test_returns_missing_translation_message_without_span_wrap
|
2015-12-01 10:49:44 -05:00
|
|
|
old_value = ActionView::Base.debug_missing_translation
|
|
|
|
ActionView::Base.debug_missing_translation = false
|
|
|
|
|
2016-08-06 12:50:17 -04:00
|
|
|
expected = "translation missing: en.translations.missing"
|
2015-12-01 10:49:44 -05:00
|
|
|
assert_equal expected, translate(:"translations.missing")
|
|
|
|
ensure
|
|
|
|
ActionView::Base.debug_missing_translation = old_value
|
|
|
|
end
|
|
|
|
|
2009-02-03 21:25:37 -05:00
|
|
|
def test_returns_missing_translation_message_wrapped_into_span
|
2010-11-14 12:46:40 -05:00
|
|
|
expected = '<span class="translation_missing" title="translation missing: en.translations.missing">Missing</span>'
|
|
|
|
assert_equal expected, translate(:"translations.missing")
|
2011-06-14 20:20:50 -04:00
|
|
|
assert_equal true, translate(:"translations.missing").html_safe?
|
2009-02-03 21:25:37 -05:00
|
|
|
end
|
|
|
|
|
2015-09-15 04:20:10 -04:00
|
|
|
def test_returns_missing_translation_message_with_unescaped_interpolation
|
|
|
|
expected = '<span class="translation_missing" title="translation missing: en.translations.missing, name: Kir, year: 2015, vulnerable: &quot; onclick=&quot;alert()&quot;">Missing</span>'
|
|
|
|
assert_equal expected, translate(:"translations.missing", name: "Kir", year: "2015", vulnerable: %{" onclick="alert()"})
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate translate(:"translations.missing"), :html_safe?
|
2015-09-15 04:20:10 -04:00
|
|
|
end
|
|
|
|
|
2015-10-28 16:52:33 -04:00
|
|
|
def test_returns_missing_translation_message_does_filters_out_i18n_options
|
|
|
|
expected = '<span class="translation_missing" title="translation missing: en.translations.missing, year: 2015">Missing</span>'
|
2016-08-06 12:50:17 -04:00
|
|
|
assert_equal expected, translate(:"translations.missing", year: "2015", default: [])
|
2015-10-28 16:52:33 -04:00
|
|
|
|
|
|
|
expected = '<span class="translation_missing" title="translation missing: en.scoped.translations.missing, year: 2015">Missing</span>'
|
2016-08-06 12:50:17 -04:00
|
|
|
assert_equal expected, translate(:"translations.missing", year: "2015", scope: %i(scoped))
|
2015-10-28 16:52:33 -04:00
|
|
|
end
|
|
|
|
|
2014-01-26 17:05:34 -05:00
|
|
|
def test_raises_missing_translation_message_with_raise_config_option
|
2021-10-14 18:09:03 -04:00
|
|
|
ActionView::Helpers::TranslationHelper.raise_on_missing_translations = true
|
2014-01-26 17:05:34 -05:00
|
|
|
|
|
|
|
assert_raise(I18n::MissingTranslationData) do
|
|
|
|
translate("translations.missing")
|
|
|
|
end
|
|
|
|
ensure
|
2021-10-14 18:09:03 -04:00
|
|
|
ActionView::Helpers::TranslationHelper.raise_on_missing_translations = false
|
2014-01-26 17:05:34 -05:00
|
|
|
end
|
|
|
|
|
2020-08-04 12:30:44 -04:00
|
|
|
def test_raise_arg_overrides_raise_config_option
|
2021-10-14 18:09:03 -04:00
|
|
|
ActionView::Helpers::TranslationHelper.raise_on_missing_translations = true
|
2020-08-04 12:30:44 -04:00
|
|
|
|
|
|
|
expected = "translation missing: en.translations.missing"
|
|
|
|
assert_equal expected, translate(:"translations.missing", raise: false)
|
|
|
|
ensure
|
2021-10-14 18:09:03 -04:00
|
|
|
ActionView::Helpers::TranslationHelper.raise_on_missing_translations = false
|
2020-08-04 12:30:44 -04:00
|
|
|
end
|
|
|
|
|
2013-12-03 22:48:21 -05:00
|
|
|
def test_raises_missing_translation_message_with_raise_option
|
|
|
|
assert_raise(I18n::MissingTranslationData) do
|
2016-08-06 13:36:34 -04:00
|
|
|
translate(:"translations.missing", raise: true)
|
2013-12-03 22:48:21 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-11-19 09:08:56 -05:00
|
|
|
def test_uses_custom_exception_handler_when_specified
|
2015-01-05 12:39:52 -05:00
|
|
|
old_exception_handler = I18n.exception_handler
|
2014-11-19 09:08:56 -05:00
|
|
|
I18n.exception_handler = I18n::CustomExceptionHandler
|
2016-08-06 12:50:17 -04:00
|
|
|
assert_equal "from CustomExceptionHandler", translate(:"translations.missing", raise: false)
|
2015-01-05 12:39:52 -05:00
|
|
|
ensure
|
|
|
|
I18n.exception_handler = old_exception_handler
|
2014-11-19 09:08:56 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_uses_custom_exception_handler_when_specified_for_html
|
2015-01-05 12:39:52 -05:00
|
|
|
old_exception_handler = I18n.exception_handler
|
2014-11-19 09:08:56 -05:00
|
|
|
I18n.exception_handler = I18n::CustomExceptionHandler
|
2016-08-06 12:50:17 -04:00
|
|
|
assert_equal "from CustomExceptionHandler", translate(:"translations.missing_html", raise: false)
|
2015-01-05 12:39:52 -05:00
|
|
|
ensure
|
|
|
|
I18n.exception_handler = old_exception_handler
|
2014-11-19 09:08:56 -05:00
|
|
|
end
|
|
|
|
|
2010-05-24 23:16:50 -04:00
|
|
|
def test_translation_returning_an_array
|
2010-11-14 12:46:40 -05:00
|
|
|
expected = %w(foo bar)
|
|
|
|
assert_equal expected, translate(:"translations.array")
|
2010-05-24 23:16:50 -04:00
|
|
|
end
|
|
|
|
|
2010-11-14 12:46:40 -05:00
|
|
|
def test_finds_translation_scoped_by_partial
|
2019-03-19 14:56:53 -04:00
|
|
|
assert_equal "Foo", view.render(template: "translations/templates/found").strip
|
2008-07-08 06:37:49 -04:00
|
|
|
end
|
2010-08-14 01:13:00 -04:00
|
|
|
|
Extend `ActionView::Helpers#translate` to yield
This commit extends the `ActionView::Helpers#translate` (and by way of
alias, `#t`) helper methods to accept blocks.
When invoked with a block, the `translate` call will yield the
translated text as its first block argument, along with the resolved
translation key as its second:
```erb
<%= translate(".key") do |translation, resolved_key| %>
<span data-i18n-key="<%= resolved_key %>"><%= translation %></span>
<% end %>
```
In cases where relative translation keys are foregone in lieu of fully
qualified keys, or if the caller is not interested in the resolved key,
the second block argument can be omitted:
```erb
<%= translate("action.template.key") do |translation| %>
<p><%= translation %></p>
<p><%= translation %>, but a second time</p>
<% end %>
```
A benefit of yielding the translation is that it enabled template-local
variable re-use. Alternatively, [`Object#tap`][tap] could be used.
Prior to this commit, however, the resolution of the translation key was
internal to `ActionView`, and unavailable to the caller (unless they
were willing to explicitly determine the resolved key themselves). By
making it available as a block parameter, it could be used to annotate
the translated value in the resulting elements.
[tap]: https://ruby-doc.org/core-2.7.0/Object.html#method-i-tap
2020-03-06 10:34:26 -05:00
|
|
|
def test_finds_translation_scoped_by_partial_yielding_single_argument_block
|
|
|
|
assert_equal "Foo", view.render(template: "translations/templates/found_yield_single_argument").strip
|
|
|
|
end
|
|
|
|
|
2020-12-30 06:50:03 -05:00
|
|
|
def test_finds_lazy_translation_scoped_by_partial
|
|
|
|
assert_equal "Partial foo", view.render(template: "translations/templates/partial_lazy_translation").strip
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_finds_lazy_translation_scoped_by_partial_with_block
|
|
|
|
assert_equal "Partial foo", view.render(template: "translations/templates/partial_lazy_translation_block").strip
|
|
|
|
end
|
|
|
|
|
Extend `ActionView::Helpers#translate` to yield
This commit extends the `ActionView::Helpers#translate` (and by way of
alias, `#t`) helper methods to accept blocks.
When invoked with a block, the `translate` call will yield the
translated text as its first block argument, along with the resolved
translation key as its second:
```erb
<%= translate(".key") do |translation, resolved_key| %>
<span data-i18n-key="<%= resolved_key %>"><%= translation %></span>
<% end %>
```
In cases where relative translation keys are foregone in lieu of fully
qualified keys, or if the caller is not interested in the resolved key,
the second block argument can be omitted:
```erb
<%= translate("action.template.key") do |translation| %>
<p><%= translation %></p>
<p><%= translation %>, but a second time</p>
<% end %>
```
A benefit of yielding the translation is that it enabled template-local
variable re-use. Alternatively, [`Object#tap`][tap] could be used.
Prior to this commit, however, the resolution of the translation key was
internal to `ActionView`, and unavailable to the caller (unless they
were willing to explicitly determine the resolved key themselves). By
making it available as a block parameter, it could be used to annotate
the translated value in the resulting elements.
[tap]: https://ruby-doc.org/core-2.7.0/Object.html#method-i-tap
2020-03-06 10:34:26 -05:00
|
|
|
def test_finds_translation_scoped_by_partial_yielding_translation_and_key
|
|
|
|
assert_equal "translations.templates.found_yield_block.foo: Foo", view.render(template: "translations/templates/found_yield_block").strip
|
|
|
|
end
|
|
|
|
|
2010-11-14 12:46:40 -05:00
|
|
|
def test_finds_array_of_translations_scoped_by_partial
|
2019-03-19 14:56:53 -04:00
|
|
|
assert_equal "Foo Bar", @view.render(template: "translations/templates/array").strip
|
2009-02-10 06:57:12 -05:00
|
|
|
end
|
2010-03-16 19:12:04 -04:00
|
|
|
|
2012-04-30 02:44:39 -04:00
|
|
|
def test_default_lookup_scoped_by_partial
|
2019-03-19 14:56:53 -04:00
|
|
|
assert_equal "Foo", view.render(template: "translations/templates/default").strip
|
2012-04-30 02:44:39 -04:00
|
|
|
end
|
|
|
|
|
2010-11-14 12:46:40 -05:00
|
|
|
def test_missing_translation_scoped_by_partial
|
|
|
|
expected = '<span class="translation_missing" title="translation missing: en.translations.templates.missing.missing">Missing</span>'
|
2019-03-19 14:56:53 -04:00
|
|
|
assert_equal expected, view.render(template: "translations/templates/missing").strip
|
2010-03-16 19:12:04 -04:00
|
|
|
end
|
2010-08-14 01:13:00 -04:00
|
|
|
|
Extend `ActionView::Helpers#translate` to yield
This commit extends the `ActionView::Helpers#translate` (and by way of
alias, `#t`) helper methods to accept blocks.
When invoked with a block, the `translate` call will yield the
translated text as its first block argument, along with the resolved
translation key as its second:
```erb
<%= translate(".key") do |translation, resolved_key| %>
<span data-i18n-key="<%= resolved_key %>"><%= translation %></span>
<% end %>
```
In cases where relative translation keys are foregone in lieu of fully
qualified keys, or if the caller is not interested in the resolved key,
the second block argument can be omitted:
```erb
<%= translate("action.template.key") do |translation| %>
<p><%= translation %></p>
<p><%= translation %>, but a second time</p>
<% end %>
```
A benefit of yielding the translation is that it enabled template-local
variable re-use. Alternatively, [`Object#tap`][tap] could be used.
Prior to this commit, however, the resolution of the translation key was
internal to `ActionView`, and unavailable to the caller (unless they
were willing to explicitly determine the resolved key themselves). By
making it available as a block parameter, it could be used to annotate
the translated value in the resulting elements.
[tap]: https://ruby-doc.org/core-2.7.0/Object.html#method-i-tap
2020-03-06 10:34:26 -05:00
|
|
|
def test_missing_translation_scoped_by_partial_yield_block
|
|
|
|
expected = 'translations.templates.missing_yield_block.missing: <span class="translation_missing" title="translation missing: en.translations.templates.missing_yield_block.missing">Missing</span>'
|
|
|
|
assert_equal expected, view.render(template: "translations/templates/missing_yield_block").strip
|
|
|
|
end
|
|
|
|
|
2020-08-04 12:30:44 -04:00
|
|
|
def test_missing_translation_scoped_by_partial_yield_block_without_debug_wrapper
|
|
|
|
old_debug_missing_translation = ActionView::Base.debug_missing_translation
|
|
|
|
ActionView::Base.debug_missing_translation = false
|
|
|
|
|
|
|
|
expected = "translations.templates.missing_yield_block.missing: translation missing: en.translations.templates.missing_yield_block.missing"
|
|
|
|
assert_equal expected, view.render(template: "translations/templates/missing_yield_block").strip
|
|
|
|
ensure
|
|
|
|
ActionView::Base.debug_missing_translation = old_debug_missing_translation
|
|
|
|
end
|
|
|
|
|
Extend `ActionView::Helpers#translate` to yield
This commit extends the `ActionView::Helpers#translate` (and by way of
alias, `#t`) helper methods to accept blocks.
When invoked with a block, the `translate` call will yield the
translated text as its first block argument, along with the resolved
translation key as its second:
```erb
<%= translate(".key") do |translation, resolved_key| %>
<span data-i18n-key="<%= resolved_key %>"><%= translation %></span>
<% end %>
```
In cases where relative translation keys are foregone in lieu of fully
qualified keys, or if the caller is not interested in the resolved key,
the second block argument can be omitted:
```erb
<%= translate("action.template.key") do |translation| %>
<p><%= translation %></p>
<p><%= translation %>, but a second time</p>
<% end %>
```
A benefit of yielding the translation is that it enabled template-local
variable re-use. Alternatively, [`Object#tap`][tap] could be used.
Prior to this commit, however, the resolution of the translation key was
internal to `ActionView`, and unavailable to the caller (unless they
were willing to explicitly determine the resolved key themselves). By
making it available as a block parameter, it could be used to annotate
the translated value in the resulting elements.
[tap]: https://ruby-doc.org/core-2.7.0/Object.html#method-i-tap
2020-03-06 10:34:26 -05:00
|
|
|
def test_missing_translation_with_default_scoped_by_partial_yield_block
|
|
|
|
expected = "translations.templates.missing_with_default_yield_block.missing: Default"
|
|
|
|
assert_equal expected, view.render(template: "translations/templates/missing_with_default_yield_block").strip
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_missing_translation_scoped_by_partial_yield_single_argument_block
|
|
|
|
expected = '<span class="translation_missing" title="translation missing: en.translations.templates.missing_yield_single_argument_block.missing">Missing</span>'
|
|
|
|
assert_equal expected, view.render(template: "translations/templates/missing_yield_single_argument_block").strip
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_missing_translation_with_default_scoped_by_partial_yield_single_argument_block
|
|
|
|
expected = "Default"
|
|
|
|
assert_equal expected, view.render(template: "translations/templates/missing_with_default_yield_single_argument_block").strip
|
|
|
|
end
|
|
|
|
|
2010-04-09 21:01:32 -04:00
|
|
|
def test_translate_does_not_mark_plain_text_as_safe_html
|
2010-11-14 12:46:40 -05:00
|
|
|
assert_equal false, translate(:'translations.hello').html_safe?
|
2010-04-09 21:01:32 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_translate_marks_translations_named_html_as_safe_html
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate translate(:'translations.html'), :html_safe?
|
2010-04-09 21:01:32 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_translate_marks_translations_with_a_html_suffix_as_safe_html
|
2018-01-25 18:14:09 -05:00
|
|
|
assert_predicate translate(:'translations.hello_html'), :html_safe?
|
2010-04-09 21:01:32 -04:00
|
|
|
end
|
2010-05-24 23:16:50 -04:00
|
|
|
|
2011-11-17 10:29:55 -05:00
|
|
|
def test_translate_escapes_interpolations_in_translations_with_a_html_suffix
|
2015-08-22 00:03:53 -04:00
|
|
|
word_struct = Struct.new(:to_s)
|
2016-08-06 13:36:34 -04:00
|
|
|
assert_equal "<a>Hello <World></a>", translate(:'translations.interpolated_html', word: "<World>")
|
|
|
|
assert_equal "<a>Hello <World></a>", translate(:'translations.interpolated_html', word: word_struct.new("<World>"))
|
2011-11-17 10:29:55 -05:00
|
|
|
end
|
|
|
|
|
2011-11-19 08:19:20 -05:00
|
|
|
def test_translate_with_html_count
|
2016-08-06 13:36:34 -04:00
|
|
|
assert_equal "<a>One 1</a>", translate(:'translations.count_html', count: 1)
|
|
|
|
assert_equal "<a>Other 2</a>", translate(:'translations.count_html', count: 2)
|
|
|
|
assert_equal "<a>Other <One></a>", translate(:'translations.count_html', count: "<One>")
|
2011-11-19 08:19:20 -05:00
|
|
|
end
|
|
|
|
|
2018-07-03 11:50:02 -04:00
|
|
|
def test_translate_marks_array_of_translations_with_a_html_safe_suffix_as_safe_html
|
|
|
|
translate(:'translations.array_html').tap do |translated|
|
|
|
|
assert_equal %w( foo bar ), translated
|
|
|
|
assert translated.all?(&:html_safe?)
|
|
|
|
end
|
2010-05-24 23:16:50 -04:00
|
|
|
end
|
2012-04-30 02:44:39 -04:00
|
|
|
|
Improve Action View `translate` helper
This disentangles the control flow between Action View's `translate` and
I18n's `translate`. In doing so, it fixes a handful of corner cases,
for which tests have now been added. It also reduces memory
allocations, and improves speed when using a default:
**Memory**
```ruby
require "benchmark/memory"
Benchmark.memory do |x|
x.report("warmup") { translate(:"translations.foo"); translate(:"translations.html") }
x.report("text") { translate(:"translations.foo") }
x.report("html") { translate(:"translations.html") }
x.report("text 1 default") { translate(:"translations.missing", default: :"translations.foo") }
x.report("html 1 default") { translate(:"translations.missing", default: :"translations.html") }
x.report("text 2 defaults") { translate(:"translations.missing", default: [:"translations.missing", :"translations.foo"]) }
x.report("html 2 defaults") { translate(:"translations.missing", default: [:"translations.missing", :"translations.html"]) }
end
```
Before:
```
text 1.240k memsize ( 0.000 retained)
13.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
html 1.600k memsize ( 0.000 retained)
19.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
text 1 default 4.728k memsize ( 1.200k retained)
39.000 objects ( 4.000 retained)
5.000 strings ( 0.000 retained)
html 1 default 5.056k memsize ( 1.160k retained)
41.000 objects ( 3.000 retained)
4.000 strings ( 0.000 retained)
text 2 defaults 7.464k memsize ( 2.392k retained)
54.000 objects ( 6.000 retained)
4.000 strings ( 0.000 retained)
html 2 defaults 7.944k memsize ( 2.384k retained)
60.000 objects ( 6.000 retained)
4.000 strings ( 0.000 retained)
```
After:
```
text 952.000 memsize ( 0.000 retained)
9.000 objects ( 0.000 retained)
1.000 strings ( 0.000 retained)
html 1.008k memsize ( 0.000 retained)
10.000 objects ( 0.000 retained)
1.000 strings ( 0.000 retained)
text 1 default 2.400k memsize ( 40.000 retained)
24.000 objects ( 1.000 retained)
4.000 strings ( 0.000 retained)
html 1 default 2.464k memsize ( 0.000 retained)
22.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
text 2 defaults 3.232k memsize ( 0.000 retained)
30.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
html 2 defaults 3.456k memsize ( 0.000 retained)
32.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
```
**Speed**
```ruby
require "benchmark/ips"
Benchmark.ips do |x|
x.report("text") { translate(:"translations.foo") }
x.report("html") { translate(:"translations.html") }
x.report("text 1 default") { translate(:"translations.missing", default: :"translations.foo") }
x.report("html 1 default") { translate(:"translations.missing", default: :"translations.html") }
x.report("text 2 defaults") { translate(:"translations.missing", default: [:"translations.missing", :"translations.foo"]) }
x.report("html 2 defaults") { translate(:"translations.missing", default: [:"translations.missing", :"translations.html"]) }
end
```
Before:
```
text 35.685k (± 0.7%) i/s - 179.050k in 5.017773s
html 28.569k (± 3.1%) i/s - 143.871k in 5.040128s
text 1 default 13.953k (± 2.0%) i/s - 70.737k in 5.071651s
html 1 default 12.507k (± 0.4%) i/s - 63.546k in 5.080908s
text 2 defaults 9.103k (± 0.3%) i/s - 46.308k in 5.087323s
html 2 defaults 8.570k (± 4.3%) i/s - 43.071k in 5.034322s
```
After:
```
text 36.694k (± 2.0%) i/s - 186.864k in 5.094367s
html 30.415k (± 0.5%) i/s - 152.900k in 5.027226s
text 1 default 18.095k (± 2.7%) i/s - 91.086k in 5.036857s
html 1 default 15.934k (± 1.7%) i/s - 80.223k in 5.036085s
text 2 defaults 12.179k (± 0.6%) i/s - 61.659k in 5.062910s
html 2 defaults 11.193k (± 2.1%) i/s - 56.406k in 5.041433s
```
2020-08-05 11:27:50 -04:00
|
|
|
def test_translate_with_default_and_raise_false
|
|
|
|
translation = translate(:"translations.missing", default: :"translations.foo", raise: false)
|
|
|
|
assert_equal "Foo", translation
|
|
|
|
end
|
|
|
|
|
2012-04-30 02:44:39 -04:00
|
|
|
def test_translate_with_default_named_html
|
2016-08-06 13:36:34 -04:00
|
|
|
translation = translate(:'translations.missing', default: :'translations.hello_html')
|
2016-08-06 12:50:17 -04:00
|
|
|
assert_equal "<a>Hello World</a>", translation
|
2012-05-22 02:56:23 -04:00
|
|
|
assert_equal true, translation.html_safe?
|
2012-04-30 02:44:39 -04:00
|
|
|
end
|
|
|
|
|
Improve Action View `translate` helper
This disentangles the control flow between Action View's `translate` and
I18n's `translate`. In doing so, it fixes a handful of corner cases,
for which tests have now been added. It also reduces memory
allocations, and improves speed when using a default:
**Memory**
```ruby
require "benchmark/memory"
Benchmark.memory do |x|
x.report("warmup") { translate(:"translations.foo"); translate(:"translations.html") }
x.report("text") { translate(:"translations.foo") }
x.report("html") { translate(:"translations.html") }
x.report("text 1 default") { translate(:"translations.missing", default: :"translations.foo") }
x.report("html 1 default") { translate(:"translations.missing", default: :"translations.html") }
x.report("text 2 defaults") { translate(:"translations.missing", default: [:"translations.missing", :"translations.foo"]) }
x.report("html 2 defaults") { translate(:"translations.missing", default: [:"translations.missing", :"translations.html"]) }
end
```
Before:
```
text 1.240k memsize ( 0.000 retained)
13.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
html 1.600k memsize ( 0.000 retained)
19.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
text 1 default 4.728k memsize ( 1.200k retained)
39.000 objects ( 4.000 retained)
5.000 strings ( 0.000 retained)
html 1 default 5.056k memsize ( 1.160k retained)
41.000 objects ( 3.000 retained)
4.000 strings ( 0.000 retained)
text 2 defaults 7.464k memsize ( 2.392k retained)
54.000 objects ( 6.000 retained)
4.000 strings ( 0.000 retained)
html 2 defaults 7.944k memsize ( 2.384k retained)
60.000 objects ( 6.000 retained)
4.000 strings ( 0.000 retained)
```
After:
```
text 952.000 memsize ( 0.000 retained)
9.000 objects ( 0.000 retained)
1.000 strings ( 0.000 retained)
html 1.008k memsize ( 0.000 retained)
10.000 objects ( 0.000 retained)
1.000 strings ( 0.000 retained)
text 1 default 2.400k memsize ( 40.000 retained)
24.000 objects ( 1.000 retained)
4.000 strings ( 0.000 retained)
html 1 default 2.464k memsize ( 0.000 retained)
22.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
text 2 defaults 3.232k memsize ( 0.000 retained)
30.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
html 2 defaults 3.456k memsize ( 0.000 retained)
32.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
```
**Speed**
```ruby
require "benchmark/ips"
Benchmark.ips do |x|
x.report("text") { translate(:"translations.foo") }
x.report("html") { translate(:"translations.html") }
x.report("text 1 default") { translate(:"translations.missing", default: :"translations.foo") }
x.report("html 1 default") { translate(:"translations.missing", default: :"translations.html") }
x.report("text 2 defaults") { translate(:"translations.missing", default: [:"translations.missing", :"translations.foo"]) }
x.report("html 2 defaults") { translate(:"translations.missing", default: [:"translations.missing", :"translations.html"]) }
end
```
Before:
```
text 35.685k (± 0.7%) i/s - 179.050k in 5.017773s
html 28.569k (± 3.1%) i/s - 143.871k in 5.040128s
text 1 default 13.953k (± 2.0%) i/s - 70.737k in 5.071651s
html 1 default 12.507k (± 0.4%) i/s - 63.546k in 5.080908s
text 2 defaults 9.103k (± 0.3%) i/s - 46.308k in 5.087323s
html 2 defaults 8.570k (± 4.3%) i/s - 43.071k in 5.034322s
```
After:
```
text 36.694k (± 2.0%) i/s - 186.864k in 5.094367s
html 30.415k (± 0.5%) i/s - 152.900k in 5.027226s
text 1 default 18.095k (± 2.7%) i/s - 91.086k in 5.036857s
html 1 default 15.934k (± 1.7%) i/s - 80.223k in 5.036085s
text 2 defaults 12.179k (± 0.6%) i/s - 61.659k in 5.062910s
html 2 defaults 11.193k (± 2.1%) i/s - 56.406k in 5.041433s
```
2020-08-05 11:27:50 -04:00
|
|
|
def test_translate_with_default_named_html_and_raise_false
|
|
|
|
translation = translate(:"translations.missing", default: :"translations.hello_html", raise: false)
|
|
|
|
assert_equal "<a>Hello World</a>", translation
|
|
|
|
assert_predicate translation, :html_safe?
|
|
|
|
end
|
|
|
|
|
2015-05-04 16:26:07 -04:00
|
|
|
def test_translate_with_missing_default
|
Improve Action View `translate` helper
This disentangles the control flow between Action View's `translate` and
I18n's `translate`. In doing so, it fixes a handful of corner cases,
for which tests have now been added. It also reduces memory
allocations, and improves speed when using a default:
**Memory**
```ruby
require "benchmark/memory"
Benchmark.memory do |x|
x.report("warmup") { translate(:"translations.foo"); translate(:"translations.html") }
x.report("text") { translate(:"translations.foo") }
x.report("html") { translate(:"translations.html") }
x.report("text 1 default") { translate(:"translations.missing", default: :"translations.foo") }
x.report("html 1 default") { translate(:"translations.missing", default: :"translations.html") }
x.report("text 2 defaults") { translate(:"translations.missing", default: [:"translations.missing", :"translations.foo"]) }
x.report("html 2 defaults") { translate(:"translations.missing", default: [:"translations.missing", :"translations.html"]) }
end
```
Before:
```
text 1.240k memsize ( 0.000 retained)
13.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
html 1.600k memsize ( 0.000 retained)
19.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
text 1 default 4.728k memsize ( 1.200k retained)
39.000 objects ( 4.000 retained)
5.000 strings ( 0.000 retained)
html 1 default 5.056k memsize ( 1.160k retained)
41.000 objects ( 3.000 retained)
4.000 strings ( 0.000 retained)
text 2 defaults 7.464k memsize ( 2.392k retained)
54.000 objects ( 6.000 retained)
4.000 strings ( 0.000 retained)
html 2 defaults 7.944k memsize ( 2.384k retained)
60.000 objects ( 6.000 retained)
4.000 strings ( 0.000 retained)
```
After:
```
text 952.000 memsize ( 0.000 retained)
9.000 objects ( 0.000 retained)
1.000 strings ( 0.000 retained)
html 1.008k memsize ( 0.000 retained)
10.000 objects ( 0.000 retained)
1.000 strings ( 0.000 retained)
text 1 default 2.400k memsize ( 40.000 retained)
24.000 objects ( 1.000 retained)
4.000 strings ( 0.000 retained)
html 1 default 2.464k memsize ( 0.000 retained)
22.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
text 2 defaults 3.232k memsize ( 0.000 retained)
30.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
html 2 defaults 3.456k memsize ( 0.000 retained)
32.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
```
**Speed**
```ruby
require "benchmark/ips"
Benchmark.ips do |x|
x.report("text") { translate(:"translations.foo") }
x.report("html") { translate(:"translations.html") }
x.report("text 1 default") { translate(:"translations.missing", default: :"translations.foo") }
x.report("html 1 default") { translate(:"translations.missing", default: :"translations.html") }
x.report("text 2 defaults") { translate(:"translations.missing", default: [:"translations.missing", :"translations.foo"]) }
x.report("html 2 defaults") { translate(:"translations.missing", default: [:"translations.missing", :"translations.html"]) }
end
```
Before:
```
text 35.685k (± 0.7%) i/s - 179.050k in 5.017773s
html 28.569k (± 3.1%) i/s - 143.871k in 5.040128s
text 1 default 13.953k (± 2.0%) i/s - 70.737k in 5.071651s
html 1 default 12.507k (± 0.4%) i/s - 63.546k in 5.080908s
text 2 defaults 9.103k (± 0.3%) i/s - 46.308k in 5.087323s
html 2 defaults 8.570k (± 4.3%) i/s - 43.071k in 5.034322s
```
After:
```
text 36.694k (± 2.0%) i/s - 186.864k in 5.094367s
html 30.415k (± 0.5%) i/s - 152.900k in 5.027226s
text 1 default 18.095k (± 2.7%) i/s - 91.086k in 5.036857s
html 1 default 15.934k (± 1.7%) i/s - 80.223k in 5.036085s
text 2 defaults 12.179k (± 0.6%) i/s - 61.659k in 5.062910s
html 2 defaults 11.193k (± 2.1%) i/s - 56.406k in 5.041433s
```
2020-08-05 11:27:50 -04:00
|
|
|
translation = translate(:"translations.missing", default: :also_missing)
|
|
|
|
expected = '<span class="translation_missing" title="translation missing: en.translations.missing">Missing</span>'
|
2015-05-04 16:26:07 -04:00
|
|
|
assert_equal expected, translation
|
|
|
|
assert_equal true, translation.html_safe?
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_translate_with_missing_default_and_raise_option
|
|
|
|
assert_raise(I18n::MissingTranslationData) do
|
2016-08-06 13:36:34 -04:00
|
|
|
translate(:'translations.missing', default: :'translations.missing_html', raise: true)
|
2015-05-04 16:26:07 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-08-04 12:30:44 -04:00
|
|
|
def test_translate_with_html_key_and_missing_default_and_raise_option
|
|
|
|
assert_raise(I18n::MissingTranslationData) do
|
|
|
|
translate(:"translations.missing_html", default: :"translations.missing_html", raise: true)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-04-30 02:44:39 -04:00
|
|
|
def test_translate_with_two_defaults_named_html
|
2016-08-06 13:36:34 -04:00
|
|
|
translation = translate(:'translations.missing', default: [:'translations.missing_html', :'translations.hello_html'])
|
2016-08-06 12:50:17 -04:00
|
|
|
assert_equal "<a>Hello World</a>", translation
|
2012-05-22 02:56:23 -04:00
|
|
|
assert_equal true, translation.html_safe?
|
2012-04-30 02:44:39 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_translate_with_last_default_named_html
|
2016-08-06 13:36:34 -04:00
|
|
|
translation = translate(:'translations.missing', default: [:'translations.missing', :'translations.hello_html'])
|
2016-08-06 12:50:17 -04:00
|
|
|
assert_equal "<a>Hello World</a>", translation
|
2012-05-22 02:56:23 -04:00
|
|
|
assert_equal true, translation.html_safe?
|
|
|
|
end
|
|
|
|
|
2015-01-02 18:07:01 -05:00
|
|
|
def test_translate_with_last_default_not_named_html
|
2016-08-06 13:36:34 -04:00
|
|
|
translation = translate(:'translations.missing', default: [:'translations.missing_html', :'translations.foo'])
|
2016-08-06 12:50:17 -04:00
|
|
|
assert_equal "Foo", translation
|
2015-01-02 18:07:01 -05:00
|
|
|
assert_equal false, translation.html_safe?
|
|
|
|
end
|
|
|
|
|
2020-09-09 14:41:31 -04:00
|
|
|
def test_translate_does_not_mark_unsourced_string_default_as_html_safe
|
|
|
|
untrusted_string = "<script>alert()</script>"
|
|
|
|
translation = translate(:"translations.missing", default: [:"translations.missing_html", untrusted_string])
|
|
|
|
assert_equal untrusted_string, translation
|
|
|
|
assert_not_predicate translation, :html_safe?
|
|
|
|
end
|
|
|
|
|
2012-05-22 02:56:23 -04:00
|
|
|
def test_translate_with_string_default
|
2016-08-06 12:50:17 -04:00
|
|
|
translation = translate(:'translations.missing', default: "A Generic String")
|
|
|
|
assert_equal "A Generic String", translation
|
2012-05-22 02:56:23 -04:00
|
|
|
end
|
|
|
|
|
2020-11-25 17:47:25 -05:00
|
|
|
def test_translate_with_interpolated_string_default
|
|
|
|
translation = translate(:"translations.missing", default: "An %{kind} String", kind: "Interpolated")
|
|
|
|
assert_equal "An Interpolated String", translation
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_translate_with_hash_default
|
|
|
|
hash = { one: "%{count} thing", other: "%{count} things" }
|
|
|
|
assert_equal hash, translate(:"translations.missing", default: hash)
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_translate_with_hash_default_and_count
|
|
|
|
hash = { one: "%{count} thing", other: "%{count} things" }
|
|
|
|
assert_equal "1 thing", translate(:"translations.missing", default: hash, count: 1)
|
|
|
|
assert_equal "2 things", translate(:"translations.missing", default: hash, count: 2)
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_translate_with_proc_default
|
|
|
|
translation = translate(:"translations.missing", default: Proc.new { "From Proc" })
|
|
|
|
assert_equal "From Proc", translation
|
|
|
|
end
|
|
|
|
|
2015-02-26 16:18:58 -05:00
|
|
|
def test_translate_with_object_default
|
|
|
|
translation = translate(:'translations.missing', default: 123)
|
|
|
|
assert_equal 123, translation
|
|
|
|
end
|
|
|
|
|
2012-05-22 02:56:23 -04:00
|
|
|
def test_translate_with_array_of_string_defaults
|
2016-08-06 12:50:17 -04:00
|
|
|
translation = translate(:'translations.missing', default: ["A Generic String", "Second generic string"])
|
|
|
|
assert_equal "A Generic String", translation
|
2012-04-30 02:44:39 -04:00
|
|
|
end
|
2014-05-12 11:20:51 -04:00
|
|
|
|
2015-03-20 16:58:54 -04:00
|
|
|
def test_translate_with_array_of_defaults_with_nil
|
2016-08-06 12:50:17 -04:00
|
|
|
translation = translate(:'translations.missing', default: [:'also_missing', nil, "A Generic String"])
|
|
|
|
assert_equal "A Generic String", translation
|
2015-03-20 16:58:54 -04:00
|
|
|
end
|
|
|
|
|
2015-04-04 12:46:45 -04:00
|
|
|
def test_translate_with_array_of_array_default
|
|
|
|
translation = translate(:'translations.missing', default: [[]])
|
|
|
|
assert_equal [], translation
|
|
|
|
end
|
|
|
|
|
2019-12-05 17:13:28 -05:00
|
|
|
def test_translate_with_false_default
|
|
|
|
translation = translate(:'translations.missing', default: false)
|
|
|
|
assert_equal false, translation
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_translate_with_nil_default
|
|
|
|
translation = translate(:'translations.missing', default: nil)
|
|
|
|
assert_nil translation
|
|
|
|
end
|
|
|
|
|
2020-08-04 12:30:44 -04:00
|
|
|
def test_translate_bulk_lookup
|
|
|
|
translations = translate([:"translations.foo", :"translations.foo"])
|
|
|
|
assert_equal ["Foo", "Foo"], translations
|
|
|
|
end
|
|
|
|
|
Improve Action View `translate` helper
This disentangles the control flow between Action View's `translate` and
I18n's `translate`. In doing so, it fixes a handful of corner cases,
for which tests have now been added. It also reduces memory
allocations, and improves speed when using a default:
**Memory**
```ruby
require "benchmark/memory"
Benchmark.memory do |x|
x.report("warmup") { translate(:"translations.foo"); translate(:"translations.html") }
x.report("text") { translate(:"translations.foo") }
x.report("html") { translate(:"translations.html") }
x.report("text 1 default") { translate(:"translations.missing", default: :"translations.foo") }
x.report("html 1 default") { translate(:"translations.missing", default: :"translations.html") }
x.report("text 2 defaults") { translate(:"translations.missing", default: [:"translations.missing", :"translations.foo"]) }
x.report("html 2 defaults") { translate(:"translations.missing", default: [:"translations.missing", :"translations.html"]) }
end
```
Before:
```
text 1.240k memsize ( 0.000 retained)
13.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
html 1.600k memsize ( 0.000 retained)
19.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
text 1 default 4.728k memsize ( 1.200k retained)
39.000 objects ( 4.000 retained)
5.000 strings ( 0.000 retained)
html 1 default 5.056k memsize ( 1.160k retained)
41.000 objects ( 3.000 retained)
4.000 strings ( 0.000 retained)
text 2 defaults 7.464k memsize ( 2.392k retained)
54.000 objects ( 6.000 retained)
4.000 strings ( 0.000 retained)
html 2 defaults 7.944k memsize ( 2.384k retained)
60.000 objects ( 6.000 retained)
4.000 strings ( 0.000 retained)
```
After:
```
text 952.000 memsize ( 0.000 retained)
9.000 objects ( 0.000 retained)
1.000 strings ( 0.000 retained)
html 1.008k memsize ( 0.000 retained)
10.000 objects ( 0.000 retained)
1.000 strings ( 0.000 retained)
text 1 default 2.400k memsize ( 40.000 retained)
24.000 objects ( 1.000 retained)
4.000 strings ( 0.000 retained)
html 1 default 2.464k memsize ( 0.000 retained)
22.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
text 2 defaults 3.232k memsize ( 0.000 retained)
30.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
html 2 defaults 3.456k memsize ( 0.000 retained)
32.000 objects ( 0.000 retained)
2.000 strings ( 0.000 retained)
```
**Speed**
```ruby
require "benchmark/ips"
Benchmark.ips do |x|
x.report("text") { translate(:"translations.foo") }
x.report("html") { translate(:"translations.html") }
x.report("text 1 default") { translate(:"translations.missing", default: :"translations.foo") }
x.report("html 1 default") { translate(:"translations.missing", default: :"translations.html") }
x.report("text 2 defaults") { translate(:"translations.missing", default: [:"translations.missing", :"translations.foo"]) }
x.report("html 2 defaults") { translate(:"translations.missing", default: [:"translations.missing", :"translations.html"]) }
end
```
Before:
```
text 35.685k (± 0.7%) i/s - 179.050k in 5.017773s
html 28.569k (± 3.1%) i/s - 143.871k in 5.040128s
text 1 default 13.953k (± 2.0%) i/s - 70.737k in 5.071651s
html 1 default 12.507k (± 0.4%) i/s - 63.546k in 5.080908s
text 2 defaults 9.103k (± 0.3%) i/s - 46.308k in 5.087323s
html 2 defaults 8.570k (± 4.3%) i/s - 43.071k in 5.034322s
```
After:
```
text 36.694k (± 2.0%) i/s - 186.864k in 5.094367s
html 30.415k (± 0.5%) i/s - 152.900k in 5.027226s
text 1 default 18.095k (± 2.7%) i/s - 91.086k in 5.036857s
html 1 default 15.934k (± 1.7%) i/s - 80.223k in 5.036085s
text 2 defaults 12.179k (± 0.6%) i/s - 61.659k in 5.062910s
html 2 defaults 11.193k (± 2.1%) i/s - 56.406k in 5.041433s
```
2020-08-05 11:27:50 -04:00
|
|
|
def test_translate_bulk_lookup_with_default
|
|
|
|
translations = translate([:"translations.missing", :"translations.missing"], default: :"translations.foo")
|
|
|
|
assert_equal ["Foo", "Foo"], translations
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_translate_bulk_lookup_html
|
|
|
|
translations = translate([:"translations.html", :"translations.hello_html"])
|
|
|
|
assert_equal ["<a>Hello World</a>", "<a>Hello World</a>"], translations
|
|
|
|
translations.each do |translation|
|
|
|
|
assert_predicate translation, :html_safe?
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_translate_bulk_lookup_html_with_default
|
|
|
|
translations = translate([:"translations.missing", :"translations.missing"], default: :"translations.html")
|
|
|
|
assert_equal ["<a>Hello World</a>", "<a>Hello World</a>"], translations
|
|
|
|
translations.each do |translation|
|
|
|
|
assert_predicate translation, :html_safe?
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-05-13 11:09:11 -04:00
|
|
|
def test_translate_does_not_change_options
|
2014-05-12 11:20:51 -04:00
|
|
|
options = {}
|
2021-02-04 23:04:29 -05:00
|
|
|
translate(:"translations.missing", **options)
|
2014-05-13 11:09:11 -04:00
|
|
|
assert_equal({}, options)
|
2014-05-12 11:20:51 -04:00
|
|
|
end
|
2009-02-03 21:25:37 -05:00
|
|
|
end
|