webduonederland/laravel-missing-translations
A Laravel package that scans your application for missing translation keys and reports them, helping you keep language files complete and catch untranslated strings before they reach production.
This is the first published assessment for webduonederland/laravel-missing-translations. It is a developer-facing utility: it scans your application source for translation keys that are actually used, compares them against your lang/ files, and reports what is missing (and typically what is unused). It is not a runtime feature — it is a diagnostic you run locally and in CI.
composer require webduonederland/laravel-missing-translations
php artisan vendor:publish --tag=config
Upgrade first if you are on 1.1.0/1.2.0. Release 1.3.0 ships two things: a widened framework constraint for Laravel 13, and a bugfix in config publish. The publish bugfix matters for onboarding — if vendor:publish previously produced nothing or the wrong file, you would have had to hand-copy the config out of vendor/. Published 1.3.0 should work on a clean install, but verify the file actually landed before troubleshooting anything else.
Where to look first: the published config file (config/missing-translations.php). Everything that causes false positives or missed keys lives there — scan paths, locale list, and ignore/exclude rules. Open it before running anything; the defaults are tuned for a stock Laravel app and will be wrong for anything with modules, packages, or non-standard view paths.
First use case: run the scan once against a single locale to get a baseline count of missing keys. Do this on the main branch, unmodified, so you are measuring the package's behaviour rather than your config edits. If the baseline looks wildly wrong (hundreds of keys you know exist), the problem is almost always scan paths or locale config, not the package.
Confirm the exact command names with php artisan list | grep -i translat — the package registers its commands under the console kernel and the naming has been stable, but check rather than assume.
Baseline-then-ratchet in CI. The highest-value pattern: capture the current missing-key count, commit it as an accepted baseline, and fail the build only when the number increases. A hard fail on "any missing key" is unusable on any real codebase; a ratchet keeps the debt from growing without blocking merges on legacy strings.
// pseudo-config shape — adapt to the published file
'scan' => [
app_path(),
resource_path('views'),
base_path('routes'),
],
'locales' => ['en', 'nl', 'de'],
Designate a reference locale. Pick the locale that is actually complete (en in most projects), treat it as the source of truth, and diff every other locale against it. Diffing locales pairwise produces noise; diffing against one reference produces an actionable list.
Pair it with unused-key detection. Missing keys and orphaned keys are the same audit from two directions. Run both in the same job so one PR cleans up both, rather than accumulating a "translation cleanup" ticket nobody picks up.
Scope paths per module. For modular or multi-package apps, add each module's views and source directory explicitly rather than scanning base_path(). Broad scans pull in vendor/, storage/framework/views/ (compiled Blade — always a false-positive source), and test fixtures.
Framework-version support. 1.3.0 adds Laravel 13. If you are upgrading a Laravel 12 app to 13, expect the usual constraint negotiation:
composer require webduonederland/laravel-missing-translations --with-all-dependencies
Check that the package's own PHP constraint is satisfied by your platform before blaming the upgrade on something else.
__($key), trans('messages.' . $type), and @lang("{$section}.title") cannot be statically resolved. The scanner will either miss them or guess wrong. Route these through ignore rules or a naming convention that keeps the resolvable prefix static (__("messages.$type") is still unresolvable — wrap it in a match statement returning literal keys if you need coverage).storage/framework/views/*.php contains generated PHP with the same __() calls. If it is inside a scan path you will get duplicates at best and confusing paths at worst. Exclude it.vendor/*/resources/lang are not "missing" — you just do not have them locally. Do not let the scanner flag these as debt; either map them or ignore that namespace.messages.welcome) often live in a flat lang/en.json in the same project. Mixing both conventions in one app will produce a permanently non-zero missing count. Standardise, or configure both.config:cache after edits. In production-like environments, config is cached — a config change without php artisan config:cache (or config:clear locally) silently does nothing. This looks identical to "the package is ignoring my ignore rules."^1.3 in composer.json.1.1.0...1.2.0, so the changelog does not fully describe 1.3.0 and 1.2.0's contents are unstated. Pin exact versions rather than floating dev-main / * if reproducibility matters, and read the commit diff if you need to know exactly what changed for Laravel 13.How can I help you explore Laravel packages today?