API Reference
Complete reference for every class and interface derafu/translation
ships.
Interfaces
TranslatableInterface
Derafu\Translation\Contract\TranslatableInterface. Extends Symfony’s own
Symfony\Contracts\Translation\TranslatableInterface and adds
Stringable, so a translatable value can always be cast to a string (the
untranslated, eagerly ICU-formatted version) even without a translator.
interface TranslatableInterface extends
\Symfony\Contracts\Translation\TranslatableInterface,
Stringable
{
public function __toString(): string;
}
Symfony\Contracts\Translation\TranslatableInterface itself requires:
public function trans(TranslatorInterface $translator, ?string $locale = null): string;
TranslationResourceProviderInterface
Derafu\Translation\Contract\TranslationResourceProviderInterface.
Declares directories containing translation files, meant to be collected
(e.g. via a dependency-injection tagged iterator) so
TranslationResourceRegistrar can register every directory without a
central place having to know about each source in advance.
interface TranslationResourceProviderInterface
{
/**
* @return iterable<string> Absolute paths to directories with files
* named `domain(+intl-icu)?.locale.format`.
*/
public function getDirectories(): iterable;
}
TranslatableMessageInterface
Derafu\Translation\Contract\TranslatableMessageInterface. A message that
can be read: its id, its parameters and its domain. It is what is behind
TranslatableMessage and what a translatable exception is made of. It lets a
message be logged by its id and its parameters, sent as data (for example to an
API client that translates by itself), or checked in a test without a
translator.
interface TranslatableMessageInterface extends TranslatableInterface, JsonSerializable
{
public function getMessage(): string;
/** @return array<string, mixed> */
public function getParameters(): array;
public function getDomain(): ?string;
public function getDefaultLocale(): ?string;
public function jsonSerialize(): array;
}
A message is also exported as data, so json_encode($message) gives its id, its
parameters, its domain and its default locale, and not an empty object:
{"message":"Hello {name}","parameters":{"name":"Ana"},"domain":"auth","defaultLocale":null}
It is a representation to read, for logs, APIs and the stores that keep JSON (the
session of Mezzio, for example). The message is not rebuilt from it: a view
translates from the data, message.message|trans(message.parameters, message.domain).
A parameter that is a translatable message, or a translatable exception, is
exported as its own data; any other exception, as its message. What is not JSON (a
date, an object, a resource) stays as json_encode() makes it, so it does not come
back as it was.
TranslatableAwareInterface
Derafu\Translation\Contract\TranslatableAwareInterface. Something that is
made of a translatable value and gives it:
interface TranslatableAwareInterface
{
public function getTranslatable(): TranslatableInterface;
}
TranslatableMessageAwareInterface
Derafu\Translation\Contract\TranslatableMessageAwareInterface. Extends
TranslatableAwareInterface for the objects whose translatable value is a
message that can be read:
interface TranslatableMessageAwareInterface extends TranslatableAwareInterface
{
/** @throws TranslatableLogicException If the value is not a message. */
public function getTranslatableMessage(): TranslatableMessageInterface;
}
Every translatable exception and error of the package implements it (see Exceptions).
HeadersAwareInterface
Derafu\Translation\Contract\HeadersAwareInterface. Something that carries the
headers of an HTTP response and gives them: a translatable exception that an HTTP
layer turns into a response, for example (WWW-Authenticate, Retry-After).
It is in this package and not in one of HTTP so an exception that has nothing to
do with HTTP can still carry them, and a package of HTTP can read them from any
exception without knowing it. HeadersAwareTrait implements it.
interface HeadersAwareInterface
{
/** @param array<string, string|list<string>> $headers */
public function setHeaders(array $headers): static;
/** @return array<string, string|list<string>> The headers, or none. */
public function getHeaders(): array;
}
The value of a header is a string, or a list of strings for a header with several values (Set-Cookie, Link), as PSR-7 has them. A value that is not text, a number for example, is converted to a string by whoever sets it.
Classes
TranslatableMessage
Derafu\Translation\TranslatableMessage. A message with ICU placeholders
that can be translated using a translator, or eagerly ICU-formatted (as-is)
when no translator is available.
final class TranslatableMessage implements TranslatableMessageInterface
{
/**
* @param string $message ICU MessageFormat string, used as the
* translation id and as the fallback text.
* @param array<string, mixed> $parameters ICU placeholder values.
* @param string|null $domain Translation domain, null for Symfony's
* default ('messages').
* @param string|null $defaultLocale Locale used for eager ICU
* formatting in __toString() when no translator is available, and as
* the fallback locale in trans() when none is given.
*/
public function __construct(
string $message,
array $parameters = [],
?string $domain = null,
?string $defaultLocale = null,
);
/**
* Translates the message. If the translator knows its catalogues
* (`TranslatorBagInterface`) and has no entry for the message (in the
* locale, its fallbacks and the domain), returns what __toString()
* returns instead of letting the translator substitute the parameter
* names inside the braces.
*/
public function trans(TranslatorInterface $translator, ?string $locale = null): string;
/**
* Formats the raw message via ICU, without a translator. Returns the
* raw message unchanged if the pattern is invalid or if ICU can not format
* it with the parameters (a parameter that is not a date for `{d, date}`).
*/
public function __toString(): string;
// The getters and the data of TranslatableMessageInterface.
public function getMessage(): string;
public function getParameters(): array;
public function getDomain(): ?string;
public function getDefaultLocale(): ?string;
public function jsonSerialize(): array;
}
A parameter that is a Throwable is its message, not its __toString() (the
dump that PHP makes of an exception, with its file, line and trace). A
translatable exception is translated by the translator when it has the entry, and
without one it is formatted with its message.
TranslatableList
Derafu\Translation\TranslatableList. A translatable value that has several
translatable values inside, joined in one text. It is for what is not known when
the code is written: how many items there are. They can not be the parameters of
a message, one by one, and they can not be joined before they are translated,
because then they would be joined in the language they were written in. A list
keeps them as they are, and translates each one and joins them when the
translator and the language are known.
It is created with one of two named constructors, because there are two ways to join the items:
final readonly class TranslatableList implements TranslatableInterface, JsonSerializable
{
/**
* Short items: "red, green and blue". The separator and the last
* conjunction are the ones of the language.
*
* @param iterable<TranslatableInterface|string> $items
* @param int $type IntlListFormatter::TYPE_AND, TYPE_OR or TYPE_UNITS.
* @param int $width IntlListFormatter::WIDTH_WIDE, WIDTH_SHORT or WIDTH_NARROW.
* @throws InvalidArgumentException If the type or the width is not valid.
*/
public static function enumeration(
iterable $items,
int $type = IntlListFormatter::TYPE_AND,
int $width = IntlListFormatter::WIDTH_WIDE
): self;
/**
* Items that are sentences, like errors: they are joined as they are, with
* the separator. Their text is not changed.
*
* @param iterable<TranslatableInterface|string> $items
*/
public static function separated(iterable $items, string $separator = ' '): self;
public function trans(TranslatorInterface $translator, ?string $locale = null): string;
public function __toString(): string;
public function jsonSerialize(): array;
}
See Lists of messages for when to use each one.
TranslationResourceRegistrar
Derafu\Translation\TranslationResourceRegistrar. Discovers translation
files in directories and registers them as resources of a
Symfony\Component\Translation\Translator, using the LoaderInterfaces
registered on it. This is the piece symfony/translation doesn’t provide
standalone (directory discovery is normally wired by a full framework).
final class TranslationResourceRegistrar
{
public function __construct(private readonly Translator $translator);
/**
* Registers every file directly inside a directory.
*
* @throws InvalidArgumentException If the directory doesn't exist, a
* file's name doesn't follow the `domain.locale.format` convention, or
* its extension isn't supported.
*/
public function registerDirectory(string $directory): void;
/**
* @param iterable<string> $directories
*/
public function registerDirectories(iterable $directories): void;
/**
* @param iterable<TranslationResourceProviderInterface> $providers
*/
public function registerFromProviders(iterable $providers): void;
/** @return array<string> Every locale registered so far. */
public function getRegisteredLocales(): array;
/** @return array<string> Every domain registered so far. */
public function getRegisteredDomains(): array;
}
Registration order is precedence order: for the same key in the same domain/locale, the last directory registered wins.
Supported file extensions: yaml, yml, json, php, xlf, xliff,
po, mo, csv, ini.
SimpleTranslationResourceProvider
Derafu\Translation\SimpleTranslationResourceProvider. Default
implementation of TranslationResourceProviderInterface that wraps a
fixed list of directories.
final class SimpleTranslationResourceProvider implements TranslationResourceProviderInterface
{
/** @param iterable<string> $directories */
public function __construct(private readonly iterable $directories);
public function getDirectories(): iterable;
}
TranslatorFactory
Derafu\Translation\TranslatorFactory. Builds a
Symfony\Component\Translation\Translator with every file loader
TranslationResourceRegistrar supports already registered, plus the given
resource providers, plus fallback locales.
final class TranslatorFactory
{
/**
* @param array<string> $fallbackLocales
* @param iterable<TranslationResourceProviderInterface> $resourceProviders
* Registered immediately, inside this call, so registration always
* happens whenever a Translator is built — regardless of whether
* anything else references a registrar directly (a dependency-injected
* service nothing depends on is never instantiated by the container).
*/
public static function create(
string $defaultLocale,
array $fallbackLocales = [],
iterable $resourceProviders = [],
): Translator;
}
Trait
TranslatableExceptionTrait
Derafu\Translation\Trait\TranslatableExceptionTrait. Adds translation
support to any exception. Used internally by every exception class listed
below — apply it directly only if you can’t extend one of them (see
Exceptions).
trait TranslatableExceptionTrait
{
protected string $defaultDomain = 'errors';
protected ?string $defaultLocale = null;
/**
* @param string|array|TranslatableInterface $message
* - string: used as both message and translation id.
* - array: first element is the message/id, the rest are named ICU
* parameters.
* - TranslatableInterface: used directly.
* @throws InvalidArgumentException When an empty array is given, its
* first element isn't a string, or a parameter has no name (a number
* as its key).
*/
public function __construct(
string|array|TranslatableInterface $message,
int $code = 0,
?Throwable $previous = null,
);
public function trans(TranslatorInterface $translator, ?string $locale = null): string;
/** @return array<string, mixed> */
public function __serialize(): array;
/** @param array<string, mixed> $data */
public function __unserialize(array $data): void;
}
getTranslatable() and getTranslatableMessage() give what the exception
translates with (see Exceptions).
__serialize()/__unserialize() intentionally drop the stack trace (it
may contain non-serializable values such as closures or resources), so
these exceptions can safely be stored in sessions (e.g. for flash
messages). After unserializing, getTrace() no longer reflects the
exception’s original call stack.
__unserialize() validates the data that it receives (the message, the code,
the file, the line, the domain, the locale and the translatable value): data
that is not valid, from a session that was altered or from another version, is a
TranslatableInvalidArgumentException and not an exception in an inconsistent
state.
HeadersAwareTrait
Derafu\Translation\Trait\HeadersAwareTrait. Implements HeadersAwareInterface:
it keeps the headers (protected array $headers = [], none by default) and
gives them with setHeaders() and getHeaders(). Apply it to an exception that
has to carry the headers of an HTTP response.
final class RateLimitedException extends TranslatableRuntimeException implements HeadersAwareInterface
{
use HeadersAwareTrait;
}
throw (new RateLimitedException('Too many requests.'))->setHeaders(['Retry-After' => '30']);
Exception Hierarchy
Every class below implements TranslatableInterface via
TranslatableExceptionTrait, and mirrors the equivalent native PHP SPL
exception:
| Class | Extends |
|---|---|
Exception\Core\TranslatableException |
Exception |
Exception\Core\TranslatableLogicException |
LogicException |
Exception\Core\TranslatableRuntimeException |
RuntimeException |
Exception\Core\TranslatableJsonException |
JsonException |
Exception\Logic\TranslatableBadFunctionCallException |
BadFunctionCallException |
Exception\Logic\TranslatableBadMethodCallException |
BadMethodCallException |
Exception\Logic\TranslatableDomainException |
DomainException |
Exception\Logic\TranslatableInvalidArgumentException |
InvalidArgumentException |
Exception\Logic\TranslatableLengthException |
LengthException |
Exception\Logic\TranslatableOutOfRangeException |
OutOfRangeException |
Exception\Runtime\TranslatableOutOfBoundsException |
OutOfBoundsException |
Exception\Runtime\TranslatableOverflowException |
OverflowException |
Exception\Runtime\TranslatableRangeException |
RangeException |
Exception\Runtime\TranslatableUnderflowException |
UnderflowException |
Exception\Runtime\TranslatableUnexpectedValueException |
UnexpectedValueException |
All under the Derafu\Translation\Exception\ namespace.
The \Error family has its equivalents, with the same trait, under
Derafu\Translation\Error\:
| Class | Extends |
|---|---|
TranslatableError |
Error |
TranslatableTypeError |
TypeError |
TranslatableValueError |
ValueError |
TranslatableArithmeticError |
ArithmeticError |
TranslatableDivisionByZeroError |
DivisionByZeroError |
Lint
Tools for tests, never for the code that runs. They need
nikic/php-parser. See Lint for the conventions, what not to do and
the known limits.
| Class | What it is |
|---|---|
Lint\TranslationAudit |
audit(string $directory, TranslationResourceProviderInterface|iterable $providers, string $locale = 'es', array $allowedThrowables = [], array $messageMethods = []): TranslationAuditReport. Messages, catalogues and exceptions in one call. |
Lint\TranslationAuditReport |
The facts that an audit found: dynamicMessages, missingTranslations, notUsedBySources, notTranslatable, nothingFound; and describe(array $findings): list<string> to turn them into lines for a failed test. |
Lint\MessageMethod |
A method of a package that receives the id of a message: __construct(string $class, string $method, ?string $domain = null, int|string $id = 0, int|string|null $domainArgument = null). |
Lint\MessageReferenceScanner |
__construct(list<MessageMethod> $methods = []), scanFile(), scanDirectory(): the messages that the code builds. |
Lint\MessageReference |
A message found: id, domain, class, file, line, function, expression, isDynamic(), identity(). |
Lint\ThrowReferenceScanner |
scanFile(), scanDirectory(): the exceptions that are not translatable, thrown or declared. |
Lint\ThrowReference |
An exception found: class, extends, file, line, loadable, isDeclaration(). |
Dependencies
derafu/translation requires:
php: ^8.5ext-intl(PHP’s ICU bindings, for\MessageFormatter)symfony/translation-contracts: ^3.7symfony/yaml: ^8.1symfony/translation: ^8.1
No dependency on symfony/framework-bundle, symfony/http-kernel, or
symfony/dependency-injection — the dependency-injection recipe described
in Advanced Usage is entirely optional wiring that lives
in the consuming application.