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

Translation Bundle Laravel Package

trekels/translation-bundle

A Symfony bundle for managing application translations, letting you store and edit translation strings from a web interface instead of flat files. Useful for multi-language projects that need runtime editing.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the bundle with Composer and wire it into the kernel. Because this is a Symfony 2/3/4-era bundle (last tagged release November 2019), the exact registration style depends on your Symfony version:

composer require trekels/translation-bundle
// config/bundles.php
return [
    // ...
    Lexik\Bundle\TranslationBundle\LexikTranslationBundle::class => ['all' => true],
];

Point it at your translation directories and locales:

# config/packages/lexik_translation.yaml
lexik_translation:
    fallback_locale: [en]
    managed_locales: [en, fr, de]
    default_domain: messages
    resources_dir: "%kernel.project_dir%/translations"   # legacy: app/Resources/translations
    translation_key_structure: "%domain%.%locale%.%key%"
    storage:
        type: orm
        object_manager: doctrine.orm.default_entity_manager
    auto_cache_clean: true
    enable_ui: true
    enable_management: true
    base_layout: "admin/layout.html.twig"

Mount the routes behind your admin prefix and lock them down — the GUI ships enabled by default and will let anyone with access edit translations:

# config/routes/lexik_translation.yaml
lexik_translation:
    resource: "@LexikTranslationBundle/Resources/config/routing.yml"
    prefix:   /admin/translations
# config/packages/security.yaml
security:
    access_control:
        - { path: ^/admin, roles: ROLE_ADMIN }

Create the three tables the bundle expects (lexik_translation_unit, lexik_translation_translation, lexik_translation_file) from its Doctrine mappings, then import your existing YAML/XLIFF files:

php bin/console doctrine:schema:update --force     # or generate a migration
php bin/console lexik:translation:import --force

First use case: the canonical workflow is "files exist, humans need to edit them." Import pulls file content into the DB, the grid at /admin/translations/grid lets a non-developer edit copy in every locale side by side, and lexik:translation:export writes the result back out to resources_dir. Look first at config/packages/lexik_translation.yaml, then at the three DB tables — they are the real state.

Implementation Patterns

Decide who owns the truth. There are two viable operating modes and mixing them silently is how people lose translations:

  1. DB as runtime source — the bundle's translator loads managed locales from the database (falling back to files for anything not imported). Files become a seed/export artifact only.
  2. Files as source, DB as editor — developers commit files; the GUI is an editing surface; a deploy job runs import then editors work, and export is run and committed before release.

For a team, mode 2 with a CI check is the safest.

CI drift guard. Export to a temp dir and diff against what is committed; fail the build if the GUI has changes nobody exported:

php bin/console lexik:translation:export --path=/tmp/export
diff -ru translations/ /tmp/export/ || exit 1

Deployment sequence. import --force on deploy only if you intend file content to win; otherwise import before editors touch the grid and never run it again in that environment, or you will overwrite their work.

php bin/console lexik:translation:import --force --locale=fr
php bin/console lexik:translation:export --locale=fr
php bin/console lexik:translation:clean --dry-run

Programmatic writes. For seeding from an external CMS or a migration script, use the manager/storage services rather than raw SQL:

use Lexik\Bundle\TranslationBundle\Manager\TranslationManager;

public function __construct(private TranslationManager $translationManager) {}

public function seed(string $domain, string $key, string $locale, string $content): void
{
    $unit = $this->translationManager->getUnit($domain, $key, false)
        ?? $this->translationManager->newUnit($domain, $key);

    $this->translationManager->addTranslation($unit, $locale, $content);
    $this->translationManager->flush();
}

This keeps the lexik_translation_file rows and the hash bookkeeping consistent, which hand-written INSERTs do not.

Locale scoping. managed_locales governs what the grid shows and what --locale accepts. Keep it a subset of framework.translator.enabled_locales so the DB never serves a locale the app will not negotiate. The locale_provider extension point lets you restrict the GUI to locales your app actually ships.

Embedding the UI. base_layout lets the grid render inside your existing admin theme instead of the bundle's bare layout. Check the bundle's Resources/views/Translations/*.twig for the blocks it expects your layout to define; getting this wrong produces a blank page rather than an error.

Cache behaviour. With auto_cache_clean: true, saves trigger a translation cache invalidation (auto_cache_clean_batch_size chunks it for large catalogs). If you disable it for performance, you own the invalidation — cache:pool:clear cache.app or a full cache:clear after bulk edits, or editors will report "my change didn't save."

Gotchas and Tips

This package is effectively unmaintained. Zero stars, zero dependents, last release November 2019. It predates Symfony 5/6/7 and PHP 8. Concrete friction you should expect:

  • Symfony\Component\Translation\TranslatorInterface was removed in Symfony 5 — any code path referencing it is dead.
  • Doctrine\Common\Persistence\* namespaces moved to Doctrine\Persistence\*; the ORM storage layer needs patching.
  • ContainerAwareCommand, untyped ArrayAccess/Iterator implementations, and old service definition syntax all trip PHP 8.x and Symfony 5+.
  • The grid's front-end assets are unversioned and assume an older Twig/jQuery setup.

If you adopt it, pin the exact version and budget for a fork. If you're starting greenfield, Symfony's translation:extract plus a lightweight CRUD admin over your own translations table is usually less work than reviving this.

It replaces the translator service. The bundle's translator decorates/replaces the default so DB content can be served. If you or another bundle also decorates translator (or translator.default), decoration order decides who wins and failures are silent — one side just stops being consulted. Verify with:

php bin/console debug:container translator --show-arguments

import --force is destructive. Without --force the importer leaves existing DB rows alone; with it, file content overwrites GUI edits for matching keys. Never put --force in a cron job that runs after editors have been working. Run --help on lexik:translation:import to see the exact flags your pinned version supports — they have drifted between releases.

translation_key_structure drives export paths. If it does not match your file naming (messages.en.yaml vs en/messages.yaml vs one-file-per-domain), exports land in surprising places or create new files next to your real ones. Set it explicitly on day one and verify with a single export into a temp directory before trusting it.

Stored file paths go stale. lexik_translation_file records path and a content hash. Rename or move translation files and export will target the old location or skip the file. Re-import after any structural file change, or truncate the file table and re-import cleanly.

clean cannot see dynamic keys. lexik:translation:clean removes units with no translations, but keys built at runtime ('errors.' . $code) never appear in static scanning and can look unused. Always start with --dry-run and treat its output as a suggestion, not a policy.

Large catalogs are slow in the grid. The UI loads and paginates across units and every managed locale; with 20k+ keys and several locales, page loads become painful and the "toggle similar" feature multiplies the row count. Keep managed_locales narrow in the admin environment and consider a read-only replica for browsing.

Debugging translations served from the DB is different from debugging files. debug:translation reports what the translator resolved, which for this bundle may come from lexik_translation_translation rather than your YAML. When a string looks wrong, check the DB row first, then the file, then the cache — in that order.

php bin/console debug:translation fr --domain=messages
php bin/console doctrine:query:sql "SELECT u.\`key\`, t.content FROM lexik_translation_unit u JOIN lexik_translation_translation t ON t.unit_id = u.id WHERE t.locale = 'fr' LIMIT 20"

Extending it is easiest at the file-formatter and storage layers (storage.type, custom formatter services) and at the event dispatcher — inspect EventDispatcher/Event/ in the vendor directory for the actual event class names before subscribing, since they are not documented and differ between forks. Avoid subclassing the controllers; the grid's action signatures are the least stable part of the bundle.

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.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib