tater 2.0.4 → 3.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.org +2 -2
- data/lib/tater/aliases.rb +10 -0
- data/lib/tater/hash.rb +19 -0
- data/lib/tater/utils.rb +93 -0
- data/lib/tater.rb +34 -93
- data/test/tater_test.rb +25 -12
- metadata +10 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7176a93dd72c2135f8114c2baed2d4fdda8ffcb7c02312517810d94f9e275dce
|
4
|
+
data.tar.gz: 5dea4c80c2a894063790d2cd4ed2232e8566b3cae4a3ab9a73a2be3c45ffd4f5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b32716e497cc9a2e81eded3bc277450d143ffec829b4accf3b3396b2c1afb28b4c66bab044695cf13bd6b64ff830d4168b181435565864e977f59ce9fcf6df1e
|
7
|
+
data.tar.gz: bd697439d8db3cd1607762bd92adb13e066fb40e16c7db6469a8e2a226774480a4cd1ba19651d45673c6ce819e9f7913af82c0b779c37c5f803e0c72ce3d42ff
|
data/README.org
CHANGED
@@ -212,8 +212,8 @@ i18n.translate('login.special.description', cascade: true) # => 'Normal descript
|
|
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.
|
data/lib/tater/hash.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class Tater
|
3
|
+
# Refine Hash and add the #except method from Ruby 3.
|
4
|
+
module HashExcept
|
5
|
+
refine Hash do
|
6
|
+
# Taken from the excellent backports gem written by Marc-André Lafortune.
|
7
|
+
# https://github.com/marcandre/backports/blob/master/lib/backports/3.0.0/hash/except.rb
|
8
|
+
def except(*keys)
|
9
|
+
if keys.size > 4 && size > 4 # index if O(m*n) is big
|
10
|
+
h = {}
|
11
|
+
keys.each { |key| h[key] = true }
|
12
|
+
keys = h
|
13
|
+
end
|
14
|
+
|
15
|
+
reject { |key, _value| keys.include?(key) }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/tater/utils.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class Tater
|
3
|
+
# Utility methods that require no state.
|
4
|
+
module Utils
|
5
|
+
HASH = {}.freeze
|
6
|
+
FORMAT_CURLY = '%{'
|
7
|
+
FORMAT_NAMED = '%<'
|
8
|
+
|
9
|
+
# Merge all the way down.
|
10
|
+
#
|
11
|
+
# @param to [Hash]
|
12
|
+
# The target Hash to merge into.
|
13
|
+
# @param from [Hash]
|
14
|
+
# The Hash to copy values from.
|
15
|
+
# @return [Hash]
|
16
|
+
def self.deep_merge(to, from)
|
17
|
+
to.merge(from) do |_key, left, right|
|
18
|
+
if left.is_a?(Hash) && right.is_a?(Hash)
|
19
|
+
Utils.deep_merge(left, right)
|
20
|
+
else
|
21
|
+
right
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Transform keys all the way down.
|
27
|
+
#
|
28
|
+
# @param hash [Hash]
|
29
|
+
# The Hash to stringify keys for.
|
30
|
+
# @return [Hash]
|
31
|
+
def self.deep_stringify_keys(hash)
|
32
|
+
hash.transform_keys(&:to_s).transform_values do |value|
|
33
|
+
if value.is_a?(Hash)
|
34
|
+
Utils.deep_stringify_keys(value)
|
35
|
+
else
|
36
|
+
value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Freeze all the way down.
|
42
|
+
#
|
43
|
+
# @param hash [Hash]
|
44
|
+
# @return [Hash]
|
45
|
+
def self.deep_freeze(hash)
|
46
|
+
hash.transform_keys(&:freeze).transform_values do |value|
|
47
|
+
if value.is_a?(Hash)
|
48
|
+
Utils.deep_freeze(value)
|
49
|
+
else
|
50
|
+
value.freeze
|
51
|
+
end
|
52
|
+
end.freeze
|
53
|
+
end
|
54
|
+
|
55
|
+
# Format values into a string if appropriate.
|
56
|
+
#
|
57
|
+
# @param string [String]
|
58
|
+
# The target string to interpolate into.
|
59
|
+
# @param options [Hash]
|
60
|
+
# The values to interpolate into the target string.
|
61
|
+
#
|
62
|
+
# @return [String]
|
63
|
+
def self.interpolate(string, options = HASH)
|
64
|
+
return string if options.empty?
|
65
|
+
return string unless interpolation_string?(string)
|
66
|
+
|
67
|
+
format(string, options)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Determine whether a string includes any interpolation placeholders e.g.
|
71
|
+
# "%{" or "%<"
|
72
|
+
#
|
73
|
+
# @param string [String]
|
74
|
+
# @return [Boolean]
|
75
|
+
def self.interpolation_string?(string)
|
76
|
+
string.include?(FORMAT_CURLY) || string.include?(FORMAT_NAMED)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Convert a Numeric to a string, particularly formatting BigDecimals to a
|
80
|
+
# Float-like string representation.
|
81
|
+
#
|
82
|
+
# @param numeric [Numeric]
|
83
|
+
#
|
84
|
+
# @return [String]
|
85
|
+
def self.string_from_numeric(numeric)
|
86
|
+
if numeric.is_a?(BigDecimal)
|
87
|
+
numeric.to_s('F')
|
88
|
+
else
|
89
|
+
numeric.to_s
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/tater.rb
CHANGED
@@ -4,96 +4,24 @@ require 'date'
|
|
4
4
|
require 'time'
|
5
5
|
require 'yaml'
|
6
6
|
|
7
|
+
require 'tater/utils'
|
8
|
+
require 'tater/hash' unless Hash.method_defined?(:except)
|
9
|
+
|
7
10
|
# Tater is a internationalization (i18n) and localization (l10n) library
|
8
11
|
# designed for speed and simplicity.
|
9
12
|
class Tater
|
10
13
|
class MissingLocalizationFormat < ArgumentError; end
|
11
|
-
|
12
14
|
class UnLocalizableObject < ArgumentError; end
|
13
15
|
|
14
|
-
module Utils # :nodoc:
|
15
|
-
# Merge all the way down.
|
16
|
-
#
|
17
|
-
# @param to [Hash]
|
18
|
-
# The target Hash to merge into.
|
19
|
-
# @param from [Hash]
|
20
|
-
# The Hash to copy values from.
|
21
|
-
# @return [Hash]
|
22
|
-
def self.deep_merge(to, from)
|
23
|
-
to.merge(from) do |_key, left, right|
|
24
|
-
if left.is_a?(Hash) && right.is_a?(Hash)
|
25
|
-
Utils.deep_merge(left, right)
|
26
|
-
else
|
27
|
-
right
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# Transform keys all the way down.
|
33
|
-
#
|
34
|
-
# @param hash [Hash]
|
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|
|
39
|
-
if value.is_a?(Hash)
|
40
|
-
Utils.deep_stringify_keys(value)
|
41
|
-
else
|
42
|
-
value
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
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
|
-
|
61
|
-
# Try to interpolate these things, if one of them is a string.
|
62
|
-
#
|
63
|
-
# @param string [String]
|
64
|
-
# The target string to interpolate into.
|
65
|
-
# @param options [Hash]
|
66
|
-
# The values to interpolate into the target string.
|
67
|
-
#
|
68
|
-
# @return [String]
|
69
|
-
def self.interpolate(string, options = HASH)
|
70
|
-
return string unless string.is_a?(String)
|
71
|
-
return string if options.empty?
|
72
|
-
|
73
|
-
format(string, options)
|
74
|
-
end
|
75
|
-
|
76
|
-
# Convert a Numeric to a string, particularly formatting BigDecimals to a
|
77
|
-
# Float-like string representation.
|
78
|
-
#
|
79
|
-
# @param numeric [Numeric]
|
80
|
-
#
|
81
|
-
# @return [String]
|
82
|
-
def self.string_from_numeric(numeric)
|
83
|
-
if numeric.is_a?(BigDecimal)
|
84
|
-
numeric.to_s('F')
|
85
|
-
else
|
86
|
-
numeric.to_s
|
87
|
-
end
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
16
|
DEFAULT = 'default'
|
92
17
|
DELIMITING_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/.freeze
|
93
18
|
HASH = {}.freeze
|
94
19
|
SEPARATOR = '.'
|
95
20
|
SUBSTITUTION_REGEX = /%(|\^)[aAbBpP]/.freeze
|
96
21
|
|
22
|
+
# Needed for Ruby < 3.
|
23
|
+
using HashExcept unless Hash.method_defined?(:except)
|
24
|
+
|
97
25
|
# @return [String]
|
98
26
|
attr_reader :locale
|
99
27
|
|
@@ -154,7 +82,7 @@ class Tater
|
|
154
82
|
end
|
155
83
|
|
156
84
|
Dir.glob(File.join(path, '**', '*.rb')).each do |file|
|
157
|
-
@messages = Utils.deep_merge(@messages, Utils.deep_stringify_keys(eval(
|
85
|
+
@messages = Utils.deep_merge(@messages, Utils.deep_stringify_keys(eval(File.read(file), binding, file))) # rubocop:disable Security/Eval
|
158
86
|
end
|
159
87
|
end
|
160
88
|
|
@@ -220,7 +148,6 @@ class Tater
|
|
220
148
|
raise(UnLocalizableObject, "The object class #{ object.class } cannot be localized by Tater.")
|
221
149
|
end
|
222
150
|
end
|
223
|
-
alias l localize
|
224
151
|
|
225
152
|
# Lookup a key in the messages hash, using the current locale or an override.
|
226
153
|
#
|
@@ -325,25 +252,39 @@ class Tater
|
|
325
252
|
# The translated and interpreted string, if found, or any data at the
|
326
253
|
# defined key.
|
327
254
|
def translate(key, options = HASH)
|
328
|
-
|
329
|
-
|
330
|
-
options[:locales].append(@locale) if @locale && !options[:locales].include?(@locale)
|
255
|
+
if options.empty?
|
256
|
+
message = lookup(key)
|
331
257
|
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
end
|
258
|
+
if message.is_a?(Proc) # rubocop:disable Style/CaseLikeIf
|
259
|
+
message.call(key)
|
260
|
+
elsif message.is_a?(String)
|
261
|
+
message
|
337
262
|
else
|
338
|
-
lookup
|
263
|
+
"Tater lookup failed: #{ locale }.#{ key }"
|
339
264
|
end
|
265
|
+
else
|
266
|
+
message =
|
267
|
+
if options.key?(:locales)
|
268
|
+
options[:locales].append(@locale) if @locale && !options[:locales].include?(@locale)
|
340
269
|
|
341
|
-
|
342
|
-
|
270
|
+
options[:locales].find do |accept|
|
271
|
+
found = lookup(key, locale: accept, cascade: options[:cascade])
|
272
|
+
|
273
|
+
break found unless found.nil?
|
274
|
+
end
|
275
|
+
else
|
276
|
+
lookup(key, locale: options[:locale], cascade: options[:cascade])
|
277
|
+
end
|
343
278
|
|
344
|
-
|
279
|
+
if message.is_a?(Proc) # rubocop:disable Style/CaseLikeIf
|
280
|
+
message.call(key, options.except(:cascade, :default, :locale, :locales))
|
281
|
+
elsif message.is_a?(String)
|
282
|
+
Utils.interpolate(message, options.except(:cascade, :default, :locale, :locales))
|
283
|
+
else
|
284
|
+
options[:default] || "Tater lookup failed: #{ options[:locale] || options[:locales] || locale }.#{ key }"
|
285
|
+
end
|
286
|
+
end
|
345
287
|
end
|
346
|
-
alias t translate
|
347
288
|
|
348
289
|
private
|
349
290
|
|
data/test/tater_test.rb
CHANGED
@@ -61,6 +61,20 @@ describe Tater do
|
|
61
61
|
assert_equal '1.0', Tater::Utils.string_from_numeric(BigDecimal('1'))
|
62
62
|
end
|
63
63
|
end
|
64
|
+
|
65
|
+
describe '#interpolation_string?' do
|
66
|
+
def is?(arg)
|
67
|
+
Tater::Utils.interpolation_string?(arg)
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'checks whether a string contains interpolation placeholders' do
|
71
|
+
assert is?('Hey %{there}!')
|
72
|
+
assert is?('Hey %<there>s!')
|
73
|
+
refute is?('Nah, this is fine')
|
74
|
+
refute is?("<b>HTML shouldn't count")
|
75
|
+
refute is?("A single % shouldn't count")
|
76
|
+
end
|
77
|
+
end
|
64
78
|
end
|
65
79
|
|
66
80
|
describe '#available?' do
|
@@ -143,6 +157,10 @@ describe Tater do
|
|
143
157
|
assert_nil i18n.lookup('nope')
|
144
158
|
end
|
145
159
|
|
160
|
+
it 'returns arbitrary data at keys' do
|
161
|
+
assert_equal({ 'key' => 'This key is deeper' }, i18n.lookup('deep'))
|
162
|
+
end
|
163
|
+
|
146
164
|
it 'cascades' do
|
147
165
|
assert_equal 'Delicious', i18n.lookup('cascade.nope.tacos', cascade: true)
|
148
166
|
assert_equal 'Whoaa', i18n.lookup('cascade.another.nope.crazy', cascade: true)
|
@@ -164,8 +182,8 @@ describe Tater do
|
|
164
182
|
assert_equal 'This key is deeper', i18n.translate('deep.key')
|
165
183
|
end
|
166
184
|
|
167
|
-
it '
|
168
|
-
assert_equal
|
185
|
+
it 'does not return a hash for nested keys' do
|
186
|
+
assert_equal 'Tater lookup failed: en.deep', i18n.translate('deep')
|
169
187
|
end
|
170
188
|
|
171
189
|
it 'interpolates additional variables' do
|
@@ -182,10 +200,6 @@ describe Tater do
|
|
182
200
|
assert_equal 'Tater lookup failed: en.nope', i18n.translate('nope')
|
183
201
|
end
|
184
202
|
|
185
|
-
it 'is aliased as t' do
|
186
|
-
assert_equal 'This is a title', i18n.t('title')
|
187
|
-
end
|
188
|
-
|
189
203
|
it 'cascades lookups' do
|
190
204
|
assert_equal 'Tater lookup failed: en.cascade.another.nope.crazy', i18n.translate('cascade.another.nope.crazy', cascade: false)
|
191
205
|
assert_equal 'Tater lookup failed: en.cascade.nope.tacos', i18n.translate('cascade.nope.tacos')
|
@@ -203,9 +217,12 @@ describe Tater do
|
|
203
217
|
assert_equal 'Tater lookup failed: ["fr", "en"].neither', i18n.translate('neither', locales: %w[fr en])
|
204
218
|
end
|
205
219
|
|
206
|
-
it 'finds Ruby files
|
220
|
+
it 'finds Ruby files' do
|
207
221
|
assert_equal 'Hey ruby!', i18n.translate('ruby')
|
208
|
-
|
222
|
+
end
|
223
|
+
|
224
|
+
it 'does not interpolate messages returned by procs' do
|
225
|
+
assert_equal 'Hey %{options}!', i18n.translate('options', options: 'options')
|
209
226
|
end
|
210
227
|
end
|
211
228
|
|
@@ -316,10 +333,6 @@ describe Tater do
|
|
316
333
|
end
|
317
334
|
end
|
318
335
|
|
319
|
-
it 'is aliased l' do
|
320
|
-
assert_equal '1970/1/1', i18n.l(Date.new(1970, 1, 1))
|
321
|
-
end
|
322
|
-
|
323
336
|
describe 'month, day, and AM/PM names' do
|
324
337
|
let :i18n do
|
325
338
|
Tater.new(path: File.expand_path('test/fixtures'), locale: 'fr')
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tater
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Evan Lecklider
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-12-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -132,6 +132,9 @@ files:
|
|
132
132
|
- LICENSE.txt
|
133
133
|
- README.org
|
134
134
|
- lib/tater.rb
|
135
|
+
- lib/tater/aliases.rb
|
136
|
+
- lib/tater/hash.rb
|
137
|
+
- lib/tater/utils.rb
|
135
138
|
- test/fixtures/another.yml
|
136
139
|
- test/fixtures/fixtures.yml
|
137
140
|
- test/fixtures/messages/more.yml
|
@@ -142,8 +145,9 @@ licenses:
|
|
142
145
|
- MIT
|
143
146
|
metadata:
|
144
147
|
bug_tracker_uri: https://github.com/evanleck/tater/issues
|
148
|
+
rubygems_mfa_required: 'true'
|
145
149
|
source_code_uri: https://github.com/evanleck/tater
|
146
|
-
post_install_message:
|
150
|
+
post_install_message:
|
147
151
|
rdoc_options: []
|
148
152
|
require_paths:
|
149
153
|
- lib
|
@@ -158,8 +162,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
158
162
|
- !ruby/object:Gem::Version
|
159
163
|
version: '2.0'
|
160
164
|
requirements: []
|
161
|
-
rubygems_version: 3.2.
|
162
|
-
signing_key:
|
165
|
+
rubygems_version: 3.2.32
|
166
|
+
signing_key:
|
163
167
|
specification_version: 4
|
164
168
|
summary: Minimal internationalization and localization library.
|
165
169
|
test_files:
|