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

Jms Translation Bundle Laravel Package

ibexa/jms-translation-bundle

JMSTranslationBundle for Symfony provides a web interface for viewing and editing application translations, extracts translation keys from templates and source code, and helps keep message catalogs organized and consistent across locales.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony bundle (the Ibexa-maintained fork of schmittjoh/JMSTranslationBundle), not a Laravel package. There is no service provider, no config file to publish, and no Artisan command. "Getting started" therefore means deciding in the first ten minutes whether you are using it as a bundle (Symfony/Ibexa app) or as a library of extraction components (Laravel).

Install and check constraints first

composer require ibexa/jms-translation-bundle

It pulls symfony/framework-bundle, twig/twig and nikic/php-parser. In a Laravel app that is a heavy, overlapping dependency set — install it only if you actually intend to drive the extractor/updater classes yourself.

Where to look first (in this order)

  1. JMS\TranslationBundle\Translation\Extractor\FileExtractor — the PHP/Twig source scanner; this is the piece with real value.
  2. JMS\TranslationBundle\Translation\Updater — merges extracted messages into existing catalogues and preserves translation state.
  3. JMS\TranslationBundle\Translation\Dumper\* — XLIFF, YAML, PHP, PO writers.
  4. JMS\TranslationBundle\Model\MessageCatalogue / Message — the in-memory model everything passes through (id, domain, locale, meaning, desc, state).
  5. Command\ExtractTranslationCommand — the reference wiring for all of the above; read it as documentation even if you never run it.
  6. Resources/doc in the repo (published at jmsyst.com/bundles/JMSTranslationBundle) — the README is a stub, the real docs are there.

First use case

If you are in Symfony: run the extractor against one bundle and look at the diff before trusting it.

bin/console translation:extract en --bundle=AppBundle --output-format=xliff --keep -vvv

--keep preserves messages that are no longer referenced (as obsolete) instead of deleting them, and -vvv surfaces parse errors that are otherwise swallowed. Commit your translation files first — the command rewrites files in place.

If you are in Laravel: mirror the above in a throwaway script. Build a FileExtractor over php-parser + a Twig environment, point it at app/, and dump to a temp YAML file. Expect the PHP extractor to find trans(...) / $translator->trans(...) calls in your classes, and expect Blade templates to be invisible until you supply your own extractor. That gap is the deciding factor for most Laravel teams.

Implementation Patterns

Pattern 1 — Artisan command wrapping the extractor (PHP sources only)

The pragmatic Laravel bridge is a custom command that constructs the component stack manually and skips everything Symfony-specific:

// app/Console/Commands/ExtractTranslations.php
$extractor = new FileExtractor($phpParser, $twig, ['php', 'twig']);
$catalogue = $extractor->extract(new FileSource(base_path('app')));
// then hand $catalogue to a Dumper of your choice

Use this when your translation calls live in PHP classes, service objects, or Blade-free code. Don't expect it to understand __(), @lang, trans_choice() semantics out of the box — the PHP visitor is looking for translator-method calls, and the argument shapes it recognises are Symfony's.

Pattern 2 — Custom extractor for Blade

Implement JMS\TranslationBundle\Translation\Extractor\ExtractorInterface and register it in your own stack. A regex pass over resources/views/**/*.blade.php for __('key'), @lang('key'), trans('key'), and @choice('key', n) is genuinely enough for most apps:

preg_match_all("/__\(\s*['\"]([^'\"]+)['\"]\s*(?:,\s*\[[^\]]*\])?\s*(?:,\s*['\"]([^'\"]+)['\"])?/", $contents, $m);

Set the domain to $m[2] ?: 'messages' and let the Updater do the merging. This is where the bundle earns its keep: you get the catalogue model, state tracking, and dumpers for free while replacing only the 100 lines of source parsing.

Pattern 3 — Extract → diff → commit as a CI gate

The highest-value day-to-day workflow is not extraction for its own sake, it's making "developer added a string but forgot the key" a build failure:

php artisan translations:extract --locale=en --output-dir=storage/translations
git diff --exit-code storage/translations/ lang/en/

Because Updater preserves existing translations and marks new/obsolete entries, the diff is stable and reviewable. Wire it into CI on pull requests.

Pattern 4 — XLIFF handoff to translators

