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 Checker Bundle Laravel Package

incenteev/translation-checker-bundle

Symfony bundle that keeps translations in sync across locales. It scans templates and code to flag missing, unused, and inconsistent messages, works with XLIFF and YAML catalogs, and adds a command you can run in CI to catch translation regressions early.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Symfony bundle (not Laravel), so the first step is confirming you're in a Symfony app — the commands hook into the FrameworkBundle console and the translator service.

  1. Check the platform requirements before installing. As of v1.10.0 the bundle requires PHP ≥ 8.2 and Symfony ≥ 6.4, and it now declares support for Symfony 8. If you're still on Symfony 5.4/6.0–6.3 or PHP 8.0/8.1, Composer will resolve you to v1.9.1 — that's the last release with the wider constraints. Pin explicitly if you need to stay there:

    composer require --dev incenteev/translation-checker-bundle:^1.9   # legacy stack
    composer require --dev incenteev/translation-checker-bundle:^1.10  # PHP 8.2+, Symfony 6.4+, incl. Symfony 8
    
  2. Install as a dev dependency — it's a static-analysis/lint tool, not runtime code:

    composer require --dev incenteev/translation-checker-bundle
    
  3. Register the bundle in dev/test only. In modern Symfony (Symfony Flex), add it to config/bundles.php:

    Incenteev\TranslationCheckerBundle\IncenteevTranslationCheckerBundle::class => ['dev' => true, 'test' => true],
    

    On older apps, register it in AppKernel::registerBundles() inside the if (in_array($this->getEnvironment(), ['dev', 'test'])) block.

  4. Discover the command surface first. There is no required configuration to get value:

    php bin/console list translation
    php bin/console translation:check --help
    

    The bundle's whole job is one command: it statically scans your Twig templates and PHP source for translation keys actually used, compares them against the keys defined in your translations/ files, and reports missing keys (used but not defined) and unused keys (defined but never referenced).

  5. First real use case: run it on an existing project right after install. The "missing" report is immediately actionable (you shipped a key that doesn't exist → users see the raw key). The "unused" report is noisier and usually needs an ignore list before you make it blocking.

Where to look first: the README for the exact option set of your installed version, then DependencyInjection/Configuration.php for the config tree, and the TranslationChecker service class if you need to extend extraction.

Implementation Patterns

Local pre-commit / pre-push loop. Run translation:check after adding new templates or controller messages. It's fast enough to run on every change and catches the classic bug of adding {{ 'checkout.title'|trans }} without adding the key to messages.en.yaml.

CI gate — split the two checks. Missing translations should fail the build; unused translations should warn. Typical wiring:

# composer.json scripts
"scripts": {
    "check-translations": "bin/console translation:check --env=test"
}

Then in CI run composer check-translations alongside lint:twig and lint:yaml. If the command supports separate exit behaviour or --ignore-unused, use that so a leftover key doesn't block a deploy while a missing key still does.

Pin your CI matrix to the release line you support. With v1.10.0 the support window splits cleanly: PHP 8.2+/Symfony 6.4–8 on ^1.10, older stacks on ^1.9. Run the checker on both the lowest and highest Symfony versions in your matrix so a Symfony 8 upgrade doesn't quietly change which keys the extractor finds.

Scope the run per domain/bundle. On large apps the default scan can be slow and noisy. Narrow it to the bundle or directory you're touching (options such as domain/prefix/bundle filtering — confirm names with --help for your version). A common pattern is one fast scoped check in the inner loop and a full check in CI.

Maintain an explicit ignore list. Dynamic keys — {{ ('status.' ~ code)|trans }}, $translator->trans($key), keys assembled from enums — are invisible to static analysis, so they show up as "unused." Keep a curated ignore list in the bundle config rather than disabling the unused check entirely; it documents why each key exists.

Use the checker service directly. The bundle exposes a TranslationChecker-style service that returns the used/defined key sets. Inject it into a custom console command or a PHPUnit test to assert your own invariants, e.g. "every key in the admin domain has a translation in en and fr."

Decorate for custom sources. If you have translation keys in JavaScript, Vue components, or a custom DSL, decorate the checker (or the extractor it delegates to) with an extra pass that appends keys to the "used" set. That keeps one source of truth instead of maintaining a parallel script.

Pair with Symfony's built-in tools. debug:translation shows per-locale coverage; translation:check shows static usage. Run both — debug:translation catches empty/missing per-locale values, the checker catches keys that are entirely absent or orphaned.

Gotchas and Tips

  • It's static analysis — dynamic keys are invisible. Variable-based trans() calls, string concatenation, and keys computed at runtime will be reported as unused and their real keys as missing. Budget for an ignore list; don't treat "unused" as gospel.
  • Only literal first arguments are detected in PHP ($translator->trans('literal.key')). If you wrap the translator in your own service, the checker won't see through it unless you decorate the extractor.
  • Domain detection is heuristic. |trans({}, 'admin') is picked up, but keys used without an explicit domain default to messages; a key that exists only under a custom domain can be falsely flagged as missing.
  • Frontend-only keys look unused. Anything translated in JS or fetched from an API will be flagged. Either extract those keys in a decorator or ignore them.
  • Vendor bundles aren't scanned by default. Keys used inside third-party templates are outside the scan; override files in templates/bundles/ are scanned if they live in your app directories.
  • Enable dev/test only. There's no reason to load the bundle in prod — it adds services and console commands you never use there.
  • Platform floor moved in v1.10.0. Symfony < 6.4 and PHP < 8.2 are no longer supported on the current release. If you upgrade the bundle without upgrading the framework, expect a Composer constraint error rather than a runtime surprise — but do remember that composer update of a transitive dev tool can be what drags the floor up. Pin the bundle version in composer.lock, and read the diff of any major Symfony bump.
  • Symfony 8 is now supported (v1.10.0+). That's the headline win of this release: you can keep the checker in the toolchain through a Symfony 8 upgrade instead of dropping it or running a fork. Verify your nikic/php-parser constraint still resolves alongside PHPStan/Rector — that parser version is the most common conflict, not the bundle itself.
  • Maintenance signal improved. The 1.10.0 line landed with contributions from Symfony core-adjacent maintainers and explicit support for the next major, so the "effectively unmaintained, pin and pray" advice from earlier versions is now too pessimistic. Still pin it, but don't budget for a fork.
  • Exit codes drive CI. Confirm the command returns non-zero on failure for your installed version before wiring it as a gate — otherwise your "fail the build" step silently passes.
  • Debugging noisy reports: rerun with -v, then narrow by domain/prefix to isolate the offending directory. When a report seems wrong, grep the template/PHP for the literal key first — most surprises are a dynamic call you forgot about.
  • Laravel translation: if you're coming from Laravel, note this tool only understands Symfony's translator, Twig, and translation file layout. The equivalent hygiene in Laravel is a custom Artisan command (or laravel-lang/lang linters) scanning __()/@lang calls — the same static-analysis caveats apply.
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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core