npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mongez/react-localization

v3.4.6

Published

React adapter for @mongez/localization. Adds a JSX-aware placeholder converter so `trans(...)` can interpolate React elements, plus `transX` — a JSX-bound translate function that works regardless of the globally configured converter.

Readme

@mongez/react-localization

The React adapter for @mongez/localization — drop React elements straight into translated sentences without losing structure, reactivity, or RTL ordering.

npm license bundle size downloads


Why @mongez/react-localization?

react-i18next ships a full provider, hooks, suspense pipeline, and its own message format — about 14 KB minzipped of machinery you opt into wholesale. react-intl (FormatJS) leans on ICU MessageFormat and a <FormattedMessage> component pattern, dragging in a parser plus locale data per language. react-intl-universal is lighter but still owns the registry, locale switching, and message format.

@mongez/react-localization is the React-only sliver on top of @mongez/localization — the framework-agnostic core that already owns registries, locale switching, count rules, placeholders, and events. The adapter exports exactly two things: a JSX-aware placeholder converter so trans(:tos) can interpolate <a>Terms</a>, and transX — a trans variant pre-bound to that converter for per-call JSX without flipping the global setting. One source file, no provider, no context, no hooks of its own.

import { extend, setLocalizationConfigurations, trans } from "@mongez/localization";
import { jsxConverter } from "@mongez/react-localization";

setLocalizationConfigurations({ converter: jsxConverter });
extend("en", { agreeToTerms: "You agree to our :tos." });

<p>{trans("agreeToTerms", { tos: <a href="/terms">Terms</a> })}</p>
// → <p>You agree to our <a href="/terms">Terms</a>.</p>

Features

| Feature | Description | |---|---| | jsxConverter | A placeholder-to-React converter. Wire once via setLocalizationConfigurations({ converter: jsxConverter }) and every trans(...) accepts React elements as placeholder values. | | transX | A trans variant hard-bound to jsxConverter. Use per call when the global converter is left as plainConverter and only specific call sites need JSX. | | Pattern-agnostic | Honors whatever placeholder pattern the core uses — :colon, {{doubleCurly}}, or a custom RegExp. The pattern is passed in by the caller. | | Missing-key fallback | If a :token is in the template but not in the placeholders bag, the bare key renders as text. No crash, no [object Object]. | | Deterministic React keys | Each fragment gets a numeric key derived from the split index — no "each child should have a unique key" warnings. | | Tiny surface | Two exports, one source file. No provider, no context, no hooks. Pairs with whatever state library you already use. | | TypeScript-first | Signatures returned by jsxConverter and transX are string | React.ReactNode[] — the array branch fires when at least one placeholder resolves. | | Pluralization-ready | Count routing lives in @mongez/localization. transX flows the count placeholder through unchanged. |


Installation

npm install @mongez/react-localization @mongez/localization
yarn add @mongez/react-localization @mongez/localization
pnpm add @mongez/react-localization @mongez/localization

Peer dependencies: @mongez/localization >= 3.0.0 and react >= 18.


Quick start

Pick one of the two paths below and stick to it for the project.

Path A — wire jsxConverter globally

Best when most of your translations interpolate React elements (links, icons, formatted spans). Every trans(...) call understands JSX from that point on.

import {
  extend,
  setLocalizationConfigurations,
  trans,
} from "@mongez/localization";
import { jsxConverter } from "@mongez/react-localization";

setLocalizationConfigurations({
  defaultLocaleCode: "en",
  fallback: "en",
  converter: jsxConverter,
});

extend("en", {
  minimumOrderPurchase:
    "Minimum purchase amount for this order is :amount USD",
});

export function PriceNotice() {
  return (
    <p>
      {trans("minimumOrderPurchase", {
        amount: <strong style={{ color: "red" }}>12</strong>,
      })}
    </p>
  );
}

The return type of trans(...) widens from string to string | React.ReactNode[] — keep that in mind for call sites that pass the result to string-typed APIs like document.title.

Path B — keep plainConverter, use transX per call

Best when most translations are plain strings and only a handful of call sites need JSX. trans(...) stays typed as string everywhere; transX(...) widens only where you actually need React elements.

import { extend, trans } from "@mongez/localization";
import { transX } from "@mongez/react-localization";

extend("en", {
  greeting: "Hello :name",
  agreeToTerms: "You agree to our :tos.",
});

trans("greeting", { name: "Alice" });
// → "Hello Alice" (string)

transX("agreeToTerms", { tos: <a href="/terms">Terms</a> });
// → React fragment array, rendered as a real <a> in the DOM.

The two paths are mutually exclusive — pick one. Mixing jsxConverter globally and transX at call sites produces identical output, but the extra import is noise.


jsxConverter

function jsxConverter(
  translation: string,
  placeholders: any,
  placeholderPattern: RegExp,
): string | React.ReactNode[];

Splits translation on placeholderPattern, substitutes each captured token from placeholders, and reassembles the parts as an array of React.Fragment children.

| Behaviour | Result | |---|---| | placeholders is null, undefined, a primitive, or {} | Returns translation unchanged as a string. No split, no array. | | placeholders has at least one entry | Returns Array<React.ReactNode> of fragments. Render via {out} inside any JSX expression slot. | | Token in template, missing in placeholders bag | Falls back to the bare key (e.g. Create new :item"Create new item"). The leading : is gone because the splitter captured only the name. | | Placeholder value is a React element | Renders as the element — the surrounding text stays as plain string. | | Placeholder value is null / undefined | Same as missing — bare key renders. | | Pattern is :colon / {{doubleCurly}} / custom | Honored as supplied — the pattern is sourced from @mongez/localization's placeholderPattern config. |

