i18n 1.14.1 → 1.14.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 692bec57675f1d97cdd0dff4a7b444e1777ba2f360dc3cf7162d5a98e45ab0a5
4
- data.tar.gz: 954df8c7788ce253f730e4bf6911373198b26c24c2274113b2e12b6d6527b486
3
+ metadata.gz: 8e29c720198c6fd20a8ec55a99ad4ce81c2ff09abf74a55ee6e09513ac17546c
4
+ data.tar.gz: 6ad9b851da6ba7c857dbd79b9e817493f7eaba91be46f16cec3a86cb94c32c11
5
5
  SHA512:
6
- metadata.gz: 26ebf6f4edea8d2a6cf511496bcad2c0e50323c44263c4d27e44f12860f9e4e35e093514bb1fb3cd9925591b5e3db62917af79ee2710b4cc759735dd1ff4d538
7
- data.tar.gz: 1764731c2157cddb200a8dec9286248f7fac4e8a23548305aa09e70b5591d924748179c9534bb46cd6d7d892987963566d95e20854d5928b51a0fbad76dded3b
6
+ metadata.gz: 7de3901b122039da2a99507e40dbe5e57ce8e5622c14591ebf251adf563c763f55d2f1fb1bc7d54c5f2be427a37b81839e636de7eee434a53ab80a39c4f8bf53
7
+ data.tar.gz: 1bfd4bd7c5dd8de6df524ae64d652e2f785955a1928bd3bca66e3ba8515e2d4d60c83ff576c51ca84e744625874145c0850f301b8d2fd6090c226541ad389575
data/README.md CHANGED
@@ -13,10 +13,14 @@ Currently maintained by @radar.
13
13
 
14
14
  You will most commonly use this library within a Rails app.
15
15
 
