diff --git a/docs/04-message-translation.md b/docs/04-message-translation.md index a52b73601..c6775ffc0 100644 --- a/docs/04-message-translation.md +++ b/docs/04-message-translation.md @@ -1,17 +1,52 @@ # Message translation -You're also able to translate your message to another language with Validation. -The only thing one must do is to define the param `translator` as a callable that -will handle the translation overwriting the default factory: +You're able to translate message templates with Validation. ```php -Factory::setDefaultInstance( - (new Factory())->withTranslator('gettext') -); +use Respect\Validation\Message\Translator\GettextTranslator; +use Respect\Validation\ValidatorDefaults; + +ValidatorDefaults::setTranslator(new GettextTranslator()); +``` + +After that, if you call the methods `getMessage()`, `getMessages()`, or `getFullMessage()` from the `ValidationException`, the messages will be translated. The example above will work if you have `gettext` properly configured. + +For non-static usage, pass the translator directly to the `Validator` constructor: + +```php +use Respect\Validation\Factory; +use Respect\Validation\Message\StandardFormatter; +use Respect\Validation\Message\Translator\GettextTranslator; +use Respect\Validation\Validator; + +$translator = new GettextTranslator(); + +$validator = new Validator(new Factory(), new StandardFormatter(), $translator); ``` -The example above uses `gettext()` but you can use any other callable value, like -`[$translator, 'trans']` or `you_custom_function()`. +## Supported translators -After that, if you call `getMessage()`, `getMessages()`, or `getFullMessage()`, -the message will be translated. +- `ArrayTranslator`: Translates messages using an array of messages. +- `GettextTranslator`: Translates messages using the `gettext` extension. + +## Custom translators + +You can implement custom translators by creating a class that implements the `Translator` interface. Here's an example using the `stichoza/google-translate-php` package: + +```php +use Respect\Validation\Message\Translator; +use Stichoza\GoogleTranslate\GoogleTranslate; + +final class MyTranslator implements Translator +{ + public function __construct( + private readonly GoogleTranslate $googleTranslate + ) { + } + + public function translate(string $message): string + { + return $this->googleTranslate->translate($message); + } +} +``` diff --git a/library/Factory.php b/library/Factory.php index 2628a8dbb..22c01de1c 100644 --- a/library/Factory.php +++ b/library/Factory.php @@ -13,10 +13,6 @@ use ReflectionException; use Respect\Validation\Exceptions\ComponentException; use Respect\Validation\Exceptions\InvalidClassException; -use Respect\Validation\Message\Parameter\Processor; -use Respect\Validation\Message\Parameter\Raw; -use Respect\Validation\Message\Parameter\Stringify; -use Respect\Validation\Message\Parameter\Trans; use Respect\Validation\Transformers\Aliases; use Respect\Validation\Transformers\DeprecatedAge; use Respect\Validation\Transformers\DeprecatedAttribute; @@ -42,22 +38,8 @@ final class Factory */ private array $rulesNamespaces = ['Respect\\Validation\\Rules']; - /** - * @var callable - */ - private $translator; - - private Processor $processor; - - private Transformer $transformer; - - private static Factory $defaultInstance; - - public function __construct() - { - $this->translator = static fn (string $message) => $message; - $this->processor = new Raw(new Trans($this->translator, new Stringify())); - $this->transformer = new DeprecatedAttribute( + public function __construct( + private readonly Transformer $transformer = new DeprecatedAttribute( new DeprecatedKey( new DeprecatedKeyValue( new DeprecatedMinAndMax( @@ -67,16 +49,8 @@ public function __construct() ) ) ) - ); - } - - public static function getDefaultInstance(): self - { - if (!isset(self::$defaultInstance)) { - self::$defaultInstance = new self(); - } - - return self::$defaultInstance; + ) + ) { } public function withRuleNamespace(string $rulesNamespace): self @@ -87,33 +61,6 @@ public function withRuleNamespace(string $rulesNamespace): self return $clone; } - public function withTranslator(callable $translator): self - { - $clone = clone $this; - $clone->translator = $translator; - $clone->processor = new Raw(new Trans($translator, new Stringify())); - - return $clone; - } - - public function withParameterProcessor(Processor $processor): self - { - $clone = clone $this; - $clone->processor = $processor; - - return $clone; - } - - public function getTranslator(): callable - { - return $this->translator; - } - - public function getParameterProcessor(): Processor - { - return $this->processor; - } - /** * @param mixed[] $arguments */ @@ -122,11 +69,6 @@ public function rule(string $ruleName, array $arguments = []): Validatable return $this->createRuleSpec($this->transformer->transform(new RuleSpec($ruleName, $arguments))); } - public static function setDefaultInstance(self $defaultInstance): void - { - self::$defaultInstance = $defaultInstance; - } - private function createRuleSpec(RuleSpec $ruleSpec): Validatable { $rule = $this->createRule($ruleSpec->name, $ruleSpec->arguments); diff --git a/library/Message/Formatter.php b/library/Message/Formatter.php index 4d6af775d..33e93678b 100644 --- a/library/Message/Formatter.php +++ b/library/Message/Formatter.php @@ -16,17 +16,17 @@ interface Formatter /** * @param array $templates */ - public function main(Result $result, array $templates): string; + public function main(Result $result, array $templates, Translator $translator): string; /** * @param array $templates */ - public function full(Result $result, array $templates, int $depth = 0): string; + public function full(Result $result, array $templates, Translator $translator, int $depth = 0): string; /** * @param array $templates * * @return array */ - public function array(Result $result, array $templates): array; + public function array(Result $result, array $templates, Translator $translator): array; } diff --git a/library/Message/Parameter/Processor.php b/library/Message/Parameter/Processor.php deleted file mode 100644 index 1eef366fd..000000000 --- a/library/Message/Parameter/Processor.php +++ /dev/null @@ -1,15 +0,0 @@ - - * SPDX-License-Identifier: MIT - */ - -declare(strict_types=1); - -namespace Respect\Validation\Message\Parameter; - -interface Processor -{ - public function process(string $name, mixed $value, ?string $modifier = null): string; -} diff --git a/library/Message/Parameter/Raw.php b/library/Message/Parameter/Raw.php deleted file mode 100644 index e9a573e5c..000000000 --- a/library/Message/Parameter/Raw.php +++ /dev/null @@ -1,30 +0,0 @@ - - * SPDX-License-Identifier: MIT - */ - -declare(strict_types=1); - -namespace Respect\Validation\Message\Parameter; - -use function is_bool; -use function is_scalar; - -final class Raw implements Processor -{ - public function __construct( - private readonly Processor $nextProcessor, - ) { - } - - public function process(string $name, mixed $value, ?string $modifier = null): string - { - if ($modifier === 'raw' && is_scalar($value)) { - return is_bool($value) ? (string) (int) $value : (string) $value; - } - - return $this->nextProcessor->process($name, $value, $modifier); - } -} diff --git a/library/Message/Parameter/Stringify.php b/library/Message/Parameter/Stringify.php deleted file mode 100644 index 6b1e9f608..000000000 --- a/library/Message/Parameter/Stringify.php +++ /dev/null @@ -1,25 +0,0 @@ - - * SPDX-License-Identifier: MIT - */ - -declare(strict_types=1); - -namespace Respect\Validation\Message\Parameter; - -use function is_string; -use function Respect\Stringifier\stringify; - -final class Stringify implements Processor -{ - public function process(string $name, mixed $value, ?string $modifier = null): string - { - if ($name === 'name' && is_string($value)) { - return $value; - } - - return stringify($value); - } -} diff --git a/library/Message/Parameter/Trans.php b/library/Message/Parameter/Trans.php deleted file mode 100644 index 8a485e9e4..000000000 --- a/library/Message/Parameter/Trans.php +++ /dev/null @@ -1,35 +0,0 @@ - - * SPDX-License-Identifier: MIT - */ - -declare(strict_types=1); - -namespace Respect\Validation\Message\Parameter; - -use function call_user_func; -use function is_string; - -final class Trans implements Processor -{ - /** @var callable */ - private $translator; - - public function __construct( - callable $translator, - private readonly Processor $nextProcessor, - ) { - $this->translator = $translator; - } - - public function process(string $name, mixed $value, ?string $modifier = null): string - { - if ($modifier === 'trans' && is_string($value)) { - return call_user_func($this->translator, $value); - } - - return $this->nextProcessor->process($name, $value, $modifier); - } -} diff --git a/library/Message/Renderer.php b/library/Message/Renderer.php index 75ca5367c..78187dd3b 100644 --- a/library/Message/Renderer.php +++ b/library/Message/Renderer.php @@ -13,5 +13,5 @@ interface Renderer { - public function render(Result $result, ?string $template = null): string; + public function render(Result $result, Translator $translator, ?string $template = null): string; } diff --git a/library/Message/StandardFormatter.php b/library/Message/StandardFormatter.php index 7c93a7bbc..41d7951e9 100644 --- a/library/Message/StandardFormatter.php +++ b/library/Message/StandardFormatter.php @@ -29,29 +29,29 @@ final class StandardFormatter implements Formatter { public function __construct( - private readonly Renderer $renderer, + private readonly Renderer $renderer = new StandardRenderer(), ) { } /** * @param array $templates */ - public function main(Result $result, array $templates): string + public function main(Result $result, array $templates, Translator $translator): string { $selectedTemplates = $this->selectTemplates($result, $templates); if (!$this->isFinalTemplate($result, $selectedTemplates)) { foreach ($this->extractDeduplicatedChildren($result) as $child) { - return $this->main($child, $selectedTemplates); + return $this->main($child, $selectedTemplates, $translator); } } - return $this->renderer->render($this->getTemplated($result, $selectedTemplates)); + return $this->renderer->render($this->getTemplated($result, $selectedTemplates), $translator); } /** * @param array $templates */ - public function full(Result $result, array $templates, int $depth = 0): string + public function full(Result $result, array $templates, Translator $translator, int $depth = 0): string { $selectedTemplates = $this->selectTemplates($result, $templates); $isFinalTemplate = $this->isFinalTemplate($result, $selectedTemplates); @@ -62,14 +62,14 @@ public function full(Result $result, array $templates, int $depth = 0): string $rendered .= sprintf( '%s- %s' . PHP_EOL, $indentation, - $this->renderer->render($this->getTemplated($result, $selectedTemplates)), + $this->renderer->render($this->getTemplated($result, $selectedTemplates), $translator), ); $depth++; } if (!$isFinalTemplate) { foreach ($this->extractDeduplicatedChildren($result) as $child) { - $rendered .= $this->full($child, $selectedTemplates, $depth); + $rendered .= $this->full($child, $selectedTemplates, $translator, $depth); $rendered .= PHP_EOL; } } @@ -82,17 +82,23 @@ public function full(Result $result, array $templates, int $depth = 0): string * * @return array */ - public function array(Result $result, array $templates): array + public function array(Result $result, array $templates, Translator $translator): array { $selectedTemplates = $this->selectTemplates($result, $templates); $deduplicatedChildren = $this->extractDeduplicatedChildren($result); if (count($deduplicatedChildren) === 0 || $this->isFinalTemplate($result, $selectedTemplates)) { - return [$result->id => $this->renderer->render($this->getTemplated($result, $selectedTemplates))]; + return [ + $result->id => $this->renderer->render($this->getTemplated($result, $selectedTemplates), $translator), + ]; } $messages = []; foreach ($deduplicatedChildren as $child) { - $messages[$child->id] = $this->array($child, $this->selectTemplates($child, $selectedTemplates)); + $messages[$child->id] = $this->array( + $child, + $this->selectTemplates($child, $selectedTemplates), + $translator + ); if (count($messages[$child->id]) !== 1) { continue; } @@ -101,7 +107,9 @@ public function array(Result $result, array $templates): array } if (count($messages) > 1) { - $self = ['__root__' => $this->renderer->render($this->getTemplated($result, $selectedTemplates))]; + $self = [ + '__root__' => $this->renderer->render($this->getTemplated($result, $selectedTemplates), $translator), + ]; return $self + $messages; } diff --git a/library/Message/StandardRenderer.php b/library/Message/StandardRenderer.php index ee71312bc..dbe9285ea 100644 --- a/library/Message/StandardRenderer.php +++ b/library/Message/StandardRenderer.php @@ -10,66 +10,55 @@ namespace Respect\Validation\Message; use ReflectionClass; -use Respect\Validation\Exceptions\ComponentException; -use Respect\Validation\Message\Parameter\Processor; +use Respect\Stringifier\Stringifier; +use Respect\Stringifier\Stringifiers\CompositeStringifier; use Respect\Validation\Mode; use Respect\Validation\Result; use Respect\Validation\Validatable; -use Throwable; -use function call_user_func; +use function is_bool; +use function is_scalar; +use function is_string; use function preg_replace_callback; -use function sprintf; +use function print_r; final class StandardRenderer implements Renderer { /** @var array> */ private array $templates = []; - /** @var callable */ - private $translator; + private readonly Stringifier $stringifier; - public function __construct( - callable $translator, - private readonly Processor $processor - ) { - $this->translator = $translator; + public function __construct(?Stringifier $stringifier = null) + { + $this->stringifier = $stringifier ?? CompositeStringifier::createDefault(); } - public function render(Result $result, ?string $template = null): string + public function render(Result $result, Translator $translator, ?string $template = null): string { $parameters = $result->parameters; - $parameters['name'] ??= $result->name ?? $this->processor->process('input', $result->input); + $parameters['name'] ??= $result->name ?? $this->placeholder('input', $result->input, $translator); $parameters['input'] = $result->input; $rendered = (string) preg_replace_callback( '/{{(\w+)(\|([^}]+))?}}/', - function (array $matches) use ($parameters) { + function (array $matches) use ($parameters, $translator) { if (!isset($parameters[$matches[1]])) { return $matches[0]; } - return $this->processor->process($matches[1], $parameters[$matches[1]], $matches[3] ?? null); + return $this->placeholder($matches[1], $parameters[$matches[1]], $translator, $matches[3] ?? null); }, - $this->translate($template ?? $this->getTemplateMessage($result)) + $translator->translate($template ?? $this->getTemplateMessage($result)) ); if (!$result->hasCustomTemplate() && $result->nextSibling !== null) { - $rendered .= ' ' . $this->render($result->nextSibling); + $rendered .= ' ' . $this->render($result->nextSibling, $translator); } return $rendered; } - private function translate(string $message): string - { - try { - return call_user_func($this->translator, $message); - } catch (Throwable $throwable) { - throw new ComponentException(sprintf('Failed to translate "%s"', $message), 0, $throwable); - } - } - /** @return array