bhasha-js
v0.4.0
Published
React i18n built for South Asian languages — Hindi, Bengali, Urdu, Tamil, Telugu, Marathi, Punjabi, Gujarati, Kannada, Malayalam, Nepali, Sinhala — with auto RTL, lakh/crore numbers, native digits, region-aware currency, and CLDR plurals.
Downloads
237
Maintainers
Keywords
Readme
bhasha-js · भाषाJS
React i18n built for South Asian languages — Hindi, Bengali, Urdu, Tamil, Telugu, Marathi, Punjabi, Gujarati, Kannada, Malayalam, Nepali, Sinhala. RTL switching for Urdu, native script fonts, lakh/crore numbers (₹12,34,567), region-aware currency, and CLDR plurals — all out of the box.
npm install bhasha-jsimport { I18nProvider, useTranslation, LanguageSwitcher } from "bhasha-js";
const translations = {
en: { "hero.title": "Welcome", "items": "{count} items" },
hi: { "hero.title": "स्वागत है", "items": "{count} आइटम" },
ur: { "hero.title": "خوش آمدید" },
};
export default function App() {
return (
<I18nProvider preloadedTranslations={translations} defaultLang="en">
<LanguageSwitcher />
<Page />
</I18nProvider>
);
}
function Page() {
const { t, formatCurrency } = useTranslation();
return (
<>
<h1>{t("hero.title")}</h1>
<p>{t("items", { count: 5 })}</p>
<p>{formatCurrency(1234567)} {/* → ₹12,34,567.00 */}</p>
</>
);
}That's the entire setup. Switch to Urdu and the layout flips RTL automatically. Switch to Bengali and the right Bengali font loads. Pass count: 0 and Hindi correctly treats it as singular while English treats it as plural.
Why bhasha-js
Generic i18n libraries (i18next, react-intl, next-intl) are technically language-agnostic — they support Hindi the same way they support German. That isn't enough for South Asian apps. bhasha-js solves the things they ignore:
| | i18next / react-intl | bhasha-js |
|---|---|---|
| Auto RTL/LTR switch | manual setup | automatic — sets dir, lang, CSS vars |
| Script-aware fonts | your problem | auto-loads Noto Sans Devanagari, Bengali, Tamil, Nastaliq Urdu, etc. |
| Lakh/crore grouping | generic Intl | 12,34,567 not 1,234,567 |
| Native digits | manual | useNativeDigits → १२,३४,५६७ / ১২,৩৪,৫৬৭ / ௧௨,௩௪,௫௬௭ |
| Plural rules | configure each language | built-in CLDR for 14 languages (Hindi 0=singular!) |
| Fallback chains | language → English | culturally-aware (Bengali → Hindi → English; Tamil → English direct) |
| Currency | your problem | region-aware (₹ for India, ৳ for Bangladesh, Rs for Pakistan, රු for Sri Lanka) |
| Bundle size | 10–30 kB | small, tree-shakeable |
| Languages supported | depends on your config | 14 South Asian languages pre-configured |
3 ways to use bhasha-js
Pick whichever fits your project. You can switch modes later — the API is the same.
Mode 1 · Bundled translations (no backend, 30 seconds)
The simplest path. Your translations live in your repo as JSON. Best for static sites, marketing pages, or apps with a small fixed string set.
import { I18nProvider } from "bhasha-js";
import en from "./locales/en.json";
import hi from "./locales/hi.json";
import ur from "./locales/ur.json";
<I18nProvider
preloadedTranslations={{ en, hi, ur }}
defaultLang="en"
>
<App />
</I18nProvider>No API call. No backend. You ship the JSON in your bundle.
Locale JSON can be flat or nested. Nested objects are flattened with dots by the CLI and import tools, so { "checkout": { "title": "Checkout" } } becomes checkout.title.
Mode 2 · Hosted dashboard (recommended for production)
Sign up at bhashajs.com, create a project, get an API key, and translations are managed in a UI (with AI translation, team review, glossary, history).
<I18nProvider
projectKey="bjs_your_project_api_key_here"
defaultLang="en"
>
<App />
</I18nProvider>The SDK fetches translations from https://api.bhashajs.com/api using the x-api-key header. Bundles are cached in memory and, by default, persisted to localStorage for stale-while-revalidate first paint. Set persistCache={false} to disable storage.
Mode 3 · Self-hosted
The whole stack is open source. docker compose up and you own it. Point the SDK at your URL:
<I18nProvider
projectKey="bjs_..."
apiUrl="https://i18n.your-company.com/api"
defaultLang="en"
>
<App />
</I18nProvider>See the main repo for self-hosting docs.
What's new in 0.3
- Type-safe keys +
bhashaCLI —npx bhasha pullgenerates a declaration that makest()/<Trans>autocomplete your keys and turns a typo into a compile error. Zero config; falls back tostringwhen not generated. - Next.js App Router — the main entry is marked
"use client", and pure formatters are importable frombhasha-js/serverinside Server Components. - Framework-agnostic engine —
bhasha-js/vanillaexposesBhashaStore(subscribe/emit) for Vue, Svelte, vanilla JS, and React Native. See the frameworks guide. - Correctness fixes — interpolation no longer crashes/corrupts on regex-special keys or
$-values; lakh/crore now correct for PKR & LKR;formatDatereturns""(not "Invalid Date") on bad input; SSR-safe; race-guarded language/register switches.
What's new in 0.2
- Register-aware translations — same key, three formality variants (
default/formal/casual). Casual leans into code-mixing; formal sticks to native vocabulary. - Code-mixed locales as first-class —
hi-Latn(Hinglish),ne-Latn(Roman Nepali),ur-Latn(Roman Urdu),bn-Latn(Banglish),pa-Latn(Roman Punjabi). Real locales with their own translation memory, plural rules, and fallback chains. - Segment-aware register switching — pass
userSegment+segmentRulesand the SDK picks the right register at render time. Samet("hero.cta")returns "Add करो" for Gen-Z and "जोड़ें" for enterprise users. - Compliance lock — keys marked
regulated(auto-set by regulator-pinned vertical packs) refuse to serve AI drafts. Only human-approved values reach end users. - Voice-ready outputs — every (lang, register) cell can produce IPA + SSML.
formatPhonetic("hero.cta")andformatSSML("hero.cta")give you everything a TTS engine (AWS Polly, Google Cloud TTS, ElevenLabs) needs.
Translation Memory flywheel
Every approved AI translation becomes a (source, lang, register, target) row in your project's translation memory. Today this improves AI translations via in-prompt examples. Once a (lang, register) cell has ~5,000 verified pairs, that corpus is large enough to fine-tune a small open model (LoRA on a 7B base) for register-aware South-Asian translation that doesn't depend on Gemini at runtime. The dashboard surfaces TM coverage as a visible counter — your moat compounds with every approval.
Features
Auto RTL switching for Urdu
const { setLang } = useTranslation();
setLang("ur");
// → <html lang="ur" dir="rtl"> — entire layout flips
setLang("hi");
// → <html lang="hi" dir="ltr"> — flips backSmart font loading
Each script needs the right font to render conjuncts correctly. bhasha-js loads them automatically:
| Script | Font | |---|---| | Devanagari (Hindi, Marathi, Nepali) | Noto Sans Devanagari | | Bengali | Noto Sans Bengali | | Urdu | Noto Nastaliq Urdu | | Tamil | Noto Sans Tamil | | Telugu | Noto Sans Telugu | | Gurmukhi (Punjabi) | Noto Sans Gurmukhi | | Gujarati | Noto Sans Gujarati | | Kannada | Noto Sans Kannada | | Malayalam | Noto Sans Malayalam | | Sinhala | Noto Sans Sinhala |
The active font is exposed as the CSS variable --bhasha-font so you can use it in your own styles:
body { font-family: var(--bhasha-font); }Lakh/crore numbers
import { formatNumber } from "bhasha-js";
formatNumber(1234567, "hi"); // → "12,34,567"
formatNumber(1234567, "hi", undefined, { useNativeDigits: true }); // → "१२,३४,५६७"
formatNumber(1500000, "hi", undefined, { compact: true }); // → "15 लाख"
formatNumber(20000000, "hi", undefined, { compact: true }); // → "2 करोड़"Note:
endefaults to theen-INlocale — it uses lakh grouping and ₹. Passregion="US"(or the corresponding option toformatNumber) if you want Western formatting for English.
Region-aware currency
import { formatCurrency } from "bhasha-js";
formatCurrency(1500, "hi"); // → "₹1,500.00" (Hindi defaults to India)
formatCurrency(1500, "bn"); // → "৳1,500.00" (Bengali defaults to Bangladesh)
formatCurrency(1500, "bn", "IN"); // → "₹1,500.00" (Bengali in India)
formatCurrency(1500, "ta", "LK"); // → "Rs. 1,500.00" (Tamil in Sri Lanka)
formatCurrency(1500, "ur"); // → "Rs 1,500.00" (Urdu defaults to Pakistan)Currencies are auto-detected from language + region. Override with the currency option ({ currency: "USD" }).
CLDR pluralization (Hindi 0 = singular!)
This is one of the most-broken things in other libraries. In Hindi (and most Indo-Aryan languages), 0 is treated as singular, not plural.
// In your translations file:
{
"items_count_one": "{count} आइटम",
"items_count_other": "{count} आइटम"
}
// In your component:
t("items_count", { count: 0 }) // Hindi → "0 आइटम" (uses _one)
t("items_count", { count: 0 }) // English → "0 items" (uses _other)The SDK reads count, picks the correct CLDR category for the active language, and looks up the suffixed key (_one or _other).
Culturally-aware fallback chains
If a translation is missing in the active language, the SDK walks a chain that makes linguistic sense:
| Language | Fallback | |---|---| | Hindi (hi) | hi → en | | Bengali (bn) | bn → hi → en | | Urdu (ur) | ur → hi → en | | Marathi (mr) | mr → hi → en | | Gujarati (gu) | gu → hi → en | | Punjabi-Gurmukhi (pa) | pa → hi → en | | Punjabi-Shahmukhi (pa-PK) | pa-PK → ur → en | | Nepali (ne) | ne → hi → en | | Tamil (ta) | ta → en (no Hindi — different language family) | | Telugu (te) | te → en (Dravidian) | | Kannada (kn) | kn → en (Dravidian) | | Malayalam (ml) | ml → en (Dravidian) | | Sinhala (si) | si → en |
Dravidian languages skip Hindi because they're from a completely different language family — falling back to a related language doesn't apply.
String interpolation
t("greeting", { name: "Rohan", count: 5 })
// "Hello {name}, you have {count} items" → "Hello Rohan, you have 5 items"Date formatting (DD/MM/YYYY)
import { formatDate } from "bhasha-js";
formatDate(new Date(), "hi", undefined, { preset: "short" }); // "25/4/26"
formatDate(new Date(), "hi", undefined, { preset: "medium" }); // "25 अप्रैल 2026"
formatDate(new Date(), "hi", undefined, { preset: "long" }); // "25 अप्रैल 2026"
formatDate(new Date(), "hi", undefined, { preset: "full" }); // "शनिवार, 25 अप्रैल 2026"
formatDate(new Date(), "hi", undefined, { preset: "long", useNativeDigits: true });
// → "२५ अप्रैल २०२६"CLI
npx bhasha init
npx bhasha pull
npx bhasha push --lang hi --register formal
npx bhasha scan --strictbhasha push reads flat or nested JSON files from locales/ and sends them to the dashboard. Use a scoped API key with read-only OFF and no origin allowlist. Regulated keys are reported as skipped because they can only be edited by a human in the dashboard.
API reference
<I18nProvider>
| Prop | Type | Default | Description |
|---|---|---|---|
| projectKey | string | — | Project API key (Mode 2/3). Recommended for production. |
| projectId + apiToken | string + string | — | JWT auth (advanced). |
| preloadedTranslations | Record<string, Record<string, string>> | — | Mode 1 — bundle translations into your app. |
| persistCache | boolean | true | Persist fetched text bundles to localStorage and refresh them in the background. |
| defaultLang | string | "en" | Initial language code. |
| apiUrl | string | "https://api.bhashajs.com/api" | Override for self-hosting (e.g. "https://my.host/api"). |
| region | string | — | Override default region (e.g. "IN", "BD", "PK", "LK", "NP"). Affects currency + locale. |
| onLanguageChange | (lang: string) => void | — | Callback fired whenever the language changes. |
useTranslation()
const {
t, // (key, params?) => string
currentLang, // "hi"
setLang, // (lang: string) => void
supportedLangs, // string[]
isLoading, // boolean
error, // string | null ← surfaces auth/network errors
formatNumber, // (value, options?) => string
formatCurrency, // (value, options?) => string
formatDate, // (date, options?) => string
} = useTranslation();useLangInfo()
const { code, name, englishName, dir, font, script, defaultRegion, intlLocale, defaultCurrency } = useLangInfo();<LanguageSwitcher>
| Prop | Type | Default |
|---|---|---|
| style | "dropdown" \| "floating" | "dropdown" |
| position | "top-right" \| "top-left" \| "bottom-right" \| "bottom-left" | "top-right" |
| className | string | — |
<Trans>
<Trans id="hero.title" />
<Trans id="greeting" params={{ name: "Rohan" }} as="h1" />Standalone utilities (no React required)
import {
formatNumber, formatCurrency, formatDate,
getPluralCategory, getFallbackChain, getLangInfo,
LANGUAGES, REGION_OVERRIDES,
} from "bhasha-js";Supported languages (14 total · ~1.8 billion speakers)
| Code | Language | Native | Script | Direction | Default region | Default currency |
|---|---|---|---|---|---|---|
| en | English | English | Latin | LTR | IN | INR |
| hi | Hindi | हिन्दी | Devanagari | LTR | IN | INR |
| bn | Bengali | বাংলা | Bengali | LTR | BD | BDT |
| ur | Urdu | اردو | Nastaliq | RTL | PK | PKR |
| ta | Tamil | தமிழ் | Tamil | LTR | IN | INR |
| te | Telugu | తెలుగు | Telugu | LTR | IN | INR |
| mr | Marathi | मराठी | Devanagari | LTR | IN | INR |
| ne | Nepali | नेपाली | Devanagari | LTR | NP | NPR |
| pa | Punjabi (Gurmukhi) | ਪੰਜਾਬੀ | Gurmukhi | LTR | IN | INR |
| pa-PK | Punjabi (Shahmukhi) | پنجابی | Arabic-derived | RTL | PK | PKR |
| gu | Gujarati | ગુજરાતી | Gujarati | LTR | IN | INR |
| kn | Kannada | ಕನ್ನಡ | Kannada | LTR | IN | INR |
| ml | Malayalam | മലയാളം | Malayalam | LTR | IN | INR |
| si | Sinhala | සිංහල | Sinhala | LTR | LK | LKR |
FAQ
Does this work with Next.js (App Router)?
Yes. The main bhasha-js entry is already marked "use client", so you can use the provider and hooks in any Client Component. app/layout.tsx is a Server Component by default, so put the provider in a small client wrapper:
// app/providers.tsx
"use client";
import { I18nProvider } from "bhasha-js";
export function Providers({ children }: { children: React.ReactNode }) {
return <I18nProvider projectKey="bjs_..." defaultLang="en">{children}</I18nProvider>;
}// app/layout.tsx (stays a Server Component)
import { Providers } from "./providers";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body><Providers>{children}</Providers></body>
</html>
);
}Need the formatters inside a Server Component (no provider, no hooks)? Import them from the server-safe entry — it carries no "use client" directive:
import { formatCurrency } from "bhasha-js/server"; // ✅ works in a Server Component
formatCurrency(1234567, "hi"); // "₹12,34,567.00"Does this work without React?
Yes — the pure utilities (formatNumber, formatCurrency, formatDate, getPluralCategory, getLangInfo, …) are framework-agnostic. Import them from bhasha-js/server in any non-React / server context. The components and hooks need React 17+.
How do I integrate AI translation?
Use Mode 2 (hosted dashboard) — there's a "Translate with AI" button per language. Or use the /api/translations/:projectId/ai-translate endpoint if self-hosted.
Bundle size?
Tree-shakeable ESM. If you only use formatCurrency and formatNumber, that's what ships. Components and hooks are imported separately.
Does it work with React Native / Vue / Svelte?
Yes — import the framework-agnostic engine from bhasha-js/vanilla. BhashaStore exposes t(), the formatters, and subscribe() with no React and no hard document dependency (DOM updates are opt-in and SSR-safe). Wrap it in a Vue composable, a Svelte store, or a React Native context — see the Vue, Svelte & vanilla guide.
Are the translation keys type-safe?
Run npx bhasha pull and they are — t() and <Trans> autocomplete your keys and a typo becomes a compile error. With no generated file, keys stay typed as string, so it's fully opt-in.
Links
- 🌐 Website + dashboard: https://bhashajs.com
- 📚 Docs: https://bhashajs.com/docs
- 🔧 Source: https://github.com/santoshpant23/bhashajs
- 🐛 Issues: https://github.com/santoshpant23/bhashajs/issues
License
MIT © Santosh Pant
