i18n 1.14.1 → 1.14.6
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 +4 -4
- data/README.md +4 -0
- data/lib/i18n/backend/base.rb +15 -5
- data/lib/i18n/backend/fallbacks.rb +6 -2
- data/lib/i18n/backend/interpolation_compiler.rb +5 -7
- data/lib/i18n/backend/simple.rb +8 -8
- data/lib/i18n/locale/fallbacks.rb +13 -3
- data/lib/i18n/tests/defaults.rb +7 -0
- data/lib/i18n/tests/interpolation.rb +22 -0
- data/lib/i18n/tests/lookup.rb +6 -0
- data/lib/i18n/tests/procs.rb +1 -0
- data/lib/i18n/version.rb +1 -1
- data/lib/i18n.rb +47 -5
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: abb47988c5f7107e7752235391d3841b093b22129643b5800fe208fd885c238a
|
4
|
+
data.tar.gz: 95343bb49011a881932676ca8e561a28e0779b1972893d9ece8c012886ac862c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bdeb6008dae46d97da006f84446c0be3a81ffe8fe275ea4a6866673e663b13664fc225c3dcc7314c0db4aa4ef269a8fbcb6b58b5a359452de90625e1a826ff6d
|
7
|
+
data.tar.gz: ca01e033ca49fb385a3e05b3ff72bbfd5a5f256e22379f564367e72eabe125beb0c418654fb423523077a0a9aece57ad9e94d72ee646e5a6483e3e7e14525cc3
|
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
|
data/lib/i18n/backend/base.rb
CHANGED
@@ -54,13 +54,16 @@ module I18n
|
|
54
54
|
end
|
55
55
|
|
56
56
|
deep_interpolation = options[:deep_interpolation]
|
57
|
+
skip_interpolation = options[:skip_interpolation]
|
57
58
|
values = Utils.except(options, *RESERVED_KEYS) unless options.empty?
|
58
|
-
if values
|
59
|
+
if !skip_interpolation && values && !values.empty?
|
59
60
|
entry = if deep_interpolation
|
60
61
|
deep_interpolate(locale, entry, values)
|
61
62
|
else
|
62
63
|
interpolate(locale, entry, values)
|
63
64
|
end
|
65
|
+
elsif entry.is_a?(String) && entry =~ I18n.reserved_keys_pattern
|
66
|
+
raise ReservedInterpolationKey.new($1.to_sym, entry)
|
64
67
|
end
|
65
68
|
entry
|
66
69
|
end
|
@@ -149,7 +152,14 @@ module I18n
|
|
149
152
|
result = catch(:exception) do
|
150
153
|
case subject
|
151
154
|
when Symbol
|
152
|
-
I18n.translate(
|
155
|
+
I18n.translate(
|
156
|
+
subject,
|
157
|
+
**options.merge(
|
158
|
+
:locale => locale,
|
159
|
+
:throw => true,
|
160
|
+
:skip_interpolation => true
|
161
|
+
)
|
162
|
+
)
|
153
163
|
when Proc
|
154
164
|
date_or_time = options.delete(:object) || object
|
155
165
|
resolve(locale, object, subject.call(date_or_time, **options))
|
@@ -186,8 +196,8 @@ module I18n
|
|
186
196
|
#
|
187
197
|
# if the given subject is an array then:
|
188
198
|
# 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
|
-
# # =>
|
199
|
+
# method interpolates ["yes, %{user}", ["maybe no, %{user}", "no, %{user}"]], :user => "bartuz"
|
200
|
+
# # => ["yes, bartuz", ["maybe no, bartuz", "no, bartuz"]]
|
191
201
|
def interpolate(locale, subject, values = EMPTY_HASH)
|
192
202
|
return subject if values.empty?
|
193
203
|
|
@@ -242,7 +252,7 @@ module I18n
|
|
242
252
|
# Loads a plain Ruby translations file. eval'ing the file must yield
|
243
253
|
# a Hash containing translation data with locales as toplevel keys.
|
244
254
|
def load_rb(filename)
|
245
|
-
translations = eval(IO.read(filename), binding, filename)
|
255
|
+
translations = eval(IO.read(filename), binding, filename.to_s)
|
246
256
|
[translations, false]
|
247
257
|
end
|
248
258
|
|
@@ -71,7 +71,11 @@ module I18n
|
|
71
71
|
|
72
72
|
case subject
|
73
73
|
when Symbol
|
74
|
-
I18n.translate(subject, **options.merge(
|
74
|
+
I18n.translate(subject, **options.merge(
|
75
|
+
:locale => options[:fallback_original_locale],
|
76
|
+
:throw => true,
|
77
|
+
:skip_interpolation => true
|
78
|
+
))
|
75
79
|
when Proc
|
76
80
|
date_or_time = options.delete(:object) || object
|
77
81
|
resolve_entry(options[:fallback_original_locale], object, subject.call(date_or_time, **options))
|
@@ -95,7 +99,7 @@ module I18n
|
|
95
99
|
return super unless options.fetch(:fallback, true)
|
96
100
|
I18n.fallbacks[locale].each do |fallback|
|
97
101
|
begin
|
98
|
-
return true if super(fallback, key)
|
102
|
+
return true if super(fallback, key, options)
|
99
103
|
rescue I18n::InvalidLocale
|
100
104
|
# we do nothing when the locale is invalid, as this is a fallback anyways.
|
101
105
|
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 =~
|
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
|
-
|
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(
|
56
|
-
|
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)
|
data/lib/i18n/backend/simple.rb
CHANGED
@@ -10,14 +10,14 @@ module I18n
|
|
10
10
|
# The implementation is provided by a Implementation module allowing to easily
|
11
11
|
# extend Simple backend's behavior by including modules. E.g.:
|
12
12
|
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
13
|
+
# module I18n::Backend::Pluralization
|
14
|
+
# def pluralize(*args)
|
15
|
+
# # extended pluralization logic
|
16
|
+
# super
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# I18n::Backend::Simple.include(I18n::Backend::Pluralization)
|
21
21
|
class Simple
|
22
22
|
module Implementation
|
23
23
|
include Base
|
@@ -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 =
|
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
|
-
|
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!
|
data/lib/i18n/tests/defaults.rb
CHANGED
@@ -47,6 +47,13 @@ module I18n
|
|
47
47
|
I18n.backend.store_translations(:en, { :foo => { :bar => 'bar' } }, { :separator => '|' })
|
48
48
|
assert_equal 'bar', I18n.t(nil, :default => :'foo|bar', :separator => '|')
|
49
49
|
end
|
50
|
+
|
51
|
+
# Addresses issue: #599
|
52
|
+
test "defaults: only interpolates once when resolving defaults" do
|
53
|
+
I18n.backend.store_translations(:en, :greeting => 'hey %{name}')
|
54
|
+
assert_equal 'hey %{dont_interpolate_me}',
|
55
|
+
I18n.t(:does_not_exist, :name => '%{dont_interpolate_me}', default: [:greeting])
|
56
|
+
end
|
50
57
|
end
|
51
58
|
end
|
52
59
|
end
|
@@ -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/tests/lookup.rb
CHANGED
@@ -76,6 +76,12 @@ module I18n
|
|
76
76
|
test "lookup: a resulting Hash is not frozen" do
|
77
77
|
assert !I18n.t(:hash).frozen?
|
78
78
|
end
|
79
|
+
|
80
|
+
# Addresses issue: #599
|
81
|
+
test "lookup: only interpolates once when resolving symbols" do
|
82
|
+
I18n.backend.store_translations(:en, foo: :bar, bar: '%{value}')
|
83
|
+
assert_equal '%{dont_interpolate_me}', I18n.t(:foo, value: '%{dont_interpolate_me}')
|
84
|
+
end
|
79
85
|
end
|
80
86
|
end
|
81
87
|
end
|
data/lib/i18n/tests/procs.rb
CHANGED
data/lib/i18n/version.rb
CHANGED
data/lib/i18n.rb
CHANGED
@@ -19,6 +19,7 @@ module I18n
|
|
19
19
|
RESERVED_KEYS = %i[
|
20
20
|
cascade
|
21
21
|
deep_interpolation
|
22
|
+
skip_interpolation
|
22
23
|
default
|
23
24
|
exception_handler
|
24
25
|
fallback
|
@@ -48,7 +49,7 @@ module I18n
|
|
48
49
|
end
|
49
50
|
|
50
51
|
def self.reserved_keys_pattern # :nodoc:
|
51
|
-
@reserved_keys_pattern ||=
|
52
|
+
@reserved_keys_pattern ||= /(?<!%)%\{(#{RESERVED_KEYS.join("|")})\}/
|
52
53
|
end
|
53
54
|
|
54
55
|
module Base
|
@@ -161,7 +162,7 @@ module I18n
|
|
161
162
|
# or <tt>default</tt> if no translations for <tt>:foo</tt> and <tt>:bar</tt> were found.
|
162
163
|
# I18n.t :foo, :default => [:bar, 'default']
|
163
164
|
#
|
164
|
-
#
|
165
|
+
# <b>BULK LOOKUP</b>
|
165
166
|
#
|
166
167
|
# This returns an array with the translations for <tt>:foo</tt> and <tt>:bar</tt>.
|
167
168
|
# I18n.t [:foo, :bar]
|
@@ -180,7 +181,7 @@ module I18n
|
|
180
181
|
# E.g. assuming the key <tt>:salutation</tt> resolves to:
|
181
182
|
# lambda { |key, options| options[:gender] == 'm' ? "Mr. #{options[:name]}" : "Mrs. #{options[:name]}" }
|
182
183
|
#
|
183
|
-
# Then <tt>I18n.t(:salutation, :gender => 'w', :name => 'Smith') will result in "Mrs. Smith".
|
184
|
+
# Then <tt>I18n.t(:salutation, :gender => 'w', :name => 'Smith')</tt> will result in "Mrs. Smith".
|
184
185
|
#
|
185
186
|
# Note that the string returned by lambda will go through string interpolation too,
|
186
187
|
# so the following lambda would give the same result:
|
@@ -192,7 +193,7 @@ module I18n
|
|
192
193
|
# always return the same translations/values per unique combination of argument
|
193
194
|
# values.
|
194
195
|
#
|
195
|
-
#
|
196
|
+
# <b>Ruby 2.7+ keyword arguments warning</b>
|
196
197
|
#
|
197
198
|
# This method uses keyword arguments.
|
198
199
|
# There is a breaking change in ruby that produces warning with ruby 2.7 and won't work as expected with ruby 3.0
|
@@ -231,11 +232,41 @@ module I18n
|
|
231
232
|
end
|
232
233
|
alias :t! :translate!
|
233
234
|
|
235
|
+
# Returns an array of interpolation keys for the given translation key
|
236
|
+
#
|
237
|
+
# *Examples*
|
238
|
+
#
|
239
|
+
# Suppose we have the following:
|
240
|
+
# I18n.t 'example.zero' == 'Zero interpolations'
|
241
|
+
# I18n.t 'example.one' == 'One interpolation %{foo}'
|
242
|
+
# I18n.t 'example.two' == 'Two interpolations %{foo} %{bar}'
|
243
|
+
# I18n.t 'example.three' == ['One %{foo}', 'Two %{bar}', 'Three %{baz}']
|
244
|
+
# I18n.t 'example.one', locale: :other == 'One interpolation %{baz}'
|
245
|
+
#
|
246
|
+
# Then we can expect the following results:
|
247
|
+
# I18n.interpolation_keys('example.zero') #=> []
|
248
|
+
# I18n.interpolation_keys('example.one') #=> ['foo']
|
249
|
+
# I18n.interpolation_keys('example.two') #=> ['foo', 'bar']
|
250
|
+
# I18n.interpolation_keys('example.three') #=> ['foo', 'bar', 'baz']
|
251
|
+
# I18n.interpolation_keys('one', scope: 'example', locale: :other) #=> ['baz']
|
252
|
+
# I18n.interpolation_keys('does-not-exist') #=> []
|
253
|
+
# I18n.interpolation_keys('example') #=> []
|
254
|
+
def interpolation_keys(key, **options)
|
255
|
+
raise I18n::ArgumentError if !key.is_a?(String) || key.empty?
|
256
|
+
|
257
|
+
return [] unless exists?(key, **options.slice(:locale, :scope))
|
258
|
+
|
259
|
+
translation = translate(key, **options.slice(:locale, :scope))
|
260
|
+
interpolation_keys_from_translation(translation)
|
261
|
+
.flatten.compact
|
262
|
+
end
|
263
|
+
|
234
264
|
# Returns true if a translation exists for a given key, otherwise returns false.
|
235
265
|
def exists?(key, _locale = nil, locale: _locale, **options)
|
236
266
|
locale ||= config.locale
|
237
267
|
raise Disabled.new('exists?') if locale == false
|
238
|
-
raise I18n::ArgumentError if key.is_a?(String) && key.empty?
|
268
|
+
raise I18n::ArgumentError if (key.is_a?(String) && key.empty?) || key.nil?
|
269
|
+
|
239
270
|
config.backend.exists?(locale, key, options)
|
240
271
|
end
|
241
272
|
|
@@ -429,6 +460,17 @@ module I18n
|
|
429
460
|
keys
|
430
461
|
end
|
431
462
|
end
|
463
|
+
|
464
|
+
def interpolation_keys_from_translation(translation)
|
465
|
+
case translation
|
466
|
+
when ::String
|
467
|
+
translation.scan(Regexp.union(I18n.config.interpolation_patterns))
|
468
|
+
when ::Array
|
469
|
+
translation.map { |element| interpolation_keys_from_translation(element) }
|
470
|
+
else
|
471
|
+
[]
|
472
|
+
end
|
473
|
+
end
|
432
474
|
end
|
433
475
|
|
434
476
|
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.
|
4
|
+
version: 1.14.6
|
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:
|
16
|
+
date: 2024-09-15 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: concurrent-ruby
|
@@ -91,7 +91,10 @@ metadata:
|
|
91
91
|
changelog_uri: https://github.com/ruby-i18n/i18n/releases
|
92
92
|
documentation_uri: https://guides.rubyonrails.org/i18n.html
|
93
93
|
source_code_uri: https://github.com/ruby-i18n/i18n
|
94
|
-
post_install_message:
|
94
|
+
post_install_message: 'PSA: I18n will be dropping support for Ruby < 3.2 in the next
|
95
|
+
major release (April 2025), due to Ruby''s end of life for 3.1 and below (https://endoflife.date/ruby).
|
96
|
+
Please upgrade to Ruby 3.2 or newer by April 2025 to continue using future versions
|
97
|
+
of this gem.'
|
95
98
|
rdoc_options: []
|
96
99
|
require_paths:
|
97
100
|
- lib
|
@@ -106,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
106
109
|
- !ruby/object:Gem::Version
|
107
110
|
version: 1.3.5
|
108
111
|
requirements: []
|
109
|
-
rubygems_version: 3.3.
|
112
|
+
rubygems_version: 3.3.27
|
110
113
|
signing_key:
|
111
114
|
specification_version: 4
|
112
115
|
summary: New wave Internationalization support for Ruby
|