How to Translate Validation Constraint Messages
===============================================

The validation constraints used in forms can translate their error messages by
creating a translation resource for the ``validators``
:ref:`translation domain <translation-resource-locations>`.

First of all, install the Symfony translation component (if it's not already
installed in your application) running the following command:

.. code-block:: terminal

    $ composer require symfony/translation

Suppose you've created a plain-old-PHP object that you need to use somewhere in
your application::

    // src/Entity/Author.php
    namespace App\Entity;

    class Author
    {
        public string $name;
    }

Add constraints through any of the supported methods. Set the message option
to the translation source text. For example, to guarantee that the ``$name``
property is not empty, add the following:

.. configuration-block::

    .. code-block:: php-attributes

        // src/Entity/Author.php
        namespace App\Entity;

        use Symfony\Component\Validator\Constraints as Assert;

        class Author
        {
            #[Assert\NotBlank(message: 'author.name.not_blank')]
            public string $name;
        }

    .. code-block:: yaml

        # config/validator/validation.yaml
        App\Entity\Author:
            properties:
                name:
                    - NotBlank: { message: 'author.name.not_blank' }

    .. code-block:: xml

        <!-- config/validator/validation.xml -->
        <?xml version="1.0" encoding="UTF-8" ?>
        <constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping
                https://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">

            <class name="App\Entity\Author">
                <property name="name">
                    <constraint name="NotBlank">
                        <option name="message">author.name.not_blank</option>
                    </constraint>
                </property>
            </class>
        </constraint-mapping>

    .. code-block:: php

        // src/Entity/Author.php
        namespace App\Entity;

        // ...
        use Symfony\Component\Validator\Constraints\NotBlank;
        use Symfony\Component\Validator\Mapping\ClassMetadata;

        class Author
        {
            public string $name;

            public static function loadValidatorMetadata(ClassMetadata $metadata): void
            {
                $metadata->addPropertyConstraint('name', new NotBlank([
                    'message' => 'author.name.not_blank',
                ]));
            }
        }

Now, create a ``validators`` catalog file in the ``translations/`` directory:

.. configuration-block::

    .. code-block:: xml

        <!-- translations/validators/validators.en.xlf -->
        <?xml version="1.0" encoding="UTF-8" ?>
        <xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
            <file source-language="en" datatype="plaintext" original="file.ext">
                <body>
                    <trans-unit id="author.name.not_blank">
                        <source>author.name.not_blank</source>
                        <target>Please enter an author name.</target>
                    </trans-unit>
                </body>
            </file>
        </xliff>

    .. code-block:: yaml

        # translations/validators/validators.en.yaml
        author.name.not_blank: Please enter an author name.

    .. code-block:: php

        // translations/validators/validators.en.php
        return [
            'author.name.not_blank' => 'Please enter an author name.',
        ];

You may need to clear your cache (even in the dev environment) after creating
this file for the first time.

.. tip::

    Symfony will also create translation files for the built-in validation messages.
    You can optionally set the :ref:`enabled_locales <reference-translator-enabled-locales>`
    option to restrict the available locales in your application. This will improve
    performance a bit because Symfony will only generate the translation files
    for those locales instead of all of them.

You can also use :class:`Symfony\\Component\\Translation\\TranslatableMessage` to build your violation message::

    use Symfony\Component\Translation\TranslatableMessage;
    use Symfony\Component\Validator\Constraints as Assert;
    use Symfony\Component\Validator\Context\ExecutionContextInterface;
    
    #[Assert\Callback]
    public function validate(ExecutionContextInterface $context, mixed $payload): void
    {
        // somehow you have an array of "fake names"
        $fakeNames = [/* ... */];
    
        // check if the name is actually a fake name
        if (in_array($this->getFirstName(), $fakeNames, true)) {
            $context->buildViolation(new TranslatableMessage('author.name.fake', [], 'validators'))
                ->atPath('firstName')
                ->addViolation()
            ;
        }
    }

You can learn more about translatable messages in :ref:`the dedicated section <translatable-objects>`.

Custom Translation Domain
-------------------------

The default translation domain can be changed globally using the
``FrameworkBundle`` configuration:

.. configuration-block::

    .. code-block:: yaml

        # config/packages/validator.yaml
        framework:
            validation:
                translation_domain: validation_errors

    .. code-block:: xml

        <!-- config/packages/validator.xml -->
        <?xml version="1.0" encoding="UTF-8" ?>
        <container xmlns="http://symfony.com/schema/dic/services"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:framework="http://symfony.com/schema/dic/symfony"
            xsi:schemaLocation="http://symfony.com/schema/dic/services
                https://symfony.com/schema/dic/services/services-1.0.xsd
                http://symfony.com/schema/dic/symfony
                https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">

            <framework:config>
                <framework:validation
                    translation-domain="validation_errors"
                />
            </framework:config>
        </container>

    .. code-block:: php

        // config/packages/validator.php
        use Symfony\Config\FrameworkConfig;

        return static function (FrameworkConfig $framework) {
            // ...
            $framework
                ->validation()
                    ->translationDomain('validation_errors')
            ;
        };

Or it can be customized for a specific violation from a constraint validator::

    public function validate($value, Constraint $constraint): void
    {
        // validation logic

        $this->context->buildViolation($constraint->message)
            ->setParameter('{{ string }}', $value)
            ->setTranslationDomain('validation_errors')
            ->addViolation();
    }
