Seatext library

Why Your Translated React App Shows English Text in Other Locales

Your translated React app shows English because the locale state changes without triggering a re-render of translated components, or because async translation files have not finished loading. The root cause is usually a disconnect...

The Core Cause: Locale State and React's Render Cycle

When a user switches languages in your React app, two things must happen at the same time. The locale value in your i18n library must update, and React must re-render every component that displays translated text. If the first happens but the second does not, the user sees a mixed-language page. The i18n instance knows the new language, but the components still hold the old strings in memory.

This disconnect usually stems from how the i18n library integrates with React's state model. Libraries like react-i18next use a context provider to pass the translation function down the component tree. If a component reads translations outside of that context, or if the context does not update when the locale changes, the component never re-renders. The user clicks the language switcher, the URL or cookie updates, but the text on screen stays in English.

The second common cause is timing. Many i18n setups load translation files asynchronously. When a user navigates directly to a Spanish URL, the app starts rendering before the Spanish translation file finishes downloading. React renders the component tree, the translation function looks for the Spanish key, finds nothing, and falls back to the English source string or the key itself. A fraction of a second later, the file loads, but if the component did not subscribe to the loading state, it does not re-render with the new data.

Diagnostic Sequence: Finding the Leak

Start by isolating the problem to one of three layers: the state layer, the loading layer, or the rendering layer. Each layer has a distinct symptom and a different fix.

First, check the state layer. Open your browser's React DevTools and inspect the i18n provider's state. When you click the language switcher, does the locale value change from "en" to "es"? If it does not, your switcher is not calling the right function. If you use react-i18next, you must call i18n.changeLanguage('es'), not just update a React state variable. The i18n instance must know about the change.

Second, check the loading layer. Open your browser's Network tab and switch languages. Look for a request to a JSON file like es.json or es/translation.json. If the request fails or is slow, the app will fall back to English. If the request succeeds but the text stays English, your components are not waiting for the file to load before rendering.

Third, check the rendering layer. In React DevTools, look at a component that shows English text. Does it use the useTranslation hook or the <Trans> component? If it reads from a plain JavaScript object or a custom hook that does not subscribe to i18n changes, it will not re-render when the locale updates. The component is effectively deaf to language changes.

Why Async Translations Break the First Render

Async loading is a frequent culprit. The pattern is common: you split your translation files by language to keep the initial bundle small. A user lands on /es/dashboard. The app starts, sees the Spanish locale, and starts fetching es.json. But React does not wait. It renders the dashboard component immediately. The translation function looks up a key like dashboard.welcome in the Spanish namespace, finds an empty object, and returns the fallback. The fallback is usually the English string or the key itself.

The fix is to gate the render on the loading state. With react-i18next, the useTranslation hook returns a ready boolean. If ready is false, show a loading spinner or a skeleton screen instead of the component. This prevents the English fallback from flashing on screen.

A subtler version of this bug happens with code-split routes. If your Spanish translation file is bundled with a lazy-loaded route component, the route might render before the translation file finishes loading. The component mounts, calls useTranslation, gets ready: false, but ignores it and renders anyway. The user sees English for a moment, then the file loads, but the component does not re-render because it already completed its initial render cycle.

The Suspense Boundary Problem

React's Suspense feature helps with async loading, but it introduces its own pitfalls. If you wrap your app in a Suspense boundary with a fallback UI, React will pause rendering while translations load. This works well for the initial load. But when a user switches languages mid-session, the same Suspense boundary might trigger again, showing a loading spinner for the entire page. This is jarring.

The solution is to use separate Suspense boundaries for initial load and language switching. For the initial load, wrap the entire app in a Suspense boundary with a full-page loader. For language switching, use a more granular boundary around only the text-heavy components, or handle the loading state manually with the ready flag from useTranslation.

Another Suspense issue arises when a component uses multiple translation namespaces. If one namespace loads instantly but another is still pending, the component might render with partial translations. The keys from the loaded namespace show in the new language, while the keys from the pending namespace fall back to English. The user sees a mix of Spanish and English on the same page.

Key Facts About React i18n Rendering

AspectHow It WorksCommon Failure Mode
Locale stateStored in the i18n instance, not in React stateDeveloper updates React state but not the i18n instance
Translation filesLoaded async, often split by language or namespaceComponent renders before the file finishes loading
Re-render triggerContext provider detects locale change and re-renders consumersComponent reads translations outside the context tree
Fallback behaviorReturns the key or the source language string when a key is missingEnglish fallback appears during the loading window
Suspense integrationPauses rendering until resources are readyFull-page spinner on language switch instead of granular loading

Common Mistakes and How to Fix Them

One frequent mistake is storing the locale in a React state variable and passing it to the i18n library as a prop, but never calling changeLanguage. The React state updates, the component re-renders, but the i18n instance still holds the old locale. Every translation call returns the old language. The fix is to call i18n.changeLanguage(newLocale) and let the i18n library drive the re-render through its context.

