Eliminate Hardcoded English Leaks in Multilingual Web Apps
A practical architecture for keeping static templates, client-side state, accessibility labels, and locale navigation consistently localized.
Table of Contents11 sections

A page can look translated and still be fundamentally English underneath when client interactions reset localized labels to hardcoded literals. Quick solution: Ensure event listeners and dynamic UI state pull strings from the active locale object rather than hardcoded literals, as detailed in Astro’s internationalization guide and Google’s localized-version guidance.
button.addEventListener("click", () => {
running = !running;
button.textContent = running ? UI_I18N.pause : UI_I18N.start;
});
That is not a translation problem. It is an architecture problem.
A reliable multilingual web app needs one rule: the active locale must remain the source of truth for every user-facing string, including strings created after hydration.
This article shows how to enforce that rule in a static-first application such as Astro without turning localization into a collection of one-off fixes.
The almost localized failure mode
The first version of internationalization is usually straightforward. Put strings in a dictionary, generate one route per locale, and pass translated copy into the page template.
The trouble appears when the page becomes interactive.
A timer, calculator, dashboard, or editor often has at least four different places where text can originate:
- server-rendered template copy;
- client-side event handlers;
- accessibility and tooltip attributes;
- navigation code that constructs the next URL.
If only the first layer reads from the locale dictionary, the app is localized only until the user touches it.
A common example looks harmless:
button.addEventListener("click", () => {
running = !running;
button.textContent = running ? "Pause" : "Start";
});
The initial HTML may say Pausar or Jeda, but the first click restores English because the event loop owns its own copy.
The same leak happens in title attributes, toast messages, modal buttons, empty states, validation copy, keyboard shortcut hints, and aria-label values. Visible screenshots often miss these cases.
Make the dictionary a typed product surface
The durable fix is not searching for English strings before release. The fix is to make the locale contract complete enough that new UI cannot compile cleanly without providing its localized text.
A TypeScript shape can group the strings by behavior:
interface TimerUiStrings {
start: string;
pause: string;
reset: string;
startPauseTooltip: string;
customDialog: {
title: string;
cancel: string;
apply: string;
};
accessibility: {
presenterControls: string;
presets: string;
};
}
Every locale implements the same structure. When a new interactive label is introduced, TypeScript forces the missing translations to become visible during development instead of quietly falling back to a hardcoded English literal.
This is also why a central dictionary is more valuable than scattering translation calls through components. It becomes an API between the locale layer and the UI layer.
Astro provides routing helpers for internationalized projects, and its i18n APIs expose the current locale and locale-aware URL generation. The important architectural decision is to keep those route rules and the text dictionary aligned rather than maintaining separate conventions.
Pass locale data into client code deliberately
Static rendering does not automatically make hydrated JavaScript locale-aware.
When a client-side script needs translated strings, pass only the small dictionary it requires. In Astro, server-side values can be exposed to an inline script and then used by event handlers.
Conceptually:
`<script define:vars={{ UI_I18N: locale.ui }}>`
function syncPlayButton(running) {
playButton.textContent = running ? UI_I18N.pause : UI_I18N.start;
playButton.setAttribute(
"aria-label",
running ? UI_I18N.pause : UI_I18N.start
);
}
`</script>`
The key idea is not the framework directive itself. It is that state transitions consume localized data instead of inventing copy.
This gives you a useful review rule: if client JavaScript assigns user-facing text, that assignment should be traceable to the active locale object.
Localize attributes, not only visible text
Accessibility attributes are easy to miss because they are not visible in a normal visual review.
These all belong to the localization contract:
- aria-label;
- title;
- placeholder;
- form validation copy;
- dialog labels;
- assistive descriptions;
- tooltip text.
For example, a translated icon button with aria-label=“Presenter Controls” still produces an English experience for a screen-reader user.
This is also why automated localization checks should inspect rendered HTML, not only screenshots.
A useful test for a non-English route combines two assertions:
assert(html.includes(expectedLocaleText));
assert(!html.includes("Presenter Controls"));
The positive assertion proves that a known translation rendered. The negative assertion catches a known canonical English leak.
Do not make the negative list enormous. Start with the high-risk strings that client code commonly rewrites.
Preserve route hierarchy when switching languages
Locale switchers often introduce a different class of bug: they preserve the wrong concept.
Suppose the user is on a Spanish hub:
/es/temporizador/
A naive switcher may read a default duration and send them to:
/de/timer/15-minuten/
That is a valid German page, but it is not the equivalent page.
Your switcher needs to distinguish page type before it builds the destination:
type PageKind = "hub" | "duration" | "use-case";
function localeDestination(kind, locale, routeData) {
if (kind === "hub") return localeHub(locale);
if (kind === "duration") return localeDuration(locale, routeData.minutes);
return localeUseCase(locale, routeData.useCase);
}
The rule is simple: hub maps to hub, leaf maps to equivalent leaf.
This becomes especially important once localized slugs stop being literal translations. A Spanish route may use temporizador while another locale keeps timer. Central route helpers prevent components from memorizing those differences.
If you are also managing canonicals and search metadata, treat this route matrix as part of the same architecture. RayLabs covers the broader metadata boundary in Site-Wide Structured Data and Social Graph Integration.
Test localization as a behavior
A useful localization suite does more than count dictionary keys.
For each supported locale, test at least:
- hub route renders;
- representative leaf route renders;
- expected translated strings exist;
- known English leak strings do not exist;
- language switcher keeps the same page kind;
- interaction does not revert labels to English;
- aria-label and title attributes use the active locale;
- canonical and alternate URLs still resolve.
The most valuable test is often the interaction test. A page can pass an HTML snapshot and still revert after JavaScript runs.
If you use Playwright or another browser runner, click the controls that mutate labels and inspect the resulting DOM. That catches the bugs that static checks cannot.
Avoid fallback behavior that hides missing translations
Fallbacks are useful for keeping a page alive, but they can also hide product defects.
A fallback such as:
label = locale.reset ?? english.reset;
may be appropriate during development, yet it turns a missing translation into a silent production regression.
For critical UI, prefer one of these approaches:
- fail the build when a required key is missing;
- mark optional fields explicitly in the type;
- emit a test failure for fallback usage on production locales.
A missing optional marketing sentence and a missing Reset button should not be treated equally.
Localization is also a URL contract
Internationalization touches SEO because language versions need stable, discoverable URLs. Google recommends explicit alternate-language annotations and reciprocal relationships between localized versions.
That does not mean every UI translation decision is an SEO decision. It means your route matrix, locale dictionary, canonical URLs, and alternate URLs should agree.
If you later migrate domains or route structures, preserving this mapping becomes part of the migration plan. The same principle appears in Change Domain Without Losing SEO: redirects and canonicals need to describe the same destination model.
Execution and verification checklist
- Verify all required dictionary keys exist in the TypeScript interface without missing properties.
- Confirm no event handler writes a raw user-facing English string without pulling from the locale dictionary.
- Audit aria-label, title, placeholder, and dialog copy to ensure complete non-English coverage.
- Test hub and leaf locale switching to guarantee intent preservation across route alterations.
- Run static HTML tests and automated browser assertions to catch runtime label regressions.
Conclusion on multilingual architecture
Multilingual quality is easiest to maintain when localization is treated like state management.
The active locale is state. Templates consume it. Event handlers consume it. Accessibility metadata consumes it. Navigation consumes it. Tests verify that every layer stays synchronized.
Once that boundary is explicit, the annoying class of bugs where a translated page quietly reverts to English becomes much easier to prevent than to hunt after release.
Continue Exploring
You Might Also Like

Preserve Captions in Markdown Publishing
Prevent image captions from leaking into article prose and insert internal links without rewriting the author's sentence around a destination title.

Multilingual SEO Without Broken Hreflang
Design localized URL matrices, reciprocal hreflang, edge redirects, and scalable QA for programmatic multilingual sites.

Fixing Zero-Click Technical Articles
Learn how to diagnose and resolve high-impression, zero-click search leaks on technical documentation by aligning metadata titles with developer error-intent queries.