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

Add I18n.interpolation_keys #682

Merged
merged 5 commits into from May 6, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
35 changes: 35 additions & 0 deletions lib/i18n.rb
Expand Up @@ -231,6 +231,30 @@ def translate!(key, **options)
end
alias :t! :translate!

# Returns an array of interpolation keys for the given translation key
#
# Suppose we have the following:
# I18n.t 'example.zero' == 'Zero interpolations'
# I18n.t 'example.one' == 'One interpolation %{foo}'
# I18n.t 'example.two' == 'Two interpolations %{foo} %{bar}'
# I18n.t 'example.one', locale: :other == 'One interpolation %{baz} %{bar}'
#
# Then we can expect the following results:
# I18n.interpolation_keys('example.zero') #=> []
# I18n.interpolation_keys('example.one') #=> ['foo']
# I18n.interpolation_keys('example.two') #=> ['foo', 'bar']
# I18n.interpolation_keys('one', scope: 'example', locale: :other) #=> ['baz']
# I18n.interpolation_keys('does-not-exist') #=> []
tom-lord marked this conversation as resolved.
Show resolved Hide resolved
def interpolation_keys(key, **options)
raise I18n::ArgumentError if !key.is_a?(String) || key.empty?

return [] unless exists?(key, **options.slice(:locale, :scope))

translation = translate(key, **options.slice(:locale, :scope))
interpolation_keys_from_translation(translation)
.flatten.compact
end

# Returns true if a translation exists for a given key, otherwise returns false.
def exists?(key, _locale = nil, locale: _locale, **options)
locale ||= config.locale
Expand Down Expand Up @@ -429,6 +453,17 @@ def normalize_key(key, separator)
keys
end
end

def interpolation_keys_from_translation(translation)
case translation
when ::String
translation.scan(Regexp.union(I18n.config.interpolation_patterns))
when ::Array
translation.map { |element| interpolation_keys_from_translation(element) }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that this does need to be recursive!

else
[]
end
end
end

extend Base
Expand Down
28 changes: 28 additions & 0 deletions test/i18n_test.rb
Expand Up @@ -287,6 +287,34 @@ def setup
assert_equal I18n.config.available_locales.size * 2, I18n.config.available_locales_set.size
end

test "interpolation_keys returns an array of keys" do
store_translations(:en, "example_two" => "Two interpolations %{foo} %{bar}")
assert_equal ["foo", "bar"], I18n.interpolation_keys("example_two")
end

test "interpolation_keys returns an empty array when no interpolations " do
store_translations(:en, "example_zero" => "Zero interpolations")
assert_equal [], I18n.interpolation_keys("example_zero")
end

test "interpolation_keys returns an empty array when missing translation " do
assert_equal [], I18n.interpolation_keys("does-not-exist")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could raise an exception or return nil, but in the interest of simplicity and consistency, I feel it's probably fine to return [].

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think [] is better here as it makes the return type consistently Array rather than Array | nil

end

test "interpolation_keys returns an empty array when nested translation" do
store_translations(:en, "example_nested" => { "one" => "One %{foo}", "two" => "Two %{bar}" })
assert_equal [], I18n.interpolation_keys("example_nested")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this scenario, I18n.t("example_nested") does not require/use any interpolation keys (even though the child translations do). Therefore I feel it's correct for the method to return [].

end

test "interpolation_keys returns an array of keys when translation is an Array" do
store_translations(:en, "example_array" => ["One %{foo}", ["Two %{bar}", ["Three %{baz}"]]])
assert_equal ["foo", "bar", "baz"], I18n.interpolation_keys("example_array")
end

test "interpolation_keys raises I18n::ArgumentError when non-string argument" do
assert_raises(I18n::ArgumentError) { I18n.interpolation_keys(["bad-argument"]) }
end

test "exists? given an existing key will return true" do
assert_equal true, I18n.exists?(:currency)
end
Expand Down