tater 1.0.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '04904502e539d0ef7eab036d0a3b6c22a635863cdc76e56b27648c9d1005e12c'
4
+ data.tar.gz: 1ab472fa93cff9180ed718bc54a1209a2151c7a7be77f5ed045047a5865bdfa3
5
+ SHA512:
6
+ metadata.gz: 1faf70fb545ce2e0d2211f9af52830421c13f331e0b0519fe1c92b2e9443e1230c5c63ea19c4660d4f8468da4572b304022c8a7d93bf2aa9862b6cbb0e55cd08
7
+ data.tar.gz: e87befcdfd969ebbce3350c1427070099224bfa24b4cafaafe246b0c1e3917d0e64d09fa5c1fca06678170f6411ffe548b7c3363888ab56061d16ab98657a8ae
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 Evan Lecklider
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,179 @@
1
+ # Tater the Translator
2
+
3
+ Tater is an internationalization (i18n) and localization (l10n) library designed
4
+ for simplicity. It doesn't do everything that other libraries do, but that's by
5
+ design.
6
+
7
+ Under the hood, Tater uses a Hash to store the messages, the `dig` method for
8
+ lookups, `strftime` for date and time localizations, and `format` for
9
+ interpolation. That's probably 90% of what Tater does.
10
+
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'tater'
18
+ ```
19
+
20
+ And then execute:
21
+
22
+ ```sh
23
+ bundle
24
+ ```
25
+
26
+ Or install it yourself as:
27
+
28
+ ```sh
29
+ gem install tater
30
+ ```
31
+
32
+
33
+ ## Usage
34
+
35
+ ```ruby
36
+ messages = {
37
+ 'some' => {
38
+ 'key' => 'This here string!'
39
+ },
40
+ 'interpolated' => 'Hello %{you}!'
41
+ }
42
+
43
+ i18n = Tater.new
44
+ i18n.load(messages: messages)
45
+
46
+ # Basic lookup:
47
+ i18n.translate('some.key') # => 'This here string!'
48
+
49
+ # Interpolation:
50
+ i18n.translate('interpolated', you: 'world') # => 'Hello world!'
51
+ ```
52
+
53
+ ## Numeric Localization
54
+
55
+ Numeric localization (`Numeric`, `Integer`, `Float`, and `BigDecimal`) require
56
+ filling in a separater and delimiter. For example:
57
+
58
+ ```yaml
59
+ en:
60
+ numeric:
61
+ delimiter: ','
62
+ separator: '.'
63
+ ```
64
+
65
+ With that, you can do things like this:
66
+
67
+ ```ruby
68
+ i18n.localize(1000.2) # => "1,000.20"
69
+ ```
70
+
71
+
72
+ ## Date and Time Localization
73
+
74
+ Date and time localization (`Date`, `Time`, and `DateTime`) require filling in
75
+ all of the needed names and abbreviations for days and months. Here's the
76
+ example for French, which is used in the tests.
77
+
78
+ ```yaml
79
+ fr:
80
+ time:
81
+ am: 'am'
82
+ pm: 'pm'
83
+
84
+ formats:
85
+ default: '%I%P'
86
+ loud: '%I%p'
87
+
88
+ date:
89
+ formats:
90
+ abbreviated_day: '%a'
91
+ day: '%A'
92
+
93
+ abbreviated_month: '%b'
94
+ month: '%B'
95
+
96
+ days:
97
+ - dimanche
98
+ - lundi
99
+ - mardi
100
+ - mercredi
101
+ - jeudi
102
+ - vendredi
103
+ - samedi
104
+
105
+ abbreviated_days:
106
+ - dim
107
+ - lun
108
+ - mar
109
+ - mer
110
+ - jeu
111
+ - ven
112
+ - sam
113
+
114
+ months:
115
+ - janvier
116
+ - février
117
+ - mars
118
+ - avril
119
+ - mai
120
+ - juin
121
+ - juillet
122
+ - août
123
+ - septembre
124
+ - octobre
125
+ - novembre
126
+ - décembre
127
+
128
+ abbreviated_months:
129
+ - jan.
130
+ - fév.
131
+ - mar.
132
+ - avr.
133
+ - mai
134
+ - juin
135
+ - juil.
136
+ - août
137
+ - sept.
138
+ - oct.
139
+ - nov.
140
+ - déc.
141
+ ```
142
+
143
+ The statically defined keys for dates are `days`, `abbreviated_days`, `months`,
144
+ and `abbreviated_months`. Only `am` and `pm` are needed for times and only if
145
+ you plan on using the `%p` or `%P` format strings.
146
+
147
+ With all of that, you can do something like:
148
+
149
+ ```ruby
150
+ i18n.localize(Date.new(1970, 1, 1), format: '%A') # => 'jeudi'
151
+
152
+ # Or, using a key defined in "formats":
153
+ i18n.localize(Date.new(1970, 1, 1), format: 'day') # => 'jeudi'
154
+ ```
155
+
156
+
157
+ ## Limitations
158
+
159
+ - It is not "pluggable", it does what it does and that's it.
160
+ - It doesn't handle pluralization yet, though it may in the future.
161
+ - It doesn't cache anything, that's up to you.
162
+
163
+
164
+ ## Why?
165
+
166
+ Because [Ruby I18n][rubyi18n] is amazing and I wanted to try to create a minimum
167
+ viable implementation of the bits of I18n that I use 90% of the time. Tater is a
168
+ single file that handles the basics of lookup and interpolation.
169
+
170
+
171
+ ## Trivia
172
+
173
+ I was orininally going to call this library "Translator" but with a
174
+ [numeronym][numeronym] like I18n: "t8r". I looked at it for a while but I read
175
+ it as "tater" instead of "tee-eight-arr" so I figured I'd just name it Tater.
176
+ Tater the translator.
177
+
178
+ [numeronym]: https://en.wikipedia.org/wiki/Numeronym
179
+ [rubyi18n]: https://github.com/ruby-i18n/i18n
data/lib/tater.rb ADDED
@@ -0,0 +1,225 @@
1
+ # frozen_string_literal: true
2
+ require 'bigdecimal'
3
+ require 'yaml'
4
+
5
+ # Tater is a internationalization (i18n) and localization (l10n) library
6
+ # designed for speed and simplicity.
7
+ class Tater
8
+ class MissingLocalizationFormat < ArgumentError; end
9
+ class UnLocalizableObject < ArgumentError; end
10
+
11
+ module Utils # :nodoc:
12
+ # Merge all the way down.
13
+ #
14
+ # @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.
17
+ # @param from [Hash]
18
+ # The Hash to copy values from.
19
+ def self.deep_merge!(to, from)
20
+ to.merge!(from) do |_key, left, right|
21
+ if left.is_a?(Hash) && right.is_a?(Hash)
22
+ Utils.deep_merge!(left, right)
23
+ else
24
+ right
25
+ end
26
+ end
27
+ end
28
+
29
+ # Transform keys all the way down.
30
+ #
31
+ # @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|
36
+ if value.is_a?(Hash)
37
+ Utils.deep_stringify_keys!(value)
38
+ else
39
+ value
40
+ end
41
+ end
42
+ end
43
+
44
+ # Try to interpolate these things, if one of them is a string.
45
+ #
46
+ # @param string [String]
47
+ # The target string to interpolate into.
48
+ # @param options [Hash]
49
+ # The values to interpolate into the target string.
50
+ #
51
+ # @return [String]
52
+ def self.interpolate(string, options = {})
53
+ return string unless string.is_a?(String)
54
+
55
+ format(string, options)
56
+ end
57
+
58
+ # Convert a Numeric to a string, particularly formatting BigDecimals to a
59
+ # Float-like string representation.
60
+ #
61
+ # @param numeric [Numeric]
62
+ #
63
+ # @return [String]
64
+ def self.string_from_numeric(numeric)
65
+ if numeric.is_a?(BigDecimal)
66
+ numeric.to_s('F')
67
+ else
68
+ numeric.to_s
69
+ end
70
+ end
71
+ end
72
+
73
+ DEFAULT = 'default'
74
+ DEFAULT_LOCALE = 'en'
75
+ DELIMITING_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/.freeze
76
+ SEPARATOR = '.'
77
+ SUBSTITUTION_REGEX = /%(|\^)[aAbBpP]/.freeze
78
+
79
+ # @return [String]
80
+ attr_reader :locale
81
+
82
+ # @return [Hash]
83
+ attr_reader :messages
84
+
85
+ def initialize(path: nil, messages: nil, locale: DEFAULT_LOCALE)
86
+ @locale = locale
87
+ @messages = {}
88
+
89
+ load(path: path) if path
90
+ load(messages: messages) if messages
91
+ end
92
+
93
+ # An array of the available locale codes.
94
+ #
95
+ # @return [Array]
96
+ def available
97
+ messages.keys.map(&:to_s)
98
+ end
99
+
100
+ # Is this locale available in our current set of messages?
101
+ #
102
+ # @return [Boolean]
103
+ def available?(locale)
104
+ available.include?(locale.to_s)
105
+ end
106
+
107
+ # Load messages into our internal cache, either from a path containing YAML
108
+ # files or a collection of messages.
109
+ #
110
+ # @param path [String]
111
+ # A path to search for YAML files to load messages from.
112
+ # @param messages [Hash]
113
+ # A hash of messages ready to be loaded in.
114
+ def load(path: nil, messages: nil)
115
+ if path
116
+ Dir.glob(File.join(path, '**', '*.{yml,yaml}')).each do |file|
117
+ Utils.deep_merge!(@messages, YAML.safe_load(File.read(file)))
118
+ end
119
+ end
120
+
121
+ Utils.deep_merge!(@messages, Utils.deep_stringify_keys!(messages)) if messages
122
+ end
123
+
124
+ # Set the current locale, if it's available.
125
+ #
126
+ # @param locale [String]
127
+ # The locale code to set as our default.
128
+ def locale=(locale)
129
+ @locale = locale.to_s if available?(locale)
130
+ end
131
+
132
+ # Localize a Date, Time, DateTime, or Numeric object.
133
+ #
134
+ # @param object [Date, Time, DateTime, Numeric]
135
+ # The object to localize.
136
+ #
137
+ # @return [String]
138
+ # A localized version of the object passed in.
139
+ def localize(object, options = {})
140
+ format_key = options.delete(:format) || DEFAULT
141
+ locale_override = options.delete(:locale)
142
+
143
+ case object
144
+ when String
145
+ object
146
+ when Numeric
147
+ delimiter = lookup('numeric.delimiter', locale_override)
148
+ separator = lookup('numeric.separator', locale_override)
149
+ precision = options.fetch(:precision) { 2 }
150
+
151
+ raise(MissingLocalizationFormat, "Numeric localization delimiter ('numeric.delimiter') missing") unless delimiter
152
+ raise(MissingLocalizationFormat, "Numeric localization separator ('numeric.separator') missing") unless separator
153
+
154
+ # Heavily cribbed from Rails.
155
+ integer, fraction = Utils.string_from_numeric(object).split('.')
156
+ integer.gsub!(DELIMITING_REGEX) do |number|
157
+ "#{ number }#{ delimiter }"
158
+ end
159
+
160
+ if precision.zero?
161
+ integer
162
+ else
163
+ [integer, fraction&.ljust(precision, '0')].compact.join(separator)
164
+ end
165
+ when Date, Time, DateTime
166
+ key = object.class.to_s.downcase
167
+ format = lookup("#{ key }.formats.#{ format_key }", locale_override) || format_key
168
+
169
+ # Heavily cribbed from I18n, many thanks to the people who sorted this out
170
+ # before I worked on this library.
171
+ format = format.gsub(SUBSTITUTION_REGEX) do |match|
172
+ case match
173
+ when '%a' then lookup('date.abbreviated_days', locale_override)[object.wday]
174
+ when '%^a' then lookup('date.abbreviated_days', locale_override)[object.wday].upcase
175
+ when '%A' then lookup('date.days', locale_override)[object.wday]
176
+ when '%^A' then lookup('date.days', locale_override)[object.wday].upcase
177
+ when '%b' then lookup('date.abbreviated_months', locale_override)[object.mon - 1]
178
+ when '%^b' then lookup('date.abbreviated_months', locale_override)[object.mon - 1].upcase
179
+ when '%B' then lookup('date.months', locale_override)[object.mon - 1]
180
+ when '%^B' then lookup('date.months', locale_override)[object.mon - 1].upcase
181
+ when '%p' then lookup("time.#{ object.hour < 12 ? 'am' : 'pm' }", locale_override).upcase if object.respond_to?(:hour) # rubocop:disable Metric/BlockNesting
182
+ when '%P' then lookup("time.#{ object.hour < 12 ? 'am' : 'pm' }", locale_override).downcase if object.respond_to?(:hour) # rubocop:disable Metric/BlockNesting
183
+ end
184
+ end
185
+
186
+ object.strftime(format)
187
+ else
188
+ raise(UnLocalizableObject, "The object class #{ object.class } cannot be localized by Tater.")
189
+ end
190
+ end
191
+ alias l localize
192
+
193
+ # Lookup a key in the messages hash, using the current locale or an override.
194
+ #
195
+ # @param key [String]
196
+ # @param locale_override [String]
197
+ # A locale to use instead of our current one.
198
+ #
199
+ # @return
200
+ # Basically anything that can be stored in YAML, including nil.
201
+ def lookup(key, locale_override = nil)
202
+ path = key.split(SEPARATOR).prepend(locale_override || locale).map(&:to_s)
203
+
204
+ @messages.dig(*path)
205
+ end
206
+
207
+ # Translate a key path and optional interpolation arguments into a string.
208
+ # It's effectively a combination of #lookup and #interpolate.
209
+ #
210
+ # @example
211
+ # Tater.new(messages: { 'en' => { 'hi' => 'Hello' }}).translate('hi') # => 'Hello'
212
+ #
213
+ # @param key [String]
214
+ # The period-separated key path to look within our messages for.
215
+ #
216
+ # @return [String]
217
+ # The translated and interpreted string, if found, or any data at the
218
+ # defined key.
219
+ def translate(key, options = {})
220
+ locale_override = options.delete(:locale)
221
+
222
+ Utils.interpolate(lookup(key, locale_override), options) || "Tater lookup failed: #{ locale_override || locale }.#{ key }"
223
+ end
224
+ alias t translate
225
+ end
data/test/another.yml ADDED
@@ -0,0 +1,2 @@
1
+ en:
2
+ another: 'This is from a different file'
data/test/fixtures.yml ADDED
@@ -0,0 +1,87 @@
1
+ en:
2
+ title: 'This is a title'
3
+ interpolated: 'This has some %{fancy} text'
4
+ double: '%{first} %{second}!'
5
+
6
+ deep:
7
+ key: 'This key is deeper'
8
+
9
+ date:
10
+ formats:
11
+ default: '%Y/%-m/%-d'
12
+ ohmy: '%-m something %-d oh my %Y'
13
+
14
+ time:
15
+ formats:
16
+ default: '%Y/%-m/%-d/%H/%M/%S'
17
+
18
+ datetime:
19
+ formats:
20
+ default: '%Y/%-m/%-d/%H/%M/%S'
21
+
22
+ numeric:
23
+ delimiter: 'TURKEYS'
24
+ separator: 'NAH'
25
+
26
+ fr:
27
+ time:
28
+ am: 'am'
29
+ pm: 'pm'
30
+
31
+ formats:
32
+ default: '%I%P'
33
+ loud: '%I%p'
34
+
35
+ date:
36
+ formats:
37
+ abbreviated_day: '%a'
38
+ day: '%A'
39
+
40
+ abbreviated_month: '%b'
41
+ month: '%B'
42
+
43
+ days:
44
+ - dimanche
45
+ - lundi
46
+ - mardi
47
+ - mercredi
48
+ - jeudi
49
+ - vendredi
50
+ - samedi
51
+
52
+ abbreviated_days:
53
+ - dim
54
+ - lun
55
+ - mar
56
+ - mer
57
+ - jeu
58
+ - ven
59
+ - sam
60
+
61
+ months:
62
+ - janvier
63
+ - février
64
+ - mars
65
+ - avril
66
+ - mai
67
+ - juin
68
+ - juillet
69
+ - août
70
+ - septembre
71
+ - octobre
72
+ - novembre
73
+ - décembre
74
+
75
+ abbreviated_months:
76
+ - jan.
77
+ - fév.
78
+ - mar.
79
+ - avr.
80
+ - mai
81
+ - juin
82
+ - juil.
83
+ - août
84
+ - sept.
85
+ - oct.
86
+ - nov.
87
+ - déc.
@@ -0,0 +1,12 @@
1
+ en:
2
+ more: "Oh there's more!"
3
+
4
+ delimiter_only:
5
+ numeric:
6
+ formats:
7
+ delimiter: 'Just me'
8
+
9
+ separator_only:
10
+ numeric:
11
+ formats:
12
+ separator: 'Just me'
@@ -0,0 +1,285 @@
1
+ # frozen_string_literal: true
2
+ require_relative '../lib/tater'
3
+ require 'minitest/autorun'
4
+ require 'date'
5
+
6
+ describe Tater do
7
+ describe Tater::Utils do
8
+ describe '#deep_merge!' do
9
+ it 'deeply merges two hashes, modifying the first' do
10
+ first = { 'one' => 'one', 'two' => { 'three' => 'three' } }
11
+ second = { 'two' => { 'four' => 'four' } }
12
+
13
+ Tater::Utils.deep_merge!(first, second)
14
+
15
+ assert_equal({ 'one' => 'one', 'two' => { 'three' => 'three', 'four' => 'four' } }, first)
16
+ end
17
+ end
18
+
19
+ describe '#deep_stringify_keys!' do
20
+ it 'converts all keys into strings, recursively modifying the hash passed in' do
21
+ start = { en: { login: { title: 'Hello!' } } }
22
+
23
+ Tater::Utils.deep_stringify_keys!(start)
24
+
25
+ assert_equal({ 'en' => { 'login' => { 'title' => 'Hello!' } } }, start)
26
+ end
27
+ end
28
+
29
+ describe '#interpolate' do
30
+ it 'interpolates a string and hash' do
31
+ assert_equal 'this thing', Tater::Utils.interpolate('this %{what}', what: 'thing')
32
+ end
33
+
34
+ it 'raises a KeyError when an argument is missing' do
35
+ assert_raises(KeyError) do
36
+ Tater::Utils.interpolate('this %{what}')
37
+ end
38
+ end
39
+ end
40
+
41
+ describe '#string_from_numeric' do
42
+ it 'converts numerics to decimal-ish strings' do
43
+ assert_equal '1', Tater::Utils.string_from_numeric(1)
44
+ assert_equal '1.0', Tater::Utils.string_from_numeric(1.0)
45
+ assert_equal '1.0', Tater::Utils.string_from_numeric(BigDecimal(1))
46
+ end
47
+ end
48
+ end
49
+
50
+ describe '#available?' do
51
+ let :i18n do
52
+ Tater.new(path: File.expand_path('test'))
53
+ end
54
+
55
+ it 'tells you if the locale is available' do
56
+ assert i18n.available?('en')
57
+ refute i18n.available?('romulan')
58
+ end
59
+ end
60
+
61
+ describe '#load' do
62
+ it 'loads from a path on initialization' do
63
+ i18n = Tater.new(path: File.expand_path('test'))
64
+
65
+ assert_instance_of(Hash, i18n.messages)
66
+ end
67
+
68
+ it 'loads from a path after initialization' do
69
+ i18n = Tater.new
70
+ i18n.load(path: File.expand_path('test'))
71
+
72
+ assert_instance_of(Hash, i18n.messages)
73
+ end
74
+
75
+ it 'loads from a hash of messages on initialization' do
76
+ i18n = Tater.new(messages: { 'hey' => 'Oh hi' })
77
+
78
+ assert_instance_of(Hash, i18n.messages)
79
+ end
80
+
81
+ it 'loads from a hash of messages after initialization' do
82
+ i18n = Tater.new
83
+ i18n.load(messages: { 'hey' => 'Oh hi' })
84
+
85
+ assert_instance_of(Hash, i18n.messages)
86
+ end
87
+ end
88
+
89
+ describe '#available' do
90
+ let :i18n do
91
+ Tater.new(path: File.expand_path('test'))
92
+ end
93
+
94
+ it 'returns an array with the available locales (i.e. the top-level keys in our messages hash)' do
95
+ assert_equal %w[en delimiter_only separator_only fr], i18n.available
96
+ end
97
+ end
98
+
99
+ describe '#lookup' do
100
+ let :i18n do
101
+ Tater.new(path: File.expand_path('test'))
102
+ end
103
+
104
+ it 'returns keys from messages' do
105
+ assert_equal 'This is a title', i18n.lookup('title')
106
+ end
107
+
108
+ it 'does no interpolation' do
109
+ assert_equal 'This has some %{fancy} text', i18n.lookup('interpolated')
110
+ end
111
+
112
+ it 'returns nil for missing lookups' do
113
+ assert_nil i18n.lookup('nope')
114
+ end
115
+ end
116
+
117
+ describe '#translate' do
118
+ let :i18n do
119
+ Tater.new(path: File.expand_path('test'))
120
+ end
121
+
122
+ it 'translates strings' do
123
+ assert_equal 'This is a title', i18n.translate('title')
124
+ end
125
+
126
+ it 'translates nested strings' do
127
+ assert_equal 'This key is deeper', i18n.translate('deep.key')
128
+ end
129
+
130
+ it 'returns a hash for nested keys' do
131
+ assert_equal({ 'key' => 'This key is deeper' }, i18n.translate('deep'))
132
+ end
133
+
134
+ it 'interpolates additional variables' do
135
+ assert_equal 'This has some fancy text', i18n.translate('interpolated', fancy: 'fancy')
136
+ assert_equal 'Double down!', i18n.translate('double', first: 'Double', second: 'down')
137
+ end
138
+
139
+ it 'works with multiple files' do
140
+ assert_equal 'This is from a different file', i18n.translate('another')
141
+ assert_equal "Oh there's more!", i18n.translate('more')
142
+ end
143
+
144
+ it 'returns a message for failed translations' do
145
+ assert_equal 'Tater lookup failed: en.nope', i18n.translate('nope')
146
+ end
147
+
148
+ it 'is aliased as t' do
149
+ assert_equal 'This is a title', i18n.t('title')
150
+ end
151
+ end
152
+
153
+ describe '#localize' do
154
+ let :i18n do
155
+ Tater.new(path: File.expand_path('test'))
156
+ end
157
+
158
+ it 'localizes Dates' do
159
+ assert_equal '1970/1/1', i18n.localize(Date.new(1970, 1, 1))
160
+ end
161
+
162
+ it 'localizes Times' do
163
+ assert_equal '1970/1/1/00/00/00', i18n.localize(Time.new(1970, 1, 1, 0, 0, 0))
164
+ end
165
+
166
+ it 'localizes DateTimes' do
167
+ assert_equal '1970/1/1/00/00/00', i18n.localize(DateTime.new(1970, 1, 1, 0, 0, 0))
168
+ end
169
+
170
+ it 'localizes Floats' do
171
+ assert_equal '10TURKEYS000NAH12', i18n.localize(10_000.12)
172
+ end
173
+
174
+ it 'localizes Integers' do
175
+ assert_equal '10TURKEYS000', i18n.localize(10_000)
176
+ end
177
+
178
+ it 'localizes BigDecimals' do
179
+ assert_equal '1NAH12', i18n.localize(BigDecimal('1.12'))
180
+ end
181
+
182
+ it 'accepts other formats' do
183
+ assert_equal '1 something 1 oh my 1970', i18n.localize(Date.new(1970, 1, 1), format: 'ohmy')
184
+ end
185
+
186
+ it 'uses the passed in format if the specified class and format are not present' do
187
+ assert_equal 'not there', i18n.localize(Date.new(1970, 1, 1), format: 'not there')
188
+ end
189
+
190
+ it 'raises a MissingLocalizationFormat if a delimiter is missing' do
191
+ assert_raises(Tater::MissingLocalizationFormat) do
192
+ i18n.localize(10, locale: 'separator_only')
193
+ end
194
+ end
195
+
196
+ it 'raises a MissingLocalizationFormat if a separator is missing' do
197
+ assert_raises(Tater::MissingLocalizationFormat) do
198
+ i18n.localize(10, locale: 'delimiter_only')
199
+ end
200
+ end
201
+
202
+ it 'is aliased l' do
203
+ assert_equal '1970/1/1', i18n.l(Date.new(1970, 1, 1))
204
+ end
205
+
206
+ describe 'month, day, and AM/PM names' do
207
+ let :i18n do
208
+ Tater.new(path: File.expand_path('test'), locale: 'fr')
209
+ end
210
+
211
+ it 'localizes day names' do
212
+ assert_equal 'jeudi', i18n.localize(Date.new(1970, 1, 1), format: 'day')
213
+ assert_equal 'vendredi', i18n.localize(Date.new(1970, 1, 2), format: 'day')
214
+ assert_equal 'samedi', i18n.localize(Date.new(1970, 1, 3), format: 'day')
215
+ assert_equal 'dimanche', i18n.localize(Date.new(1970, 1, 4), format: 'day')
216
+ assert_equal 'lundi', i18n.localize(Date.new(1970, 1, 5), format: 'day')
217
+ assert_equal 'mardi', i18n.localize(Date.new(1970, 1, 6), format: 'day')
218
+ assert_equal 'mercredi', i18n.localize(Date.new(1970, 1, 7), format: 'day')
219
+ end
220
+
221
+ it 'localizes abbreviated day names' do
222
+ assert_equal 'jeu', i18n.localize(Date.new(1970, 1, 1), format: 'abbreviated_day')
223
+ assert_equal 'ven', i18n.localize(Date.new(1970, 1, 2), format: 'abbreviated_day')
224
+ assert_equal 'sam', i18n.localize(Date.new(1970, 1, 3), format: 'abbreviated_day')
225
+ assert_equal 'dim', i18n.localize(Date.new(1970, 1, 4), format: 'abbreviated_day')
226
+ assert_equal 'lun', i18n.localize(Date.new(1970, 1, 5), format: 'abbreviated_day')
227
+ assert_equal 'mar', i18n.localize(Date.new(1970, 1, 6), format: 'abbreviated_day')
228
+ assert_equal 'mer', i18n.localize(Date.new(1970, 1, 7), format: 'abbreviated_day')
229
+ end
230
+
231
+ it 'localizes months' do
232
+ assert_equal 'janvier', i18n.localize(Date.new(1970, 1, 1), format: 'month')
233
+ assert_equal 'février', i18n.localize(Date.new(1970, 2, 1), format: 'month')
234
+ assert_equal 'mars', i18n.localize(Date.new(1970, 3, 1), format: 'month')
235
+ assert_equal 'avril', i18n.localize(Date.new(1970, 4, 1), format: 'month')
236
+ assert_equal 'mai', i18n.localize(Date.new(1970, 5, 1), format: 'month')
237
+ assert_equal 'juin', i18n.localize(Date.new(1970, 6, 1), format: 'month')
238
+ assert_equal 'juillet', i18n.localize(Date.new(1970, 7, 1), format: 'month')
239
+ assert_equal 'août', i18n.localize(Date.new(1970, 8, 1), format: 'month')
240
+ assert_equal 'septembre', i18n.localize(Date.new(1970, 9, 1), format: 'month')
241
+ assert_equal 'octobre', i18n.localize(Date.new(1970, 10, 1), format: 'month')
242
+ assert_equal 'novembre', i18n.localize(Date.new(1970, 11, 1), format: 'month')
243
+ assert_equal 'décembre', i18n.localize(Date.new(1970, 12, 1), format: 'month')
244
+ end
245
+
246
+ it 'localizes abbreviated months' do
247
+ assert_equal 'jan.', i18n.localize(Date.new(1970, 1, 1), format: 'abbreviated_month')
248
+ assert_equal 'fév.', i18n.localize(Date.new(1970, 2, 1), format: 'abbreviated_month')
249
+ assert_equal 'mar.', i18n.localize(Date.new(1970, 3, 1), format: 'abbreviated_month')
250
+ assert_equal 'avr.', i18n.localize(Date.new(1970, 4, 1), format: 'abbreviated_month')
251
+ assert_equal 'mai', i18n.localize(Date.new(1970, 5, 1), format: 'abbreviated_month')
252
+ assert_equal 'juin', i18n.localize(Date.new(1970, 6, 1), format: 'abbreviated_month')
253
+ assert_equal 'juil.', i18n.localize(Date.new(1970, 7, 1), format: 'abbreviated_month')
254
+ assert_equal 'août', i18n.localize(Date.new(1970, 8, 1), format: 'abbreviated_month')
255
+ assert_equal 'sept.', i18n.localize(Date.new(1970, 9, 1), format: 'abbreviated_month')
256
+ assert_equal 'oct.', i18n.localize(Date.new(1970, 10, 1), format: 'abbreviated_month')
257
+ assert_equal 'nov.', i18n.localize(Date.new(1970, 11, 1), format: 'abbreviated_month')
258
+ assert_equal 'déc.', i18n.localize(Date.new(1970, 12, 1), format: 'abbreviated_month')
259
+ end
260
+
261
+ it 'localizes AM/PM' do
262
+ assert_equal '05pm', i18n.localize(Time.new(1970, 1, 1, 17))
263
+ assert_equal '05PM', i18n.localize(Time.new(1970, 1, 1, 17), format: 'loud')
264
+ assert_equal '05am', i18n.localize(Time.new(1970, 1, 1, 5))
265
+ assert_equal '05AM', i18n.localize(Time.new(1970, 1, 1, 5), format: 'loud')
266
+ end
267
+ end
268
+ end
269
+
270
+ describe '#locale=' do
271
+ let :i18n do
272
+ Tater.new(path: File.expand_path('test'))
273
+ end
274
+
275
+ it 'overrides the locale when available' do
276
+ i18n.locale = 'delimiter_only'
277
+ assert_equal 'delimiter_only', i18n.locale
278
+ end
279
+
280
+ it 'does not override the locale when not available' do
281
+ i18n.locale = 'nopeskies'
282
+ assert_equal 'en', i18n.locale
283
+ end
284
+ end
285
+ end
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tater
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Evan Lecklider
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-03-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
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'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rubocop
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Minimal internationalization and localization library.
42
+ email:
43
+ - evan@lecklider.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - LICENSE.txt
49
+ - README.md
50
+ - lib/tater.rb
51
+ - test/another.yml
52
+ - test/fixtures.yml
53
+ - test/messages/more.yml
54
+ - test/tater_test.rb
55
+ homepage: https://github.com/evanleck/tater
56
+ licenses:
57
+ - MIT
58
+ metadata:
59
+ bug_tracker_uri: https://github.com/evanleck/tater/issues
60
+ source_code_uri: https://github.com/evanleck/tater
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubygems_version: 3.0.3
77
+ signing_key:
78
+ specification_version: 4
79
+ summary: Minimal internationalization and localization library.
80
+ test_files:
81
+ - test/messages/more.yml
82
+ - test/fixtures.yml
83
+ - test/tater_test.rb
84
+ - test/another.yml