Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

yankouskia/localize-react

Open more actions menu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

76 Commits
76 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

localize-react

localize-react

React i18n, without the weight.

Tiny, type-safe React i18n built on Context and hooks.
~1.1 kB brotli · 0 runtime deps · dual ESM + CJS · React 19 ready.

Docs · Quickstart · API · Recipes · Migration v1 → v2

npm downloads CI coverage bundle types license


import { LocalizationProvider, useLocalize } from 'localize-react';

const translations = {
  en: { hello: 'Hi {{name}}!' },
  es: { hello: '¡Hola {{name}}!' },
  ja: { hello: '{{name}}さん、こんにちは!' },
};

function Greeting() {
  const { translate } = useLocalize();
  return <h1>{translate('hello', { name: 'Alex' })}</h1>;
}

export default function App() {
  return (
    <LocalizationProvider locale="en" translations={translations}>
      <Greeting />
    </LocalizationProvider>
  );
}

That's the whole API. Three exports, no plugins, no extraction toolchain. Ship it.


✨ Why?

Most React i18n libraries are 30–80 kB and bring opinions about plural rules, ICU MessageFormat, async loading, and TMS workflows. localize-react is the smallest thing that works.

It does exactly what a frontend most often needs:

  • A nested translations tree, keyed by locale.
  • Dot-path lookups ('cart.summary').
  • {{name}}-style interpolation.
  • A graceful fallback when keys are missing.

For everything else (plurals, currency, dates) — reach for the platform: Intl.PluralRules, Intl.NumberFormat, Intl.DateTimeFormat. Free, fast, already in the browser. See the Intl formatters recipe.

📦 Specs

Property Value
Bundle (brotli) 1.13 kB ESM · 1.22 kB CJS
Runtime dependencies 0
Source Strict TypeScript 6
Module formats ESM + CJS with proper exports/types conditions
Tree-shaking sideEffects: false
Peer range React >= 16.8 < 20 (tested in CI through React 19)
Node >= 20.19 (CI on 20, 22, 24 × Linux/macOS/Windows)
Test coverage 100 % statements · 100 % functions · 98 % branches
Type-checked exports Validated by publint + @arethetypeswrong/cli in CI

🚀 Install

npm install localize-react
# or: pnpm add localize-react · yarn add localize-react · bun add localize-react

⚡️ Quickstart

1. Define translations

export const translations = {
  en: {
    greeting: { hello: 'Hi {{name}}!' },
    cart: { summary: '{{count}} items, {{total}} total' },
  },
  es: {
    greeting: { hello: '¡Hola {{name}}!' },
    cart: { summary: '{{count}} artículos, {{total}} total' },
  },
} as const;

2. Mount the provider

import { LocalizationProvider } from 'localize-react';
import { translations } from './i18n/translations';

export function App() {
  return (
    <LocalizationProvider locale="en" translations={translations}>
      <Shell />
    </LocalizationProvider>
  );
}

3. Translate, two ways

import { Message, useLocalize } from 'localize-react';

// Hook
function Cart() {
  const { translate } = useLocalize();
  return <p>{translate('cart.summary', { count: 3, total: '$42.00' })}</p>;
}

// Component
function CartHeader() {
  return (
    <h1>
      <Message descriptor="greeting.hello" values={{ name: 'Alex' }} />
    </h1>
  );
}

That's the whole story. Full docs at yankouskia.github.io/localize-react.

🛡 Fully type-safe with createLocalization()

Want the compiler to catch missing keys, typo'd descriptors, and forgotten {{tokens}}? Wrap your translations once and use the typed bindings:

import { createLocalization } from 'localize-react';

const translations = {
  en: { greeting: 'Hi {{name}}!', cart: { checkout: 'Checkout' } },
  es: { greeting: '¡Hola {{name}}!', cart: { checkout: 'Pagar' } },
} as const;

export const { LocalizationProvider, useLocalize, Message } =
  createLocalization(translations);

// inside a component — `translate` autocompletes descriptors and requires {{name}}:
function Greeting() {
  const { translate } = useLocalize();
  return <h1>{translate('greeting', { name: 'Alex' })}</h1>; // ✅
  // translate('greting');                  ❌ Type '"greting"' is not assignable
  // translate('greeting');                 ❌ Property 'name' is missing
}

A pure compile-time wrapper — no runtime cost, no second cache. Full guide: Type-safe API.

🎨 Rich content with <RichMessage />

Need a link or component inside a translated sentence? <RichMessage /> returns a ReactNode and lets values include React nodes, so a single translation can host inline rich content without splitting the sentence across JSX:

import { RichMessage } from 'localize-react';

// en: 'By signing up, you agree to our {{link}}.'
// de: 'Mit der Anmeldung stimmen Sie unseren {{link}} zu.'
function Footer() {
  return (
    <p>
      <RichMessage
        descriptor="signup.terms"
        values={{ link: <a href="/terms">Terms of Service</a> }}
      />
    </p>
  );
}

The link lands wherever the translator put {{link}} — German word order, Japanese particles, RTL flow, all handled by the translation. Strings and numbers are still accepted (and coerced) so you can mix them freely. Available as a typed export from createLocalization() too. Full recipe: Rich content.

🧠 Concept in one screen

Operation API
Mount translations <LocalizationProvider locale translations>
Translate (hook) useLocalize().translate(descriptor, values?, default?)
Translate (component) <Message descriptor values? defaultMessage? />
Translate with JSX <RichMessage descriptor values? defaultMessage? />
Switch locale at runtime Re-render with a new locale prop
Missing key Renders defaultMessage ?? descriptor (never throws)
Nested lookup translate('a.b.c') walks the tree
Interpolation {{token}} — literal replacement, safe with regex chars
Locale normalization En-USen_usen

📚 Recipes

Real-world patterns, fully documented on the site:

🥊 How it compares

localize-react react-i18next react-intl lingui
Bundle (brotli) ~1.1 kB ~17 kB ~38 kB ~9 kB
Runtime deps 0 several several one macro
Pluralization (CLDR) Use Intl ✅ (ICU) ✅ (ICU)
Number / date format Use Intl Optional
ICU MessageFormat
Lazy locale loading DIY
Auto extraction CLI CLI
TypeScript-first
Learning curve Tiny Medium Medium Medium

Use localize-react when you want a hook + a tag. Reach for the others when CLDR plurals, ICU MessageFormat, or a TMS workflow matter — they're all great at what they do.

🛡 Production-ready

  • Types ship inside the package — no @types/localize-react to chase.
  • Provenance attestation on every published version (npm OIDC trusted publishing).
  • CodeQL runs on every PR; CI matrix exercises Node 20/22/24 × Linux/macOS/Windows.
  • Size budget enforced — < 2 kB ESM, < 2.5 kB CJS, checked on every PR with size-limit.
  • No dynamic require, no eval, no regex from user input — interpolation is literal replaceAll.

📖 v1 → v2

The runtime API is unchanged. v2 modernizes the toolchain (strict TS 6, dual ESM+CJS, React 19 peer, GitHub Actions + Changesets). One soft TypeScript regression in exactOptionalPropertyTypes mode — see the migration guide.

🤝 Contributing

PRs welcome. See CONTRIBUTING.md for the setup + release flow. Security reports: please open a private security advisory rather than a public issue.

If you'd like to support the project, sponsoring helps a lot.

📄 License

MIT © Aliaksandr Yankouski

About

✈️ Lightweight React Localization Library 🇺🇸

Topics

Resources

Contributing

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages

Morty Proxy This is a proxified and sanitized view of the page, visit original site.