Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

davestewart's avatar

Custom validation translations

I've rewritten this a few times as my understanding increases, but hopefully someone can help me out...

I have a requirement to set the validation translations source at run time in order to provide field-centric error messages, i.e. "This field..." rather than "The name field..."), as seen here.

I'm unable to replace the entire validations file, as this functionality is provided part of a package.

From my initial investigations, I can seem to set up most of this with the following code:

// input
$input = [];
$rules  = ['name' => 'required'];

/**  @var Translator $trans */
$trans  = App::make('translator');
$trans->load('crud', 'validation', 'en');

/** @var \Illuminate\Validation\Validator $valid */
$valid  = App::make('validator', [$trans])->make($input, $rules);

// sanity-check
pr($trans->trans('crud::validation.required'));

// messages
pr($valid->errors()->first('name'));

However, the validation result isn't as expected: the Validator instance spits out the default (*) messages, rather than my namespaced (crud) ones:

// what I want
This field is required.

// what I get
The name field is required.

Is there a way to force the validator to use the namespaced translation?

After hacking my round the Validator class, it seems like I may need to create a subclass, and override the getMessage()method, calling my custom Translator instance and passing in my namespaced key from there.

Is this correct?

Thanks, Dave

0 likes
7 replies
nhan's avatar

public function postAdd(Request $request) {

   $messages = [
                    'name.required'     => 'Please enter your name',
                    'email.required'    => 'Please enter your email',
                    'email.email'       => 'Please enter a valid email address',
                    'email.unique'      => 'Email has been register',
                    'password.required' => 'Please enter your password',
                    'password.same'     => 'Password and repassword not match',
                    'phone.required'    => 'Please enter your phone',
                    'phone.numeric'     => 'Please enter your phone number format',
                    'birthday.required' => 'Please enter your birthday',
                    'position.not_in'   =>'Please select position',
                    'level.not_in'      =>'Please select level',
                    'department.not_in' =>'Please select department'
                ];
    $validator       = Validator::make($request->all(), [
        'name'       => 'required',
        'email'      => 'required|email|unique:staff,email',
        'password'   => 'required|same:repassword',
        'phone'      => 'required|numeric',
        'birthday'   => 'required',
        'position'   =>'not_in:0',
        'level'      =>'not_in:0',
        'department' =>'not_in:0'
    ],$messages);

    if ($validator->fails()) {
        return redirect('staff/add')
                    ->withErrors($validator)
                    ->withInput();
    }else{
    //do some thing 
  }

}

davestewart's avatar

Hi @nhan,

Nice try, but wide of the mark I'm afraid.

What I need is a way to replace the entire validation translations file:

As mentioned, I've now achieved this by extending the Validator class, loading my own translations:

class CrudValidator extends Validator
{
    public function __construct(array $data, array $rules)
    {
        /**  @var Translator $trans */
        $trans  = \App::make('translator');
        $trans->load('crud', 'validation', App::getLocale());

        // parent
        parent::__construct($trans, $data, $rules);
    }

And overriding:

  • getMessage()
  • getSizeMessage()

Both now tack on the package namespace to return the correct translation:

$key = "crud::validation.{$lowerRule}";

I think this could also be achieved by passing a custom LoaderInterface into a Translator which would load the package [crud] translations into the default [*] translations namespace:

Illuminate\Translation\Translator Object
(
   ...
    [loaded:protected] => Array
        (
            [crud] => Array <-- WOULD SET THE [*] KEY INSTEAD
                (
                    [validation] => Array
                        (
                            [en] => Array
                                (
                                    [accepted] => This field must be accepted.
                                    [active_url] => This field is not a valid URL.

But that then begins to feel like a horrible hack.

Any more insight into this problem is welcome.

pmall's avatar

I think you should register a custom validator :

$this->app['validator']->resolver(function ($translator, $data, $rules, $messages, $attributes) {

    // set $translator or $messages or $attributes accordingly here (dont know exactly what to do here but those vars are injected in the validator here)

    return new Validator($translator, $data, $rules, $messages, $attributes);

});
davestewart's avatar

Thanks @pmall - yeah, I tried this before, and the validator throws an error when it comes to array properties of the format:

    "min" =>
    [
        "numeric"           => "This field must be at least :min.",
        "file"              => "This field must be at least :min kilobytes.",
        "string"            => "This field must be at least :min characters.",
        "array"             => "This field must have at least :min items.",
    ],

I tried converting these to dot.properties but no luck.

I'll try again though - I've been through this a dozen times now, and my understanding of the underlying architecture has improved dramatically as a result - maybe I'll hit upon the right formula.

pmall's avatar

Double check this because seems to me the cleanest way of doing what you want. Don't you get some info by dd($messages) ?

davestewart's avatar

I agree! The documentation says as much which why I was really surprised it errors.

OK, here's one of my test code functions:

public function messages3()
{
    // input
    $input      = ['name' => 'dave'];
    $rules      = ['name' => 'required|min:30'];
    $messages   = [
        "required"    => "This field is required.",
        "min"         => [
            "numeric" => "This field must be at least :min.",
            "file"    => "This field must be at least :min kilobytes.",
            "string"  => "This field must be at least :min characters.",
            "array"   => "This field must have at least :min items.",
        ],
    ];

    /** @var \Illuminate\Validation\Validator $valid */
    $valid  = \Validator::make($input, $rules, $messages);

    // errors
    pr($valid->errors()->first('name'));
}

And here's the error:

error

Clearly MessageBag does not like being handed an array.

  • Maybe this is where I should be concentrating my efforts
  • Think this could be a bug in how Validator handles "size" messages.

Will start looking into it, then as you say, I can just pass in custom messages and not have to muck about with everything I'm going above

davestewart's avatar
davestewart
OP
Best Answer
Level 11

OK, looks like a bug with "inline" (seems to be the vocab used internally for custom) validation messages.

Which can be fixed with the following:

    /**
     * Get the inline message for a rule if it exists.
     *
     * @param  string  $attribute
     * @param  string  $lowerRule
     * @param  array   $source
     * @return string
     */
    protected function getInlineMessage($attribute, $lowerRule, $source = null)
    {
        $source = $source ?: $this->customMessages;

        $keys = array("{$attribute}.{$lowerRule}", $lowerRule);

        // First we will check for a custom message for an attribute specific rule
        // message for the fields, then we will check for a general custom line
        // that is not attribute specific. If we find either we'll return it.
        foreach ($keys as $key)
        {
            if (isset($source[$key]))
            {
                $message = $source[$key];
                // FIX
                if(is_array($message))
                {
                    $type = $this->getAttributeType($attribute);
                    return $message[$type];
                }
                return $message;
            }
        }
    }

I'll test this out and submit a PR if it appears to work.

Thanks @pmall for prompting me to look again at custom validation messages :)

Please or to participate in this conversation.