tater 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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