symfony/translation-contracts
Symfony Translation Contracts provides lightweight interfaces and abstractions for translation in PHP, extracted from Symfony components. Use it to build interoperable, battle‑tested translation integrations while staying framework-agnostic and compatible with Symfony implementations.
Installation: Add the package via Composer:
composer require symfony/translation-contracts
This provides only interfaces (TranslatorInterface, TranslatableInterface, MessageCatalogueInterface)—no runtime code.
First Use Case: Leverage it to decouple Laravel’s native translation system from Symfony’s implementation. For example, replace Symfony\Component\Translation\Translator with a custom adapter implementing TranslatorInterface for third-party services (e.g., AWS Translate).
Key Files:
src/Translation/TranslatorInterface.php: Core contract for translation logic.src/Translation/TranslatableInterface.php: For deferred translation (e.g., in Livewire components).src/Translation/MessageCatalogueInterface.php: For managing locale/message domains.Quick Integration: Bind your custom translator in Laravel’s container:
$app->bind(
TranslatorInterface::class,
CustomAwsTranslator::class
);
Use it via dependency injection:
public function __construct(private TranslatorInterface $translator) {}
TranslatorInterface to wrap non-Symfony providers (e.g., Google Cloud Translation):
class GoogleTranslateAdapter implements TranslatorInterface {
public function trans(string $id, array $parameters = [], string $domain = 'messages', string $locale = null): string {
// Call Google API, return translated string.
}
}
Illuminate\Translation\FileLoader to use your adapter via TranslatorInterface.TranslatableInterfaceSymfony\Contracts\Translation\TranslatableMessage in Livewire/Blade:
$message = new TranslatableMessage('welcome', ['name' => 'John'], 'messages');
return view('page', ['message' => $message]);
{{ $message->trans($this->translator) }}
TranslatorInterface into services to handle dynamic locales:
public function getPaginatedResults(TranslatorInterface $translator) {
$translator->setLocale(request()->locale);
return $this->model->paginate(10)->map(fn ($item) =>
$translator->trans($item->title)
);
}
TranslatorInterface in unit tests:
$mockTranslator = Mockery::mock(TranslatorInterface::class);
$mockTranslator->shouldReceive('trans')
->with('greeting', ['name' => 'John'])
->andReturn('Hola, John!');
TranslatorTrait for built-in fallback behavior:
class FallbackTranslator implements TranslatorInterface {
use TranslatorTrait;
public function trans($id, array $parameters = [], $domain = 'messages', $locale = null) {
return $this->doTrans($id, $parameters, $domain, $locale);
}
}
symfony/translation or a custom adapter).trans() uses :placeholder, but Symfony’s TranslatorInterface expects ICU syntax ({var}). Validate your implementation’s format.transChoice()—implementations (like Symfony’s) add it separately. Document your adapter’s pluralization support.setLocale() isn’t called in multi-tenant apps.$mock->shouldReceive('trans')
->withArgs(function ($id, $params, $domain, $locale) {
return $locale === 'es' && $domain === 'validation';
});
TranslatorTrait to trace fallback chains:
protected function doTrans($id, array $parameters, $domain, $locale) {
logger()->debug("Translating [$id] for locale [$locale], domain [$domain]");
// ...
}
MessageCatalogueInterface to load translations from a database or API:
class ApiMessageCatalogue implements MessageCatalogueInterface {
public function get($id, $locale = null, $domain = 'messages') {
return Http::get("https://api.example.com/translate/{$id}?locale={$locale}");
}
}
TranslatorTrait::trans() to support custom interpolation (e.g., for HTML-safe escaping):
protected function trans($id, array $parameters = [], $domain = 'messages', $locale = null) {
$message = $this->getCatalogue()->get($id, $locale, $domain);
return strtr($message, $parameters); // Custom logic here
}
TranslatorInterface:
$app->bind(TranslatorInterface::class, function ($app) {
$translator = new CustomTranslator();
$translator->setLocaleResolver(function () {
return session()->get('locale', config('app.locale'));
});
return $translator;
});
Illuminate\Support\Facades\Trans with a wrapper:
Trans::setTranslator($app->make(TranslatorInterface::class));
MessageCatalogue responses if using remote APIs.symfony/translation for validation messages:
use Symfony\Contracts\Translation\TranslatorInterface;
$validator->setTranslator($translator);
public function render() {
$message = new TranslatableMessage('livewire.welcome');
return view('livewire.component', ['message' => $message]);
}
```markdown
How can I help you explore Laravel packages today?