simonecerruti/laravel-translation-audit
Audit your Laravel translation files from the command line: spot missing keys, unused strings, duplicate entries, and inconsistencies across locales so every language stays complete and in sync. A lightweight PHP dev tool for cleaner, more reliable localization before shipping.
__('payments.'.$method) through Laravel's own translator instead of diffing key lists per locale file, so nested-array keys (validation.password), framework-provided strings, and unreadable locale files no longer generate noise. That remains the reason the gate survives contact with a real team. Any noise-reduction change to a gating check is decision-relevant, because it changes whether engineers tolerate the gate.ignore_paths, ignore_locales and ignore_keys can now be mapped to AuditType::Missing or AuditType::Unused, so suppressing noise in one audit no longer silently disables the other. Previously ignoring a path to silence missing-key failures also removed those files from unused detection, letting dead keys accumulate; now 'database/seeders/**' => AuditType::Missing keeps those keys counted as used. Two practical consequences: you can stage adoption (enable the low-risk unused audit first, add the missing audit once the ignore list stabilizes), and you can ignore a locale for missing keys only — e.g. 'en' => AuditType::Missing when the keys are the English text — while still reporting and purging its unused translations.'ipmi.type' => ['help' => …]) — are reported as missing. That narrow class of false positives is the price of correct detection of genuinely unresolvable keys. It is now easier to encode as an exception list, because entries can be scoped to AuditType::Missing and left out of the unused audit.unused_ignore_paths is gone, merged into ignore_paths, and its defaults (Laravel's own translation files) are now defaults of ignore_paths. A published config that defines its own ignore_paths must add those defaults back, or Laravel's own translation files are reported as unused and removed by translation:purge-unused. This is the one item in the release with a plausible path to user-visible damage (deleted validation messages), so treat it as a required migration step with a reviewed diff, not a nice-to-have.TranslationKeyResolver, Result contract and results, Data\Translation, the DisplayFormat/SaveFormat enums, InvalidConfigException, AuditType — plus marking everything else @internal and making every concrete class final, is good news for ownership: what you integrate against is now a stated contract, and future upgrades should not break it. The cost is that anything built on the internals (AuditTranslationsResult::withUnused(), jsonSections(), subclassing a command) breaks now. Worth a one-time audit of your integration for internal usage before upgrading.translation:purge-unused supports a recurring hygiene task: removing dead keys after feature removals, shrinking locale files, and reducing the surface translators must maintain. The per-audit ignore scoping makes the purge safer to reason about, since "counts as used" is now expressible rather than implied.Consider it when:
__('payments.'.$method)); translator-based detection is aimed at that pattern, though it remains heuristic and will not catch keys assembled from untrusted or highly variable input.Look elsewhere or defer when:
@internal (e.g. withUnused(), jsonSections()) or on extending a command or result class — those are now final and will break; budget a rewrite against the public API first.purge-unused cannot be run against a branch you trust — it is destructive, there is no indication of a dry-run guarantee, and the merged ignore_paths defaults make a misconfigured migration actively dangerous. Validate both before enabling it in automation.Pin to 0.13.x. Pre-1.0 semantics can still shift between minor releases — this release is itself evidence, and it is a breaking one. The upside is that the public surface is now written down, so upgrades after this one should be cheaper.
For executives: This is a small developer tooling package that catches missing app translations before release, in the same automated pipeline that already runs tests. Two things changed in the latest release. First, it now works on GitLab as well as GitHub, so more teams can adopt it without bespoke setup. Second, the latest fix makes the check easier to tune: teams can silence noise in one check without turning off the other, which is the main reason these gates get switched off. The release also freezes the tool's public interface ahead of a 1.0 milestone — meaning upgrades will be more predictable going forward, at the cost of a short, reviewed configuration migration now. Net effect: fewer untranslated screens reaching customers, less manual QA per release, and a cleaner, smaller list of strings we pay translators to maintain. Cost is a dev dependency, a short CI integration and a one-time migration; no runtime risk, no vendor lock-in.
For engineering: Upgrade deliberately — this is a breaking release. (1) Move any unused_ignore_paths patterns into ignore_paths mapped to AuditType::Unused; a published config with its own ignore_paths must re-add the defaults (lang/*/validation.php etc.), otherwise Laravel's own translation files are reported unused and deleted by translation:purge-unused. (2) Republish workflow assets: the GitHub tag is renamed to laravel-translation-audit-github-workflow, and there is a new laravel-translation-audit-gitlab-workflow publishing to .gitlab/ci/audit-translations.yml. (3) Grep your integration for anything now @internal or final — AuditTranslationsResult::withUnused(), jsonSections(), and subclassing of commands, results or the service provider will no longer work; build only against the stated public API (TranslationKeyResolver, Result, Data\Translation, the enums, InvalidConfigException, AuditType). (4) Use per-audit scoping to phase rollout: enable translation:purge-unused first with AuditType::Unused ignores, then gate on translation:audit once the missing-key ignore list stabilizes. (5) Run purge on a branch and review the diff before merging, and keep unreadable-locale-file silent skips in mind — verify locale paths in CI. Pin to 0.13.x.
How can I help you explore Laravel packages today?