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 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