haveapi 0.28.4 → 0.29.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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +139 -0
  3. data/Rakefile +1 -0
  4. data/haveapi.gemspec +2 -1
  5. data/lib/haveapi/action.rb +26 -9
  6. data/lib/haveapi/actions/default.rb +5 -2
  7. data/lib/haveapi/actions/paginable.rb +8 -4
  8. data/lib/haveapi/authentication/oauth2/provider.rb +1 -1
  9. data/lib/haveapi/authentication/token/config.rb +10 -3
  10. data/lib/haveapi/authentication/token/provider.rb +22 -21
  11. data/lib/haveapi/context.rb +4 -1
  12. data/lib/haveapi/i18n.rb +125 -0
  13. data/lib/haveapi/locales/cs.yml +167 -0
  14. data/lib/haveapi/locales/en.yml +168 -0
  15. data/lib/haveapi/metadata.rb +25 -3
  16. data/lib/haveapi/model_adapters/active_record.rb +40 -26
  17. data/lib/haveapi/output_formatter.rb +2 -2
  18. data/lib/haveapi/parameters/metadata_i18n.rb +179 -0
  19. data/lib/haveapi/parameters/resource.rb +18 -7
  20. data/lib/haveapi/parameters/typed.rb +27 -20
  21. data/lib/haveapi/params.rb +76 -7
  22. data/lib/haveapi/resource.rb +1 -1
  23. data/lib/haveapi/resources/action_state.rb +47 -27
  24. data/lib/haveapi/server.rb +156 -16
  25. data/lib/haveapi/spec/api_builder.rb +25 -0
  26. data/lib/haveapi/spec/spec_methods.rb +10 -0
  27. data/lib/haveapi/tasks/i18n.rb +198 -0
  28. data/lib/haveapi/validator_chain.rb +1 -1
  29. data/lib/haveapi/validators/acceptance.rb +5 -2
  30. data/lib/haveapi/validators/confirmation.rb +5 -2
  31. data/lib/haveapi/validators/exclusion.rb +2 -2
  32. data/lib/haveapi/validators/format.rb +5 -2
  33. data/lib/haveapi/validators/inclusion.rb +2 -2
  34. data/lib/haveapi/validators/length.rb +5 -5
  35. data/lib/haveapi/validators/numericality.rb +20 -22
  36. data/lib/haveapi/validators/presence.rb +4 -2
  37. data/lib/haveapi/version.rb +1 -1
  38. data/lib/haveapi.rb +1 -0
  39. data/spec/authentication/oauth2_spec.rb +10 -0
  40. data/spec/i18n_spec.rb +520 -0
  41. data/spec/params_spec.rb +183 -0
  42. metadata +29 -3
@@ -6,9 +6,10 @@ require 'haveapi/hooks'
6
6
 
7
7
  module HaveAPI
8
8
  class Server
9
- attr_accessor :default_version, :action_state, :validation_error_http_status
9
+ attr_accessor :default_version, :action_state, :validation_error_http_status,
10
+ :default_locale, :parameter_i18n_scope
10
11
  attr_reader :root, :routes, :module_name, :auth_chain, :versions, :extensions,
11
- :action_state_auth
12
+ :action_state_auth, :locale_header, :available_locales
12
13
 
13
14
  include Hookable
14
15
 
@@ -57,6 +58,50 @@ module HaveAPI
57
58
  }
58
59
 
59
60
  module ServerHelpers
