{"id":19716583,"url":"https://github.com/mastermunj/to-words","last_synced_at":"2026-04-03T16:01:37.896Z","repository":{"id":25515128,"uuid":"104211287","full_name":"mastermunj/to-words","owner":"mastermunj","description":"Converts Numbers (including decimal points) into words. It also converts the numbers into words for currency.","archived":false,"fork":false,"pushed_at":"2026-03-26T01:02:44.000Z","size":11519,"stargazers_count":129,"open_issues_count":3,"forks_count":85,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-03-26T15:14:54.022Z","etag":null,"topics":["conversion","convert-numbers","currency","decimal-points","i18n","number-to-words","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mastermunj.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"mastermunj"}},"created_at":"2017-09-20T12:15:09.000Z","updated_at":"2026-03-25T12:22:11.000Z","dependencies_parsed_at":"2023-11-07T06:24:18.834Z","dependency_job_id":"128a9d4b-3214-4b46-af4f-4d636ae67553","html_url":"https://github.com/mastermunj/to-words","commit_stats":{"total_commits":1231,"total_committers":17,"mean_commits":72.41176470588235,"dds":"0.15515840779853773","last_synced_commit":"b3e5cc6cf77d04c0f23954cb0e742d7adf851300"},"previous_names":[],"tags_count":53,"template":false,"template_full_name":null,"purl":"pkg:github/mastermunj/to-words","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mastermunj%2Fto-words","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mastermunj%2Fto-words/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mastermunj%2Fto-words/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mastermunj%2Fto-words/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mastermunj","download_url":"https://codeload.github.com/mastermunj/to-words/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mastermunj%2Fto-words/sbom","scorecard":{"id":536651,"data":{"date":"2025-08-11","repo":{"name":"github.com/mastermunj/to-words","commit":"da5303f452addb8b45bcf98738c1e11b7ec2e3c8"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":5.3,"checks":[{"name":"Maintained","score":10,"reason":"30 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":-1,"reason":"Found no human activity in the last 15 changesets","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/ci.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":3,"reason":"dependency not pinned by hash detected -- score normalized to 3","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:22: update your workflow using https://app.stepsecurity.io/secureworkflow/mastermunj/to-words/ci.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:24: update your workflow using https://app.stepsecurity.io/secureworkflow/mastermunj/to-words/ci.yml/main?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   1 out of   1 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 30 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":7,"reason":"3 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-52f5-9888-hmc6"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-20T07:10:08.779Z","repository_id":25515128,"created_at":"2025-08-20T07:10:08.779Z","updated_at":"2025-08-20T07:10:08.779Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31362681,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-03T15:19:21.178Z","status":"ssl_error","status_checked_at":"2026-04-03T15:19:20.670Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["conversion","convert-numbers","currency","decimal-points","i18n","number-to-words","typescript"],"created_at":"2024-11-11T22:42:37.129Z","updated_at":"2026-04-03T16:01:37.872Z","avatar_url":"https://github.com/mastermunj.png","language":"TypeScript","funding_links":["https://github.com/sponsors/mastermunj"],"categories":[],"sub_categories":[],"readme":"# to-words\n\n[![npm version](https://img.shields.io/npm/v/to-words.svg)](https://www.npmjs.com/package/to-words)\n[![npm downloads](https://img.shields.io/npm/dm/to-words.svg)](https://www.npmjs.com/package/to-words)\n[![build](https://img.shields.io/github/actions/workflow/status/mastermunj/to-words/ci.yml?branch=main\u0026label=build)](https://github.com/mastermunj/to-words/actions)\n[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/mastermunj/to-words/badge)](https://securityscorecards.dev/viewer/?uri=github.com/mastermunj/to-words)\n[![coverage](https://codecov.io/gh/mastermunj/to-words/branch/main/graph/badge.svg)](https://codecov.io/gh/mastermunj/to-words)\n[![minzipped size](https://img.shields.io/bundlephobia/minzip/to-words?label=minzipped)](https://bundlephobia.com/package/to-words)\n[![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue)](https://www.typescriptlang.org/)\n[![license](https://img.shields.io/npm/l/to-words)](https://github.com/mastermunj/to-words/blob/main/LICENSE)\n[![node](https://img.shields.io/node/v/to-words)](https://www.npmjs.com/package/to-words)\n[![Bun](https://github.com/mastermunj/to-words/actions/workflows/bun.yml/badge.svg?branch=main)](https://github.com/mastermunj/to-words/actions/workflows/bun.yml)\n[![Deno](https://github.com/mastermunj/to-words/actions/workflows/deno.yml/badge.svg?branch=main)](https://github.com/mastermunj/to-words/actions/workflows/deno.yml)\n[![CF Workers](https://img.shields.io/badge/CF_Workers-compatible-F38020)](https://workers.cloudflare.com)\n\nConvert numbers and currency amounts into words across 124 locales with production-ready BigInt, ordinal, and TypeScript support.\n\n## 🎮 Live Demo\n\n**Try it now:** **[Open Interactive Demo](https://mastermunj.github.io/to-words/)**\n\nTest locale behavior, currency conversion, ordinals, and large number inputs in the browser.\n\n## 🏆 Why to-words\n\n- **124 locale implementations** with region-specific numbering and currency conventions\n- **Built for real financial flows**: amount in words, decimals, currency units, and negatives\n- **Large number safe** with `BigInt` and string input support\n- **Run anywhere**: Node.js, Deno, Bun, Cloudflare Workers, and all modern browsers\n- **Functional API** — `toWords(42, { localeCode: 'en-US' })` for one-liners, or class-based for high-volume workloads\n- **Strong developer ergonomics**: TypeScript types, ESM/CJS/UMD, and per-locale imports\n- **Performance focused** for high-volume conversion workloads\n\n## 📑 Table of Contents\n\n- [Use Cases](#-use-cases)\n- [Features](#-features)\n- [Quick Start](#-quick-start)\n- [Installation](#-installation)\n- [Usage](#-usage)\n- [Migration Guide](#-migration-guide)\n- [CLI](#%EF%B8%8F-cli)\n- [Framework Integration](#%EF%B8%8F-framework-integration)\n- [Numbering Systems](#-numbering-systems)\n- [API Reference](#%EF%B8%8F-api-reference)\n  - [Constructor Options](#constructor-options)\n  - [Class Methods](#class-methods)\n  - [Functional Exports](#functional-exports)\n  - [Converter Options](#converter-options)\n- [Bundle Sizes](#-bundle-sizes)\n- [Performance](#-performance)\n- [Browser Compatibility](#-browser-compatibility)\n- [Supported Locales](#%EF%B8%8F-supported-locales)\n- [Error Handling](#%EF%B8%8F-error-handling)\n- [Contributing](#-contributing)\n- [FAQ](#-faq)\n- [Changelog](#-changelog)\n- [License](#-license)\n\n## 💼 Use Cases\n\n- **Invoicing \u0026 Billing** — Display amounts in words on invoices, receipts, and financial documents\n- **Check Printing** — Banks and financial institutions require amounts in words for check validation\n- **E-commerce** — Show order totals in words for clarity and accessibility\n- **Legal Documents** — Contracts and agreements often require written-out amounts\n- **Educational Apps** — Teach number pronunciation and spelling in different languages\n- **Accessibility** — Screen readers benefit from properly formatted number-to-text conversion\n- **Localization** — Support global users with region-specific number formatting\n\n## ✨ Features\n\n- **124 Locales** — The most comprehensive locale coverage available\n- **BigInt Support** — Handle numbers up to 10^63 (Vigintillion) and beyond\n- **Multiple Numbering Systems** — Short scale, Long scale, Indian, and East Asian\n- **Currency Formatting** — Locale-specific currency with fractional units\n- **Ordinal Numbers** — First, Second, Third, etc.\n- **Gender-Aware** — Grammatical gender for locales that require it (Spanish, Portuguese, Arabic, Hebrew, Slavic, and more)\n- **Formal Numerals** — Formal/financial Chinese characters (大写/大寫) via `formal: true`\n- **Tree-Shakeable** — Import only the locales you need\n- **TypeScript Native** — Full type definitions included\n- **Multiple Formats** — ESM, CommonJS, and UMD browser bundles\n- **Zero Dependencies** — Lightweight and self-contained\n- **High Performance** — Up to 4.7M ops/sec (small integers; see benchmark section for full breakdown)\n- **Functional API** — `toWords()`, `toOrdinal()`, `toCurrency()` named exports for ergonomic one-liners\n- **Auto Locale Detection** — `detectLocale()` reads `navigator.language` or `Intl` in any runtime\n- **CLI** — `npx to-words 12345 --locale en-US` for shell scripts and quick conversions\n- **Wide Compatibility** — All modern browsers, Node.js 20+, Deno, Bun, and Cloudflare Workers\n\n## 🚀 Quick Start\n\nThere are three ways to use `to-words`. Pick the one that fits your use case:\n\n**1. Class-based** — best for high-volume workloads where you reuse one instance:\n\n```js\nimport { ToWords } from 'to-words';\n\nconst tw = new ToWords({ localeCode: 'en-US' });\ntw.convert(12345); // \"Twelve Thousand Three Hundred Forty Five\"\ntw.convert(100, { currency: true }); // \"One Hundred Dollars Only\"\ntw.toOrdinal(3); // \"Third\"\n```\n\n**2. Functional (full bundle)** — one-liners with a `localeCode` option, all 124 locales available:\n\n```js\nimport { toWords, toOrdinal, toCurrency } from 'to-words';\n\ntoWords(12345, { localeCode: 'en-US' }); // \"Twelve Thousand Three Hundred Forty Five\"\ntoCurrency(100, { localeCode: 'en-US' }); // \"One Hundred Dollars Only\"\ntoOrdinal(3, { localeCode: 'en-US' }); // \"Third\"\n```\n\n**3. Functional (per-locale import)** — locale baked in, fully tree-shakeable, smallest bundle (~3.5 KB gzip):\n\n```js\nimport { toWords, toOrdinal, toCurrency } from 'to-words/en-US';\n\ntoWords(12345); // \"Twelve Thousand Three Hundred Forty Five\"\ntoCurrency(100); // \"One Hundred Dollars Only\"\ntoOrdinal(3); // \"Third\"\n```\n\n\u003e **Default locale:** When no `localeCode` is provided, the runtime locale is **auto-detected** via `detectLocale()` and falls back to `en-IN` if it cannot be matched.\n\n## 📦 Installation\n\n\u003e **Runtime requirement:** Node.js `\u003e= 20`.\n\u003e\n\u003e Moving from another library? See [`MIGRATION.md`](MIGRATION.md).\n\n### npm / yarn / pnpm\n\n```bash\nnpm install to-words\n# or\nyarn add to-words\n# or\npnpm add to-words\n```\n\n### CDN (Browser)\n\n```html\n\u003c!-- Full bundle with all locales --\u003e\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/to-words/dist/umd/to-words.min.js\"\u003e\u003c/script\u003e\n\n\u003c!-- Single locale bundle (smaller, recommended) --\u003e\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/to-words/dist/umd/en-US.min.js\"\u003e\u003c/script\u003e\n```\n\n## 📖 Usage\n\n### Importing\n\n```js\n// Class-based — ESM\nimport { ToWords } from 'to-words';\n\n// Class-based — CommonJS\nconst { ToWords } = require('to-words');\n\n// Functional helpers (full bundle) — ESM\nimport { toWords, toOrdinal, toCurrency, detectLocale } from 'to-words';\n\n// Functional helpers (per-locale, tree-shakeable) — ESM\nimport { toWords, toOrdinal, toCurrency } from 'to-words/en-US';\n\n// Per-locale class — ESM\nimport { ToWords } from 'to-words/en-US';\n```\n\n### Basic Conversion\n\n**Class-based:**\n\n```js\nconst tw = new ToWords({ localeCode: 'en-US' });\n\ntw.convert(123); // \"One Hundred Twenty Three\"\ntw.convert(123.45); // \"One Hundred Twenty Three Point Four Five\"\ntw.convert(123.045); // \"One Hundred Twenty Three Point Zero Four Five\"\n```\n\n**Functional (full bundle):**\n\n```js\nimport { toWords } from 'to-words';\n\ntoWords(123, { localeCode: 'en-US' }); // \"One Hundred Twenty Three\"\ntoWords(123.45, { localeCode: 'en-US' }); // \"One Hundred Twenty Three Point Four Five\"\n```\n\n**Functional (per-locale):**\n\n```js\nimport { toWords } from 'to-words/en-US';\n\ntoWords(123); // \"One Hundred Twenty Three\"\ntoWords(123.45); // \"One Hundred Twenty Three Point Four Five\"\n```\n\n\u003e **Note:** When the fractional part starts with zero, digits after the decimal point are converted individually.\n\n### BigInt \u0026 Large Numbers\n\nHandle numbers beyond JavaScript's safe integer limit:\n\n```js\nconst toWords = new ToWords({ localeCode: 'en-US' });\n\n// Using BigInt\ntoWords.convert(1000000000000000000n);\n// \"One Quintillion\"\n\ntoWords.convert(1000000000000000000000000000000000000000000000000000000000000000n);\n// \"One Vigintillion\"\n\n// Using string for precision\ntoWords.convert('9007199254740993');\n// \"Nine Quadrillion Seven Trillion One Hundred Ninety Nine Billion\n//  Two Hundred Fifty Four Million Seven Hundred Forty Thousand Nine Hundred Ninety Three\"\n```\n\n### Currency Conversion\n\n**Class-based:**\n\n```js\nconst tw = new ToWords({ localeCode: 'en-IN' });\n\ntw.convert(452, { currency: true });\n// \"Four Hundred Fifty Two Rupees Only\"\n\ntw.convert(452.36, { currency: true });\n// \"Four Hundred Fifty Two Rupees And Thirty Six Paise Only\"\n\n// Without \"Only\" suffix\ntw.convert(452, { currency: true, doNotAddOnly: true });\n// \"Four Hundred Fifty Two Rupees\"\n\n// Ignore decimal/fractional part\ntw.convert(452.36, { currency: true, ignoreDecimal: true });\n// \"Four Hundred Fifty Two Rupees Only\"\n\n// Ignore zero currency\ntw.convert(0.36, { currency: true, ignoreZeroCurrency: true });\n// \"Thirty Six Paise Only\"\n\n// Show fractional unit even when zero (string input preserves .00)\ntw.convert('452.00', { currency: true, includeZeroFractional: true });\n// \"Four Hundred Fifty Two Rupees And Zero Paise Only\"\n```\n\n**Functional — `toCurrency()` shorthand:**\n\n```js\nimport { toCurrency } from 'to-words';\n\ntoCurrency(452, { localeCode: 'en-IN' });\n// \"Four Hundred Fifty Two Rupees Only\"\n\ntoCurrency(452.36, { localeCode: 'en-IN' });\n// \"Four Hundred Fifty Two Rupees And Thirty Six Paise Only\"\n\ntoCurrency(452, { localeCode: 'en-IN', doNotAddOnly: true });\n// \"Four Hundred Fifty Two Rupees\"\n```\n\n**Functional per-locale** (locale baked in, no `localeCode` needed):\n\n```js\nimport { toCurrency } from 'to-words/en-IN';\n\ntoCurrency(452); // \"Four Hundred Fifty Two Rupees Only\"\ntoCurrency(452.36); // \"Four Hundred Fifty Two Rupees And Thirty Six Paise Only\"\n```\n\n### Custom Currency\n\nOverride currency settings while keeping the locale's language:\n\n```js\nconst toWords = new ToWords({\n  localeCode: 'en-US',\n  converterOptions: {\n    currency: true,\n    currencyOptions: {\n      name: 'Euro',\n      plural: 'Euros',\n      symbol: '€',\n      fractionalUnit: {\n        name: 'Cent',\n        plural: 'Cents',\n        symbol: '',\n      },\n    },\n  },\n});\n\ntoWords.convert(100.5);\n// \"One Hundred Euros And Fifty Cents Only\"\n```\n\n### Ordinal Numbers\n\n**Class-based:**\n\n```js\nconst tw = new ToWords({ localeCode: 'en-US' });\n\ntw.toOrdinal(1); // \"First\"\ntw.toOrdinal(21); // \"Twenty First\"\ntw.toOrdinal(100); // \"One Hundredth\"\n```\n\n**Functional — `toOrdinal()` (full bundle):**\n\n```js\nimport { toOrdinal } from 'to-words';\n\ntoOrdinal(1, { localeCode: 'en-US' }); // \"First\"\ntoOrdinal(21, { localeCode: 'en-US' }); // \"Twenty First\"\ntoOrdinal(100, { localeCode: 'en-US' }); // \"One Hundredth\"\n```\n\n**Functional per-locale:**\n\n```js\nimport { toOrdinal } from 'to-words/en-US';\n\ntoOrdinal(1); // \"First\"\ntoOrdinal(21); // \"Twenty First\"\n```\n\n\u003e **Note:** Full ordinal word mappings are available for English, Spanish, French, Portuguese, Turkish, and Dutch locales. Other locales use locale-specific suffix or prefix strategies.\n\n### Gender-Aware Conversion\n\nMany languages use grammatical gender for number words. Pass `gender` via converter options:\n\n```js\n// Spanish: masculine (default) vs feminine\nconst tw = new ToWords({ localeCode: 'es-ES' });\ntw.convert(1);                              // \"Uno\"\ntw.convert(1, { gender: 'feminine' });      // \"Una\"\ntw.convert(21, { gender: 'feminine' });     // \"Veintiuna\"\ntw.convert(200, { gender: 'feminine' });    // \"Doscientas\"\n\n// Portuguese\nconst pt = new ToWords({ localeCode: 'pt-BR' });\npt.convert(2, { gender: 'feminine' });      // \"Duas\"\n\n// Arabic\nconst ar = new ToWords({ localeCode: 'ar-AE' });\nar.convert(3, { gender: 'feminine' });      // \"ثلاث\"\n```\n\nGender can also be set via constructor options and overridden per call:\n\n```js\nconst tw = new ToWords({\n  localeCode: 'es-ES',\n  converterOptions: { gender: 'feminine' },\n});\ntw.convert(1);                             // \"Una\" (constructor default)\ntw.convert(1, { gender: 'masculine' });    // \"Uno\" (per-call override)\n```\n\n\u003e **Supported locales:** Spanish (7), Portuguese (4), Arabic (4), Hebrew (2), Russian, Ukrainian, Polish, Czech, Croatian, Slovak, Serbian, Belarusian, Bulgarian, Catalan, Romanian, Latvian, Lithuanian, and Slovenian.\n\n### UseAnd Option\n\nInsert the locale's \"And\" word before the last two digits:\n\n```js\nconst tw = new ToWords({ localeCode: 'en-US' });\ntw.convert(123);                            // \"One Hundred Twenty Three\"\ntw.convert(123, { useAnd: true });          // \"One Hundred And Twenty Three\"\ntw.convert(1023, { useAnd: true });         // \"One Thousand And Twenty Three\"\n\n// Works with currency too\ntw.convert(123, { currency: true, useAnd: true });\n// \"One Hundred And Twenty Three Dollars Only\"\n```\n\n\u003e **Note:** `useAnd` is a no-op for locales that already use a split word (e.g., Portuguese uses \"E\" by default) and for locales where the connector token is empty (e.g., ja-JP, zh-CN, zh-TW, yue-HK).\n\n### Formal Chinese Numerals\n\nUse formal/financial characters (大写/大寫) for Chinese locales:\n\n```js\n// Simplified Chinese\nconst cn = new ToWords({ localeCode: 'zh-CN' });\ncn.convert(123);                            // \"百 二十 三\"\ncn.convert(123, { formal: true });          // \"佰 贰拾 叁\"\ncn.convert(100, { currency: true, formal: true });\n// \"佰 圆 整\"\n\n// Traditional Chinese\nconst tw = new ToWords({ localeCode: 'zh-TW' });\ntw.convert(123, { formal: true });          // \"佰 貳拾 參\"\ntw.toOrdinal(5, { formal: true });          // \"第伍\"\n```\n\n### Tree-Shakeable Imports\n\nEvery locale entry point (`to-words/\u003clocale\u003e`) exports four things:\n\n| Export       | Type     | Description                                   |\n| ------------ | -------- | --------------------------------------------- |\n| `ToWords`    | class    | Full class API pre-configured for this locale |\n| `toWords`    | function | Convert number → words                        |\n| `toOrdinal`  | function | Convert number → ordinal words                |\n| `toCurrency` | function | Convert number → currency words               |\n\n```js\n// Class-based (locale pre-configured, no localeCode needed)\nimport { ToWords } from 'to-words/en-US';\nconst tw = new ToWords();\ntw.convert(12345); // \"Twelve Thousand Three Hundred Forty Five\"\n\n// Functional helpers (locale baked in — smallest possible import)\nimport { toWords, toOrdinal, toCurrency } from 'to-words/en-US';\ntoWords(12345); // \"Twelve Thousand Three Hundred Forty Five\"\ntoOrdinal(3); // \"Third\"\ntoCurrency(100); // \"One Hundred Dollars Only\"\n```\n\n\u003e Individual imports are ~3.5 KB gzip vs ~60 KB for the full bundle.\n\n### Browser Usage (UMD)\n\n```html\n\u003c!-- Single locale (recommended, ~3.5 KB gzip) --\u003e\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/to-words/dist/umd/en-US.min.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n  // ToWords is pre-configured for en-US\n  const toWords = new ToWords();\n  console.log(toWords.convert(12345));\n  // \"Twelve Thousand Three Hundred Forty Five\"\n\u003c/script\u003e\n\n\u003c!-- Full bundle with all locales (~60 KB gzip) --\u003e\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/to-words/dist/umd/to-words.min.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n  // Specify locale when using full bundle\n  const toWords = new ToWords({ localeCode: 'fr-FR' });\n  console.log(toWords.convert(12345));\n  // \"Douze Mille Trois Cent Quarante-Cinq\"\n\u003c/script\u003e\n```\n\n### Functional API\n\nTwo flavours depending on whether you need tree-shaking:\n\n**Full bundle** — `localeCode` is optional; omit it to use the auto-detected runtime locale:\n\n```js\nimport { toWords, toOrdinal, toCurrency } from 'to-words';\n\n// Explicit locale\ntoWords(12345, { localeCode: 'en-US' });\n// \"Twelve Thousand Three Hundred Forty Five\"\n\ntoOrdinal(21, { localeCode: 'en-US' });\n// \"Twenty First\"\n\ntoCurrency(1234.56, { localeCode: 'en-IN' });\n// \"One Thousand Two Hundred Thirty Four Rupees And Fifty Six Paise Only\"\n\n// No localeCode — uses detectLocale() automatically\ntoWords(12345);\n// result depends on the runtime locale (e.g. 'en-US' → \"Twelve Thousand Three Hundred Forty Five\")\n\ntoCurrency(1234.56, { doNotAddOnly: true });\n// currency in the runtime locale, without \"Only\" suffix\n```\n\n**Per-locale import** — locale baked in, no `localeCode` argument at all, fully tree-shakeable (~3.5 KB gzip):\n\n```js\nimport { toWords, toOrdinal, toCurrency } from 'to-words/en-US';\n\ntoWords(12345); // \"Twelve Thousand Three Hundred Forty Five\"\ntoOrdinal(21); // \"Twenty First\"\ntoCurrency(1234.56); // \"One Thousand Two Hundred Thirty Four Dollars And Fifty Six Cents Only\"\n```\n\n\u003e **Performance note:** The functional API caches one `ToWords` instance per locale. Repeated calls for the same locale reuse the cached instance.\n\n### Auto-Detect Locale\n\n`detectLocale()` is automatically used by `toWords()`, `toOrdinal()`, and `toCurrency()` when no `localeCode` is provided — so in most cases you don't need to call it directly. It is useful when you want to read the runtime locale for other purposes, display it to the user, or pass it explicitly to a class instance.\n\n```js\nimport { detectLocale, toWords, ToWords } from 'to-words';\n\n// Used implicitly — no localeCode needed\ntoWords(1000);\n// On a browser with navigator.language = 'fr-FR': \"Mille\"\n// In a Node.js process with fr-FR locale:         \"Mille\"\n// Fallback when nothing can be detected:           \"One Thousand\" (en-IN)\n\n// Used explicitly — read once, reuse across many calls\nconst locale = detectLocale('en-US'); // custom fallback if detection misses\nconst tw = new ToWords({ localeCode: locale });\ntw.convert(1000);\n```\n\n\u003e Reads `navigator.language` in browsers, `Intl.DateTimeFormat().resolvedOptions().locale` in Node.js / Deno / Bun / CF Workers. Falls back to `'en-IN'` (or your custom fallback) if the detected value cannot be matched to a supported locale.\n\n## 🔄 Migration Guide\n\nMigrating from `number-to-words`, `written-number`, `num-words`, or `n2words`?\n\n- See [`MIGRATION.md`](MIGRATION.md) for side-by-side API mapping and migration recipes.\n- Includes package comparison, behavior notes, and a regression checklist.\n\n## 🖥️ CLI\n\nRun one-off conversions from the command line without installing:\n\n```bash\nnpx to-words 12345\n# Twelve Thousand Three Hundred Forty Five\n\nnpx to-words 12345 --locale en-US\n# Twelve Thousand Three Hundred Forty Five\n\nnpx to-words 1234.56 --locale en-US --currency\n# One Thousand Two Hundred Thirty Four Dollars And Fifty Six Cents Only\n\nnpx to-words 3 --locale en-US --ordinal\n# Third\n\nnpx to-words --detect-locale\n# en-US  (or whatever your system locale is)\n```\n\nOnce installed globally (`npm i -g to-words`), the `to-words` command is available directly.\n\n## ⚛️ Framework Integration\n\n### React\n\n```tsx\n// Change the locale import to match your users' region (e.g. 'to-words/en-GB' for UK)\nimport { ToWords } from 'to-words/en-US';\n\nconst toWords = new ToWords();\n\nfunction PriceInWords({ amount }: { amount: number }) {\n  const words = toWords.convert(amount, { currency: true });\n  return \u003cspan className=\"price-words\"\u003e{words}\u003c/span\u003e;\n}\n\n// Usage: \u003cPriceInWords amount={1234.56} /\u003e\n// Renders: \"One Thousand Two Hundred Thirty Four Dollars And Fifty Six Cents Only\"\n```\n\n### Vue 3\n\n```vue\n\u003cscript setup lang=\"ts\"\u003e\nimport { computed } from 'vue';\nimport { ToWords } from 'to-words/en-US';\n\nconst props = defineProps\u003c{ amount: number }\u003e();\nconst toWords = new ToWords();\n\nconst words = computed(() =\u003e toWords.convert(props.amount, { currency: true }));\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cspan class=\"price-words\"\u003e{{ words }}\u003c/span\u003e\n\u003c/template\u003e\n```\n\n### Angular\n\n```typescript\nimport { Pipe, PipeTransform } from '@angular/core';\nimport { ToWords } from 'to-words/en-US';\n\n@Pipe({ name: 'toWords', standalone: true })\nexport class ToWordsPipe implements PipeTransform {\n  private toWords = new ToWords();\n\n  transform(value: number, currency = false): string {\n    return this.toWords.convert(value, { currency });\n  }\n}\n\n// Usage: {{ 1234.56 | toWords:true }}\n```\n\n### Svelte\n\n```svelte\n\u003cscript lang=\"ts\"\u003e\n  import { ToWords } from 'to-words/en-US';\n\n  export let amount: number;\n\n  const toWords = new ToWords();\n  $: words = toWords.convert(amount, { currency: true });\n\u003c/script\u003e\n\n\u003cspan class=\"price-words\"\u003e{words}\u003c/span\u003e\n```\n\n### Next.js\n\n```tsx\n// Server Component (App Router) — locale from request headers or user profile\nimport { toWords } from 'to-words';\n\ntype Props = { amount: number; locale: string };\n\nexport default function AmountInWords({ amount, locale }: Props) {\n  return \u003cp\u003e{toWords(amount, { localeCode: locale, currency: true })}\u003c/p\u003e;\n}\n```\n\n```tsx\n// Client Component — dynamic locale switching\n'use client';\n\nimport { useState } from 'react';\nimport { toCurrency, detectLocale } from 'to-words';\n\nexport function CurrencyDisplay({ amount }: { amount: number }) {\n  const [locale, setLocale] = useState(detectLocale('en-US'));\n  return (\n    \u003cdiv\u003e\n      \u003cselect value={locale} onChange={(e) =\u003e setLocale(e.target.value)}\u003e\n        \u003coption value=\"en-US\"\u003eEnglish (US)\u003c/option\u003e\n        \u003coption value=\"fr-FR\"\u003eFrench\u003c/option\u003e\n        \u003coption value=\"hi-IN\"\u003eHindi\u003c/option\u003e\n        \u003coption value=\"ar-AE\"\u003eArabic\u003c/option\u003e\n      \u003c/select\u003e\n      \u003cp\u003e{toCurrency(amount, { localeCode: locale })}\u003c/p\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\n### Node.js / Express\n\n```ts\nimport express from 'express';\nimport { toWords, toCurrency, detectLocale } from 'to-words';\n\nconst app = express();\n\napp.get('/convert', (req, res) =\u003e {\n  const number = String(req.query.number ?? '');\n  const locale = String(req.query.locale ?? detectLocale());\n  const currency = req.query.currency === 'true';\n\n  try {\n    const result = currency ? toCurrency(number, { localeCode: locale }) : toWords(number, { localeCode: locale });\n    res.json({ result, locale });\n  } catch (e) {\n    res.status(400).json({ error: (e as Error).message });\n  }\n});\n```\n\n## 🌍 Numbering Systems\n\nDifferent regions use different numbering systems. This library supports all major systems:\n\n### Short Scale (Western)\n\nUsed in: USA, UK, Canada, Australia, and most English-speaking countries.\n\n| Number | Name         |\n| ------ | ------------ |\n| 10^6   | Million      |\n| 10^9   | Billion      |\n| 10^12  | Trillion     |\n| 10^15  | Quadrillion  |\n| ...    | ...          |\n| 10^63  | Vigintillion |\n\n```js\nconst toWords = new ToWords({ localeCode: 'en-US' });\ntoWords.convert(1000000000000000000n);\n// \"One Quintillion\"\n```\n\n### Long Scale (European)\n\nUsed in: Germany, France, and many European countries.\n\n| Number | German    | French   |\n| ------ | --------- | -------- |\n| 10^6   | Million   | Million  |\n| 10^9   | Milliarde | Milliard |\n| 10^12  | Billion   | Billion  |\n| 10^15  | Billiarde | Billiard |\n\n```js\nconst toWords = new ToWords({ localeCode: 'de-DE' });\ntoWords.convert(1000000000);\n// \"Eins Milliarde\"\n```\n\n### Indian System\n\nUsed in: India, Bangladesh, Nepal, Pakistan.\n\n| Number | Name   |\n| ------ | ------ |\n| 10^5   | Lakh   |\n| 10^7   | Crore  |\n| 10^9   | Arab   |\n| 10^11  | Kharab |\n| 10^13  | Neel   |\n| 10^15  | Padma  |\n| 10^17  | Shankh |\n\n```js\nconst toWords = new ToWords({ localeCode: 'en-IN' });\ntoWords.convert(100000000000000000n);\n// \"One Shankh\"\n\nconst toWordsHindi = new ToWords({ localeCode: 'hi-IN' });\ntoWordsHindi.convert(100000000000000000n);\n// \"एक शंख\"\n```\n\n### East Asian System\n\nUsed in: Japan, China, Korea.\n\n| Number | Character     |\n| ------ | ------------- |\n| 10^4   | 万 (Man/Wan)  |\n| 10^8   | 億 (Oku/Yi)   |\n| 10^12  | 兆 (Chō/Zhao) |\n| 10^16  | 京 (Kei/Jing) |\n| 10^20  | 垓 (Gai)      |\n\n```js\nconst toWords = new ToWords({ localeCode: 'ja-JP' });\ntoWords.convert(100000000);\n// \"一 億\"\n```\n\n## ⚙️ API Reference\n\n### Constructor Options\n\n```typescript\ninterface ToWordsOptions {\n  localeCode?: string; // Default: 'en-IN'\n  converterOptions?: {\n    currency?: boolean; // Default: false\n    ignoreDecimal?: boolean; // Default: false\n    ignoreZeroCurrency?: boolean; // Default: false\n    doNotAddOnly?: boolean; // Default: false\n    includeZeroFractional?: boolean; // Default: false\n    currencyOptions?: {\n      name: string;\n      plural: string;\n      symbol: string;\n      fractionalUnit: {\n        name: string;\n        plural: string;\n        symbol: string;\n      };\n    };\n  };\n}\n```\n\n### Class Methods\n\n#### `convert(number, options?)`\n\nConverts a number to words.\n\n- **number**: `number | bigint | string` — The number to convert\n- **options**: `ConverterOptions` — Override instance options\n- **returns**: `string` — The number in words\n\n#### `toOrdinal(number, options?)`\n\nConverts a number to ordinal words.\n\n- **number**: `number | bigint | string` — The number to convert (must be a non-negative integer value)\n- **options**: `OrdinalOptions` — Optional settings (`{ formal?: boolean }`)\n- **returns**: `string` — The ordinal in words (e.g., \"First\", \"Twenty Third\")\n\n### Functional Exports\n\nThe three conversion helpers (`toWords`, `toOrdinal`, `toCurrency`) are available from the full bundle (`to-words`) and from every per-locale entry point (`to-words/\u003clocale\u003e`). `detectLocale` is only available from the full bundle. When importing from `to-words/\u003clocale\u003e`, the locale is already baked in and `localeCode` is not accepted.\n\n#### `toWords(number, options?)`\n\nConverts a number to words.\n\n- **number**: `number | bigint | string` — The number to convert\n- **options** _(full bundle)_: `ConverterOptions \u0026 { localeCode?: string }` — When `localeCode` is omitted, `detectLocale()` is called automatically\n- **options** _(per-locale)_: `ConverterOptions`\n- **returns**: `string`\n\n```js\nimport { toWords } from 'to-words';\ntoWords(12345, { localeCode: 'en-US' }); // explicit locale\ntoWords(12345); // auto-detects runtime locale\n\nimport { toWords } from 'to-words/en-US';\ntoWords(12345); // locale baked in, no detection needed\n```\n\n#### `toOrdinal(number, options?)`\n\nConverts a number to ordinal words.\n\n- **number**: `number | bigint | string` — Must represent a non-negative integer\n- **options** _(full bundle)_: `OrdinalOptions \u0026 { localeCode?: string }` — When `localeCode` is omitted, `detectLocale()` is called automatically\n- **options** _(per-locale)_: `OrdinalOptions`\n- **returns**: `string`\n\n```js\nimport { toOrdinal } from 'to-words';\ntoOrdinal(21, { localeCode: 'en-US' }); // explicit locale\ntoOrdinal(21); // auto-detects runtime locale\n\nimport { toOrdinal } from 'to-words/en-US';\ntoOrdinal(21); // locale baked in\n```\n\n#### `toCurrency(number, options?)`\n\nShorthand for converting a number to currency words. Equivalent to `toWords(number, { currency: true, ...options })`.\n\n- **number**: `number | bigint | string`\n- **options** _(full bundle)_: `ConverterOptions \u0026 { localeCode?: string }` — When `localeCode` is omitted, `detectLocale()` is called automatically\n- **options** _(per-locale)_: `ConverterOptions`\n- **returns**: `string`\n\n```js\nimport { toCurrency } from 'to-words';\ntoCurrency(1234.56, { localeCode: 'en-US' }); // explicit locale\ntoCurrency(1234.56); // auto-detects runtime locale\n\nimport { toCurrency } from 'to-words/en-US';\ntoCurrency(1234.56); // locale baked in\n```\n\n#### `detectLocale(fallback?)`\n\nReads the current runtime locale.\n\n- In **browsers**: reads `navigator.language`\n- In **Node.js / Deno / Bun / CF Workers**: reads `Intl.DateTimeFormat().resolvedOptions().locale`\n- Normalises BCP 47 tags (e.g. `zh-Hant-TW` → `zh-TW`) and falls back to a language-prefix match\n\n- **fallback** _(optional)_: `string` — Returned when no supported locale can be matched. Default: `'en-IN'`\n- **returns**: `string` — A supported locale code\n\n```js\nimport { detectLocale } from 'to-words';\n\ndetectLocale(); // e.g. 'en-US', 'fr-FR', 'ja-JP'\ndetectLocale('en-GB'); // custom fallback if detection fails\n```\n\n\u003e `detectLocale` is only available from the full bundle (`to-words`), not from per-locale entry points.\n\n### Converter Options\n\n| Option                  | Type    | Default   | Description                                                                                      |\n| ----------------------- | ------- | --------- | ------------------------------------------------------------------------------------------------ |\n| `currency`              | boolean | false     | Convert as currency with locale-specific formatting                                              |\n| `ignoreDecimal`         | boolean | false     | Ignore fractional part when converting                                                           |\n| `ignoreZeroCurrency`    | boolean | false     | Skip zero main currency (e.g., show only \"Thirty Six Paise\")                                     |\n| `doNotAddOnly`          | boolean | false     | Omit \"Only\" suffix in currency mode                                                              |\n| `includeZeroFractional` | boolean | false     | When input is a string like `\"123.00\"`, include \"And Zero Paise\" even though the decimal is zero |\n| `currencyOptions`       | object  | undefined | Override locale's default currency settings                                                      |\n| `gender`                | string  | undefined | Grammatical gender: `'masculine'` or `'feminine'`. Applies to locales with gendered number words |\n| `useAnd`                | boolean | undefined | Insert the locale connector before the last two digits (e.g., \"One Hundred **And** Twenty Three\"). No-op when locale already defines a split word or has an empty connector token |\n| `formal`                | boolean | undefined | Use formal/financial characters (currently supported for zh-CN and zh-TW)                        |\n\n### Common Options Example\n\n```js\nconst toWords = new ToWords({ localeCode: 'en-US' });\n\ntoWords.convert(1234.56, {\n  currency: true,\n  ignoreDecimal: false,\n  doNotAddOnly: true,\n});\n// \"One Thousand Two Hundred Thirty Four Dollars And Fifty Six Cents\"\n```\n\n## 📏 Bundle Sizes\n\n| Import Method             | Raw    | Gzip   |\n| ------------------------- | ------ | ------ |\n| Full bundle (all locales) | 654 KB | 60 KB  |\n| Single locale (en-US)     | 12 KB  | 3.5 KB |\n| Single locale (en-IN)     | 10 KB  | 3.4 KB |\n\n\u003e **Tip:** Use tree-shakeable imports or single-locale UMD bundles for the smallest bundle size.\n\n## ⚡ Performance\n\nBenchmarked on Apple M2 (Node.js 23):\n\n| Operation                  | Throughput    |\n| -------------------------- | ------------- |\n| Small integers (42)        | ~4.7M ops/sec |\n| Medium integers (12,345)   | ~2.2M ops/sec |\n| Large integers (15 digits) | ~700K ops/sec |\n| Currency conversion        | ~1M ops/sec   |\n| BigInt (30+ digits)        | ~225K ops/sec |\n\nRun benchmarks locally:\n\n```bash\nnpm run bench\n```\n\n## 🌐 Browser Compatibility\n\n| Browser | Version |\n| ------- | ------- |\n| Chrome  | 67+     |\n| Firefox | 68+     |\n| Safari  | 14+     |\n| Edge    | 79+     |\n| Opera   | 54+     |\n\n**BigInt Support:** BigInt is required for full functionality. Internet Explorer is not supported.\n\n### Runtime Compatibility\n\n| Runtime            | Support |\n| ------------------ | ------- |\n| Node.js            | 20+     |\n| Deno               | 1.28+   |\n| Bun                | 1.0+    |\n| Cloudflare Workers | ✅      |\n\nThe library uses only standard ECMAScript features (BigInt, Intl, Map) with zero Node.js-specific APIs, making it compatible with any modern JavaScript runtime.\n\n## 🗺️ Supported Locales\n\nAll 124 locales with their features:\n\n| Locale | Language        | Country             | Currency      | Scale      | Ordinal |\n| ------ | --------------- | ------------------- | ------------- | ---------- | ------- |\n| af-ZA  | Afrikaans       | South Africa        | Rand          | Short      | ✓       |\n| am-ET  | Amharic         | Ethiopia            | ብር            | Short      | ✓       |\n| ar-AE  | Arabic          | UAE                 | درهم          | Short      | ✓       |\n| ar-LB  | Arabic          | Lebanon             | ليرة          | Short      | ✓       |\n| ar-MA  | Arabic          | Morocco             | درهم          | Short      | ✓       |\n| ar-SA  | Arabic          | Saudi Arabia        | ريال          | Short      | ✓       |\n| as-IN  | Assamese        | India               | টকা           | Indian     | ✓       |\n| az-AZ  | Azerbaijani     | Azerbaijan          | Manat         | Short      | ✓       |\n| be-BY  | Belarusian      | Belarus             | Рубель        | Short      | ✓       |\n| bg-BG  | Bulgarian       | Bulgaria            | Лев           | Short      | ✓       |\n| bn-BD  | Bengali         | Bangladesh          | টাকা          | Short      | ✓       |\n| bn-IN  | Bengali         | India               | টাকা          | Short      | ✓       |\n| ca-ES  | Catalan         | Spain               | Euro          | Short      | ✓       |\n| cs-CZ  | Czech           | Czech Republic      | Koruna        | Short      | ✓       |\n| da-DK  | Danish          | Denmark             | Krone         | Long       | ✓       |\n| de-AT  | German          | Austria             | Euro          | Long       | ✓       |\n| de-CH  | German          | Switzerland         | Franken       | Long       | ✓       |\n| de-DE  | German          | Germany             | Euro          | Long       | ✓       |\n| ee-EE  | Estonian        | Estonia             | Euro          | Short      | ✓       |\n| el-GR  | Greek           | Greece              | Ευρώ          | Short      | ✓       |\n| en-AE  | English         | UAE                 | Dirham        | Short      | ✓       |\n| en-AU  | English         | Australia           | Dollar        | Short      | ✓       |\n| en-BD  | English         | Bangladesh          | Taka          | Indian     | ✓       |\n| en-CA  | English         | Canada              | Dollar        | Short      | ✓       |\n| en-GB  | English         | United Kingdom      | Pound         | Short      | ✓       |\n| en-GH  | English         | Ghana               | Cedi          | Short      | ✓       |\n| en-HK  | English         | Hong Kong           | Dollar        | Short      | ✓       |\n| en-IE  | English         | Ireland             | Euro          | Short      | ✓       |\n| en-IN  | English         | India               | Rupee         | Indian     | ✓       |\n| en-JM  | English         | Jamaica             | Dollar        | Short      | ✓       |\n| en-KE  | English         | Kenya               | Shilling      | Short      | ✓       |\n| en-LK  | English         | Sri Lanka           | Rupee         | Short      | ✓       |\n| en-MA  | English         | Morocco             | Dirham        | Short      | ✓       |\n| en-MM  | English         | Myanmar             | Kyat          | Short      | ✓       |\n| en-MU  | English         | Mauritius           | Rupee         | Indian     | ✓       |\n| en-MY  | English         | Malaysia            | Ringgit       | Short      | ✓       |\n| en-NG  | English         | Nigeria             | Naira         | Short      | ✓       |\n| en-NP  | English         | Nepal               | Rupee         | Indian     | ✓       |\n| en-NZ  | English         | New Zealand         | Dollar        | Short      | ✓       |\n| en-OM  | English         | Oman                | Rial          | Short      | ✓       |\n| en-PH  | English         | Philippines         | Peso          | Short      | ✓       |\n| en-PK  | English         | Pakistan            | Rupee         | Indian     | ✓       |\n| en-SA  | English         | Saudi Arabia        | Riyal         | Short      | ✓       |\n| en-SG  | English         | Singapore           | Dollar        | Short      | ✓       |\n| en-TT  | English         | Trinidad and Tobago | Dollar        | Short      | ✓       |\n| en-TZ  | English         | Tanzania            | Shilling      | Short      | ✓       |\n| en-UG  | English         | Uganda              | Shilling      | Short      | ✓       |\n| en-US  | English         | USA                 | Dollar        | Short      | ✓       |\n| en-ZA  | English         | South Africa        | Rand          | Short      | ✓       |\n| en-ZW  | English         | Zimbabwe            | Zimbabwe Gold | Short      | ✓       |\n| es-AR  | Spanish         | Argentina           | Peso          | Short      | ✓       |\n| es-CL  | Spanish         | Chile               | Peso          | Short      | ✓       |\n| es-CO  | Spanish         | Colombia            | Peso          | Short      | ✓       |\n| es-ES  | Spanish         | Spain               | Euro          | Short      | ✓       |\n| es-MX  | Spanish         | Mexico              | Peso          | Short      | ✓       |\n| es-US  | Spanish         | USA                 | Dólar         | Short      | ✓       |\n| es-VE  | Spanish         | Venezuela           | Bolívar       | Short      | ✓       |\n| fa-IR  | Persian         | Iran                | تومان         | Short      | ✓       |\n| fi-FI  | Finnish         | Finland             | Euro          | Short      | ✓       |\n| fil-PH | Filipino        | Philippines         | Piso          | Short      | ✓       |\n| fr-BE  | French          | Belgium             | Euro          | Long       | ✓       |\n| fr-CA  | French          | Canada              | Dollar        | Long       | ✓       |\n| fr-CH  | French          | Switzerland         | Franc         | Long       | ✓       |\n| fr-FR  | French          | France              | Euro          | Long       | ✓       |\n| fr-MA  | French          | Morocco             | Dirham        | Long       | ✓       |\n| fr-SA  | French          | Saudi Arabia        | Riyal         | Long       | ✓       |\n| gu-IN  | Gujarati        | India               | રૂપિયો        | Short      | ✓       |\n| ha-NG  | Hausa           | Nigeria             | Naira         | Short      | ✓       |\n| hbo-IL | Biblical Hebrew | Israel              | שקל           | Short      | ✓       |\n| he-IL  | Hebrew          | Israel              | שקל           | Short      | ✓       |\n| hi-IN  | Hindi           | India               | रुपया         | Indian     | ✓       |\n| hr-HR  | Croatian        | Croatia             | Euro          | Short      | ✓       |\n| hu-HU  | Hungarian       | Hungary             | Forint        | Short      | ✓       |\n| id-ID  | Indonesian      | Indonesia           | Rupiah        | Short      | ✓       |\n| ig-NG  | Igbo            | Nigeria             | Naira         | Short      | ✓       |\n| is-IS  | Icelandic       | Iceland             | Króna         | Short      | ✓       |\n| it-IT  | Italian         | Italy               | Euro          | Short      | ✓       |\n| ja-JP  | Japanese        | Japan               | 円            | East Asian | ✓       |\n| jv-ID  | Javanese        | Indonesia           | Rupiah        | Short      | ✓       |\n| ka-GE  | Georgian        | Georgia             | ლარი          | Short      | ✓       |\n| km-KH  | Khmer           | Cambodia            | រៀល           | Khmer      | ✓       |\n| kn-IN  | Kannada         | India               | ರೂಪಾಯಿ        | Short      | ✓       |\n| ko-KR  | Korean          | South Korea         | 원            | Short      | ✓       |\n| lt-LT  | Lithuanian      | Lithuania           | Euras         | Short      | ✓       |\n| lv-LV  | Latvian         | Latvia              | Eiro          | Short      | ✓       |\n| ml-IN  | Malayalam       | India               | രൂപ           | Indian     | ✓       |\n| mr-IN  | Marathi         | India               | रुपया         | Indian     | ✓       |\n| ms-MY  | Malay           | Malaysia            | Ringgit       | Short      | ✓       |\n| ms-SG  | Malay           | Singapore           | Dolar         | Short      | ✓       |\n| my-MM  | Burmese         | Myanmar             | ကျပ်          | Burmese    | ✓       |\n| nb-NO  | Norwegian       | Norway              | Krone         | Long       | ✓       |\n| nl-NL  | Dutch           | Netherlands         | Euro          | Short      | ✓       |\n| nl-SR  | Dutch           | Suriname            | Dollar        | Short      | ✓       |\n| np-NP  | Nepali          | Nepal               | रुपैयाँ       | Indian     | ✓       |\n| or-IN  | Odia            | India               | ଟଙ୍କା          | Short      | ✓       |\n| pa-IN  | Punjabi         | India               | ਰੁਪਇਆ         | Short      | ✓       |\n| pl-PL  | Polish          | Poland              | Złoty         | Short      | ✓       |\n| pt-AO  | Portuguese      | Angola              | Kwanza        | Short      | ✓       |\n| pt-BR  | Portuguese      | Brazil              | Real          | Short      | ✓       |\n| pt-MZ  | Portuguese      | Mozambique          | Metical       | Short      | ✓       |\n| pt-PT  | Portuguese      | Portugal            | Euro          | Short      | ✓       |\n| ro-RO  | Romanian        | Romania             | Leu           | Short      | ✓       |\n| ru-RU  | Russian         | Russia              | Рубль         | Short      | ✓       |\n| si-LK  | Sinhala         | Sri Lanka           | රුපියල        | Indian     | ✓       |\n| sk-SK  | Slovak          | Slovakia            | Euro          | Short      | ✓       |\n| sl-SI  | Slovenian       | Slovenia            | Euro          | Short      | ✓       |\n| sq-AL  | Albanian        | Albania             | Lek           | Short      | ✓       |\n| sr-RS  | Serbian         | Serbia              | Dinar         | Short      | ✓       |\n| sv-SE  | Swedish         | Sweden              | Krona         | Short      | ✓       |\n| sw-KE  | Swahili         | Kenya               | Shilingi      | Short      | ✓       |\n| sw-TZ  | Swahili         | Tanzania            | Shilingi      | Short      | ✓       |\n| ta-IN  | Tamil           | India               | ரூபாய்        | Short      | ✓       |\n| te-IN  | Telugu          | India               | రూపాయి        | Short      | ✓       |\n| th-TH  | Thai            | Thailand            | บาท           | Short      | ✓       |\n| tr-TR  | Turkish         | Turkey              | Lira          | Short      | ✓       |\n| uk-UA  | Ukrainian       | Ukraine             | Гривня        | Short      | ✓       |\n| ur-PK  | Urdu            | Pakistan            | روپیہ         | Short      | ✓       |\n| uz-UZ  | Uzbek           | Uzbekistan          | So'm          | Short      | ✓       |\n| vi-VN  | Vietnamese      | Vietnam             | Đồng          | Short      | ✓       |\n| yo-NG  | Yoruba          | Nigeria             | Naira         | Short      | ✓       |\n| yue-HK | Cantonese       | Hong Kong           | 元            | East Asian | ✓       |\n| zh-CN  | Chinese         | China               | 元            | East Asian | ✓       |\n| zh-TW  | Chinese         | Taiwan              | 元            | East Asian | ✓       |\n| zu-ZA  | Zulu            | South Africa        | Rand          | Short      | ✓       |\n\n**Scale Legend:**\n\n- **Short** — Western short scale (Million, Billion, Trillion...)\n- **Long** — European long scale (Million, Milliard, Billion, Billiard...)\n- **Indian** — Indian numbering (Lakh, Crore, Arab, Kharab...)\n- **East Asian** — East Asian numbering (万, 億, 兆, 京...)\n- **Burmese** — Burmese traditional scale (သောင်း=10k, သိန်း=100k, သန်း=1M)\n- **Khmer** — Khmer traditional scale (មុឺន=10k, សែន=100k, លាន=1M)\n\n**Gender Support:**\n\nThe following locales support grammatical gender via `{ gender: 'feminine' }` or `{ gender: 'masculine' }`:\n\n- **Spanish:** es-ES, es-MX, es-CO, es-CL, es-AR, es-VE, es-US\n- **Portuguese:** pt-BR, pt-PT, pt-AO, pt-MZ\n- **Arabic:** ar-AE, ar-LB, ar-MA, ar-SA\n- **Hebrew:** he-IL, hbo-IL\n- **Slavic:** ru-RU, uk-UA, pl-PL, cs-CZ, hr-HR, sk-SK, sr-RS, be-BY, bg-BG\n- **Other:** ca-ES, ro-RO, lv-LV, lt-LT, sl-SI\n\n**Formal Numerals:** zh-CN and zh-TW support formal/financial Chinese characters (大写/大寫) via `{ formal: true }`.\n\n**Scale-First Ordering:** ig-NG uses scale-first word ordering (e.g., \"Puku Abụọ\" = \"Thousand Two\" for 2000).\n\n## ⚠️ Error Handling\n\nThe library throws descriptive errors for invalid inputs:\n\n### Invalid Number\n\n```js\ntoWords.convert('abc');\n// Error: Invalid Number \"abc\"\n\ntoWords.convert(NaN);\n// Error: Invalid Number \"NaN\"\n\ntoWords.convert(Infinity);\n// Error: Invalid Number \"Infinity\"\n```\n\n### Unknown Locale\n\n```js\nconst toWords = new ToWords({ localeCode: 'xx-XX' });\ntoWords.convert(123);\n// Error: Unknown Locale \"xx-XX\"\n```\n\n### Invalid Ordinal Input\n\n```js\ntoWords.toOrdinal(-5);\n// Error: Ordinal numbers must be non-negative integers, got \"-5\"\n\ntoWords.toOrdinal(3.14);\n// Error: Ordinal numbers must be non-negative integers, got \"3.14\"\n```\n\n### Handling Errors\n\n```js\ntry {\n  const words = toWords.convert(userInput);\n  console.log(words);\n} catch (error) {\n  console.error('Conversion failed:', error.message);\n}\n```\n\n## 🤝 Contributing\n\nContributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for the full guide covering development setup, coding guidelines, how to add a new locale, commit message format, and the PR process.\n\nFor questions or ideas, [open an issue](https://github.com/mastermunj/to-words/issues) or [start a discussion](https://github.com/mastermunj/to-words/discussions).\n\nThis project follows the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md).\n\n## ❓ FAQ\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eHow do I handle numbers larger than JavaScript's safe integer limit?\u003c/strong\u003e\u003c/summary\u003e\n\nUse BigInt or pass the number as a string:\n\n```js\n// Using BigInt\ntoWords.convert(9007199254740993n);\n\n// Using string\ntoWords.convert('9007199254740993');\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eWhy am I seeing scientific notation in my output?\u003c/strong\u003e\u003c/summary\u003e\n\nJavaScript automatically converts large numbers to scientific notation. Pass them as strings or BigInt instead:\n\n```js\n// ❌ This may give unexpected results\ntoWords.convert(123456789012345678901);\n\n// ✅ Use string or BigInt\ntoWords.convert('123456789012345678901');\ntoWords.convert(123456789012345678901n);\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eCan I use a custom currency?\u003c/strong\u003e\u003c/summary\u003e\n\nYes! Override the currency options:\n\n```js\ntoWords.convert(1234.56, {\n  currency: true,\n  currencyOptions: {\n    name: 'Bitcoin',\n    plural: 'Bitcoins',\n    symbol: '₿',\n    fractionalUnit: { name: 'Satoshi', plural: 'Satoshis', symbol: 'sat' },\n  },\n});\n// \"One Thousand Two Hundred Thirty Four Bitcoins And Fifty Six Satoshis Only\"\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eDoes this work in the browser?\u003c/strong\u003e\u003c/summary\u003e\n\nYes! Use the UMD bundles via CDN:\n\n```html\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/to-words/dist/umd/en-US.min.js\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n  const toWords = new ToWords();\n  console.log(toWords.convert(123));\n\u003c/script\u003e\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eCan I inject a custom locale (crypto, internal units, custom currency)?\u003c/strong\u003e\u003c/summary\u003e\n\nYes. `ToWordsCore` exposes a `setLocale()` method that accepts any class implementing `LocaleInterface` (`{ config: LocaleConfig }`). No need to fork the package or submit a PR — your custom locale stays in your own codebase.\n\n```ts\nimport { ToWordsCore } from 'to-words';\nimport type { LocaleInterface, LocaleConfig } from 'to-words';\n\nclass BitcoinLocale implements LocaleInterface {\n  config: LocaleConfig = {\n    currency: {\n      name: 'Bitcoin',\n      plural: 'Bitcoins',\n      symbol: '₿',\n      fractionalUnit: { name: 'Satoshi', plural: 'Satoshis', symbol: 'sat' },\n    },\n    texts: { and: 'And', minus: 'Minus', only: 'Only', point: 'Point' },\n    numberWordsMapping: [\n      { number: 1, value: 'One' },\n      { number: 2, value: 'Two' },\n      // ... rest of the mapping, same structure as any built-in locale\n    ],\n  };\n}\n\nconst tw = new ToWordsCore();\ntw.setLocale(BitcoinLocale);\nconsole.log(tw.convert(2.1, { currency: true }));\n// \"Two Bitcoins And Ten Satoshis Only\"\n```\n\nThe easiest starting point is to copy the nearest built-in locale from [`src/locales/`](src/locales/) and change only what differs.\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eHow do I add support for a new locale?\u003c/strong\u003e\u003c/summary\u003e\n\nSee the [Contributing](#-contributing) section above. You'll need to create a locale file implementing the `LocaleInterface` and add tests.\n\n\u003c/details\u003e\n\n## 📋 Changelog\n\nSee [CHANGELOG.md](CHANGELOG.md) for a detailed history of changes.\n\n## 📄 License\n\n[MIT](LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmastermunj%2Fto-words","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmastermunj%2Fto-words","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmastermunj%2Fto-words/lists"}