Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laminas Translator Laravel Package

laminas/laminas-translator

Laminas Translator provides a locale-aware translation layer for PHP applications, with adapters, pluralization support, and integration with Laminas components for managing translated messages and catalogs.

View on GitHub
Deep Wiki
Context7

Getting Started

First, calibrate expectations: laminas/laminas-translator is an interface-only package. It contains Laminas\Translator\TranslatorInterface (two methods: translate() and translatePlural()) and, since 1.3.0, a DEFAULT_TEXT_DOMAIN constant. There is no translator implementation, no file loader, no service provider, and no Laravel bindings. Installing it adds roughly a single PHP file to vendor/.

composer require laminas/laminas-translator

Then read vendor/laminas/laminas-translator/src/TranslatorInterface.php before writing any code — the method signatures differ between the 1.x line (untyped parameters) and 2.0.0 (native parameter/return types), and this determines whether your adapter will fatal-error on the next composer update.

The one realistic day-to-day use case in a Laravel app is decoupling a shared/domain package from Illuminate\Contracts\Translation\Translator. If you maintain a library that is consumed by both Laminas/Mezzio and Laravel projects, you type-hint Laminas\Translator\TranslatorInterface inside that library, and in Laravel you register an adapter that wraps the framework's translator.

namespace App\I18n;

use Illuminate\Contracts\Translation\Translator as LaravelTranslator;
use Laminas\Translator\TranslatorInterface;

final class LaminasTranslatorAdapter implements TranslatorInterface
{
    public function __construct(private LaravelTranslator $translator) {}

    public function translate(string $message, ?string $textDomain = 'default', ?string $locale = null): string
    {
        return (string) $this->translator->get($message, [], $locale);
    }

    public function translatePlural(
        string $singular,
        string $plural,
        int $number,
        ?string $textDomain = 'default',
        ?string $locale = null
    ): string {
        return (string) $this->translator->choice("{$singular}|{$plural}", $number, [], $locale);
    }
}

Bind it once in a service provider and the rest of your app (and any shared package) can resolve the interface by type:

// AppServiceProvider::register()
$this->app->bind(\Laminas\Translator\TranslatorInterface::class, LaminasTranslatorAdapter::class);

Implementation Patterns

Adapter wrapping Illuminate\Contracts\Translation\Translator. This is the canonical pattern — shown above. It keeps your Laravel code idiomatically Laravel (you keep lang/, @lang, validation messages, trans_choice, php artisan lang:publish) while exposing the Laminas contract to packages that expect it.

Publishing a framework-agnostic package. The real value of the package is that a composer library author can depend on a translator contract without pulling in laminas-i18n or Laravel. Your library's constructor takes Laminas\Translator\TranslatorInterface; host applications provide the implementation. In Laravel, that implementation is the adapter above.

Text-domain mapping. Laminas passes a $textDomain argument per call; Laravel keys translations by file group (lang/en/messages.php → __('messages.welcome')). The two concepts are close but not identical. The pragmatic mapping is to treat $textDomain as the Laravel file group and concatenate: $this->translator->get("{$textDomain}.{$message}", [], $locale). If you never use multiple domains, ignore the parameter entirely rather than half-implementing it — inconsistent mapping is worse than no mapping.

Locale handling. Laminas passes a locale per call; Laravel has a global app locale but its get()/choice() methods accept a $locale argument directly. Always forward $locale into that third/fourth parameter rather than calling App::setLocale() — the latter mutates global state and is unsafe in queued jobs, Octane workers, and concurrent requests.

Testing. Because it's a two-method interface, write a trivial in-memory fake for unit tests instead of mocking:

final class ArrayTranslator implements TranslatorInterface
{
    public function __construct(private array $catalog) {}

    public function translate(string $message, ?string $textDomain = 'default', ?string $locale = null): string
    {
        return $this->catalog[$textDomain][$message] ?? $message;
    }

    public function translatePlural(string $s, string $p, int $n, ?string $td = 'default', ?string $l = null): string
    {
        return $n === 1 ? $s : $p;
    }
}

Keeping translation out of Blade. Do not try to reach for this interface inside Blade templates — use __() / @lang there. Only type-hint the interface in services, actions, jobs, and mailables, so you retain the option of swapping implementations.

Gotchas and Tips

  • This is not the translator you are looking for. If you wanted something that actually loads CSV/XLIFF/Gettext catalogs and applies plural rules, you want laminas/laminas-i18n (Laminas\I18n\Translator\Translator), which implements this interface. laminas-translator is only the contract.
  • 2.0.0 adds native types — a silent breaking change for implementers. If you wrote an adapter against 1.x (public function translate($message, $textDomain = 'default', $locale = null)), upgrading to 2.0.0 produces a PHP fatal error about incompatible signatures. Pin the major version in composer.json and treat any bump as a migration.
  • DEFAULT_TEXT_DOMAIN. Introduced in 1.3.0. Use the constant (rather than the string 'default') in default parameter values so your adapter and any Laminas-side code stay in sync.
  • Two contracts, different return types. Illuminate\Contracts\Translation\Translator::get() returns array|string; the Laminas interface returns string. Cast with (string) in the adapter — otherwise a caller passing an array key gets an array back and PHP 8 strict types blow up downstream.
  • Pluralisation rules don't line up. Laravel's MessageSelector uses interval syntax ({1} one|[2,*] many); Laminas provides translatePlural($singular, $plural, $number) with its own rule set. Concatenating to "{$singular}|{$plural}" works for two-form languages (English, German) but is wrong for Russian, Polish, Arabic, and other multi-form locales. If you need those, build the interval string explicitly from the locale, or accept that this adapter is English-only and document it.
  • No configuration, no provider, no facade. Don't hunt for config/laminas-translator.php. There is nothing to publish. The only setup is your own container binding.
  • Low adoption is the actual risk. The interface itself is ten lines and cannot meaningfully break you. The risk is the maintenance signal (3 stars, 2 contributors, one of them a bot). Mitigate cheaply: define your own application-local interface with the same two methods, and let this package be an optional bridge — that way a future abandonment costs you a one-file replace instead of a rewrite.
  • Debugging "translation not happening": every conforming implementation returns the input message when a key is missing. If translate() echoes your string back, the bug is in your catalog or group-name mapping, not in this package.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
gmostafa/php-graphql-client
xeviant/paystack
snowsoft/faker
meditationsteps/blogetc
animalyoung/laravel-influxdb
onix-solutions/sms-telera
lonely-lights/prosetta
oxygen/data
oxygen/core
jengo/base
xiloryx/log-collector
a21ns1g4ts/filament-stripe
la-php/laravel-admin
artisan-build/community-prompts
vleroy/smart
ayles-software/laravel-australian-public-holidays
cboxdk/laravel-id-spatie
cboxdk/laravel-id-billing
cboxdk/laravel-id-billing-bridge
simple-as-fuck/php-performance-log