The function is pure — same inputs, same output. No React hooks, no subscriptions, no side effects.


transX

function transX(keyword: string, placeholders?: any): string | React.ReactNode[];

Equivalent to:

import { getTranslationLocaleCode, transFrom } from "@mongez/localization";
import { jsxConverter } from "@mongez/react-localization";

transFrom(getTranslationLocaleCode(), keyword, placeholders, jsxConverter);

transX hard-codes jsxConverter as the converter argument, ignoring whatever is set via setLocalizationConfigurations({ converter }). That's the entire point of the function.

Because it delegates to transFrom, it inherits every feature of the core package:

  • Locale resolution via getTranslationLocaleCode() (translation locale, falling back to current locale).
  • Fallback locale via setFallbackLocaleCode().
  • Missing-keyword fallback (returns the keyword itself if nothing resolves).
  • Count-based pluralization when placeholders.count is set.
  • Object-shaped keyword support (transX({ en: "Hello", ar: "مرحبا" })).

No locale-change subscription. transX is a plain function call that reads getTranslationLocaleCode() once and returns. Components that already rendered will keep their old translation when setCurrentLocaleCode("ar") fires — drive the re-render through state, an atom, or a useSyncExternalStore over localizationEvents. See Recipes.


Hooks and components — what's NOT in the box

This package intentionally ships no useLocale() hook, useTranslate() hook, <Translate> component, or context provider. All three are recipes built on top of what's exported — one-liners that compose with whatever state library you already use.

| Concern | Lives where | |---|---| | Translation registry, locale switching, count rules, events | @mongez/localization | | JSX placeholder support | @mongez/react-localization (this package) | | Locale-driven re-renders | Your state library — @mongez/react-atom, Zustand, Redux, or a custom hook over localizationEvents |

If you build a useLocale() on top, prefer useSyncExternalStore over the older useState + useEffect(localizationEvents.onChange(...)) pattern — see Recipes. The latter has a known stale-read window under React 18 concurrent rendering.


Recipes

Render a translation with embedded React component

Reach for this when a localized sentence needs a link, icon, or styled span inline — typical for terms-of-service notices, prompts with branded names, or any UI where the translator owns the surrounding wording.

import { extend } from "@mongez/localization";
import { transX } from "@mongez/react-localization";

extend("en", {
  agreeToTerms:
    "By clicking Continue, you agree to our :tos and :privacy.",
});

extend("ar", {
  agreeToTerms:
    "بالنقر على متابعة، فإنك توافق على :tos و :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 where the visual order differs from the source order.

Switch locale on user action

Lift the locale into local state and mirror it into the core in an effect. This is the simplest re-render path — no extra dependency, every consumer re-mounts on switch.

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={locale}` 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 the subtree. For surgical re-renders without the remount, see the next recipe.

Build a useLocale() hook over the event bus

Reach for this when you want components to subscribe to locale changes individually — no parent state, no remount, no extra dependency. Use useSyncExternalStore to stay tear-free under React 18 concurrent rendering.

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(),
  );
}
import { transX } from "@mongez/react-localization";

function Title() {
  useLocale(); // subscribes; re-renders on flip
  return <h1>{transX("title")}</h1>;
}

Don't reach for useState + useEffect(localizationEvents.onChange(...)). It looks equivalent but introduces a stale-read window between the synchronous render snapshot and the effect-time subscription — siblings can disagree on the locale under concurrent rendering. useSyncExternalStore collapses the snapshot and subscribe into one tear-free operation.

Render plural forms with count

Pluralization lives in @mongez/localization's count rules. transX flows the count placeholder through, picks the right _zero / _one / _two / _few / _many variant per locale, and runs the result through jsxConverter.

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 منتجاً",
});

function ProductsBadge({ count }: { count: number }) {
  return <span>{transX("products", { count })}</span>;
}

// <ProductsBadge count={0} />  → "No products"
// <ProductsBadge count={1} />  → "1 product"
// <ProductsBadge count={42} /> → "42 products"

When count is present in the placeholders bag, the converter runs even for templates with no :count token (like _one"1 product"), so the return is always a fragment array. Render into an element or wrap in <>{...}</> — don't pass it to a string-typed API.

Write a tiny <Translate> component

Some teams prefer a JSX-in / JSX-out shape over {transX(...)} calls. Trivial to write on top of the two exports.

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 composes more cleanly with transObject, groupedTranslations, and conditionals — that's why the package doesn't ship the component itself — but the wrapper is a one-liner if your codebase prefers it.


Related packages

| Package | Use when you need | |---|---| | @mongez/localization | The framework-agnostic core. Registry, locale switching, count rules, fallback, events — extend, trans, transFrom, setCurrentLocaleCode, localizationEvents, groupedTranslations, transObject all live here. | | @mongez/react-atom | Reactive state primitive with React hooks. Drop the locale into an atom and useValue() subscribes a component to changes — cleaner than the key= remount trick. | | @mongez/events | Event bus. Used internally by @mongez/localization to broadcast localizationEvents — pairs well with a useSyncExternalStore-based useLocale() hook. |


Further reading


License

MIT — see LICENSE.