lexik/translation-bundle
Symfony bundle to manage translations in a database: import from xliff/yml/php, edit via a web GUI, track missing domain translations, add new keys, and export back to files. Database loader overrides file-based translations.
Install the Bundle:
composer require lexik/translation-bundle
Ensure doctrine/doctrine-bundle and symfony/security-csrf, symfony/form, symfony/twig-bundle, and symfony/asset are installed (required for the UI).
Configure the Bundle:
Add to config/packages/lexik_translation.yaml:
lexik_translation:
locales: ['en', 'fr', 'de']
fallback_locales: ['en']
loaded_locales: ['en', 'fr']
sources:
- 'translations'
default_locale: 'en'
Create a Migration: Run the bundle’s migration command to set up the database tables:
php bin/console lexik:translation:import --source=translations --locale=en
First Use Case:
Import existing translation files (e.g., translations/messages.en.yml) into the database:
php bin/console lexik:translation:import --source=translations --locale=en
Access the GUI at /translation to edit translations dynamically.
index.md for installation and configuration./translation route provides a grid-based editor for translations.lexik:translation:import: Load translations from files into the database.lexik:translation:export: Export database translations back to files.lexik:translation:purge: Clear translations for a locale/domain.Import Translations:
Use the import command to migrate existing .yml, .xliff, or .php files into the database. Example:
php bin/console lexik:translation:import --source=translations --locale=en --domain=messages
Configure Fallback Locales:
Set fallback_locales in lexik_translation.yaml to ensure untranslated strings fall back to a default (e.g., English).
Edit Translations via GUI:
Non-technical team members (e.g., translators, content editors) use the /translation interface to update strings. The GUI supports:
Override File-Based Translations: Database translations take precedence over file-based ones. Use this to:
Export for Version Control: Periodically export translations back to files for backup or collaboration:
php bin/console lexik:translation:export --source=translations --locale=fr --domain=validation
Customize the Translator:
Extend the DatabaseLoader to add logic (e.g., logging translation access or caching strategies). Example:
// config/services.yaml
services:
App\Translation\CustomDatabaseLoader:
decorates: 'lexik_translation.database_loader'
arguments: ['@lexik_translation.database_loader.inner']
Add Translation Domains Dynamically: Use Doctrine events to auto-create domains for new bundles. Example listener:
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
public function loadClassMetadata(LoadClassMetadataEventArgs $event): void
{
$classMetadata = $event->getClassMetadata();
if ($classMetadata->getReflectionClass()->getNamespace() === 'App\Translation') {
$this->translationManager->addDomain('app');
}
}
Localize Routes/Entities:
Use Symfony’s translator service with the bundle’s loader:
{{ 'app.welcome'|trans({ '%name%': user.name }) }}
The bundle ensures database translations override file-based ones.
Translation Workflows: Use Doctrine lifecycle events to trigger actions (e.g., notify Slack when a translation is updated):
use Doctrine\ORM\Event\OnFlushEventArgs;
public function onFlush(OnFlushEventArgs $event): void
{
$entityManager = $event->getEntityManager();
$changes = $entityManager->getUnitOfWork()->getScheduledEntityUpdates();
foreach ($changes as $entity) {
if ($entity instanceof Translation) {
$this->slackClient->sendMessage("Translation updated: {$entity->getId()}");
}
}
}
Multi-Tenant Translations:
Extend the Translation entity to include a tenant_id field and filter queries accordingly:
// src/Entity/Translation.php
/**
* @ORM\Column(type="integer")
*/
private ?int $tenantId = null;
Translation Validation:
Add constraints to the Translation entity to enforce rules (e.g., max length):
use Symfony\Component\Validator\Constraints as Assert;
/**
* @Assert\Length(max=255)
*/
private ?string $content = null;
Symfony Flex Recipes: Use the bundle’s Symfony Flex recipe to auto-configure routes and services.
Translation Domains:
translations/messages.en.yml → messages domain).# config/packages/lexik_translation.yaml
lexik_translation:
domains:
- 'app'
- 'validation'
Caching:
# config/packages/framework.yaml
framework:
translator:
paths: ['%kernel.project_dir%/translations']
caching: true
Testing:
DatabaseLoader in tests to avoid hitting the database:
$this->container->set('lexik_translation.database_loader', $this->createMock(DatabaseLoader::class));
Deployment:
export command to sync translations between environments:
# Export from production
php bin/console lexik:translation:export --source=translations --locale=fr --domain=messages --output-dir=backups/prod
# Import into staging
php bin/console lexik:translation:import --source=backups/prod/messages.fr.yml --locale=fr --domain=messages
Database vs. File Precedence:
export command to sync files with the database periodically, or document the precedence rules for your team.Locale Loading:
loaded_locales setting must include all locales you want to edit via the GUI. Missing locales won’t appear in the UI.loaded_locales matches your locales setting:
lexik_translation:
locales: ['en', 'fr', 'de']
loaded_locales: ['en', 'fr', 'de'] # Must match
Doctrine Events:
preUpdate) on the Translation entity may conflict with the bundle’s internal logic.postUpdate or postPersist instead to avoid race conditions.GUI Performance:
/translation route can be slow for large translation sets (>10,000 entries).setMaxResults().indexBy hint to queries to avoid N+1 issues:
$query->setHint('indexBy', ['locale', 'domain', 'id']);
Symfony 6+ Deprecations:
v8.0+ for Symfony 6/7/8 compatibility. Check the changelog for breaking changes.**Translation Keys
How can I help you explore Laravel packages today?