tater 2.0.2 → 3.0.1

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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/lib/tater.rb +163 -196
  3. data/test/tater_test.rb +29 -14
  4. metadata +21 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eb7166fcdf9b548f8837e4411bfe5d83712f202e1de27c299fa0e5694b50bc2b
4
- data.tar.gz: ab3f53e56fa8538f39ce59ba9f2e42496e5bea116a880fc26e23ad2091e5e93c
3
+ metadata.gz: 866326a5050b49de428a273e71709fe84f1a4e2e82c5325fef634fcea8282878
4
+ data.tar.gz: af9bdb6c35c928886bf3f50779447e88a9a4f4ce7c36222bb3f5bb762ec2f59a
5
5
  SHA512:
6
- metadata.gz: ceb821ff34a086711feb0df9e21a7aa4c7891a3e84fc52a9cf62d5972462c6ab0a5f81eef2ab820ef95df7211dda82e2a56299a1ced017e468c50358b6e537db
7
- data.tar.gz: 2ad3f4c078bb63e39b5650254f92cb3859c39c73f4eaf2dfae18320e60712bf2c632e42c38770c39a456ee62f13fbf6262e1fef527846c17c53d7ebd859fa6de
6
+ metadata.gz: fb353520b23e2235ee49899e84460f5667a6767c38019ce38e19eb823fe181eba3d7cecc9a4f72a907c77afc279a94994ab505073e29861169cb064b1f56bf32
7
+ data.tar.gz: b6ff46ec8d5f33f44bae20a3996aa9e77bd1a101a6f841e1a35c7af74fca33353817974a2414739734095f6a3315898bec0edd2726eef18ec78feae28edee58d
data/lib/tater.rb CHANGED
@@ -1,97 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
  require 'bigdecimal'
3
+ require 'date'
4
+ require 'time'
3
5
  require 'yaml'
6
+ require 'tater/utils'
7
+ require 'tater/hash' unless Hash.method_defined?(:except)
4
8
 
5
9
  # Tater is a internationalization (i18n) and localization (l10n) library
6
10
  # designed for speed and simplicity.
7
11
  class Tater
8
12
  class MissingLocalizationFormat < ArgumentError; end
9
-
10
13
  class UnLocalizableObject < ArgumentError; end
11
14
 
12
- module Utils # :nodoc:
13
- # Merge all the way down.
14
- #
15
- # @param to [Hash]
16
- # The target Hash to merge into.
17
- # @param from [Hash]
18
- # The Hash to copy values from.
19
- # @return [Hash]
20
- def self.deep_merge(to, from)
21
- to.merge(from) do |_key, left, right|
22
- if left.is_a?(Hash) && right.is_a?(Hash)
23
- Utils.deep_merge(left, right)
24
- else
25
- right
26
- end
27
- end
28
- end
29
-
30
- # Transform keys all the way down.
31
- #
32
- # @param hash [Hash]
33
- # The Hash to stringify keys for.
34
- # @return [Hash]
35
- def self.deep_stringify_keys(hash)
36
- hash.transform_keys(&:to_s).transform_values do |value|
37
- if value.is_a?(Hash)
38
- Utils.deep_stringify_keys(value)
39
- else
40
- value
41
- end
42
- end
43
- end
44
-
45
- # Freeze all the way down.
46
- #
47
- # @param hash [Hash]
48
- # @return [Hash]
49
- def self.deep_freeze(hash)
50
- hash.transform_keys(&:freeze).transform_values do |value|
51
- if value.is_a?(Hash)
52
- Utils.deep_freeze(value)
53
- else
54
- value.freeze
55
- end
56
- end.freeze
57
- end
58
-
59
- # Try to interpolate these things, if one of them is a string.
60
- #
61
- # @param string [String]
62
- # The target string to interpolate into.
63
- # @param options [Hash]
64
- # The values to interpolate into the target string.
65
- #
66
- # @return [String]
67
- def self.interpolate(string, options = HASH)
68
- return string unless string.is_a?(String)
69
- return string if options.empty?
70
-
71
- format(string, options)
72
- end
73
-
74
- # Convert a Numeric to a string, particularly formatting BigDecimals to a
75
- # Float-like string representation.
76
- #
77
- # @param numeric [Numeric]
78
- #
79
- # @return [String]
80
- def self.string_from_numeric(numeric)
81
- if numeric.is_a?(BigDecimal)
82
- numeric.to_s('F')
83
- else
84
- numeric.to_s
85
- end
86
- end
87
- end
88
-
89
15
  DEFAULT = 'default'