61
+ def setup_request_locale
62
+ return if @haveapi_locale_setup
63
+
64
+ server = settings.api_server
65
+ @haveapi_previous_locale = ::I18n.locale
66
+ locale_header_value = server.locale_header_value(request)
67
+ @haveapi_locale_requested = !locale_header_value.nil?
68
+ @haveapi_explicit_locale = server.request_locale(request)
69
+ @haveapi_locale = if @haveapi_locale_requested
70
+ @haveapi_explicit_locale || server.default_locale
71
+ else
72
+ server.resolved_locale(
73
+ request:,
74
+ current_user: nil
75
+ )
76
+ end
77
+ server.activate_locale(@haveapi_locale)
78
+ add_vary_header(server.locale_header) if server.locale_header
79
+ @haveapi_locale_setup = true
80
+ end
81
+
82
+ def resolve_request_locale(user = current_user)
83
+ setup_request_locale
84
+ return @haveapi_locale if @haveapi_locale_requested
85
+
86
+ @haveapi_locale = settings.api_server.resolved_locale(
87
+ request:,
88
+ current_user: user
89
+ )
90
+ settings.api_server.activate_locale(@haveapi_locale)
91
+ end
92
+
93
+ def restore_request_locale
94
+ return unless @haveapi_locale_setup
95
+
96
+ settings.api_server.activate_locale(@haveapi_previous_locale)
97
+ end
98
+
99
+ def add_vary_header(name)
100
+ values = response['Vary'].to_s.split(/\s*,\s*/).reject(&:empty?)
101
+ values << name unless values.include?(name)
102
+ headers 'Vary' => values.join(', ')
103
+ end
104
+
60
105
  def setup_formatter
61
106
  return if @formatter
62
107
 
@@ -64,7 +109,7 @@ module HaveAPI
64
109
  accept = request.accept
65
110
  rescue ArgumentError, EncodingError
66
111
  @formatter.supports?([])
67
- report_error(400, {}, 'Bad Accept header')
112
+ report_error(400, {}, HaveAPI.message('haveapi.errors.bad_accept_header'))
68
113
  else
69
114
  unless @formatter.supports?(accept)
70
115
  @halted = true
@@ -79,7 +124,10 @@ module HaveAPI
79
124
  end
80
125
 
81
126
  def authenticated?(v)
82
- return @current_user if @current_user
127
+ if @current_user
128
+ resolve_request_locale
129
+ return @current_user
130
+ end
83
131
 
84
132
  begin
85
133
  @current_user = settings.api_server.send(:do_authenticate, v, request)
@@ -92,12 +140,13 @@ module HaveAPI
92
140
  report_error(400, {}, e.message)
93
141
  end
94
142
  settings.api_server.call_hooks_for(:post_authenticated, args: [@current_user])
143
+ resolve_request_locale
95
144
  @current_user
96
145
  end
97
146
 
98
147
  def authenticated_versions
99
- settings.api_server.versions.each_with_object({}) do |v, ret|
100
- ret[v] = settings.api_server.send(:do_authenticate, v, request)
148
+ ret = settings.api_server.versions.each_with_object({}) do |v, users|
149
+ users[v] = settings.api_server.send(:do_authenticate, v, request)
101
150
  rescue HaveAPI::Authentication::TokenConflict => e
102
151
  unless @formatter
103
152
  @formatter = OutputFormatter.new
@@ -106,6 +155,9 @@ module HaveAPI
106
155
 
107
156
  report_error(400, {}, e.message)
108
157
  end
158
+
159
+ resolve_request_locale(ret[settings.api_server.default_version] || ret.values.compact.first)
160
+ ret
109
161
  end
110
162
 
111
163
  def access_control
@@ -133,7 +185,7 @@ module HaveAPI
133
185
  report_error(
134
186
  401,
135
187
  { 'www-authenticate' => 'Basic realm="Restricted Area"' },
136
- 'Action requires user to authenticate'
188
+ HaveAPI.message('haveapi.authentication.required')
137
189
  )
138
190
  end
139
191
 
@@ -170,7 +222,7 @@ module HaveAPI
170
222
  report_error(
171
223
  tmp[:http_status] || 500,
172
224
  {},
173
- tmp[:message] || 'Server error occurred'
225
+ tmp[:message] || HaveAPI.message('haveapi.errors.server_error')
174
226
  )
175
227
  end
176
228
 
@@ -260,6 +312,62 @@ module HaveAPI
260
312
  @extensions = []
261
313
  @action_state_auth = :backend
262
314
  @validation_error_http_status = nil
