tater 1.3.1 → 2.0.3
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 +242 -128
- 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: 7e5ec34021f3488e2ef8ef3365abc37258813e5d9a79b8dbd0a49fb8254c946a
|
4
|
+
data.tar.gz: 8613b2a54d59d0b4a64f84790e60f55316447714d6013a6751041a2aa9dada6a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f65062d798ace02b68084b1a9189f09c092e32f2a819bd88909d651038a205a36b60816f8c6e890614c9aaea1a17e074b89a7f3a5163969bf10c6f35a8eac56e
|
7
|
+
data.tar.gz: cf3ae7da9a2d911f90aa93dd1f2e088425becf21d2b5558c485d86002bcccf09a4bdccfed39385442a7865a27e76196aa68bf90b259ea30f5b8beeda8eb1807c
|
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,8 +66,9 @@ 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)
|
71
|
+
return string if options.empty?
|
54
72
|
|
55
73
|
format(string, options)
|
56
74
|
end
|
@@ -71,8 +89,8 @@ class Tater
|
|
71
89
|
end
|
72
90
|
|
73
91
|
DEFAULT = 'default'
|
74
|
-
DEFAULT_LOCALE = 'en'
|
75
92
|
DELIMITING_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/.freeze
|
93
|
+
HASH = {}.freeze
|
76
94
|
SEPARATOR = '.'
|
77
95
|
SUBSTITUTION_REGEX = /%(|\^)[aAbBpP]/.freeze
|
78
96
|
|
@@ -82,7 +100,16 @@ class Tater
|
|
82
100
|
# @return [Hash]
|
83
101
|
attr_reader :messages
|
84
102
|
|
85
|
-
|
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)
|
112
|
+
@cache = {}
|
86
113
|
@cascade = cascade
|
87
114
|
@locale = locale
|
88
115
|
@messages = {}
|
@@ -98,11 +125,11 @@ class Tater
|
|
98
125
|
@cascade
|
99
126
|
end
|
100
127
|
|
101
|
-
# An array of the available locale codes.
|
128
|
+
# An array of the available locale codes found in loaded messages.
|
102
129
|
#
|
103
130
|
# @return [Array]
|
104
131
|
def available
|
105
|
-
messages.keys
|
132
|
+
@available ||= messages.keys
|
106
133
|
end
|
107
134
|
|
108
135
|
# Is this locale available in our current set of messages?
|
@@ -113,24 +140,36 @@ class Tater
|
|
113
140
|
end
|
114
141
|
|
115
142
|
# Load messages into our internal cache, either from a path containing YAML
|
116
|
-
# files or a
|
143
|
+
# files or a Hash of messages.
|
117
144
|
#
|
118
145
|
# @param path [String]
|
119
146
|
# A path to search for YAML or Ruby files to load messages from.
|
120
147
|
# @param messages [Hash]
|
121
148
|
# A hash of messages ready to be loaded in.
|
122
149
|
def load(path: nil, messages: nil)
|
150
|
+
return if path.nil? && messages.nil?
|
151
|
+
|
123
152
|
if path
|
124
153
|
Dir.glob(File.join(path, '**', '*.{yml,yaml}')).each do |file|
|
125
|
-
Utils.deep_merge
|
154
|
+
@messages = Utils.deep_merge(@messages, YAML.load_file(file))
|
126
155
|
end
|
127
156
|
|
128
157
|
Dir.glob(File.join(path, '**', '*.rb')).each do |file|
|
129
|
-
Utils.deep_merge
|
158
|
+
@messages = Utils.deep_merge(@messages, Utils.deep_stringify_keys(eval(IO.read(file), binding, file))) # rubocop:disable Security/Eval
|
130
159
|
end
|
131
160
|
end
|
132
161
|
|
133
|
-
Utils.deep_merge
|
162
|
+
@messages = Utils.deep_merge(@messages, Utils.deep_stringify_keys(messages)) if messages
|
163
|
+
@messages = Utils.deep_freeze(@messages)
|
164
|
+
|
165
|
+
# Gotta recalculate available locales after updating.
|
166
|
+
remove_instance_variable(:@available) if instance_variable_defined?(:@available)
|
167
|
+
|
168
|
+
# Not only does this clear our cache but it establishes the basic structure
|
169
|
+
# that we rely on in other methods.
|
170
|
+
@messages.each_key do |key|
|
171
|
+
@cache[key] = { true => {}, false => {} }
|
172
|
+
end
|
134
173
|
end
|
135
174
|
|
136
175
|
# Set the current locale, if it's available.
|
@@ -143,7 +182,7 @@ class Tater
|
|
143
182
|
|
144
183
|
# Localize an Array, Date, Time, DateTime, or Numeric object.
|
145
184
|
#
|
146
|
-
# @param object [Date, Time, DateTime, Numeric]
|
185
|
+
# @param object [Array<String>, Date, Time, DateTime, Numeric]
|
147
186
|
# The object to localize.
|
148
187
|
# @param options [Hash]
|
149
188
|
# Options to configure localization.
|
@@ -153,9 +192,9 @@ class Tater
|
|
153
192
|
# @option options [String] :locale
|
154
193
|
# The locale to use in lieu of the current default.
|
155
194
|
# @option options [String] :delimiter
|
156
|
-
# The delimiter to use when localizing
|
195
|
+
# The delimiter to use when localizing numeric values.
|
157
196
|
# @option options [String] :separator
|
158
|
-
# The separator to use when localizing
|
197
|
+
# The separator to use when localizing numeric values.
|
159
198
|
# @option options [String] :two_words_connector
|
160
199
|
# The string used to join two array elements together e.g. " and ".
|
161
200
|
# @option options [String] :words_connector
|
@@ -166,75 +205,16 @@ class Tater
|
|
166
205
|
#
|
167
206
|
# @return [String]
|
168
207
|
# 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
|
-
|
208
|
+
def localize(object, options = HASH)
|
173
209
|
case object
|
174
210
|
when String
|
175
211
|
object
|
176
212
|
when Numeric
|
177
|
-
|
178
|
-
separator = options.delete(:separator) || lookup('numeric.separator', locale_override)
|
179
|
-
precision = options.delete(:precision) || 2
|
180
|
-
|
181
|
-
raise(MissingLocalizationFormat, "Numeric localization delimiter ('numeric.delimiter') missing or not passed as option :delimiter") unless delimiter
|
182
|
-
raise(MissingLocalizationFormat, "Numeric localization separator ('numeric.separator') missing or not passed as option :separator") unless separator
|
183
|
-
|
184
|
-
# Heavily cribbed from Rails.
|
185
|
-
integer, fraction = Utils.string_from_numeric(object).split('.')
|
186
|
-
integer.gsub!(DELIMITING_REGEX) do |number|
|
187
|
-
"#{ number }#{ delimiter }"
|
188
|
-
end
|
189
|
-
|
190
|
-
if precision.zero?
|
191
|
-
integer
|
192
|
-
else
|
193
|
-
[integer, fraction&.ljust(precision, '0')].compact.join(separator)
|
194
|
-
end
|
213
|
+
localize_numeric(object, options)
|
195
214
|
when Date, Time, DateTime
|
196
|
-
|
197
|
-
format = lookup("#{ key }.formats.#{ format_key }", locale_override) || format_key
|
198
|
-
|
199
|
-
# Heavily cribbed from I18n, many thanks to the people who sorted this out
|
200
|
-
# before I worked on this library.
|
201
|
-
format = format.gsub(SUBSTITUTION_REGEX) do |match|
|
202
|
-
case match
|
203
|
-
when '%a' then lookup('date.abbreviated_days', locale_override)[object.wday]
|
204
|
-
when '%^a' then lookup('date.abbreviated_days', locale_override)[object.wday].upcase
|
205
|
-
when '%A' then lookup('date.days', locale_override)[object.wday]
|
206
|
-
when '%^A' then lookup('date.days', locale_override)[object.wday].upcase
|
207
|
-
when '%b' then lookup('date.abbreviated_months', locale_override)[object.mon - 1]
|
208
|
-
when '%^b' then lookup('date.abbreviated_months', locale_override)[object.mon - 1].upcase
|
209
|
-
when '%B' then lookup('date.months', locale_override)[object.mon - 1]
|
210
|
-
when '%^B' then lookup('date.months', locale_override)[object.mon - 1].upcase
|
211
|
-
when '%p' then lookup("time.#{ object.hour < 12 ? 'am' : 'pm' }", locale_override).upcase if object.respond_to?(:hour) # rubocop:disable Metrics/BlockNesting
|
212
|
-
when '%P' then lookup("time.#{ object.hour < 12 ? 'am' : 'pm' }", locale_override).downcase if object.respond_to?(:hour) # rubocop:disable Metrics/BlockNesting
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
|
-
object.strftime(format)
|
215
|
+
localize_datetime(object, options)
|
217
216
|
when Array
|
218
|
-
|
219
|
-
when 0
|
220
|
-
''
|
221
|
-
when 1
|
222
|
-
object[0]
|
223
|
-
when 2
|
224
|
-
two_words_connector = options.delete(:two_words_connector) || lookup('array.two_words_connector', locale_override)
|
225
|
-
|
226
|
-
raise(MissingLocalizationFormat, "Sentence localization connector ('array.two_words_connector') missing or not passed as option :two_words_connector") unless two_words_connector
|
227
|
-
|
228
|
-
"#{ object[0] }#{ two_words_connector }#{ object[1] }"
|
229
|
-
else
|
230
|
-
last_word_connector = options.delete(:last_word_connector) || lookup('array.last_word_connector', locale_override)
|
231
|
-
words_connector = options.delete(:words_connector) || lookup('array.words_connector', locale_override)
|
232
|
-
|
233
|
-
raise(MissingLocalizationFormat, "Sentence localization connector ('array.last_word_connector') missing or not passed as option :last_word_connector") unless last_word_connector
|
234
|
-
raise(MissingLocalizationFormat, "Sentence localization connector ('array.words_connector') missing or not passed as option :words_connector") unless words_connector
|
235
|
-
|
236
|
-
"#{ object[0...-1].join(words_connector) }#{ last_word_connector }#{ object[-1] }"
|
237
|
-
end
|
217
|
+
localize_array(object, options)
|
238
218
|
else
|
239
219
|
raise(UnLocalizableObject, "The object class #{ object.class } cannot be localized by Tater.")
|
240
220
|
end
|
@@ -244,26 +224,36 @@ class Tater
|
|
244
224
|
# Lookup a key in the messages hash, using the current locale or an override.
|
245
225
|
#
|
246
226
|
# @param key [String]
|
247
|
-
# @param
|
227
|
+
# @param locale [String]
|
248
228
|
# A locale to use instead of our current one.
|
249
|
-
# @param
|
229
|
+
# @param cascade [Boolean]
|
250
230
|
# A boolean to forcibly set the cascade option for this lookup.
|
251
231
|
#
|
252
232
|
# @return
|
253
|
-
# Basically anything that can be stored in
|
254
|
-
def lookup(key,
|
255
|
-
|
233
|
+
# Basically anything that can be stored in your messages Hash.
|
234
|
+
def lookup(key, locale: nil, cascade: nil)
|
235
|
+
locale = locale.nil? ? @locale : locale
|
236
|
+
cascade = cascade.nil? ? @cascade : cascade
|
256
237
|
|
257
|
-
|
258
|
-
|
259
|
-
attempt = @messages.dig(*path)
|
238
|
+
cached(key, locale, cascade) || begin
|
239
|
+
return nil unless @messages.key?(locale.to_s)
|
260
240
|
|
261
|
-
|
241
|
+
path = key.split(SEPARATOR).prepend(locale).map(&:to_s)
|
262
242
|
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
243
|
+
message =
|
244
|
+
if cascade
|
245
|
+
while path.length >= 2
|
246
|
+
attempt = @messages.dig(*path)
|
247
|
+
|
248
|
+
break attempt unless attempt.nil?
|
249
|
+
|
250
|
+
path.delete_at(path.length - 2)
|
251
|
+
end
|
252
|
+
else
|
253
|
+
@messages.dig(*path)
|
254
|
+
end
|
255
|
+
|
256
|
+
cache(key, locale, cascade, message)
|
267
257
|
end
|
268
258
|
end
|
269
259
|
|
@@ -283,20 +273,18 @@ class Tater
|
|
283
273
|
# An array of locales to look within.
|
284
274
|
#
|
285
275
|
# @return [Boolean]
|
286
|
-
def includes?(key, options =
|
287
|
-
cascade_override = options.delete(:cascade)
|
288
|
-
locale_override = options.delete(:locale)
|
289
|
-
locales = options.delete(:locales)
|
290
|
-
|
276
|
+
def includes?(key, options = HASH)
|
291
277
|
message =
|
292
|
-
if
|
293
|
-
|
294
|
-
|
295
|
-
locales.find do |accept|
|
296
|
-
found = lookup(key, accept,
|
278
|
+
if options.key?(:locales)
|
279
|
+
options[:locales].append(@locale) if @locale && !options[:locales].include?(@locale)
|
280
|
+
|
281
|
+
options[:locales].find do |accept|
|
282
|
+
found = lookup(key, locale: accept, cascade: options[:cascade])
|
297
283
|
|
298
|
-
break found
|
284
|
+
break found unless found.nil?
|
299
285
|
end
|
286
|
+
else
|
287
|
+
lookup(key, locale: options[:locale], cascade: options[:cascade])
|
300
288
|
end
|
301
289
|
|
302
290
|
!message.nil?
|
@@ -306,7 +294,7 @@ class Tater
|
|
306
294
|
# It's effectively a combination of #lookup and #interpolate.
|
307
295
|
#
|
308
296
|
# @example
|
309
|
-
# Tater.new(messages: { 'en' => { 'hi' => 'Hello' }}).translate('hi') # => 'Hello'
|
297
|
+
# Tater.new(locale: 'en', messages: { 'en' => { 'hi' => 'Hello' }}).translate('hi') # => 'Hello'
|
310
298
|
#
|
311
299
|
# @param key [String]
|
312
300
|
# The period-separated key path to look within our messages for.
|
@@ -318,36 +306,162 @@ class Tater
|
|
318
306
|
# @option options [String] :default
|
319
307
|
# A default string to return, should lookup fail.
|
320
308
|
# @option options [String] :locale
|
321
|
-
# A specific locale to lookup within.
|
322
|
-
# :locales option.
|
309
|
+
# A specific locale to lookup within.
|
323
310
|
# @option options [Array<String>] :locales
|
324
|
-
# An array of locales to look within.
|
311
|
+
# An array of locales to look within. This will take precedence over the
|
312
|
+
# :locale option and will append the default :locale option passed during
|
313
|
+
# initialization if present.
|
325
314
|
#
|
326
315
|
# @return [String]
|
327
316
|
# The translated and interpreted string, if found, or any data at the
|
328
317
|
# defined key.
|
329
|
-
def translate(key, options =
|
330
|
-
cascade_override = options.delete(:cascade)
|
331
|
-
locale_override = options.delete(:locale)
|
332
|
-
locales = options.delete(:locales)
|
333
|
-
|
318
|
+
def translate(key, options = HASH)
|
334
319
|
message =
|
335
|
-
if
|
336
|
-
|
337
|
-
else
|
338
|
-
locales.find do |accept|
|
339
|
-
found = lookup(key, accept, cascade_override)
|
320
|
+
if options.key?(:locales)
|
321
|
+
options[:locales].append(@locale) if @locale && !options[:locales].include?(@locale)
|
340
322
|
|
341
|
-
|
323
|
+
options[:locales].find do |accept|
|
324
|
+
found = lookup(key, locale: accept, cascade: options[:cascade])
|
325
|
+
|
326
|
+
break found unless found.nil?
|
342
327
|
end
|
328
|
+
else
|
329
|
+
lookup(key, locale: options[:locale], cascade: options[:cascade])
|
343
330
|
end
|
344
331
|
|
345
332
|
# Call procs that should return a string.
|
346
|
-
if message.is_a?(Proc)
|
347
|
-
message = message.call(key, options)
|
348
|
-
end
|
333
|
+
message = message.call(key, options) if message.is_a?(Proc)
|
349
334
|
|
350
|
-
Utils.interpolate(message, options) || options
|
335
|
+
Utils.interpolate(message, options) || options[:default] || "Tater lookup failed: #{ options[:locale] || options[:locales] || locale }.#{ key }"
|
351
336
|
end
|
352
337
|
alias t translate
|
338
|
+
|
339
|
+
private
|
340
|
+
|
341
|
+
# @param key [String]
|
342
|
+
# The cache key, often in the form "something.nested.like.this"
|
343
|
+
# @param locale [String]
|
344
|
+
# The locale to store the value for.
|
345
|
+
# @param cascade [Boolean]
|
346
|
+
# Was this a cascading lookup?
|
347
|
+
# @param message [String]
|
348
|
+
# The message being cached, often a String.
|
349
|
+
# @return [String]
|
350
|
+
# Whatever value is being cached, often a String.
|
351
|
+
def cache(key, locale, cascade, message)
|
352
|
+
@cache[locale][cascade][key] = message
|
353
|
+
end
|
354
|
+
|
355
|
+
# @param key [String]
|
356
|
+
# The cache key, often in the form "something.nested.like.this"
|
357
|
+
# @param locale [String]
|
358
|
+
# The locale to store the value for.
|
359
|
+
# @param cascade [Boolean]
|
360
|
+
# Was this a cascading lookup?
|
361
|
+
# @return [String, nil]
|
362
|
+
# The cached message or nil.
|
363
|
+
def cached(key, locale, cascade)
|
364
|
+
@cache.dig(locale, cascade, key)
|
365
|
+
end
|
366
|
+
|
367
|
+
# Localize an Array object.
|
368
|
+
#
|
369
|
+
# @param object [Array<String>]
|
370
|
+
# The array to localize.
|
371
|
+
# @param options [Hash]
|
372
|
+
# Options to configure localization.
|
373
|
+
# @return [String]
|
374
|
+
# The localize array string.
|
375
|
+
def localize_array(object, options)
|
376
|
+
case object.length
|
377
|
+
when 0
|
378
|
+
''
|
379
|
+
when 1
|
380
|
+
object[0]
|
381
|
+
when 2
|
382
|
+
two_words_connector = options[:two_words_connector] || lookup('array.two_words_connector', locale: options[:locale])
|
383
|
+
|
384
|
+
raise(MissingLocalizationFormat, "Sentence localization connector ('array.two_words_connector') missing or not passed as option :two_words_connector") unless two_words_connector
|
385
|
+
|
386
|
+
"#{ object[0] }#{ two_words_connector }#{ object[1] }"
|
387
|
+
else
|
388
|
+
last_word_connector = options[:last_word_connector] || lookup('array.last_word_connector', locale: options[:locale])
|
389
|
+
words_connector = options[:words_connector] || lookup('array.words_connector', locale: options[:locale])
|
390
|
+
|
391
|
+
raise(MissingLocalizationFormat, "Sentence localization connector ('array.last_word_connector') missing or not passed as option :last_word_connector") unless last_word_connector
|
392
|
+
raise(MissingLocalizationFormat, "Sentence localization connector ('array.words_connector') missing or not passed as option :words_connector") unless words_connector
|
393
|
+
|
394
|
+
"#{ object[0...-1].join(words_connector) }#{ last_word_connector }#{ object[-1] }"
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
# Localize a Date, DateTime, or Time object.
|
399
|
+
#
|
400
|
+
# @param object [Date, DateTime, Time]
|
401
|
+
# The date-ish object to localize.
|
402
|
+
# @param options [Hash]
|
403
|
+
# Options to configure localization.
|
404
|
+
# @return [String]
|
405
|
+
# The localized date string.
|
406
|
+
def localize_datetime(object, options)
|
407
|
+
frmt = options[:format] || DEFAULT
|
408
|
+
loc = options[:locale]
|
409
|
+
format = lookup("#{ object.class.to_s.downcase }.formats.#{ frmt }", locale: loc) || frmt
|
410
|
+
|
411
|
+
# Heavily cribbed from I18n, many thanks to the people who sorted this out
|
412
|
+
# before I worked on this library.
|
413
|
+
format = format.gsub(SUBSTITUTION_REGEX) do |match|
|
414
|
+
case match
|
415
|
+
when '%a' then lookup('date.abbreviated_days', locale: loc)[object.wday]
|
416
|
+
when '%^a' then lookup('date.abbreviated_days', locale: loc)[object.wday].upcase
|
417
|
+
when '%A' then lookup('date.days', locale: loc)[object.wday]
|
418
|
+
when '%^A' then lookup('date.days', locale: loc)[object.wday].upcase
|
419
|
+
when '%b' then lookup('date.abbreviated_months', locale: loc)[object.mon - 1]
|
420
|
+
when '%^b' then lookup('date.abbreviated_months', locale: loc)[object.mon - 1].upcase
|
421
|
+
when '%B' then lookup('date.months', locale: loc)[object.mon - 1]
|
422
|
+
when '%^B' then lookup('date.months', locale: loc)[object.mon - 1].upcase
|
423
|
+
when '%p' then lookup("time.#{ object.hour < 12 ? 'am' : 'pm' }", locale: loc).upcase if object.respond_to?(:hour)
|
424
|
+
when '%P' then lookup("time.#{ object.hour < 12 ? 'am' : 'pm' }", locale: loc).downcase if object.respond_to?(:hour)
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
if format.include?('%')
|
429
|
+
object.strftime(format)
|
430
|
+
else
|
431
|
+
format
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
# Localize a Numeric object.
|
436
|
+
#
|
437
|
+
# @param object [Array<String>, Date, Time, DateTime, Numeric]
|
438
|
+
# The object to localize.
|
439
|
+
# @param options [Hash]
|
440
|
+
# Options to configure localization.
|
441
|
+
# @return [String]
|
442
|
+
# The localized numeric string.
|
443
|
+
def localize_numeric(object, options)
|
444
|
+
delimiter = options[:delimiter] || lookup('numeric.delimiter', locale: options[:locale])
|
445
|
+
separator = options[:separator] || lookup('numeric.separator', locale: options[:locale])
|
446
|
+
precision = options[:precision] || 2
|
447
|
+
|
448
|
+
raise(MissingLocalizationFormat, "Numeric localization delimiter ('numeric.delimiter') missing or not passed as option :delimiter") unless delimiter
|
449
|
+
raise(MissingLocalizationFormat, "Numeric localization separator ('numeric.separator') missing or not passed as option :separator") unless separator
|
450
|
+
|
451
|
+
# Break the number up into integer and fraction parts.
|
452
|
+
integer = Utils.string_from_numeric(object)
|
453
|
+
integer, fraction = integer.split('.') unless object.is_a?(Integer)
|
454
|
+
|
455
|
+
if object >= 1_000
|
456
|
+
integer.gsub!(DELIMITING_REGEX) do |number|
|
457
|
+
"#{ number }#{ delimiter }"
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
if precision.zero? || fraction.nil?
|
462
|
+
integer
|
463
|
+
else
|
464
|
+
"#{ integer }#{ separator }#{ fraction.ljust(precision, '0').slice(0, precision) }"
|
465
|
+
end
|
466
|
+
end
|
353
467
|
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
|
|
@@ -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.3
|
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-22 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
|