From f36376689fe810731a395d860660acbb2a546170 Mon Sep 17 00:00:00 2001 From: mahdyaralipor Date: Sat, 29 Aug 2026 14:57:39 +0330 Subject: [PATCH] Make validation errors translatable Turns the hard-coded English validation messages into language keys so they follow the console locale: - add 'argument', 'option', 'validation.required' and 'validation.type' keys to the en, es and pt-br language files - render validation messages through the console language, falling back to the previous English strings when the command has no console - drop the #[Pure] attribute as message rendering no longer is pure Closes webisters/cli#52 --- src/Command.php | 13 ++++++++++--- src/Languages/en/cli.php | 4 ++++ src/Languages/es/cli.php | 4 ++++ src/Languages/pt-br/cli.php | 4 ++++ tests/ValidationTest.php | 37 +++++++++++++++++++++++++++++++++++++ 5 files changed, 59 insertions(+), 3 deletions(-) diff --git a/src/Command.php b/src/Command.php index 5251160..6552d77 100644 --- a/src/Command.php +++ b/src/Command.php @@ -348,15 +348,20 @@ public function validate(array $arguments, array $options) : array * * @return array */ - #[Pure] protected function validateDefinitions(array $definitions, array $values, string $label) : array { $errors = []; + $translator = isset($this->console) ? $this->console->getLanguage() : null; + if ($translator) { + $label = $translator->render('cli', $label, []); + } foreach ($definitions as $key => $definition) { $value = $values[$key] ?? null; if ($value === null || $value === false) { if (!empty($definition['required'])) { - $errors[] = $label . ' "' . $key . '" is required.'; + $errors[] = $translator + ? $translator->render('cli', 'validation.required', [$label, (string) $key]) + : $label . ' "' . $key . '" is required.'; } continue; } @@ -374,7 +379,9 @@ protected function validateDefinitions(array $definitions, array $values, string default => true, }; if (!$valid) { - $errors[] = $label . ' "' . $key . '" must be of type ' . $type . '.'; + $errors[] = $translator + ? $translator->render('cli', 'validation.type', [$label, (string) $key, $type]) + : $label . ' "' . $key . '" must be of type ' . $type . '.'; } } return $errors; diff --git a/src/Languages/en/cli.php b/src/Languages/en/cli.php index ca93717..1991349 100644 --- a/src/Languages/en/cli.php +++ b/src/Languages/en/cli.php @@ -15,6 +15,7 @@ 'about.line4' => 'Visit our website to know more: https://webisters.com', 'about.line5' => 'Thanks for using Webisters!', 'aliases' => 'Aliases', + 'argument' => 'argument', 'availableCommands' => 'Available Commands', 'command' => 'Command', 'commandNotFound' => 'Command not found: "{0}"', @@ -30,6 +31,9 @@ 'index.description' => 'Shows commands list.', 'index.option.greet' => 'Shows greeting.', 'noDescription' => 'This command does not provide a description.', + 'option' => 'option', 'options' => 'Options', 'usage' => 'Usage', + 'validation.required' => '{0} "{1}" is required.', + 'validation.type' => '{0} "{1}" must be of type {2}.', ]; diff --git a/src/Languages/es/cli.php b/src/Languages/es/cli.php index 3625948..9d6e08a 100644 --- a/src/Languages/es/cli.php +++ b/src/Languages/es/cli.php @@ -16,6 +16,7 @@ 'about.line5' => '¡Gracias por usar Webisters!', 'availableCommands' => 'Comandos Disponibles', 'aliases' => 'Alias', + 'argument' => 'argumento', 'command' => 'Comando', 'commandNotFound' => 'Comando no encontrado: "{0}"', 'commands' => 'Comandos', @@ -30,6 +31,9 @@ 'index.description' => 'Muestra la lista de comandos.', 'index.option.greet' => 'Muestra saludo.', 'noDescription' => 'Este comando no proporciona una descripción.', + 'option' => 'opción', 'options' => 'Opciones', 'usage' => 'Uso', + 'validation.required' => '{0} "{1}" es obligatorio.', + 'validation.type' => '{0} "{1}" debe ser del tipo {2}.', ]; diff --git a/src/Languages/pt-br/cli.php b/src/Languages/pt-br/cli.php index 5fb044b..4c5446a 100644 --- a/src/Languages/pt-br/cli.php +++ b/src/Languages/pt-br/cli.php @@ -16,6 +16,7 @@ 'about.line5' => 'Obrigado por usar o Webisters!', 'availableCommands' => 'Comandos Disponíveis', 'aliases' => 'Aliases', + 'argument' => 'argumento', 'command' => 'Comando', 'commandNotFound' => 'Comando não encontrado: "{0}"', 'commands' => 'Comandos', @@ -30,6 +31,9 @@ 'index.description' => 'Mostra a lista de comandos.', 'index.option.greet' => 'Mostra saudação.', 'noDescription' => 'Este comando não fornece uma descrição.', + 'option' => 'opção', 'options' => 'Opções', 'usage' => 'Uso', + 'validation.required' => '{0} "{1}" é obrigatório.', + 'validation.type' => '{0} "{1}" deve ser do tipo {2}.', ]; diff --git a/tests/ValidationTest.php b/tests/ValidationTest.php index 0546eae..7a3e6bc 100644 --- a/tests/ValidationTest.php +++ b/tests/ValidationTest.php @@ -13,6 +13,7 @@ use Framework\CLI\Command; use Framework\CLI\Streams\Stderr; use Framework\CLI\Streams\Stdout; +use Framework\Language\Language; use PHPUnit\Framework\TestCase; /** @@ -93,6 +94,42 @@ public function testMissingRequiredOptionReportsAnError() : void self::assertStringContainsString('option "count" is required', Stderr::getContents()); } + public function testValidationErrorsAreTranslatedToSpanish() : void + { + $console = new ConsoleMock(new Language('es')); + $command = new ValidatedCommandMock($console); + $command->setArgumentDefinitions([ + 0 => ['type' => 'int', 'required' => true], + ]); + $command->setOptionDefinitions([ + 'count' => ['type' => 'int', 'required' => true], + ]); + $console->addCommand($command); + $console->exec('validated'); + self::assertStringContainsString('argumento "0" es obligatorio.', Stderr::getContents()); + self::assertStringContainsString('opción "count" es obligatorio.', Stderr::getContents()); + $console->exec('validated abc'); + self::assertStringContainsString('argumento "0" debe ser del tipo int.', Stderr::getContents()); + } + + public function testValidationErrorsAreTranslatedToBrazilianPortuguese() : void + { + $console = new ConsoleMock(new Language('pt-br')); + $command = new ValidatedCommandMock($console); + $command->setArgumentDefinitions([ + 0 => ['type' => 'int', 'required' => true], + ]); + $command->setOptionDefinitions([ + 'count' => ['type' => 'int', 'required' => true], + ]); + $console->addCommand($command); + $console->exec('validated'); + self::assertStringContainsString('argumento "0" é obrigatório.', Stderr::getContents()); + self::assertStringContainsString('opção "count" é obrigatório.', Stderr::getContents()); + $console->exec('validated abc'); + self::assertStringContainsString('argumento "0" deve ser do tipo int.', Stderr::getContents()); + } + public function testGettersReturnTheDefinitions() : void { $command = new ValidatedCommandMock($this->console);