Engineeringこの記事を日本語で読む

What should happen when a translation is missing?

Every localized app eventually ships a screen where a string was never translated into the language being shown — a new feature that shipped before translation caught up, a key added to the source file but not copied everywhere, or a regional variant that simply doesn't cover every string the base language does. What the app does at that moment is a design decision, and the three common answers each trade off differently.

Three ways to handle a missing string

The first option is a silent fallback to the source language: if the current locale doesn't have a translation for a key, show the source-language text instead. The user sees a real, readable sentence, just in the wrong language for one spot on the screen. This is the least jarring option for end users, since a stray sentence in another language usually reads as an incomplete translation rather than a broken app.

The second option is showing the raw key itself, like welcome_title, directly to the user. This is unambiguous and impossible to mistake for correct output, which is exactly why it's useful in development — but showing an identifier meant for code to an end user in production looks broken and gives them no useful information.

The third option is an empty string. Visually the cleanest failure in isolation, but it can be the most misleading: a blank label next to a working icon might look intentional, and a blank error message can leave a user with no idea what happened at all. Silence isn't the same as grace.

  • Source-language fallback — readable, but mixes languages on screen
  • Raw key shown — unmistakable, but meaningless and unpolished for end users
  • Empty string — visually quiet, but can hide the fact that anything is missing

Development wants loud, production wants graceful

These trade-offs point toward different defaults for different environments. In development, a missing translation should be as visible as possible — the raw key, a distinct visual marker, or even a console warning — because the person looking at the screen is the person who can fix it, and the whole point is to make the gap impossible to miss before it ships.

In production, the same loud failure is a liability: a real user has no way to fix a missing translation and no use for a raw key on screen. Production should degrade as gracefully as possible, almost always meaning a fallback to a language the app can be confident has full coverage — never a raw key, and rarely a silent empty field where the user needs actual information.

// development: unmistakable
t("welcome_title") // => "[[welcome_title]]"

// production: graceful
t("welcome_title") // => falls back to source-language text

Chained fallbacks: regional to base language

Fallback doesn't have to be a single jump straight to the source language. A common pattern is a chain: a regional variant like pt-BR falls back first to its base language pt, and only falls back to the source language if the base language doesn't have the string either. This usually produces a better result than jumping straight to the source language, because a regional variant and its base language are far more likely to share vocabulary and tone than the regional variant and an unrelated source language.

The chain should be explicit and short — regional variant, then base language, then source language, stop — rather than an open-ended search across every language the app happens to ship. An unbounded fallback chain makes it hard to predict, for any given missing string, which language a user will actually end up seeing.

Why the usual answer is 'both'

Treating development and production the same way tends to fail in one direction or the other. Make missing translations loud everywhere, and production users occasionally see raw identifiers on screen. Make them graceful everywhere, and a translator or developer can go weeks without noticing that a whole batch of strings never got translated, because the app quietly covers the gap every time.

The usual answer is to run both behaviors deliberately: fail loudly where the person who can fix it will see it, and fail gracefully where the person seeing it can't do anything about it. Which environment gets which behavior is a configuration choice, not a hardcoded one, so a staging environment used for translator review can also be set to the loud mode when that's useful.

Related articles