diglabby/laravel-find-missing-translations
A Laravel artisan command that scans your app for translation keys and reports any missing from your language files, so you can keep every locale complete and catch untranslated strings before they ship.
composer require --dev diglabby/laravel-find-missing-translations. There is no service provider config to publish for the basic use case — package discovery registers the artisan command for you.php artisan list and look for the package's command (it is registered under the translations namespace, e.g. translations:missing). That confirms discovery worked before you start tuning anything.php artisan translations:missing --locale=en. This gives you the smallest useful signal — keys referenced in code (via __(), trans(), @lang) that do not exist in lang/en.--verbose on the second run to get the per-file detail (which file declared the locale, which key is absent). On 1.8.1 the verbose line now names the file that declares a locale, which is the fastest way to understand why the tool thinks a locale exists.lang path is probably not where the package expects it (root lang/ in Laravel 9+ vs resources/lang/ in older apps). Check that first, before assuming a bug.--locale. Wrap it in a small shell step so each locale produces its own report:
for locale in en de fr; do
php artisan translations:missing --locale=$locale || exit 1
done
This keeps failures attributable — a red build tells you which locale regressed, not just that something did.grep -q on a known header and fail the step yourself. Verify the exit code once, then codify it — don't assume.lang/en, then re-run to confirm zero output.en baseline. Because 1.8.1 treats a locale as declared if it exists as either a lang/<locale>/ directory or a lang/<locale>.json file, a JSON-only locale (common for JS-side strings) is now correctly recognised — so a "missing locale" report for a JSON-only locale is a real finding, not a false alarm.require-dev alongside PHPUnit/Pint, and it should never run on a request path.__($key), trans("messages.{$type}"), and keys built from config are not resolvable statically. The command will not report them as missing, and it will not report them as present either. Do not read a clean report as proof of full coverage.lang/de/) or a single JSON file (lang/de.json). If you were on 1.8.0 and saw a JSON-only locale skipped or wrongly reported, re-run after upgrading — the results for those locales will change. This is a behaviour fix, not a new flag, so no config change is needed.lang/de/ and lang/de.json exist), the verbose line tells you which one the tool used. Use this whenever the report contradicts what you see on disk.resources/lang to the project root lang/. If the package's configured paths don't match your layout, you get a silent, clean report. Sanity-check by temporarily adding a deliberately missing key to a Blade view and confirming it shows up.vendor/ packages are not yours to fix. Make sure the scan is limited to your application's lang directory and your own source paths, or you will chase keys you cannot resolve.lang/en/messages.php returning ['auth' => ['failed' => '...']] is keyed as messages.auth.failed. If your code uses a different separator or a flat file with literal dots in the key, the lookup will disagree with the tool. When a key is reported missing but you can see it, this mismatch is almost always the cause.trans_choice. Strings selected via trans_choice have pipe-delimited variants; confirm they are recognised by your version before treating a report as authoritative for those keys.php artisan list confirms the command is registered, (2) run with --verbose to see the resolved lang paths and declaring files, (3) -vvv if the command supports Symfony verbosity for the underlying scan, (4) only then suspect the package. Nearly every surprising result traces back to path config or dynamic keys.How can I help you explore Laravel packages today?