Raqam
Guides

Locales & i18n

Using raqam with non-Latin digit systems — Persian, Arabic, Bengali, Hindi, Thai.

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

Basic locale switching

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.

Non-Latin digit systems

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

Installing a plugin

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.

All plugins at once

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

Each plugin is tiny — well under 200 B (e.g. raqam/locales/fa is 189 B, min + brotli), enforced in CI via .size-limit.json.

Locale metadata exports

If you want to build locale pickers or feature flags, the raqam/locales entrypoint also re-exports the supported locale lists (the main raqam entry does not):

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.

Lakh/crore grouping

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. Native digit input for Marathi (mr) and Nepali (ne) ships in the raqam/locales/hi plugin.

RTL locales

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.

Custom digit blocks

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]],
});

Locale plugins are tree-shakeable. Only the plugins you import are included in your bundle.

Supported locale examples

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" } },
];

On this page