tater 1.3.2 → 2.0.4
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 → README.org} +93 -98
- data/lib/tater.rb +224 -128
- data/test/tater_test.rb +70 -20
- metadata +61 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 51644f41d80e87d20ed43b9e8c6b4797fb7aeb4258c94d5d9f9d7a50eb5ee5c6
|
4
|
+
data.tar.gz: 1fe43b99fbc2f24cdc342ec4278ff7140ee6b9579dd51d236fcc713907f1455d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d4c059fdc696d39c489e61d3e53954fe921feaba78055bd5e62c944d45daf10dfa178b76e4e836d326ba61be0dbf7bbec13b63f52b95e13f23da9ca5be4d3b8c
|
7
|
+
data.tar.gz: 6b12ffa8f2a1186f2de92c9c16bb5c2e1027f93ab5ee2c877dbad297777274b9f4f38882255490db3c555a0dafb129ed876bd784d3a872e07a4e7e9f6e0e24ec
|
data/{README.md → README.org}
RENAMED
@@ -1,112 +1,111 @@
|
|
1
|
-
|
1
|
+
* Tater
|
2
2
|
|
3
|
-
[
|
4
|
-
[](https://travis-ci.org/evanleck/tater)
|
3
|
+
[[https://badge.fury.io/rb/tater][https://badge.fury.io/rb/tater.svg]]
|
5
4
|
|
6
5
|
Tater is an internationalization (i18n) and localization (l10n) library designed
|
7
6
|
for simplicity. It doesn't do everything that other libraries do, but that's by
|
8
7
|
design.
|
9
8
|
|
10
|
-
Under the hood, Tater uses a Hash to store the messages, the
|
11
|
-
lookups,
|
9
|
+
Under the hood, Tater uses a Hash to store the messages, the =dig= method for
|
10
|
+
lookups, =strftime= for date and time localizations, and =format= for
|
12
11
|
interpolation. That's probably 90% of what Tater does.
|
13
12
|
|
14
|
-
|
15
|
-
## Installation
|
13
|
+
** Installation
|
16
14
|
|
17
15
|
Tater requires Ruby 2.5 or higher. To install Tater, add this line to your
|
18
16
|
application's Gemfile (or gems.rb):
|
19
17
|
|
20
|
-
|
18
|
+
#+begin_src ruby
|
21
19
|
gem 'tater'
|
22
|
-
|
20
|
+
#+end_src
|
23
21
|
|
24
22
|
And then execute:
|
25
23
|
|
26
|
-
|
24
|
+
#+begin_src sh
|
27
25
|
bundle
|
28
|
-
|
26
|
+
#+end_src
|
29
27
|
|
30
|
-
Or install it yourself
|
28
|
+
Or install it yourself by running:
|
31
29
|
|
32
|
-
|
30
|
+
#+begin_src sh
|
33
31
|
gem install tater
|
34
|
-
|
35
|
-
|
32
|
+
#+end_src
|
36
33
|
|
37
|
-
|
34
|
+
** Usage
|
38
35
|
|
39
|
-
|
36
|
+
#+begin_src ruby
|
40
37
|
require 'tater'
|
41
38
|
|
42
39
|
messages = {
|
43
|
-
'
|
44
|
-
'
|
45
|
-
|
46
|
-
|
40
|
+
'en' => {
|
41
|
+
'some' => {
|
42
|
+
'key' => 'This here string!'
|
43
|
+
},
|
44
|
+
'interpolated' => 'Hello %{you}!'
|
45
|
+
}
|
47
46
|
}
|
48
47
|
|
49
|
-
i18n = Tater.new
|
48
|
+
i18n = Tater.new(locale: 'en')
|
50
49
|
i18n.load(messages: messages)
|
51
50
|
|
51
|
+
# OR
|
52
|
+
i18n = Tater.new(locale: 'en', messages: messages)
|
53
|
+
|
52
54
|
# Basic lookup:
|
53
55
|
i18n.translate('some.key') # => 'This here string!'
|
54
56
|
|
55
57
|
# Interpolation:
|
56
58
|
i18n.translate('interpolated', you: 'world') # => 'Hello world!'
|
57
|
-
|
58
|
-
|
59
|
+
#+end_src
|
59
60
|
|
60
|
-
|
61
|
+
** Array localization
|
61
62
|
|
62
63
|
Given an array, Tater will do it's best to join the elements of the array into a
|
63
64
|
sentence based on how many elements there are.
|
64
65
|
|
65
|
-
|
66
|
+
#+begin_example
|
66
67
|
en:
|
67
68
|
array:
|
68
69
|
last_word_connector: ", and "
|
69
70
|
two_words_connector: " and "
|
70
71
|
words_connector: ", "
|
71
|
-
|
72
|
+
#+end_example
|
72
73
|
|
73
|
-
|
74
|
+
#+begin_src ruby
|
74
75
|
i18n.localize(%w[tacos enchiladas burritos]) # => "tacos, enchiladas, and burritos"
|
75
|
-
|
76
|
-
|
76
|
+
#+end_src
|
77
77
|
|
78
|
-
|
78
|
+
** Numeric localization
|
79
79
|
|
80
|
-
Numeric localization (
|
80
|
+
Numeric localization (=Numeric=, =Integer=, =Float=, and =BigDecimal=) require
|
81
81
|
filling in a separator and delimiter. For example:
|
82
82
|
|
83
|
-
|
83
|
+
#+begin_example
|
84
84
|
en:
|
85
85
|
numeric:
|
86
86
|
delimiter: ','
|
87
87
|
separator: '.'
|
88
|
-
|
88
|
+
#+end_example
|
89
89
|
|
90
90
|
With that, you can do things like this:
|
91
91
|
|
92
|
-
|
92
|
+
#+begin_src ruby
|
93
93
|
i18n.localize(1000.2) # => "1,000.20"
|
94
|
-
|
94
|
+
#+end_src
|
95
95
|
|
96
96
|
The separator and delimiter can also be passed in per-call:
|
97
97
|
|
98
|
-
|
98
|
+
#+begin_src ruby
|
99
99
|
i18n.localize(1000.2, delimiter: '_', separator: '+') # => "1_000+20"
|
100
|
-
|
100
|
+
#+end_src
|
101
101
|
|
102
|
+
** Date and time localization
|
102
103
|
|
103
|
-
|
104
|
-
|
105
|
-
Date and time localization (`Date`, `Time`, and `DateTime`) require filling in
|
104
|
+
Date and time localization (=Date=, =Time=, and =DateTime=) require filling in
|
106
105
|
all of the needed names and abbreviations for days and months. Here's the
|
107
106
|
example for French, which is used in the tests.
|
108
107
|
|
109
|
-
|
108
|
+
#+begin_example
|
110
109
|
fr:
|
111
110
|
time:
|
112
111
|
am: 'am'
|
@@ -169,96 +168,95 @@ fr:
|
|
169
168
|
- oct.
|
170
169
|
- nov.
|
171
170
|
- déc.
|
172
|
-
|
171
|
+
#+end_example
|
173
172
|
|
174
|
-
The statically defined keys for dates are
|
175
|
-
and
|
176
|
-
you plan on using the
|
173
|
+
The statically defined keys for dates are =days=, =abbreviated_days=, =months=,
|
174
|
+
and =abbreviated_months=. Only =am= and =pm= are needed for times and only if
|
175
|
+
you plan on using the =%p= or =%P= format strings.
|
177
176
|
|
178
177
|
With all of that, you can do something like:
|
179
178
|
|
180
|
-
|
179
|
+
#+begin_src ruby
|
181
180
|
i18n.localize(Date.new(1970, 1, 1), format: '%A') # => 'jeudi'
|
182
181
|
|
183
182
|
# Or, using a key defined in "formats":
|
184
183
|
i18n.localize(Date.new(1970, 1, 1), format: 'day') # => 'jeudi'
|
185
|
-
|
186
|
-
|
184
|
+
#+end_src
|
187
185
|
|
188
|
-
|
186
|
+
** Cascading lookups
|
189
187
|
|
190
|
-
Lookups can be cascaded, i.e.
|
188
|
+
Lookups can be cascaded, i.e. pieces of the scope of the can be lopped off
|
191
189
|
incrementally.
|
192
190
|
|
193
|
-
|
191
|
+
#+begin_src ruby
|
194
192
|
messages = {
|
195
|
-
'
|
196
|
-
'
|
197
|
-
|
193
|
+
'en' => {
|
194
|
+
'login' => {
|
195
|
+
'title' => 'Login',
|
196
|
+
'description' => 'Normal description.'
|
198
197
|
|
199
|
-
|
200
|
-
|
198
|
+
'special' => {
|
199
|
+
'title' => 'Special Login'
|
200
|
+
}
|
201
201
|
}
|
202
202
|
}
|
203
203
|
}
|
204
204
|
|
205
|
-
i18n = Tater.new(messages: messages)
|
205
|
+
i18n = Tater.new(locale: 'en', messages: messages)
|
206
206
|
i18n.translate('login.special.title') # => 'Special Login'
|
207
207
|
i18n.translate('login.special.description') # => 'Tater lookup failed'
|
208
208
|
|
209
209
|
i18n.translate('login.special.description', cascade: true) # => 'Normal description.'
|
210
|
-
|
210
|
+
#+end_src
|
211
211
|
|
212
212
|
With cascade, the final key stays the same, but pieces of the scope get lopped
|
213
213
|
off. In this case, lookups will be tried in this order:
|
214
214
|
|
215
|
-
1.
|
216
|
-
2.
|
215
|
+
1. ='login.special.description'=
|
216
|
+
2. ='login.description'=
|
217
217
|
|
218
218
|
This can be useful when you want to override some messages but don't want to
|
219
219
|
have to copy all of the other, non-overwritten messages.
|
220
220
|
|
221
221
|
Cascading can also be enabled by default when initializing an instance of Tater.
|
222
222
|
|
223
|
-
|
223
|
+
#+begin_src ruby
|
224
224
|
Tater.new(cascade: true)
|
225
|
-
|
225
|
+
#+end_src
|
226
226
|
|
227
227
|
Cascading is off by default.
|
228
228
|
|
229
|
-
|
230
|
-
## Defaults
|
229
|
+
** Defaults
|
231
230
|
|
232
231
|
If you'd like to default to another value in case of a missed lookup, you can
|
233
|
-
provide the
|
232
|
+
provide the =:default= option to =#translate=.
|
234
233
|
|
235
|
-
|
234
|
+
#+begin_src ruby
|
236
235
|
Tater.new.translate('nope', default: 'Yep!') # => 'Yep!'
|
237
|
-
|
238
|
-
|
236
|
+
#+end_src
|
239
237
|
|
240
|
-
|
238
|
+
** Procs and messages in Ruby
|
241
239
|
|
242
240
|
Ruby files can be used to store messages in addition to YAML, so long as the
|
243
|
-
Ruby file returns a
|
241
|
+
Ruby file returns a =Hash= when evalled.
|
244
242
|
|
245
|
-
|
243
|
+
#+begin_src ruby
|
246
244
|
{
|
247
|
-
en
|
245
|
+
'en' => {
|
248
246
|
ruby: proc do |key, options = {}|
|
249
247
|
"Hey #{ key }!"
|
250
248
|
end
|
251
249
|
}
|
252
250
|
}
|
253
|
-
|
254
|
-
|
251
|
+
#+end_src
|
255
252
|
|
256
|
-
|
253
|
+
** Multiple locales
|
257
254
|
|
258
|
-
If you like to check multiple locales and pull the first matching one out,
|
259
|
-
can pass the
|
255
|
+
If you would like to check multiple locales and pull the first matching one out,
|
256
|
+
you can pass the =:locales= option to initialization or the =translate= method
|
257
|
+
with an array of top-level locale keys.
|
260
258
|
|
261
|
-
|
259
|
+
#+begin_src ruby
|
262
260
|
messages = {
|
263
261
|
'en' => {
|
264
262
|
'title' => 'Login',
|
@@ -272,31 +270,28 @@ messages = {
|
|
272
270
|
i18n = Tater.new(messages: messages)
|
273
271
|
i18n.translate('title', locales: %w[fr en]) # => 'la connexion'
|
274
272
|
i18n.translate('description', locales: %w[fr en]) # => 'English description.'
|
275
|
-
```
|
276
273
|
|
277
|
-
|
274
|
+
# OR
|
275
|
+
i18n = Tater.new(messages: messages, locales: %w[fr en])
|
276
|
+
i18n.translate('title') # => 'la connexion'
|
277
|
+
i18n.translate('description') # => 'English description.'
|
278
|
+
#+end_src
|
278
279
|
|
280
|
+
Locales will be tried in order and whichever one matches first will be returned.
|
279
281
|
|
280
|
-
|
282
|
+
** Limitations
|
281
283
|
|
282
|
-
- It is not
|
284
|
+
- It is not pluggable, it does what it does and that's it.
|
283
285
|
- It doesn't handle pluralization yet, though it may in the future.
|
284
|
-
- It doesn't cache anything, that's up to you.
|
285
|
-
|
286
|
-
|
287
|
-
## Why?
|
288
|
-
|
289
|
-
Because [Ruby I18n][rubyi18n] is amazing and I wanted to try to create a minimum
|
290
|
-
viable implementation of the bits of I18n that I use 90% of the time. Tater is a
|
291
|
-
single file that handles the basics of lookup and interpolation.
|
292
286
|
|
287
|
+
** Why?
|
293
288
|
|
294
|
-
|
289
|
+
Because [[https://github.com/ruby-i18n/i18n][Ruby I18n]] is amazing and I wanted to try to create a minimum viable
|
290
|
+
implementation of the bits of I18n that I use 90% of the time. Tater is a single
|
291
|
+
file that handles the basics of lookup and interpolation.
|
295
292
|
|
296
|
-
|
297
|
-
[numeronym][numeronym] like I18n: "t8r". I looked at it for a while but I read
|
298
|
-
it as "tater" instead of "tee-eight-arr" so I figured I'd just name it Tater.
|
299
|
-
Tater the translator.
|
293
|
+
** Trivia
|
300
294
|
|
301
|
-
[
|
302
|
-
|
295
|
+
I was orininally going to call this library "Translator" but with a [[https://en.wikipedia.org/wiki/Numeronym][numeronym]]
|
296
|
+
like I18n: "t8r". I looked at it for a while but I read it as "tater" instead
|
297
|
+
of "tee-eight-arr" so I figured I'd just name it Tater. Tater the translator.
|
data/lib/tater.rb
CHANGED
@@ -1,25 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'bigdecimal'
|
3
|
+
require 'date'
|
4
|
+
require 'time'
|
3
5
|
require 'yaml'
|
4
6
|
|
5
7
|
# Tater is a internationalization (i18n) and localization (l10n) library
|
6
8
|
# designed for speed and simplicity.
|
7
9
|
class Tater
|
8
10
|
class MissingLocalizationFormat < ArgumentError; end
|
11
|
+
|
9
12
|
class UnLocalizableObject < ArgumentError; end
|
10
13
|
|
11
14
|
module Utils # :nodoc:
|
12
15
|
# Merge all the way down.
|
13
16
|
#
|
14
17
|
# @param to [Hash]
|
15
|
-
# The target Hash to merge into.
|
16
|
-
# not on a copy of the object.
|
18
|
+
# The target Hash to merge into.
|
17
19
|
# @param from [Hash]
|
18
20
|
# The Hash to copy values from.
|
19
|
-
|
20
|
-
|
21
|
+
# @return [Hash]
|
22
|
+
def self.deep_merge(to, from)
|
23
|
+
to.merge(from) do |_key, left, right|
|
21
24
|
if left.is_a?(Hash) && right.is_a?(Hash)
|
22
|
-
Utils.deep_merge
|
25
|
+
Utils.deep_merge(left, right)
|
23
26
|
else
|
24
27
|
right
|
25
28
|
end
|
@@ -29,18 +32,32 @@ class Tater
|
|
29
32
|
# Transform keys all the way down.
|
30
33
|
#
|
31
34
|
# @param hash [Hash]
|
32
|
-
# The Hash to
|
33
|
-
#
|
34
|
-
def self.deep_stringify_keys
|
35
|
-
hash.transform_keys
|
35
|
+
# The Hash to stringify keys for.
|
36
|
+
# @return [Hash]
|
37
|
+
def self.deep_stringify_keys(hash)
|
38
|
+
hash.transform_keys(&:to_s).transform_values do |value|
|
36
39
|
if value.is_a?(Hash)
|
37
|
-
Utils.deep_stringify_keys
|
40
|
+
Utils.deep_stringify_keys(value)
|
38
41
|
else
|
39
42
|
value
|
40
43
|
end
|
41
44
|
end
|
42
45
|
end
|
43
46
|
|
47
|
+
# Freeze all the way down.
|
48
|
+
#
|
49
|
+
# @param hash [Hash]
|
50
|
+
# @return [Hash]
|
51
|
+
def self.deep_freeze(hash)
|
52
|
+
hash.transform_keys(&:freeze).transform_values do |value|
|
53
|
+
if value.is_a?(Hash)
|
54
|
+
Utils.deep_freeze(value)
|
55
|
+
else
|
56
|
+
value.freeze
|
57
|
+
end
|
58
|
+
end.freeze
|
59
|
+
end
|
60
|
+
|
44
61
|
# Try to interpolate these things, if one of them is a string.
|
45
62
|
#
|
46
63
|
# @param string [String]
|
@@ -49,7 +66,7 @@ class Tater
|
|
49
66
|
# The values to interpolate into the target string.
|
50
67
|
#
|
51
68
|
# @return [String]
|
52
|
-
def self.interpolate(string, options =
|
69
|
+
def self.interpolate(string, options = HASH)
|
53
70
|
return string unless string.is_a?(String)
|
54
71
|
return string if options.empty?
|
55
72
|
|
@@ -72,8 +89,8 @@ class Tater
|
|
72
89
|
end
|
73
90
|
|
74
91
|
DEFAULT = 'default'
|
75
|
-
DEFAULT_LOCALE = 'en'
|
76
92
|
DELIMITING_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/.freeze
|
93
|
+
HASH = {}.freeze
|
77
94
|
SEPARATOR = '.'
|
78
95
|
SUBSTITUTION_REGEX = /%(|\^)[aAbBpP]/.freeze
|
79
96
|
|
@@ -83,7 +100,15 @@ class Tater
|
|
83
100
|
# @return [Hash]
|
84
101
|
attr_reader :messages
|
85
102
|
|
86
|
-
|
103
|
+
# @param cascade [Boolean]
|
104
|
+
# A boolean indicating if lookups should cascade by default.
|
105
|
+
# @param locale [String]
|
106
|
+
# The default locale.
|
107
|
+
# @param messages [Hash]
|
108
|
+
# A hash of messages ready to be loaded in.
|
109
|
+
# @param path [String]
|
110
|
+
# A path to search for YAML or Ruby files to load messages from.
|
111
|
+
def initialize(cascade: false, locale: nil, messages: nil, path: nil)
|
87
112
|
@cascade = cascade
|
88
113
|
@locale = locale
|
89
114
|
@messages = {}
|
@@ -99,11 +124,11 @@ class Tater
|
|
99
124
|
@cascade
|
100
125
|
end
|
101
126
|
|
102
|
-
# An array of the available locale codes.
|
127
|
+
# An array of the available locale codes found in loaded messages.
|
103
128
|
#
|
104
129
|
# @return [Array]
|
105
130
|
def available
|
106
|
-
messages.keys
|
131
|
+
@available ||= messages.keys
|
107
132
|
end
|
108
133
|
|
109
134
|
# Is this locale available in our current set of messages?
|
@@ -114,24 +139,38 @@ class Tater
|
|
114
139
|
end
|
115
140
|
|
116
141
|
# Load messages into our internal cache, either from a path containing YAML
|
117
|
-
# files or a
|
142
|
+
# files or a Hash of messages.
|
118
143
|
#
|
119
144
|
# @param path [String]
|
120
145
|
# A path to search for YAML or Ruby files to load messages from.
|
121
146
|
# @param messages [Hash]
|
122
147
|
# A hash of messages ready to be loaded in.
|
123
148
|
def load(path: nil, messages: nil)
|
149
|
+
return if path.nil? && messages.nil?
|
150
|
+
|
124
151
|
if path
|
125
152
|
Dir.glob(File.join(path, '**', '*.{yml,yaml}')).each do |file|
|
126
|
-
Utils.deep_merge
|
153
|
+
@messages = Utils.deep_merge(@messages, YAML.load_file(file))
|
127
154
|
end
|
128
155
|
|
129
156
|
Dir.glob(File.join(path, '**', '*.rb')).each do |file|
|
130
|
-
Utils.deep_merge
|
157
|
+
@messages = Utils.deep_merge(@messages, Utils.deep_stringify_keys(eval(IO.read(file), binding, file))) # rubocop:disable Security/Eval
|
131
158
|
end
|
132
159
|
end
|
133
160
|
|
134
|
-
Utils.deep_merge
|
161
|
+
@messages = Utils.deep_merge(@messages, Utils.deep_stringify_keys(messages)) if messages
|
162
|
+
@messages = Utils.deep_freeze(@messages)
|
163
|
+
|
164
|
+
# Gotta recalculate available locales after updating.
|
165
|
+
remove_instance_variable(:@available) if instance_variable_defined?(:@available)
|
166
|
+
|
167
|
+
# Not only does this clear our cache but it establishes the basic structure
|
168
|
+
# that we rely on in other methods.
|
169
|
+
@cache = {}
|
170
|
+
|
171
|
+
@messages.each_key do |key|
|
172
|
+
@cache[key] = { false => {}, true => {} }
|
173
|
+
end
|
135
174
|
end
|
136
175
|
|
137
176
|
# Set the current locale, if it's available.
|
@@ -144,7 +183,7 @@ class Tater
|
|
144
183
|
|
145
184
|
# Localize an Array, Date, Time, DateTime, or Numeric object.
|
146
185
|
#
|
147
|
-
# @param object [Date, Time, DateTime, Numeric]
|
186
|
+
# @param object [Array<String>, Date, Time, DateTime, Numeric]
|
148
187
|
# The object to localize.
|
149
188
|
# @param options [Hash]
|
150
189
|
# Options to configure localization.
|
@@ -154,9 +193,9 @@ class Tater
|
|
154
193
|
# @option options [String] :locale
|
155
194
|
# The locale to use in lieu of the current default.
|
156
195
|
# @option options [String] :delimiter
|
157
|
-
# The delimiter to use when localizing
|
196
|
+
# The delimiter to use when localizing numeric values.
|
158
197
|
# @option options [String] :separator
|
159
|
-
# The separator to use when localizing
|
198
|
+
# The separator to use when localizing numeric values.
|
160
199
|
# @option options [String] :two_words_connector
|
161
200
|
# The string used to join two array elements together e.g. " and ".
|
162
201
|
# @option options [String] :words_connector
|
@@ -167,75 +206,16 @@ class Tater
|
|
167
206
|
#
|
168
207
|
# @return [String]
|
169
208
|
# 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
|
-
|
209
|
+
def localize(object, options = HASH)
|
174
210
|
case object
|
175
211
|
when String
|
176
212
|
object
|
177
213
|
when Numeric
|
178
|
-
|
179
|
-
separator = options.delete(:separator) || lookup('numeric.separator', locale_override)
|
180
|
-
precision = options.delete(:precision) || 2
|
181
|
-
|
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
|
184
|
-
|
185
|
-
# Heavily cribbed from Rails.
|
186
|
-
integer, fraction = Utils.string_from_numeric(object).split('.')
|
187
|
-
integer.gsub!(DELIMITING_REGEX) do |number|
|
188
|
-
"#{ number }#{ delimiter }"
|
189
|
-
end
|
190
|
-
|
191
|
-
if precision.zero?
|
192
|
-
integer
|
193
|
-
else
|
194
|
-
[integer, fraction&.ljust(precision, '0')].compact.join(separator)
|
195
|
-
end
|
214
|
+
localize_numeric(object, options)
|
196
215
|
when Date, Time, DateTime
|
197
|
-
|
198
|
-
format = lookup("#{ key }.formats.#{ format_key }", locale_override) || format_key
|
199
|
-
|
200
|
-
# Heavily cribbed from I18n, many thanks to the people who sorted this out
|
201
|
-
# before I worked on this library.
|
202
|
-
format = format.gsub(SUBSTITUTION_REGEX) do |match|
|
203
|
-
case match
|
204
|
-
when '%a' then lookup('date.abbreviated_days', locale_override)[object.wday]
|
205
|
-
when '%^a' then lookup('date.abbreviated_days', locale_override)[object.wday].upcase
|
206
|
-
when '%A' then lookup('date.days', locale_override)[object.wday]
|
207
|
-
when '%^A' then lookup('date.days', locale_override)[object.wday].upcase
|
208
|
-
when '%b' then lookup('date.abbreviated_months', locale_override)[object.mon - 1]
|
209
|
-
when '%^b' then lookup('date.abbreviated_months', locale_override)[object.mon - 1].upcase
|
210
|
-
when '%B' then lookup('date.months', locale_override)[object.mon - 1]
|
211
|
-
when '%^B' then lookup('date.months', locale_override)[object.mon - 1].upcase
|
212
|
-
when '%p' then lookup("time.#{ object.hour < 12 ? 'am' : 'pm' }", locale_override).upcase if object.respond_to?(:hour) # rubocop:disable Metrics/BlockNesting
|
213
|
-
when '%P' then lookup("time.#{ object.hour < 12 ? 'am' : 'pm' }", locale_override).downcase if object.respond_to?(:hour) # rubocop:disable Metrics/BlockNesting
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
object.strftime(format)
|
216
|
+
localize_datetime(object, options)
|
218
217
|
when Array
|
219
|
-
|
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
|
218
|
+
localize_array(object, options)
|
239
219
|
else
|
240
220
|
raise(UnLocalizableObject, "The object class #{ object.class } cannot be localized by Tater.")
|
241
221
|
end
|
@@ -244,27 +224,45 @@ class Tater
|
|
244
224
|
|
245
225
|
# Lookup a key in the messages hash, using the current locale or an override.
|
246
226
|
#
|
227
|
+
# @example Using the default locale, look up a key's value.
|
228
|
+
# i18n = Tater.new(locale: 'en', messages: { 'en' => { 'greeting' => { 'world' => 'Hello, world!' } } })
|
229
|
+
# i18n.lookup('greeting.world') # => "Hello, world!"
|
230
|
+
#
|
247
231
|
# @param key [String]
|
248
|
-
#
|
249
|
-
#
|
250
|
-
#
|
232
|
+
# The period-separated key path to look for within our messages.
|
233
|
+
# @param locale [String]
|
234
|
+
# A locale to use instead of our current one, if any.
|
235
|
+
# @param cascade [Boolean]
|
251
236
|
# A boolean to forcibly set the cascade option for this lookup.
|
252
237
|
#
|
253
238
|
# @return
|
254
|
-
# Basically anything that can be stored in
|
255
|
-
def lookup(key,
|
256
|
-
|
239
|
+
# Basically anything that can be stored in your messages Hash.
|
240
|
+
def lookup(key, locale: nil, cascade: nil)
|
241
|
+
locale =
|
242
|
+
if locale.nil?
|
243
|
+
@locale
|
244
|
+
else
|
245
|
+
locale.to_s
|
246
|
+
end
|
247
|
+
|
248
|
+
cascade = @cascade if cascade.nil?
|
249
|
+
|
250
|
+
@cache[locale][cascade][key] ||= begin
|
251
|
+
path = key.split(SEPARATOR)
|
257
252
|
|
258
|
-
|
259
|
-
while path.length >= 2
|
260
|
-
attempt = @messages.dig(*path)
|
253
|
+
message = @messages[locale].dig(*path)
|
261
254
|
|
262
|
-
|
255
|
+
if message.nil? && cascade
|
256
|
+
message =
|
257
|
+
while path.length > 1
|
258
|
+
path.delete_at(path.length - 2)
|
259
|
+
attempt = @messages[locale].dig(*path)
|
263
260
|
|
264
|
-
|
261
|
+
break attempt unless attempt.nil?
|
262
|
+
end
|
265
263
|
end
|
266
|
-
|
267
|
-
|
264
|
+
|
265
|
+
message
|
268
266
|
end
|
269
267
|
end
|
270
268
|
|
@@ -284,20 +282,18 @@ class Tater
|
|
284
282
|
# An array of locales to look within.
|
285
283
|
#
|
286
284
|
# @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
|
-
|
285
|
+
def includes?(key, options = HASH)
|
292
286
|
message =
|
293
|
-
if
|
294
|
-
|
295
|
-
|
296
|
-
locales.find do |accept|
|
297
|
-
found = lookup(key, accept,
|
287
|
+
if options.key?(:locales)
|
288
|
+
options[:locales].append(@locale) if @locale && !options[:locales].include?(@locale)
|
289
|
+
|
290
|
+
options[:locales].find do |accept|
|
291
|
+
found = lookup(key, locale: accept, cascade: options[:cascade])
|
298
292
|
|
299
|
-
break found
|
293
|
+
break found unless found.nil?
|
300
294
|
end
|
295
|
+
else
|
296
|
+
lookup(key, locale: options[:locale], cascade: options[:cascade])
|
301
297
|
end
|
302
298
|
|
303
299
|
!message.nil?
|
@@ -307,7 +303,7 @@ class Tater
|
|
307
303
|
# It's effectively a combination of #lookup and #interpolate.
|
308
304
|
#
|
309
305
|
# @example
|
310
|
-
# Tater.new(messages: { 'en' => { 'hi' => 'Hello' }}).translate('hi') # => 'Hello'
|
306
|
+
# Tater.new(locale: 'en', messages: { 'en' => { 'hi' => 'Hello' }}).translate('hi') # => 'Hello'
|
311
307
|
#
|
312
308
|
# @param key [String]
|
313
309
|
# The period-separated key path to look within our messages for.
|
@@ -319,36 +315,136 @@ class Tater
|
|
319
315
|
# @option options [String] :default
|
320
316
|
# A default string to return, should lookup fail.
|
321
317
|
# @option options [String] :locale
|
322
|
-
# A specific locale to lookup within.
|
323
|
-
# :locales option.
|
318
|
+
# A specific locale to lookup within.
|
324
319
|
# @option options [Array<String>] :locales
|
325
|
-
# An array of locales to look within.
|
320
|
+
# An array of locales to look within. This will take precedence over the
|
321
|
+
# :locale option and will append the default :locale option passed during
|
322
|
+
# initialization if present.
|
326
323
|
#
|
327
324
|
# @return [String]
|
328
325
|
# The translated and interpreted string, if found, or any data at the
|
329
326
|
# 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
|
-
|
327
|
+
def translate(key, options = HASH)
|
335
328
|
message =
|
336
|
-
if
|
337
|
-
|
338
|
-
|
339
|
-
locales.find do |accept|
|
340
|
-
found = lookup(key, accept,
|
329
|
+
if options.key?(:locales)
|
330
|
+
options[:locales].append(@locale) if @locale && !options[:locales].include?(@locale)
|
331
|
+
|
332
|
+
options[:locales].find do |accept|
|
333
|
+
found = lookup(key, locale: accept, cascade: options[:cascade])
|
341
334
|
|
342
|
-
break found
|
335
|
+
break found unless found.nil?
|
343
336
|
end
|
337
|
+
else
|
338
|
+
lookup(key, locale: options[:locale], cascade: options[:cascade])
|
344
339
|
end
|
345
340
|
|
346
341
|
# Call procs that should return a string.
|
347
|
-
if message.is_a?(Proc)
|
348
|
-
message = message.call(key, options)
|
349
|
-
end
|
342
|
+
message = message.call(key, options) if message.is_a?(Proc)
|
350
343
|
|
351
|
-
Utils.interpolate(message, options) || options
|
344
|
+
Utils.interpolate(message, options) || options[:default] || "Tater lookup failed: #{ options[:locale] || options[:locales] || locale }.#{ key }"
|
352
345
|
end
|
353
346
|
alias t translate
|
347
|
+
|
348
|
+
private
|
349
|
+
|
350
|
+
# Localize an Array object.
|
351
|
+
#
|
352
|
+
# @param object [Array<String>]
|
353
|
+
# The array to localize.
|
354
|
+
# @param options [Hash]
|
355
|
+
# Options to configure localization.
|
356
|
+
# @return [String]
|
357
|
+
# The localize array string.
|
358
|
+
def localize_array(object, options)
|
359
|
+
case object.length
|
360
|
+
when 0
|
361
|
+
''
|
362
|
+
when 1
|
363
|
+
object[0]
|
364
|
+
when 2
|
365
|
+
two_words_connector = options[:two_words_connector] || lookup('array.two_words_connector', locale: options[:locale])
|
366
|
+
|
367
|
+
raise(MissingLocalizationFormat, "Sentence localization connector ('array.two_words_connector') missing or not passed as option :two_words_connector") unless two_words_connector
|
368
|
+
|
369
|
+
"#{ object[0] }#{ two_words_connector }#{ object[1] }"
|
370
|
+
else
|
371
|
+
last_word_connector = options[:last_word_connector] || lookup('array.last_word_connector', locale: options[:locale])
|
372
|
+
words_connector = options[:words_connector] || lookup('array.words_connector', locale: options[:locale])
|
373
|
+
|
374
|
+
raise(MissingLocalizationFormat, "Sentence localization connector ('array.last_word_connector') missing or not passed as option :last_word_connector") unless last_word_connector
|
375
|
+
raise(MissingLocalizationFormat, "Sentence localization connector ('array.words_connector') missing or not passed as option :words_connector") unless words_connector
|
376
|
+
|
377
|
+
"#{ object[0...-1].join(words_connector) }#{ last_word_connector }#{ object[-1] }"
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
# Localize a Date, DateTime, or Time object.
|
382
|
+
#
|
383
|
+
# @param object [Date, DateTime, Time]
|
384
|
+
# The date-ish object to localize.
|
385
|
+
# @param options [Hash]
|
386
|
+
# Options to configure localization.
|
387
|
+
# @return [String]
|
388
|
+
# The localized date string.
|
389
|
+
def localize_datetime(object, options)
|
390
|
+
frmt = options[:format] || DEFAULT
|
391
|
+
loc = options[:locale]
|
392
|
+
format = lookup("#{ object.class.to_s.downcase }.formats.#{ frmt }", locale: loc) || frmt
|
393
|
+
|
394
|
+
# Heavily cribbed from I18n, many thanks to the people who sorted this out
|
395
|
+
# before I worked on this library.
|
396
|
+
format = format.gsub(SUBSTITUTION_REGEX) do |match|
|
397
|
+
case match
|
398
|
+
when '%a' then lookup('date.abbreviated_days', locale: loc)[object.wday]
|
399
|
+
when '%^a' then lookup('date.abbreviated_days', locale: loc)[object.wday].upcase
|
400
|
+
when '%A' then lookup('date.days', locale: loc)[object.wday]
|
401
|
+
when '%^A' then lookup('date.days', locale: loc)[object.wday].upcase
|
402
|
+
when '%b' then lookup('date.abbreviated_months', locale: loc)[object.mon - 1]
|
403
|
+
when '%^b' then lookup('date.abbreviated_months', locale: loc)[object.mon - 1].upcase
|
404
|
+
when '%B' then lookup('date.months', locale: loc)[object.mon - 1]
|
405
|
+
when '%^B' then lookup('date.months', locale: loc)[object.mon - 1].upcase
|
406
|
+
when '%p' then lookup("time.#{ object.hour < 12 ? 'am' : 'pm' }", locale: loc).upcase if object.respond_to?(:hour)
|
407
|
+
when '%P' then lookup("time.#{ object.hour < 12 ? 'am' : 'pm' }", locale: loc).downcase if object.respond_to?(:hour)
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
if format.include?('%')
|
412
|
+
object.strftime(format)
|
413
|
+
else
|
414
|
+
format
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
# Localize a Numeric object.
|
419
|
+
#
|
420
|
+
# @param object [Array<String>, Date, Time, DateTime, Numeric]
|
421
|
+
# The object to localize.
|
422
|
+
# @param options [Hash]
|
423
|
+
# Options to configure localization.
|
424
|
+
# @return [String]
|
425
|
+
# The localized numeric string.
|
426
|
+
def localize_numeric(object, options)
|
427
|
+
delimiter = options[:delimiter] || lookup('numeric.delimiter', locale: options[:locale])
|
428
|
+
separator = options[:separator] || lookup('numeric.separator', locale: options[:locale])
|
429
|
+
precision = options[:precision] || 2
|
430
|
+
|
431
|
+
raise(MissingLocalizationFormat, "Numeric localization delimiter ('numeric.delimiter') missing or not passed as option :delimiter") unless delimiter
|
432
|
+
raise(MissingLocalizationFormat, "Numeric localization separator ('numeric.separator') missing or not passed as option :separator") unless separator
|
433
|
+
|
434
|
+
# Break the number up into integer and fraction parts.
|
435
|
+
integer = Utils.string_from_numeric(object)
|
436
|
+
integer, fraction = integer.split('.') unless object.is_a?(Integer)
|
437
|
+
|
438
|
+
if object >= 1_000
|
439
|
+
integer.gsub!(DELIMITING_REGEX) do |number|
|
440
|
+
"#{ number }#{ delimiter }"
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
if precision.zero? || fraction.nil?
|
445
|
+
integer
|
446
|
+
else
|
447
|
+
"#{ integer }#{ separator }#{ fraction.ljust(precision, '0').slice(0, precision) }"
|
448
|
+
end
|
449
|
+
end
|
354
450
|
end
|
data/test/tater_test.rb
CHANGED
@@ -1,28 +1,40 @@
|
|
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
|
8
|
-
describe '#deep_merge
|
9
|
-
it 'deeply merges two hashes,
|
10
|
+
describe '#deep_merge' do
|
11
|
+
it 'deeply merges two hashes, returning a new one' do
|
10
12
|
first = { 'one' => 'one', 'two' => { 'three' => 'three' } }
|
11
13
|
second = { 'two' => { 'four' => 'four' } }
|
12
14
|
|
13
|
-
Tater::Utils.deep_merge
|
15
|
+
third = Tater::Utils.deep_merge(first, second)
|
14
16
|
|
15
|
-
assert_equal({ 'one' => 'one', 'two' => { 'three' => 'three', 'four' => 'four' } },
|
17
|
+
assert_equal({ 'one' => 'one', 'two' => { 'three' => 'three', 'four' => 'four' } }, third)
|
16
18
|
end
|
17
19
|
end
|
18
20
|
|
19
|
-
describe '#deep_stringify_keys
|
20
|
-
it 'converts all keys into strings, recursively
|
21
|
+
describe '#deep_stringify_keys' do
|
22
|
+
it 'converts all keys into strings, recursively' do
|
21
23
|
start = { en: { login: { title: 'Hello!' } } }
|
24
|
+
finish = Tater::Utils.deep_stringify_keys(start)
|
25
|
+
|
26
|
+
assert_equal({ 'en' => { 'login' => { 'title' => 'Hello!' } } }, finish)
|
27
|
+
end
|
28
|
+
end
|
22
29
|
|
23
|
-
|
30
|
+
describe '#deep_freeze' do
|
31
|
+
it 'freezes the keys and values, recursively' do
|
32
|
+
start = Tater::Utils.deep_stringify_keys({ en: { login: { title: 'Hello!' } } })
|
33
|
+
finish = Tater::Utils.deep_freeze(start)
|
24
34
|
|
25
|
-
|
35
|
+
assert finish.frozen?
|
36
|
+
assert finish.keys.all?(&:frozen?)
|
37
|
+
assert finish.values.all?(&:frozen?)
|
26
38
|
end
|
27
39
|
end
|
28
40
|
|
@@ -46,14 +58,14 @@ describe Tater do
|
|
46
58
|
it 'converts numerics to decimal-ish strings' do
|
47
59
|
assert_equal '1', Tater::Utils.string_from_numeric(1)
|
48
60
|
assert_equal '1.0', Tater::Utils.string_from_numeric(1.0)
|
49
|
-
assert_equal '1.0', Tater::Utils.string_from_numeric(BigDecimal(1))
|
61
|
+
assert_equal '1.0', Tater::Utils.string_from_numeric(BigDecimal('1'))
|
50
62
|
end
|
51
63
|
end
|
52
64
|
end
|
53
65
|
|
54
66
|
describe '#available?' do
|
55
67
|
let :i18n do
|
56
|
-
Tater.new(path: File.expand_path('test/fixtures'))
|
68
|
+
Tater.new(path: File.expand_path('test/fixtures'), locale: 'en')
|
57
69
|
end
|
58
70
|
|
59
71
|
it 'tells you if the locale is available' do
|
@@ -88,6 +100,14 @@ describe Tater do
|
|
88
100
|
|
89
101
|
assert_instance_of(Hash, i18n.messages)
|
90
102
|
end
|
103
|
+
|
104
|
+
it 'freezes messages after loading' do
|
105
|
+
i18n = Tater.new(messages: { 'hey' => 'Oh hi' })
|
106
|
+
|
107
|
+
assert i18n.messages.frozen?
|
108
|
+
assert i18n.messages.keys.all?(&:frozen?)
|
109
|
+
assert i18n.messages.values.all?(&:frozen?)
|
110
|
+
end
|
91
111
|
end
|
92
112
|
|
93
113
|
describe '#available' do
|
@@ -98,11 +118,17 @@ describe Tater do
|
|
98
118
|
it 'returns an array with the available locales (i.e. the top-level keys in our messages hash)' do
|
99
119
|
assert_equal %w[en delimiter_only separator_only fr].sort, i18n.available.sort
|
100
120
|
end
|
121
|
+
|
122
|
+
it 'updates the available list when new messages are loaded' do
|
123
|
+
i18n.load(messages: { 'added' => { 'hey' => 'yeah' } })
|
124
|
+
|
125
|
+
assert_equal %w[en delimiter_only separator_only fr added].sort, i18n.available.sort
|
126
|
+
end
|
101
127
|
end
|
102
128
|
|
103
129
|
describe '#lookup' do
|
104
130
|
let :i18n do
|
105
|
-
Tater.new(path: File.expand_path('test/fixtures'))
|
131
|
+
Tater.new(path: File.expand_path('test/fixtures'), locale: 'en')
|
106
132
|
end
|
107
133
|
|
108
134
|
it 'returns keys from messages' do
|
@@ -118,16 +144,16 @@ describe Tater do
|
|
118
144
|
end
|
119
145
|
|
120
146
|
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',
|
147
|
+
assert_equal 'Delicious', i18n.lookup('cascade.nope.tacos', cascade: true)
|
148
|
+
assert_equal 'Whoaa', i18n.lookup('cascade.another.nope.crazy', cascade: true)
|
149
|
+
assert_nil i18n.lookup('cascade.another.nope.crazy', cascade: false)
|
124
150
|
assert_nil i18n.lookup('cascade.nahhhhhh')
|
125
151
|
end
|
126
152
|
end
|
127
153
|
|
128
154
|
describe '#translate' do
|
129
155
|
let :i18n do
|
130
|
-
Tater.new(path: File.expand_path('test/fixtures'))
|
156
|
+
Tater.new(path: File.expand_path('test/fixtures'), locale: 'en')
|
131
157
|
end
|
132
158
|
|
133
159
|
it 'translates strings' do
|
@@ -185,7 +211,7 @@ describe Tater do
|
|
185
211
|
|
186
212
|
describe '#localize' do
|
187
213
|
let :i18n do
|
188
|
-
Tater.new(path: File.expand_path('test/fixtures'))
|
214
|
+
Tater.new(path: File.expand_path('test/fixtures'), locale: 'en')
|
189
215
|
end
|
190
216
|
|
191
217
|
let :fr do
|
@@ -241,6 +267,30 @@ describe Tater do
|
|
241
267
|
assert_equal '1NAH12', i18n.localize(BigDecimal('1.12'))
|
242
268
|
end
|
243
269
|
|
270
|
+
describe 'precision option' do
|
271
|
+
it 'defaults to 2' do
|
272
|
+
assert_equal '10NAH00', i18n.localize(BigDecimal('10'))
|
273
|
+
assert_equal '10NAH00', i18n.localize(10.0)
|
274
|
+
end
|
275
|
+
|
276
|
+
it 'defaults to zero for integers' do
|
277
|
+
assert_equal '10', i18n.localize(10)
|
278
|
+
end
|
279
|
+
|
280
|
+
it 'removes fractional pieces when the precision is 0' do
|
281
|
+
assert_equal '10', i18n.localize(BigDecimal('10.123456'), precision: 0)
|
282
|
+
assert_equal '10', i18n.localize(10.123456, precision: 0)
|
283
|
+
|
284
|
+
assert_equal '10', i18n.localize(BigDecimal('10.12'), precision: 0)
|
285
|
+
assert_equal '10', i18n.localize(10.12, precision: 0)
|
286
|
+
end
|
287
|
+
|
288
|
+
it 'truncates long values to the desired precision' do
|
289
|
+
assert_equal '10NAH00', i18n.localize(BigDecimal('10.00234'))
|
290
|
+
assert_equal '10NAH00', i18n.localize(10.00234)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
244
294
|
it 'allows overriding the delimiter and separator' do
|
245
295
|
assert_equal '10WOO000NAH12', i18n.localize(10_000.12, delimiter: 'WOO')
|
246
296
|
assert_equal '10TURKEYS000YA12', i18n.localize(10_000.12, separator: 'YA')
|
@@ -336,7 +386,7 @@ describe Tater do
|
|
336
386
|
|
337
387
|
describe '#locale=' do
|
338
388
|
let :i18n do
|
339
|
-
Tater.new(path: File.expand_path('test/fixtures'))
|
389
|
+
Tater.new(path: File.expand_path('test/fixtures'), locale: 'en')
|
340
390
|
end
|
341
391
|
|
342
392
|
it 'overrides the locale when available' do
|
@@ -370,7 +420,7 @@ describe Tater do
|
|
370
420
|
|
371
421
|
describe '#includes?' do
|
372
422
|
let :i18n do
|
373
|
-
Tater.new(path: File.expand_path('test/fixtures'))
|
423
|
+
Tater.new(path: File.expand_path('test/fixtures'), locale: 'en')
|
374
424
|
end
|
375
425
|
|
376
426
|
it 'tells you if you have a translation' do
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tater
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Evan Lecklider
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-07-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: minitest
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,6 +66,34 @@ dependencies:
|
|
52
66
|
- - ">="
|
53
67
|
- !ruby/object:Gem::Version
|
54
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop-minitest
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
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'
|
55
97
|
- !ruby/object:Gem::Dependency
|
56
98
|
name: rubocop-performance
|
57
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -66,6 +108,20 @@ dependencies:
|
|
66
108
|
- - ">="
|
67
109
|
- !ruby/object:Gem::Version
|
68
110
|
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rubocop-rake
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
69
125
|
description: Minimal internationalization and localization library.
|
70
126
|
email:
|
71
127
|
- evan@lecklider.com
|
@@ -74,7 +130,7 @@ extensions: []
|
|
74
130
|
extra_rdoc_files: []
|
75
131
|
files:
|
76
132
|
- LICENSE.txt
|
77
|
-
- README.
|
133
|
+
- README.org
|
78
134
|
- lib/tater.rb
|
79
135
|
- test/fixtures/another.yml
|
80
136
|
- test/fixtures/fixtures.yml
|
@@ -102,13 +158,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
102
158
|
- !ruby/object:Gem::Version
|
103
159
|
version: '2.0'
|
104
160
|
requirements: []
|
105
|
-
rubygems_version: 3.
|
161
|
+
rubygems_version: 3.2.22
|
106
162
|
signing_key:
|
107
163
|
specification_version: 4
|
108
164
|
summary: Minimal internationalization and localization library.
|
109
165
|
test_files:
|
110
|
-
- test/fixtures/ruby.rb
|
111
166
|
- test/fixtures/another.yml
|
112
167
|
- test/fixtures/fixtures.yml
|
113
168
|
- test/fixtures/messages/more.yml
|
169
|
+
- test/fixtures/ruby.rb
|
114
170
|
- test/tater_test.rb
|