SDKs
React SDK
Messagevisor's React SDK connects a Messagevisor runtime to React. It gives your application a single provider, ergonomic hooks for translations and formats, and access to the underlying JavaScript SDK.
Use it when values such as the active locale, datafile, context, currency, or time zone should update your UI naturally.
Install#
$ npm install --save @messagevisor/sdk @messagevisor/reactMost applications that use placeholders, plurals, selects, dates, numbers, or rich text should also install the ICU module:
$ npm install --save @messagevisor/module-icu@messagevisor/react supports React 16.8 and later. The package uses React's useSyncExternalStore when available and has a compatible fallback for older supported React versions.
Start with one SDK instance#
Create the Messagevisor instance outside React when the datafile is bundled, or memoize it when it belongs to an application root. Do not create a new instance during every render: replacing the provider instance also replaces its subscriptions and runtime state.
import { createMessagevisor } from "@messagevisor/sdk";import { createICUModule } from "@messagevisor/module-icu";import { MessagevisorProvider } from "@messagevisor/react";import datafile from "./datafiles/messagevisor-web-en-US.json";const messagevisor = createMessagevisor({ datafile, context: { platform: "web" }, modules: [createICUModule({ ignoreTags: false })],});export function App() { return ( <MessagevisorProvider instance={messagevisor}> {/* your application */} </MessagevisorProvider> );}The SDK instance owns datafiles, locale, context, format defaults, diagnostics, and SDK modules. The React provider only makes that instance available to descendants and adds React-specific rich-text support.
Load datafiles at runtime#
Messagevisor does not prescribe fetching or caching. A common browser pattern is to create one empty instance, load a target-and-locale datafile from your CDN, and then select its locale.
import { useEffect, useMemo } from "react";import { createMessagevisor } from "@messagevisor/sdk";import { createICUModule } from "@messagevisor/module-icu";import { MessagevisorProvider } from "@messagevisor/react";export function MessagevisorRoot({ children }: { children: React.ReactNode }) { const messagevisor = useMemo( () => createMessagevisor({ modules: [createICUModule({ ignoreTags: false })] }), [], ); useEffect(() => { let cancelled = false; async function load() { const datafile = await fetch("/datafiles/messagevisor-web-en-US.json").then((res) => { if (!res.ok) throw new Error("Could not load Messagevisor datafile"); return res.json(); }); if (!cancelled) { messagevisor.setDatafile(datafile); messagevisor.setLocale(datafile.locale); } } void load(); return () => { cancelled = true; }; }, [messagevisor]); return <MessagevisorProvider instance={messagevisor}>{children}</MessagevisorProvider>;}For a locale switch, load the datafile first, call setDatafile(datafile), then call setLocale(locale). Setting a datafile for another locale does not change the active locale by itself. See datafile operations for merging, replacing, and split-datafile behavior.
Build and serve the datafile for the application’s target, not a broader export, so the browser only receives the messages, overrides, and formats it needs.
Choose the right hook#
There are three useful levels of access.
| Need | Use | Re-renders when Messagevisor changes? |
|---|---|---|
| Translate or format in JSX | focused hooks such as useTranslation() | Yes |
| Read current locale, direction, context, currency, or time zone | focused state hooks | Yes |
| Call setters in an event handler or perform an imperative lookup | useMessagevisor() | No |
| Access the raw SDK, events, diagnostics, or an API not wrapped by React | useSdk() | No |
| Inspect all observable state | useMessagevisorSnapshot() | Yes |
useMessagevisor() returns stable, bound methods for the current provider instance. It intentionally does not subscribe a component to state changes. Use a reactive hook alongside it when a render needs both actions and current state.
Translate reactively#
useTranslation() is the default choice for message keys rendered in a component. It uses the active locale and current context, so overrides react when either changes.
import { useTranslation } from "@messagevisor/react";export function Greeting({ name }: { name: string }) { const welcome = useTranslation("dashboard.welcome", { name }); return <h1>{welcome}</h1>;}You can supply the same translation options as the JavaScript SDK, including per-call context, locale, currency, time zone, or defaultTranslation:
const text = useTranslation("checkout.total", { amount: 42 }, { context: { plan: "enterprise" }, defaultTranslation: "Total: {amount, number, money}",});The per-call context is useful for an isolated lookup. Use setContext() for state that should affect many messages.
Format an arbitrary message#
Use useFormatMessage() for a string that is not a Messagevisor key but should run through the same modules and locale settings:
import { useFormatMessage } from "@messagevisor/react";const preview = useFormatMessage("Hello {name}", { name: "Ada" });Read locale-aware state#
The focused state hooks avoid making your component work with the whole SDK object:
import { useDirection, useLocale, useLocaleInfo } from "@messagevisor/react";export function AppShell({ children }: { children: React.ReactNode }) { const locale = useLocale(); const direction = useDirection(); const localeInfo = useLocaleInfo(); // { locale, direction } return ( <div lang={locale || undefined} dir={direction} data-locale={localeInfo.locale || undefined}> {children} </div> );}Also available:
useMessagevisorContext()useCurrency()useTimeZone()useMessagevisorSnapshot()for{ version, locale, direction, context, currency, timeZone, datafileLocales, datafileRevisionsByLocale }
Keep dir close to your application shell. Messagevisor tells you whether the active locale is ltr or rtl; your app and CSS decide how to apply that direction.
Change runtime state from React#
Use useMessagevisor() for actions. Its methods are safe to destructure because they are bound to the SDK instance.
import { useMessagevisor } from "@messagevisor/react";export function LocaleSwitcher() { const { setContext, setCurrency, setLocale } = useMessagevisor(); return ( <div> <button onClick={() => setLocale("nl-NL")}>Nederlands</button> <button onClick={() => setContext({ plan: "pro" })}>Use pro copy</button> <button onClick={() => setCurrency("EUR")}>Show EUR</button> </div> );}setContext() shallow-merges top-level keys. Pass true as its second argument to replace the entire context. Changing locale, context, currency, time zone, or datafiles publishes a new Messagevisor snapshot, so reactive hooks update automatically.
For an arbitrary locale lookup without changing the app state, use the imperative getDirection(locale?), getRevision(locale?), or pass locale in a translation or formatter option.
Locale-aware formatting hooks#
The React package provides reactive wrappers for the JavaScript SDK formatters. They use the active locale, datafile presets, and current currency/time-zone state unless you override them in the call.
import { useFormatDate, useFormatDateTimeRange, useFormatNumber, useFormatRelativeTime,} from "@messagevisor/react";export function OrderSummary({ total, createdAt }: { total: number; createdAt: string }) { const price = useFormatNumber(total, "money"); const date = useFormatDate(createdAt, "long"); const updated = useFormatRelativeTime(-1, "day", "short"); const range = useFormatDateTimeRange(createdAt, "2026-12-31", "short"); return <p>{price} · {date} · {updated} · {range}</p>;}Available hooks include:
useFormatNumber()anduseFormatNumberToParts()useFormatDate(),useFormatDateToParts(),useFormatTime(), anduseFormatTimeToParts()useFormatDateTimeRange()useFormatRelativeTime()useFormatPlural()useFormatList()anduseFormatListToParts()useFormatDisplayName()
Named presets come from the active datafile’s formats. You can also pass an inline Intl options object when a one-off format is clearer.
Rich text in translations#
For ICU rich text such as Read our <link>terms</link>., configure the SDK with createICUModule({ ignoreTags: false }). Then provide React handlers either once on the provider or per translation.
<MessagevisorProvider instance={messagevisor} defaultRichTextElements={{ strong: (chunks) => <strong>{chunks}</strong>, link: (chunks) => <a href="/terms">{chunks}</a>, }}> <AppContent /></MessagevisorProvider>import { useTranslation } from "@messagevisor/react";export function Terms() { return useTranslation("legal.terms", { link: (chunks) => <a href="/legal/terms">{chunks}</a>, });}t() from useMessagevisor() supports the same React rich-text values. Use it wherever you already have the imperative API:
import { useMessagevisor } from "@messagevisor/react";export function Terms() { const { t } = useMessagevisor(); return t("legal.terms", { link: (chunks) => <a href="/legal/terms">{chunks}</a>, });}Provider defaults are only supplied for tag names actually present in the source message, so a default link handler does not accidentally replace an ordinary {link} placeholder. Per-call handlers take precedence over provider defaults.
By default, rich output that contains multiple chunks is wrapped in a React fragment, making it safe to render directly. Pass wrapRichTextChunksInFragment={false} only when you deliberately need the raw chunk array.
React-only provider modules#
MessagevisorProvider also accepts modules for final React-node transformations. These run after SDK modules and before rich chunks are wrapped. They are useful for concerns that genuinely belong in the view layer, such as annotating translated output.
<MessagevisorProvider instance={messagevisor} modules={[ { name: "mark-translations", transform: ({ translation, messageKey, source, locale }) => ( <span data-message-key={messageKey} data-source={source} lang={locale}> {translation} </span> ), }, ]}> <AppContent /></MessagevisorProvider>Keep business logic, interpolation, and datafile-independent formatting in SDK modules. Keep React provider modules small and presentation-specific. Provider modules receive translation, locale, source ("translation" or "formatMessage"), and messageKey when a datafile key was translated.
Server rendering and framework applications#
The React hooks can render from the provider’s current SDK snapshot. For server rendering, create an instance per request or use a request-scoped provider and make sure the first client render uses the same initial locale, datafile, context, and defaults. Do not share a mutable SDK instance between server requests.
For frameworks that have server and client component boundaries, keep MessagevisorProvider and hooks in the client side of the boundary when they need browser fetching or interactive locale changes. You can still load a datafile on the server and pass its serializable content to that client provider.
For framework-specific setup, see Next.js, Remix, TanStack Start, and Astro.
Diagnostics, events, and missing translations#
React hooks use the same diagnostics and fallback behavior as the JavaScript SDK. Configure onDiagnostic while creating the instance to send missing translations, invalid datafiles, and module issues to your logging or observability system.
const messagevisor = createMessagevisor({ datafile, onDiagnostic(diagnostic) { if (diagnostic.code === "missing_translation") { reportMissingTranslation(diagnostic); } },});For SDK events such as change, locale_set, or datafile_set, use useSdk() and subscribe in an effect with cleanup. In most UI code, prefer the reactive hooks instead of manually subscribing.
const sdk = useSdk();useEffect(() => sdk.on("datafile_set", (event) => { analytics.track("messagevisor_datafile_set", { locale: event.locale });}), [sdk]);Read JavaScript SDK diagnostics and events for the complete runtime contract.
React Intl migrations#
If an existing application already uses react-intl, Messagevisor provides a separate compatibility package with useIntl, FormattedMessage, FormattedNumber, injectIntl, and related familiar APIs. It is designed for incremental migration, not as the preferred API for new code.
See React Intl compatibility for installation, defaultMessage fallback behavior, formatted components, and migration guidance.
Practical guidance#
- Put one provider near the application root. Add a nested provider only when you intentionally need a separate SDK instance.
- Keep the instance stable; update its state with SDK setters instead of recreating it for a locale or context change.
- Use
useTranslation()and formatter hooks in render output. UseuseMessagevisor()in callbacks and effects. - Load target-specific datafiles, and cache them according to your application’s existing network strategy.
- Use provider defaults for common rich-text tags and per-call handlers for route- or message-specific links.
- Keep request context and locale switching explicit, especially in server-rendered applications.

