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.
- checksums.yaml +4 -4
- data/lib/tater.rb +163 -196
- data/test/tater_test.rb +29 -14
- metadata +21 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 866326a5050b49de428a273e71709fe84f1a4e2e82c5325fef634fcea8282878
|
4
|
+
data.tar.gz: af9bdb6c35c928886bf3f50779447e88a9a4f4ce7c36222bb3f5bb762ec2f59a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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(
|
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] = {
|
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
|
123
|
+
# The delimiter to use when localizing numeric values.
|
194
124
|
# @option options [String] :separator
|
195
|
-
# The separator to use when localizing
|
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
|
-
|
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
|
-
|
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
|
-
|
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 =
|
293
|
-
|
167
|
+
locale =
|
168
|
+
if locale.nil?
|
169
|
+
@locale
|
170
|
+
else
|
171
|
+
locale.to_s
|
172
|
+
end
|
294
173
|
|
295
|
-
|
296
|
-
return nil unless @messages.key?(locale.to_s)
|
174
|
+
cascade = @cascade if cascade.nil?
|
297
175
|
|
298
|
-
|
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
|
-
|
310
|
-
@messages.dig(*path)
|
311
|
-
end
|
189
|
+
end
|
312
190
|
|
313
|
-
|
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
|
-
|
377
|
-
|
378
|
-
options[:locales].append(@locale) if @locale && !options[:locales].include?(@locale)
|
254
|
+
if options.empty?
|
255
|
+
message = lookup(key)
|
379
256
|
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
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
|
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
|
-
|
390
|
-
|
269
|
+
options[:locales].find do |accept|
|
270
|
+
found = lookup(key, locale: accept, cascade: options[:cascade])
|
391
271
|
|
392
|
-
|
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
|
-
#
|
399
|
-
#
|
400
|
-
# @param
|
401
|
-
# The
|
402
|
-
# @param
|
403
|
-
#
|
404
|
-
# @
|
405
|
-
# The
|
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
|
-
#
|
408
|
-
def
|
409
|
-
|
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
|
-
#
|
413
|
-
#
|
414
|
-
# @param
|
415
|
-
# The
|
416
|
-
# @param
|
417
|
-
#
|
418
|
-
# @return [String
|
419
|
-
# The
|
420
|
-
def
|
421
|
-
|
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
|
-
|
3
|
-
|
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 '
|
166
|
-
assert_equal
|
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
|
220
|
+
it 'finds Ruby files' do
|
205
221
|
assert_equal 'Hey ruby!', i18n.translate('ruby')
|
206
|
-
|
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:
|
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-
|
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.
|
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:
|