Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve localization helpers #842

Merged
merged 4 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 36 additions & 6 deletions bridgetown-core/lib/bridgetown-core/helpers.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require "active_support/html_safe_translation"

module Bridgetown
class RubyTemplateView
class Helpers
Expand Down Expand Up @@ -128,21 +130,49 @@ def attributes_from_options(options)
safe(segments.join(" "))
end

# Forward all arguments to I18n.t method
# Delegates to <tt>I18n#translate</tt> but also performs two additional
# functions.
#
# First, if the key starts with a period <tt>translate</tt> will scope
# the key by the current view. Calling <tt>translate(".foo")</tt> from
# the <tt>people/index.html.erb</tt> template is equivalent to calling
# <tt>translate("people.index.foo")</tt>. This makes it less
# repetitive to translate many keys within the same view and provides
# a convention to scope keys consistently.
#
# Second, the translation will be marked as <tt>html_safe</tt> if the key
# has the suffix "_html" or the last element of the key is "html". Calling
# <tt>translate("footer_html")</tt> or <tt>translate("footer.html")</tt>
# will return an HTML safe string that won't be escaped by other HTML
# helper methods. This naming convention helps to identify translations
# that include HTML tags so that you know what kind of output to expect
# when you call translate in a template and translators know which keys
# they can provide HTML values for.
#
# @return [String] the translated string
# @see I18n
def t(*args, **kwargs)
I18n.send :t, *args, **kwargs
def translate(key, **options) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
return key.map { |k| translate(k, **options) } if key.is_a?(Array)

key = key&.to_s

if key&.start_with?(".")
view_path = view&.page&.relative_path&.to_s&.split(".")&.first
key = "#{view_path.tr("/", ".")}#{key}" if view_path.present?
end

ActiveSupport::HtmlSafeTranslation.translate(key, **options)
end
alias_method :t, :translate

# Forward all arguments to I18n.l method
# Delegates to <tt>I18n.localize</tt> with no additional functionality.
#
# @return [String] the localized string
# @see I18n
def l(*args, **kwargs)
I18n.send :l, *args, **kwargs
def localize(...)
I18n.localize(...)
end
alias_method :l, :localize

# For template contexts where ActiveSupport's output safety is loaded, we
# can ensure a string has been marked safe
Expand Down
8 changes: 8 additions & 0 deletions bridgetown-core/test/source/src/_locales/en.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
en:
about:
foo: foo
bar: bar
foo_html: <strong>foo</strong>
contacts:
bar:
foo: foo
58 changes: 57 additions & 1 deletion bridgetown-core/test/test_ruby_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ class TestRubyHelpers < BridgetownUnitTest
def setup
@site = fixture_site
@site.read
@helpers = Bridgetown::RubyTemplateView::Helpers.new(self, @site)
@helpers = Bridgetown::RubyTemplateView::Helpers.new(
Bridgetown::ERBView.new(
@site.collections.pages.resources.find { |p| p.basename_without_ext == "about" }
),
@site
)
end

context "link_to" do
Expand Down Expand Up @@ -77,4 +82,55 @@ def setup
assert_includes "<p class=\"#{@helpers.class_map blank: !"".empty?, truthy: true, "more-truthy" => yes_var == "yes", falsy: nil, "more-falsy" => "no" == "yes"}\">classes!</p>", "<p class=\"truthy more-truthy\">"
end
end

context "translate" do
should "return translation when given a string" do
assert_equal "foo", @helpers.translate("about.foo")
end

should "return translations when given an array" do
assert_equal %w[foo bar], @helpers.translate(%w[about.foo about.bar])
end

should "return html safe string when key ends with _html" do
assert @helpers.translate("about.foo_html").html_safe?
end

should "not return html safe string when key does not end with _html" do
refute @helpers.translate("about.foo").html_safe?
end

should "return relative translation when key starts with period" do
assert_equal "foo", @helpers.translate(".foo")
end

should "return relative translation when key starts with period and view is in a folder" do
helpers = Bridgetown::RubyTemplateView::Helpers.new(
Bridgetown::ERBView.new(
@site.collections.pages.resources.find { |p| p.basename_without_ext == "bar" }
),
@site
)
assert_equal "foo", helpers.translate(".foo")
end

should "return translation missing if key doesn't exist" do
assert_equal "Translation missing: en.about.not_here", @helpers.translate(".not_here")
end

should "have alias method t" do
assert_equal @helpers.method(:translate), @helpers.method(:t)
end
end

context "localize" do
should "return same output as I18n.localize" do
time = Time.now
assert_equal I18n.localize(time), @helpers.localize(time)
end

should "have alias method l" do
assert_equal @helpers.method(:localize), @helpers.method(:l)
end
end
end
41 changes: 40 additions & 1 deletion bridgetown-website/src/_docs/internationalization.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ and in ERB:
<%= t("welcome.intro") %>
```

The Ruby helper in particular also supports variable interpolation. If you store a translation like this:
The Ruby helper in particular also supports some additional functionality.

#### Variable Interpolation

If you store a translation like this:

```yml
en:
Expand All @@ -90,6 +94,41 @@ Then you can pass that variable to the `t` helper:
<%= t("products.price", price: resource.data.price) %>
```

#### Relative page keys

If you start your translation key starts with a period, we'll automatically scope the key to the page. For example, if the page is `about.html`:

```erb
<%= t(".foo") %>
```

Will retrieve the key from:

```yml
en:
about:
foo: Foo
```

Or if the page is `contact/about.html`:

```yml
en:
contact:
about:
foo: Foo
```

#### Automatic HTML key safety

If your translation key ends with `_html` or is `html`, it will automatically be marked `html_safe`.

```yml
en:
products:
tagline_html: The <storng>best</strong> product!
```

There are many other useful features of the **i18n** gem, so feel free to peruse the [Rails Guide to Internationalization](https://guides.rubyonrails.org/i18n.html) for additional documentation.

{%@ Note do %}
Expand Down