Translating validation messages on client [proposal]

Hello! Consider the use-case when internationalization (including translating error messages) is done entirely on the client. This means that instead of returning the client a message string for each validation error we need to provide the information which can be used to construct these messages on the client. The essential information consists of the name of the failed predicate and an optional variable => value map for messages which require interpolation.

I have a proof-of-concept implementation here (disregard Messages::NoOp, naming is hard).

A “message” is a hash with following members:

  • :predicate — the name of a failed predicate (e.g., :size?);
  • :variables — a hash of interpolation variables with their values (e.g., { size_left: 4, size_right: 8 } for the :size? predicate when the argument type is Range);
  • :options — other information like what rule has failed or what are the types of arguments and values.

Example of generated errors:


schema = Dry::Validation.Schema do
  configure do
    config.messages = :no_op

    def palindrome?(value)
      value = value.downcase
      value == value.reverse
    end
  end

  required(:name).filled(:str?, :palindrome?, size?: 4..8)
end

schema.call(name: 3).errors
# => {:name=>[{:predicate=>:str?, :options=>{:path=>[:name], :rule=>:name, :message_type=>:failure, :locale=>:en, :arg_type=>"default", :val_type=>"default", :input=>3}, :variables=>{:input=>3}}]}

schema.call(name: 'ivi').errors
# => {:name=>
#  [{:predicate=>:size?,
#    :options=>{:path=>[:name], :rule=>:name, :message_type=>:failure, :locale=>:en, :arg_type=>"range",  :val_type=>"string", :size_left=>4, :size_right=>8, :input=>"ivi"},
#    :variables=>{:size_left=>4, :size_right=>8, :input=>"ivi"}}]}

schema.call(name: 'leya').errors
# => {:name=>[{:predicate=>:palindrome?, :options=>{:path=>[:name], :rule=>:name, :message_type=>:failure, :locale=>:en, :arg_type=>"default", :val_type=>"string", :value=>"leya"}, :variables=>{:value=>"leya"}}]}

Full messages get weird though:

schema.call(name: 'maya').errors(full: true)
# => {:name=>
#   ["name {:predicate=>:palindrome?, :options=>{:path=>[:name], :rule=>:name, :message_type=>:failure, :locale=>:en, :arg_type=>\"default\", :val_type=>\"string\", :value=>\"maya\"}, :variables=>{:value=>\"maya\"}}"]}

What do you think about it?


On an unrelated note, the contribution guidelines are not clear to me. PR guidelines say that a PR must address a previously reported issue, issue guidelines say that feature requests should be reported only after discussing it here. Does it mean that to create a PR I must create a corresponding topic on the discussion forum first, then (after discussing it) convert it to a Github issue, then create a PR?

This can be solved by introducing a new method to Messages::Abstract and using it in MessageCompiler#message_text, then Messages::NoOp can override it to do nothing.