Install the Bundle
composer require arxy/translations-bundle
Register in config/bundles.php (Symfony 4.3+):
return [
// ...
Arxy\TranslationsBundle\ArxyTranslationsBundle::class => ['all' => true],
];
Define Database Entities
Copy the provided Language, Token, and Translation entity classes into src/Entity/ and update namespace references.
Create a Custom Repository
Extend Arxy\TranslationsBundle\Repository and implement findByLocale() and persistCatalogue() methods (see example in README). Place it in src/Repository/TranslationRepository.php.
Run Migrations
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate
Import Translations Update your database with translations from YAML/JSON files:
php bin/console translation:update --output-format="db" en --force --no-interaction --prefix=
Inject the repository into a service/controller:
use App\Repository\TranslationRepository;
class MyService
{
public function __construct(private TranslationRepository $translationRepo) {}
public function getTranslations(string $locale): array
{
return iterator_to_array($this->translationRepo->findByLocale($locale));
}
}
Store Translations
Use persistCatalogue() to import translations from Symfony’s MessageCatalogueInterface (e.g., from YAML files):
$catalogue = $translator->getCatalogue('en');
$this->translationRepo->persistCatalogue($catalogue);
Retrieve Translations
Fetch translations for a locale via findByLocale():
$translations = $this->translationRepo->findByLocale('fr');
foreach ($translations as $translation) {
echo $translation->getTranslation(); // Output: "Bonjour"
}
Dynamic Translation Loading Integrate with Symfony’s translator:
# config/services.yaml
services:
App\Translation\Loader:
arguments:
- '@App\Repository\TranslationRepository'
Extend Symfony\Component\Translation\Loader\LoaderInterface to load translations from the database.
--prefix in translation:update to organize translations by domain (e.g., messages, validation).null in findByLocale().findByLocale() to reduce database load:
$cacheKey = 'translations_' . $locale;
if (!$translations = $cache->get($cacheKey)) {
$translations = iterator_to_array($this->translationRepo->findByLocale($locale));
$cache->set($cacheKey, $translations, 3600);
}
Entity Naming Conflicts
Ensure your Token and Translation entities do not conflict with existing Doctrine mappings. Use unique table names if needed:
@ORM\Table(name="app_translation_tokens")
Locale-Specific Tokens
The bundle assumes tokens are locale-agnostic (stored in Token table). If you need locale-specific tokens, extend the schema or use a separate bundle.
BC Breaks in persistCatalogue()
The method expects MessageCatalogueInterface and will skip existing tokens (checked via exists()). Override this behavior if needed:
public function persistCatalogue(MessageCatalogueInterface $catalogue): void {
// Custom logic (e.g., update existing tokens)
}
Symfony 6+ Compatibility
The bundle targets Symfony 5.x (last release: 2021). For Symfony 6+, test with symfony/translation-contracts and adjust service wiring.
Missing Translations
Verify the locale column in languages table matches your input (e.g., en, fr_FR). Use:
php bin/console doctrine:query:sql "SELECT * FROM languages"
Duplicate Tokens
The uniqueConstraints on translations table prevents duplicates. If duplicates appear, check persistCatalogue() for logic errors.
Performance Issues
Optimize findByLocale() with indexes:
CREATE INDEX idx_translations_language ON translations(language_id);
CREATE INDEX idx_translations_token ON translations(token_id);
Custom Translation Models
Extend TranslationModel to add metadata (e.g., created_at):
class ExtendedTranslationModel extends TranslationModel {
public function __construct(string $translation, string $token, string $catalogue, string $createdAt) {
parent::__construct($translation, $token, $catalogue);
$this->createdAt = $createdAt;
}
}
Translation Events Dispatch events when translations are persisted/updated:
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
class TranslationRepository {
public function __construct(
private EventDispatcherInterface $dispatcher
) {}
public function persistCatalogue(MessageCatalogueInterface $catalogue): void {
// ... existing logic ...
$this->dispatcher->dispatch(new TranslationUpdatedEvent($catalogue));
}
}
API Endpoints Expose translations via API:
#[Route('/api/translations/{locale}', methods: ['GET'])]
public function getTranslations(string $locale, TranslationRepository $repo): JsonResponse {
return new JsonResponse(iterator_to_array($repo->findByLocale($locale)));
}
How can I help you explore Laravel packages today?