90
16
  DELIMITING_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/.freeze
91
17
  HASH = {}.freeze
92
18
  SEPARATOR = '.'
93
19
  SUBSTITUTION_REGEX = /%(|\^)[aAbBpP]/.freeze
94
20
 
21
+ # Needed for Ruby < 3.
22
+ using HashExcept unless Hash.method_defined?(:except)
23
+
95
24
  # @return [String]
96
25
  attr_reader :locale
97
26
 
@@ -107,7 +36,6 @@ class Tater
107
36
  # @param path [String]
108
37
  # A path to search for YAML or Ruby files to load messages from.
109
38
  def initialize(cascade: false, locale: nil, messages: nil, path: nil)
110
- @cache = {}
111
39
  @cascade = cascade
112
40
  @locale = locale
113
41
  @messages = {}
@@ -153,7 +81,7 @@ class Tater
153
81
  end
154
82
 
155
83
  Dir.glob(File.join(path, '**', '*.rb')).each do |file|
156
- @messages = Utils.deep_merge(@messages, Utils.deep_stringify_keys(eval(IO.read(file), binding, file))) # rubocop:disable Security/Eval
84
+ @messages = Utils.deep_merge(@messages, Utils.deep_stringify_keys(eval(File.read(file), binding, file))) # rubocop:disable Security/Eval
157
85
  end
158
86
  end
159
87
 
@@ -165,8 +93,10 @@ class Tater
165
93
 
166
94
  # Not only does this clear our cache but it establishes the basic structure
167
95
  # that we rely on in other methods.
96
+ @cache = {}
97
+
168
98
  @messages.each_key do |key|
169
- @cache[key] = { true => {}, false => {} }
99
+ @cache[key] = { false => {}, true => {} }
170
100
  end
171
101
  end
172
102
 
@@ -190,9 +120,9 @@ class Tater
190
120
  # @option options [String] :locale
191
121
  # The locale to use in lieu of the current default.
192
122
  # @option options [String] :delimiter
193
- # The delimiter to use when localizing numberic values.
123
+ # The delimiter to use when localizing numeric values.
194
124
  # @option options [String] :separator
195
- # The separator to use when localizing numberic values.
125
+ # The separator to use when localizing numeric values.
196
126
  # @option options [String] :two_words_connector
197
127
  # The string used to join two array elements together e.g. " and ".
198
128
  # @option options [String] :words_connector
@@ -208,109 +138,57 @@ class Tater
208
138
  when String
209
139
  object
210
140
  when Numeric
211
- delimiter = options[:delimiter] || lookup('numeric.delimiter', locale: options[:locale])
212
- separator = options[:separator] || lookup('numeric.separator', locale: options[:locale])
213
- precision = options[:precision] || 2
214
-
215
- raise(MissingLocalizationFormat, "Numeric localization delimiter ('numeric.delimiter') missing or not passed as option :delimiter") unless delimiter
216
- raise(MissingLocalizationFormat, "Numeric localization separator ('numeric.separator') missing or not passed as option :separator") unless separator
217
-
218
- # Break the number up into integer and fraction parts.
219
- integer = Utils.string_from_numeric(object)
220
- integer, fraction = integer.split('.') unless object.is_a?(Integer)
221
-
222
- if integer.length > 3
223
- integer.gsub!(DELIMITING_REGEX) do |number|
224
- "#{ number }#{ delimiter }"
225
- end
226
- end
227
-
228
- if precision.zero? || fraction.nil?
229
- integer
230
- else
231
- "#{ integer }#{ separator }#{ fraction.ljust(precision, '0').slice(0, precision) }"
232
- end
141
+ localize_numeric(object, options)
233
142
  when Date, Time, DateTime
