laminas/laminas-mvc-i18n
Laminas MVC I18n integrates laminas-i18n with laminas-mvc, wiring translators, locale detection, and translated validators and route messages into your application via configurable service managers and view helpers.
laminas/laminas-i18n if needed):composer require laminas/laminas-mvc-i18n
config/modules.config.php; laminas-component-installer may add it automatically:return [
'Laminas\Mvc\I18n',
// ...
];
config/autoload/global.php:// module/Application/config/module.config.php
return [
'translator' => [
'locale' => 'en_US',
'translation_file_patterns' => [
[
'type' => 'phparray',
'base_dir' => __DIR__ . '/../language',
'pattern' => '%s.php',
],
],
],
];
module/Application/language/en_US.php:<?php
return [
'Hello' => 'Hello',
'Welcome, %s' => 'Welcome, %s',
];
<?= $this->translate('Hello') ?>
<?= $this->translatePlural('One item', '%d items', $count) ?>
Where to look first:
vendor/laminas/laminas-mvc-i18n/src/ConfigProvider.php shows the service aliases that make this package useful: translator, MvcTranslator, and Laminas\I18n\Translator\TranslatorInterface.translator config controls locale and translation file discovery.vendor/laminas/laminas-mvc-i18n/src/Translator.php and DummyTranslator.php show the runtime translator wrapper and its no-op counterpart.The Laravel mental model: MvcTranslator is roughly app('translator'), $this->translate() is roughly __(), and translation_file_patterns is roughly Laravel’s lang/ discovery. The package is a thin wiring layer, not a full localization framework.
Set the locale after routing, because route params are not available during bootstrap. Attach a listener to MvcEvent::EVENT_ROUTE:
namespace Application\Listener;
use Laminas\Mvc\MvcEvent;
class LocaleListener
{
public function onRoute(MvcEvent $event): void
{
$routeMatch = $event->getRouteMatch();
$locale = $routeMatch?->getParam('locale', 'en_US') ?? 'en_US';
$event->getApplication()
->getServiceManager()
->get('MvcTranslator')
->setLocale($locale);
}
}
Register it in Module::onBootstrap():
public function onBootstrap(MvcEvent $event): void
{
$events = $event->getApplication()->getEventManager();
$events->attach(MvcEvent::EVENT_ROUTE, [new LocaleListener(), 'onRoute'], 1);
}
If locale comes from a session, user profile, or Accept-Language, resolve it in the same listener. For a simple route prefix:
'route' => '/{locale}/about',
'defaults' => ['locale' => 'en_US'],
en_US.php, de_DE.php, fr_FR.php.<?= $this->translate('Save', 'Application') ?>
Configure a domain by adding 'text_domain' => 'Application' to the file pattern, then pass the same domain in the helper.
phparray for small translation sets and gettext for larger sets or plural-heavy content. Gettext’s .mo files handle plural rules better than hand-written PHP arrays.The module aliases the laminas-i18n translator interface to MvcTranslator, so built-in validator messages resolve through the MVC translator. If you use custom validators, inject the translator or rely on the default validator translator.
Form labels are not universally auto-translated. Be explicit:
$form->add([
'name' => 'name',
'type' => 'Text',
'options' => [
'label' => $this->translator->translate('Name'),
],
]);
Or translate labels in the view template with $this->translate(). If your form object supports a translator, set it once and keep labels as translation keys.
For localized URL segments, the package ships Laminas\Mvc\I18n\Router\TranslatorAwareTreeRouteStack. Use it only if you need translated route paths; otherwise leave the default router alone.
Conceptually, route paths become translation keys. Each locale must provide a translation for every route path, or matching/assembly can break. Test route assembly per locale:
$url = $this->url('about', ['locale' => 'de_DE']);
If you adopt it, configure the router class or use a delegator to inject the MvcTranslator into the route stack. Keep a fallback locale and verify both match() and assemble().
The same MvcTranslator service is available in console commands. Set the locale from a CLI option or config before translating.
In tests, set the locale explicitly in the bootstrap. If you want translation to be a no-op, the package provides DummyTranslator:
$translator = new \Laminas\Mvc\I18n\DummyTranslator();
translator service. The module aliases translator, MvcTranslator, and Laminas\I18n\Translator\TranslatorInterface. Replacing translator with a custom implementation can break view helpers, validators, and form integrations unless it implements the expected interfaces.onBootstrap is too early to read route params. Use MvcEvent::EVENT_ROUTE for route-derived locales, or EVENT_DISPATCH if you only need the locale inside controllers/views.cache option. Enable it for production, but clear it on deploy. File-mtime-based caches can serve stale translations after a deployment.translate() returns the input key when no translation exists. Add a decorator or logging wrapper around MvcTranslator during development to surface missing keys.default.TranslatorAwareTreeRouteStack, every locale needs route-path translations. A missing translation can cause a route not to match or a URL to assemble incorrectly. Test assemble() and match() for each supported locale.laminas/laminas-i18n directly with a PSR-11 factory and your own middleware for locale negotiation.laminas-i18n for translation loading, plural rules, and caching.MvcTranslator via a custom factory, decorate it to log missing keys, register custom view helper factories, or add a delegator around the router to inject the translator into TranslatorAwareTreeRouteStack.How can I help you explore Laravel packages today?