tater 1.3.0 → 2.0.2
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 +160 -92
- data/test/fixtures/ruby.rb +4 -3
- data/test/tater_test.rb +72 -20
- metadata +76 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eb7166fcdf9b548f8837e4411bfe5d83712f202e1de27c299fa0e5694b50bc2b
|
4
|
+
data.tar.gz: ab3f53e56fa8538f39ce59ba9f2e42496e5bea116a880fc26e23ad2091e5e93c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ceb821ff34a086711feb0df9e21a7aa4c7891a3e84fc52a9cf62d5972462c6ab0a5f81eef2ab820ef95df7211dda82e2a56299a1ced017e468c50358b6e537db
|
7
|
+
data.tar.gz: 2ad3f4c078bb63e39b5650254f92cb3859c39c73f4eaf2dfae18320e60712bf2c632e42c38770c39a456ee62f13fbf6262e1fef527846c17c53d7ebd859fa6de
|
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
@@ -6,20 +6,21 @@ require 'yaml'
|
|
6
6
|
# designed for speed and simplicity.
|
7
7
|
class Tater
|
8
8
|
class MissingLocalizationFormat < ArgumentError; end
|
9
|
+
|
9
10
|
class UnLocalizableObject < ArgumentError; end
|
10
11
|
|
11
12
|
module Utils # :nodoc:
|
12
13
|
# Merge all the way down.
|
13
14
|
#
|
14
15
|
# @param to [Hash]
|
15
|
-
# The target Hash to merge into.
|
16
|
-
# not on a copy of the object.
|
16
|
+
# The target Hash to merge into.
|
17
17
|
# @param from [Hash]
|
18
18
|
# The Hash to copy values from.
|
19
|
-
|
20
|
-
|
19
|
+
# @return [Hash]
|
20
|
+
def self.deep_merge(to, from)
|
21
|
+
to.merge(from) do |_key, left, right|
|
21
22
|
if left.is_a?(Hash) && right.is_a?(Hash)
|
22
|
-
Utils.deep_merge
|
23
|
+
Utils.deep_merge(left, right)
|
23
24
|
else
|
24
25
|
right
|
25
26
|
end
|
@@ -29,18 +30,32 @@ class Tater
|
|
29
30
|
# Transform keys all the way down.
|
30
31
|
#
|
31
32
|
# @param hash [Hash]
|
32
|
-
# The Hash to
|
33
|
-
#
|
34
|
-
def self.deep_stringify_keys
|
35
|
-
hash.transform_keys
|
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|
|
36
37
|
if value.is_a?(Hash)
|
37
|
-
Utils.deep_stringify_keys
|
38
|
+
Utils.deep_stringify_keys(value)
|
38
39
|
else
|
39
40
|
value
|
40
41
|
end
|
41
42
|
end
|
42
43
|
end
|
43
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
|
+
|
44
59
|
# Try to interpolate these things, if one of them is a string.
|
45
60
|
#
|
46
61
|
# @param string [String]
|
@@ -49,8 +64,9 @@ class Tater
|
|
49
64
|
# The values to interpolate into the target string.
|
50
65
|
#
|
51
66
|
# @return [String]
|
52
|
-
def self.interpolate(string, options =
|
67
|
+
def self.interpolate(string, options = HASH)
|
53
68
|
return string unless string.is_a?(String)
|
69
|
+
return string if options.empty?
|
54
70
|
|
55
71
|
format(string, options)
|
56
72
|
end
|
@@ -71,8 +87,8 @@ class Tater
|
|
71
87
|
end
|
72
88
|
|
73
89
|
DEFAULT = 'default'
|
74
|
-
DEFAULT_LOCALE = 'en'
|
75
90
|
DELIMITING_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/.freeze
|
91
|
+
HASH = {}.freeze
|
76
92
|
SEPARATOR = '.'
|
77
93
|
SUBSTITUTION_REGEX = /%(|\^)[aAbBpP]/.freeze
|
78
94
|
|
@@ -82,7 +98,16 @@ class Tater
|
|
82
98
|
# @return [Hash]
|
83
99
|
attr_reader :messages
|
84
100
|
|
85
|
-
|
101
|
+
# @param cascade [Boolean]
|
102
|
+
# A boolean indicating if lookups should cascade by default.
|
103
|
+
# @param locale [String]
|
104
|
+
# The default locale.
|
105
|
+
# @param messages [Hash]
|
106
|
+
# A hash of messages ready to be loaded in.
|
107
|
+
# @param path [String]
|
108
|
+
# A path to search for YAML or Ruby files to load messages from.
|
109
|
+
def initialize(cascade: false, locale: nil, messages: nil, path: nil)
|
110
|
+
@cache = {}
|
86
111
|
@cascade = cascade
|
87
112
|
@locale = locale
|
88
113
|
@messages = {}
|
@@ -98,11 +123,11 @@ class Tater
|
|
98
123
|
@cascade
|
99
124
|
end
|
100
125
|
|
101
|
-
# An array of the available locale codes.
|
126
|
+
# An array of the available locale codes found in loaded messages.
|
102
127
|
#
|
103
128
|
# @return [Array]
|
104
129
|
def available
|
105
|
-
messages.keys
|
130
|
+
@available ||= messages.keys
|
106
131
|
end
|
107
132
|
|
108
133
|
# Is this locale available in our current set of messages?
|
@@ -113,24 +138,36 @@ class Tater
|
|
113
138
|
end
|
114
139
|
|
115
140
|
# Load messages into our internal cache, either from a path containing YAML
|
116
|
-
# files or a
|
141
|
+
# files or a Hash of messages.
|
117
142
|
#
|
118
143
|
# @param path [String]
|
119
144
|
# A path to search for YAML or Ruby files to load messages from.
|
120
145
|
# @param messages [Hash]
|
121
146
|
# A hash of messages ready to be loaded in.
|
122
147
|
def load(path: nil, messages: nil)
|
148
|
+
return if path.nil? && messages.nil?
|
149
|
+
|
123
150
|
if path
|
124
151
|
Dir.glob(File.join(path, '**', '*.{yml,yaml}')).each do |file|
|
125
|
-
Utils.deep_merge
|
152
|
+
@messages = Utils.deep_merge(@messages, YAML.load_file(file))
|
126
153
|
end
|
127
154
|
|
128
155
|
Dir.glob(File.join(path, '**', '*.rb')).each do |file|
|
129
|
-
Utils.deep_merge
|
156
|
+
@messages = Utils.deep_merge(@messages, Utils.deep_stringify_keys(eval(IO.read(file), binding, file))) # rubocop:disable Security/Eval
|
130
157
|
end
|
131
158
|
end
|
132
159
|
|
133
|
-
Utils.deep_merge
|
160
|
+
@messages = Utils.deep_merge(@messages, Utils.deep_stringify_keys(messages)) if messages
|
161
|
+
@messages = Utils.deep_freeze(@messages)
|
162
|
+
|
163
|
+
# Gotta recalculate available locales after updating.
|
164
|
+
remove_instance_variable(:@available) if instance_variable_defined?(:@available)
|
165
|
+
|
166
|
+
# Not only does this clear our cache but it establishes the basic structure
|
167
|
+
# that we rely on in other methods.
|
168
|
+
@messages.each_key do |key|
|
169
|
+
@cache[key] = { true => {}, false => {} }
|
170
|
+
end
|
134
171
|
end
|
135
172
|
|
136
173
|
# Set the current locale, if it's available.
|
@@ -143,7 +180,7 @@ class Tater
|
|
143
180
|
|
144
181
|
# Localize an Array, Date, Time, DateTime, or Numeric object.
|
145
182
|
#
|
146
|
-
# @param object [Date, Time, DateTime, Numeric]
|
183
|
+
# @param object [Array<String>, Date, Time, DateTime, Numeric]
|
147
184
|
# The object to localize.
|
148
185
|
# @param options [Hash]
|
149
186
|
# Options to configure localization.
|
@@ -166,50 +203,50 @@ class Tater
|
|
166
203
|
#
|
167
204
|
# @return [String]
|
168
205
|
# A localized version of the object passed in.
|
169
|
-
def localize(object, options =
|
170
|
-
format_key = options.delete(:format) || DEFAULT
|
171
|
-
locale_override = options.delete(:locale)
|
172
|
-
|
206
|
+
def localize(object, options = HASH)
|
173
207
|
case object
|
174
208
|
when String
|
175
209
|
object
|
176
210
|
when Numeric
|
177
|
-
delimiter = options
|
178
|
-
separator = options
|
179
|
-
precision = options
|
211
|
+
delimiter = options[:delimiter] || lookup('numeric.delimiter', locale: options[:locale])
|
212
|
+
separator = options[:separator] || lookup('numeric.separator', locale: options[:locale])
|
213
|
+
precision = options[:precision] || 2
|
180
214
|
|
181
215
|
raise(MissingLocalizationFormat, "Numeric localization delimiter ('numeric.delimiter') missing or not passed as option :delimiter") unless delimiter
|
182
216
|
raise(MissingLocalizationFormat, "Numeric localization separator ('numeric.separator') missing or not passed as option :separator") unless separator
|
183
217
|
|
184
|
-
#
|
185
|
-
integer
|
186
|
-
integer.
|
187
|
-
|
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
|
188
226
|
end
|
189
227
|
|
190
|
-
if precision.zero?
|
228
|
+
if precision.zero? || fraction.nil?
|
191
229
|
integer
|
192
230
|
else
|
193
|
-
|
231
|
+
"#{ integer }#{ separator }#{ fraction.ljust(precision, '0').slice(0, precision) }"
|
194
232
|
end
|
195
233
|
when Date, Time, DateTime
|
196
|
-
|
197
|
-
format = lookup("#{ key }.formats.#{ format_key }", locale_override) || format_key
|
234
|
+
format = lookup("#{ object.class.to_s.downcase }.formats.#{ options[:format] || DEFAULT }", locale: options[:locale]) || options[:format] || DEFAULT
|
198
235
|
|
199
236
|
# Heavily cribbed from I18n, many thanks to the people who sorted this out
|
200
237
|
# before I worked on this library.
|
201
238
|
format = format.gsub(SUBSTITUTION_REGEX) do |match|
|
202
239
|
case match
|
203
|
-
when '%a' then lookup('date.abbreviated_days',
|
204
|
-
when '%^a' then lookup('date.abbreviated_days',
|
205
|
-
when '%A' then lookup('date.days',
|
206
|
-
when '%^A' then lookup('date.days',
|
207
|
-
when '%b' then lookup('date.abbreviated_months',
|
208
|
-
when '%^b' then lookup('date.abbreviated_months',
|
209
|
-
when '%B' then lookup('date.months',
|
210
|
-
when '%^B' then lookup('date.months',
|
211
|
-
when '%p' then lookup("time.#{ object.hour < 12 ? 'am' : 'pm' }",
|
212
|
-
when '%P' then lookup("time.#{ object.hour < 12 ? 'am' : 'pm' }",
|
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
|
213
250
|
end
|
214
251
|
end
|
215
252
|
|
@@ -221,14 +258,14 @@ class Tater
|
|
221
258
|
when 1
|
222
259
|
object[0]
|
223
260
|
when 2
|
224
|
-
two_words_connector = options
|
261
|
+
two_words_connector = options[:two_words_connector] || lookup('array.two_words_connector', locale: options[:locale])
|
225
262
|
|
226
263
|
raise(MissingLocalizationFormat, "Sentence localization connector ('array.two_words_connector') missing or not passed as option :two_words_connector") unless two_words_connector
|
227
264
|
|
228
265
|
"#{ object[0] }#{ two_words_connector }#{ object[1] }"
|
229
266
|
else
|
230
|
-
last_word_connector = options
|
231
|
-
words_connector = options
|
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])
|
232
269
|
|
233
270
|
raise(MissingLocalizationFormat, "Sentence localization connector ('array.last_word_connector') missing or not passed as option :last_word_connector") unless last_word_connector
|
234
271
|
raise(MissingLocalizationFormat, "Sentence localization connector ('array.words_connector') missing or not passed as option :words_connector") unless words_connector
|
@@ -244,28 +281,36 @@ class Tater
|
|
244
281
|
# Lookup a key in the messages hash, using the current locale or an override.
|
245
282
|
#
|
246
283
|
# @param key [String]
|
247
|
-
# @param
|
284
|
+
# @param locale [String]
|
248
285
|
# A locale to use instead of our current one.
|
249
|
-
# @param
|
286
|
+
# @param cascade [Boolean]
|
250
287
|
# A boolean to forcibly set the cascade option for this lookup.
|
251
288
|
#
|
252
289
|
# @return
|
253
|
-
# Basically anything that can be stored in
|
254
|
-
def lookup(key,
|
255
|
-
|
290
|
+
# Basically anything that can be stored in your messages Hash.
|
291
|
+
def lookup(key, locale: nil, cascade: nil)
|
292
|
+
locale = locale.nil? ? @locale : locale
|
293
|
+
cascade = cascade.nil? ? @cascade : cascade
|
294
|
+
|
295
|
+
cached(key, locale, cascade) || begin
|
296
|
+
return nil unless @messages.key?(locale.to_s)
|
256
297
|
|
257
|
-
|
258
|
-
while path.length >= 2 do
|
259
|
-
attempt = @messages.dig(*path)
|
298
|
+
path = key.split(SEPARATOR).prepend(locale).map(&:to_s)
|
260
299
|
|
261
|
-
|
262
|
-
|
300
|
+
message =
|
301
|
+
if cascade
|
302
|
+
while path.length >= 2
|
303
|
+
attempt = @messages.dig(*path)
|
304
|
+
|
305
|
+
break attempt unless attempt.nil?
|
306
|
+
|
307
|
+
path.delete_at(path.length - 2)
|
308
|
+
end
|
263
309
|
else
|
264
|
-
|
310
|
+
@messages.dig(*path)
|
265
311
|
end
|
266
|
-
|
267
|
-
|
268
|
-
@messages.dig(*path)
|
312
|
+
|
313
|
+
cache(key, locale, cascade, message)
|
269
314
|
end
|
270
315
|
end
|
271
316
|
|
@@ -285,20 +330,18 @@ class Tater
|
|
285
330
|
# An array of locales to look within.
|
286
331
|
#
|
287
332
|
# @return [Boolean]
|
288
|
-
def includes?(key, options =
|
289
|
-
cascade_override = options.delete(:cascade)
|
290
|
-
locale_override = options.delete(:locale)
|
291
|
-
locales = options.delete(:locales)
|
292
|
-
|
333
|
+
def includes?(key, options = HASH)
|
293
334
|
message =
|
294
|
-
if
|
295
|
-
|
296
|
-
|
297
|
-
locales.find do |accept|
|
298
|
-
found = lookup(key, accept,
|
335
|
+
if options.key?(:locales)
|
336
|
+
options[:locales].append(@locale) if @locale && !options[:locales].include?(@locale)
|
337
|
+
|
338
|
+
options[:locales].find do |accept|
|
339
|
+
found = lookup(key, locale: accept, cascade: options[:cascade])
|
299
340
|
|
300
|
-
break found
|
341
|
+
break found unless found.nil?
|
301
342
|
end
|
343
|
+
else
|
344
|
+
lookup(key, locale: options[:locale], cascade: options[:cascade])
|
302
345
|
end
|
303
346
|
|
304
347
|
!message.nil?
|
@@ -308,7 +351,7 @@ class Tater
|
|
308
351
|
# It's effectively a combination of #lookup and #interpolate.
|
309
352
|
#
|
310
353
|
# @example
|
311
|
-
# Tater.new(messages: { 'en' => { 'hi' => 'Hello' }}).translate('hi') # => 'Hello'
|
354
|
+
# Tater.new(locale: 'en', messages: { 'en' => { 'hi' => 'Hello' }}).translate('hi') # => 'Hello'
|
312
355
|
#
|
313
356
|
# @param key [String]
|
314
357
|
# The period-separated key path to look within our messages for.
|
@@ -320,36 +363,61 @@ class Tater
|
|
320
363
|
# @option options [String] :default
|
321
364
|
# A default string to return, should lookup fail.
|
322
365
|
# @option options [String] :locale
|
323
|
-
# A specific locale to lookup within.
|
324
|
-
# :locales option.
|
366
|
+
# A specific locale to lookup within.
|
325
367
|
# @option options [Array<String>] :locales
|
326
|
-
# An array of locales to look within.
|
368
|
+
# An array of locales to look within. This will take precedence over the
|
369
|
+
# :locale option and will append the default :locale option passed during
|
370
|
+
# initialization if present.
|
327
371
|
#
|
328
372
|
# @return [String]
|
329
373
|
# The translated and interpreted string, if found, or any data at the
|
330
374
|
# defined key.
|
331
|
-
def translate(key, options =
|
332
|
-
cascade_override = options.delete(:cascade)
|
333
|
-
locale_override = options.delete(:locale)
|
334
|
-
locales = options.delete(:locales)
|
335
|
-
|
375
|
+
def translate(key, options = HASH)
|
336
376
|
message =
|
337
|
-
if
|
338
|
-
|
339
|
-
|
340
|
-
locales.find do |accept|
|
341
|
-
found = lookup(key, accept,
|
377
|
+
if options.key?(:locales)
|
378
|
+
options[:locales].append(@locale) if @locale && !options[:locales].include?(@locale)
|
379
|
+
|
380
|
+
options[:locales].find do |accept|
|
381
|
+
found = lookup(key, locale: accept, cascade: options[:cascade])
|
342
382
|
|
343
|
-
break found
|
383
|
+
break found unless found.nil?
|
344
384
|
end
|
385
|
+
else
|
386
|
+
lookup(key, locale: options[:locale], cascade: options[:cascade])
|
345
387
|
end
|
346
388
|
|
347
389
|
# Call procs that should return a string.
|
348
|
-
if message.is_a?(Proc)
|
349
|
-
message = message.call(key, options)
|
350
|
-
end
|
390
|
+
message = message.call(key, options) if message.is_a?(Proc)
|
351
391
|
|
352
|
-
Utils.interpolate(message, options) || options
|
392
|
+
Utils.interpolate(message, options) || options[:default] || "Tater lookup failed: #{ options[:locale] || options[:locales] || locale }.#{ key }"
|
353
393
|
end
|
354
394
|
alias t translate
|
395
|
+
|
396
|
+
private
|
397
|
+
|
398
|
+
# @param key [String]
|
399
|
+
# The cache key, often in the form "something.nested.like.this"
|
400
|
+
# @param locale [String]
|
401
|
+
# The locale to store the value for.
|
402
|
+
# @param cascade [Boolean]
|
403
|
+
# Was this a cascading lookup?
|
404
|
+
# @param message [String]
|
405
|
+
# The message being cached, often a String.
|
406
|
+
# @return [String]
|
407
|
+
# Whatever value is being cached, often a String.
|
408
|
+
def cache(key, locale, cascade, message)
|
409
|
+
@cache[locale][cascade][key] = message
|
410
|
+
end
|
411
|
+
|
412
|
+
# @param key [String]
|
413
|
+
# The cache key, often in the form "something.nested.like.this"
|
414
|
+
# @param locale [String]
|
415
|
+
# The locale to store the value for.
|
416
|
+
# @param cascade [Boolean]
|
417
|
+
# Was this a cascading lookup?
|
418
|
+
# @return [String, nil]
|
419
|
+
# The cached message or nil.
|
420
|
+
def cached(key, locale, cascade)
|
421
|
+
@cache.dig(locale, cascade, key)
|
422
|
+
end
|
355
423
|
end
|
data/test/fixtures/ruby.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
{
|
2
3
|
en: {
|
3
|
-
ruby: proc do |key,
|
4
|
+
ruby: proc do |key, _options = {}|
|
4
5
|
"Hey #{ key }!"
|
5
6
|
end,
|
6
|
-
options: proc do |_key,
|
7
|
-
|
7
|
+
options: proc do |_key, _options = {}|
|
8
|
+
'Hey %{options}!'
|
8
9
|
end
|
9
10
|
}
|
10
11
|
}
|
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
|
|
@@ -31,25 +41,29 @@ describe Tater do
|
|
31
41
|
assert_equal 'this thing', Tater::Utils.interpolate('this %{what}', what: 'thing')
|
32
42
|
end
|
33
43
|
|
34
|
-
it 'raises a KeyError when an argument is missing' do
|
44
|
+
it 'raises a KeyError when an argument is missing (but options are passed)' do
|
35
45
|
assert_raises(KeyError) do
|
36
|
-
Tater::Utils.interpolate('this %{what}')
|
46
|
+
Tater::Utils.interpolate('this %{what}', nope: 'thing')
|
37
47
|
end
|
38
48
|
end
|
49
|
+
|
50
|
+
it 'returns the string unchanged when options are empty (does not raise a KeyError)' do
|
51
|
+
assert_equal 'this %{what}', Tater::Utils.interpolate('this %{what}')
|
52
|
+
end
|
39
53
|
end
|
40
54
|
|
41
55
|
describe '#string_from_numeric' do
|
42
56
|
it 'converts numerics to decimal-ish strings' do
|
43
57
|
assert_equal '1', Tater::Utils.string_from_numeric(1)
|
44
58
|
assert_equal '1.0', Tater::Utils.string_from_numeric(1.0)
|
45
|
-
assert_equal '1.0', Tater::Utils.string_from_numeric(BigDecimal(1))
|
59
|
+
assert_equal '1.0', Tater::Utils.string_from_numeric(BigDecimal('1'))
|
46
60
|
end
|
47
61
|
end
|
48
62
|
end
|
49
63
|
|
50
64
|
describe '#available?' do
|
51
65
|
let :i18n do
|
52
|
-
Tater.new(path: File.expand_path('test/fixtures'))
|
66
|
+
Tater.new(path: File.expand_path('test/fixtures'), locale: 'en')
|
53
67
|
end
|
54
68
|
|
55
69
|
it 'tells you if the locale is available' do
|
@@ -84,6 +98,14 @@ describe Tater do
|
|
84
98
|
|
85
99
|
assert_instance_of(Hash, i18n.messages)
|
86
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
|
87
109
|
end
|
88
110
|
|
89
111
|
describe '#available' do
|
@@ -94,11 +116,17 @@ describe Tater do
|
|
94
116
|
it 'returns an array with the available locales (i.e. the top-level keys in our messages hash)' do
|
95
117
|
assert_equal %w[en delimiter_only separator_only fr].sort, i18n.available.sort
|
96
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
|
97
125
|
end
|
98
126
|
|
99
127
|
describe '#lookup' do
|
100
128
|
let :i18n do
|
101
|
-
Tater.new(path: File.expand_path('test/fixtures'))
|
129
|
+
Tater.new(path: File.expand_path('test/fixtures'), locale: 'en')
|
102
130
|
end
|
103
131
|
|
104
132
|
it 'returns keys from messages' do
|
@@ -114,16 +142,16 @@ describe Tater do
|
|
114
142
|
end
|
115
143
|
|
116
144
|
it 'cascades' do
|
117
|
-
assert_equal 'Delicious', i18n.lookup('cascade.nope.tacos',
|
118
|
-
assert_equal 'Whoaa', i18n.lookup('cascade.another.nope.crazy',
|
119
|
-
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)
|
120
148
|
assert_nil i18n.lookup('cascade.nahhhhhh')
|
121
149
|
end
|
122
150
|
end
|
123
151
|
|
124
152
|
describe '#translate' do
|
125
153
|
let :i18n do
|
126
|
-
Tater.new(path: File.expand_path('test/fixtures'))
|
154
|
+
Tater.new(path: File.expand_path('test/fixtures'), locale: 'en')
|
127
155
|
end
|
128
156
|
|
129
157
|
it 'translates strings' do
|
@@ -181,7 +209,7 @@ describe Tater do
|
|
181
209
|
|
182
210
|
describe '#localize' do
|
183
211
|
let :i18n do
|
184
|
-
Tater.new(path: File.expand_path('test/fixtures'))
|
212
|
+
Tater.new(path: File.expand_path('test/fixtures'), locale: 'en')
|
185
213
|
end
|
186
214
|
|
187
215
|
let :fr do
|
@@ -237,6 +265,30 @@ describe Tater do
|
|
237
265
|
assert_equal '1NAH12', i18n.localize(BigDecimal('1.12'))
|
238
266
|
end
|
239
267
|
|
268
|
+
describe 'precision option' do
|
269
|
+
it 'defaults to 2' do
|
270
|
+
assert_equal '10NAH00', i18n.localize(BigDecimal('10'))
|
271
|
+
assert_equal '10NAH00', i18n.localize(10.0)
|
272
|
+
end
|
273
|
+
|
274
|
+
it 'defaults to zero for integers' do
|
275
|
+
assert_equal '10', i18n.localize(10)
|
276
|
+
end
|
277
|
+
|
278
|
+
it 'removes fractional pieces when the precision is 0' do
|
279
|
+
assert_equal '10', i18n.localize(BigDecimal('10.123456'), precision: 0)
|
280
|
+
assert_equal '10', i18n.localize(10.123456, precision: 0)
|
281
|
+
|
282
|
+
assert_equal '10', i18n.localize(BigDecimal('10.12'), precision: 0)
|
283
|
+
assert_equal '10', i18n.localize(10.12, precision: 0)
|
284
|
+
end
|
285
|
+
|
286
|
+
it 'truncates long values to the desired precision' do
|
287
|
+
assert_equal '10NAH00', i18n.localize(BigDecimal('10.00234'))
|
288
|
+
assert_equal '10NAH00', i18n.localize(10.00234)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
240
292
|
it 'allows overriding the delimiter and separator' do
|
241
293
|
assert_equal '10WOO000NAH12', i18n.localize(10_000.12, delimiter: 'WOO')
|
242
294
|
assert_equal '10TURKEYS000YA12', i18n.localize(10_000.12, separator: 'YA')
|
@@ -332,7 +384,7 @@ describe Tater do
|
|
332
384
|
|
333
385
|
describe '#locale=' do
|
334
386
|
let :i18n do
|
335
|
-
Tater.new(path: File.expand_path('test/fixtures'))
|
387
|
+
Tater.new(path: File.expand_path('test/fixtures'), locale: 'en')
|
336
388
|
end
|
337
389
|
|
338
390
|
it 'overrides the locale when available' do
|
@@ -366,7 +418,7 @@ describe Tater do
|
|
366
418
|
|
367
419
|
describe '#includes?' do
|
368
420
|
let :i18n do
|
369
|
-
Tater.new(path: File.expand_path('test/fixtures'))
|
421
|
+
Tater.new(path: File.expand_path('test/fixtures'), locale: 'en')
|
370
422
|
end
|
371
423
|
|
372
424
|
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.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Evan Lecklider
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-06-21 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
|
@@ -24,6 +38,20 @@ dependencies:
|
|
24
38
|
- - ">="
|
25
39
|
- !ruby/object:Gem::Version
|
26
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: rubocop
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,6 +66,48 @@ dependencies:
|
|
38
66
|
- - ">="
|
39
67
|
- !ruby/object:Gem::Version
|
40
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-performance
|
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'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop-rake
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
41
111
|
description: Minimal internationalization and localization library.
|
42
112
|
email:
|
43
113
|
- evan@lecklider.com
|
@@ -46,7 +116,7 @@ extensions: []
|
|
46
116
|
extra_rdoc_files: []
|
47
117
|
files:
|
48
118
|
- LICENSE.txt
|
49
|
-
- README.
|
119
|
+
- README.org
|
50
120
|
- lib/tater.rb
|
51
121
|
- test/fixtures/another.yml
|
52
122
|
- test/fixtures/fixtures.yml
|
@@ -74,13 +144,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
74
144
|
- !ruby/object:Gem::Version
|
75
145
|
version: '2.0'
|
76
146
|
requirements: []
|
77
|
-
rubygems_version: 3.
|
147
|
+
rubygems_version: 3.2.15
|
78
148
|
signing_key:
|
79
149
|
specification_version: 4
|
80
150
|
summary: Minimal internationalization and localization library.
|
81
151
|
test_files:
|
152
|
+
- test/fixtures/another.yml
|
153
|
+
- test/fixtures/fixtures.yml
|
82
154
|
- test/fixtures/messages/more.yml
|
83
155
|
- test/fixtures/ruby.rb
|
84
|
-
- test/fixtures/fixtures.yml
|
85
|
-
- test/fixtures/another.yml
|
86
156
|
- test/tater_test.rb
|