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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2f47c6fb87ebbbaa22bd33575dc6009a31e62fcbf81c9de5bd3d9a9e46d59e79
4
- data.tar.gz: 3ba6ff030c7f2aa67163c2d230a2839f43b16381a295f3258d2b538c03283251
3
+ metadata.gz: bce1afa07abcf6f94618ca249808f14d4a16b107c112bc074b4f0e6e2703b86d
4
+ data.tar.gz: 3f481fbb8e9551fee057472c3bef0fbc2420c9e267a37e09bf353a19e9877130
5
5
  SHA512:
6
- metadata.gz: c95c9b85915657d2327d1c92488dda8041fd8a3fc0f8cbdf27681c972723b2a26009b26f021b6b937114c7690acb4c27c58d99e6693fb870c0f905860d267b17
7
- data.tar.gz: 5cb0d1ad507632d7ba48368d8df5c26047e1ee7beca54eb591dd7f6e3ab37869ae5eb077d3dc5f5bb7e8c56dd5339904b6a91a4abf49ada5e98a6def48137e1a
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 separater and delimiter. For example:
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.safe_load(File.read(file)))
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
- @messages.dig(*path)
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(lookup(key, locale_override), options) || "Tater lookup failed: #{ locale_override || locale }.#{ key }"
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
@@ -0,0 +1,10 @@
1
+ {
2
+ en: {
3
+ ruby: proc do |key, options = {}|
4
+ "Hey #{ key }!"
5
+ end,
6
+ options: proc do |_key, options = {}|
7
+ "Hey %{options}!"
8
+ end
9
+ }
10
+ }
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.1
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-03-17 00:00:00.000000000 Z
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.3
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.yml
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