React i18n vs automatic translation: which approach is right for your app? (2026)
React teams have two fundamentally different paths to multilingual support: i18n libraries that instrument your source code, and automatic translation that works at the DOM level. Neither is universally better. This guide maps out when each approach is the right call.
The traditional React i18n approach
The standard libraries for React internationalization are react-i18next, react-intl (part of FormatJS), and LinguiJS. Despite their differences, they share the same core workflow:
- 1Replace every string in your components with a
t()call:<p>Submit</p>becomes<p>{t('form.submit')}</p>. - 2Create a JSON translation file per locale:
en.json,fr.json,de.json, etc. Each key maps to a translated string. - 3Handle pluralization per language — the rules for plurals in Polish or Arabic are significantly more complex than English.
- 4Add type-safe key definitions so TypeScript catches missing or mistyped translation keys at compile time (via
typescript-i18nor i18next's own typing support). - 5Configure the build or runtime to load the correct locale bundle, and set up a process so new strings added during development automatically surface in the translation workflow.
For a Next.js App Router project, add next-intl or next-i18next for locale routing and SSR-rendered translations.
The real cost of i18n libraries
The per-unit cost of each string wrapped in t() is small. The aggregate cost is significant:
Initial migration: 2–5 engineering days
For an existing React application with a year or more of UI development, auditing every component for hardcoded strings, wrapping them in t(), creating the initial translation namespace structure, and setting up the i18n provider is a multi-day project — even before any strings are translated.
Ongoing maintenance per new string
Every new UI string requires: a key definition, an English entry in the source locale file, and an update to every target language file. If the string is added in a PR on Tuesday, the translation files for French and German need to ship before the feature is visible to those users.
Context and namespace management
As translation files grow into hundreds or thousands of keys, namespace management becomes its own discipline. Keys need prefixes, namespaces need loading strategies, and the risk of key collision or unused-key accumulation increases over time.
RTL requires separate layout work
i18n libraries handle string translation but not layout direction. Supporting Arabic or Hebrew requires separate CSS work on top of the library setup — logical properties, icon mirroring, and testing under dir="rtl".
When i18n libraries are the right choice
There are specific situations where the overhead of a library is justified or even required:
Open-source projects
When translation files are community-contributed on GitHub, a standard i18n library format (JSON or PO files) is the right interface for contributors. Automatic translation doesn't work for projects that want human community contributions.
Compliance-driven string review
Regulated industries (legal, medical, financial) may require that every UI string is explicitly reviewed and approved before deployment. An i18n library makes this auditable — every string is a discrete artifact with a version history.
Heavy number, date, and currency formatting
If your app displays formatted numbers, dates, and currency amounts throughout the UI (e.g., an analytics dashboard or accounting tool), libraries like FormatJS provide locale-aware formatting via the IntlAPI that DOM-based translation doesn't provide.
Offline-first apps
Apps that must work offline (PWAs with full offline support) need bundled translation files because they can't call a translation API when there's no network. i18n libraries bundle translations at build time.
When automatic translation is better
The DOM-based approach is better suited to a different set of constraints:
Adding localization to an existing app without a rewrite
The single strongest case for automatic translation. If you have a working React application and want to add five languages without touching 200 components, DOM-based translation is the only pragmatic option.
Teams without a dedicated i18n engineer
Setting up and maintaining a library-based i18n system well requires expertise in the library, the translation workflow, and pluralization edge cases. A script-tag approach requires none of that.
Rapidly evolving UI
When the product ships new UI every week, maintaining translation file parity is a constant friction. Automatic translation handles new strings on the first page visit with no additional steps.
Speed-to-market matters more than perfection
A high-quality automatic translation live in a week beats a perfect i18n-library integration that ships in three months. For many international markets, the translation quality difference is not the deciding factor — presence is.
How DOM-based translation works in React
Lingvit uses a MutationObserver-based approach that integrates naturally with React's rendering model:
- 1On initial load, the script walks all existing text nodes in the DOM and sends them for translation. React's virtual DOM and rendering mechanism are invisible to this process — only the final rendered HTML matters.
- 2A MutationObserver watches for subsequent DOM mutations — new nodes added by React state changes, route transitions (client-side navigation in Next.js), lazy-loaded components, and dynamic content from API responses.
- 3Stored translations are fetched through the API. Switching languages does not require a page reload because translated strings are swapped in place.
- 4For RTL languages,
document.documentElement.diris set automatically, flipping any CSS logical properties in your layout.
Setup comparison
| Step | i18n library | Lingvit |
|---|---|---|
| Install | npm install react-i18next i18next | 1 script tag |
| Configuration | i18n instance + provider + namespace config (~100 lines) | Dashboard: pick languages |
| Instrument strings | Wrap every string in t() across all components | No code changes |
| Translation files | JSON file per language, maintained in repo | Generated automatically, stored in Lingvit |
| Type safety | Optional via typescript-i18n (~50 lines config) | Not applicable |
| New UI string | Add key + update all locale files | Translated on first page visit |
| RTL support | Separate CSS work required | Automatic for Arabic, Hebrew, Persian, Urdu |
Scroll horizontally to view every column.
Frequently asked questions
- Does Lingvit work with React 18/19 Concurrent Mode and streaming SSR?
- Yes. The MutationObserver approach is React-version-agnostic — it observes the final DOM output regardless of how React renders it. Concurrent Mode, Suspense, and streaming SSR all eventually produce DOM nodes, and the observer handles them normally. There are no React-version-specific APIs involved.
- Does it work with Next.js App Router and server components?
- Yes. Server Components produce HTML that is sent to the browser and inserted into the DOM just like any other HTML. The script tag in your root layout.tsx picks up this content normally. Client-side navigation handled by Next.js triggers the MutationObserver for the newly rendered content.
- What about professional translators — can they still review strings?
- Yes. Lingvit's dashboard provides a translation browser where professional translators can review, edit, and approve every string per language. Overrides are stored permanently and take precedence over automatic translations. This gives you the review workflow of an i18n library without maintaining translation files in your repository.
Related guides
Add languages to your React app
One script tag. Any React version. Works with Next.js, Vite, CRA, and any framework.