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.5
  • ext-intl (PHP’s ICU bindings, for \MessageFormatter)
  • symfony/translation-contracts: ^3.7
  • symfony/yaml: ^8.1
  • symfony/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.

On this page

Last updated on 30/10/2026 by Anonymous