315
+ @default_locale = :en
316
+ self.available_locales = HaveAPI::I18n.available_locales
317
+ self.locale_header = 'Accept-Language'
318
+ end
319
+
320
+ def available_locales=(locales)
321
+ @available_locales = Array(locales)
322
+ allow_i18n_locales(@available_locales)
323
+ end
324
+
325
+ def locale_header=(header)
326
+ @locale_header = header
327
+ allow_header(header) if header
328
+ end
329
+
330
+ def activate_locale(locale)
331
+ allow_i18n_locales([locale])
332
+ ::I18n.locale = locale
333
+ end
334
+
335
+ def allow_i18n_locales(locales)
336
+ requested = Array(locales).compact.map { |locale| locale.to_s.to_sym }
337
+ current = ::I18n.available_locales
338
+ missing = requested.reject do |locale|
339
+ current.any? { |available| available.to_s == locale.to_s }
340
+ end
341
+
342
+ ::I18n.available_locales = current + missing unless missing.empty?
343
+ end
344
+
345
+ def locale(&block)
346
+ @locale_resolver = block if block
347
+ @locale_resolver
348
+ end
349
+
350
+ def request_locale(request)
351
+ HaveAPI::I18n.accept_language(
352
+ locale_header_value(request),
353
+ available_locales
354
+ )
355
+ end
356
+
357
+ def locale_header_value(request)
358
+ request.env["HTTP_#{locale_header.to_s.upcase.tr('-', '_')}"]
359
+ end
360
+
361
+ def resolved_locale(request:, current_user:)
362
+ locale = if @locale_resolver
363
+ @locale_resolver.call(
364
+ request:,
365
+ current_user:,
366
+ default_locale:
367
+ )
368
+ end
369
+
370
+ HaveAPI::I18n.normalize_locale(locale, available_locales) || default_locale
263
371
  end
264
372
 
265
373
  def action_state_auth=(mode)
@@ -321,6 +429,8 @@ module HaveAPI
321
429
  helpers DocHelpers
322
430
 
323
431
  before do
432
+ setup_request_locale
433
+
324
434
  if request.env['HTTP_ORIGIN']
325
435
  headers 'access-control-allow-origin' => '*',
326
436
  'access-control-allow-credentials' => 'false'
@@ -329,7 +439,7 @@ module HaveAPI
329
439
 
330
440
  not_found do
331
441
  setup_formatter
332
- report_error(404, {}, 'Action not found') unless @halted
442
+ report_error(404, {}, HaveAPI.message('haveapi.errors.action_not_found')) unless @halted
333
443
  end
334
444
 
335
445
  error do
@@ -337,6 +447,8 @@ module HaveAPI
337
447
  end
338
448
 
339
449
  after do
450
+ restore_request_locale
451
+
340
452
  if Object.const_defined?(:ActiveRecord)
341
453
  ActiveRecord::Base.connection_handler.clear_active_connections!
342
454
  end
@@ -534,6 +646,27 @@ module HaveAPI
534
646
  end
535
647
  end
536
648
 
649
+ def collect_parameter_metadata_i18n_items(resources, context)
650
+ resources.flat_map do |resource, children|
651
+ original_resource_path = context.resource_path
652
+ context.resource_path = context.resource_path + [resource.resource_name.underscore]
653
+
654
+ action_items = children[:actions].flat_map do |action, path|
655
+ context.action = action
656
+ context.path = path
657
+
658
+ action.parameter_metadata_i18n_items(context)
659
+ end
660
+
661
+ child_items = collect_parameter_metadata_i18n_items(children[:resources], context)
662
+
663
+ action_items + child_items
664
+ ensure
665
+ context.resource_path = original_resource_path
666
+ end
667
+ end
668
+ private :collect_parameter_metadata_i18n_items
669
+
537
670
  def mount_resource(prefix, v, resource, hash)
538
671
  hash[resource] = { resources: {}, actions: {} }
539
672
 
@@ -584,17 +717,17 @@ module HaveAPI
584
717
  body_method = !%i[get head options].include?(route.http_method.to_sym)
585
718
 
586
719
  if body_method && !raw_body.empty? && !settings.api_server.send(:json_content_type?, request)
587
- report_error(415, {}, 'Unsupported Content-Type')
720
+ report_error(415, {}, HaveAPI.message('haveapi.errors.unsupported_content_type'))
588
721
  end
