modera/translations-bundle
A Laravel bundle for managing application translations from a database or files, with tools for editing strings across locales, organizing language lines, and keeping multilingual content in sync.
There is no installation path into a Laravel app. This is a Symfony bundle (Modera\TranslationsBundle, registered via AppKernel::registerBundles() in the Symfony 2/3 era it shipped in — last release 2016-06-10). It has no service provider, no facade, no config/ publish step, no Laravel auto-discovery. composer require modera/translations-bundle inside a Laravel project will install code you cannot boot, because there is no Kernel to register it with.
So "getting started" for a Laravel developer means one of two things:
composer require modera/translations-bundle, register the bundle in AppKernel, run Doctrine schema update for its entities, configure its translation domains and source paths, then invoke the extract and dump commands. Expect to pin PHP 5.6/7.0-range and Symfony ≤3.x — there is no PHP 8 or Symfony 4+ guarantee, and the repo is a [READ-ONLY] subtree split of a larger Modera monorepo, so any fix is a permanent fork.Your first use case in Laravel is not "install this." It is: take the four-stage model and implement it natively as four Artisan commands — translations:extract, translations:sync, translations:dump, and a diff check in CI. That is a contained piece of work (roughly a sprint), and it is the only way the pattern survives contact with Laravel's lang/ conventions, which this bundle does not target.
The loop the bundle implements (and what to mirror):
trans, transChoice in the Symfony era), derive a key and a group/domain, emit a manifest.Resources/translations/*.yml|xliff).Mapping each stage to native Laravel:
app/, resources/views/, routes/ for __('...'), @lang('...'), trans('...'), trans_choice('...'). Match the group from the dotted namespace (messages.welcome → group messages, key welcome), and JSON keys as a separate group (lang/{locale}.json).translations table: (locale, group, key, value, status, source_hash) with a unique index on the first three. Status (new / translated / stale) is what makes the admin surface usable; source_hash lets you detect when an English string changed and mark downstream locales stale.FileLoader — do not replace FileLoader, or you lose the ability to boot a fresh checkout before the DB is populated.translations table is the Laravel-native equivalent of the bundle's admin UI. Filter by locale, status, and group; that is the whole screen.lang/{locale}/{group}.php with var_export and lang/{locale}.json with json_encode(..., JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE). Sort keys deterministically before writing — the single most important detail for reviewable diffs.Integration tip — the CI gate is the pattern, not the code. After translations:dump, run git diff --exit-code lang/ in CI. If the dump produces changes, the DB and files have drifted and the build should fail. This one check is what keeps the whole pipeline honest, and it is cheaper and more reliable than anything the bundle itself provides.
Workflow that works day-to-day:
__('checkout.title') in a blade file.translations:extract picks it up, translations:sync creates the row in new status.translated.translations:dump runs as a single-owner job on main (not developer-local), regenerates files, commits.FileLoader reads PHP arrays and JSON. Dumping XLIFF/YAML into lang/ produces "translations exist but nothing renders." If you ever bridge the two, write a one-time converter and a smoke test that asserts a known key resolves per locale.trans/transChoice and Twig's |trans. It will not match @lang, {{ __('...') }}, or trans_choice. Any port starts with rewriting the extractor, not reusing it.transChoice range syntax; Laravel uses pipe-delimited pluralization ('apples' => 'one apple|:count apples'). A 2016 extractor will not emit Laravel's format, and there is no ICU MessageFormat or gender/context support to salvage. If you need those, this pattern (and this package) is the wrong tool — that's a TMS problem.checkout.title) and treat the English string purely as content.cache:clear. Stale overrides after an admin edit are the most common "why didn't my string show up" ticket.tinker (app('translator')->get('group.key')), and check config/app.php locale plus the lang/ path Laravel actually resolves. Most "translations don't load" reports are a loader registration that never took effect.[READ-ONLY] subtree split, so a fix is a hard fork you own forever. Treat it as a spec, not a dependency; adopt the extract→persist→translate→dump pattern, build it in ~a sprint of native Artisan commands, and gate it in CI.How can I help you explore Laravel packages today?