234
- format = lookup("#{ object.class.to_s.downcase }.formats.#{ options[:format] || DEFAULT }", locale: options[:locale]) || options[:format] || DEFAULT
235
-
236
- # Heavily cribbed from I18n, many thanks to the people who sorted this out
237
- # before I worked on this library.
238
- format = format.gsub(SUBSTITUTION_REGEX) do |match|
239
- case match
240
- when '%a' then lookup('date.abbreviated_days', locale: options[:locale])[object.wday]
241
- when '%^a' then lookup('date.abbreviated_days', locale: options[:locale])[object.wday].upcase
242
- when '%A' then lookup('date.days', locale: options[:locale])[object.wday]
243
- when '%^A' then lookup('date.days', locale: options[:locale])[object.wday].upcase
244
- when '%b' then lookup('date.abbreviated_months', locale: options[:locale])[object.mon - 1]
245
- when '%^b' then lookup('date.abbreviated_months', locale: options[:locale])[object.mon - 1].upcase
246
- when '%B' then lookup('date.months', locale: options[:locale])[object.mon - 1]
247
- when '%^B' then lookup('date.months', locale: options[:locale])[object.mon - 1].upcase
248
- when '%p' then lookup("time.#{ object.hour < 12 ? 'am' : 'pm' }", locale: options[:locale]).upcase if object.respond_to?(:hour) # rubocop:disable Metrics/BlockNesting
249
- when '%P' then lookup("time.#{ object.hour < 12 ? 'am' : 'pm' }", locale: options[:locale]).downcase if object.respond_to?(:hour) # rubocop:disable Metrics/BlockNesting
250
- end
251
- end
252
-
253
- object.strftime(format)
143
+ localize_datetime(object, options)
254
144
  when Array
255
- case object.length
256
- when 0
257
- ''
258
- when 1
259
- object[0]
260
- when 2
261
- two_words_connector = options[:two_words_connector] || lookup('array.two_words_connector', locale: options[:locale])
262
-
263
- raise(MissingLocalizationFormat, "Sentence localization connector ('array.two_words_connector') missing or not passed as option :two_words_connector") unless two_words_connector
264
-
265
- "#{ object[0] }#{ two_words_connector }#{ object[1] }"
266
- else
267
- last_word_connector = options[:last_word_connector] || lookup('array.last_word_connector', locale: options[:locale])
268
- words_connector = options[:words_connector] || lookup('array.words_connector', locale: options[:locale])
269
-
270
- raise(MissingLocalizationFormat, "Sentence localization connector ('array.last_word_connector') missing or not passed as option :last_word_connector") unless last_word_connector
271
- raise(MissingLocalizationFormat, "Sentence localization connector ('array.words_connector') missing or not passed as option :words_connector") unless words_connector
272
-
273
- "#{ object[0...-1].join(words_connector) }#{ last_word_connector }#{ object[-1] }"
274
- end
145
+ localize_array(object, options)
275
146
  else
276
147
  raise(UnLocalizableObject, "The object class #{ object.class } cannot be localized by Tater.")
277
148
  end
278
149
  end
279
- alias l localize
280
150
 
281
151
  # Lookup a key in the messages hash, using the current locale or an override.
282
152
  #
153
+ # @example Using the default locale, look up a key's value.
154
+ # i18n = Tater.new(locale: 'en', messages: { 'en' => { 'greeting' => { 'world' => 'Hello, world!' } } })
155
+ # i18n.lookup('greeting.world') # => "Hello, world!"
156
+ #
283
157
  # @param key [String]
158
+ # The period-separated key path to look for within our messages.
284
159
  # @param locale [String]
285
- # A locale to use instead of our current one.
160
+ # A locale to use instead of our current one, if any.
286
161
  # @param cascade [Boolean]
287
162
  # A boolean to forcibly set the cascade option for this lookup.
288
163
  #
289
164
  # @return
290
165
  # Basically anything that can be stored in your messages Hash.
291
166
  def lookup(key, locale: nil, cascade: nil)
292
- locale = locale.nil? ? @locale : locale
293
- cascade = cascade.nil? ? @cascade : cascade
167
+ locale =
168
+ if locale.nil?
169
+ @locale
170
+ else
171
+ locale.to_s
172
+ end
294
173
 
295
- cached(key, locale, cascade) || begin
296
- return nil unless @messages.key?(locale.to_s)
174
+ cascade = @cascade if cascade.nil?
297
175
 
298
- path = key.split(SEPARATOR).prepend(locale).map(&:to_s)
176
+ @cache[locale][cascade][key] ||= begin
177
+ path = key.split(SEPARATOR)
299
178
 
300
- message =
301
- if cascade
302
- while path.length >= 2
303
- attempt = @messages.dig(*path)
304
-
305
- break attempt unless attempt.nil?
179
+ message = @messages[locale].dig(*path)
306
180
 
181
+ if message.nil? && cascade
182
+ message =
183
+ while path.length > 1
307
184
  path.delete_at(path.length - 2)
185
+ attempt = @messages[locale].dig(*path)
186
+
187
+ break attempt unless attempt.nil?
308
188
  end
309
- else
310
- @messages.dig(*path)
311
- end
189
+ end
312
190
 
313
- cache(key, locale, cascade, message)
191
+ message
314
192
  end
315
193
  end
316
194
 
@@ -373,51 +251,140 @@ class Tater
373
251
  # The translated and interpreted string, if found, or any data at the
374
252
  # defined key.
375
253
  def translate(key, options = HASH)
376
- message =
377
- if options.key?(:locales)
378
- options[:locales].append(@locale) if @locale && !options[:locales].include?(@locale)
254
+ if options.empty?
255
+ message = lookup(key)
379
256
 
380
- options[:locales].find do |accept|
381
- found = lookup(key, locale: accept, cascade: options[:cascade])
382
-
383
- break found unless found.nil?
384
- end
257
+ if message.is_a?(Proc) # rubocop:disable Style/CaseLikeIf
258
+ message.call(key)
259
+ elsif message.is_a?(String)
260
+ message
385
261
  else
386
- lookup(key, locale: options[:locale], cascade: options[:cascade])
262
+ "Tater lookup failed: #{ locale }.#{ key }"
387
263
  end
264
+ else
265
+ message =
266
+ if options.key?(:locales)
267
+ options[:locales].append(@locale) if @locale && !options[:locales].include?(@locale)
388
268
 
389
- # Call procs that should return a string.
390
- message = message.call(key, options) if message.is_a?(Proc)
269
+ options[:locales].find do |accept|
270
+ found = lookup(key, locale: accept, cascade: options[:cascade])
391
271
 
392
- Utils.interpolate(message, options) || options[:default] || "Tater lookup failed: #{ options[:locale] || options[:locales] || locale }.#{ key }"
272
+ break found unless found.nil?
273
+ end
274
+ else
275
+ lookup(key, locale: options[:locale], cascade: options[:cascade])
276
+ end
277
+
278
+ if message.is_a?(Proc) # rubocop:disable Style/CaseLikeIf
279
+ message.call(key, options.except(:cascade, :default, :locale, :locales))
280
+ elsif message.is_a?(String)
281
+ Utils.interpolate(message, options.except(:cascade, :default, :locale, :locales))
282
+ else
283
+ options[:default] || "Tater lookup failed: #{ options[:locale] || options[:locales] || locale }.#{ key }"
284
+ end
285
+ end
393
286
  end
394
- alias t translate
395
287
 
396
288
  private
397
289
 
398
- # @param key [String]
399
- # The cache key, often in the form "something.nested.like.this"
400
- # @param locale [String]
401
- # The locale to store the value for.
402
- # @param cascade [Boolean]
403
- # Was this a cascading lookup?
404
- # @param message [String]
405
- # The message being cached, often a String.
290
+ # Localize an Array object.
291
+ #
292
+ # @param object [Array<String>]
293
+ # The array to localize.
294
+ # @param options [Hash]
295
+ # Options to configure localization.
296
+ # @return [String]
297
+ # The localize array string.
298
+ def localize_array(object, options)
299
+ case object.length
300
+ when 0
301
+ ''
302
+ when 1
303
+ object[0]
304
+ when 2
305
+ two_words_connector = options[:two_words_connector] || lookup('array.two_words_connector', locale: options[:locale])
306
+
307
+ raise(MissingLocalizationFormat, "Sentence localization connector ('array.two_words_connector') missing or not passed as option :two_words_connector") unless two_words_connector
308
+
309
+ "#{ object[0] }#{ two_words_connector }#{ object[1] }"
310
+ else
311
+ last_word_connector = options[:last_word_connector] || lookup('array.last_word_connector', locale: options[:locale])
312
+ words_connector = options[:words_connector] || lookup('array.words_connector', locale: options[:locale])
313
+
314
+ raise(MissingLocalizationFormat, "Sentence localization connector ('array.last_word_connector') missing or not passed as option :last_word_connector") unless last_word_connector
315
+ raise(MissingLocalizationFormat, "Sentence localization connector ('array.words_connector') missing or not passed as option :words_connector") unless words_connector
316
+
317
+ "#{ object[0...-1].join(words_connector) }#{ last_word_connector }#{ object[-1] }"
318
+ end
319
+ end
320
+
321
+ # Localize a Date, DateTime, or Time object.
322
+ #
323
+ # @param object [Date, DateTime, Time]
324
+ # The date-ish object to localize.
325
+ # @param options [Hash]
326
+ # Options to configure localization.
406
327
  # @return [String]