16
+ We support Rails versions from 6.0 and up.
17
+
16
18
  [See the Rails Guide](https://guides.rubyonrails.org/i18n.html) for an example of its usage.
17
19
 
18
20
  ### Ruby (without Rails)
19
21
 
22
+ We support Ruby versions from 3.0 and up.
23
+
20
24
  If you want to use this library without Rails, you can simply add `i18n` to your `Gemfile`:
21
25
 
22
26
  ```ruby
@@ -55,12 +55,14 @@ module I18n
55
55
 
56
56
  deep_interpolation = options[:deep_interpolation]
57
57
  values = Utils.except(options, *RESERVED_KEYS) unless options.empty?
58
- if values
58
+ if values && !values.empty?
59
59
  entry = if deep_interpolation
60
60
  deep_interpolate(locale, entry, values)
61
61
  else
62
62
  interpolate(locale, entry, values)
63
63
  end
64
+ elsif entry.is_a?(String) && entry =~ I18n.reserved_keys_pattern
65
+ raise ReservedInterpolationKey.new($1.to_sym, entry)
64
66
  end
65
67
  entry
66
68
  end
@@ -186,8 +188,8 @@ module I18n
186
188
  #
187
189
  # if the given subject is an array then:
188
190
  # each element of the array is recursively interpolated (until it finds a string)
189
- # method interpolates ["yes, %{user}", ["maybe no, %{user}, "no, %{user}"]], :user => "bartuz"
190
- # # => "["yes, bartuz",["maybe no, bartuz", "no, bartuz"]]"
191
+ # method interpolates ["yes, %{user}", ["maybe no, %{user}", "no, %{user}"]], :user => "bartuz"
192
+ # # => ["yes, bartuz", ["maybe no, bartuz", "no, bartuz"]]
191
193
  def interpolate(locale, subject, values = EMPTY_HASH)
192
194
  return subject if values.empty?
193
195
 
@@ -95,7 +95,7 @@ module I18n
95
95
  return super unless options.fetch(:fallback, true)
96
96
  I18n.fallbacks[locale].each do |fallback|
97
97
  begin
98
- return true if super(fallback, key)
98
+ return true if super(fallback, key, options)
99
99
  rescue I18n::InvalidLocale
100
100
  # we do nothing when the locale is invalid, as this is a fallback anyways.
101
101
  end
@@ -21,8 +21,7 @@ module I18n
21
21
  module Compiler
22
22
  extend self
23
23
 
24
- TOKENIZER = /(%%\{[^\}]+\}|%\{[^\}]+\})/
25
- INTERPOLATION_SYNTAX_PATTERN = /(%)?(%\{([^\}]+)\})/
24
+ TOKENIZER = /(%%?\{[^}]+\})/
26
25
 
27
26
  def compile_if_an_interpolation(string)
28
27
  if interpolated_str?(string)
@@ -37,7 +36,7 @@ module I18n
37
36
  end
38
37
 
39
38
  def interpolated_str?(str)
40
- str.kind_of?(::String) && str =~ INTERPOLATION_SYNTAX_PATTERN
39
+ str.kind_of?(::String) && str =~ TOKENIZER
41
40
  end
42
41
 
43
42
  protected
@@ -48,13 +47,12 @@ module I18n
48
47
 
49
48
  def compiled_interpolation_body(str)
50
49
  tokenize(str).map do |token|
51
- (matchdata = token.match(INTERPOLATION_SYNTAX_PATTERN)) ? handle_interpolation_token(token, matchdata) : escape_plain_str(token)
50
+ token.match(TOKENIZER) ? handle_interpolation_token(token) : escape_plain_str(token)
52
51
  end.join
53
52
  end
54
53
 
55
- def handle_interpolation_token(interpolation, matchdata)
56
- escaped, pattern, key = matchdata.values_at(1, 2, 3)
57
- escaped ? pattern : compile_interpolation_token(key.to_sym)
54
+ def handle_interpolation_token(token)
55
+ token.start_with?('%%') ? token[1..] : compile_interpolation_token(token[2..-2])
58
56
  end
59
57
 
60
58
  def compile_interpolation_token(key)
@@ -79,14 +79,24 @@ module I18n
79
79
  end
80
80
  end
81
81
 
82
+ def empty?
83
+ @map.empty? && @defaults.empty?
84
+ end
85
+
86
+ def inspect
87
+ "#<#{self.class.name} @map=#{@map.inspect} @defaults=#{@defaults.inspect}>"
88
+ end
89
+
82
90
  protected
83
91
 
84
92
  def compute(tags, include_defaults = true, exclude = [])
85
- result = Array(tags).flat_map do |tag|
93
+ result = []
94
+ Array(tags).each do |tag|
86
95
  tags = I18n::Locale::Tag.tag(tag).self_and_parents.map! { |t| t.to_sym } - exclude
87
- tags.each { |_tag| tags += compute(@map[_tag], false, exclude + tags) if @map[_tag] }
88
- tags
96
+ result += tags
97
+ tags.each { |_tag| result += compute(@map[_tag], false, exclude + result) if @map[_tag] }
89
98
  end
99
+
90
100
  result.push(*defaults) if include_defaults
91
101
  result.uniq!
92
102
  result.compact!
@@ -112,6 +112,28 @@ module I18n
112
112
  assert_raises(I18n::ReservedInterpolationKey) { interpolate(:foo => :bar, :default => '%{default}') }
113
113
  assert_raises(I18n::ReservedInterpolationKey) { interpolate(:foo => :bar, :default => '%{separator}') }
114
114
  assert_raises(I18n::ReservedInterpolationKey) { interpolate(:foo => :bar, :default => '%{scope}') }
115
+ assert_raises(I18n::ReservedInterpolationKey) { interpolate(:default => '%{scope}') }
116
+
117
+ I18n.backend.store_translations(:en, :interpolate => 'Hi %{scope}!')
118
+ assert_raises(I18n::ReservedInterpolationKey) { interpolate(:interpolate) }
119
+ end
120
+
121
+ test "interpolation: it does not raise I18n::ReservedInterpolationKey for escaped variables" do
122
+ assert_nothing_raised do
123
+ assert_equal '%{separator}', interpolate(:foo => :bar, :default => '%%{separator}')
124
+ end
125
+
126
+ # Note: The two interpolations below do not remove the escape character (%) because
127
+ # I18n should not alter the strings when no interpolation parameters are given,
128
+ # see the comment at the top of this file.
129
+ assert_nothing_raised do
130
+ assert_equal '%%{scope}', interpolate(:default => '%%{scope}')
131
+ end
132
+
133
+ I18n.backend.store_translations(:en, :interpolate => 'Hi %%{scope}!')
134
+ assert_nothing_raised do
135
+ assert_equal 'Hi %%{scope}!', interpolate(:interpolate)
136
+ end
115
137
  end
116
138
 
117
139
  test "interpolation: deep interpolation for default string" do
data/lib/i18n/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module I18n
4
- VERSION = "1.14.1"
4
+ VERSION = "1.14.5"
5
5
  end
data/lib/i18n.rb CHANGED
@@ -48,7 +48,7 @@ module I18n
48
48
  end
49
49
 
50
50
  def self.reserved_keys_pattern # :nodoc:
51
- @reserved_keys_pattern ||= /%\{(#{RESERVED_KEYS.join("|")})\}/
51
+ @reserved_keys_pattern ||= /(?<!%)%\{(#{RESERVED_KEYS.join("|")})\}/
52
52
  end
53
53
 
54
54
  module Base
@@ -231,6 +231,35 @@ module I18n
231
231
  end
232
232
  alias :t! :translate!
233
233
 
234
+ # Returns an array of interpolation keys for the given translation key
235
+ #
236
+ # *Examples*
237
+ #
238
+ # Suppose we have the following:
239
+ # I18n.t 'example.zero' == 'Zero interpolations'
240
+ # I18n.t 'example.one' == 'One interpolation %{foo}'
241
+ # I18n.t 'example.two' == 'Two interpolations %{foo} %{bar}'
242
+ # I18n.t 'example.three' == ['One %{foo}', 'Two %{bar}', 'Three %{baz}']
243
+ # I18n.t 'example.one', locale: :other == 'One interpolation %{baz}'
244
+ #
245
+ # Then we can expect the following results:
246
+ # I18n.interpolation_keys('example.zero') #=> []
247
+ # I18n.interpolation_keys('example.one') #=> ['foo']
248
+ # I18n.interpolation_keys('example.two') #=> ['foo', 'bar']
249
+ # I18n.interpolation_keys('example.three') #=> ['foo', 'bar', 'baz']
250
+ # I18n.interpolation_keys('one', scope: 'example', locale: :other) #=> ['baz']
251
+ # I18n.interpolation_keys('does-not-exist') #=> []
252
+ # I18n.interpolation_keys('example') #=> []
253
+ def interpolation_keys(key, **options)
254
+ raise I18n::ArgumentError if !key.is_a?(String) || key.empty?
255
+
256
+ return [] unless exists?(key, **options.slice(:locale, :scope))
257
+
258
+ translation = translate(key, **options.slice(:locale, :scope))
259
+ interpolation_keys_from_translation(translation)
260
+ .flatten.compact
261
+ end
262
+
234
263
  # Returns true if a translation exists for a given key, otherwise returns false.
235
264
  def exists?(key, _locale = nil, locale: _locale, **options)
236
265
  locale ||= config.locale
@@ -429,6 +458,17 @@ module I18n
429
458
  keys
430
459
  end
431
460
  end
461
+
462
+ def interpolation_keys_from_translation(translation)
463
+ case translation
464
+ when ::String
465
+ translation.scan(Regexp.union(I18n.config.interpolation_patterns))
466
+ when ::Array
467
+ translation.map { |element| interpolation_keys_from_translation(element) }
468
+ else
469
+ []
470
+ end
471
+ end
432
472
  end
433
473
 
434
474
  extend Base
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: i18n
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.14.1
4
+ version: 1.14.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sven Fuchs
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2023-06-04 00:00:00.000000000 Z
16
+ date: 2024-05-06 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: concurrent-ruby
@@ -106,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
106
106
  - !ruby/object:Gem::Version
107
107
  version: 1.3.5
108
108
  requirements: []
109
- rubygems_version: 3.3.16
109
+ rubygems_version: 3.5.1
110
110
  signing_key:
111
111
  specification_version: 4
112
112
  summary: New wave Internationalization support for Ruby