09:10 a release adds Portuguese for Angola. 09:40 support in Luanda reports prices shown in Brazilian reais with Brazilian tax wording. 10:05 one page shows English legal copy inside an otherwise Portuguese flow. No pricing or tax code changed in the release and every response is HTTP 200. What failed and which design decision allowed it?
Show the full answer Hide the answer
The trigger
A new locale tag was added with a catalogue roughly 60% complete. Language negotiation did what the standard says: RFC 4647, published in 2006, defines a lookup scheme that progressively truncates subtags, so the requested tag with no exact match was truncated and matched the only Portuguese catalogue present, the Brazilian one. That resolved tag was then used for three unrelated decisions — which strings to show, how to format numbers and currency, and which tax and legal text to render.
Why it propagated
The design decision is that one locale identifier was treated as the key for both presentation and commercial configuration. Falling back from a missing translation to a nearby language is correct behaviour and almost always what a user wants. Falling back from a missing market to a nearby market is a statement about price and law, and the code could not tell the two apart because they were the same variable.
The English page is the same bug one layer down: that page's namespace had no entry at all, so the resolver went past Portuguese to the default locale. The chain worked exactly as configured on both pages.
Why detection lagged
Nothing errored. Every request returned 200 with a complete page, so error-rate alerts and synthetic checks were green. Pseudo-locale testing — which this team had — exposes layout and concatenation defects and says nothing about resolution, because it tests a locale that is always present. No test asserted which tag was actually resolved, and no telemetry recorded it.
The structural fix versus the tempting local fix
The tempting fix is to add the missing Angolan catalogue. It closes today's gap and leaves the next market exposed on launch day.
The structural fix separates the chains:
- A translation chain that may degrade, declared per locale, and may end at English.
- A commercial chain that must not degrade. Currency, tax wording, legal text and measurement units resolve from the market record keyed by country, and a missing value is a hard failure of that component — render a placeholder and alert, never a neighbour's value.
- Per-namespace fallback policy. Interface chrome falls back silently. Choose a hard failure for any namespace marked priced or legal: it does not render on a miss unless an exact market match exists.
- Log the resolved tag on every render and alert when it differs from the requested tag for a market that is configured as launched. One counter would have caught this at 09:12 rather than 09:40.
When this is the wrong answer
For a product with one currency and one legal jurisdiction, splitting the chains is ceremony: language is the only axis that varies, and a single tag is honest. The split earns its cost at the second market, which is also when retrofitting it starts costing weeks.
The general lesson
A fallback chain is an availability mechanism. Pointed at text it degrades gracefully. Pointed at money or law it converts a missing translation into a confident false statement, which is the more expensive failure and the one that will not page anybody.