407
- # Whatever value is being cached, often a String.
408
- def cache(key, locale, cascade, message)
409
- @cache[locale][cascade][key] = message
328
+ # The localized date string.
329
+ def localize_datetime(object, options)
330
+ frmt = options[:format] || DEFAULT
331
+ loc = options[:locale]
332
+ format = lookup("#{ object.class.to_s.downcase }.formats.#{ frmt }", locale: loc) || frmt
333
+
334
+ # Heavily cribbed from I18n, many thanks to the people who sorted this out
335
+ # before I worked on this library.
336
+ format = format.gsub(SUBSTITUTION_REGEX) do |match|
337
+ case match
338
+ when '%a' then lookup('date.abbreviated_days', locale: loc)[object.wday]
339
+ when '%^a' then lookup('date.abbreviated_days', locale: loc)[object.wday].upcase
340
+ when '%A' then lookup('date.days', locale: loc)[object.wday]
341
+ when '%^A' then lookup('date.days', locale: loc)[object.wday].upcase
342
+ when '%b' then lookup('date.abbreviated_months', locale: loc)[object.mon - 1]
343
+ when '%^b' then lookup('date.abbreviated_months', locale: loc)[object.mon - 1].upcase
344
+ when '%B' then lookup('date.months', locale: loc)[object.mon - 1]
345
+ when '%^B' then lookup('date.months', locale: loc)[object.mon - 1].upcase
346
+ when '%p' then lookup("time.#{ object.hour < 12 ? 'am' : 'pm' }", locale: loc).upcase if object.respond_to?(:hour)
347
+ when '%P' then lookup("time.#{ object.hour < 12 ? 'am' : 'pm' }", locale: loc).downcase if object.respond_to?(:hour)
348
+ end
349
+ end
350
+
351
+ if format.include?('%')
352
+ object.strftime(format)
353
+ else
354
+ format
355
+ end
410
356
  end
411
357
 
412
- # @param key [String]
413
- # The cache key, often in the form "something.nested.like.this"
414
- # @param locale [String]
415
- # The locale to store the value for.
416
- # @param cascade [Boolean]
417
- # Was this a cascading lookup?
418
- # @return [String, nil]
419
- # The cached message or nil.
420
- def cached(key, locale, cascade)
421
- @cache.dig(locale, cascade, key)
358
+ # Localize a Numeric object.
359
+ #
360
+ # @param object [Array<String>, Date, Time, DateTime, Numeric]
361
+ # The object to localize.
362
+ # @param options [Hash]
363
+ # Options to configure localization.
364
+ # @return [String]
365
+ # The localized numeric string.
366
+ def localize_numeric(object, options)
367
+ delimiter = options[:delimiter] || lookup('numeric.delimiter', locale: options[:locale])
368
+ separator = options[:separator] || lookup('numeric.separator', locale: options[:locale])
369
+ precision = options[:precision] || 2
370
+
371
+ raise(MissingLocalizationFormat, "Numeric localization delimiter ('numeric.delimiter') missing or not passed as option :delimiter") unless delimiter
372
+ raise(MissingLocalizationFormat, "Numeric localization separator ('numeric.separator') missing or not passed as option :separator") unless separator
373
+
374
+ # Break the number up into integer and fraction parts.
375
+ integer = Utils.string_from_numeric(object)
376
+ integer, fraction = integer.split('.') unless object.is_a?(Integer)
377
+
378
+ if object >= 1_000
379
+ integer.gsub!(DELIMITING_REGEX) do |number|
380
+ "#{ number }#{ delimiter }"
381
+ end
382
+ end
383
+
384
+ if precision.zero? || fraction.nil?
385
+ integer
386
+ else
387
+ "#{ integer }#{ separator }#{ fraction.ljust(precision, '0').slice(0, precision) }"
388
+ end
422
389
  end
423
390
  end
data/test/tater_test.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
- require_relative '../lib/tater'
3
- require 'minitest/autorun'
2
+ $LOAD_PATH.unshift File.expand_path('lib', __dir__)
3
+
4
4
  require 'date'
5
+ require 'minitest/autorun'
6
+ require 'tater'
5
7
 
6
8
  describe Tater do
7
9
  describe Tater::Utils do
@@ -59,6 +61,20 @@ describe Tater do
59
61
  assert_equal '1.0', Tater::Utils.string_from_numeric(BigDecimal('1'))
60
62
  end
61
63
  end