For multi-locale projects, extract the source locale to XLIFF and treat lang/en/*.xlf as the artefact you send out:

php artisan translations:extract --locale=en --output-format=xliff --keep

XLIFF carries the desc/meaning metadata and the new/translated/obsolete state that this bundle is built around, which is exactly what translation vendors consume. On return, convert back to your native Laravel format (PHP array or JSON) with a small script rather than trying to load XLIFF at runtime — Laravel's loader won't read it.

Pattern 5 — Per-domain extraction to keep catalogues reviewable

--domain=messages --domain=validation (and the only_domains / domain-management config options) limits output to the domains you care about. Without this, form-label and validation-string extraction floods messages.* with entries nobody wants to translate as prose. In a Laravel app, decide on two or three domains up front (messages, validation, emails) and enforce them.

Pattern 6 — ICU output where you need plurals

Version 1.6 added ICU message format dumping. If you have real pluralisation or gender/select requirements and are moving toward ICU-style messages, use that dumper option rather than hand-maintaining trans_choice pipes — but only for the domains that need it; mixing ICU and pipe syntax in one catalogue is a debugging trap.

Pattern 7 — Extension points instead of forks

If you need to adapt behaviour, prefer these over patching the vendor:

  • jms_translation.file_visitor tag — post-process extracted messages (strip prefixes, normalise keys, drop ignored patterns).
  • Custom ExtractorInterface — new source formats (Blade, Vue SFCs, JS).
  • Custom DumperInterface — output Laravel-shaped PHP arrays or JSON instead of XLIFF/YAML.
  • @Ignore annotation / Ignore on messages — suppress keys you never want extracted (test fixtures, admin-only strings).

Gotchas and Tips

Package identity and lifecycle. The repo you're installing is ibexa/jms-translation-bundle, a fork of schmittjoh/JMSTranslationBundle. The PHP namespace is still JMS\TranslationBundle, and the documentation URL still points at jmsyst.com. Stars are effectively zero and there are no dependents, so treat it as an Ibexa-internal fork with community upstream: pin the version, read the actual composer.json constraints, and be prepared to maintain it.

It is a Symfony bundle, and that is not a small caveat. Services are DI-registered, the extractors assume Symfony's translator conventions, and the web UI for translators (routes, base.html.twig) has been broken since Symfony 4 and is not worth reviving. In Laravel you use ~30% of the package (model, updater, dumpers, PHP extractor) and hand-wire it. Budget for that; do not promise stakeholders a "translation UI" from this.

FileExtractor + nikic/php-parser is the fragile joint. The changelog shows repeated breakage when php-parser moved (issue #50, PR #40 pinning a parser version). If your lockfile resolves a major php-parser version the bundle doesn't expect, extraction silently returns nothing or fatals. Check the resolved version first whenever extraction produces an empty catalogue.

Twig is a hard dependency of the file extractor even if you have zero Twig. Constructing the extractor wants a Twig environment; in a Laravel app you'll be instantiating Twig\Environment purely as a dummy. If that offends you, write the Blade extractor and skip FileExtractor entirely.

The command writes in place, destructively. There is no dry-run flag worth relying on; the safe pattern is commit → run → inspect git diff → revert if the ordering churned. Related: sorting/comparison of entries was historically non-deterministic (PR #62 fixed a uasort+strcasecmp callback), so older versions generate spurious ordering diffs and merge conflicts. If you see whole-file churn, it's ordering, not content.

XLIFF id attributes are hashes, not keys. PR #59 changed ids to sha1 specifically to reduce merge conflicts. Consequence: renaming a key or changing its source wording rewrites the id, and naive XLIFF tooling will treat that as delete+add, losing the translation. Keep your translation memory keyed on the message source, not the id.

Domain inference is quietly opinionated. Untagged trans calls fall into messages; form invalid_message options are forced into validators (PR #66, #15); translation_domain options on form types are honoured (PR #63, #65) but only if the extractor can resolve them statically. Any dynamically-assigned domain ($domain = $request->get('x')) is invisible — those keys land in messages and you'll get phantom entries. Use @Ignore on them.

Form extraction is noisy by design. The FormExtractor picks up labels, placeholders, choice labels, empty_value, and constraint messages. Multiple PRs exist purely to stop it extracting the wrong option. After your first run, audit validation.* and messages.* for entries that are really constraint-option names. Use only_domains to fence it in.

@Ignore is not a blanket suppressor. It has a history of being honoured for some node types and not others (issues #13, #20; desc content not dumped #14, transchoice #22). Verify it works for your call shape with a one-file test case before building a convention on top of it.

Empty strings and YAML quirks. PR #43 ("Keep empty strings, Yml line breaks") exists because empty translations were being dropped and multi-line strings mangled. If translations round-trip through YAML and come back wrong, that's the class of bug you're in — prefer XLIFF or PHP arrays as the on-disk format if you have many multiline strings.

Logging is opt-in and non-fatal. --enable-logger / the LoggerAwareInterface support (PR #5) reports parse errors on individual files. Without it, an unparseable template is skipped and you never learn the key was missed. Turn it on in CI and grep the output for "error".

Debugging checklist when a key doesn't appear: is the source file inside the scanned path (--bundle scoping was buggy historically, #37)? Is the call shape recognised (named arguments, concatenated strings and interpolation are usually not)? Is the domain computed statically? Is the file extension in the extractor's enabled list? Is the key already present-but-obsolete, so it isn't re-emitted as new?

Exit strategy. If your needs are "extract keys from Blade and diff against lang/", the honest cost comparison is: this package gives you a mature catalogue model and state machine wrapped in Symfony baggage; a 200-line custom Artisan command gives you exactly what you need with zero dependency risk. Choose this bundle when you specifically want XLIFF round-tripping with translation state and desc/meaning metadata — not merely because "translation extraction" sounds like a solved problem.

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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager