dry-schema 1.4.3 → 1.5.4
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/CHANGELOG.md +219 -97
- data/config/errors.yml +4 -0
- data/dry-schema.gemspec +46 -0
- data/lib/dry-schema.rb +1 -1
- data/lib/dry/schema.rb +19 -6
- data/lib/dry/schema/compiler.rb +5 -5
- data/lib/dry/schema/config.rb +15 -6
- data/lib/dry/schema/constants.rb +16 -7
- data/lib/dry/schema/dsl.rb +87 -27
- data/lib/dry/schema/extensions.rb +10 -2
- data/lib/dry/schema/extensions/hints.rb +15 -8
- data/lib/dry/schema/extensions/hints/message_compiler_methods.rb +1 -1
- data/lib/dry/schema/extensions/hints/message_set_methods.rb +0 -47
- data/lib/dry/schema/extensions/info.rb +27 -0
- data/lib/dry/schema/extensions/info/schema_compiler.rb +105 -0
- data/lib/dry/schema/extensions/monads.rb +1 -1
- data/lib/dry/schema/extensions/struct.rb +32 -0
- data/lib/dry/schema/json.rb +1 -1
- data/lib/dry/schema/key.rb +16 -1
- data/lib/dry/schema/key_coercer.rb +4 -4
- data/lib/dry/schema/key_map.rb +9 -4
- data/lib/dry/schema/key_validator.rb +67 -0
- data/lib/dry/schema/macros.rb +8 -8
- data/lib/dry/schema/macros/array.rb +17 -4
- data/lib/dry/schema/macros/core.rb +9 -4
- data/lib/dry/schema/macros/dsl.rb +34 -19
- data/lib/dry/schema/macros/each.rb +4 -4
- data/lib/dry/schema/macros/filled.rb +5 -5
- data/lib/dry/schema/macros/hash.rb +21 -3
- data/lib/dry/schema/macros/key.rb +9 -9
- data/lib/dry/schema/macros/maybe.rb +3 -3
- data/lib/dry/schema/macros/optional.rb +1 -1
- data/lib/dry/schema/macros/required.rb +1 -1
- data/lib/dry/schema/macros/schema.rb +23 -2
- data/lib/dry/schema/macros/value.rb +32 -10
- data/lib/dry/schema/message.rb +35 -9
- data/lib/dry/schema/message/or.rb +18 -39
- data/lib/dry/schema/message/or/abstract.rb +28 -0
- data/lib/dry/schema/message/or/multi_path.rb +37 -0
- data/lib/dry/schema/message/or/single_path.rb +64 -0
- data/lib/dry/schema/message_compiler.rb +55 -19
- data/lib/dry/schema/message_compiler/visitor_opts.rb +2 -2
- data/lib/dry/schema/message_set.rb +25 -36
- data/lib/dry/schema/messages.rb +6 -6
- data/lib/dry/schema/messages/abstract.rb +54 -56
- data/lib/dry/schema/messages/i18n.rb +29 -27
- data/lib/dry/schema/messages/namespaced.rb +12 -2
- data/lib/dry/schema/messages/template.rb +19 -44
- data/lib/dry/schema/messages/yaml.rb +60 -13
- data/lib/dry/schema/params.rb +1 -1
- data/lib/dry/schema/path.rb +44 -5
- data/lib/dry/schema/predicate.rb +4 -2
- data/lib/dry/schema/predicate_inferrer.rb +4 -184
- data/lib/dry/schema/predicate_registry.rb +2 -2
- data/lib/dry/schema/primitive_inferrer.rb +16 -0
- data/lib/dry/schema/processor.rb +49 -28
- data/lib/dry/schema/processor_steps.rb +50 -27
- data/lib/dry/schema/result.rb +44 -6
- data/lib/dry/schema/rule_applier.rb +7 -7
- data/lib/dry/schema/step.rb +79 -0
- data/lib/dry/schema/trace.rb +5 -4
- data/lib/dry/schema/type_container.rb +3 -3
- data/lib/dry/schema/type_registry.rb +2 -2
- data/lib/dry/schema/types.rb +1 -1
- data/lib/dry/schema/value_coercer.rb +2 -2
- data/lib/dry/schema/version.rb +1 -1
- metadata +22 -8
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
3
|
+
require "set"
|
4
|
+
require "concurrent/map"
|
5
|
+
require "dry/equalizer"
|
6
|
+
require "dry/configurable"
|
7
7
|
|
8
|
-
require
|
9
|
-
require
|
8
|
+
require "dry/schema/constants"
|
9
|
+
require "dry/schema/messages/template"
|
10
10
|
|
11
11
|
module Dry
|
12
12
|
module Schema
|
@@ -21,36 +21,31 @@ module Dry
|
|
21
21
|
setting :default_locale, nil
|
22
22
|
setting :load_paths, Set[DEFAULT_MESSAGES_PATH]
|
23
23
|
setting :top_namespace, DEFAULT_MESSAGES_ROOT
|
24
|
-
setting :root,
|
24
|
+
setting :root, "errors"
|
25
25
|
setting :lookup_options, %i[root predicate path val_type arg_type].freeze
|
26
26
|
|
27
27
|
setting :lookup_paths, [
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
28
|
+
"%<root>s.rules.%<path>s.%<predicate>s.arg.%<arg_type>s",
|
29
|
+
"%<root>s.rules.%<path>s.%<predicate>s",
|
30
|
+
"%<root>s.%<predicate>s.%<message_type>s",
|
31
|
+
"%<root>s.%<predicate>s.value.%<path>s",
|
32
|
+
"%<root>s.%<predicate>s.value.%<val_type>s.arg.%<arg_type>s",
|
33
|
+
"%<root>s.%<predicate>s.value.%<val_type>s",
|
34
|
+
"%<root>s.%<predicate>s.arg.%<arg_type>s",
|
35
|
+
"%<root>s.%<predicate>s"
|
36
36
|
].freeze
|
37
37
|
|
38
|
-
setting :rule_lookup_paths, [
|
38
|
+
setting :rule_lookup_paths, ["rules.%<name>s"].freeze
|
39
39
|
|
40
|
-
setting :arg_types, Hash.new { |*|
|
41
|
-
Range =>
|
40
|
+
setting :arg_types, Hash.new { |*| "default" }.update(
|
41
|
+
Range => "range"
|
42
42
|
)
|
43
43
|
|
44
|
-
setting :val_types, Hash.new { |*|
|
45
|
-
Range =>
|
46
|
-
String =>
|
44
|
+
setting :val_types, Hash.new { |*| "default" }.update(
|
45
|
+
Range => "range",
|
46
|
+
String => "string"
|
47
47
|
)
|
48
48
|
|
49
|
-
# @api private
|
50
|
-
def self.cache
|
51
|
-
@cache ||= Concurrent::Map.new { |h, k| h[k] = Concurrent::Map.new }
|
52
|
-
end
|
53
|
-
|
54
49
|
# @api private
|
55
50
|
def self.build(options = EMPTY_HASH)
|
56
51
|
messages = new
|
@@ -79,7 +74,7 @@ module Dry
|
|
79
74
|
|
80
75
|
# @api private
|
81
76
|
def rule(name, options = {})
|
82
|
-
tokens = {
|
77
|
+
tokens = {name: name, locale: options.fetch(:locale, default_locale)}
|
83
78
|
path = rule_lookup_paths(tokens).detect { |key| key?(key, options) }
|
84
79
|
|
85
80
|
rule = get(path, options) if path
|
@@ -92,13 +87,35 @@ module Dry
|
|
92
87
|
#
|
93
88
|
# @api public
|
94
89
|
def call(predicate, options)
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
90
|
+
options = {locale: default_locale, **options}
|
91
|
+
opts = options.reject { |k,| config.lookup_options.include?(k) }
|
92
|
+
path = lookup_paths(predicate, options).detect { |key| key?(key, opts) }
|
93
|
+
|
94
|
+
return unless path
|
95
|
+
|
96
|
+
result = get(path, opts)
|
97
|
+
|
98
|
+
[
|
99
|
+
Template.new(
|
100
|
+
messages: self,
|
101
|
+
key: path,
|
102
|
+
options: opts
|
103
|
+
),
|
104
|
+
result[:meta]
|
105
|
+
]
|
99
106
|
end
|
107
|
+
|
100
108
|
alias_method :[], :call
|
101
109
|
|
110
|
+
# Check if given key is defined
|
111
|
+
#
|
112
|
+
# @return [Boolean]
|
113
|
+
#
|
114
|
+
# @api public
|
115
|
+
def key?(_key, _options = EMPTY_HASH)
|
116
|
+
raise NotImplementedError
|
117
|
+
end
|
118
|
+
|
102
119
|
# Retrieve an array of looked up paths
|
103
120
|
#
|
104
121
|
# @param [Symbol] predicate
|
@@ -112,21 +129,6 @@ module Dry
|
|
112
129
|
filled_lookup_paths(tokens)
|
113
130
|
end
|
114
131
|
|
115
|
-
# Try to find a message for the given predicate and its options
|
116
|
-
#
|
117
|
-
# @api private
|
118
|
-
#
|
119
|
-
# rubocop:disable Metrics/AbcSize
|
120
|
-
def lookup(predicate, options)
|
121
|
-
opts = options.reject { |k, _| config.lookup_options.include?(k) }
|
122
|
-
path = lookup_paths(predicate, options).detect { |key| key?(key, opts) }
|
123
|
-
|
124
|
-
return unless path
|
125
|
-
|
126
|
-
get(path, opts).values_at(:text, :meta)
|
127
|
-
end
|
128
|
-
# rubocop:enable Metrics/AbcSize
|
129
|
-
|
130
132
|
# @api private
|
131
133
|
def lookup_paths(predicate, options)
|
132
134
|
tokens = lookup_tokens(predicate, options)
|
@@ -162,22 +164,18 @@ module Dry
|
|
162
164
|
end
|
163
165
|
|
164
166
|
# @api private
|
165
|
-
def
|
166
|
-
|
167
|
+
def default_locale
|
168
|
+
config.default_locale
|
167
169
|
end
|
168
170
|
|
169
171
|
# @api private
|
170
|
-
def
|
171
|
-
|
172
|
+
def interpolatable_data(_key, _options, **_data)
|
173
|
+
raise NotImplementedError
|
172
174
|
end
|
173
175
|
|
174
176
|
# @api private
|
175
|
-
def
|
176
|
-
|
177
|
-
[predicate, options.reject { |k,| k.equal?(:input) }]
|
178
|
-
else
|
179
|
-
[predicate, options]
|
180
|
-
end
|
177
|
+
def interpolate(_key, _options, **_data)
|
178
|
+
raise NotImplementedError
|
181
179
|
end
|
182
180
|
|
183
181
|
private
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "i18n"
|
4
|
+
require "dry/schema/messages/abstract"
|
5
5
|
|
6
6
|
module Dry
|
7
7
|
module Schema
|
@@ -31,21 +31,19 @@ module Dry
|
|
31
31
|
def get(key, options = EMPTY_HASH)
|
32
32
|
return unless key
|
33
33
|
|
34
|
-
|
34
|
+
result = t.(key, locale: options.fetch(:locale, default_locale))
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
meta: EMPTY_HASH
|
43
|
-
}
|
36
|
+
if result.is_a?(Hash)
|
37
|
+
text = result[:text]
|
38
|
+
meta = result.dup.tap { |h| h.delete(:text) }
|
39
|
+
else
|
40
|
+
text = result
|
41
|
+
meta = EMPTY_HASH.dup
|
44
42
|
end
|
45
43
|
|
46
44
|
{
|
47
|
-
text:
|
48
|
-
meta:
|
45
|
+
text: text,
|
46
|
+
meta: meta
|
49
47
|
}
|
50
48
|
end
|
51
49
|
|
@@ -84,7 +82,7 @@ module Dry
|
|
84
82
|
top_namespace = config.top_namespace
|
85
83
|
|
86
84
|
mapped_data = data
|
87
|
-
.map { |k, v| [k, {
|
85
|
+
.map { |k, v| [k, {top_namespace => v[DEFAULT_MESSAGES_ROOT]}] }
|
88
86
|
.to_h
|
89
87
|
|
90
88
|
store_translations(mapped_data)
|
@@ -97,12 +95,23 @@ module Dry
|
|
97
95
|
end
|
98
96
|
|
99
97
|
# @api private
|
100
|
-
def
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
98
|
+
def interpolatable_data(_key, _options, **data)
|
99
|
+
data
|
100
|
+
end
|
101
|
+
|
102
|
+
# @api private
|
103
|
+
def interpolate(key, options, **data)
|
104
|
+
text_key = "#{key}.text"
|
105
|
+
|
106
|
+
opts = {
|
107
|
+
locale: default_locale,
|
108
|
+
**options,
|
109
|
+
**data
|
110
|
+
}
|
111
|
+
|
112
|
+
resolved_key = key?(text_key, opts) ? text_key : key
|
113
|
+
|
114
|
+
t.(resolved_key, **opts)
|
106
115
|
end
|
107
116
|
|
108
117
|
private
|
@@ -117,13 +126,6 @@ module Dry
|
|
117
126
|
I18n.backend.store_translations(locale, data[locale.to_s])
|
118
127
|
end
|
119
128
|
end
|
120
|
-
|
121
|
-
def extract_meta(parent_key, translation, options)
|
122
|
-
translation.keys.each_with_object({}) do |k, meta|
|
123
|
-
meta_key = "#{parent_key}.#{k}"
|
124
|
-
meta[k] = t.(meta_key, **options) if k != :text && key?(meta_key, options)
|
125
|
-
end
|
126
|
-
end
|
127
129
|
end
|
128
130
|
end
|
129
131
|
end
|
@@ -24,7 +24,7 @@ module Dry
|
|
24
24
|
@config = messages.config
|
25
25
|
@namespace = namespace
|
26
26
|
@messages = messages
|
27
|
-
@call_opts = {
|
27
|
+
@call_opts = {namespace: namespace}.freeze
|
28
28
|
end
|
29
29
|
|
30
30
|
# Get a message for the given key and its options
|
@@ -62,13 +62,23 @@ module Dry
|
|
62
62
|
# @api private
|
63
63
|
def rule_lookup_paths(tokens)
|
64
64
|
base_paths = messages.rule_lookup_paths(tokens)
|
65
|
-
base_paths.map { |key| key.gsub(
|
65
|
+
base_paths.map { |key| key.gsub("dry_schema", "dry_schema.#{namespace}") } + base_paths
|
66
66
|
end
|
67
67
|
|
68
68
|
# @api private
|
69
69
|
def cache_key(predicate, options)
|
70
70
|
messages.cache_key(predicate, options)
|
71
71
|
end
|
72
|
+
|
73
|
+
# @api private
|
74
|
+
def interpolatable_data(key, options, **data)
|
75
|
+
messages.interpolatable_data(key, options, **data)
|
76
|
+
end
|
77
|
+
|
78
|
+
# @api private
|
79
|
+
def interpolate(key, options, **data)
|
80
|
+
messages.interpolate(key, options, **data)
|
81
|
+
end
|
72
82
|
end
|
73
83
|
end
|
74
84
|
end
|
@@ -1,67 +1,42 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "dry/initializer"
|
4
|
+
require "dry/equalizer"
|
4
5
|
|
5
|
-
require
|
6
|
+
require "dry/schema/constants"
|
6
7
|
|
7
8
|
module Dry
|
8
9
|
module Schema
|
9
10
|
module Messages
|
10
|
-
# Template wraps a string with interpolation tokens and defines evaluator function
|
11
|
-
# dynamically
|
12
|
-
#
|
13
11
|
# @api private
|
14
12
|
class Template
|
15
|
-
|
13
|
+
extend Dry::Initializer
|
14
|
+
include Dry::Equalizer(:messages, :key, :options)
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
# @return [String]
|
21
|
-
attr_reader :text
|
22
|
-
|
23
|
-
# !@attribute [r] tokens
|
24
|
-
# @return [Hash]
|
25
|
-
attr_reader :tokens
|
26
|
-
|
27
|
-
# !@attribute [r] evaluator
|
28
|
-
# @return [Proc]
|
29
|
-
attr_reader :evaluator
|
16
|
+
option :messages
|
17
|
+
option :key
|
18
|
+
option :options
|
30
19
|
|
31
20
|
# @api private
|
32
|
-
def
|
33
|
-
|
21
|
+
def data(data = EMPTY_HASH)
|
22
|
+
ensure_message!
|
23
|
+
messages.interpolatable_data(key, options, **options, **data)
|
34
24
|
end
|
35
25
|
|
36
26
|
# @api private
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
evaluator = <<-RUBY.strip
|
42
|
-
-> (#{tokens.map { |token| "#{token}:" }.join(", ")}) { "#{text}" }
|
43
|
-
RUBY
|
44
|
-
|
45
|
-
[text, tokens, eval(evaluator, binding, __FILE__, __LINE__ - 3)]
|
27
|
+
def call(data = EMPTY_HASH)
|
28
|
+
ensure_message!
|
29
|
+
messages.interpolate(key, options, **data)
|
46
30
|
end
|
31
|
+
alias_method :[], :call
|
47
32
|
|
48
|
-
|
49
|
-
def initialize(text, tokens, evaluator)
|
50
|
-
@text = text
|
51
|
-
@tokens = tokens
|
52
|
-
@evaluator = evaluator
|
53
|
-
end
|
33
|
+
private
|
54
34
|
|
55
|
-
|
56
|
-
|
57
|
-
tokens.each_with_object({}) { |k, h| h[k] = input[k] }
|
58
|
-
end
|
35
|
+
def ensure_message!
|
36
|
+
return if messages.key?(key, options)
|
59
37
|
|
60
|
-
|
61
|
-
def call(data = EMPTY_HASH)
|
62
|
-
data.empty? ? evaluator.() : evaluator.(**data)
|
38
|
+
raise KeyError, "No message found for template, template=#{inspect}"
|
63
39
|
end
|
64
|
-
alias_method :[], :call
|
65
40
|
end
|
66
41
|
end
|
67
42
|
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "yaml"
|
4
|
+
require "pathname"
|
5
5
|
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
6
|
+
require "dry/equalizer"
|
7
|
+
require "dry/schema/constants"
|
8
|
+
require "dry/schema/messages/abstract"
|
9
9
|
|
10
10
|
module Dry
|
11
11
|
module Schema
|
@@ -13,7 +13,13 @@ module Dry
|
|
13
13
|
#
|
14
14
|
# @api public
|
15
15
|
class Messages::YAML < Messages::Abstract
|
16
|
-
LOCALE_TOKEN =
|
16
|
+
LOCALE_TOKEN = "%<locale>s"
|
17
|
+
TOKEN_REGEXP = /%{(\w*)}/.freeze
|
18
|
+
EMPTY_CONTEXT = Object.new.tap { |ctx|
|
19
|
+
def ctx.context
|
20
|
+
binding
|
21
|
+
end
|
22
|
+
}.freeze.context
|
17
23
|
|
18
24
|
include Dry::Equalizer(:data)
|
19
25
|
|
@@ -45,21 +51,26 @@ module Dry
|
|
45
51
|
hash.each do |key, value|
|
46
52
|
flat_hash(value, [*path, key], keys) if value.is_a?(Hash)
|
47
53
|
|
48
|
-
if value.is_a?(String) && hash[
|
54
|
+
if value.is_a?(String) && hash["text"] != value
|
49
55
|
keys[[*path, key].join(DOT)] = {
|
50
56
|
text: value,
|
51
57
|
meta: EMPTY_HASH
|
52
58
|
}
|
53
|
-
elsif value.is_a?(Hash) && value[
|
59
|
+
elsif value.is_a?(Hash) && value["text"].is_a?(String)
|
54
60
|
keys[[*path, key].join(DOT)] = {
|
55
|
-
text: value[
|
56
|
-
meta: value.dup.delete_if { |k| k ==
|
61
|
+
text: value["text"],
|
62
|
+
meta: value.dup.delete_if { |k| k == "text" }.map { |k, v| [k.to_sym, v] }.to_h
|
57
63
|
}
|
58
64
|
end
|
59
65
|
end
|
60
66
|
keys
|
61
67
|
end
|
62
68
|
|
69
|
+
# @api private
|
70
|
+
def self.cache
|
71
|
+
@cache ||= Concurrent::Map.new { |h, k| h[k] = Concurrent::Map.new }
|
72
|
+
end
|
73
|
+
|
63
74
|
# @api private
|
64
75
|
def initialize(data: EMPTY_HASH, config: nil)
|
65
76
|
super()
|
@@ -77,7 +88,7 @@ module Dry
|
|
77
88
|
#
|
78
89
|
# @api public
|
79
90
|
def looked_up_paths(predicate, options)
|
80
|
-
super.map { |path| path % {
|
91
|
+
super.map { |path| path % {locale: options[:locale] || default_locale} }
|
81
92
|
end
|
82
93
|
|
83
94
|
# Get a message for the given key and its options
|
@@ -124,12 +135,48 @@ module Dry
|
|
124
135
|
|
125
136
|
# @api private
|
126
137
|
def prepare
|
127
|
-
@data = config.load_paths.map { |path| load_translations(path) }.reduce(:merge)
|
138
|
+
@data = config.load_paths.map { |path| load_translations(path) }.reduce({}, :merge)
|
128
139
|
self
|
129
140
|
end
|
130
141
|
|
142
|
+
# @api private
|
143
|
+
def interpolatable_data(key, options, **data)
|
144
|
+
tokens = evaluation_context(key, options).fetch(:tokens)
|
145
|
+
data.select { |k,| tokens.include?(k) }
|
146
|
+
end
|
147
|
+
|
148
|
+
# @api private
|
149
|
+
def interpolate(key, options, **data)
|
150
|
+
evaluator = evaluation_context(key, options).fetch(:evaluator)
|
151
|
+
data.empty? ? evaluator.() : evaluator.(**data)
|
152
|
+
end
|
153
|
+
|
131
154
|
private
|
132
155
|
|
156
|
+
# @api private
|
157
|
+
def evaluation_context(key, options)
|
158
|
+
cache.fetch_or_store(get(key, options).fetch(:text)) do |input|
|
159
|
+
tokens = input.scan(TOKEN_REGEXP).flatten(1).map(&:to_sym).to_set
|
160
|
+
text = input.gsub("%", "#")
|
161
|
+
|
162
|
+
# rubocop:disable Security/Eval
|
163
|
+
evaluator = eval(<<~RUBY, EMPTY_CONTEXT, __FILE__, __LINE__ + 1)
|
164
|
+
-> (#{tokens.map { |token| "#{token}:" }.join(", ")}) { "#{text}" }
|
165
|
+
RUBY
|
166
|
+
# rubocop:enable Security/Eval
|
167
|
+
|
168
|
+
{
|
169
|
+
tokens: tokens,
|
170
|
+
evaluator: evaluator
|
171
|
+
}
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# @api private
|
176
|
+
def cache
|
177
|
+
@cache ||= self.class.cache[self]
|
178
|
+
end
|
179
|
+
|
133
180
|
# @api private
|
134
181
|
def load_translations(path)
|
135
182
|
data = self.class.flat_hash(YAML.load_file(path))
|
@@ -143,7 +190,7 @@ module Dry
|
|
143
190
|
def evaluated_key(key, options)
|
144
191
|
return key unless key.include?(LOCALE_TOKEN)
|
145
192
|
|
146
|
-
key % {
|
193
|
+
key % {locale: options[:locale] || default_locale}
|
147
194
|
end
|
148
195
|
end
|
149
196
|
end
|