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).

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.
     */
    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.

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 = 'en';

    /**
     * @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, or
     * its first element isn't a string.
     */
    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.

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 10/10/2026 by Anonymous