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