tater 1.0.1 → 1.1.0
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 +99 -1
- data/lib/tater.rb +51 -7
- data/test/{another.yml → fixtures/another.yml} +0 -0
- data/test/{fixtures.yml → fixtures/fixtures.yml} +10 -0
- data/test/{messages → fixtures/messages}/more.yml +0 -0
- data/test/fixtures/ruby.rb +10 -0
- data/test/tater_test.rb +59 -9
- metadata +12 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bce1afa07abcf6f94618ca249808f14d4a16b107c112bc074b4f0e6e2703b86d
|
4
|
+
data.tar.gz: 3f481fbb8e9551fee057472c3bef0fbc2420c9e267a37e09bf353a19e9877130
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9096e096441db2696735213dd21c313aa333877062f3094680a2ff7bbb14c5e3e699d817a8df59626f570eff345b5b69a942ec99cd19ab3bc74cbdee0cb9b398
|
7
|
+
data.tar.gz: 8511e008cf509bd76869aef9957e98e7f73bb60b50649abe811f09ad4ee01447553689ffe52451f8073110d1338ad430ef7848f25a4e3fce1614c22d60153dc3
|
data/README.md
CHANGED
@@ -59,7 +59,7 @@ i18n.translate('interpolated', you: 'world') # => 'Hello world!'
|
|
59
59
|
## Numeric Localization
|
60
60
|
|
61
61
|
Numeric localization (`Numeric`, `Integer`, `Float`, and `BigDecimal`) require
|
62
|
-
filling in a
|
62
|
+
filling in a separator and delimiter. For example:
|
63
63
|
|
64
64
|
```yaml
|
65
65
|
en:
|
@@ -74,6 +74,12 @@ With that, you can do things like this:
|
|
74
74
|
i18n.localize(1000.2) # => "1,000.20"
|
75
75
|
```
|
76
76
|
|
77
|
+
The separator and delimiter can also be passed in per-call:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
i18n.localize(1000.2, delimiter: '_', separator: '+') # => "1_000+20"
|
81
|
+
```
|
82
|
+
|
77
83
|
|
78
84
|
## Date and Time Localization
|
79
85
|
|
@@ -160,6 +166,98 @@ i18n.localize(Date.new(1970, 1, 1), format: 'day') # => 'jeudi'
|
|
160
166
|
```
|
161
167
|
|
162
168
|
|
169
|
+
## Cascading Lookups
|
170
|
+
|
171
|
+
Lookups can be cascaded, i.e. pieces of the scope of the can be lopped off
|
172
|
+
incrementally.
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
messages = {
|
176
|
+
'login' => {
|
177
|
+
'title' => 'Login',
|
178
|
+
'description' => 'Normal description.'
|
179
|
+
|
180
|
+
'special' => {
|
181
|
+
'title' => 'Special Login'
|
182
|
+
}
|
183
|
+
}
|
184
|
+
}
|
185
|
+
|
186
|
+
i18n = Tater.new(messages: messages)
|
187
|
+
i18n.translate('login.special.title') # => 'Special Login'
|
188
|
+
i18n.translate('login.special.description') # => 'Tater lookup failed'
|
189
|
+
|
190
|
+
i18n.translate('login.special.description', cascade: true) # => 'Normal description.'
|
191
|
+
```
|
192
|
+
|
193
|
+
With cascade, the final key stays the same, but pieces of the scope get lopped
|
194
|
+
off. In this case, lookups will be tried in this order:
|
195
|
+
|
196
|
+
1. `'login.special.description'`
|
197
|
+
2. `'login.description'`
|
198
|
+
|
199
|
+
This can be useful when you want to override some messages but don't want to
|
200
|
+
have to copy all of the other, non-overwritten messages.
|
201
|
+
|
202
|
+
Cascading can also be enabled by default when initializing an instance of Tater.
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
Tater.new(cascade: true)
|
206
|
+
```
|
207
|
+
|
208
|
+
Cascading is off by default.
|
209
|
+
|
210
|
+
|
211
|
+
## Defaults
|
212
|
+
|
213
|
+
If you'd like to default to another value in case of a missed lookup, you can
|
214
|
+
provide the `:default` option to `#translate`.
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
Tater.new.translate('nope', default: 'Yep!') # => 'Yep!'
|
218
|
+
```
|
219
|
+
|
220
|
+
|
221
|
+
## Procs and Messages in Ruby
|
222
|
+
|
223
|
+
Ruby files can be used to store messages in addition to YAML, so long as the
|
224
|
+
Ruby file returns a `Hash` when evalled.
|
225
|
+
|
226
|
+
```ruby
|
227
|
+
{
|
228
|
+
en: {
|
229
|
+
ruby: proc do |key, options = {}|
|
230
|
+
"Hey #{ key }!"
|
231
|
+
end
|
232
|
+
}
|
233
|
+
}
|
234
|
+
```
|
235
|
+
|
236
|
+
|
237
|
+
## Multiple Locales
|
238
|
+
|
239
|
+
If you like to check multiple locales and pull the first matching one out, you
|
240
|
+
can pass the `:locales` option an array of top-level locale keys.
|
241
|
+
|
242
|
+
```ruby
|
243
|
+
messages = {
|
244
|
+
'en' => {
|
245
|
+
'title' => 'Login',
|
246
|
+
'description' => 'English description.'
|
247
|
+
},
|
248
|
+
'fr' => {
|
249
|
+
'title' => 'la connexion'
|
250
|
+
}
|
251
|
+
}
|
252
|
+
|
253
|
+
i18n = Tater.new(messages: messages)
|
254
|
+
i18n.translate('title', locales: %w[fr en]) # => 'la connexion'
|
255
|
+
i18n.translate('description', locales: %w[fr en]) # => 'English description.'
|
256
|
+
```
|
257
|
+
|
258
|
+
Locales will tried in order and which one matches first will be returned.
|
259
|
+
|
260
|
+
|
163
261
|
## Limitations
|
164
262
|
|
165
263
|
- It is not "pluggable", it does what it does and that's it.
|
data/lib/tater.rb
CHANGED
@@ -82,7 +82,8 @@ class Tater
|
|
82
82
|
# @return [Hash]
|
83
83
|
attr_reader :messages
|
84
84
|
|
85
|
-
def initialize(path: nil, messages: nil, locale: DEFAULT_LOCALE)
|
85
|
+
def initialize(path: nil, messages: nil, locale: DEFAULT_LOCALE, cascade: false)
|
86
|
+
@cascade = cascade
|
86
87
|
@locale = locale
|
87
88
|
@messages = {}
|
88
89
|
|
@@ -90,6 +91,11 @@ class Tater
|
|
90
91
|
load(messages: messages) if messages
|
91
92
|
end
|
92
93
|
|
94
|
+
# @return [Boolean]
|
95
|
+
def cascades?
|
96
|
+
@cascade
|
97
|
+
end
|
98
|
+
|
93
99
|
# An array of the available locale codes.
|
94
100
|
#
|
95
101
|
# @return [Array]
|
@@ -114,7 +120,11 @@ class Tater
|
|
114
120
|
def load(path: nil, messages: nil)
|
115
121
|
if path
|
116
122
|
Dir.glob(File.join(path, '**', '*.{yml,yaml}')).each do |file|
|
117
|
-
Utils.deep_merge!(@messages, YAML.
|
123
|
+
Utils.deep_merge!(@messages, YAML.load_file(file))
|
124
|
+
end
|
125
|
+
|
126
|
+
Dir.glob(File.join(path, '**', '*.rb')).each do |file|
|
127
|
+
Utils.deep_merge!(@messages, Utils.deep_stringify_keys!(eval(IO.read(file), binding, file)))
|
118
128
|
end
|
119
129
|
end
|
120
130
|
|
@@ -144,8 +154,8 @@ class Tater
|
|
144
154
|
when String
|
145
155
|
object
|
146
156
|
when Numeric
|
147
|
-
delimiter = lookup('numeric.delimiter', locale_override)
|
148
|
-
separator = lookup('numeric.separator', locale_override)
|
157
|
+
delimiter = options.delete(:delimiter) || lookup('numeric.delimiter', locale_override)
|
158
|
+
separator = options.delete(:separator) || lookup('numeric.separator', locale_override)
|
149
159
|
precision = options.fetch(:precision) { 2 }
|
150
160
|
|
151
161
|
raise(MissingLocalizationFormat, "Numeric localization delimiter ('numeric.delimiter') missing") unless delimiter
|
@@ -198,10 +208,22 @@ class Tater
|
|
198
208
|
#
|
199
209
|
# @return
|
200
210
|
# Basically anything that can be stored in YAML, including nil.
|
201
|
-
def lookup(key, locale_override = nil)
|
211
|
+
def lookup(key, locale_override = nil, cascade_override = nil)
|
202
212
|
path = key.split(SEPARATOR).prepend(locale_override || locale).map(&:to_s)
|
203
213
|
|
204
|
-
@
|
214
|
+
if cascade_override.nil? ? @cascade : cascade_override
|
215
|
+
while path.length >= 2 do
|
216
|
+
attempt = @messages.dig(*path)
|
217
|
+
|
218
|
+
if attempt
|
219
|
+
break attempt
|
220
|
+
else
|
221
|
+
path.delete_at(path.length - 2)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
else
|
225
|
+
@messages.dig(*path)
|
226
|
+
end
|
205
227
|
end
|
206
228
|
|
207
229
|
# Translate a key path and optional interpolation arguments into a string.
|
@@ -212,14 +234,36 @@ class Tater
|
|
212
234
|
#
|
213
235
|
# @param key [String]
|
214
236
|
# The period-separated key path to look within our messages for.
|
237
|
+
# @option options [Boolean] :cascade
|
238
|
+
# @option options [String] :default
|
239
|
+
# @option options [String] :locale
|
215
240
|
#
|
216
241
|
# @return [String]
|
217
242
|
# The translated and interpreted string, if found, or any data at the
|
218
243
|
# defined key.
|
219
244
|
def translate(key, options = {})
|
245
|
+
cascade_override = options.delete(:cascade)
|
246
|
+
default = options.delete(:default)
|
220
247
|
locale_override = options.delete(:locale)
|
248
|
+
locales = options.delete(:locales)
|
249
|
+
|
250
|
+
message =
|
251
|
+
if locale_override || !locales
|
252
|
+
lookup(key, locale_override, cascade_override)
|
253
|
+
elsif locales
|
254
|
+
locales.find do |accept|
|
255
|
+
found = lookup(key, accept, cascade_override)
|
256
|
+
|
257
|
+
break found if found
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# Call procs that should return a string.
|
262
|
+
if message.is_a?(Proc)
|
263
|
+
message = message.call(key, options)
|
264
|
+
end
|
221
265
|
|
222
|
-
Utils.interpolate(
|
266
|
+
Utils.interpolate(message, options) || default || "Tater lookup failed: #{ locale_override || locales || locale }.#{ key }"
|
223
267
|
end
|
224
268
|
alias t translate
|
225
269
|
end
|
File without changes
|
@@ -1,4 +1,6 @@
|
|
1
1
|
en:
|
2
|
+
english: 'Found in English'
|
3
|
+
|
2
4
|
title: 'This is a title'
|
3
5
|
interpolated: 'This has some %{fancy} text'
|
4
6
|
double: '%{first} %{second}!'
|
@@ -23,7 +25,15 @@ en:
|
|
23
25
|
delimiter: 'TURKEYS'
|
24
26
|
separator: 'NAH'
|
25
27
|
|
28
|
+
cascade:
|
29
|
+
tacos: 'Delicious'
|
30
|
+
|
31
|
+
another:
|
32
|
+
crazy: 'Whoaa'
|
33
|
+
|
26
34
|
fr:
|
35
|
+
french: 'Found in French'
|
36
|
+
|
27
37
|
time:
|
28
38
|
am: 'am'
|
29
39
|
pm: 'pm'
|
File without changes
|
data/test/tater_test.rb
CHANGED
@@ -49,7 +49,7 @@ describe Tater do
|
|
49
49
|
|
50
50
|
describe '#available?' do
|
51
51
|
let :i18n do
|
52
|
-
Tater.new(path: File.expand_path('test'))
|
52
|
+
Tater.new(path: File.expand_path('test/fixtures'))
|
53
53
|
end
|
54
54
|
|
55
55
|
it 'tells you if the locale is available' do
|
@@ -60,14 +60,14 @@ describe Tater do
|
|
60
60
|
|
61
61
|
describe '#load' do
|
62
62
|
it 'loads from a path on initialization' do
|
63
|
-
i18n = Tater.new(path: File.expand_path('test'))
|
63
|
+
i18n = Tater.new(path: File.expand_path('test/fixtures'))
|
64
64
|
|
65
65
|
assert_instance_of(Hash, i18n.messages)
|
66
66
|
end
|
67
67
|
|
68
68
|
it 'loads from a path after initialization' do
|
69
69
|
i18n = Tater.new
|
70
|
-
i18n.load(path: File.expand_path('test'))
|
70
|
+
i18n.load(path: File.expand_path('test/fixtures'))
|
71
71
|
|
72
72
|
assert_instance_of(Hash, i18n.messages)
|
73
73
|
end
|
@@ -88,7 +88,7 @@ describe Tater do
|
|
88
88
|
|
89
89
|
describe '#available' do
|
90
90
|
let :i18n do
|
91
|
-
Tater.new(path: File.expand_path('test'))
|
91
|
+
Tater.new(path: File.expand_path('test/fixtures'))
|
92
92
|
end
|
93
93
|
|
94
94
|
it 'returns an array with the available locales (i.e. the top-level keys in our messages hash)' do
|
@@ -98,7 +98,7 @@ describe Tater do
|
|
98
98
|
|
99
99
|
describe '#lookup' do
|
100
100
|
let :i18n do
|
101
|
-
Tater.new(path: File.expand_path('test'))
|
101
|
+
Tater.new(path: File.expand_path('test/fixtures'))
|
102
102
|
end
|
103
103
|
|
104
104
|
it 'returns keys from messages' do
|
@@ -112,11 +112,17 @@ describe Tater do
|
|
112
112
|
it 'returns nil for missing lookups' do
|
113
113
|
assert_nil i18n.lookup('nope')
|
114
114
|
end
|
115
|
+
|
116
|
+
it 'cascades' do
|
117
|
+
assert_equal 'Delicious', i18n.lookup('cascade.nope.tacos', nil, true)
|
118
|
+
assert_equal 'Whoaa', i18n.lookup('cascade.another.nope.crazy', nil, true)
|
119
|
+
assert_nil i18n.lookup('cascade.nahhhhhh')
|
120
|
+
end
|
115
121
|
end
|
116
122
|
|
117
123
|
describe '#translate' do
|
118
124
|
let :i18n do
|
119
|
-
Tater.new(path: File.expand_path('test'))
|
125
|
+
Tater.new(path: File.expand_path('test/fixtures'))
|
120
126
|
end
|
121
127
|
|
122
128
|
it 'translates strings' do
|
@@ -148,11 +154,32 @@ describe Tater do
|
|
148
154
|
it 'is aliased as t' do
|
149
155
|
assert_equal 'This is a title', i18n.t('title')
|
150
156
|
end
|
157
|
+
|
158
|
+
it 'cascades lookups' do
|
159
|
+
assert_equal 'Tater lookup failed: en.cascade.nope.tacos', i18n.translate('cascade.nope.tacos')
|
160
|
+
assert_equal 'Delicious', i18n.translate('cascade.nope.tacos', cascade: true)
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'defaults lookups' do
|
164
|
+
assert_equal 'Tater lookup failed: en.default.missing', i18n.translate('default.missing')
|
165
|
+
assert_equal 'Nope', i18n.translate('default.missing', default: 'Nope')
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'does lookups across different locales' do
|
169
|
+
assert_equal 'Found in French', i18n.translate('french', locales: %w[fr en])
|
170
|
+
assert_equal 'Found in English', i18n.translate('english', locales: %w[fr en])
|
171
|
+
assert_equal 'Tater lookup failed: ["fr", "en"].neither', i18n.translate('neither', locales: %w[fr en])
|
172
|
+
end
|
173
|
+
|
174
|
+
it 'finds Ruby files as well' do
|
175
|
+
assert_equal 'Hey ruby!', i18n.translate('ruby')
|
176
|
+
assert_equal 'Hey options!', i18n.translate('options', options: 'options')
|
177
|
+
end
|
151
178
|
end
|
152
179
|
|
153
180
|
describe '#localize' do
|
154
181
|
let :i18n do
|
155
|
-
Tater.new(path: File.expand_path('test'))
|
182
|
+
Tater.new(path: File.expand_path('test/fixtures'))
|
156
183
|
end
|
157
184
|
|
158
185
|
it 'localizes Dates' do
|
@@ -179,6 +206,11 @@ describe Tater do
|
|
179
206
|
assert_equal '1NAH12', i18n.localize(BigDecimal('1.12'))
|
180
207
|
end
|
181
208
|
|
209
|
+
it 'allows overriding the delimiter and separator' do
|
210
|
+
assert_equal '10WOO000NAH12', i18n.localize(10_000.12, delimiter: 'WOO')
|
211
|
+
assert_equal '10TURKEYS000YA12', i18n.localize(10_000.12, separator: 'YA')
|
212
|
+
end
|
213
|
+
|
182
214
|
it 'accepts other formats' do
|
183
215
|
assert_equal '1 something 1 oh my 1970', i18n.localize(Date.new(1970, 1, 1), format: 'ohmy')
|
184
216
|
end
|
@@ -205,7 +237,7 @@ describe Tater do
|
|
205
237
|
|
206
238
|
describe 'month, day, and AM/PM names' do
|
207
239
|
let :i18n do
|
208
|
-
Tater.new(path: File.expand_path('test'), locale: 'fr')
|
240
|
+
Tater.new(path: File.expand_path('test/fixtures'), locale: 'fr')
|
209
241
|
end
|
210
242
|
|
211
243
|
it 'localizes day names' do
|
@@ -269,7 +301,7 @@ describe Tater do
|
|
269
301
|
|
270
302
|
describe '#locale=' do
|
271
303
|
let :i18n do
|
272
|
-
Tater.new(path: File.expand_path('test'))
|
304
|
+
Tater.new(path: File.expand_path('test/fixtures'))
|
273
305
|
end
|
274
306
|
|
275
307
|
it 'overrides the locale when available' do
|
@@ -282,4 +314,22 @@ describe Tater do
|
|
282
314
|
assert_equal 'en', i18n.locale
|
283
315
|
end
|
284
316
|
end
|
317
|
+
|
318
|
+
describe '#cascades?' do
|
319
|
+
let :default do
|
320
|
+
Tater.new
|
321
|
+
end
|
322
|
+
|
323
|
+
let :cascade do
|
324
|
+
Tater.new(cascade: true)
|
325
|
+
end
|
326
|
+
|
327
|
+
it 'returns false by default' do
|
328
|
+
refute default.cascades?
|
329
|
+
end
|
330
|
+
|
331
|
+
it 'returns true when passed during initialization' do
|
332
|
+
assert cascade.cascades?
|
333
|
+
end
|
334
|
+
end
|
285
335
|
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.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Evan Lecklider
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -48,9 +48,10 @@ files:
|
|
48
48
|
- LICENSE.txt
|
49
49
|
- README.md
|
50
50
|
- lib/tater.rb
|
51
|
-
- test/another.yml
|
52
|
-
- test/fixtures.yml
|
53
|
-
- test/messages/more.yml
|
51
|
+
- test/fixtures/another.yml
|
52
|
+
- test/fixtures/fixtures.yml
|
53
|
+
- test/fixtures/messages/more.yml
|
54
|
+
- test/fixtures/ruby.rb
|
54
55
|
- test/tater_test.rb
|
55
56
|
homepage: https://github.com/evanleck/tater
|
56
57
|
licenses:
|
@@ -71,14 +72,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
71
72
|
requirements:
|
72
73
|
- - ">="
|
73
74
|
- !ruby/object:Gem::Version
|
74
|
-
version: '0'
|
75
|
+
version: '2.0'
|
75
76
|
requirements: []
|
76
|
-
rubygems_version: 3.0.
|
77
|
+
rubygems_version: 3.0.4
|
77
78
|
signing_key:
|
78
79
|
specification_version: 4
|
79
80
|
summary: Minimal internationalization and localization library.
|
80
81
|
test_files:
|
81
|
-
- test/messages/more.yml
|
82
|
-
- test/fixtures.
|
82
|
+
- test/fixtures/messages/more.yml
|
83
|
+
- test/fixtures/ruby.rb
|
84
|
+
- test/fixtures/fixtures.yml
|
85
|
+
- test/fixtures/another.yml
|
83
86
|
- test/tater_test.rb
|
84
|
-
- test/another.yml
|