589
722
 
590
723
  begin
591
724
  body = raw_body.empty? ? nil : JSON.parse(raw_body)
592
725
  rescue JSON::ParserError
593
- report_error(400, {}, 'Bad JSON syntax')
726
+ report_error(400, {}, HaveAPI.message('haveapi.errors.bad_json_syntax'))
594
727
  end
595
728
 
596
729
  if !raw_body.empty? && !body.is_a?(Hash)
597
- report_error(400, {}, 'JSON body must be an object')
730
+ report_error(400, {}, HaveAPI.message('haveapi.errors.json_body_object'))
598
731
  end
599
732
 
600
733
  action_params = settings.api_server.send(:path_params, route, params)
@@ -616,7 +749,7 @@ module HaveAPI
616
749
  action = route.action.new(request, v, action_params, action_input, context)
617
750
 
618
751
  unless action.authorized?(current_user)
619
- report_error(403, {}, 'Access denied. Insufficient permissions.')
752
+ report_error(403, {}, HaveAPI.message('haveapi.authorization.insufficient_permissions'))
620
753
  end
621
754
 
622
755
  status, reply, errors, http_status = action.safe_exec
@@ -671,16 +804,16 @@ module HaveAPI
671
804
  desc = route.action.describe(ctx)
672
805
 
673
806
  unless desc
674
- report_error(403, {}, 'Access denied. Insufficient permissions.')
807
+ report_error(403, {}, HaveAPI.message('haveapi.authorization.insufficient_permissions'))
675
808
  end
676
809
  rescue ValidationError => e
677
- report_error(400, e.to_hash, e.message)
810
+ report_error(400, e.to_hash, e.message_value)
678
811
  rescue StandardError => e
679
812
  tmp = settings.api_server.call_hooks_for(:description_exception, args: [ctx, e])
680
813
  report_error(
681
814
  tmp[:http_status] || 500,
682
815
  {},
683
- tmp[:message] || 'Server error occured'
816
+ tmp[:message] || HaveAPI.message('haveapi.errors.server_error')
684
817
  )
685
818
  end
686
819
 
@@ -742,6 +875,13 @@ module HaveAPI
742
875
  r.describe(hash, context)
743
876
  end
744
877
 
878
+ def parameter_metadata_i18n_items(version: @default_version)
879
+ routes = @routes.fetch(version)
880
+ context = Context.new(self, version:, doc: true)
881
+
882
+ collect_parameter_metadata_i18n_items(routes[:resources], context)
883
+ end
884
+
745
885
  def path_for_action(version, action)
746
886
  routes = @routes && @routes[version]
747
887
  return unless routes
@@ -64,6 +64,31 @@ module HaveAPI::Spec
64
64
  opt(:validation_error_http_status, status)
65
65
  end
66
66
 
67
+ # Set default response locale.
68
+ def default_locale(locale)
69
+ opt(:default_locale, locale)
70
+ end
71
+
72
+ # Set locales available to the API server.
73
+ def available_locales(locales)
74
+ opt(:available_locales, locales)
75
+ end
76
+
77
+ # Set request header used to negotiate the response locale.
78
+ def locale_header(header)
79
+ opt(:locale_header, header)
80
+ end
81
+
82
+ # Set custom locale resolver.
83
+ def locale(&block)
84
+ opt(:locale, block)
85
+ end
86
+
87
+ # Set the translation scope for action parameter labels/descriptions.
88
+ def parameter_i18n_scope(scope)
89
+ opt(:parameter_i18n_scope, scope)
90
+ end
91
+
67
92
  # Set a custom mount path.
68
93
  def mount_to(path)
69
94
  opt(:mount, path)
@@ -19,6 +19,16 @@ module HaveAPI::Spec
19
19
  @api.action_state_auth = asa if asa
20
20
  ves = get_opt(:validation_error_http_status)
21
21
  @api.validation_error_http_status = ves if ves