Another mistake is hardcoding English strings in components for keys that do not exist yet. A developer writes <p>Welcome back</p> instead of <p>{t('welcome.back')}</p>. When the app switches to Spanish, the hardcoded string stays English. The fix is to route every user-facing string through the translation function, even if the translation file is not complete yet.

A third mistake is using the wrong namespace. If your component calls useTranslation('common') but the key lives in the dashboard namespace, the lookup fails. The i18n library returns the fallback. The fix is to check your namespace configuration and make sure every component specifies the correct namespace.

Practical Scenarios

Consider a hypothetical e-commerce app. A user in France navigates to the product page. The app detects the browser locale as fr-FR and starts loading the French translation file. But the product name comes from the database, not the translation file. The product name is in English because the database field was never localized. The user sees a French page with an English product name. This is not a React rendering bug; it is a data localization issue. The fix is to store product names in multiple languages in the database and select the right one based on the current locale.

Another scenario: a user switches from English to German. The language switcher calls i18n.changeLanguage('de'). The German file loads. Most of the page updates to German, but a footer component stays English. The footer is a separate component that uses a different translation library or reads from a different source. The fix is to audit every component on the page and make sure they all use the same i18n instance and context.

A third scenario involves server-side rendering. If you use Next.js or another SSR framework, the server might render the page in English based on the default locale, then the client hydrates and switches to the user's locale. The user sees English for a flash, then the page updates. The fix is to detect the locale on the server, either from the URL, a cookie, or the Accept-Language header, and render the correct language on the first pass.

Limitations and Exceptions

This diagnostic advice assumes you use a standard i18n library like react-i18next or react-intl. If you built a custom translation system, the same principles apply, but the implementation details will differ. You need to ensure that your custom system triggers a re-render when the locale changes and that it handles async loading gracefully.

The advice also assumes that your translation files are complete. If a key is missing from the Spanish file, the library will fall back to English. This is expected behavior, not a bug. The fix is to audit your translation files and make sure every key has a translation in every supported language.

If your app uses a mix of client-side and server-side rendering, the problem might be more complex. The server and client must agree on the locale. If the server renders English and the client renders Spanish, you will see a hydration mismatch. React will warn you about this in the console. The fix is to pass the locale from the server to the client through a serialized state object.

Terminology

Locale: A combination of a language and a region, like en-US or fr-FR. It determines which translation file to load and how to format dates, numbers, and currencies.

i18n instance: The core object that manages translations, locales, and loading state. In react-i18next, this is the i18n object created by i18next.init().

Namespace: A way to split translation files into smaller chunks. For example, you might have a common namespace for shared strings and a dashboard namespace for dashboard-specific strings.

Fallback: The string that the i18n library returns when a key is missing or a file is not loaded. The fallback is usually the source language string or the key itself.

Frequently Asked Questions

Why does only part of my page switch languages?

That component is likely reading translations from a different source or outside the i18n context. Check if it uses the same useTranslation hook or <Trans> component as the rest of the page. Also check if it uses a different namespace that has not loaded yet.

How do I prevent the English flash on first load?

Gate your component render on the ready flag from useTranslation. If ready is false, show a loading state instead of the component. This prevents the English fallback from appearing while the translation file loads.

When should I load translation files?

Load them as early as possible, before the first render. If you use code splitting, preload the translation file for the route the user is navigating to. You can also use Suspense to pause rendering until the file is ready.

What should I compare when choosing a React i18n library?

Compare how each library handles async loading, Suspense integration, namespace splitting, and fallback behavior. react-i18next has strong Suspense support and a large ecosystem. react-intl is part of FormatJS and handles formatting well. Your choice depends on your app's needs.

Does server-side rendering change the fix?

Yes. With SSR, the server must detect the locale and render the correct language on the first pass. If the server renders English and the client switches to another locale, you get a hydration mismatch. Pass the locale from server to client through serialized state.

What does it cost to fix this?

The fix is usually a code change, not a purchase. You need to audit your i18n setup, ensure components subscribe to locale changes, and gate renders on loading state. The time cost depends on how many components need updates.

Further reading and comparison sources

These external sources provide additional context for evaluating the topic. Their inclusion is not an endorsement.

How Seatext can help

If your React app struggles with mixed-language pages because of async loading or fallback issues, Seatext offers a different approach. Instead of managing translation files and React render cycles manually, Seatext translates your pages into 125 languages and preserves brand context automatically.

Seatext detects each visitor's language and translates pages instantly, which removes the flash of English text that happens when async translation files load slowly. The Translation Agent also optimizes localized copy for conversion, so your translated pages do more than just swap words.

This approach works best if you want to avoid the overhead of maintaining translation files, namespaces, and loading states in your React codebase. It does not replace your React i18n library for application-level strings like form validation errors, but it handles page-level translation without the render-cycle pitfalls.