mega-sharp-sys 0.0.1

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.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/dry-schema-1.16.0/CHANGELOG.md +901 -0
  3. data/dry-schema-1.16.0/LICENSE +20 -0
  4. data/dry-schema-1.16.0/README.md +17 -0
  5. data/dry-schema-1.16.0/config/errors.yml +127 -0
  6. data/dry-schema-1.16.0/dry-schema.gemspec +50 -0
  7. data/dry-schema-1.16.0/lib/dry/schema/compiler.rb +51 -0
  8. data/dry-schema-1.16.0/lib/dry/schema/config.rb +83 -0
  9. data/dry-schema-1.16.0/lib/dry/schema/constants.rb +47 -0
  10. data/dry-schema-1.16.0/lib/dry/schema/dsl.rb +517 -0
  11. data/dry-schema-1.16.0/lib/dry/schema/extensions/hints/compiler_methods.rb +19 -0
  12. data/dry-schema-1.16.0/lib/dry/schema/extensions/hints/message_compiler_methods.rb +89 -0
  13. data/dry-schema-1.16.0/lib/dry/schema/extensions/hints/message_set_methods.rb +44 -0
  14. data/dry-schema-1.16.0/lib/dry/schema/extensions/hints/result_methods.rb +47 -0
  15. data/dry-schema-1.16.0/lib/dry/schema/extensions/hints.rb +65 -0
  16. data/dry-schema-1.16.0/lib/dry/schema/extensions/info/schema_compiler.rb +123 -0
  17. data/dry-schema-1.16.0/lib/dry/schema/extensions/info.rb +27 -0
  18. data/dry-schema-1.16.0/lib/dry/schema/extensions/json_schema/schema_compiler.rb +314 -0
  19. data/dry-schema-1.16.0/lib/dry/schema/extensions/json_schema.rb +29 -0
  20. data/dry-schema-1.16.0/lib/dry/schema/extensions/monads.rb +32 -0
  21. data/dry-schema-1.16.0/lib/dry/schema/extensions/struct.rb +62 -0
  22. data/dry-schema-1.16.0/lib/dry/schema/extensions.rb +21 -0
  23. data/dry-schema-1.16.0/lib/dry/schema/json.rb +17 -0
  24. data/dry-schema-1.16.0/lib/dry/schema/key.rb +184 -0
  25. data/dry-schema-1.16.0/lib/dry/schema/key_coercer.rb +41 -0
  26. data/dry-schema-1.16.0/lib/dry/schema/key_map.rb +136 -0
  27. data/dry-schema-1.16.0/lib/dry/schema/key_validator.rb +117 -0
  28. data/dry-schema-1.16.0/lib/dry/schema/macros/array.rb +54 -0
  29. data/dry-schema-1.16.0/lib/dry/schema/macros/core.rb +55 -0
  30. data/dry-schema-1.16.0/lib/dry/schema/macros/dsl.rb +282 -0
  31. data/dry-schema-1.16.0/lib/dry/schema/macros/each.rb +31 -0
  32. data/dry-schema-1.16.0/lib/dry/schema/macros/filled.rb +58 -0
  33. data/dry-schema-1.16.0/lib/dry/schema/macros/hash.rb +36 -0
  34. data/dry-schema-1.16.0/lib/dry/schema/macros/key.rb +50 -0
  35. data/dry-schema-1.16.0/lib/dry/schema/macros/maybe.rb +38 -0
  36. data/dry-schema-1.16.0/lib/dry/schema/macros/optional.rb +22 -0
  37. data/dry-schema-1.16.0/lib/dry/schema/macros/required.rb +17 -0
  38. data/dry-schema-1.16.0/lib/dry/schema/macros/schema.rb +103 -0
  39. data/dry-schema-1.16.0/lib/dry/schema/macros/value.rb +125 -0
  40. data/dry-schema-1.16.0/lib/dry/schema/message/or/abstract.rb +28 -0
  41. data/dry-schema-1.16.0/lib/dry/schema/message/or/multi_path.rb +100 -0
  42. data/dry-schema-1.16.0/lib/dry/schema/message/or/single_path.rb +72 -0
  43. data/dry-schema-1.16.0/lib/dry/schema/message/or.rb +30 -0
  44. data/dry-schema-1.16.0/lib/dry/schema/message.rb +116 -0
  45. data/dry-schema-1.16.0/lib/dry/schema/message_compiler/visitor_opts.rb +56 -0
  46. data/dry-schema-1.16.0/lib/dry/schema/message_compiler.rb +234 -0
  47. data/dry-schema-1.16.0/lib/dry/schema/message_set.rb +144 -0
  48. data/dry-schema-1.16.0/lib/dry/schema/messages/abstract.rb +202 -0
  49. data/dry-schema-1.16.0/lib/dry/schema/messages/i18n.rb +139 -0
  50. data/dry-schema-1.16.0/lib/dry/schema/messages/namespaced.rb +93 -0
  51. data/dry-schema-1.16.0/lib/dry/schema/messages/template.rb +43 -0
  52. data/dry-schema-1.16.0/lib/dry/schema/messages/yaml.rb +209 -0
  53. data/dry-schema-1.16.0/lib/dry/schema/messages.rb +32 -0
  54. data/dry-schema-1.16.0/lib/dry/schema/namespaced_rule.rb +38 -0
  55. data/dry-schema-1.16.0/lib/dry/schema/params.rb +17 -0
  56. data/dry-schema-1.16.0/lib/dry/schema/path.rb +107 -0
  57. data/dry-schema-1.16.0/lib/dry/schema/predicate.rb +102 -0
  58. data/dry-schema-1.16.0/lib/dry/schema/predicate_inferrer.rb +25 -0
  59. data/dry-schema-1.16.0/lib/dry/schema/predicate_registry.rb +22 -0
  60. data/dry-schema-1.16.0/lib/dry/schema/primitive_inferrer.rb +22 -0
  61. data/dry-schema-1.16.0/lib/dry/schema/processor.rb +213 -0
  62. data/dry-schema-1.16.0/lib/dry/schema/processor_steps.rb +141 -0
  63. data/dry-schema-1.16.0/lib/dry/schema/result.rb +203 -0
  64. data/dry-schema-1.16.0/lib/dry/schema/rule_applier.rb +47 -0
  65. data/dry-schema-1.16.0/lib/dry/schema/step.rb +59 -0
  66. data/dry-schema-1.16.0/lib/dry/schema/trace.rb +114 -0
  67. data/dry-schema-1.16.0/lib/dry/schema/type_container.rb +27 -0
  68. data/dry-schema-1.16.0/lib/dry/schema/type_registry.rb +44 -0
  69. data/dry-schema-1.16.0/lib/dry/schema/types.rb +14 -0
  70. data/dry-schema-1.16.0/lib/dry/schema/types_merger.rb +139 -0
  71. data/dry-schema-1.16.0/lib/dry/schema/value_coercer.rb +36 -0
  72. data/dry-schema-1.16.0/lib/dry/schema/version.rb +7 -0
  73. data/dry-schema-1.16.0/lib/dry/schema.rb +114 -0
  74. data/dry-schema-1.16.0/lib/dry-schema.rb +3 -0
  75. data/mega-sharp-sys.gemspec +11 -0
  76. metadata +114 -0
