tater 1.1.0 → 1.3.2
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 +19 -0
- data/lib/tater.rb +100 -15
- data/test/fixtures/fixtures.yml +5 -0
- data/test/fixtures/ruby.rb +4 -3
- data/test/tater_test.rb +62 -2
- metadata +33 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d41ee7aa6cc55a97ad00ee6b96ccbb6127344f9b3e85c4cf3b162f72f3d115d8
|
4
|
+
data.tar.gz: 2fe1b6c5aff5fdfcc48dbd8212ddbbf0903f9af071c42ff1178c1f20752f536a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d686b81192264e1f78f17c0decf0ac3811582e0fe464d2f5f44281481351abca8bd3906fe218a2ae9309a3d84177382f5de7348f63ae3967b7d2af2dc452bcb6
|
7
|
+
data.tar.gz: 942fd8b6a433b65778f3d7a78f07a8d99c902c5483eec105e0c75689d40db7d0664170963e514d5e78928450b6c0499dce8c3afd63f0eaded6d9843cf3e703a8
|
data/README.md
CHANGED
@@ -56,6 +56,25 @@ i18n.translate('some.key') # => 'This here string!'
|
|
56
56
|
i18n.translate('interpolated', you: 'world') # => 'Hello world!'
|
57
57
|
```
|
58
58
|
|
59
|
+
|
60
|
+
## Array Localization
|
61
|
+
|
62
|
+
Given an array, Tater will do it's best to join the elements of the array into a
|
63
|
+
sentence based on how many elements there are.
|
64
|
+
|
65
|
+
```yaml
|
66
|
+
en:
|
67
|
+
array:
|
68
|
+
last_word_connector: ", and "
|
69
|
+
two_words_connector: " and "
|
70
|
+
words_connector: ", "
|
71
|
+
```
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
i18n.localize(%w[tacos enchiladas burritos]) # => "tacos, enchiladas, and burritos"
|
75
|
+
```
|
76
|
+
|
77
|
+
|
59
78
|
## Numeric Localization
|
60
79
|
|
61
80
|
Numeric localization (`Numeric`, `Integer`, `Float`, and `BigDecimal`) require
|
data/lib/tater.rb
CHANGED
@@ -51,6 +51,7 @@ class Tater
|
|
51
51
|
# @return [String]
|
52
52
|
def self.interpolate(string, options = {})
|
53
53
|
return string unless string.is_a?(String)
|
54
|
+
return string if options.empty?
|
54
55
|
|
55
56
|
format(string, options)
|
56
57
|
end
|
@@ -91,6 +92,8 @@ class Tater
|
|
91
92
|
load(messages: messages) if messages
|
92
93
|
end
|
93
94
|
|
95
|
+
# Do lookups cascade by default?
|
96
|
+
#
|
94
97
|
# @return [Boolean]
|
95
98
|
def cascades?
|
96
99
|
@cascade
|
@@ -114,7 +117,7 @@ class Tater
|
|
114
117
|
# files or a collection of messages.
|
115
118
|
#
|
116
119
|
# @param path [String]
|
117
|
-
# A path to search for YAML files to load messages from.
|
120
|
+
# A path to search for YAML or Ruby files to load messages from.
|
118
121
|
# @param messages [Hash]
|
119
122
|
# A hash of messages ready to be loaded in.
|
120
123
|
def load(path: nil, messages: nil)
|
@@ -124,7 +127,7 @@ class Tater
|
|
124
127
|
end
|
125
128
|
|
126
129
|
Dir.glob(File.join(path, '**', '*.rb')).each do |file|
|
127
|
-
Utils.deep_merge!(@messages, Utils.deep_stringify_keys!(eval(IO.read(file), binding, file)))
|
130
|
+
Utils.deep_merge!(@messages, Utils.deep_stringify_keys!(eval(IO.read(file), binding, file))) # rubocop:disable Security/Eval
|
128
131
|
end
|
129
132
|
end
|
130
133
|
|
@@ -139,10 +142,28 @@ class Tater
|
|
139
142
|
@locale = locale.to_s if available?(locale)
|
140
143
|
end
|
141
144
|
|
142
|
-
# Localize
|
145
|
+
# Localize an Array, Date, Time, DateTime, or Numeric object.
|
143
146
|
#
|
144
147
|
# @param object [Date, Time, DateTime, Numeric]
|
145
148
|
# The object to localize.
|
149
|
+
# @param options [Hash]
|
150
|
+
# Options to configure localization.
|
151
|
+
#
|
152
|
+
# @option options [String] :format
|
153
|
+
# The key or format string to use for localizing the current object.
|
154
|
+
# @option options [String] :locale
|
155
|
+
# The locale to use in lieu of the current default.
|
156
|
+
# @option options [String] :delimiter
|
157
|
+
# The delimiter to use when localizing numberic values.
|
158
|
+
# @option options [String] :separator
|
159
|
+
# The separator to use when localizing numberic values.
|
160
|
+
# @option options [String] :two_words_connector
|
161
|
+
# The string used to join two array elements together e.g. " and ".
|
162
|
+
# @option options [String] :words_connector
|
163
|
+
# The string used to connect multiple array elements e.g. ", ".
|
164
|
+
# @option options [String] :last_word_connector
|
165
|
+
# The string used to connect the final element with preceding array elements
|
166
|
+
# e.g. ", and ".
|
146
167
|
#
|
147
168
|
# @return [String]
|
148
169
|
# A localized version of the object passed in.
|
@@ -156,10 +177,10 @@ class Tater
|
|
156
177
|
when Numeric
|
157
178
|
delimiter = options.delete(:delimiter) || lookup('numeric.delimiter', locale_override)
|
158
179
|
separator = options.delete(:separator) || lookup('numeric.separator', locale_override)
|
159
|
-
precision = options.
|
180
|
+
precision = options.delete(:precision) || 2
|
160
181
|
|
161
|
-
raise(MissingLocalizationFormat, "Numeric localization delimiter ('numeric.delimiter') missing") unless delimiter
|
162
|
-
raise(MissingLocalizationFormat, "Numeric localization separator ('numeric.separator') missing") unless separator
|
182
|
+
raise(MissingLocalizationFormat, "Numeric localization delimiter ('numeric.delimiter') missing or not passed as option :delimiter") unless delimiter
|
183
|
+
raise(MissingLocalizationFormat, "Numeric localization separator ('numeric.separator') missing or not passed as option :separator") unless separator
|
163
184
|
|
164
185
|
# Heavily cribbed from Rails.
|
165
186
|
integer, fraction = Utils.string_from_numeric(object).split('.')
|
@@ -194,6 +215,27 @@ class Tater
|
|
194
215
|
end
|
195
216
|
|
196
217
|
object.strftime(format)
|
218
|
+
when Array
|
219
|
+
case object.length
|
220
|
+
when 0
|
221
|
+
''
|
222
|
+
when 1
|
223
|
+
object[0]
|
224
|
+
when 2
|
225
|
+
two_words_connector = options.delete(:two_words_connector) || lookup('array.two_words_connector', locale_override)
|
226
|
+
|
227
|
+
raise(MissingLocalizationFormat, "Sentence localization connector ('array.two_words_connector') missing or not passed as option :two_words_connector") unless two_words_connector
|
228
|
+
|
229
|
+
"#{ object[0] }#{ two_words_connector }#{ object[1] }"
|
230
|
+
else
|
231
|
+
last_word_connector = options.delete(:last_word_connector) || lookup('array.last_word_connector', locale_override)
|
232
|
+
words_connector = options.delete(:words_connector) || lookup('array.words_connector', locale_override)
|
233
|
+
|
234
|
+
raise(MissingLocalizationFormat, "Sentence localization connector ('array.last_word_connector') missing or not passed as option :last_word_connector") unless last_word_connector
|
235
|
+
raise(MissingLocalizationFormat, "Sentence localization connector ('array.words_connector') missing or not passed as option :words_connector") unless words_connector
|
236
|
+
|
237
|
+
"#{ object[0...-1].join(words_connector) }#{ last_word_connector }#{ object[-1] }"
|
238
|
+
end
|
197
239
|
else
|
198
240
|
raise(UnLocalizableObject, "The object class #{ object.class } cannot be localized by Tater.")
|
199
241
|
end
|
@@ -205,6 +247,8 @@ class Tater
|
|
205
247
|
# @param key [String]
|
206
248
|
# @param locale_override [String]
|
207
249
|
# A locale to use instead of our current one.
|
250
|
+
# @param cascade_override [Boolean]
|
251
|
+
# A boolean to forcibly set the cascade option for this lookup.
|
208
252
|
#
|
209
253
|
# @return
|
210
254
|
# Basically anything that can be stored in YAML, including nil.
|
@@ -212,20 +256,53 @@ class Tater
|
|
212
256
|
path = key.split(SEPARATOR).prepend(locale_override || locale).map(&:to_s)
|
213
257
|
|
214
258
|
if cascade_override.nil? ? @cascade : cascade_override
|
215
|
-
while path.length >= 2
|
259
|
+
while path.length >= 2
|
216
260
|
attempt = @messages.dig(*path)
|
217
261
|
|
218
|
-
if attempt
|
219
|
-
|
220
|
-
|
221
|
-
path.delete_at(path.length - 2)
|
222
|
-
end
|
262
|
+
break attempt if attempt
|
263
|
+
|
264
|
+
path.delete_at(path.length - 2)
|
223
265
|
end
|
224
266
|
else
|
225
267
|
@messages.dig(*path)
|
226
268
|
end
|
227
269
|
end
|
228
270
|
|
271
|
+
# Check that there's a key at the given path.
|
272
|
+
#
|
273
|
+
# @param key [String]
|
274
|
+
# The period-separated key path to look within our messages for.
|
275
|
+
# @param options [Hash]
|
276
|
+
# Options to pass to the #lookup method, including locale overrides.
|
277
|
+
#
|
278
|
+
# @option options [Boolean] :cascade
|
279
|
+
# Should this lookup cascade or not? Can override @cascade.
|
280
|
+
# @option options [String] :locale
|
281
|
+
# A specific locale to lookup within. This will take precedence over the
|
282
|
+
# :locales option.
|
283
|
+
# @option options [Array<String>] :locales
|
284
|
+
# An array of locales to look within.
|
285
|
+
#
|
286
|
+
# @return [Boolean]
|
287
|
+
def includes?(key, options = {})
|
288
|
+
cascade_override = options.delete(:cascade)
|
289
|
+
locale_override = options.delete(:locale)
|
290
|
+
locales = options.delete(:locales)
|
291
|
+
|
292
|
+
message =
|
293
|
+
if locale_override || !locales
|
294
|
+
lookup(key, locale_override, cascade_override)
|
295
|
+
else
|
296
|
+
locales.find do |accept|
|
297
|
+
found = lookup(key, accept, cascade_override)
|
298
|
+
|
299
|
+
break found if found
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
!message.nil?
|
304
|
+
end
|
305
|
+
|
229
306
|
# Translate a key path and optional interpolation arguments into a string.
|
230
307
|
# It's effectively a combination of #lookup and #interpolate.
|
231
308
|
#
|
@@ -234,23 +311,31 @@ class Tater
|
|
234
311
|
#
|
235
312
|
# @param key [String]
|
236
313
|
# The period-separated key path to look within our messages for.
|
314
|
+
# @param options [Hash]
|
315
|
+
# Options, including values to interpolate to any found string.
|
316
|
+
#
|
237
317
|
# @option options [Boolean] :cascade
|
318
|
+
# Should this lookup cascade or not? Can override @cascade.
|
238
319
|
# @option options [String] :default
|
320
|
+
# A default string to return, should lookup fail.
|
239
321
|
# @option options [String] :locale
|
322
|
+
# A specific locale to lookup within. This will take precedence over the
|
323
|
+
# :locales option.
|
324
|
+
# @option options [Array<String>] :locales
|
325
|
+
# An array of locales to look within.
|
240
326
|
#
|
241
327
|
# @return [String]
|
242
328
|
# The translated and interpreted string, if found, or any data at the
|
243
329
|
# defined key.
|
244
330
|
def translate(key, options = {})
|
245
331
|
cascade_override = options.delete(:cascade)
|
246
|
-
default = options.delete(:default)
|
247
332
|
locale_override = options.delete(:locale)
|
248
333
|
locales = options.delete(:locales)
|
249
334
|
|
250
335
|
message =
|
251
336
|
if locale_override || !locales
|
252
337
|
lookup(key, locale_override, cascade_override)
|
253
|
-
|
338
|
+
else
|
254
339
|
locales.find do |accept|
|
255
340
|
found = lookup(key, accept, cascade_override)
|
256
341
|
|
@@ -263,7 +348,7 @@ class Tater
|
|
263
348
|
message = message.call(key, options)
|
264
349
|
end
|
265
350
|
|
266
|
-
Utils.interpolate(message, options) || default
|
351
|
+
Utils.interpolate(message, options) || options.delete(:default) { "Tater lookup failed: #{ locale_override || locales || locale }.#{ key }" }
|
267
352
|
end
|
268
353
|
alias t translate
|
269
354
|
end
|
data/test/fixtures/fixtures.yml
CHANGED
data/test/fixtures/ruby.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
{
|
2
3
|
en: {
|
3
|
-
ruby: proc do |key,
|
4
|
+
ruby: proc do |key, _options = {}|
|
4
5
|
"Hey #{ key }!"
|
5
6
|
end,
|
6
|
-
options: proc do |_key,
|
7
|
-
|
7
|
+
options: proc do |_key, _options = {}|
|
8
|
+
'Hey %{options}!'
|
8
9
|
end
|
9
10
|
}
|
10
11
|
}
|
data/test/tater_test.rb
CHANGED
@@ -31,11 +31,15 @@ describe Tater do
|
|
31
31
|
assert_equal 'this thing', Tater::Utils.interpolate('this %{what}', what: 'thing')
|
32
32
|
end
|
33
33
|
|
34
|
-
it 'raises a KeyError when an argument is missing' do
|
34
|
+
it 'raises a KeyError when an argument is missing (but options are passed)' do
|
35
35
|
assert_raises(KeyError) do
|
36
|
-
Tater::Utils.interpolate('this %{what}')
|
36
|
+
Tater::Utils.interpolate('this %{what}', nope: 'thing')
|
37
37
|
end
|
38
38
|
end
|
39
|
+
|
40
|
+
it 'returns the string unchanged when options are empty (does not raise a KeyError)' do
|
41
|
+
assert_equal 'this %{what}', Tater::Utils.interpolate('this %{what}')
|
42
|
+
end
|
39
43
|
end
|
40
44
|
|
41
45
|
describe '#string_from_numeric' do
|
@@ -116,6 +120,7 @@ describe Tater do
|
|
116
120
|
it 'cascades' do
|
117
121
|
assert_equal 'Delicious', i18n.lookup('cascade.nope.tacos', nil, true)
|
118
122
|
assert_equal 'Whoaa', i18n.lookup('cascade.another.nope.crazy', nil, true)
|
123
|
+
assert_nil i18n.lookup('cascade.another.nope.crazy', nil, false)
|
119
124
|
assert_nil i18n.lookup('cascade.nahhhhhh')
|
120
125
|
end
|
121
126
|
end
|
@@ -156,6 +161,7 @@ describe Tater do
|
|
156
161
|
end
|
157
162
|
|
158
163
|
it 'cascades lookups' do
|
164
|
+
assert_equal 'Tater lookup failed: en.cascade.another.nope.crazy', i18n.translate('cascade.another.nope.crazy', cascade: false)
|
159
165
|
assert_equal 'Tater lookup failed: en.cascade.nope.tacos', i18n.translate('cascade.nope.tacos')
|
160
166
|
assert_equal 'Delicious', i18n.translate('cascade.nope.tacos', cascade: true)
|
161
167
|
end
|
@@ -182,6 +188,35 @@ describe Tater do
|
|
182
188
|
Tater.new(path: File.expand_path('test/fixtures'))
|
183
189
|
end
|
184
190
|
|
191
|
+
let :fr do
|
192
|
+
Tater.new(path: File.expand_path('test/fixtures'), locale: 'fr')
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'localizes arrays' do
|
196
|
+
assert_equal 'tacos and burritos', i18n.localize(%w[tacos burritos])
|
197
|
+
assert_equal 'tacos', i18n.localize(%w[tacos])
|
198
|
+
assert_equal 'tacos, enchiladas, and burritos', i18n.localize(%w[tacos enchiladas burritos])
|
199
|
+
|
200
|
+
assert_equal 'tacos + enchiladas ++ burritos', fr.localize(%w[tacos enchiladas burritos], words_connector: ' + ', last_word_connector: ' ++ ')
|
201
|
+
assert_equal 'tacostwoburritos', fr.localize(%w[tacos burritos], two_words_connector: 'two')
|
202
|
+
|
203
|
+
assert_raises(Tater::MissingLocalizationFormat) do
|
204
|
+
fr.localize(%w[tacos burritos])
|
205
|
+
end
|
206
|
+
|
207
|
+
assert_raises(Tater::MissingLocalizationFormat) do
|
208
|
+
fr.localize(%w[tacos burritos], last_word_connector: 'last', words_connector: 'words')
|
209
|
+
end
|
210
|
+
|
211
|
+
assert_raises(Tater::MissingLocalizationFormat) do
|
212
|
+
fr.localize(%w[tacos burritos], last_word_connector: 'last')
|
213
|
+
end
|
214
|
+
|
215
|
+
assert_raises(Tater::MissingLocalizationFormat) do
|
216
|
+
fr.localize(%w[tacos burritos], words_connector: 'words')
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
185
220
|
it 'localizes Dates' do
|
186
221
|
assert_equal '1970/1/1', i18n.localize(Date.new(1970, 1, 1))
|
187
222
|
end
|
@@ -332,4 +367,29 @@ describe Tater do
|
|
332
367
|
assert cascade.cascades?
|
333
368
|
end
|
334
369
|
end
|
370
|
+
|
371
|
+
describe '#includes?' do
|
372
|
+
let :i18n do
|
373
|
+
Tater.new(path: File.expand_path('test/fixtures'))
|
374
|
+
end
|
375
|
+
|
376
|
+
it 'tells you if you have a translation' do
|
377
|
+
assert i18n.includes?('deep')
|
378
|
+
assert i18n.includes?('deep.key')
|
379
|
+
refute i18n.includes?('deep.nope')
|
380
|
+
refute i18n.includes?('nope')
|
381
|
+
end
|
382
|
+
|
383
|
+
it 'allows overriding the locale' do
|
384
|
+
assert i18n.includes?('french', locale: 'fr')
|
385
|
+
assert i18n.includes?('french', locales: %w[en fr])
|
386
|
+
refute i18n.includes?('french', locales: %w[en])
|
387
|
+
refute i18n.includes?('french')
|
388
|
+
end
|
389
|
+
|
390
|
+
it 'allows cascading' do
|
391
|
+
assert i18n.includes?('cascade.nope.tacos', cascade: true)
|
392
|
+
refute i18n.includes?('cascade.nope.tacos', cascade: false)
|
393
|
+
end
|
394
|
+
end
|
335
395
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tater
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Evan Lecklider
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-08-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: rubocop
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,6 +52,20 @@ dependencies:
|
|
38
52
|
- - ">="
|
39
53
|
- !ruby/object:Gem::Version
|
40
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rubocop-performance
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
41
69
|
description: Minimal internationalization and localization library.
|
42
70
|
email:
|
43
71
|
- evan@lecklider.com
|
@@ -74,13 +102,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
74
102
|
- !ruby/object:Gem::Version
|
75
103
|
version: '2.0'
|
76
104
|
requirements: []
|
77
|
-
rubygems_version: 3.
|
105
|
+
rubygems_version: 3.1.4
|
78
106
|
signing_key:
|
79
107
|
specification_version: 4
|
80
108
|
summary: Minimal internationalization and localization library.
|
81
109
|
test_files:
|
82
|
-
- test/fixtures/messages/more.yml
|
83
110
|
- test/fixtures/ruby.rb
|
84
|
-
- test/fixtures/fixtures.yml
|
85
111
|
- test/fixtures/another.yml
|
112
|
+
- test/fixtures/fixtures.yml
|
113
|
+
- test/fixtures/messages/more.yml
|
86
114
|
- test/tater_test.rb
|