tater 1.3.1 → 2.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fcee8d4b1aa16306c1ca8b0ce013e2210a317a05b4c3b466a37203600fb90237
4
- data.tar.gz: a00686eebe67c2c0e8cb92c6d0c82c45664cacddc17111f6f4fcb6f539439841
3
+ metadata.gz: 7e5ec34021f3488e2ef8ef3365abc37258813e5d9a79b8dbd0a49fb8254c946a
4
+ data.tar.gz: 8613b2a54d59d0b4a64f84790e60f55316447714d6013a6751041a2aa9dada6a
5
5
  SHA512:
6
- metadata.gz: 4157223bbd415daaf23d494aea0c16a42708d92629119b8c1f3071bf424dfa29775aac52b136af669fe5b24b17c4f521271f76921f3c7d04da8c15921227ee7d
7
- data.tar.gz: 29ab82f420856629db3c559594fa0bf8a6abb8b5bc24295913528945cfb823f80eb3646b616ae749ad88d4b457229fd5b6005d50bc9b6aece52617152313c457
6
+ metadata.gz: f65062d798ace02b68084b1a9189f09c092e32f2a819bd88909d651038a205a36b60816f8c6e890614c9aaea1a17e074b89a7f3a5163969bf10c6f35a8eac56e
7
+ data.tar.gz: cf3ae7da9a2d911f90aa93dd1f2e088425becf21d2b5558c485d86002bcccf09a4bdccfed39385442a7865a27e76196aa68bf90b259ea30f5b8beeda8eb1807c
@@ -1,112 +1,111 @@
1
- # Tater
1
+ * Tater
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/tater.svg)](https://badge.fury.io/rb/tater)
4
- [![Build Status](https://secure.travis-ci.org/evanleck/tater.svg)](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 `dig` method for
11
- lookups, `strftime` for date and time localizations, and `format` for
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
- ```ruby
18
+ #+begin_src ruby
21
19
  gem 'tater'
22
- ```
20
+ #+end_src
23
21
 
24
22
  And then execute:
25
23
 
26
- ```sh
24
+ #+begin_src sh
27
25
  bundle
28
- ```
26
+ #+end_src
29
27
 
30
- Or install it yourself as:
28
+ Or install it yourself by running:
31
29
 
32
- ```sh
30
+ #+begin_src sh
33
31
  gem install tater
34
- ```
35
-
32
+ #+end_src
36
33
 
37
- ## Usage
34
+ ** Usage
38
35
 
39
- ```ruby
36
+ #+begin_src ruby
40
37
  require 'tater'
41
38
 
42
39
  messages = {
43
- 'some' => {
44
- 'key' => 'This here string!'
45
- },
46
- 'interpolated' => 'Hello %{you}!'
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
- ## Array Localization
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
- ```yaml
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
- ```ruby
74
+ #+begin_src ruby
74
75
  i18n.localize(%w[tacos enchiladas burritos]) # => "tacos, enchiladas, and burritos"
75
- ```
76
-
76
+ #+end_src
77
77
 
78
- ## Numeric Localization
78
+ ** Numeric localization
79
79
 
80
- Numeric localization (`Numeric`, `Integer`, `Float`, and `BigDecimal`) require
80
+ Numeric localization (=Numeric=, =Integer=, =Float=, and =BigDecimal=) require
81
81
  filling in a separator and delimiter. For example:
82
82
 
83
- ```yaml
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
- ```ruby
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
- ```ruby
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
- ## Date and Time Localization
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
- ```yaml
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 `days`, `abbreviated_days`, `months`,
175
- and `abbreviated_months`. Only `am` and `pm` are needed for times and only if
176
- you plan on using the `%p` or `%P` format strings.
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
- ```ruby
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
- ## Cascading Lookups
186
+ ** Cascading lookups
189
187
 
190
- Lookups can be cascaded, i.e. pieces of the scope of the can be lopped off
188
+ Lookups can be cascaded, i.e. pieces of the scope of the can be lopped off
191
189
  incrementally.
192
190
 
193
- ```ruby
191
+ #+begin_src ruby
194
192
  messages = {
195
- 'login' => {
196
- 'title' => 'Login',
197
- 'description' => 'Normal description.'
193
+ 'en' => {
194
+ 'login' => {
195
+ 'title' => 'Login',
196
+ 'description' => 'Normal description.'
198
197
 
199
- 'special' => {
200
- 'title' => 'Special Login'
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. `'login.special.description'`
216
- 2. `'login.description'`
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
- ```ruby
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 `:default` option to `#translate`.
232
+ provide the =:default= option to =#translate=.
234
233
 
235
- ```ruby
234
+ #+begin_src ruby
236
235
  Tater.new.translate('nope', default: 'Yep!') # => 'Yep!'
237
- ```
238
-
236
+ #+end_src
239
237
 
240
- ## Procs and Messages in Ruby
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 `Hash` when evalled.
241
+ Ruby file returns a =Hash= when evalled.
244
242
 
245
- ```ruby
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
- ## Multiple Locales
253
+ ** Multiple locales
257
254
 
258
- If you like to check multiple locales and pull the first matching one out, you
259
- can pass the `:locales` option an array of top-level locale keys.
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
- ```ruby
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
- Locales will tried in order and which one matches first will be returned.
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
- ## Limitations
282
+ ** Limitations
281
283
 
282
- - It is not "pluggable", it does what it does and that's it.
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
- ## Trivia
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
- I was orininally going to call this library "Translator" but with a
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
- [numeronym]: https://en.wikipedia.org/wiki/Numeronym
302
- [rubyi18n]: https://github.com/ruby-i18n/i18n
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. Note that modification is done in-place,
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
- def self.deep_merge!(to, from)
20
- to.merge!(from) do |_key, left, right|
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!(left, right)
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 modify. Note that modification is done in-place, not on a copy
33
- # of the object.
34
- def self.deep_stringify_keys!(hash)
35
- hash.transform_keys!(&:to_s).transform_values! do |value|
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!(value)
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
- def initialize(path: nil, messages: nil, locale: DEFAULT_LOCALE, cascade: false)
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.map(&:to_s)
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 collection of messages.
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!(@messages, YAML.load_file(file))
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!(@messages, Utils.deep_stringify_keys!(eval(IO.read(file), binding, file))) # rubocop:disable Security/Eval
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!(@messages, Utils.deep_stringify_keys!(messages)) if messages
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 numberic values.
195
+ # The delimiter to use when localizing numeric values.
157
196
  # @option options [String] :separator
158
- # The separator to use when localizing numberic values.
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
- delimiter = options.delete(:delimiter) || lookup('numeric.delimiter', locale_override)
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
- key = object.class.to_s.downcase
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
- case object.length
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 locale_override [String]
227
+ # @param locale [String]
248
228
  # A locale to use instead of our current one.
249
- # @param cascade_override [Boolean]
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 YAML, including nil.
254
- def lookup(key, locale_override = nil, cascade_override = nil)
255
- path = key.split(SEPARATOR).prepend(locale_override || locale).map(&:to_s)
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
- if cascade_override.nil? ? @cascade : cascade_override
258
- while path.length >= 2
259
- attempt = @messages.dig(*path)
238
+ cached(key, locale, cascade) || begin
239
+ return nil unless @messages.key?(locale.to_s)
260
240
 
261
- break attempt if attempt
241
+ path = key.split(SEPARATOR).prepend(locale).map(&:to_s)
262
242
 
263
- path.delete_at(path.length - 2)
264
- end
265
- else
266
- @messages.dig(*path)
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 locale_override || !locales
293
- lookup(key, locale_override, cascade_override)
294
- else
295
- locales.find do |accept|
296
- found = lookup(key, accept, cascade_override)
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 if 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. This will take precedence over the
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 locale_override || !locales
336
- lookup(key, locale_override, cascade_override)
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
- break found if found
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.delete(:default) { "Tater lookup failed: #{ locale_override || locales || locale }.#{ key }" }
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!' do
9
- it 'deeply merges two hashes, modifying the first' do
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!(first, second)
13
+ third = Tater::Utils.deep_merge(first, second)
14
14
 
15
- assert_equal({ 'one' => 'one', 'two' => { 'three' => 'three', 'four' => 'four' } }, first)
15
+ assert_equal({ 'one' => 'one', 'two' => { 'three' => 'three', 'four' => 'four' } }, third)
16
16
  end
17
17
  end
18
18
 
19
- describe '#deep_stringify_keys!' do
20
- it 'converts all keys into strings, recursively modifying the hash passed in' do
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
- Tater::Utils.deep_stringify_keys!(start)
24
+ assert_equal({ 'en' => { 'login' => { 'title' => 'Hello!' } } }, finish)
25
+ end
26
+ end
24
27
 
25
- assert_equal({ 'en' => { 'login' => { 'title' => 'Hello!' } } }, start)
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', nil, true)
118
- assert_equal 'Whoaa', i18n.lookup('cascade.another.nope.crazy', nil, true)
119
- assert_nil i18n.lookup('cascade.another.nope.crazy', nil, false)
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: 1.3.1
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: 2019-08-18 00:00:00.000000000 Z
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.md
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.0.4
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