@@ -0,0 +1,234 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/initializer"
4
+ require "dry/schema/constants"
5
+
6
+ module Dry
7
+ module Schema
8
+ # Compiles rule results AST into human-readable format
9
+ #
10
+ # @api private
11
+ class MessageCompiler
12
+ extend ::Dry::Initializer
13
+
14
+ resolve_key_predicate = proc { |node, opts|
15
+ *arg_vals, val = node.map(&:last)
16
+ [[*opts.path, arg_vals[0]], arg_vals[1..arg_vals.size - 1], val]
17
+ }
18
+
19
+ resolve_predicate = proc { |node, opts|
20
+ [Array(opts.path), *node.map(&:last)]
21
+ }
22
+
23
+ DEFAULT_PREDICATE_RESOLVERS = ::Hash
24
+ .new(resolve_predicate).update(key?: resolve_key_predicate).freeze
25
+
26
+ EMPTY_OPTS = VisitorOpts.new
27
+ EMPTY_MESSAGE_SET = MessageSet.new(EMPTY_ARRAY).freeze
28
+ FULL_MESSAGE_WHITESPACE = ::Hash.new(" ").merge(
29
+ ja: "",
30
+ zh: "",
31
+ bn: "",
32
+ th: "",
33
+ lo: "",
34
+ my: ""
35
+ )
36
+
37
+ param :messages
38
+
39
+ option :full, default: -> { false }
40
+ option :locale, default: -> { :en }
41
+ option :predicate_resolvers, default: -> { DEFAULT_PREDICATE_RESOLVERS }
42
+
43
+ attr_reader :options
44
+
45
+ attr_reader :default_lookup_options
46
+
47
+ # @api private
48
+ def initialize(messages, **options)
49
+ super
50
+ @options = options
51
+ @default_lookup_options = options[:locale] ? {locale: locale} : EMPTY_HASH
52
+ end
53
+
54
+ # @api private
55
+ def with(new_options)
56
+ return self if new_options.empty?
57
+
58
+ updated_opts = options.merge(new_options)
59
+
60
+ return self if updated_opts.eql?(options)
61
+
62
+ self.class.new(messages, **updated_opts)
63
+ end
64
+
65
+ # @api private
66
+ def call(ast)
67
+ return EMPTY_MESSAGE_SET if ast.empty?
68
+
69
+ current_messages = EMPTY_ARRAY.dup
70
+ compiled_messages = ast.map { |node| visit(node, EMPTY_OPTS.dup(current_messages)) }
71
+
72
+ MessageSet[compiled_messages, failures: options.fetch(:failures, true)]
73
+ end
74
+
75
+ # @api private
76
+ def visit(node, opts = EMPTY_OPTS.dup)
77
+ __send__(:"visit_#{node[0]}", node[1], opts)
78
+ end
79
+
80
+ # @api private
81
+ def visit_failure(node, opts)
82
+ rule, other = node
83
+ visit(other, opts.(rule: rule))
84
+ end
85
+
86
+ # @api private
87
+ def visit_hint(*)
88
+ nil
89
+ end
90
+
91
+ # @api private
92
+ def visit_not(node, opts)
93
+ visit(node, opts.(not: true))
94
+ end
95
+
96
+ # @api private
97
+ def visit_and(node, opts)
98
+ left, right = node.map { |n| visit(n, opts) }
99
+
100
+ if right
101
+ [left, right]
102
+ else
103
+ left
104
+ end
105
+ end
106
+
107
+ # @api private
108
+ def visit_unexpected_key(node, opts)
109
+ visit_predicate([:unexpected_key, []], opts.dup.update(path: Path[node.first]))
110
+ end
111
+
112
+ # @api private
113
+ def visit_or(node, opts)
114
+ left, right = node.map { |n| visit(n, opts) }
115
+ Message::Or[left, right, or_translator]
116
+ end
117
+
118
+ # @api private
119
+ def or_translator
120
+ @or_translator ||= proc { |k|
121
+ message = messages.translate(k, **default_lookup_options)
122
+ message.is_a?(Hash) ? message[:text] : message
123
+ }
124
+ end
125
+
126
+ # @api private
127
+ def visit_namespace(node, opts)
128
+ ns, rest = node
129
+ self.class.new(messages.namespaced(ns), **options).visit(rest, opts)
130
+ end
131
+
132
+ # @api private
133
+ def visit_predicate(node, opts)
134
+ predicate, args = node
135
+
136
+ tokens = message_tokens(args)
137
+ path, *arg_vals, input = predicate_resolvers[predicate].(args, opts)
138
+
139
+ options = opts.dup.update(
140
+ path: path.last, **tokens, **lookup_options(arg_vals: arg_vals, input: input)
141
+ ).to_h
142
+
143
+ template, meta = messages[predicate, options]
144
+
145
+ unless template
146
+ raise MissingMessageError.new(path, messages.looked_up_paths(predicate, options))
147
+ end
148
+
149
+ text = message_text(template, tokens, options)
150
+
151
+ message_type(options).new(
152
+ text: text,
153
+ meta: meta,
154
+ path: path,
155
+ predicate: predicate,
156
+ args: arg_vals,
157
+ input: input
158
+ )
159
+ end
160
+
161
+ # @api private
162
+ def message_type(*)
163
+ Message
164
+ end
165
+
166
+ # @api private
167
+ def visit_key(node, opts)
168
+ name, other = node
169
+ visit(other, opts.(path: name))
170
+ end
171
+
172
+ # @api private
173
+ def visit_set(node, opts)
174
+ node.map { |el| visit(el, opts) }
175
+ end
176
+
177
+ # @api private
178
+ def visit_implication(node, *args)
179
+ _, right = node
180
+ visit(right, *args)
181
+ end
182
+
183
+ # @api private
184
+ def visit_xor(node, opts)
185
+ left, right = node
186
+ [visit(left, opts), visit(right, opts)].uniq
187
+ end
188
+
189
+ # @api private
190
+ def lookup_options(arg_vals:, input:)
191
+ default_lookup_options.merge(
192
+ arg_type: arg_vals.size == 1 && arg_vals[0].class,
193
+ val_type: input.equal?(Undefined) ? ::NilClass : input.class
194
+ )
195
+ end
196
+
197
+ # @api private
198
+ def message_text(template, tokens, options)
199
+ text = template[template.data(tokens)]
200
+
201
+ return text if !text || !full
202
+
203
+ rule = options[:path]
204
+ [messages.rule(rule, options) || rule,
205
+ text].join(FULL_MESSAGE_WHITESPACE[template.options[:locale]])
206
+ end
207
+
208
+ # @api private
209
+ def message_tokens(args)
210
+ tokens = args.each_with_object({}) do |arg, hash|
211
+ case arg[1]
212
+ when ::Array, ::Set
213
+ hash[arg[0]] = arg[1].join(LIST_SEPARATOR)
214
+ when ::Range
215
+ hash[:"#{arg[0]}_left"] = arg[1].first
216
+ hash[:"#{arg[0]}_right"] = arg[1].last
217
+ else
218
+ hash[arg[0]] = arg[1]
219
+ end
220
+ end
221
+ args.any? { |e| e.first == :size } ? append_mapped_size_tokens(tokens) : tokens
222
+ end
223
+
224
+ # @api private
225
+ def append_mapped_size_tokens(tokens)
226
+ # this is a temporary fix for the inconsistency in the "size" errors arguments
227
+ mapped_hash = tokens.transform_keys do |k|
228
+ k.to_s.gsub("size", "num").to_sym
229
+ end
230
+ tokens.merge(mapped_hash)
231
+ end
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/equalizer"
4
+
5
+ module Dry
6
+ module Schema
7
+ # A set of messages used to generate errors
8
+ #
9
+ # @see Result#message_set
10
+ #
11
+ # @api public
12
+ class MessageSet
13
+ include ::Enumerable
14
+ include ::Dry::Equalizer(:messages, :options)
15
+
16
+ # A list of compiled message objects
17
+ #
18
+ # @return [Array<Message>]
19
+ attr_reader :messages
20
+
21
+ # Options hash
22
+ #
23
+ # @return [Hash]
24
+ attr_reader :options
25
+
26
+ # @api private
27
+ def self.[](messages, options = EMPTY_HASH)
28
+ new(messages.flatten, options)
29
+ end
30
+
31
+ # @api private
32
+ def initialize(messages, options = EMPTY_HASH)
33
+ @messages = messages
34
+ @options = options
35
+ end
36
+
37
+ # Iterate over messages
38
+ #
39
+ # @example
40
+ # result.errors.each do |message|
41
+ # puts message.text
42
+ # end
43
+ #
44
+ # @return [Array]
45
+ #
46
+ # @api public
47
+ def each(&)
48
+ return self if empty?
49
+ return to_enum unless block_given?
50
+
51
+ messages.each(&)
52
+ end
53
+
54
+ # Dump message set to a hash
55
+ #
56
+ # @return [Hash<Symbol=>Array<String>>]
57
+ #
58
+ # @api public
59
+ def to_h
60
+ @to_h ||= messages_map
61
+ end
62
+ alias_method :to_hash, :to_h
63
+
64
+ # Get a list of message texts for the given key
65
+ #
66
+ # @param [Symbol] key
67
+ #
68
+ # @return [Array<String>]
69
+ #
70
+ # @api public
71
+ def [](key)
72
+ to_h[key]
73
+ end
74
+
75
+ # Get a list of message texts for the given key
76
+ #
77
+ # @param [Symbol] key
78
+ #
79
+ # @return [Array<String>]
80
+ #
81
+ # @raise KeyError
82
+ #
83
+ # @api public
84
+ def fetch(key)
85
+ self[key] || raise(::KeyError, "+#{key}+ message was not found")
86
+ end
87
+
88
+ # Check if a message set is empty
89
+ #
90
+ # @return [Boolean]
91
+ #
92
+ # @api public
93
+ def empty?
94
+ @empty ||= messages.empty?
95
+ end
96
+
97
+ # @api private
98
+ def freeze
99
+ to_h
100
+ empty?
101
+ super
102
+ end
103
+
104
+ private
105
+
106
+ # @api private
107
+ def messages_map(messages = self.messages)
108
+ combine_message_hashes(messages.map(&:to_h)).freeze
109
+ end
110
+
111
+ # @api private
112
+ def combine_message_hashes(hashes)
113
+ hashes.reduce(EMPTY_HASH.dup) do |a, e|
114
+ a.merge(e) do |_, *values|
115
+ combine_message_values(values)
116
+ end
117
+ end
118
+ end
119
+
120
+ # @api private
121
+ def combine_message_values(values)
122
+ hashes, other = partition_message_values(values)
123
+ combined = combine_message_hashes(hashes)
124
+ flattened = other.flatten
125
+
126
+ if flattened.empty?
127
+ combined
128
+ elsif combined.empty?
129
+ flattened
130
+ else
131
+ [flattened, combined]
132
+ end
133
+ end
134
+
135
+ # @api private
136
+ def partition_message_values(values)
137
+ values
138
+ .map { |value| value.is_a?(::Array) ? value : [value] }
139
+ .reduce(EMPTY_ARRAY.dup, :+)
140
+ .partition { |value| value.is_a?(::Hash) && !value[:text].is_a?(::String) }
141
+ end
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,202 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "concurrent/map"
4
+
5
+ require "dry/schema/constants"
6
+
7
+ module Dry
8
+ module Schema
9
+ module Messages
10
+ # Abstract class for message backends
11
+ #
12
+ # @api public
13
+ class Abstract
14
+ include ::Dry::Configurable
15
+ include ::Dry::Equalizer(:config)
16
+
17
+ setting :default_locale
18
+ setting :load_paths, default: ::Set[DEFAULT_MESSAGES_PATH]
19
+ setting :top_namespace, default: DEFAULT_MESSAGES_ROOT
20
+ setting :root, default: "errors"
21
+ setting :lookup_options, default: %i[root predicate path val_type arg_type].freeze
22
+
23
+ setting :lookup_paths, default: [
24
+ "%<root>s.rules.%<path>s.%<predicate>s.arg.%<arg_type>s",
25
+ "%<root>s.rules.%<path>s.%<predicate>s",
26
+ "%<root>s.%<predicate>s.%<message_type>s",
27
+ "%<root>s.%<predicate>s.value.%<path>s",
28
+ "%<root>s.%<predicate>s.value.%<val_type>s.arg.%<arg_type>s",
29
+ "%<root>s.%<predicate>s.value.%<val_type>s",
30
+ "%<root>s.%<predicate>s.arg.%<arg_type>s",
31
+ "%<root>s.%<predicate>s"
32
+ ].freeze
33
+
34
+ setting :rule_lookup_paths, default: ["rules.%<name>s"].freeze
35
+
36
+ setting :arg_types, default: ::Hash.new { |*| "default" }.update(
37
+ ::Range => "range"
38
+ )
39
+
40
+ setting :val_types, default: ::Hash.new { |*| "default" }.update(
41
+ ::Range => "range",
42
+ ::String => "string"
43
+ )
44
+
45
+ # @api private
46
+ def self.setting_names
47
+ @setting_names ||= settings.map { _1.name.to_sym }
48
+ end
49
+
50
+ # @api private
51
+ def self.build(options = EMPTY_HASH)
52
+ messages = new
53
+
54
+ messages.configure do |config|
55
+ options.each do |key, value|
56
+ config.public_send(:"#{key}=", value)
57
+ end
58
+
59
+ config.root = "#{config.top_namespace}.#{config.root}"
60
+
61
+ config.rule_lookup_paths = config.rule_lookup_paths.map { |path|
62
+ "#{config.top_namespace}.#{path}"
63
+ }
64
+
65
+ yield(config) if block_given?
66
+ end
67
+
68
+ messages.prepare
69
+ end
70
+
71
+ # @api private
72
+ def translate(key, locale: default_locale)
73
+ t["#{config.top_namespace}.#{key}", locale: locale]
74
+ end
75
+
76
+ # @api private
77
+ def rule(name, options = {})
78
+ tokens = {name: name, locale: options.fetch(:locale, default_locale)}
79
+ path = rule_lookup_paths(tokens).detect { |key| key?(key, options) }
80
+
81
+ rule = get(path, options) if path
82
+ rule.is_a?(::Hash) ? rule[:text] : rule
83
+ end
84
+
85
+ # Retrieve a message template
86
+ #
87
+ # @return [Template]
88
+ #
89
+ # @api public
90
+ def call(predicate, options)
91
+ options = {locale: default_locale, **options}
92
+ opts = options.reject { |k,| config.lookup_options.include?(k) }
93
+ path = lookup_paths(predicate, options).detect { |key| key?(key, opts) }
94
+
95
+ return unless path
96
+
97
+ result = get(path, opts)
98
+
99
+ [
100
+ Template.new(
101
+ messages: self,
102
+ key: path,
103
+ options: opts
104
+ ),
105
+ result[:meta]
106
+ ]
107
+ end
108
+
109
+ alias_method :[], :call
110
+
111
+ # Check if given key is defined
112
+ #
113
+ # @return [Boolean]
114
+ #
115
+ # @api public
116
+ def key?(_key, _options = EMPTY_HASH)
117
+ raise ::NotImplementedError
118
+ end
119
+
120
+ # Retrieve an array of looked up paths
121
+ #
122
+ # @param [Symbol] predicate
123
+ # @param [Hash] options
124
+ #
125
+ # @return [String]
126
+ #
127
+ # @api public
128
+ def looked_up_paths(predicate, options)
129
+ tokens = lookup_tokens(predicate, options)
130
+ filled_lookup_paths(tokens)
131
+ end
132
+
133
+ # @api private
134
+ def lookup_paths(predicate, options)
135
+ tokens = lookup_tokens(predicate, options)
136
+ filled_lookup_paths(tokens)
137
+ end
138
+
139
+ # @api private
140
+ def filled_lookup_paths(tokens)
141
+ config.lookup_paths.map { |path| path % tokens }
142
+ end
143
+
144
+ # @api private
145
+ def rule_lookup_paths(tokens)
146
+ config.rule_lookup_paths.map { |key| key % tokens }
147
+ end
148
+
149
+ # Return a new message backend that will look for messages under provided namespace
150
+ #
151
+ # @param [Symbol,String] namespace
152
+ #
153
+ # @api public
154
+ def namespaced(namespace)
155
+ Dry::Schema::Messages::Namespaced.new(namespace, self)
156
+ end
157
+
158
+ # Return root path to messages file
159
+ #
160
+ # @return [Pathname]
161
+ #
162
+ # @api public
163
+ def root
164
+ config.root
165
+ end
166
+
167
+ # @api private
168
+ def default_locale
169
+ config.default_locale
170
+ end
171
+
172
+ # @api private
173
+ def interpolatable_data(_key, _options, **_data)
174
+ raise ::NotImplementedError
175
+ end
176
+
177
+ # @api private
178
+ def interpolate(_key, _options, **_data)
179
+ raise ::NotImplementedError
180
+ end
181
+
182
+ private
183
+
184
+ # @api private
185
+ def lookup_tokens(predicate, options)
186
+ options.merge(
187
+ predicate: predicate,
188
+ root: options[:not] ? "#{root}.not" : root,
189
+ arg_type: config.arg_types[options[:arg_type]],
190
+ val_type: config.val_types[options[:val_type]],
191
+ message_type: options[:message_type] || :failure
192
+ )
193
+ end
194
+
195
+ # @api private
196
+ def custom_top_namespace?(path)
197
+ path.to_s == DEFAULT_MESSAGES_PATH.to_s && config.top_namespace != DEFAULT_MESSAGES_ROOT
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "i18n"
5
+
6
+ module Dry
7
+ module Schema
8
+ module Messages
9
+ # I18n message backend
10
+ #
11
+ # @api public
12
+ class I18n < Abstract
13
+ # Translation function
14
+ #
15
+ # @return [Method]
16
+ attr_reader :t
17
+
18
+ # @api private
19
+ def initialize
20
+ super
21
+ @t = ::I18n.method(:t)
22
+ end
23
+
24
+ # Get a message for the given key and its options
25
+ #
26
+ # @param [Symbol] key
27
+ # @param [Hash] options
28
+ #
29
+ # @return [String]
30
+ #
31
+ # @api public
32
+ def get(key, options = EMPTY_HASH)
33
+ return unless key
34
+
35
+ result = t.(key, locale: default_locale, **options)
36
+
37
+ if result.is_a?(Hash)
38
+ text = result[:text]
39
+ meta = result.dup.tap { |h| h.delete(:text) }
40
+ else
41
+ text = result
42
+ meta = EMPTY_HASH.dup
43
+ end
44
+
45
+ {
46
+ text: text,
47
+ meta: meta
48
+ }
49
+ end
50
+
51
+ # Check if given key is defined
52
+ #
53
+ # @return [Boolean]
54
+ #
55
+ # @api public
56
+ def key?(key, options)
57
+ ::I18n.exists?(key, options.fetch(:locale, default_locale)) ||
58
+ ::I18n.exists?(key, ::I18n.default_locale)
59
+ end
60
+
61
+ # Merge messages from an additional path
62
+ #
63
+ # @param [String, Array<String>] paths
64
+ #
65
+ # @return [Messages::I18n]
66
+ #
67
+ # @api public
68
+ def merge(paths)
69
+ prepare(paths)
70
+ end
71
+
72
+ # @api private
73
+ def default_locale
74
+ super || ::I18n.locale || ::I18n.default_locale
75
+ end
76
+
77
+ # @api private
78
+ def prepare(paths = config.load_paths)
79
+ paths.each do |path|
80
+ data = ::YAML.load_file(path)
81
+
82
+ if custom_top_namespace?(path)
83
+ top_namespace = config.top_namespace
84
+
85
+ mapped_data = data.transform_values { |v|
86
+ {top_namespace => v[DEFAULT_MESSAGES_ROOT]}
87
+ }
88
+
89
+ store_translations(mapped_data)
90
+ else
91
+ store_translations(data)
92
+ end
93
+ end
94
+
95
+ self
96
+ end
97
+
98
+ # @api private
99
+ def interpolatable_data(_key, _options, **data)
100
+ data
101
+ end
102
+
103
+ # @api private
104
+ def interpolate(key, options, **data)
105
+ text_key = "#{key}.text"
106
+
107
+ opts = {
108
+ locale: default_locale,
109
+ **options,
110
+ **data
111
+ }
112
+
113
+ resolved_key = key?(text_key, opts) ? text_key : key
114
+
115
+ result = t.(resolved_key, **opts)
116
+
117
+ if result.is_a?(::Hash)
118
+ result[:text]
119
+ else
120
+ result
121
+ end
122
+ end
123
+
124
+ private
125
+
126
+ # @api private
127
+ def store_translations(data)
128
+ locales = data.keys.map(&:to_sym)
129
+
130
+ ::I18n.available_locales |= locales
131
+
132
+ locales.each do |locale|
133
+ ::I18n.backend.store_translations(locale, data[locale.to_s])
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end