Recipes
Real-world flows for @mongez/react-localization. Every recipe assumes you’ve also installed @mongez/localization (the core).
JSX inside a translated sentence
The single most common use case: dropping a link / icon / styled span into a localized sentence without breaking translation structure.
import { extend } from "@mongez/localization";import { transX } from "@mongez/react-localization";
extend("en", { agreeToTerms: "By clicking Continue, you agree to our :tos and :privacy.",});
function CheckoutFooter() { return ( <p> {transX("agreeToTerms", { tos: <a href="/terms">Terms of Service</a>, privacy: <a href="/privacy">Privacy Policy</a>, })} </p> );}The result is a fragment array; React renders each fragment in document order, preserving the original sentence structure of the translation — including in RTL locales.
Pluralization (delegated to @mongez/localization)
Count-based variants are handled by the core package. transX flows the count placeholder through.
import { extend } from "@mongez/localization";import { transX } from "@mongez/react-localization";
extend("en", { products_zero: "No products", products_one: "1 product", products_many: ":count products",});
extend("ar", { products_zero: "لا توجد منتجات", products_one: "منتج واحد", products_two: "منتجان", products_few: ":count منتجات", products_many: ":count منتجاً",});
transX("products", { count: 0 }); // "No products" (as fragment array)transX("products", { count: 1 }); // "1 product"transX("products", { count: 42 }); // "42 products"When count is in the placeholders bag, the converter runs even for templates with no :count token (like _one → "1 product"), so you always get an array back. Wrap in <>{...}</> or render into an element — don’t pass it to string-typed APIs.
Locale switch via component state
Simplest re-render path. Lift the locale into local state, mirror to the core in an effect.
import { useEffect, useState } from "react";import { setCurrentLocaleCode } from "@mongez/localization";import { transX } from "@mongez/react-localization";
function App() { const [locale, setLocale] = useState<"en" | "ar">("en");
useEffect(() => { setCurrentLocaleCode(locale); }, [locale]);
return ( <> <button onClick={() => setLocale(l => (l === "en" ? "ar" : "en"))}> {transX("toggleLocale")} </button> {/* `key` forces children to remount on switch */} <Page key={locale} /> </> );}The key={locale} trick is the cheapest way to re-render every consumer when the locale flips — at the cost of unmounting and remounting the subtree. For surgical re-renders, use an atom or a custom hook.
Locale switch via @mongez/react-atom
Atom-driven re-renders without the remount.
import { atom } from "@mongez/react-atom";import { setCurrentLocaleCode } from "@mongez/localization";import { transX } from "@mongez/react-localization";
const localeAtom = atom({ key: "ui.locale", default: "en" });
// Mirror the atom into the core package's state.localeAtom.onChange((next) => setCurrentLocaleCode(next));
function LocaleSwitch() { const [locale, setLocale] = localeAtom.useState(); return ( <button onClick={() => setLocale(locale === "en" ? "ar" : "en")}> {transX("toggleLocale")} </button> );}
function Greeting() { localeAtom.useValue(); // subscribes; drives re-render on flip return <h1>{transX("greeting")}</h1>;}localeAtom.useValue() does the work — the return value is unused, but the subscription it creates re-renders the component when the locale changes. Cleaner than key=-based remounts.
Custom useLocale() over the event bus
If you don’t want a new dependency, you can wire your own hook over localizationEvents. Use useSyncExternalStore — the useState + useEffect(localizationEvents.onChange) shape has a tearing-style stale-read window between the render snapshot and the effect-time subscription.
import { useSyncExternalStore } from "react";import { getCurrentLocaleCode, localizationEvents,} from "@mongez/localization";
export function useLocale(): string { return useSyncExternalStore( (notify) => { const sub = localizationEvents.onChange("localeCode", notify); return () => sub.unsubscribe(); }, () => getCurrentLocaleCode(), () => getCurrentLocaleCode(), );}Now any component can:
function Title() { useLocale(); // subscribes; re-renders on flip return <h1>{transX("title")}</h1>;}This is the recipe @mongez/react-localization itself would ship if it grew a hooks surface. If you build it, prefer useSyncExternalStore over the older useState + useEffect shape.
A tiny <Translate> component
Some teams prefer a JSX-in / JSX-out shape. Trivial to write:
import { transX } from "@mongez/react-localization";
type TranslateProps = { k: string; placeholders?: any;};
export function Translate({ k, placeholders }: TranslateProps) { return <>{transX(k, placeholders)}</>;}
// Usage:<Translate k="welcome" placeholders={{ name: <strong>Ada</strong> }} />The function-call shape ({transX(k, p)}) composes more cleanly with transObject and conditionals — that’s why the package doesn’t ship the component by default — but the wrapper is a one-liner if your codebase prefers it.
Mixing converters per call site
Suppose your app is 90% plain text and 10% JSX. Wire plainConverter (the default) globally, then upgrade specific call sites to JSX:
import { trans, plainTrans } from "@mongez/localization";import { transX } from "@mongez/react-localization";
trans("greeting"); // plain stringplainTrans("greeting", { name: "Ada" }); // forced plain stringtransX("greeting", { name: <em>Ada</em> }); // JSX fragment arrayThe TypeScript signatures stay narrow at most call sites (you get string), and only the JSX call sites widen to string | React.ReactNode[].