Four Detection Layers to Catch Missing Translations for Developers
Four Detection Layers to Catch Missing Translations for Developers

Use a layered approach: static extraction or TypeScript typing, a lint or CLI check like translint, runtime i18n tests, and CI gating that blocks regressions. Prioritize catching missing keys, empty values, and placeholder mismatches first. If your codebase runs TypeScript, compile-time typing is the fastest high-leverage fix you can add today.
TL;DR:
- Static extraction and TypeScript typing catch most missing keys early, but cannot detect dynamically constructed or runtime-loaded keys.
- Combining lint tools like translint with runtime UI tests provides broader coverage, with CI gating blocking high-confidence failures such as missing keys and placeholder mismatches.
- Locale negotiation and Intl formatting differences can cause runtime tests to fail even with correct static translations, so tests should focus on content presence rather than exact strings.
- Automating translation production and validation through platforms reduces manual errors, unifies workflows, and minimizes the risk of missing keys across the pipeline.
- High-confidence detection signals should fail CI builds immediately, while lower-confidence flags like untranslated or identical strings are better suited for warnings and regular reviews.
Table of Contents
- The four detection layers you need to stack
- Tooling and commands cheat sheet
- CI integration: fail fast on the right signals
- Runtime i18n testing and locale negotiation gotchas
- TypeScript compile-time typing example
- How automation cuts the errors behind missing keys
- Automated reporting and notification systems
- Organizing translation keys to prevent gaps
- Connecting detection to your translation workflow
- Keeping translation files in sync across environments
- When to choose lightweight checks vs. full automation
- Arkian: an automation option for validation and language-package delivery
- FAQ
- Sources
The four detection layers you need to stack
No single check catches every way a translation goes missing. Each layer below trades coverage for simplicity, so stacking them is what closes the gaps.
Static extraction scans your code or templates for translation keys and compares them against your locale files. It is fast and cheap to run, but it misses dynamically constructed keys, like a key built from a variable at runtime, because the string doesn’t exist literally in the source.
Lint and CLI tools close part of that gap by comparing locale files directly. A tool like translint flags missing keys, empty string values, and placeholder mismatches, then returns an exit code your pipeline can read. translint reports findings with exit code 1, and strict mode can also fail on extra keys or values it suspects were never translated. Static extractors, including the one documented in Symfony’s debug:translation guide, note the same limit: they can’t find messages translated outside templates or built dynamically, so a catalog diff alone leaves blind spots.
Runtime i18n tests catch what file-level checks never see: a locale file can be complete and still render broken text if a component fails to look up the right key, falls back silently, or mishandles pluralization. These tests render your actual UI in each target locale and check what shows up on screen.
CI gating ties the layers together. The goal is combining their outputs so high-confidence failures stop a build while noisier heuristics get flagged for review instead of blocking anyone.
A sensible adoption order:
- Add static extraction or TypeScript key typing first, since it costs almost nothing and runs on every save.
- Layer in a lint/CLI tool like translint next to catch empty values and placeholder drift.
- Add runtime tests once your UI has enough locale coverage to make them worth maintaining.
- Wire CI gating last, once you know which signals are reliable enough to trust blocking a merge.
Pro Tip: Start every new locale file by running your lint tool against it before a single string gets translated, so missing-key noise doesn’t bury the one real placeholder bug.
Tooling and commands cheat sheet
A handful of tools cover most detection needs without custom scripting.
- translint checks locale files for missing keys, empty values, and placeholder mismatches. Running it against your locale directory returns exit code 0 when clean and 1 when it finds issues, which makes it a direct fit for a CI step.
- Strict mode in translint also flags extra keys and values that look identical to their source, a useful heuristic for catching copy-pasted strings nobody translated, though it needs an allow list for cases like brand names or numbers that are correctly identical across locales.
- i18next-cli extracts keys from your codebase and can output machine-readable JSON or JSONL reports, which its documentation shows is built for piping straight into automation rather than reading by hand.
- translate-missing fills gaps in locale files using Google Translate and can mark which entries were auto-filled. That’s useful for unblocking a build, but its own documentation is clear that auto-translated strings still need human review before shipping, since relying on machine fill as a substitute for QA risks shipping confidently wrong text.
- Framework-native commands like Symfony’s
debug:translationcompare your templates against your catalog and report missing or unused messages directly, while Laravel and Blade projects often rely on custom scans oftrans()and__()calls since no single built-in command covers every usage pattern.
Pick translint or a similar linter as your baseline, reserve translate-missing for draft locales you plan to review before launch, and lean on framework commands when your stack already ships one.
CI integration: fail fast on the right signals
Not every finding deserves to block a merge. Missing keys, placeholder mismatches, and locale file parse errors are high-confidence problems: they almost always indicate a real bug, so they should fail the build. Extra keys and “possibly untranslated” heuristics are lower-confidence; treat them as warnings in a report rather than a blocking failure, or your team will start ignoring red builds altogether.
A simple pipeline step might run your linter and check the exit code directly, failing the job only when it returns a nonzero status for missing keys or placeholder issues, while writing the full report to a build artifact for review regardless of outcome.
- Run a fast lint check as a pre-commit hook so obvious issues never reach a pull request.
- Run the same check plus a placeholder-mismatch scan on every PR, blocking merge on missing keys and mismatches.
- Run a full nightly scan across all locales, including slower runtime UI tests, and report results without blocking anything.
Pro Tip: Keep your PR-level check fast enough to finish in under a minute, or developers will start merging past a yellow pipeline instead of waiting for it.
Runtime i18n testing and locale negotiation gotchas
File-level checks can’t see how your app actually resolves a locale at runtime. Browsers and runtimes negotiate locale through a precedence chain, from a fully qualified locale down to a language-only match and finally a default, and MDN’s internationalization guidance notes that Intl output itself can vary between JavaScript engines. A test that only loads a locale file and checks its contents will miss a case where your app actually negotiates a different locale than the one you intended to test.
- Simulate the real negotiation path in tests, using
navigator.languagesorAccept-Languageheaders, rather than importing a locale file directly. - Avoid exact-text snapshot assertions across locales and engines; Intl formatting differs enough that a passing test today can fail after a runtime update with no actual bug involved.
- Prefer checking for the presence of translated content, correct element roles, or the absence of raw, untranslated keys rather than matching an entire rendered string.
- Run interpolation tests with representative, realistic values, since placeholder mismatches often only surface when a real name, count, or date actually renders into the template.
Placeholder mismatches deserve particular attention here. A locale file can pass every static check and still crash in production if a translator drops or renames a placeholder token, which is why translint’s placeholder checks and runtime interpolation tests both matter: one catches the mismatch in the file, the other catches what happens when it actually renders.
TypeScript compile-time typing example
If your project already uses TypeScript, this is the cheapest check you can add, since it runs during every compile with no extra tooling.
- Import your base locale file directly:
import enJson from './locales/en.json'. - Derive a type from its keys:
type TranslationKeys = keyof typeof enJson. - Type your translation function against that type:
function translate(key: TranslationKeys): string. - Any call to
translate()with a key that doesn’t exist in your base locale now fails at compile time instead of at runtime.
This pattern has real limits. It only catches keys known at compile time, so dynamically constructed keys and locales loaded at runtime from a server or CDN slip past it entirely. It also only checks that a key exists, not that every other locale has a matching, non-empty translation for it. Keep your JSON file as the single source of truth for key names, and pair this typing layer with a lint tool and runtime tests so the parts TypeScript can’t see still get checked. Our guide on localizing TypeScript language files covers the file structure this pattern assumes.
How automation cuts the errors behind missing keys
Most missing translations don’t start as translation problems. They start as process problems: a developer forgets to add a new key to every locale file, a copy-paste step drops a bracket, or a packaging script ships the wrong file shape to the wrong platform. We built our platform around closing that gap by automating the production of multilingual strings, voice output, and metadata, then running validation and packaging as one connected step instead of separate manual handoffs.
That matters most for small teams without a dedicated localization engineer: fewer manual steps means fewer places for a key to silently disappear between source file and shipped package. Our collaboration with the Quiet Harbour app shows this kind of coherent, multi-language output in practice, keeping the localized product consistent across every target language from a single source pass.
Automated reporting and notification systems
Detection only helps if the right person sees the result in time to act on it. A lint check that only prints to a terminal gets ignored the moment nobody is watching that terminal, so most teams route results into something persistent: a build artifact, a dashboard, or a message in the channel where the team already works.
A practical setup posts a summary to a shared channel whenever a scan finds missing keys or placeholder mismatches, tagging whoever owns that locale or feature. Pair that with a persisted report, whether a JSON file, a build log, or a simple dashboard, so a pattern across weeks is visible instead of each failure disappearing once the next build overwrites it.
Severity matters here too. A missing key in a core onboarding flow deserves an immediate, loud alert. A single “possibly untranslated” heuristic flag on a rarely seen settings label can sit in a weekly digest instead. Splitting notifications by severity keeps the urgent channel from becoming noise that people learn to scroll past.

