Compare translation files and find what is missing

Paste your source locale and a target locale. The diff lists missing and extra keys, then checks every value for broken :placeholders, plural forms Laravel cannot select, HTML that went missing, empty values and text nobody translated.

Files are compared in memory and never stored.

How the comparison works

Both files are parsed without executing any code: PHP files are read token by token, so only strings, numbers and nested arrays are accepted, and a PHP file that calls a function is rejected. Nested arrays are flattened into the same dot notation you pass to __(), so 'cart' => ['checkout' => '…'] becomes cart.checkout. Every translation key in the source that is absent from the target is reported as missing, and every key that exists only in the target is reported as extra, usually a leftover from a string you deleted or renamed.

The completeness figure is the share of source keys that have a non-empty value in the target. It is the same number a translation coverage report tracks per locale, measured for the one file you pasted.

Why missing translations are hard to spot in Laravel

Laravel never throws on a missing translation. For a short key such as cart.checkout it tries the fallback locale, and if that also lacks the key it prints the key itself. With JSON files the key is usually the English sentence, so a missing Spanish line simply shows English and looks fine to anyone who does not read Spanish. Empty values behave differently by format: an empty string in a PHP file renders as nothing at all, while an empty string in a JSON file falls back to the key. Both are reported here as empty.

The value checks

  • Placeholders. Laravel replaces :name, :Name and :NAME from the same name replacement, changing only the capitalisation, so the three count as one placeholder here. A translated placeholder such as :nombre is never replaced and reaches the user as literal text. More on this in placeholders done right.
  • Plural forms. trans_choice() splits a line on every pipe. Segments that start with an explicit condition such as {0} or [2,*] are matched first; otherwise Laravel picks a segment by index using its own per-locale rule, and when that segment does not exist it returns the first one. Set the target locale and the tool checks the segment count against that rule. Without a locale it only compares segment counts, because languages legitimately differ: Polish needs three forms where English needs two. The trans_choice() deep dive covers the rules language by language.
  • HTML tags. Opening and closing tags are counted on both sides. A link or a <strong> that disappeared from a translation is a silent bug when the line is printed with {!! !!}.
  • Untranslated values. A target value identical to the source is flagged only if real text remains once placeholders, tags and plural conditions are removed. Values with fewer than four letters and all-capital values such as OK or PDF are ignored. Single words are marked as info rather than warnings, since brand names and loanwords are often correct as they are.

A worked example

The example loaded above compares an English file with a Spanish one that contains the usual problems: one key never added (cart.coupon), one key left behind after a rename (legacy_banner), an empty saved message, a translated :name placeholder, a terms line that lost its link and its :url, a plural line with a different set of conditions, and a settings label that was copied over in English. The generated stub contains only the missing key, nested the same way as the source, so you can paste it straight into the Spanish file.

Keep it from happening again

A one-off diff fixes today's files. To stop missing keys reaching production, add a key parity test that fails the build whenever a locale falls behind; the guide to testing Laravel localization with Pest has one you can copy. If you are choosing between formats, the JSON vs PHP lang files comparison explains how each behaves when a key is missing. And to check what users actually see, run the untranslated key detector against your live site.

Frequently asked questions

How do I find missing translation keys in Laravel?

Compare each target locale against your source locale key by key. Paste lang/en/messages.php and lang/es/messages.php (or en.json and es.json) into this tool and it lists every key the target lacks, plus a ready-to-paste stub. To catch new gaps automatically, add a key parity test to your test suite so the build fails when a locale falls behind.

What does Laravel show when a translation is missing?

It never throws. For a short key such as auth.failed it tries the fallback locale, and if that also lacks the key it returns the key itself. For JSON files the key is usually the source sentence, so a missing line quietly shows the source language. An empty string behaves differently by format: it renders blank in a PHP file and falls back to the key in a JSON file.

Are :name, :Name and :NAME different placeholders?

No. Laravel fills all three from the same name replacement, as written, with the first letter upper-cased, or fully upper-cased. The diff therefore treats them as one placeholder and only reports a mismatch when a name is missing or new, such as a :name that a translator turned into :nombre.

Why does it flag plural forms that look fine?

trans_choice() picks a pipe-separated segment by index using a per-locale rule, and returns the first segment when the index does not exist. If you set the target locale, the tool checks the segment count against that rule, so a Polish line with two forms is flagged because Laravel selects up to three. Lines with explicit {0} or [2,*] conditions are compared segment by segment with the source instead.

Is the content of my lang files stored or executed?

Neither. PHP files are parsed token by token, so only string, number and array literals are accepted and nothing is evaluated. Both files are compared in memory and discarded when the response is sent.

Useful? Link to it

The tool is free and stays free. Paste this into a README, a wiki page or a forum answer.

<a href="https://langsyncer.com/tools/lang-file-diff">Lang File Diff &amp; Validator</a> by LangSyncer

See missing translations for every locale at once

This diff checks one pair of files. LangSyncer imports all your lang files, shows coverage per language, translates the missing keys with AI and publishes the result without a deploy.

We'd like to use analytics and marketing cookies to understand how the site is used. They stay off unless you accept them. Cookie Policy