tater 1.1.0 → 1.3.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|