LingoProvider is the React context that holds the active locale and the messages map. Wrap it once at the root of your app — everything inside can call useLingo() to translate strings or read locale metadata.
Basic usage#
import { LingoProvider } from "@lingo.dev/react";
import messages from "./locales/es.json";
<LingoProvider locale="es" messages={messages}>
<App />
</LingoProvider>Props#
| Prop | Type | Required | What it does |
|---|---|---|---|
locale | string | yes | BCP-47 tag, e.g. "en", "es", "ar-SA". Drives all formatting + RTL detection. |
messages | Messages | no | Hash-keyed translations. Defaults to {} (everything falls back to source). |
children | ReactNode | yes | Your app. |
Messages is just Record<string, string> — the same shape the CLI writes to locales/<locale>.json.
Nesting providers#
Providers can nest. The rules are different depending on whether the nested provider has the same locale as the parent or a different one.
Same locale — messages merge#
<LingoProvider locale="es" messages={sharedMessages}>
{/* Route-scoped messages override shared on collision; missing keys fall through to shared. */}
<LingoProvider locale="es" messages={dashboardMessages}>
<Dashboard />
</LingoProvider>
</LingoProvider>Use this to split bundles per route while keeping a common "shell" set of translations at the root.
Different locales — standalone#
<LingoProvider locale="es" messages={esMessages}>
<Header />
<LingoProvider locale="ar-SA" messages={arMessages}>
{/* This subtree is entirely Arabic; the parent's es messages are NOT visible. */}
<ArabicEmbed />
</LingoProvider>
</LingoProvider>Handy for rendering a single component in a fixed language (a quote, an embed, a preview pane) inside an otherwise different app.
Switching locale at runtime#
Treat locale as React state — change it, and every useLingo() consumer below re-renders with the new locale and message bag.
function AppRoot() {
const [locale, setLocale] = useState("en");
const [messages, setMessages] = useState({});
async function switchTo(next: string) {
const next_messages = await import(`./locales/${next}.json`);
setLocale(next);
setMessages(next_messages.default);
}
return (
<LingoProvider locale={locale} messages={messages}>
<LocaleSwitcher current={locale} onSelect={switchTo} />
<App />
</LingoProvider>
);
}On Next.js, prefer useLocaleSwitch() from @lingo.dev/react-next — it handles router-aware locale changes plus persistence.
What you can read from the context#
useLingo() returns the active Lingo object. Beyond text() and rich() it carries:
locale— the BCP-47 string you passed indirection—"ltr"or"rtl", computed viaIntl.Locale.textInfowith a fallback list of known RTL languagesscript— e.g."Latn","Cyrl","Arab"region— e.g."US","DE","SA"
These are useful for conditional layout (mirroring icons in RTL) or analytics tagging — no extra parsing required.
Common mistakes#
- Forgetting
<LingoProvider>.useLingo()throws outside one. The error message tells you to add a provider; if you're seeing it in tests, wrap the render in a test-mode provider with emptymessagesto make assertions stable. - Passing async-loaded messages directly.
messagesmust be a synchronous value. Resolve the promise in a parent (with Suspense or state), then pass the result down. - Switching
localewithout updatingmessages. The provider trusts both props together — change them in the sameuseStateupdate or you'll briefly render the new locale with the old translations.
