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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bce1afa07abcf6f94618ca249808f14d4a16b107c112bc074b4f0e6e2703b86d
4
- data.tar.gz: 3f481fbb8e9551fee057472c3bef0fbc2420c9e267a37e09bf353a19e9877130
3
+ metadata.gz: d41ee7aa6cc55a97ad00ee6b96ccbb6127344f9b3e85c4cf3b162f72f3d115d8
4
+ data.tar.gz: 2fe1b6c5aff5fdfcc48dbd8212ddbbf0903f9af071c42ff1178c1f20752f536a
5
5
  SHA512:
6
- metadata.gz: 9096e096441db2696735213dd21c313aa333877062f3094680a2ff7bbb14c5e3e699d817a8df59626f570eff345b5b69a942ec99cd19ab3bc74cbdee0cb9b398
7
- data.tar.gz: 8511e008cf509bd76869aef9957e98e7f73bb60b50649abe811f09ad4ee01447553689ffe52451f8073110d1338ad430ef7848f25a4e3fce1614c22d60153dc3
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
@@ -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 a Date, Time, DateTime, or Numeric object.
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.fetch(:precision) { 2 }
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 do
259
+ while path.length >= 2
216
260
  attempt = @messages.dig(*path)
217
261
 
218
- if attempt
219
- break attempt
220
- else
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
- elsif locales
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 || "Tater lookup failed: #{ locale_override || locales || locale }.#{ key }"
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
@@ -8,6 +8,11 @@ en:
8
8
  deep:
9
9
  key: 'This key is deeper'
10
10
 
11
+ array:
12
+ last_word_connector: ", and "
13
+ two_words_connector: " and "
14
+ words_connector: ", "
15
+
11
16
  date:
12
17
  formats:
13
18
  default: '%Y/%-m/%-d'
@@ -1,10 +1,11 @@
1
+ # frozen_string_literal: true
1
2
  {
2
3
  en: {
3
- ruby: proc do |key, options = {}|
4
+ ruby: proc do |key, _options = {}|
4
5
  "Hey #{ key }!"
5
6
  end,
6
- options: proc do |_key, options = {}|
7
- "Hey %{options}!"
7
+ options: proc do |_key, _options = {}|
8
+ 'Hey %{options}!'
8
9
  end
9
10
  }
10
11
  }
@@ -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.1.0
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: 2019-07-09 00:00:00.000000000 Z
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.0.4
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