tater 1.3.2 → 2.0.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 +4 -4
- data/README.md +35 -22
- data/lib/tater.rb +149 -85
- data/test/tater_test.rb +42 -18
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 16fddfa1cedd9af51e3d2e04422a083a16ee055e1102ad4482bfb28beee3be78
|
4
|
+
data.tar.gz: 73273faf3a6bb5842d6adc2854ca98e81363efd0c45835de07fc2e4d807effa2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a9da7fc7081b2043891c83f6ef46c1facdff44f85175ce5052af04a0e6f22bb82ffb0505a29d24a7d2a91ba00e81c5b8fccdb1c6b7118de48680639e474a7f13
|
7
|
+
data.tar.gz: 0ccb2a820ea03144b96319c61eb0b645ca86002cf88c657b906669b39702dbdefb83e009a3b34fb5adda4a9fd47deaa30fbd0c0580ff34b80c9e9c5d4051a204
|
data/README.md
CHANGED
@@ -27,7 +27,7 @@ And then execute:
|
|
27
27
|
bundle
|
28
28
|
```
|
29
29
|
|
30
|
-
Or install it yourself
|
30
|
+
Or install it yourself by running:
|
31
31
|
|
32
32
|
```sh
|
33
33
|
gem install tater
|
@@ -40,15 +40,20 @@ gem install tater
|
|
40
40
|
require 'tater'
|
41
41
|
|
42
42
|
messages = {
|
43
|
-
'
|
44
|
-
'
|
45
|
-
|
46
|
-
|
43
|
+
'en' => {
|
44
|
+
'some' => {
|
45
|
+
'key' => 'This here string!'
|
46
|
+
},
|
47
|
+
'interpolated' => 'Hello %{you}!'
|
48
|
+
}
|
47
49
|
}
|
48
50
|
|
49
|
-
i18n = Tater.new
|
51
|
+
i18n = Tater.new(locale: 'en')
|
50
52
|
i18n.load(messages: messages)
|
51
53
|
|
54
|
+
# OR
|
55
|
+
i18n = Tater.new(locale: 'en', messages: messages)
|
56
|
+
|
52
57
|
# Basic lookup:
|
53
58
|
i18n.translate('some.key') # => 'This here string!'
|
54
59
|
|
@@ -57,7 +62,7 @@ i18n.translate('interpolated', you: 'world') # => 'Hello world!'
|
|
57
62
|
```
|
58
63
|
|
59
64
|
|
60
|
-
## Array
|
65
|
+
## Array localization
|
61
66
|
|
62
67
|
Given an array, Tater will do it's best to join the elements of the array into a
|
63
68
|
sentence based on how many elements there are.
|
@@ -75,7 +80,7 @@ i18n.localize(%w[tacos enchiladas burritos]) # => "tacos, enchiladas, and burrit
|
|
75
80
|
```
|
76
81
|
|
77
82
|
|
78
|
-
## Numeric
|
83
|
+
## Numeric localization
|
79
84
|
|
80
85
|
Numeric localization (`Numeric`, `Integer`, `Float`, and `BigDecimal`) require
|
81
86
|
filling in a separator and delimiter. For example:
|
@@ -100,7 +105,7 @@ i18n.localize(1000.2, delimiter: '_', separator: '+') # => "1_000+20"
|
|
100
105
|
```
|
101
106
|
|
102
107
|
|
103
|
-
## Date and
|
108
|
+
## Date and time localization
|
104
109
|
|
105
110
|
Date and time localization (`Date`, `Time`, and `DateTime`) require filling in
|
106
111
|
all of the needed names and abbreviations for days and months. Here's the
|
@@ -185,24 +190,26 @@ i18n.localize(Date.new(1970, 1, 1), format: 'day') # => 'jeudi'
|
|
185
190
|
```
|
186
191
|
|
187
192
|
|
188
|
-
## Cascading
|
193
|
+
## Cascading lookups
|
189
194
|
|
190
195
|
Lookups can be cascaded, i.e. pieces of the scope of the can be lopped off
|
191
196
|
incrementally.
|
192
197
|
|
193
198
|
```ruby
|
194
199
|
messages = {
|
195
|
-
'
|
196
|
-
'
|
197
|
-
|
200
|
+
'en' => {
|
201
|
+
'login' => {
|
202
|
+
'title' => 'Login',
|
203
|
+
'description' => 'Normal description.'
|
198
204
|
|
199
|
-
|
200
|
-
|
205
|
+
'special' => {
|
206
|
+
'title' => 'Special Login'
|
207
|
+
}
|
201
208
|
}
|
202
209
|
}
|
203
210
|
}
|
204
211
|
|
205
|
-
i18n = Tater.new(messages: messages)
|
212
|
+
i18n = Tater.new(locale: 'en', messages: messages)
|
206
213
|
i18n.translate('login.special.title') # => 'Special Login'
|
207
214
|
i18n.translate('login.special.description') # => 'Tater lookup failed'
|
208
215
|
|
@@ -237,14 +244,14 @@ Tater.new.translate('nope', default: 'Yep!') # => 'Yep!'
|
|
237
244
|
```
|
238
245
|
|
239
246
|
|
240
|
-
## Procs and
|
247
|
+
## Procs and messages in Ruby
|
241
248
|
|
242
249
|
Ruby files can be used to store messages in addition to YAML, so long as the
|
243
250
|
Ruby file returns a `Hash` when evalled.
|
244
251
|
|
245
252
|
```ruby
|
246
253
|
{
|
247
|
-
en
|
254
|
+
'en' => {
|
248
255
|
ruby: proc do |key, options = {}|
|
249
256
|
"Hey #{ key }!"
|
250
257
|
end
|
@@ -253,10 +260,11 @@ Ruby file returns a `Hash` when evalled.
|
|
253
260
|
```
|
254
261
|
|
255
262
|
|
256
|
-
## Multiple
|
263
|
+
## Multiple locales
|
257
264
|
|
258
|
-
If you like to check multiple locales and pull the first matching one out,
|
259
|
-
can pass the `:locales` option
|
265
|
+
If you would like to check multiple locales and pull the first matching one out,
|
266
|
+
you can pass the `:locales` option to initialization or the `translate` method
|
267
|
+
with an array of top-level locale keys.
|
260
268
|
|
261
269
|
```ruby
|
262
270
|
messages = {
|
@@ -272,9 +280,14 @@ messages = {
|
|
272
280
|
i18n = Tater.new(messages: messages)
|
273
281
|
i18n.translate('title', locales: %w[fr en]) # => 'la connexion'
|
274
282
|
i18n.translate('description', locales: %w[fr en]) # => 'English description.'
|
283
|
+
|
284
|
+
# OR
|
285
|
+
i18n = Tater.new(messages: messages, locales: %w[fr en])
|
286
|
+
i18n.translate('title') # => 'la connexion'
|
287
|
+
i18n.translate('description') # => 'English description.'
|
275
288
|
```
|
276
289
|
|
277
|
-
Locales will tried in order and
|
290
|
+
Locales will be tried in order and whichever one matches first will be returned.
|
278
291
|
|
279
292
|
|
280
293
|
## Limitations
|
data/lib/tater.rb
CHANGED
@@ -12,14 +12,14 @@ class Tater
|
|
12
12
|
# Merge all the way down.
|
13
13
|
#
|
14
14
|
# @param to [Hash]
|
15
|
-
# The target Hash to merge into.
|
16
|
-
# not on a copy of the object.
|
15
|
+
# The target Hash to merge into.
|
17
16
|
# @param from [Hash]
|
18
17
|
# The Hash to copy values from.
|
19
|
-
|
20
|
-
|
18
|
+
# @return [Hash]
|
19
|
+
def self.deep_merge(to, from)
|
20
|
+
to.merge(from) do |_key, left, right|
|
21
21
|
if left.is_a?(Hash) && right.is_a?(Hash)
|
22
|
-
Utils.deep_merge
|
22
|
+
Utils.deep_merge(left, right)
|
23
23
|
else
|
24
24
|
right
|
25
25
|
end
|
@@ -29,18 +29,32 @@ class Tater
|
|
29
29
|
# Transform keys all the way down.
|
30
30
|
#
|
31
31
|
# @param hash [Hash]
|
32
|
-
# The Hash to
|
33
|
-
#
|
34
|
-
def self.deep_stringify_keys
|
35
|
-
hash.transform_keys
|
32
|
+
# The Hash to stringify keys for.
|
33
|
+
# @return [Hash]
|
34
|
+
def self.deep_stringify_keys(hash)
|
35
|
+
hash.transform_keys(&:to_s).transform_values do |value|
|
36
36
|
if value.is_a?(Hash)
|
37
|
-
Utils.deep_stringify_keys
|
37
|
+
Utils.deep_stringify_keys(value)
|
38
38
|
else
|
39
39
|
value
|
40
40
|
end
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
+
# Freeze all the way down.
|
45
|
+
#
|
46
|
+
# @param hash [Hash]
|
47
|
+
# @return [Hash]
|
48
|
+
def self.deep_freeze(hash)
|
49
|
+
hash.transform_keys(&:freeze).transform_values do |value|
|
50
|
+
if value.is_a?(Hash)
|
51
|
+
Utils.deep_freeze(value)
|
52
|
+
else
|
53
|
+
value.freeze
|
54
|
+
end
|
55
|
+
end.freeze
|
56
|
+
end
|
57
|
+
|
44
58
|
# Try to interpolate these things, if one of them is a string.
|
45
59
|
#
|
46
60
|
# @param string [String]
|
@@ -49,7 +63,7 @@ class Tater
|
|
49
63
|
# The values to interpolate into the target string.
|
50
64
|
#
|
51
65
|
# @return [String]
|
52
|
-
def self.interpolate(string, options =
|
66
|
+
def self.interpolate(string, options = HASH)
|
53
67
|
return string unless string.is_a?(String)
|
54
68
|
return string if options.empty?
|
55
69
|
|
@@ -72,8 +86,8 @@ class Tater
|
|
72
86
|
end
|
73
87
|
|
74
88
|
DEFAULT = 'default'
|
75
|
-
DEFAULT_LOCALE = 'en'
|
76
89
|
DELIMITING_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/.freeze
|
90
|
+
HASH = {}.freeze
|
77
91
|
SEPARATOR = '.'
|
78
92
|
SUBSTITUTION_REGEX = /%(|\^)[aAbBpP]/.freeze
|
79
93
|
|
@@ -83,7 +97,16 @@ class Tater
|
|
83
97
|
# @return [Hash]
|
84
98
|
attr_reader :messages
|
85
99
|
|
86
|
-
|
100
|
+
# @param cascade [Boolean]
|
101
|
+
# A boolean indicating if lookups should cascade by default.
|
102
|
+
# @param locale [String]
|
103
|
+
# The default locale.
|
104
|
+
# @param messages [Hash]
|
105
|
+
# A hash of messages ready to be loaded in.
|
106
|
+
# @param path [String]
|
107
|
+
# A path to search for YAML or Ruby files to load messages from.
|
108
|
+
def initialize(cascade: false, locale: nil, messages: nil, path: nil)
|
109
|
+
@cache = {}
|
87
110
|
@cascade = cascade
|
88
111
|
@locale = locale
|
89
112
|
@messages = {}
|
@@ -99,11 +122,11 @@ class Tater
|
|
99
122
|
@cascade
|
100
123
|
end
|
101
124
|
|
102
|
-
# An array of the available locale codes.
|
125
|
+
# An array of the available locale codes found in loaded messages.
|
103
126
|
#
|
104
127
|
# @return [Array]
|
105
128
|
def available
|
106
|
-
messages.keys
|
129
|
+
@available ||= messages.keys
|
107
130
|
end
|
108
131
|
|
109
132
|
# Is this locale available in our current set of messages?
|
@@ -114,24 +137,36 @@ class Tater
|
|
114
137
|
end
|
115
138
|
|
116
139
|
# Load messages into our internal cache, either from a path containing YAML
|
117
|
-
# files or a
|
140
|
+
# files or a Hash of messages.
|
118
141
|
#
|
119
142
|
# @param path [String]
|
120
143
|
# A path to search for YAML or Ruby files to load messages from.
|
121
144
|
# @param messages [Hash]
|
122
145
|
# A hash of messages ready to be loaded in.
|
123
146
|
def load(path: nil, messages: nil)
|
147
|
+
return if path.nil? && messages.nil?
|
148
|
+
|
124
149
|
if path
|
125
150
|
Dir.glob(File.join(path, '**', '*.{yml,yaml}')).each do |file|
|
126
|
-
Utils.deep_merge
|
151
|
+
@messages = Utils.deep_merge(@messages, YAML.load_file(file))
|
127
152
|
end
|
128
153
|
|
129
154
|
Dir.glob(File.join(path, '**', '*.rb')).each do |file|
|
130
|
-
Utils.deep_merge
|
155
|
+
@messages = Utils.deep_merge(@messages, Utils.deep_stringify_keys(eval(IO.read(file), binding, file))) # rubocop:disable Security/Eval
|
131
156
|
end
|
132
157
|
end
|
133
158
|
|
134
|
-
Utils.deep_merge
|
159
|
+
@messages = Utils.deep_merge(@messages, Utils.deep_stringify_keys(messages)) if messages
|
160
|
+
@messages = Utils.deep_freeze(@messages)
|
161
|
+
|
162
|
+
# Gotta recalculate available locales after updating.
|
163
|
+
remove_instance_variable(:@available) if instance_variable_defined?(:@available)
|
164
|
+
|
165
|
+
# Not only does this clear our cache but it establishes the basic structure
|
166
|
+
# that we rely on in other methods.
|
167
|
+
@messages.each_key do |key|
|
168
|
+
@cache[key] = { true => {}, false => {} }
|
169
|
+
end
|
135
170
|
end
|
136
171
|
|
137
172
|
# Set the current locale, if it's available.
|
@@ -144,7 +179,7 @@ class Tater
|
|
144
179
|
|
145
180
|
# Localize an Array, Date, Time, DateTime, or Numeric object.
|
146
181
|
#
|
147
|
-
# @param object [Date, Time, DateTime, Numeric]
|
182
|
+
# @param object [Array<String>, Date, Time, DateTime, Numeric]
|
148
183
|
# The object to localize.
|
149
184
|
# @param options [Hash]
|
150
185
|
# Options to configure localization.
|
@@ -167,17 +202,14 @@ class Tater
|
|
167
202
|
#
|
168
203
|
# @return [String]
|
169
204
|
# A localized version of the object passed in.
|
170
|
-
def localize(object, options =
|
171
|
-
format_key = options.delete(:format) || DEFAULT
|
172
|
-
locale_override = options.delete(:locale)
|
173
|
-
|
205
|
+
def localize(object, options = HASH)
|
174
206
|
case object
|
175
207
|
when String
|
176
208
|
object
|
177
209
|
when Numeric
|
178
|
-
delimiter = options
|
179
|
-
separator = options
|
180
|
-
precision = options
|
210
|
+
delimiter = options[:delimiter] || lookup('numeric.delimiter', locale: options[:locale])
|
211
|
+
separator = options[:separator] || lookup('numeric.separator', locale: options[:locale])
|
212
|
+
precision = options[:precision] || 2
|
181
213
|
|
182
214
|
raise(MissingLocalizationFormat, "Numeric localization delimiter ('numeric.delimiter') missing or not passed as option :delimiter") unless delimiter
|
183
215
|
raise(MissingLocalizationFormat, "Numeric localization separator ('numeric.separator') missing or not passed as option :separator") unless separator
|
@@ -194,23 +226,22 @@ class Tater
|
|
194
226
|
[integer, fraction&.ljust(precision, '0')].compact.join(separator)
|
195
227
|
end
|
196
228
|
when Date, Time, DateTime
|
197
|
-
|
198
|
-
format = lookup("#{ key }.formats.#{ format_key }", locale_override) || format_key
|
229
|
+
format = lookup("#{ object.class.to_s.downcase }.formats.#{ options[:format] || DEFAULT }", locale: options[:locale]) || options[:format] || DEFAULT
|
199
230
|
|
200
231
|
# Heavily cribbed from I18n, many thanks to the people who sorted this out
|
201
232
|
# before I worked on this library.
|
202
233
|
format = format.gsub(SUBSTITUTION_REGEX) do |match|
|
203
234
|
case match
|
204
|
-
when '%a' then lookup('date.abbreviated_days',
|
205
|
-
when '%^a' then lookup('date.abbreviated_days',
|
206
|
-
when '%A' then lookup('date.days',
|
207
|
-
when '%^A' then lookup('date.days',
|
208
|
-
when '%b' then lookup('date.abbreviated_months',
|
209
|
-
when '%^b' then lookup('date.abbreviated_months',
|
210
|
-
when '%B' then lookup('date.months',
|
211
|
-
when '%^B' then lookup('date.months',
|
212
|
-
when '%p' then lookup("time.#{ object.hour < 12 ? 'am' : 'pm' }",
|
213
|
-
when '%P' then lookup("time.#{ object.hour < 12 ? 'am' : 'pm' }",
|
235
|
+
when '%a' then lookup('date.abbreviated_days', locale: options[:locale])[object.wday]
|
236
|
+
when '%^a' then lookup('date.abbreviated_days', locale: options[:locale])[object.wday].upcase
|
237
|
+
when '%A' then lookup('date.days', locale: options[:locale])[object.wday]
|
238
|
+
when '%^A' then lookup('date.days', locale: options[:locale])[object.wday].upcase
|
239
|
+
when '%b' then lookup('date.abbreviated_months', locale: options[:locale])[object.mon - 1]
|
240
|
+
when '%^b' then lookup('date.abbreviated_months', locale: options[:locale])[object.mon - 1].upcase
|
241
|
+
when '%B' then lookup('date.months', locale: options[:locale])[object.mon - 1]
|
242
|
+
when '%^B' then lookup('date.months', locale: options[:locale])[object.mon - 1].upcase
|
243
|
+
when '%p' then lookup("time.#{ object.hour < 12 ? 'am' : 'pm' }", locale: options[:locale]).upcase if object.respond_to?(:hour) # rubocop:disable Metrics/BlockNesting
|
244
|
+
when '%P' then lookup("time.#{ object.hour < 12 ? 'am' : 'pm' }", locale: options[:locale]).downcase if object.respond_to?(:hour) # rubocop:disable Metrics/BlockNesting
|
214
245
|
end
|
215
246
|
end
|
216
247
|
|
@@ -222,14 +253,14 @@ class Tater
|
|
222
253
|
when 1
|
223
254
|
object[0]
|
224
255
|
when 2
|
225
|
-
two_words_connector = options
|
256
|
+
two_words_connector = options[:two_words_connector] || lookup('array.two_words_connector', locale: options[:locale])
|
226
257
|
|
227
258
|
raise(MissingLocalizationFormat, "Sentence localization connector ('array.two_words_connector') missing or not passed as option :two_words_connector") unless two_words_connector
|
228
259
|
|
229
260
|
"#{ object[0] }#{ two_words_connector }#{ object[1] }"
|
230
261
|
else
|
231
|
-
last_word_connector = options
|
232
|
-
words_connector = options
|
262
|
+
last_word_connector = options[:last_word_connector] || lookup('array.last_word_connector', locale: options[:locale])
|
263
|
+
words_connector = options[:words_connector] || lookup('array.words_connector', locale: options[:locale])
|
233
264
|
|
234
265
|
raise(MissingLocalizationFormat, "Sentence localization connector ('array.last_word_connector') missing or not passed as option :last_word_connector") unless last_word_connector
|
235
266
|
raise(MissingLocalizationFormat, "Sentence localization connector ('array.words_connector') missing or not passed as option :words_connector") unless words_connector
|
@@ -245,26 +276,36 @@ class Tater
|
|
245
276
|
# Lookup a key in the messages hash, using the current locale or an override.
|
246
277
|
#
|
247
278
|
# @param key [String]
|
248
|
-
# @param
|
279
|
+
# @param locale [String]
|
249
280
|
# A locale to use instead of our current one.
|
250
|
-
# @param
|
281
|
+
# @param cascade [Boolean]
|
251
282
|
# A boolean to forcibly set the cascade option for this lookup.
|
252
283
|
#
|
253
284
|
# @return
|
254
|
-
# Basically anything that can be stored in
|
255
|
-
def lookup(key,
|
256
|
-
|
285
|
+
# Basically anything that can be stored in your messages Hash.
|
286
|
+
def lookup(key, locale: nil, cascade: nil)
|
287
|
+
locale = locale.nil? ? @locale : locale
|
288
|
+
cascade = cascade.nil? ? @cascade : cascade
|
257
289
|
|
258
|
-
|
259
|
-
|
260
|
-
attempt = @messages.dig(*path)
|
290
|
+
cached(key, locale, cascade) || begin
|
291
|
+
return nil unless @messages.key?(locale.to_s)
|
261
292
|
|
262
|
-
|
293
|
+
path = key.split(SEPARATOR).prepend(locale).map(&:to_s)
|
263
294
|
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
295
|
+
message =
|
296
|
+
if cascade
|
297
|
+
while path.length >= 2
|
298
|
+
attempt = @messages.dig(*path)
|
299
|
+
|
300
|
+
break attempt unless attempt.nil?
|
301
|
+
|
302
|
+
path.delete_at(path.length - 2)
|
303
|
+
end
|
304
|
+
else
|
305
|
+
@messages.dig(*path)
|
306
|
+
end
|
307
|
+
|
308
|
+
cache(key, locale, cascade, message)
|
268
309
|
end
|
269
310
|
end
|
270
311
|
|
@@ -284,20 +325,18 @@ class Tater
|
|
284
325
|
# An array of locales to look within.
|
285
326
|
#
|
286
327
|
# @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
|
-
|
328
|
+
def includes?(key, options = HASH)
|
292
329
|
message =
|
293
|
-
if
|
294
|
-
|
295
|
-
else
|
296
|
-
locales.find do |accept|
|
297
|
-
found = lookup(key, accept, cascade_override)
|
330
|
+
if options.key?(:locales)
|
331
|
+
options[:locales].append(@locale) if @locale && !options[:locales].include?(@locale)
|
298
332
|
|
299
|
-
|
333
|
+
options[:locales].find do |accept|
|
334
|
+
found = lookup(key, locale: accept, cascade: options[:cascade])
|
335
|
+
|
336
|
+
break found unless found.nil?
|
300
337
|
end
|
338
|
+
else
|
339
|
+
lookup(key, locale: options[:locale], cascade: options[:cascade])
|
301
340
|
end
|
302
341
|
|
303
342
|
!message.nil?
|
@@ -307,7 +346,7 @@ class Tater
|
|
307
346
|
# It's effectively a combination of #lookup and #interpolate.
|
308
347
|
#
|
309
348
|
# @example
|
310
|
-
# Tater.new(messages: { 'en' => { 'hi' => 'Hello' }}).translate('hi') # => 'Hello'
|
349
|
+
# Tater.new(locale: 'en', messages: { 'en' => { 'hi' => 'Hello' }}).translate('hi') # => 'Hello'
|
311
350
|
#
|
312
351
|
# @param key [String]
|
313
352
|
# The period-separated key path to look within our messages for.
|
@@ -319,36 +358,61 @@ class Tater
|
|
319
358
|
# @option options [String] :default
|
320
359
|
# A default string to return, should lookup fail.
|
321
360
|
# @option options [String] :locale
|
322
|
-
# A specific locale to lookup within.
|
323
|
-
# :locales option.
|
361
|
+
# A specific locale to lookup within.
|
324
362
|
# @option options [Array<String>] :locales
|
325
|
-
# An array of locales to look within.
|
363
|
+
# An array of locales to look within. This will take precedence over the
|
364
|
+
# :locale option and will append the default :locale option passed during
|
365
|
+
# initialization if present.
|
326
366
|
#
|
327
367
|
# @return [String]
|
328
368
|
# The translated and interpreted string, if found, or any data at the
|
329
369
|
# defined key.
|
330
|
-
def translate(key, options =
|
331
|
-
cascade_override = options.delete(:cascade)
|
332
|
-
locale_override = options.delete(:locale)
|
333
|
-
locales = options.delete(:locales)
|
334
|
-
|
370
|
+
def translate(key, options = HASH)
|
335
371
|
message =
|
336
|
-
if
|
337
|
-
|
338
|
-
|
339
|
-
locales.find do |accept|
|
340
|
-
found = lookup(key, accept,
|
372
|
+
if options.key?(:locales)
|
373
|
+
options[:locales].append(@locale) if @locale && !options[:locales].include?(@locale)
|
374
|
+
|
375
|
+
options[:locales].find do |accept|
|
376
|
+
found = lookup(key, locale: accept, cascade: options[:cascade])
|
341
377
|
|
342
|
-
break found
|
378
|
+
break found unless found.nil?
|
343
379
|
end
|
380
|
+
else
|
381
|
+
lookup(key, locale: options[:locale], cascade: options[:cascade])
|
344
382
|
end
|
345
383
|
|
346
384
|
# Call procs that should return a string.
|
347
|
-
if message.is_a?(Proc)
|
348
|
-
message = message.call(key, options)
|
349
|
-
end
|
385
|
+
message = message.call(key, options) if message.is_a?(Proc)
|
350
386
|
|
351
|
-
Utils.interpolate(message, options) || options
|
387
|
+
Utils.interpolate(message, options) || options[:default] || "Tater lookup failed: #{ options[:locale] || options[:locales] || locale }.#{ key }"
|
352
388
|
end
|
353
389
|
alias t translate
|
390
|
+
|
391
|
+
private
|
392
|
+
|
393
|
+
# @param key [String]
|
394
|
+
# The cache key, often in the form "something.nested.like.this"
|
395
|
+
# @param locale [String]
|
396
|
+
# The locale to store the value for.
|
397
|
+
# @param cascade [Boolean]
|
398
|
+
# Was this a cascading lookup?
|
399
|
+
# @param message [String]
|
400
|
+
# The message being cached, often a String.
|
401
|
+
# @return [String]
|
402
|
+
# Whatever value is being cached, often a String.
|
403
|
+
def cache(key, locale, cascade, message)
|
404
|
+
@cache[locale][cascade][key] = message
|
405
|
+
end
|
406
|
+
|
407
|
+
# @param key [String]
|
408
|
+
# The cache key, often in the form "something.nested.like.this"
|
409
|
+
# @param locale [String]
|
410
|
+
# The locale to store the value for.
|
411
|
+
# @param cascade [Boolean]
|
412
|
+
# Was this a cascading lookup?
|
413
|
+
# @return [String, nil]
|
414
|
+
# The cached message or nil.
|
415
|
+
def cached(key, locale, cascade)
|
416
|
+
@cache.dig(locale, cascade, key)
|
417
|
+
end
|
354
418
|
end
|
data/test/tater_test.rb
CHANGED
@@ -5,24 +5,34 @@ require 'date'
|
|
5
5
|
|
6
6
|
describe Tater do
|
7
7
|
describe Tater::Utils do
|
8
|
-
describe '#deep_merge
|
9
|
-
it 'deeply merges two hashes,
|
8
|
+
describe '#deep_merge' do
|
9
|
+
it 'deeply merges two hashes, returning a new one' do
|
10
10
|
first = { 'one' => 'one', 'two' => { 'three' => 'three' } }
|
11
11
|
second = { 'two' => { 'four' => 'four' } }
|
12
12
|
|
13
|
-
Tater::Utils.deep_merge
|
13
|
+
third = Tater::Utils.deep_merge(first, second)
|
14
14
|
|
15
|
-
assert_equal({ 'one' => 'one', 'two' => { 'three' => 'three', 'four' => 'four' } },
|
15
|
+
assert_equal({ 'one' => 'one', 'two' => { 'three' => 'three', 'four' => 'four' } }, third)
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
-
describe '#deep_stringify_keys
|
20
|
-
it 'converts all keys into strings, recursively
|
19
|
+
describe '#deep_stringify_keys' do
|
20
|
+
it 'converts all keys into strings, recursively' do
|
21
21
|
start = { en: { login: { title: 'Hello!' } } }
|
22
|
+
finish = Tater::Utils.deep_stringify_keys(start)
|
22
23
|
|
23
|
-
|
24
|
+
assert_equal({ 'en' => { 'login' => { 'title' => 'Hello!' } } }, finish)
|
25
|
+
end
|
26
|
+
end
|
24
27
|
|
25
|
-
|
28
|
+
describe '#deep_freeze' do
|
29
|
+
it 'freezes the keys and values, recursively' do
|
30
|
+
start = Tater::Utils.deep_stringify_keys({ en: { login: { title: 'Hello!' } } })
|
31
|
+
finish = Tater::Utils.deep_freeze(start)
|
32
|
+
|
33
|
+
assert finish.frozen?
|
34
|
+
assert finish.keys.all?(&:frozen?)
|
35
|
+
assert finish.values.all?(&:frozen?)
|
26
36
|
end
|
27
37
|
end
|
28
38
|
|
@@ -46,14 +56,14 @@ describe Tater do
|
|
46
56
|
it 'converts numerics to decimal-ish strings' do
|
47
57
|
assert_equal '1', Tater::Utils.string_from_numeric(1)
|
48
58
|
assert_equal '1.0', Tater::Utils.string_from_numeric(1.0)
|
49
|
-
assert_equal '1.0', Tater::Utils.string_from_numeric(BigDecimal(1))
|
59
|
+
assert_equal '1.0', Tater::Utils.string_from_numeric(BigDecimal('1'))
|
50
60
|
end
|
51
61
|
end
|
52
62
|
end
|
53
63
|
|
54
64
|
describe '#available?' do
|
55
65
|
let :i18n do
|
56
|
-
Tater.new(path: File.expand_path('test/fixtures'))
|
66
|
+
Tater.new(path: File.expand_path('test/fixtures'), locale: 'en')
|
57
67
|
end
|
58
68
|
|
59
69
|
it 'tells you if the locale is available' do
|
@@ -88,6 +98,14 @@ describe Tater do
|
|
88
98
|
|
89
99
|
assert_instance_of(Hash, i18n.messages)
|
90
100
|
end
|
101
|
+
|
102
|
+
it 'freezes messages after loading' do
|
103
|
+
i18n = Tater.new(messages: { 'hey' => 'Oh hi' })
|
104
|
+
|
105
|
+
assert i18n.messages.frozen?
|
106
|
+
assert i18n.messages.keys.all?(&:frozen?)
|
107
|
+
assert i18n.messages.values.all?(&:frozen?)
|
108
|
+
end
|
91
109
|
end
|
92
110
|
|
93
111
|
describe '#available' do
|
@@ -98,11 +116,17 @@ describe Tater do
|
|
98
116
|
it 'returns an array with the available locales (i.e. the top-level keys in our messages hash)' do
|
99
117
|
assert_equal %w[en delimiter_only separator_only fr].sort, i18n.available.sort
|
100
118
|
end
|
119
|
+
|
120
|
+
it 'updates the available list when new messages are loaded' do
|
121
|
+
i18n.load(messages: { 'added' => { 'hey' => 'yeah' }})
|
122
|
+
|
123
|
+
assert_equal %w[en delimiter_only separator_only fr added].sort, i18n.available.sort
|
124
|
+
end
|
101
125
|
end
|
102
126
|
|
103
127
|
describe '#lookup' do
|
104
128
|
let :i18n do
|
105
|
-
Tater.new(path: File.expand_path('test/fixtures'))
|
129
|
+
Tater.new(path: File.expand_path('test/fixtures'), locale: 'en')
|
106
130
|
end
|
107
131
|
|
108
132
|
it 'returns keys from messages' do
|
@@ -118,16 +142,16 @@ describe Tater do
|
|
118
142
|
end
|
119
143
|
|
120
144
|
it 'cascades' do
|
121
|
-
assert_equal 'Delicious', i18n.lookup('cascade.nope.tacos',
|
122
|
-
assert_equal 'Whoaa', i18n.lookup('cascade.another.nope.crazy',
|
123
|
-
assert_nil i18n.lookup('cascade.another.nope.crazy',
|
145
|
+
assert_equal 'Delicious', i18n.lookup('cascade.nope.tacos', cascade: true)
|
146
|
+
assert_equal 'Whoaa', i18n.lookup('cascade.another.nope.crazy', cascade: true)
|
147
|
+
assert_nil i18n.lookup('cascade.another.nope.crazy', cascade: false)
|
124
148
|
assert_nil i18n.lookup('cascade.nahhhhhh')
|
125
149
|
end
|
126
150
|
end
|
127
151
|
|
128
152
|
describe '#translate' do
|
129
153
|
let :i18n do
|
130
|
-
Tater.new(path: File.expand_path('test/fixtures'))
|
154
|
+
Tater.new(path: File.expand_path('test/fixtures'), locale: 'en')
|
131
155
|
end
|
132
156
|
|
133
157
|
it 'translates strings' do
|
@@ -185,7 +209,7 @@ describe Tater do
|
|
185
209
|
|
186
210
|
describe '#localize' do
|
187
211
|
let :i18n do
|
188
|
-
Tater.new(path: File.expand_path('test/fixtures'))
|
212
|
+
Tater.new(path: File.expand_path('test/fixtures'), locale: 'en')
|
189
213
|
end
|
190
214
|
|
191
215
|
let :fr do
|
@@ -336,7 +360,7 @@ describe Tater do
|
|
336
360
|
|
337
361
|
describe '#locale=' do
|
338
362
|
let :i18n do
|
339
|
-
Tater.new(path: File.expand_path('test/fixtures'))
|
363
|
+
Tater.new(path: File.expand_path('test/fixtures'), locale: 'en')
|
340
364
|
end
|
341
365
|
|
342
366
|
it 'overrides the locale when available' do
|
@@ -370,7 +394,7 @@ describe Tater do
|
|
370
394
|
|
371
395
|
describe '#includes?' do
|
372
396
|
let :i18n do
|
373
|
-
Tater.new(path: File.expand_path('test/fixtures'))
|
397
|
+
Tater.new(path: File.expand_path('test/fixtures'), locale: 'en')
|
374
398
|
end
|
375
399
|
|
376
400
|
it 'tells you if you have a translation' do
|
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: 2.0.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: 2020-
|
11
|
+
date: 2020-09-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|