64
+
65
+ describe '#interpolation_string?' do
66
+ def is?(arg)
67
+ Tater::Utils.interpolation_string?(arg)
68
+ end
69
+
70
+ it 'checks whether a string contains interpolation placeholders' do
71
+ assert is?('Hey %{there}!')
72
+ assert is?('Hey %<there>s!')
73
+ refute is?('Nah, this is fine')
74
+ refute is?("<b>HTML shouldn't count")
75
+ refute is?("A single % shouldn't count")
76
+ end
77
+ end
62
78
  end
63
79
 
64
80
  describe '#available?' do
@@ -141,6 +157,10 @@ describe Tater do
141
157
  assert_nil i18n.lookup('nope')
142
158
  end
143
159
 
160
+ it 'returns arbitrary data at keys' do
161
+ assert_equal({ 'key' => 'This key is deeper' }, i18n.lookup('deep'))
162
+ end
163
+
144
164
  it 'cascades' do
145
165
  assert_equal 'Delicious', i18n.lookup('cascade.nope.tacos', cascade: true)
146
166
  assert_equal 'Whoaa', i18n.lookup('cascade.another.nope.crazy', cascade: true)
@@ -162,8 +182,8 @@ describe Tater do
162
182
  assert_equal 'This key is deeper', i18n.translate('deep.key')
163
183
  end
164
184
 
165
- it 'returns a hash for nested keys' do
166
- assert_equal({ 'key' => 'This key is deeper' }, i18n.translate('deep'))
185
+ it 'does not return a hash for nested keys' do
186
+ assert_equal 'Tater lookup failed: en.deep', i18n.translate('deep')
167
187
  end
168
188
 
169
189
  it 'interpolates additional variables' do
@@ -180,10 +200,6 @@ describe Tater do
180
200
  assert_equal 'Tater lookup failed: en.nope', i18n.translate('nope')
181
201
  end
182
202
 
183
- it 'is aliased as t' do
184
- assert_equal 'This is a title', i18n.t('title')
185
- end
186
-
187
203
  it 'cascades lookups' do
188
204
  assert_equal 'Tater lookup failed: en.cascade.another.nope.crazy', i18n.translate('cascade.another.nope.crazy', cascade: false)
189
205
  assert_equal 'Tater lookup failed: en.cascade.nope.tacos', i18n.translate('cascade.nope.tacos')
@@ -201,9 +217,12 @@ describe Tater do
201
217
  assert_equal 'Tater lookup failed: ["fr", "en"].neither', i18n.translate('neither', locales: %w[fr en])
202
218
  end
203
219
 
204
- it 'finds Ruby files as well' do
220
+ it 'finds Ruby files' do
205
221
  assert_equal 'Hey ruby!', i18n.translate('ruby')
206
- assert_equal 'Hey options!', i18n.translate('options', options: 'options')
222
+ end
223
+
224
+ it 'does not interpolate messages returned by procs' do
225
+ assert_equal 'Hey %{options}!', i18n.translate('options', options: 'options')
207
226
  end
208
227
  end
209
228
 
@@ -314,10 +333,6 @@ describe Tater do
314
333
  end
315
334
  end
316
335
 
317
- it 'is aliased l' do
318
- assert_equal '1970/1/1', i18n.l(Date.new(1970, 1, 1))
319
- end
320
-
321
336
  describe 'month, day, and AM/PM names' do
322
337
  let :i18n do
323
338
  Tater.new(path: File.expand_path('test/fixtures'), locale: 'fr')
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: 2.0.2
4
+ version: 3.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Lecklider
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-21 00:00:00.000000000 Z
11
+ date: 2021-12-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -80,6 +80,20 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-packaging
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
98
  name: rubocop-performance
85
99
  requirement: !ruby/object:Gem::Requirement
@@ -128,8 +142,9 @@ licenses:
128
142
  - MIT
129
143
  metadata:
130
144
  bug_tracker_uri: https://github.com/evanleck/tater/issues
145
+ rubygems_mfa_required: 'true'
131
146
  source_code_uri: https://github.com/evanleck/tater
132
- post_install_message:
147
+ post_install_message:
133
148
  rdoc_options: []
134
149
  require_paths:
135
150
  - lib
@@ -144,8 +159,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
144
159
  - !ruby/object:Gem::Version
145
160
  version: '2.0'
146
161
  requirements: []
147
- rubygems_version: 3.2.15
148
- signing_key:
162
+ rubygems_version: 3.2.32
163
+ signing_key:
149
164
  specification_version: 4
150
165
  summary: Minimal internationalization and localization library.
151
166
  test_files: