From d55f40a0fa605733913e4f68578e9eeaa2971263 Mon Sep 17 00:00:00 2001 From: tom-lord Date: Fri, 1 Mar 2024 19:17:54 +0000 Subject: [PATCH 1/5] Add I18n.interpolation_keys --- lib/i18n.rb | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/lib/i18n.rb b/lib/i18n.rb index d3369704..c8fdc4d4 100644 --- a/lib/i18n.rb +++ b/lib/i18n.rb @@ -231,6 +231,31 @@ 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') #=> [] + def interpolation_keys(key, **options) + raise I18n::ArgumentError if !key.is_a?(String) || key.empty? + + return [] unless exists?(key, **options.slice(:locale, :scope)) + + translate(key, **options.slice(:locale, :scope)) + .scan(Regexp.union(I18n.config.interpolation_patterns)) + .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 From afb20cbaca3eb1f12914546c636c4932b8a5c58b Mon Sep 17 00:00:00 2001 From: tom-lord Date: Sat, 23 Mar 2024 21:50:25 +0000 Subject: [PATCH 2/5] Add tests and error handling for non-string translations --- lib/i18n.rb | 5 ++++- lib/i18n/exceptions.rb | 2 ++ test/i18n_test.rb | 23 +++++++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/lib/i18n.rb b/lib/i18n.rb index c8fdc4d4..0aa5b65a 100644 --- a/lib/i18n.rb +++ b/lib/i18n.rb @@ -250,7 +250,10 @@ def interpolation_keys(key, **options) return [] unless exists?(key, **options.slice(:locale, :scope)) - translate(key, **options.slice(:locale, :scope)) + translation = translate(key, **options.slice(:locale, :scope)) + + raise I18n::NonStringTranslationError unless translation.is_a?(String) + translation .scan(Regexp.union(I18n.config.interpolation_patterns)) .flatten .compact diff --git a/lib/i18n/exceptions.rb b/lib/i18n/exceptions.rb index 23ca46ec..e30d5986 100644 --- a/lib/i18n/exceptions.rb +++ b/lib/i18n/exceptions.rb @@ -154,4 +154,6 @@ def initialize(file_errors) MSG end end + + class NonStringTranslationError < ArgumentError; end end diff --git a/test/i18n_test.rb b/test/i18n_test.rb index 3f576fb8..eddb9dad 100644 --- a/test/i18n_test.rb +++ b/test/i18n_test.rb @@ -287,6 +287,29 @@ 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") + end + + test "interpolation_keys raises I18n::ArgumentError when non-string argument" do + assert_raises(I18n::ArgumentError) { I18n.interpolation_keys(["bad-argument"]) } + end + + test "interpolation_keys raises I18n::NonStringTranslationError when translation is not a String" do + store_translations(:en, "example_nested" => { "one" => "One", "two" => "Two" }) + assert_raises(I18n::NonStringTranslationError) { I18n.interpolation_keys("example_nested") } + end + test "exists? given an existing key will return true" do assert_equal true, I18n.exists?(:currency) end From fe82be84e0baa3c6d6632438c42259471d544d00 Mon Sep 17 00:00:00 2001 From: tom-lord Date: Sun, 24 Mar 2024 00:01:13 +0000 Subject: [PATCH 3/5] Modify behaviour for Hash and (maybe nested) Array translations --- lib/i18n.rb | 19 +++++++++++++------ lib/i18n/exceptions.rb | 2 -- test/i18n_test.rb | 15 ++++++++++----- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/lib/i18n.rb b/lib/i18n.rb index 0aa5b65a..2a30431c 100644 --- a/lib/i18n.rb +++ b/lib/i18n.rb @@ -251,12 +251,8 @@ def interpolation_keys(key, **options) return [] unless exists?(key, **options.slice(:locale, :scope)) translation = translate(key, **options.slice(:locale, :scope)) - - raise I18n::NonStringTranslationError unless translation.is_a?(String) - translation - .scan(Regexp.union(I18n.config.interpolation_patterns)) - .flatten - .compact + interpolation_keys_from_translation(translation) + .flatten.compact end # Returns true if a translation exists for a given key, otherwise returns false. @@ -457,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) } + else + [] + end + end end extend Base diff --git a/lib/i18n/exceptions.rb b/lib/i18n/exceptions.rb index e30d5986..23ca46ec 100644 --- a/lib/i18n/exceptions.rb +++ b/lib/i18n/exceptions.rb @@ -154,6 +154,4 @@ def initialize(file_errors) MSG end end - - class NonStringTranslationError < ArgumentError; end end diff --git a/test/i18n_test.rb b/test/i18n_test.rb index eddb9dad..85679d40 100644 --- a/test/i18n_test.rb +++ b/test/i18n_test.rb @@ -301,13 +301,18 @@ def setup assert_equal [], I18n.interpolation_keys("does-not-exist") end - test "interpolation_keys raises I18n::ArgumentError when non-string argument" do - assert_raises(I18n::ArgumentError) { I18n.interpolation_keys(["bad-argument"]) } + 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") + 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::NonStringTranslationError when translation is not a String" do - store_translations(:en, "example_nested" => { "one" => "One", "two" => "Two" }) - assert_raises(I18n::NonStringTranslationError) { I18n.interpolation_keys("example_nested") } + 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 From 7559fef756c20ba200e60276a85e071474f3cbbf Mon Sep 17 00:00:00 2001 From: tom-lord Date: Sun, 24 Mar 2024 00:21:23 +0000 Subject: [PATCH 4/5] More examples documented --- lib/i18n.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/i18n.rb b/lib/i18n.rb index 2a30431c..c0532d1f 100644 --- a/lib/i18n.rb +++ b/lib/i18n.rb @@ -233,18 +233,23 @@ def translate!(key, **options) # Returns an array of interpolation keys for the given translation key # + # *Examples* + # # 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.three' == ['One %{foo}', 'Two %{bar}', 'Three %{baz}'] # 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('example.three') #=> ['foo', 'bar', 'baz'] # I18n.interpolation_keys('one', scope: 'example', locale: :other) #=> ['baz'] # I18n.interpolation_keys('does-not-exist') #=> [] + # I18n.interpolation_keys('example') #=> [] def interpolation_keys(key, **options) raise I18n::ArgumentError if !key.is_a?(String) || key.empty? From 40aef981c8bfa358e048eaf652307abb8a1f7bdf Mon Sep 17 00:00:00 2001 From: tom-lord Date: Sun, 24 Mar 2024 00:25:06 +0000 Subject: [PATCH 5/5] Fix example doc --- lib/i18n.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/i18n.rb b/lib/i18n.rb index c0532d1f..06fb7844 100644 --- a/lib/i18n.rb +++ b/lib/i18n.rb @@ -240,7 +240,7 @@ def translate!(key, **options) # I18n.t 'example.one' == 'One interpolation %{foo}' # I18n.t 'example.two' == 'Two interpolations %{foo} %{bar}' # I18n.t 'example.three' == ['One %{foo}', 'Two %{bar}', 'Three %{baz}'] - # I18n.t 'example.one', locale: :other == 'One interpolation %{baz} %{bar}' + # I18n.t 'example.one', locale: :other == 'One interpolation %{baz}' # # Then we can expect the following results: # I18n.interpolation_keys('example.zero') #=> []