A Laravel charity site's Polish pages show raw keys like donations.thanks and some English sentences; how do you find and prevent missing translation keys?
answer
- raw key versus English sentence
- fallback hides PHP-key gaps
- Lang::handleMissingKeysUsing callback
- hasForLocale skips the fallback
- compare key sets in a test
basics
~10 sA raw group.key means neither Polish nor the fallback locale has that PHP line; an English sentence means pl.json lacks a JSON key. Catch them with Lang::handleMissingKeysUsing() and a test comparing keys with Lang::hasForLocale().
solid answer
~40 sRead the symptom first. A raw `donations.thanks` means the short key exists in neither `lang/pl` nor the fallback locale, usually a typo, a wrong group file or a locale directory named differently from the active locale. An English sentence means a JSON key is missing from `lang/pl.json`, since JSON lookup returns the source text. Then add two defences. `Lang::handleMissingKeysUsing()` registers a callback that receives the key, replacements, locale and fallback flag whenever a lookup gives up, so you can log or, outside production, throw. But it does not fire when a PHP key is missing only in Polish, because the fallback line is found silently. For that gap, a test iterates the English keys and asserts `Lang::hasForLocale($key, 'pl')`, which checks without the fallback.
code
php · 15 lines<?php
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Lang;
test('every English group line exists in Polish', function () {
foreach (glob(lang_path('en/*.php')) as $file) {
$group = basename($file, '.php');
foreach (array_keys(Arr::dot(require $file)) as $key) {
expect(Lang::hasForLocale("{$group}.{$key}", 'pl'))
->toBeTrue("missing pl: {$group}.{$key}");
}
}
});go deeper
Recognise that a raw dotted key on a page means a missing or misspelt translation, not a crash.
Trace the lookup chain to tell a JSON gap from a PHP-key gap, and name has() versus hasForLocale().
Combine a logging missing-key handler with a CI completeness test, and explain why the fallback hides per-locale gaps from the handler.
Define the translation-completeness bar per locale and how gaps are triaged, balancing release speed against shipping partially translated pages.
## Reading the symptom Laravel's translator never throws for a missing line, so the page itself is your first diagnostic. The lookup in `Translator::get()` explains each symptom: 1. The **current locale's JSON** file is checked for the exact key. 2. The key is parsed into group and item, and `lang/{locale}/{group}.php` is checked for the **current locale, then the fallback locale**. 3. If everything misses, the optional missing-key callback runs and the **key string** is returned. | What the Polish page shows | What it means | |---|---| | `donations.thanks` | the short key is in neither `lang/pl` nor the fallback locale's files | | an English sentence | a sentence key is missing from `lang/pl.json` (JSON has no fallback step) | | the English line for a short key | the key exists only in the fallback locale, a gap that stays silent | Common root causes of the raw-key case: - a typo in the key or in the group file name; - a locale directory spelled differently from the active locale (`pl_PL` versus `pl`); - a line moved to another group during a refactor; - a sentence key reworded in the source code, orphaning the translation. ## Hook 1: a missing-key handler `Lang::handleMissingKeysUsing()` registers a callback on the translator. It receives `$key`, `$replace`, `$locale` and `$fallback`, and runs only when the lookup gives up; a non-null return value replaces the key that would be printed. ```php <?php use Illuminate\Support\Facades\Lang; use Illuminate\Support\Facades\Log; // AppServiceProvider::boot() Lang::handleMissingKeysUsing(function (string $key, array $replace, ?string $locale) { Log::warning('Missing translation', ['key' => $key, 'locale' => $locale]); return null; // keep printing the key }); ``` Useful facts about it: - The translator disables the handler while it runs, so a translation call inside it cannot loop. - `Lang::has()` and `Lang::hasForLocale()` switch it off during their checks. - In local and testing environments many teams throw from the callback so a missing key fails loudly. The limit: for a **short key present in English but missing in Polish**, the fallback line is found and returned, so the handler never runs. The page looks fine to an English-reading reviewer and wrong to a Polish donor. ## Hook 2: a completeness test To catch per-locale gaps, compare key sets directly: - For group files, flatten each `lang/en/*.php` array to dotted keys and assert `Lang::hasForLocale("{$group}.{$key}", 'pl')` for each. `hasForLocale` passes `false` for the fallback flag, so an English-only line fails the test. - For JSON files, compare the key lists of `lang/en.json` and `lang/pl.json`, or scan the codebase for `__('...')` sentences. Running this in CI turns a silent regression into a failed build. ## Operating it - Log missing keys in production at a low level, grouped by locale, instead of throwing. - After a framework upgrade, diff the framework's sentence strings: a reworded source sentence silently orphans your JSON entry. - Use `Lang::determineLocalesUsing()` only when the default two-step chain (current, then fallback) is wrong for a locale family, such as trying `pt` between `pt_BR` and `en`. ## A triage order for the charity site 1. Confirm the active locale on the failing page and that it matches the directory name exactly (`pl`, not `pl-PL`). 2. For a raw short key, search `lang/pl/{group}.php` and `lang/en/{group}.php` for the item; if both lack it, the key in code is wrong or the line was never written. 3. For an English sentence, search `lang/pl.json` for the exact sentence, including punctuation and capitalisation, since JSON keys must match exactly. 4. Check recent commits for reworded sentences in views, and the framework's strings after an upgrade. 5. Add the missing lines, then make sure the completeness test covers the key so it cannot regress.
- Why does the handler registered with handleMissingKeysUsing stay silent for a line that exists only in English?The translator tries the current locale and then the fallback locale before giving up. When the English line is found, it is returned immediately and the missing-key step is never reached. Only a check without fallback, such as `Lang::hasForLocale()`, exposes that gap.
- What is the difference between Lang::has() and Lang::hasForLocale()?`has($key, $locale, $fallback = true)` counts a line found in the fallback locale as present. `hasForLocale($key, $locale)` calls `has()` with the fallback flag off, so only the named locale counts; it is the one to use in completeness tests.
saying these in an interview costs you the question
- A missing key throws, so a clean error log means translations are complete
- The missing-key handler fires whenever Polish lacks a line English has
- Lang::has() with defaults proves the Polish file contains the key
- A missing pl.json sentence is looked up in en.json before the key is shown
- config:cache also caches translations, so clear it to see new lines