For teams running checks across multiple localized sites rather than a single app, a tool like DBLScanner’s international site checks extends this kind of monitoring to full site crawls, catching drift across pages and locales that a single-repo lint check never sees.
Organizing translation keys to prevent gaps
Flat key structures (welcome_message, error_login_failed) scale poorly once a project passes a few hundred strings, because nothing stops two features from colliding on the same name or a key from getting added to one locale and forgotten in the rest. Namespacing by feature or screen, such as onboarding.welcome or auth.error.login_failed, keeps related strings together and makes a missing section obvious at a glance rather than buried in an alphabetical list.
A few habits reduce missing entries before they happen:
- Treat one locale, usually your base language, as the single source of truth and generate every other locale’s key list from it rather than editing each file independently.
- Avoid deeply nested key structures beyond two or three levels, since deep nesting makes diff tools harder to read and increases the odds of a typo breaking a lookup.
- Delete unused keys deliberately on a schedule rather than letting them accumulate, since stale keys inflate every report and hide real gaps among false ones.
Our documentation on localization strings covers the file formats and structure conventions that make this kind of key management easier to automate across a project.
Connecting detection to your translation workflow
Detection tools are only half the loop. Once a scan finds a missing key, something needs to route that gap to whoever can actually fill it, whether that’s a translator, an agency, or an automated production step.
Teams using a full translation management system typically sync locale files through an API or a connector plugin, so a missing key detected in CI can automatically open a task in the TMS without anyone copying strings by hand. That integration overhead is exactly what makes a full TMS a heavier commitment than a small team often needs, especially early on when the string count is still manageable.
For teams that want validated, packaged output without standing up a full TMS or granting repository access, our platform handles the translation, validation, and packaging steps as one pass instead of a chain of separate tools. The output lands in review-ready language files, whether that’s JSON, iOS strings, or Android XML, so the connection between “we found a gap” and “we have a translated, packaged fix” stays short. Our structured packaging page covers how that packaging step is organized by file format and platform.
Keeping translation files in sync across environments
A locale file that’s correct in development can still go stale by the time it reaches staging or production, especially when multiple branches touch translation keys at once. The most common failure mode is a merge that silently drops a key one branch added, because most version control diffs don’t flag a missing JSON key the way they’d flag a missing line of code.
Running your lint check as part of the merge process, not just before deploy, catches this early. A pre-merge check that compares the incoming branch’s locale files against the base branch’s key set will flag a dropped key before it ever reaches a shared environment.
For teams shipping to multiple platforms, keeping file shape consistent matters as much as keeping keys consistent. A key present in your iOS .strings file but missing from the equivalent Android XML file is still a missing translation, just one that a single-platform check won’t catch. Our guide on multilingual output folder structure covers how to organize locale directories so a sync check can compare across platforms rather than just across locales within one format.
A nightly full-environment scan, comparing production’s deployed locale files against the source repository, catches the cases a PR-level check misses entirely, like a manual hotfix that touched a locale file directly without going through the normal pipeline.

