← All articles

WeightPlay Blog - Engineering notes

A Language Dropdown Is Not Localization

A translated button is only the visible beginning. The route, the next status message, and the screen reader announcement have to belong to the same language too.

Open the WeightPlay lobby →

The easiest localization bug to ship is the one that looks finished. The language dropdown says French, the main heading changes, and then a live status message or Result button quietly switches back to English. The screen feels translated until the player does something.

That is why I think of localization as a product contract rather than a bag of translated sentences. A selected language has to survive the route, the first paint, the next interaction, and the state after the game ends. If one of those layers falls back without a clear reason, the player receives a mixed interface.

The route is part of the language

WeightPlay keeps a canonical locale list and maps each locale to a public route segment. English uses /en/, Traditional Chinese uses /zh-tw/, Simplified Chinese uses /zh-cn/, and Brazilian Portuguese uses /pt-br/. The route is not cosmetic. It gives a refresh, a shared link, and a browser history entry a stable language identity.

A simplified version of the decision looks like this:

const locale = localeFromPathname(pathname);
const copy = dictionaries[locale] ?? dictionaries.en;
const segment = localeSegments[locale] ?? "en";

The important detail is the order. Read the language from the route first, then load the matching copy. A saved preference can help on a neutral entry page, but it should not silently override a locale-specific URL.

Static copy is only the first layer

A complete game surface has more text than the opening card. The language contract covers the lobby, public guide, Main, Stage, Battle, and Result screens. It also covers the page title, description, structured metadata, FAQ, dialog copy, dynamic progress, live-region feedback, and accessibility labels.

Dynamic text is where a partial translation becomes obvious. “Wave cleared,” “Retry,” a loading message, a score summary, or a count with a changing number is still part of the player’s current state. Leaving one of those strings in English is not harmless filler. It changes the language of the moment when the player most needs to understand what happened.

Accessibility copy makes the same point from another angle. A visible button may say “Start game” in Spanish while its accessible name still announces the English source string. The interface then disagrees with itself. A localized aria-label and live-region message are not polish added after the work; they are part of the control.

Four failures worth checking

These failures are easy to miss when checking only the lobby. They also explain why a language audit that counts dictionary keys is useful but not sufficient. The route must resolve, the rendered page must use the route’s language, and an interaction must prove that changing state does not fall back.

The smallest useful verification loop

For each required locale, I want one bounded journey:

  1. Open the locale route directly and confirm the visible shell, page title, and metadata belong to that language.
  2. Change a state that produces dynamic feedback, then inspect the visible message and its accessible name.
  3. Reach a completion, failure, or retry state and check the Result or dialog copy.
  4. Refresh and follow one internal link to make sure the locale identity survives navigation.

Automation is good at finding missing routes, incomplete dictionaries, mixed strings, and unexpected fallback. A short hands-on pass is still valuable because it catches the copy that appears only after a click, a timeout, a failed round, or a screen transition. The two checks answer different questions.

Know when English-only is the honest choice

Not every surface needs thirteen translated copies. WeightPlay’s Blog is intentionally English-only. That is a clear content boundary: the article navigation and metadata use English, while the games and public lobby own the broader locale contract.

The distinction matters. Saying “this surface is English-only” is honest product scope. Showing a language selector that changes only half the experience is a broken promise. A smaller, clearly declared surface is better than a multilingual label attached to an incomplete route.

A language dropdown is a promise, not the proof. The proof is that the chosen language survives the URL, the first render, the next state, the Result screen, and the words a screen reader hears. The next time you add a translated label, ask one more question: which locale owns it after the player does something?


This article was outlined with AI assistance and then checked and edited against the actual WeightPlay localization contract and source structure by the WeightStudio team.