Skip to content

Locales & i18n

raqam uses Intl.NumberFormat for all formatting and parsing. You never hardcode separators — they’re extracted dynamically from the browser’s internationalization engine.

Pass any BCP 47 locale tag to the locale prop:

// German: 1.234,56
<NumberField.Root locale="de-DE" formatOptions={{ style: "currency", currency: "EUR" }} />
// French: 1 234,56 €
<NumberField.Root locale="fr-FR" formatOptions={{ style: "currency", currency: "EUR" }} />
// Japanese: ¥1,234
<NumberField.Root locale="ja-JP" formatOptions={{ style: "currency", currency: "JPY" }} />

Separators, currency symbols, minus signs, and digit systems are all resolved automatically — no configuration needed.

Five locale plugins add support for writing systems that use non-ASCII digits:

PluginScriptDigitsBCP 47 tags
raqam/locales/faPersian (Extended Arabic-Indic)۰۱۲۳۴۵۶۷۸۹fa, fa-IR, fa-AF
raqam/locales/arArabic-Indic٠١٢٣٤٥٦٧٨٩ar, ar-EG, ar-SA, …
raqam/locales/hiDevanagari०१२३४५६७८९hi, hi-IN, mr, ne
raqam/locales/bnBengali০১২৩৪৫৬৭৮৯bn, bn-BD, bn-IN
raqam/locales/thThai๐๑๒๓๔๕๖๗๘๙th, th-TH

Import the locale plugin once, anywhere in your app (it runs as a side effect):

// app/layout.tsx or src/main.tsx
import "raqam/locales/fa"; // adds Persian digit support
import "raqam/locales/ar"; // adds Arabic-Indic digit support

Then use the locale normally:

<NumberField.Root locale="fa-IR" />

Without the plugin, Persian ۱۲۳ typed by the user won’t be normalized. With the plugin, ۱۲۳ is accepted and internally treated as 123.

import "raqam/locales"; // imports fa, ar, hi, bn, th

Each plugin is only ~100 bytes gzipped.

If you want to build locale pickers or feature flags, the bundle entrypoint also re-exports the supported locale lists:

import {
FA_LOCALE_CODES,
AR_LOCALE_CODES,
BN_LOCALE_CODES,
HI_LOCALE_CODES,
TH_LOCALE_CODES,
} from "raqam/locales";

These arrays are useful when you want to map a runtime locale to a matching plugin without hardcoding the supported tags yourself.

South Asian locales (hi-IN, bn-BD, mr-IN) use a different grouping pattern than Western locales (lakhs and crores):

en-US: 10,000,000 (millions)
hi-IN: 1,00,00,000 (crores)

raqam handles this automatically via Intl.NumberFormat.

Arabic, Persian, Hebrew, and Urdu are right-to-left. raqam automatically detects RTL locales and applies the correct rendering. See the RTL guide for details.

If you need a digit system not covered by the built-in plugins, register it:

import { registerLocale } from "raqam/core";
// Mongolian digits: ᠐᠑᠒᠓᠔᠕᠖᠗᠘᠙ (U+1810–U+1819)
registerLocale({
digitBlocks: [[0x1810, 0x1819]],
});
const LOCALES = [
{ locale: "en-US", label: "English (US)", formatOptions: { style: "currency", currency: "USD" } },
{ locale: "de-DE", label: "German", formatOptions: { style: "currency", currency: "EUR" } },
{ locale: "fr-FR", label: "French", formatOptions: { style: "currency", currency: "EUR" } },
{ locale: "fa-IR", label: "Persian (Iran)", formatOptions: { style: "currency", currency: "IRR" } },
{ locale: "ar-EG", label: "Arabic (Egypt)", formatOptions: { style: "currency", currency: "EGP" } },
{ locale: "hi-IN", label: "Hindi (India)", formatOptions: { style: "currency", currency: "INR" } },
{ locale: "bn-BD", label: "Bengali (Bangladesh)", formatOptions: { style: "currency", currency: "BDT" } },
{ locale: "th-TH", label: "Thai", formatOptions: { style: "currency", currency: "THB" } },
{ locale: "ja-JP", label: "Japanese", formatOptions: { style: "currency", currency: "JPY" } },
{ locale: "zh-CN", label: "Chinese (Simplified)", formatOptions: { style: "currency", currency: "CNY" } },
];