When to choose lightweight checks vs. full automation
Small, fast-moving teams should start with TypeScript typing where applicable plus a translint-style CLI check in CI. Growing or regulated products justify runtime locale tests and packaged automation, since the maintenance cost is worth the reduced risk once a missing string actually affects compliance or revenue.
— Arkian
Arkian: an automation option for validation and language-package delivery
Everything above assumes your team has the time to wire together a linter, a test suite, and a CI pipeline, and then keep maintaining all three as your string count grows. Not every small team has that time, and that gap is exactly what we built our platform to close.

The production of translation strings, voice output, and structured metadata can be automated, running validation and packaging as part of the same workflow instead of separate manual steps. This approach reduces places for a key to get dropped between a source file and a shipped package, and eliminates the need to grant repository access or stand up a full translation management system to keep locale files in sync.
- We handle localization strings across common formats like JSON, iOS strings, and Android XML.
- We package validated output into review-ready language files your team can inspect before release.
- We generate structured metadata alongside your translated strings in the same pass.
To be direct: automating production and validation doesn’t remove your team’s role in source copy quality or final translation decisions, those calls still belong to you. If you want to see pricing for a membership or pay-as-you-go credits, our pricing page lays out the Starter, Pro, and Studio options.
FAQ
What’s the fastest way to detect missing translations?
If your project uses TypeScript, deriving a type from your base locale JSON and typing your translation function against it catches missing keys at compile time with almost no setup cost. Pair that with a lint tool like translint in CI to catch empty values and placeholder mismatches that typing alone won’t see.
What’s the difference between a missing key and an untranslated string?
A missing key means the translation function has no entry to look up at all, which typically throws an error or falls back to a default. An untranslated string means the key exists but its value is empty, a placeholder, or identical to the source language, which translint’s heuristics can flag but require configuration to avoid false positives on legitimately identical values.
Can I rely on automatic translation to fill missing keys?
Automatic fill tools like translate-missing can unblock a build by filling gaps with machine translation, but the tool’s own documentation notes this isn’t a substitute for human review. Treat auto-filled entries as drafts flagged for translator review, not as finished, shippable text.
Why do exact-string tests fail across different browsers or locales?
Intl formatting behavior can vary between JavaScript engines and locales, so a test asserting an exact rendered string can break even when your translation is correct, as MDN’s internationalization documentation explains. Prefer tests that check for translated content or correct formatting behavior rather than matching a full string character for character.
Should missing translation checks block a CI pipeline?
High-confidence issues like missing keys, placeholder mismatches, and locale file parse errors should block a build, since they almost always indicate a real bug. Lower-confidence heuristics, like flags for possibly untranslated text, work better as warnings in a report so teams don’t start ignoring red pipelines.
Sources
- Internationalization - JavaScript | MDN
- translint v0.4.0 (PyPI)
- How to find missing or unused translation messages (Symfony)
- translate-missing (GitHub)