# How can i translate OptionalInputFilter validator messages in Mezzio?

**URL:** <https://discourse.laminas.dev/t/how-can-i-translate-optionalinputfilter-validator-messages-in-mezzio/3327>\
**Category:** Mezzio\
**Created:** [May 5, 2023, 12:41pm UTC](https://discourse.laminas.dev/t/how-can-i-translate-optionalinputfilter-validator-messages-in-mezzio/3327 "2023-05-05T12:41:23Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![eguvenc](https://yyz2.discourse-cdn.com/flex032/user_avatar/discourse.laminas.dev/eguvenc/32/265_2.png) [@eguvenc](https://discourse.laminas.dev/u/eguvenc)\
**Post date:** [May 5, 2023, 12:41pm UTC](https://discourse.laminas.dev/t/how-can-i-translate-optionalinputfilter-validator-messages-in-mezzio/3327/1 "2023-05-05T12:41:23Z")

</div>

When i use OptinalInputFilter class, the translate object does not translate the validations errors.

i run the below the codes.

```auto
<?php

namespace App\Filter;

use App\Validator\Db\RecordExists;
use App\Validator\Db\NoRecordExists;
use Laminas\Filter\StringTrim;
use Laminas\Validator\Uuid;
use Laminas\InputFilter\OptionalInputFilter;
use Laminas\Validator\StringLength;
use Laminas\Db\Adapter\AdapterInterface;
use Laminas\InputFilter\InputFilterPluginManager;

class WorkplaceSaveFilter extends InputFilter
{
    public function __construct(
        AdapterInterface $adapter,
        InputFilterPluginManager $filter
    )
    {
        $this->filter = $filter;
        $this->adapter = $adapter;
    }

    public function setInputData(array $data)
    {
        $this->add([
            'name' => 'id',
            'required' => true,
            'validators' => [
                ['name' => Uuid::class],
                [
                    'name' => HTTP_METHOD == 'POST' ? NoRecordExists::class : RecordExists::class,
                    'options' => [
                        'table' => 'workplaces',
                        'field' => 'workplaceId',
                        'adapter' => $this->adapter,
                    ]
                ]
            ],
        ]);
        $companyFilter = new OptionalInputFilter();
        $companyFilter->add([
            'name' => 'id',
            'required' => true,
            'validators' => [
                ['name' => Uuid::class],
                [
                    'name' => RecordExists::class,
                    'options' => [
                        'table' => 'companies',
                        'field' => 'companyId',
                        'adapter' => $this->adapter,
                    ]
                ]
            ],
        ]);
        $this->add($companyFilter, 'companyId');
        $this->add([
            'name' => 'workplaceName',
            'required' => true,
            'filters' => [
                ['name' => StringTrim::class],
            ],
            'validators' => [
                [
                    'name' => StringLength::class,
                    'options' => [
                        'encoding' => 'UTF-8',
                        'min' => 3,
                        'max' => 255,
                    ],
                ],
            ],
        ]);
        $this->add([
            'name' => 'registrationNumber',
            'required' => false,
            'filters' => [
                ['name' => StringTrim::class],
            ],
            'validators' => [
                [
                    'name' => StringLength::class,
                    'options' => [
                        'encoding' => 'UTF-8',
                        'min' => 3,
                        'max' => 100,
                    ],
                ],
            ],
        ]);
        $this->add([
            'name' => 'address',
            'required' => false,
            'filters' => [
                ['name' => StringTrim::class],
            ],
            'validators' => [
                [
                    'name' => StringLength::class,
                    'options' => [
                        'encoding' => 'UTF-8',
                        'min' => 3,
                        'max' => 255,
                    ],
                ],
            ],
        ]);
        // Set input data
        //
        $this->setData($data);
    }
}

```

Expected output:

```auto
{
    "data": {
        "error": {
            "companyId": [
                {
                    "id": [
                        "Şirket: Değer gereklidir ve boş bırakılamaz"
                    ]
                }
            ]
        }
    }
}

```

Active Result:

```auto
{
    "data": {
        "error": {
            "companyId": [
                {
                    "id": [
                        "Şirket: Value is required and can't be empty"
                    ]
                }
            ]
        }
    }
}

```

Here is my config provider

```auto
<?php
declare(strict_types=1);

use Laminas\ConfigAggregator\ArrayProvider;
use Laminas\ConfigAggregator\ConfigAggregator;
use Laminas\ConfigAggregator\PhpFileProvider;

// To enable or disable caching, set the `ConfigAggregator::ENABLE_CACHE` boolean in
// `config/autoload/local.php`.
$cacheConfig = [
    'config_cache_path' => 'data/cache/config-cache.php',
];

$aggregator = new ConfigAggregator([
    \Laminas\Cache\Storage\Adapter\Redis\ConfigProvider::class,
    \Mezzio\Tooling\ConfigProvider::class,
    \Mezzio\Cors\ConfigProvider::class,
    \Laminas\Mail\ConfigProvider::class,
    \Laminas\Paginator\ConfigProvider::class,
    \Laminas\Mvc\I18n\ConfigProvider::class,
    \Mezzio\Authorization\ConfigProvider::class,
    \Laminas\Serializer\ConfigProvider::class,
    \Laminas\Cache\ConfigProvider::class,
    // \Mezzio\Authentication\LaminasAuthentication\ConfigProvider::class,
    // \Mezzio\Authentication\OAuth2\ConfigProvider::class,
    // \Mezzio\Authentication\ConfigProvider::class,
    \Laminas\Db\ConfigProvider::class,
    \Laminas\InputFilter\ConfigProvider::class,
    \Laminas\Filter\ConfigProvider::class,
    \Laminas\I18n\ConfigProvider::class,
    \Laminas\HttpHandlerRunner\ConfigProvider::class,
    // \Mezzio\LaminasView\ConfigProvider::class,
    \Mezzio\Router\LaminasRouter\ConfigProvider::class,
    \Laminas\Router\ConfigProvider::class,
    \Laminas\Validator\ConfigProvider::class,
    // Include cache configuration
    new ArrayProvider($cacheConfig),
    \Mezzio\Helper\ConfigProvider::class,
    \Mezzio\ConfigProvider::class,
    \Mezzio\Router\ConfigProvider::class,
    \Laminas\Diactoros\ConfigProvider::class,
    // Swoole config to overwrite some services (if installed)
    class_exists(\Mezzio\Swoole\ConfigProvider::class)
        ? \Mezzio\Swoole\ConfigProvider::class
        : function(){ return[]; },
    // Default App module config
    App\ConfigProvider::class,
    // Load application config in a pre-defined order in such a way that local settings
    // overwrite global settings. (Loaded as first to last):
    // - `global.php`
    // - `*.global.php`
    // - `local.php`
    // - `*.local.php`

    // new PhpFileProvider(realpath( __DIR__ ) . '/autoload/{{,*.}global,{,*.}local}.php'),
    new PhpFileProvider(realpath( __DIR__ ) . sprintf('/autoload/{,*.}{global,%s}.php', getenv('APP_ENV') ?: 'local')),
    
    // Load development config if it exists
    new PhpFileProvider(realpath( __DIR__ ) . '/development.config.php'),
], $cacheConfig['config_cache_path']);

return $aggregator->getMergedConfig();

```

Here is my TranslatorDelagatorFactory class

```auto
<?php

declare(strict_types=1);

namespace App\Container;

use Interop\Container\ContainerInterface;
use Laminas\I18n\Translator\Resources;
use Laminas\ServiceManager\Factory\DelegatorFactoryInterface;

class TranslatorDelegatorFactory implements DelegatorFactoryInterface
{
    public function __invoke(ContainerInterface $container, $name, callable $callback, array $options = null)
    {
        $translator = $callback();
        
        $translator->addTranslationFilePattern(
            'phpArray',
            Resources::getBasePath(),
            Resources::getPatternForValidator()
        );
        $translator->addTranslationFilePattern(
            'phpArray',
            Resources::getBasePath(),
            Resources::getPatternForCaptcha()
        );

        return $translator;
    }
}

```

Help please thanks.

---

<div class="post-metadata">

**Author:** ![froschdesign](https://yyz2.discourse-cdn.com/flex032/user_avatar/discourse.laminas.dev/froschdesign/32/38_2.png) [@froschdesign](https://discourse.laminas.dev/u/froschdesign)\
**Post date:** [May 8, 2023, 9:05am UTC](https://discourse.laminas.dev/t/how-can-i-translate-optionalinputfilter-validator-messages-in-mezzio/3327/2 "2023-05-08T09:05:48Z")

</div>

> [@eguvenc](#):
>
> When i use OptinalInputFilter class, the translate object does not translate the validations errors.

I have not tested it, but the optional input filter is created independently, so the translator might not work.

Try the following:

```php
$this->add(
    [
        'name' => 'companyId',
        'type' => Laminas\InputFilter\OptionalInputFilter::class,
        [
            'name' => 'id',
            'required' => true,
            // …
        ],
    ]
);

```

---

<div class="post-metadata">

**Author:** ![eguvenc](https://yyz2.discourse-cdn.com/flex032/user_avatar/discourse.laminas.dev/eguvenc/32/265_2.png) [@eguvenc](https://discourse.laminas.dev/u/eguvenc)\
**Post date:** [May 8, 2023, 9:23am UTC](https://discourse.laminas.dev/t/how-can-i-translate-optionalinputfilter-validator-messages-in-mezzio/3327/3 "2023-05-08T09:23:37Z")

</div>

Thanks for your response,  
I tried like this,

```auto
$this->add(
    [
        'name' => 'companyId',
        'type' => OptionalInputFilter::class,
        [
            'name' => 'id',
            'required' => true,
            'validators' => [
                ['name' => Uuid::class],
                [
                    'name' => RecordExists::class,
                    'options' => [
                        'table' => 'companies',
                        'field' => 'companyId',
                        'adapter' => $this->adapter,
                    ]
                ]
            ],
        ],
    ]
);
$this->add($companyFilter, 'companyId');

```

but i get below the error

```auto
{
    "title": "Laminas\\InputFilter\\Exception\\InvalidArgumentException",
    "type": "https://httpstatus.es/400",
    "status": 400,
    "file": "/vendor/laminas/laminas-inputfilter/src/Factory.php",
    "line": 156,
    "error": "Laminas\\InputFilter\\Factory::createInput expects an array or Traversable; received \"string\"",
    "trace": [
        {

```

Also, I tried it in another way,

```auto
$companyFilter->add([
    'name' => 'id',
    'required' => true,
    'type' => OptionalInputFilter::class,
    'validators' => [
        ['name' => Uuid::class],
        [
            'name' => RecordExists::class,
            'options' => [
                'table' => 'companies',
                'field' => 'companyId',
                'adapter' => $this->adapter,
            ]
        ]
    ],
]);
$this->add($companyFilter, 'companyId');

```

But i am getting same error…

---

<div class="post-metadata">

**Author:** ![froschdesign](https://yyz2.discourse-cdn.com/flex032/user_avatar/discourse.laminas.dev/froschdesign/32/38_2.png) [@froschdesign](https://discourse.laminas.dev/u/froschdesign)\
**Post date:** [May 8, 2023, 9:35am UTC](https://discourse.laminas.dev/t/how-can-i-translate-optionalinputfilter-validator-messages-in-mezzio/3327/4 "2023-05-08T09:35:01Z")

</div>

My mistake, the method `add` does not work directly, therefore:

```php
$this->add(
    $this->getFactory()->createInputFilter(
        [
            'type' => Laminas\InputFilter\OptionalInputFilter::class,
            [
                'name' => 'id',
                'required' => true,
                // …
            ],
        ]
    ),
    'companyId'
);

```

Background: if an array is used with the `add` method, then only a _single_ input is created, so that no input filter via array can be used here. But the method `add` can use an object of the type input filter.

_I hope my memory is correct._

---

<div class="post-metadata">

**Author:** ![froschdesign](https://yyz2.discourse-cdn.com/flex032/user_avatar/discourse.laminas.dev/froschdesign/32/38_2.png) [@froschdesign](https://discourse.laminas.dev/u/froschdesign)\
**Post date:** [May 8, 2023, 9:59am UTC](https://discourse.laminas.dev/t/how-can-i-translate-optionalinputfilter-validator-messages-in-mezzio/3327/5 "2023-05-08T09:59:35Z")

</div>

Do you set the default translator for the validators somewhere?

```php
Laminas\Validator\AbstractValidator::setDefaultTranslator($translator);

```

Or how do you use the translator?

> [@eguvenc](#):
>
> `\Laminas\Mvc\I18n\ConfigProvider::class,`

That looks strange! MVC in Mezzio?!

---

<div class="post-metadata">

**Author:** ![eguvenc](https://yyz2.discourse-cdn.com/flex032/user_avatar/discourse.laminas.dev/eguvenc/32/265_2.png) [@eguvenc](https://discourse.laminas.dev/u/eguvenc)\
**Post date:** [May 8, 2023, 2:18pm UTC](https://discourse.laminas.dev/t/how-can-i-translate-optionalinputfilter-validator-messages-in-mezzio/3327/6 "2023-05-08T14:18:21Z")

</div>

I removed **\Laminas\Mvc\I18n\ConfigProvider::class** , class from config provider. And i tried below the code.

```auto
$this->add(
    $this->getFactory()->createInputFilter(
        [
            'type' => OptionalInputFilter::class,
            [
                'name' => 'id',
                'required' => true,
                'validators' => [
                    ['name' => Uuid::class],
                    [
                        'name' => RecordExists::class,
                        'options' => [
                            'table' => 'companies',
                            'field' => 'companyId',
                            'adapter' => $this->adapter,
                        ]
                    ]
                ],
            ],
        ]
    ),
    'companyId'
);

```

But result is same translator does not work.

In my config provider class I use **TranslatorDelegatorFactory** to init translation object.

```auto
return [
    'invokables' => [
        // Handler\PingHandler::class => Handler\PingHandler::class,
    ],
    'delegators' => [
        TranslatorInterface::class => [
            'App\Container\TranslatorDelegatorFactory',
        ],
    ],

```

Do i need to really use this code ?

```auto
Laminas\Validator\AbstractValidator::setDefaultTranslator($translator);

```

If yes how i can get the validator translator instance ?

`Laminas\\Validator\\AbstractValidator::setDefaultTranslator(): Argument #1 ($translator) must be of type ?Laminas\\Validator\\Translator\\TranslatorInterface, Laminas\\I18n\\Translator\\Translator given`

---

<div class="post-metadata">

**Author:** ![froschdesign](https://yyz2.discourse-cdn.com/flex032/user_avatar/discourse.laminas.dev/froschdesign/32/38_2.png) [@froschdesign](https://discourse.laminas.dev/u/froschdesign)\
**Post date:** [May 8, 2023, 2:28pm UTC](https://discourse.laminas.dev/t/how-can-i-translate-optionalinputfilter-validator-messages-in-mezzio/3327/7 "2023-05-08T14:28:51Z")

</div>

Give me some and I will test the entire process with my Mezzio test application.

> [@eguvenc](#):
>
> In my config provider class I use **TranslatorDelegatorFactory** to init translation object.

[A configuration for the translator](https://docs.laminas.dev/laminas-i18n/translator/factory/#setting-translation-file-patterns) should be enough, a delegator is not needed. The rest of the work is done by factory method of the translator.

---

<div class="post-metadata">

**Author:** ![eguvenc](https://yyz2.discourse-cdn.com/flex032/user_avatar/discourse.laminas.dev/eguvenc/32/265_2.png) [@eguvenc](https://discourse.laminas.dev/u/eguvenc)\
**Post date:** [May 10, 2023, 10:50am UTC](https://discourse.laminas.dev/t/how-can-i-translate-optionalinputfilter-validator-messages-in-mezzio/3327/8 "2023-05-10T10:50:57Z")

</div>

Hi, have you had a chance to test?

---

<div class="post-metadata">

**Author:** ![froschdesign](https://yyz2.discourse-cdn.com/flex032/user_avatar/discourse.laminas.dev/froschdesign/32/38_2.png) [@froschdesign](https://discourse.laminas.dev/u/froschdesign)\
**Post date:** [June 16, 2023, 8:56am UTC](https://discourse.laminas.dev/t/how-can-i-translate-optionalinputfilter-validator-messages-in-mezzio/3327/9 "2023-06-16T08:56:49Z")

</div>

_Sorry, I’m very busy with client projects._

The problem here is laminas-validator:

> <https://github.com/laminas/laminas-validator/blob/1d82b5c0ede9316263e14d11e9284dfb3130f158/src/ValidatorPluginManager.php#L591-L593>

This means that your first idea of using laminas-mvc-i18n seems to be correct here - strange, but correct.

I hope I can test it in my local test application…

---

<div class="post-metadata">

**Author:** ![froschdesign](https://yyz2.discourse-cdn.com/flex032/user_avatar/discourse.laminas.dev/froschdesign/32/38_2.png) [@froschdesign](https://discourse.laminas.dev/u/froschdesign)\
**Post date:** [June 23, 2023, 11:54am UTC](https://discourse.laminas.dev/t/how-can-i-translate-optionalinputfilter-validator-messages-in-mezzio/3327/10 "2023-06-23T11:54:27Z")

</div>

> <https://github.com/laminas/laminas-validator/issues/194>
>
> \### Bug Report
> 
> | Q | A
> |------------ | ------
> | Version(s) | 2….33.0
> 
> \#### Summary
> 
> The validator plugin manager tries to inject a translator with name \`MvcTranslator\` but outside of a laminas-mvc based application there is no \`MvcTranslator\`. This means it will fail in a Mezzio based application or any other context.
> 
> \#### Current behavior
> 
> The validator plugin manager searches for a service with name \`MvcTranslator\`:
> 
> https://github.com/laminas/laminas-validator/blob/1d82b5c0ede9316263e14d11e9284dfb3130f158/src/ValidatorPluginManager.php#L590-L594
> 
> A registered service with name \`Laminas\\I18n\\Translator\\TranslatorInterface::class\` will not be used and an alias can not be used because the \`AbstractValidator\` expected an implementation of \`Laminas\\Validator\\Translator\\TranslatorInterface\`:
> 
> https://github.com/laminas/laminas-validator/blob/1d82b5c0ede9316263e14d11e9284dfb3130f158/src/Translator/TranslatorInterface.php#L5-L14
> 
> https://github.com/laminas/laminas-validator/blob/1d82b5c0ede9316263e14d11e9284dfb3130f158/src/AbstractValidator.php#L441
> 
> \#### How to reproduce
> 
> \`\`\`php
> // Service manager
> $serviceManager = new Laminas\\ServiceManager\\ServiceManager();
> $serviceManager-\>setService('config', \[\]); // for translator factory
> $serviceManager-\>configure(
> (new Laminas\\I18n\\ConfigProvider())-\>getDependencyConfig()
> );
> 
> // Translator
> /\*\* @var Laminas\\I18n\\Translator\\TranslatorInterface $translator \*/
> $translator = $serviceManager-\>get(
> Laminas\\I18n\\Translator\\TranslatorInterface::class
> );
> $translator-\>addTranslationFilePattern(
> Laminas\\I18n\\Translator\\Loader\\PhpArray::class,
> Laminas\\I18n\\Translator\\Resources::getBasePath(),
> Laminas\\I18n\\Translator\\Resources::getPatternForValidator()
> );
> Locale::setDefault('de');
> 
> // Test
> $validatorManager = new Laminas\\Validator\\ValidatorPluginManager(
> $serviceManager
> );
> $validator = $validatorManager-\>get(Laminas\\Validator\\NotEmpty::class);
> $validator-\>isValid('');
> 
> // Is not translated!
> echo $validator-\>getMessages()\['isEmpty'\]; // Value is required and can't be empty
> \`\`\`
> 
> A service must be added manually:
> 
> \`\`\`php
> $serviceManager-\>setService(
> 'MvcTranslator',
> new class ($translator)
> implements Laminas\\Validator\\Translator\\TranslatorInterface {
> public function \_\_construct(
> private readonly Laminas\\I18n\\Translator\\TranslatorInterface $translator
> ) {
> }
> 
> public function translate(
> $message,
> $textDomain = 'default',
> $locale = null
> ) {
> return $this-\>translator-\>translate($message, $textDomain, $locale);
> }
> }
> );
> \`\`\`
> 
> Or \[laminas-mvc-i18n\](https://docs.laminas.dev/laminas-mvc-i18n/) must be used:
> 
> \`\`\`php
> $serviceManager-\>configure(
> (new Laminas\\Mvc\\I18n\\ConfigProvider())-\>getDependencyConfig()
> );
> \`\`\`
> 
> \`\`\`php
> echo $validator-\>getMessages()\['isEmpty'\]; // Es wird eine Eingabe benötigt
> \`\`\`
> 
> \#### Expected behavior
> 
> Seamless integration should also be available outside of laminas-mvc based application without the user having to write anything manually or have to use an MVC package in a Mezzio application.