22
+ dl = get_opt(:default_locale)
23
+ @api.default_locale = dl if dl
24
+ al = get_opt(:available_locales)
25
+ @api.available_locales = al if al
26
+ lh = get_opt(:locale_header)
27
+ @api.locale_header = lh if lh
28
+ locale = get_opt(:locale)
29
+ @api.locale(&locale) if locale
30
+ parameter_i18n_scope = get_opt(:parameter_i18n_scope)
31
+ @api.parameter_i18n_scope = parameter_i18n_scope if parameter_i18n_scope
22
32
  @api.mount(get_opt(:mount) || '/')
23
33
  @api.app
24
34
  end
@@ -0,0 +1,198 @@
1
+ require 'yaml'
2
+
3
+ module HaveAPI
4
+ module Tasks
5
+ class I18nHealth
6
+ KEY_LITERAL_PATTERN = /['"](haveapi\.[a-z0-9_.]+)['"]/
7
+
8
+ RAW_MESSAGE_PATTERNS = [
9
+ /report_error\([^#\n]*,\s*['"]/,
10
+ /error!\(\s*['"]/,
11
+ /HaveAPI::ValidationError(?:\.new)?\(\s*['"]/,
12
+ /raise\s+HaveAPI::ValidationError,\s*['"]/,
13
+ /ret\[:message\]\s*=\s*['"]/,
14
+ /\|\|\s*['"][^'"]+(?:failed|error|denied|not found|invalid|requires|unsupported|missing)/,
15
+ /@message\s*=\s*take\(\s*:message\s*,\s*['"]/
16
+ ].freeze
17
+
18
+ PARAMETER_METHODS = %w[
19
+ bool custom datetime float id integer password resource string text
20
+ ].freeze
21
+ PARAMETER_CALL_PATTERN = /\A\s*(?:#{PARAMETER_METHODS.join('|')})\b/
22
+ RAW_PARAMETER_METADATA_PATTERN = /\b(?:label|desc):\s*['"]/
23
+
24
+ def initialize(root:)
25
+ @root = root
26
+ end
27
+
28
+ def check!
29
+ errors = []
30
+ errors.concat(missing_key_errors)
31
+ errors.concat(unused_key_errors)
32
+ errors.concat(raw_message_errors)
33
+
34
+ return true if errors.empty?
35
+
36
+ raise "i18n health check failed:\n#{errors.join("\n")}"
37
+ end
38
+
39
+ def normalize!
40
+ locale_data.each do |locale, data|
41
+ file = File.join(locale_dir, "#{locale}.yml")
42
+ File.write(file, YAML.dump(locale.to_s => deep_sort(data)))
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def missing_key_errors
49
+ reference_locale = :en
50
+ reference_keys = locale_keys.fetch(reference_locale)
51
+
52
+ locale_keys.flat_map do |locale, keys|
53
+ next [] if locale == reference_locale
54
+
55
+ (reference_keys - keys).sort.map do |key|
56
+ "#{locale}: missing #{key}"
57
+ end
58
+ end
59
+ end
60
+
61
+ def unused_key_errors
62
+ keys = locale_keys.fetch(:en)
63
+ used = used_keys
64
+
65
+ (keys - used).sort.map { |key| "unused translation key #{key}" }
66
+ end
67
+
68
+ def raw_message_errors
69
+ source_files.flat_map do |file|
70
+ rel = relative_path(file)
71
+
72
+ File.readlines(file, chomp: true).each_with_index.filter_map do |line, index|
73
+ stripped = line.strip
74
+ next if stripped.empty? || stripped.start_with?('#')
75
+
76
+ if RAW_MESSAGE_PATTERNS.any? { |pattern| pattern.match?(line) }
77
+ "#{rel}:#{index + 1}: raw user-facing framework message"
78
+ end
79
+ end
80
+ end + raw_parameter_metadata_errors
81
+ end
82
+
83
+ def raw_parameter_metadata_errors
84
+ source_files.flat_map do |file|
85
+ rel = relative_path(file)
86
+ inside_parameter = false
87
+ remaining_lines = 0
88
+
89
+ File.readlines(file, chomp: true).each_with_index.filter_map do |line, index|
90
+ stripped = line.strip
91
+ next if stripped.empty? || stripped.start_with?('#')
92
+
93
+ if PARAMETER_CALL_PATTERN.match?(stripped)
94
+ inside_parameter = true
95
+ remaining_lines = 12
96
+ end
97
+
98
+ if inside_parameter && RAW_PARAMETER_METADATA_PATTERN.match?(line)
99
+ "#{rel}:#{index + 1}: raw action parameter label/description"
100
+ end
101
+ ensure
102
+ if inside_parameter
103
+ remaining_lines -= 1
104
+ inside_parameter = false if remaining_lines <= 0 || parameter_call_end?(stripped)
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ def parameter_call_end?(line)
111
+ return false if line.nil?
112
+
113
+ stripped = line.strip
114
+ return false if stripped.empty?
115
+ return false if stripped.end_with?(',', '\\')
116
+
117
+ true
118
+ end
119
+
120
+ def used_keys
121
+ @used_keys ||= source_files.each_with_object(Set.new) do |file, ret|
122
+ File.read(file).scan(KEY_LITERAL_PATTERN) do |match|
123
+ ret << match.first
124
+ end
125
+ end
126
+ end
127
+
128
+ def locale_keys
129
+ @locale_keys ||= locale_data.transform_values do |data|
130
+ flatten_keys(data)
131
+ end
132
+ end
133
+
134
+ def locale_data
135
+ @locale_data ||= Dir[File.join(locale_dir, '*.yml')].to_h do |file|
136
+ locale = File.basename(file, '.yml').to_sym
137
+ data = YAML.safe_load_file(file, aliases: true) || {}
138
+
139
+ [locale, data.fetch(locale.to_s)]
140
+ end
141
+ end
142
+
143
+ def flatten_keys(hash, prefix = nil)
144
+ hash.each_with_object(Set.new) do |(key, value), ret|
145
+ name = [prefix, key].compact.join('.')
146
+
147
+ if value.is_a?(Hash)
148
+ ret.merge(flatten_keys(value, name))
149
+ else
150
+ ret << name
151
+ end
152
+ end
153
+ end
154
+
155
+ def deep_sort(value)
156
+ case value
157
+ when Hash
158
+ value.keys.sort.to_h do |key|
159
+ [key, deep_sort(value[key])]
160
+ end
161
+ else
162
+ value
163
+ end
164
+ end
165
+
166
+ def source_files
167
+ @source_files ||= Dir[File.join(@root, 'lib/haveapi/**/*.rb')].reject do |file|
168
+ rel = relative_path(file)
169
+ rel.start_with?('lib/haveapi/public/') ||
170
+ rel.start_with?('lib/haveapi/views/') ||
171
+ rel.start_with?('lib/haveapi/client_examples/') ||
172
+ rel.start_with?('lib/haveapi/spec/') ||
173
+ rel.start_with?('lib/haveapi/tasks/i18n.rb')
174
+ end
175
+ end
176
+
177
+ def relative_path(file)
178
+ file.delete_prefix("#{@root}/")
179
+ end
180
+
181
+ def locale_dir
182
+ File.join(@root, 'lib/haveapi/locales')
183
+ end
184
+ end
185
+ end
186
+ end
187
+
188
+ namespace :i18n do
189
+ desc 'Check HaveAPI translation coverage and raw framework messages'
190
+ task :health do
191
+ HaveAPI::Tasks::I18nHealth.new(root: File.expand_path('../../..', __dir__)).check!
192
+ end
193
+
194
+ desc 'Normalize HaveAPI locale files'
195
+ task :normalize do
196
+ HaveAPI::Tasks::I18nHealth.new(root: File.expand_path('../../..', __dir__)).normalize!
197
+ end
198
+ end
@@ -72,7 +72,7 @@ module HaveAPI
72
72
  validator = validator.clone
73
73
  next if validator.validate(value, params)
74
74
 
75
- ret << format(validator.message, value:)
75
+ ret << HaveAPI.localize(validator.message, value:)
76
76
  end
77
77
 
78
78
  ret.empty? ? true : ret
@@ -23,13 +23,16 @@ module HaveAPI
23
23
  take(:value)
24
24
  end
25
25
 
26
- @message = take(:message, "has to be #{@value}")
26
+ @message = take(
27
+ :message,
28
+ HaveAPI.message('haveapi.validators.acceptance.accepted', value: @value)
29
+ )
27
30
  end
28
31
 
29
32
  def describe
30
33
  {
31
34
  value: @value,
32
- message: @message
35
+ message: HaveAPI.localize(@message)
33
36
  }
34
37
  end
35
38
 
@@ -24,7 +24,10 @@ module HaveAPI
24
24
  @equal = take(:equal, true)
25
25
  @message = take(
26
26
  :message,
27
- @equal ? "must be the same as #{@param}" : "must be different from #{@param}"
27
+ HaveAPI.message(
28
+ @equal ? 'haveapi.validators.confirmation.same' : 'haveapi.validators.confirmation.different',
29
+ parameter: @param
30
+ )
28
31
  )
29
32
  end
30
33
 
@@ -32,7 +35,7 @@ module HaveAPI
32
35
  {
33
36
  equal: @equal ? true : false,
34
37
  parameter: @param,
35
- message: @message
38
+ message: HaveAPI.localize(@message)
36
39
  }
37
40
  end
38
41
 
@@ -23,13 +23,13 @@ module HaveAPI
23
23
  v.is_a?(::Symbol) ? v.to_s : v
24
24
  end
25
25
 
26
- @message = take(:message, '%{value} cannot be used')
26
+ @message = take(:message, HaveAPI.message('haveapi.validators.exclusion.excluded'))
27
27
  end
28
28
 
29
29
  def describe
30
30
  {
31
31
  values: @values,
32
- message: @message
32
+ message: HaveAPI.localize(@message)
33
33
  }
34
34
  end
35
35
 
@@ -20,7 +20,10 @@ module HaveAPI
20
20
  @rx = simple? ? take : take(:rx)
21
21
  @match = take(:match, true)
22
22
  @desc = take(:desc)
23
- @message = take(:message, @desc || '%{value} is not in a valid format')
23
+ @message = take(
24
+ :message,
25
+ @desc || HaveAPI.message('haveapi.validators.format.invalid')
26
+ )
24
27
  end
25
28
 
26
29
  def describe
@@ -28,7 +31,7 @@ module HaveAPI
28
31
  rx: @rx.source,
29
32
  match: @match,
30
33
  description: @desc,
31
- message: @message
34
+ message: HaveAPI.localize(@message)
32
35
  }
33
36
  end
34
37
 
@@ -31,13 +31,13 @@ module HaveAPI
31
31
  @values = values.map { |v| v.is_a?(::Symbol) ? v.to_s : v }
32
32
  end
33
33
 
34
- @message = take(:message, '%{value} cannot be used')
34
+ @message = take(:message, HaveAPI.message('haveapi.validators.inclusion.included'))
35
35
  end
36
36
 
37
37
  def describe
38
38
  {
39
39
  values: @values,
40
- message: @message
40
+ message: HaveAPI.localize(@message)
41
41
  }
42
42
  end
43
43
 
@@ -30,16 +30,16 @@ module HaveAPI
30
30
  end
31
31
 
32
32
  msg = if @equals
33
- "length has to be #{@equals}"
33
+ HaveAPI.message('haveapi.validators.length.equals', equals: @equals)
34
34
 
35
35
  elsif @min && !@max
36
- "length has to be minimally #{@min}"
36
+ HaveAPI.message('haveapi.validators.length.min', min: @min)
37
37
 
38
38
  elsif !@min && @max
39
- "length has to be maximally #{@max}"
39
+ HaveAPI.message('haveapi.validators.length.max', max: @max)
40
40
 
41
41
  else
42
- "length has to be in range <#{@min}, #{@max}>"
42
+ HaveAPI.message('haveapi.validators.length.range', min: @min, max: @max)
43
43
  end
44
44
 
45
45
  @message = take(:message, msg)
@@ -47,7 +47,7 @@ module HaveAPI
47
47
 
48
48
  def describe
49
49
  ret = {
50
- message: @message
50
+ message: HaveAPI.localize(@message)
51
51
  }
52
52
 
53
53
  if @equals