mastermunj/to-words

GitHub: mastermunj/to-words

将数字与货币金额转换为单词,支持多语言与超大数,适用于前端与后端的文本化展示场景。

Stars: 129 | Forks: 86

# to-words [![npm version](https://img.shields.io/npm/v/to-words.svg)](https://www.npmjs.com/package/to-words) [![npm downloads](https://img.shields.io/npm/dm/to-words.svg)](https://www.npmjs.com/package/to-words) [![build](https://img.shields.io/github/actions/workflow/status/mastermunj/to-words/ci.yml?branch=main&label=build)](https://github.com/mastermunj/to-words/actions) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/mastermunj/to-words/badge)](https://securityscorecards.dev/viewer/?uri=github.com/mastermunj/to-words) [![coverage](https://codecov.io/gh/mastermunj/to-words/branch/main/graph/badge.svg)](https://codecov.io/gh/mastermunj/to-words) [![minzipped size](https://img.shields.io/bundlephobia/minzip/to-words?label=minzipped)](https://bundlephobia.com/package/to-words) [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue)](https://www.typescriptlang.org/) [![license](https://img.shields.io/npm/l/to-words)](https://github.com/mastermunj/to-words/blob/main/LICENSE) [![node](https://img.shields.io/node/v/to-words)](https://www.npmjs.com/package/to-words) [![Bun](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/e3da1db7cb010021.svg)](https://github.com/mastermunj/to-words/actions/workflows/bun.yml) [![Deno](https://static.pigsec.cn/wp-content/uploads/repos/2026/04/77f231dbfb010023.svg)](https://github.com/mastermunj/to-words/actions/workflows/deno.yml) [![CF Workers](https://img.shields.io/badge/CF_Workers-compatible-F38020)](https://workers.cloudflare.com) 将数字和货币金额转换为单词,支持 124 种语言环境、原生的 BigInt、序数以及 TypeScript。 ## 🎮 实时演示 **立即体验:** **[打开交互式演示](https://mastermunj.github.io/to-words/)** 在浏览器中测试语言环境行为、货币转换、序数和大数字输入。 ## 🏆 为什么选择 to-words - **124 种语言实现**,支持区域特定的数字和货币约定 - **专为真实金融流程设计**:金额、货币单位、小数和负数的单词表达 - **大数字安全**:支持 `BigInt` 和字符串输入 - **随处运行**:Node.js、Deno、Bun、Cloudflare Workers 以及所有现代浏览器 - **函数式 API**:`toWords(42, { localeCode: 'en-US' })` 用于单行调用,或基于类的 API 适用于高吞吐量场景 - **出色的开发者体验**:TypeScript 类型定义、ESM/CJS/UMD 支持,以及按语言环境导入 - **性能优先**:针对高吞吐量转换场景优化 ## 📑 目录 - [使用场景](#-use-cases) - [功能特性](#-features) - [快速开始](#-quick-start) - [安装指南](#-installation) - [用法](#-usage) - [迁移指南](#-migration-guide) - [命令行工具](#%EF%B8%8F-cli) - [框架集成](#%EF%B8%8F-framework-integration) - [数字系统](#-numbering-systems) - [API 参考](#%EF%B8%8F-api-reference) - [构造函数选项](#constructor-options) - [类方法](#class-methods) - [函数式导出](#functional-exports) - [转换器选项](#converter-options) - [包体积](#-bundle-sizes) - [性能表现](#-performance) - [浏览器兼容性](#-browser-compatibility) - [支持的语言环境](#%EF%B8%8F-supported-locales) - [错误处理](#%EF%B8%8F-error-handling) - [贡献指南](#-contributing) - [常见问题](#-faq) - [更新日志](#-changelog) - [许可证](#-license) ## 💼 使用场景 - **发票与账单**:在发票、收据和财务文件中以单词形式显示金额 - **支票打印**:银行和金融机构需要以单词形式填写金额用于支票校验 - **电子商务**:以单词形式展示订单总额,提高清晰度和可访问性 - **法律文件**:合同和协议通常要求以文字形式写出金额 - **教育应用**:教授不同语言中的数字发音和拼写 - **无障碍访问**:屏幕阅读器受益于格式正确的数字到文本转换 - **本地化支持**:为全球用户提供区域特定的数字格式 ## ✨ 功能特性 - **124 种语言环境**:最全面的语言覆盖 - **BigInt 支持**:处理高达 10^63(万京)和更大的数字 - **多种数字系统**:短级、欧洲长级、印度级和东亚级 - **货币格式化**:带小数单位的本地化货币格式 - **序数数字**:First、Second、Third 等 - **性别感知**:支持需要语法性别的语言(西班牙语、葡萄牙语、阿拉伯语、希伯来语、斯拉夫语等) - **正式数字**:通过 `formal: true` 支持中文正式/财务字符(大写/大寫) - **可摇树优化**:仅导入所需的语言环境 - **原生 TypeScript**:包含完整的类型定义 - **多格式支持**:ESM、CJS 和 UMD 浏览器包 - **零依赖**:轻量且自包含 - **高性能**:最高可达 470 万次操作/秒(小整数;详见性能章节) - **函数式 API**:`toWords()`、`toOrdinal()`、`toCurrency()` 命名导出,便于单行调用 - **自动语言检测**:`detectLocale()` 读取 `navigator.language` 或 `Intl` 的运行环境语言 - **命令行工具**:`npx to-words 12345 --locale en-US` 用于脚本和快速转换 - **广泛兼容**:所有现代浏览器、Node.js 20+、Deno、Bun 和 Cloudflare Workers ## 🚀 快速开始 使用 `to-words` 有三种方式。根据你的使用场景选择合适的一种: **1. 基于类的 API** —— 适用于需要复用实例的高吞吐量场景: ``` import { ToWords } from 'to-words'; const tw = new ToWords({ localeCode: 'en-US' }); tw.convert(12345); // "Twelve Thousand Three Hundred Forty Five" tw.convert(100, { currency: true }); // "One Hundred Dollars Only" tw.toOrdinal(3); // "Third" ``` **2. 函数式(完整包)** —— 支持 `localeCode` 选项的单行调用,所有 124 种语言环境可用: ``` import { toWords, toOrdinal, toCurrency } from 'to-words'; toWords(12345, { localeCode: 'en-US' }); // "Twelve Thousand Three Hundred Forty Five" toCurrency(100, { localeCode: 'en-US' }); // "One Hundred Dollars Only" toOrdinal(3, { localeCode: 'en-US' }); // "Third" ``` **3. 函数式(按语言环境导入)** —— 语言环境已内嵌,完全可摇树优化,包体积最小(约 3.5 KB gzip): ``` import { toWords, toOrdinal, toCurrency } from 'to-words/en-US'; toWords(12345); // "Twelve Thousand Three Hundred Forty Five" toCurrency(100); // "One Hundred Dollars Only" toOrdinal(3); // "Third" ``` ## 📦 安装 ### npm / yarn / pnpm ``` npm install to-words # 或 yarn add to-words # 或 pnpm add to-words ``` ### CDN(浏览器) ``` ``` ## 📖 用法 ### 导入 ``` // Class-based — ESM import { ToWords } from 'to-words'; // Class-based — CommonJS const { ToWords } = require('to-words'); // Functional helpers (full bundle) — ESM import { toWords, toOrdinal, toCurrency, detectLocale } from 'to-words'; // Functional helpers (per-locale, tree-shakeable) — ESM import { toWords, toOrdinal, toCurrency } from 'to-words/en-US'; // Per-locale class — ESM import { ToWords } from 'to-words/en-US'; ``` ### 基础转换 **基于类:** ``` const tw = new ToWords({ localeCode: 'en-US' }); tw.convert(123); // "One Hundred Twenty Three" tw.convert(123.45); // "One Hundred Twenty Three Point Four Five" tw.convert(123.045); // "One Hundred Twenty Three Point Zero Four Five" ``` **函数式(完整包):** ``` import { toWords } from 'to-words'; toWords(123, { localeCode: 'en-US' }); // "One Hundred Twenty Three" toWords(123.45, { localeCode: 'en-US' }); // "One Hundred Twenty Three Point Four Five" ``` **函数式(按语言环境):** ``` import { toWords } from 'to-words/en-US'; toWords(123); // "One Hundred Twenty Three" toWords(123.45); // "One Hundred Twenty Three Point Four Five" ``` ### BigInt 与大数字 处理超出 JavaScript 安全整数限制的数字: ``` const toWords = new ToWords({ localeCode: 'en-US' }); // Using BigInt toWords.convert(1000000000000000000n); // "One Quintillion" toWords.convert(1000000000000000000000000000000000000000000000000000000000000000n); // "One Vigintillion" // Using string for precision toWords.convert('9007199254740993'); // "Nine Quadrillion Seven Trillion One Hundred Ninety Nine Billion // Two Hundred Fifty Four Million Seven Hundred Forty Thousand Nine Hundred Ninety Three" ``` ### 货币转换 **基于类:** ``` const tw = new ToWords({ localeCode: 'en-IN' }); tw.convert(452, { currency: true }); // "Four Hundred Fifty Two Rupees Only" tw.convert(452.36, { currency: true }); // "Four Hundred Fifty Two Rupees And Thirty Six Paise Only" // Without "Only" suffix tw.convert(452, { currency: true, doNotAddOnly: true }); // "Four Hundred Fifty Two Rupees" // Ignore decimal/fractional part tw.convert(452.36, { currency: true, ignoreDecimal: true }); // "Four Hundred Fifty Two Rupees Only" // Ignore zero currency tw.convert(0.36, { currency: true, ignoreZeroCurrency: true }); // "Thirty Six Paise Only" // Show fractional unit even when zero (string input preserves .00) tw.convert('452.00', { currency: true, includeZeroFractional: true }); // "Four Hundred Fifty Two Rupees And Zero Paise Only" ``` **函数式 —— `toCurrency()` 快捷方式:** ``` import { toCurrency } from 'to-words'; toCurrency(452, { localeCode: 'en-IN' }); // "Four Hundred Fifty Two Rupees Only" toCurrency(452.36, { localeCode: 'en-IN' }); // "Four Hundred Fifty Two Rupees And Thirty Six Paise Only" toCurrency(452, { localeCode: 'en-IN', doNotAddOnly: true }); // "Four Hundred Fifty Two Rupees" ``` **函数式按语言环境**(语言环境已内嵌,无需 `localeCode` 参数): ``` import { toCurrency } from 'to-words/en-IN'; toCurrency(452); // "Four Hundred Fifty Two Rupees Only" toCurrency(452.36); // "Four Hundred Fifty Two Rupees And Thirty Six Paise Only" ``` ### 自定义货币 在不改变语言环境的前提下覆盖货币设置: ``` const toWords = new ToWords({ localeCode: 'en-US', converterOptions: { currency: true, currencyOptions: { name: 'Euro', plural: 'Euros', symbol: '€', fractionalUnit: { name: 'Cent', plural: 'Cents', symbol: '', }, }, }, }); toWords.convert(100.5); // "One Hundred Euros And Fifty Cents Only" ``` ### 序数数字 **基于类:** ``` const tw = new ToWords({ localeCode: 'en-US' }); tw.toOrdinal(1); // "First" tw.toOrdinal(21); // "Twenty First" tw.toOrdinal(100); // "One Hundredth" ``` **函数式 —— `toOrdinal()`(完整包):** ``` import { toOrdinal } from 'to-words'; toOrdinal(1, { localeCode: 'en-US' }); // "First" toOrdinal(21, { localeCode: 'en-US' }); // "Twenty First" toOrdinal(100, { localeCode: 'en-US' }); // "One Hundredth" ``` **函数式按语言环境:** ``` import { toOrdinal } from 'to-words/en-US'; toOrdinal(1); // "First" toOrdinal(21); // "Twenty First" ``` ### 语法性别感知 许多语言对数字词使用语法性别。通过转换器选项传入 `gender`: ``` // Spanish: masculine (default) vs feminine const tw = new ToWords({ localeCode: 'es-ES' }); tw.convert(1); // "Uno" tw.convert(1, { gender: 'feminine' }); // "Una" tw.convert(21, { gender: 'feminine' }); // "Veintiuna" tw.convert(200, { gender: 'feminine' }); // "Doscientas" // Portuguese const pt = new ToWords({ localeCode: 'pt-BR' }); pt.convert(2, { gender: 'feminine' }); // "Duas" // Arabic const ar = new ToWords({ localeCode: 'ar-AE' }); ar.convert(3, { gender: 'feminine' }); // "ثلاث" ``` 性别也可通过构造函数选项设置,并在每次调用时覆盖: ``` const tw = new ToWords({ localeCode: 'es-ES', converterOptions: { gender: 'feminine' }, }); tw.convert(1); // "Una" (constructor default) tw.convert(1, { gender: 'masculine' }); // "Uno" (per-call override) ``` ### 使用 And 选项 在最后两个数字之间插入语言环境的“And”词: ``` const tw = new ToWords({ localeCode: 'en-US' }); tw.convert(123); // "One Hundred Twenty Three" tw.convert(123, { useAnd: true }); // "One Hundred And Twenty Three" tw.convert(1023, { useAnd: true }); // "One Thousand And Twenty Three" // Works with currency too tw.convert(123, { currency: true, useAnd: true }); // "One Hundred And Twenty Three Dollars Only" ``` ### 中文正式数字 对中文语言环境使用正式/财务字符(大写/大寫): ``` // Simplified Chinese const cn = new ToWords({ localeCode: 'zh-CN' }); cn.convert(123); // "百 二十 三" cn.convert(123, { formal: true }); // "佰 贰拾 叁" cn.convert(100, { currency: true, formal: true }); // "佰 圆 整" // Traditional Chinese const tw = new ToWords({ localeCode: 'zh-TW' }); tw.convert(123, { formal: true }); // "佰 貳拾 參" tw.toOrdinal(5, { formal: true }); // "第伍" ``` ### 可摇树导入 每个语言环境入口点(`to-words/`)导出内容: | 导出 | 类型 | 描述 | | ---------- | -------- | -------------------------------------- | | `ToWords` | 类 | 针对该语言环境预配置好的完整类 API | | `toWords` | 函数 | 数字 → 单词转换 | | `toOrdinal`| 函数 | 数字 → 序数单词转换 | | `toCurrency`| 函数 | 数字 → 货币单词转换 | ``` // Class-based (locale pre-configured, no localeCode needed) import { ToWords } from 'to-words/en-US'; const tw = new ToWords(); tw.convert(12345); // "Twelve Thousand Three Hundred Forty Five" // Functional helpers (locale baked in — smallest possible import) import { toWords, toOrdinal, toCurrency } from 'to-words/en-US'; toWords(12345); // "Twelve Thousand Three Hundred Forty Five" toOrdinal(3); // "Third" toCurrency(100); // "One Hundred Dollars Only" ``` ### 浏览器使用(UMD) ``` ``` ### 函数式 API 两种变体,取决于是否需要摇树优化: **完整包** —— `localeCode` 为可选参数;不传时使用自动检测的运行环境语言: ``` import { toWords, toOrdinal, toCurrency } from 'to-words'; // Explicit locale toWords(12345, { localeCode: 'en-US' }); // "Twelve Thousand Three Hundred Forty Five" toOrdinal(21, { localeCode: 'en-US' }); // "Twenty First" toCurrency(1234.56, { localeCode: 'en-IN' }); // "One Thousand Two Hundred Thirty Four Rupees And Fifty Six Paise Only" // No localeCode — uses detectLocale() automatically toWords(12345); // result depends on the runtime locale (e.g. 'en-US' → "Twelve Thousand Three Hundred Forty Five") toCurrency(1234.56, { doNotAddOnly: true }); // currency in the runtime locale, without "Only" suffix ``` **按语言环境导入** —— 语言环境已内嵌,无需 `localeCode` 参数,完全可摇树优化(约 3.5 KB gzip): ``` import { toWords, toOrdinal, toCurrency } from 'to-words/en-US'; toWords(12345); // "Twelve Thousand Three Hundred Forty Five" toOrdinal(21); // "Twenty First" toCurrency(1234.56); // "One Thousand Two Hundred Thirty Four Dollars And Fifty Six Cents Only" ``` ### 自动检测语言环境 `detectLocale()` 在未提供 `localeCode` 时,会自动被 `toWords()`、`toOrdinal()` 和 `toCurrency()` 使用 —— 因此大多数情况下无需直接调用。它适用于读取运行时的语言环境、显示给用户或显式传递给类实例。 ``` import { detectLocale, toWords, ToWords } from 'to-words'; // Used implicitly — no localeCode needed toWords(1000); // On a browser with navigator.language = 'fr-FR': "Mille" // In a Node.js process with fr-FR locale: "Mille" // Fallback when nothing can be detected: "One Thousand" (en-IN) // Used explicitly — read once, reuse across many calls const locale = detectLocale('en-US'); // custom fallback if detection misses const tw = new ToWords({ localeCode: locale }); tw.convert(1000); ``` ## 🔄 迁移指南 从 `number-to-words`、`written-number`、`num-words` 或 `n2words` 迁移? - 参考 [`MIGRATION.md`](MIGRATION.md) 获取 API 映射和迁移方案。 - 包含包对比、行为说明和回归检查清单。 ## 🖥️ 命令行工具 无需安装即可在命令行中进行一次性转换: ``` npx to-words 12345 # 一万二千三百四十五 npx to-words 12345 --locale en-US # 一万二千三百四十五 npx to-words 1234.56 --locale en-US --currency # 一美元二千三百四十五美分整 npx to-words 3 --locale en-US --ordinal # 第三 npx to-words --detect-locale # en-US(或系统默认区域设置) ``` 安装后(`npm i -g to-words`),`to-words` 命令可直接使用。 ## ⚛️ 框架集成 ### React ``` // Change the locale import to match your users' region (e.g. 'to-words/en-GB' for UK) import { ToWords } from 'to-words/en-US'; const toWords = new ToWords(); function PriceInWords({ amount }: { amount: number }) { const words = toWords.convert(amount, { currency: true }); return {words}; } // Usage: // Renders: "One Thousand Two Hundred Thirty Four Dollars And Fifty Six Cents Only" ``` ### Vue 3 ``` ``` ### Angular ``` import { Pipe, PipeTransform } from '@angular/core'; import { ToWords } from 'to-words/en-US'; @Pipe({ name: 'toWords', standalone: true }) export class ToWordsPipe implements PipeTransform { private toWords = new ToWords(); transform(value: number, currency = false): string { return this.toWords.convert(value, { currency }); } } // Usage: {{ 1234.56 | toWords:true }} ``` ### Svelte ``` {words} ``` ### Next.js ``` // Server Component (App Router) — locale from request headers or user profile import { toWords } from 'to-words'; type Props = { amount: number; locale: string }; export default function AmountInWords({ amount, locale }: Props) { return

{toWords(amount, { localeCode: locale, currency: true })}

; } ``` ``` // Client Component — dynamic locale switching 'use client'; import { useState } from 'react'; import { toCurrency, detectLocale } from 'to-words'; export function CurrencyDisplay({ amount }: { amount: number }) { const [locale, setLocale] = useState(detectLocale('en-US')); return (

{toCurrency(amount, { localeCode: locale })}

); } ``` ### Node.js / Express ``` import express from 'express'; import { toWords, toCurrency, detectLocale } from 'to-words'; const app = express(); app.get('/convert', (req, res) => { const number = String(req.query.number ?? ''); const locale = String(req.query.locale ?? detectLocale()); const currency = req.query.currency === 'true'; try { const result = currency ? toCurrency(number, { localeCode: locale }) : toWords(number, { localeCode: locale }); res.json({ result, locale }); } catch (e) { res.status(400).json({ error: (e as Error).message }); } }); ``` ## 🌍 数字系统 不同地区使用不同的数字系统。本库支持所有主流系统: ### 短级(西方) 使用地区:美国、英国、加拿大、澳大利亚及大多数英语国家。 | 数字 | 名称 | | ---- | ------------ | | 10^6 | Million | | 10^9 | Billion | | 10^12| Trillion | | 10^15| Quadrillion | | ... | ... | | 10^63| Vigintillion | ``` const toWords = new ToWords({ localeCode: 'en-US' }); toWords.convert(1000000000000000000n); // "One Quintillion" ``` ### 欧洲长级 使用地区:德国、法国及许多欧洲国家。 | 数字 | 德文 | 法文 | | ---- | -------- | -------- | | 10^6 | Million | Million | | 10^9 | Milliarde| Milliard | | 10^12| Billion | Billion | | 10^15| Billiarde| Billiard | ``` const toWords = new ToWords({ localeCode: 'de-DE' }); toWords.convert(1000000000); // "Eins Milliarde" ``` ### 印度级 使用地区:印度、孟加拉国、尼泊尔、巴基斯坦。 | 数字 | 名称 | | ---- | ------ | | 10^5 | Lakh | | 10^7 | Crore | | 10^9 | Arab | | 10^11| Kharab | | 10^13| Neel | | 10^15| Padma | | 10^17| Shankh | ``` const toWords = new ToWords({ localeCode: 'en-IN' }); toWords.convert(100000000000000000n); // "One Shankh" const toWordsHindi = new ToWords({ localeCode: 'hi-IN' }); toWordsHindi.convert(100000000000000000n); // "एक शंख" ``` ### 东亚级 使用地区:日本、中国、韩国。 | 数字 | 字符 | | ---- | -------- | | 10^4 | 万 (Man/Wan) | | 10^8 | 億 (Oku/Yi) | | 10^12| 兆 (Chō/Zhao) | | 10^16| 京 (Kei/Jing) | | 10^20| 垓 (Gai) | ``` const toWords = new ToWords({ localeCode: 'ja-JP' }); toWords.convert(100000000); // "一 億" ``` ## ⚙️ API 参考 ### 构造函数选项 ``` interface ToWordsOptions { localeCode?: string; // Default: 'en-IN' converterOptions?: { currency?: boolean; // Default: false ignoreDecimal?: boolean; // Default: false ignoreZeroCurrency?: boolean; // Default: false doNotAddOnly?: boolean; // Default: false includeZeroFractional?: boolean; // Default: false currencyOptions?: { name: string; plural: string; symbol: string; fractionalUnit: { name: string; plural: string; symbol: string; }; }; }; } ``` ### 类方法 #### `convert(number, options?)` 将数字转换为单词。 - **number**:`number | bigint | string` —— 待转换的数字 - **options**:`ConverterOptions` —— 覆盖实例选项 - **返回**:`string` —— 单词形式的数字 #### `toOrdinal(number, options?)` 将数字转换为序数单词。 - **number**:`number | bigint | string` —— 待转换的数字(必须是非负整数) - **options**:`OrdinalOptions` —— 可选设置(`{ formal?: boolean }`) - **返回**:`string` —— 序数单词(如 "First"、"Twenty Third") ### 函数式导出 三个转换函数(`toWords`、`toOrdinal`、`toCurrency`)在完整包(`to-words`)及每个语言环境入口点(`to-words/`)中均可用。`detectLocale` 仅在完整包中提供。在 `to-words/` 中导入时,语言环境已内嵌,无需 `localeCode` 参数。 #### `toWords(number, options?)` 将数字转换为单词。 - **number**:`number | bigint | string` —— 待转换的数字 - **options**(完整包):`ConverterOptions & { localeCode?: string }` —— 省略 `localeCode` 时自动调用 `detectLocale()` - **options**(按语言环境):`ConverterOptions` - **返回**:`string` ``` import { toWords } from 'to-words'; toWords(12345, { localeCode: 'en-US' }); // explicit locale toWords(12345); // auto-detects runtime locale import { toWords } from 'to-words/en-US'; toWords(12345); // locale baked in, no detection needed ``` #### `toOrdinal(number, options?)` 将数字转换为序数单词。 - **number**:`number | bigint | string` —— 必须表示非负整数 - **options**(完整包):`OrdinalOptions & { localeCode?: string }` —— 省略 `localeCode` 时自动调用 `detectLocale()` - **options**(按语言环境):`OrdinalOptions` - **返回**:`string` ``` import { toOrdinal } from 'to-words'; toOrdinal(21, { localeCode: 'en-US' }); // explicit locale toOrdinal(21); // auto-detects runtime locale import { toOrdinal } from 'to-words/en-US'; toOrdinal(21); // locale baked in ``` #### `toCurrency(number, options?)` 转换为货币单词的快捷方式。相当于 `toWords(number, { currency: true, ...options })`。 - **number**:`number | bigint | string` - **options**(完整包):`ConverterOptions & { localeCode?: string }` —— 省略 `localeCode` 时自动调用 `detectLocale()` - **options**(按语言环境):`ConverterOptions` - **返回**:`string` ``` import { toCurrency } from 'to-words'; toCurrency(1234.56, { localeCode: 'en-US' }); // explicit locale toCurrency(1234.56); // auto-detects runtime locale import { toCurrency } from 'to-words/en-US'; toCurrency(1234.56); // locale baked in ``` #### `detectLocale(fallback?)` 读取当前运行时的语言环境。 - 在 **浏览器** 中:读取 `navigator.language` - 在 **Node.js / Deno / Bun / CF Workers** 中:读取 `Intl.DateTimeFormat().resolvedOptions().locale` - 标准化 BCP 47 标签(如 `zh-Hant-TW` → `zh-TW`),并回退到语言前缀匹配 - **fallback**(可选):`string` —— 当无匹配语言环境时返回,默认为 `'en-IN'` - **返回**:`string` —— 支持的语言环境代码 ``` import { detectLocale } from 'to-words'; detectLocale(); // e.g. 'en-US', 'fr-FR', 'ja-JP' detectLocale('en-GB'); // custom fallback if detection fails ``` ### 转换器选项 | 选项 | 类型 | 默认值 | 描述 | | ---- | ---- | ------ | ---- | | `currency` | 布尔值 | false | 以货币格式转换,使用本地化格式 | | `ignoreDecimal` | 布尔值 | false | 转换时忽略小数部分 | | `ignoreZeroCurrency` | 布尔值 | false | 忽略零主货币(例如仅显示 "Thirty Six Paise") | | `doNotAddOnly` | 布尔值 | false | 货币模式下省略 "Only" 后缀 | | `includeZeroFractional` | 值 | false | 当输入为 `"123.00"` 时包含 "And Zero Paise",即使小数为零 | | `currencyOptions` | 对象 | undefined | 覆盖默认的本地化货币设置 | | `gender` | 字符串 | undefined | 语法性别:`'masculine'` 或 `'feminine'`。适用于需要语法性别的语言环境 | | `useAnd` | 布尔值 | undefined | 在最后两个数字之间插入语言环境的“And”词(例如 "One Hundred **And** Twenty Three")。若语言环境已有分隔词或空连接符则无效果 | | `formal` | 布尔值 | undefined | 使用正式/财务字符(目前支持 zh-CN 和 zh-TW) | ### 通用选项示例 ``` const toWords = new ToWords({ localeCode: 'en-US' }); toWords.convert(1234.56, { currency: true, ignoreDecimal: false, doNotAddOnly: true, }); // "One Thousand Two Hundred Thirty Four Dollars And Fifty Six Cents" ``` ## 📏 包体积 | 导入方式 | 原始体积 | 压缩后体积 | | -------- | -------- | ---------- | | 完整包(所有语言环境) | 654 KB | 60 KB | | 单个语言环境(en-US) | 12 KB | 3.5 KB | | 单个语言环境(en-IN) | 10 KB | 3.4 KB | ## ⚡ 性能 在 Apple M2(Node.js 23)上测试: | 操作 | 吞吐量 | | ---- | ------ | | 小整数(42) | ~470 万次/秒 | | 中等整数(12,345) | ~220 万次/秒 | | 大整数(15 位) | ~70 万次/秒 | | 货币转换 | ~100 万次/秒 | | BigInt(30 位以上) | ~22.5 万次/秒 | 本地运行基准测试: ``` npm run bench ``` ## 🌐 浏览器兼容性 | 浏览器 | 版本 | | ------ | ---- | | Chrome | 67+ | | Firefox | 68+ | | Safari | 14+ | | Edge | 79+ | | Opera | 54+ | **BigInt 支持:** 完整功能需要 BigInt。Internet Explorer 不支持。 ### 运行时兼容性 | 运行时 | 支持 | | ------ | ---- | | Node.js | 20+ | | Deno | 1.28+ | | Bun | 1.0+ | | Cloudflare Workers | ✅ | 该库仅使用标准 ECMAScript 特性(BigInt、Intl、Map),不依赖任何 Node.js 专属 API,因此兼容任何现代 JavaScript 运行环境。 ## 🗺️ 支持的语言环境 全部 124 种语言环境及其功能: | 语言环境 | 语言 | 国家/地区 | 货币 | 数字系统 | 序数 | | -------- | ---- | --------- | ---- | -------- | ---- | | af-ZA | Afrikaans | 南非 | Rand | 短级 | ✓ | | am-ET | Amharic | 埃塞俄比亚 | ብር | 短级 | ✓ | | ar-AE | Arabic | 阿联酋 | درهم | 短级 | ✓ | | ar-LB | Arabic | 黎巴嫩 | ليرة | 短级 | ✓ | | ar-MA | Arabic | 摩洛哥 | درهم | 短级 | ✓ | | ar-SA | Arabic | 沙特阿拉伯 | ريال | 短级 | ✓ | | as-IN | Assamese | 印度 | টকা | 印度级 | ✓ | | az-AZ | Azerbaijani | 阿塞拜疆 | Manat | 短级 | ✓ | | be-BY | Belarusian | 白俄罗斯 | Рубель | 短级 | ✓ | | bg-BG | Bulgarian | 保加利亚 | Лев | 短级 | ✓ | | bn-BD | Bengali | 孟加拉国 | টাকা | 印度级 | ✓ | | bn-IN | Bengali | 印度 | টাকা | 印度级 | ✓ | | ca-ES | Catalan | 西班牙 | Euro | 短级 | ✓ | | cs-CZ | Czech | 捷克 | Koruna | 短级 | ✓ | | da-DK | Danish | 丹麦 | Krone | 欧洲长级 | ✓ | | de-AT | German | 奥地利 | Euro | 欧洲长级 | ✓ | | de-CH | German | 瑞士 | Franken | 欧洲长级 | ✓ | | de-DE | German | 德国 | Euro | 欧洲长级 | ✓ | | ee-EE | Estonian | 爱沙尼亚 | Euro | 短级 | ✓ | | el-GR | Greek | 希腊 | Ευρώ | 短级 | ✓ | | en-AE | English | 阿联酋 | Dirham | 短级 | ✓ | | en-AU | English | 澳大利亚 | Dollar | 短级 | ✓ | | en-BD | English | 孟加拉国 | Taka | 印度级 | ✓ | | en-CA | English | 加拿大 | Dollar | 短级 | ✓ | | en-GB | English | 英国 | Pound | 短级 | ✓ | | en-GH | English | 加纳 | Cedi | 短级 | ✓ | | en-HK | English | 香港 | Dollar | 短级 | ✓ | | en-IE | English | 爱尔兰 | Euro | 短级 | ✓ | | en-IN | English | 印度 | Rupee | 印度级 | ✓ | | en-JM | English | 牙买加 | Dollar | 短级 | ✓ | | en-KE | English | 肯尼亚 | Shilling | 短级 | ✓ | | en-LK | English | 斯里兰卡 | Rupee | 印度级 | ✓ | | en-MA | English | 摩洛哥 | Dirham | 短级 | ✓ | | en-MM | English | 缅甸 | Kyat | 短级 | ✓ | | en-MU | English | 毛里求斯 | Rupee | 印度级 | ✓ | | en-MY | English | 马来西亚 | Ringgit | 短级 | ✓ | | en-NG | English | 尼日利亚 | Naira | 短级 | ✓ | | en-NP | English | 尼泊尔 | Rupee | 印度级 | ✓ | | en-NZ | English | 新西兰 | Dollar | 短级 | ✓ | | en-OM | English | 阿曼 | Rial | 短级 | ✓ | | en-PH | English | 菲律宾 | Peso | 短级 | ✓ | | en-PK | English | 巴基斯坦 | Rupee | 印度级 | ✓ | | en-SA | English | 沙特阿拉伯 | Riyal | 短级 | ✓ | | en-SG | English | 新加坡 | Dollar | 短级 | ✓ | | en-TT | English | 特立尼达和多巴哥 | Dollar | 短级 | ✓ | | en-TZ | English | 坦桑尼亚 | Shilling | 短级 | ✓ | | en-UG | English | 乌干达 | Shilling | 短级 | ✓ | | en-US | English | 美国 | Dollar | 短级 | ✓ | | en-ZA | English | 南非 | Rand | 短级 | ✓ | | en-ZW | English | 津巴布韦 | Zimbabwe Gold | 短级 | ✓ | | es-AR | Spanish | 阿根廷 | Peso | 短级 | ✓ | | es-CL | Spanish | 智利 | Peso | 短级 | ✓ | | es-CO | Spanish | 哥伦比亚 | Peso | 短级 | ✓ | | es-ES | Spanish | 西班牙 | Euro | 短级 | ✓ | | es-MX | Spanish | 墨西哥 | Peso | 短级 | ✓ | | es-US | Spanish | 美国 | Dólar | 短级 | ✓ | | es-VE | Spanish | 委内瑞拉 | Bolívar | 短级 | ✓ | | fa-IR | Persian | 伊朗 | تومان | 短级 | ✓ | | fi-FI | Finnish | 芬兰 | Euro | 短级 | ✓ | | fil-PH | Filipino | 菲律宾 | Piso | 短级 | ✓ | | fr-BE | French | 比利时 | Euro | 欧洲长级 | ✓ | | fr-CA | French |加拿大 | Dollar | 欧洲长级 | ✓ | | fr-CH | French | 瑞士 | Franc | 欧洲长级 | ✓ | | fr-FR | French | 法国 | Euro | 欧洲长级 | ✓ | | fr-MA | French | 摩洛哥 | Dirham | 欧洲长级 | ✓ | | fr-SA | French | 沙特阿拉伯 | Riyal | 欧洲长级 | ✓ | | gu-IN | Gujarati | 印度 | રૂપિયો | 印度级 | ✓ | | ha-NG | Hausa | 尼日利亚 | Naira | 短级 | ✓ | | hbo-IL | Biblical Hebrew | 以色列 | שקל | 短级 | ✓ | | he-IL | Hebrew | 以色列 | שקל | 短级 | ✓ | | hi-IN | Hindi | 印度 | रुपया | 印度级 | ✓ | | hr-HR | Croatian | 克罗地亚 | Euro | 短级 | ✓ | | hu-HU | Hungarian | 匈牙利 | Forint | 短级 | ✓ | | id-ID | Indonesian | 印度尼西亚 | Rupiah | 短级 | ✓ | | ig-NG | Igbo | 尼日利亚 | Naira | 短级 | ✓ | | is-IS | Icelandic | 冰岛 | Króna | 短级 | ✓ | | it-IT | Italian | 意大利 | Euro | 短级 | ✓ | | ja-JP | Japanese | 日本 | 円 | 东亚级 | ✓ | | jv-ID | Javanese | 印度尼西亚 | Rupiah | 短级 | ✓ | | ka-GE | Georgian | 格鲁吉亚 | ლარი | 短级 | ✓ | | km-KH | Khmer | 柬埔寨 | រៀល | 高棉级 | ✓ | | kn-IN | Kannada | 印度 | ರೂಪಾಯಿ | 印度级 | ✓ | | ko-KR | Korean | 韩国 | 원 | 短级 | ✓ | | lt-LT | Lithuanian | 立陶宛 | Euras | 短级 | ✓ | | lv-LV | Latvian | 拉脱维亚 | Eiro | 短级 | ✓ | | ml-IN | Malayalam | 印度 | രൂപ | 印度级 | ✓ | | mr-IN | Marathi | 印度 | रुपया | 印度级 | ✓ | | ms-MY | Malay | 马来西亚 | Ringgit | 短级 | ✓ | | ms-SG | Malay | 新加坡 | Dolar | 短级 | ✓ | | my-MM | Burmese | 缅甸 | ကျပ် | 缅甸级 | ✓ | | nb-NO | Norwegian | 挪威 | Krone | 欧洲长级 | ✓ | | nl-NL | Dutch | 荷兰 | Euro | 短级 | ✓ | | nl-SR | Dutch | 苏里南 | Dollar | 短级 | ✓ | | np-NP | Nepali | 尼泊尔 | रुपैयाँ | 印度级 | ✓ | | or-IN | Odia | 印度 | ଟଙ୍କା | 短级 | ✓ | | pa-IN | Punjabi | 印度 | ਰੁਪਇਆ | 短级 | ✓ | | pl-PL | Polish | 波兰 | Złoty | 短级 | ✓ | | pt-AO | Portuguese | 安哥拉 | Kwanza | 短级 | ✓ | | pt-BR | Portuguese | 巴西 | Real | 短级 | ✓ | | pt-MZ | Portuguese | 莫桑比克 | Metical | 短级 | ✓ | | pt-PT | Portuguese | 葡萄牙 | Euro | 短级 | ✓ | | ro-RO | Romanian | 罗马尼亚 | Leu | 短级 | ✓ | | ru-RU | Russian | 俄罗斯 | Рубль | 短级 | ✓ | | si-LK | Sinhala | 斯里兰卡 | රුපියල | 印度级 | ✓ | | sk-SK | Slovak | 斯洛伐克 | Euro | 短级 | ✓ | | sl-SI | Slovenian | 斯洛文尼亚 | Euro | 短级 | ✓ | | sq-AL | Albanian | 阿尔巴尼亚 | Lek | 短级 | ✓ | | sr-RS | Serbian | 塞尔维亚 | Dinar | 短级 | ✓ | | sv-SE | Swedish | 瑞典 | Krona | 短级 | ✓ | | sw-KE | Swahili | 肯尼亚 | Shilingi | 短级 | ✓ | | sw-TZ | Swahili | 坦桑尼亚 | Shilingi | 短级 | ✓ | | ta-IN | Tamil | 印度 | ரூபாய் | 印度级 | ✓ | | te-IN | Telugu | 印度 | రూపాయి | 印度级 | ✓ | | th-TH | Thai | 泰国 | บาท | 短级 | ✓ | | tr-TR | Turkish | 土耳其 | Lira | 短级 | ✓ | | uk-UA | Ukrainian | 乌克兰 | Гривня | 短级 | ✓ | | ur-PK | Urdu | 巴基斯坦 | روپیہ | 短级 | ✓ | | uz-UZ | Uzbek | 乌兹别克斯坦 | So'm | 短级 | ✓ | | vi-VN | Vietnamese | 越南 | Đồng | 短级 | ✓ | | yo-NG | Yoruba | 尼日利亚 | Naira | 短级 | ✓ | | yue-HK | Cantonese | 香港 | 元 | 东亚级 | ✓ | | zh-CN | Chinese | 中国 | 元 | 东亚级 | ✓ | | zh-TW | Chinese | 台湾 | 元 | 东亚级 | ✓ | | zu-ZA | Zulu | 南非 | Rand | 短级 | ✓ | **等级说明:** - **短级** — 西方短级(百万、十亿、万亿……) - **欧洲长级** — 欧洲长级(百万、十亿、十亿级……) - **印度级** — 印度级(拉克、克罗尔、阿拉伯……) - **东亚级** — 东亚级(万、亿、兆、京……) - **缅甸级** — 缅甸传统级(သောင်း=10⁴,သိန်း=10⁵,သန်း=1M) - **高棉级** — 高棉传统级(មុឺន=10⁴,សែន=10⁵,លាន=1M) ### 语法性别支持 以下语言环境支持通过 `{ gender: 'feminine' }` 或 `{ gender: 'masculine' }` 进行语法性别控制: - **西班牙语:** es-ES、es-MX、es-CO、es-CL、es-AR、es-VE、es-US - **葡萄牙语:** pt-BR、pt-PT、pt-AO、pt-MZ - **阿拉伯语:** ar-AE、ar-LB、ar-MA、ar-SA - **希伯来语:** he-IL、hbo-IL - **斯拉夫语:** ru-RU、uk-UA、pl-PL、cs-CZ、hr-HR、sk-SK、sr-RS、be-BY、bg-BG - **其他:** ca-ES、ro-RO、lv-LV、lt-LT、sl-SI **正式数字:** zh-CN 和 zh-TW 支持通过 `{ formal: true }` 使用正式/财务中文字符(大写/大寫)。 **按规模优先排序:** ig-NG 使用按规模优先的单词顺序(例如 "Puku Abụọ" = "Thousand Two" 表示 2000)。 ## ⚠️ 错误处理 该库会对无效输入抛出描述性错误: ### 无效数字 ``` toWords.convert('abc'); // Error: Invalid Number "abc" toWords.convert(NaN); // Error: Invalid Number "NaN" toWords.convert(Infinity); // Error: Invalid Number "Infinity" ``` ### 未知语言环境 ``` const toWords = new ToWords({ localeCode: 'xx-XX' }); toWords.convert(123); // Error: Unknown Locale "xx-XX" ``` ### 无效序数输入 ``` toWords.toOrdinal(-5); // Error: Ordinal numbers must be non-negative integers, got "-5" toWords.toOrdinal(3.14); // Error: Ordinal numbers must be non-negative integers, got "3.14" ``` ### 错误处理 ``` try { const words = toWords.convert(userInput); console.log(words); } catch (error) { console.error('Conversion failed:', error.message); } ``` ## 🤝 贡献 欢迎贡献!请阅读 [CONTRIBUTING.md](CONTRIBUTING.md) 获取完整的开发指南,涵盖开发环境设置、编码规范、如何添加新语言环境、提交信息格式以及 PR 流程。 如有问题或想法,请[打开议题](https://github.com/mastermunj/to-words/issues)或[开始讨论](https://github.com/mastermunj/to-words/discussions)。 本项目遵循 [Contributor Covenant 行为准则](CODE_OF_CONDUCT.md)。 ## ❓ 常见问题
如何处理大于 JavaScript 安全整数限制的数字? 使用 BigInt 或以字符串形式传递数字: ``` // Using BigInt toWords.convert(9007199254740993n); // Using string toWords.convert('9007199254740993'); ```
为什么输出中会出现科学计数法? JavaScript 会自动将大数字转换为科学计数法。请改用字符串或 BigInt 传递: ``` // ❌ This may give unexpected results toWords.convert(123456789012345678901); // ✅ Use string or BigInt toWords.convert('123456789012345678901'); toWords.convert(123456789012345678901n); ```
能否使用自定义货币? 可以!覆盖货币选项即可: ``` toWords.convert(1234.56, { currency: true, currencyOptions: { name: 'Bitcoin', plural: 'Bitcoins', symbol: '₿', fractionalUnit: { name: 'Satoshi', plural: 'Satoshis', symbol: 'sat' }, }, }); // "One Thousand Two Hundred Thirty Four Bitcoins And Fifty Six Satoshis Only" ```
是否可在浏览器中使用? 是的!可通过 CDN 使用 UMD 包: ``` ```
能否注入自定义语言环境(crypto、内部单位、自定义货币)? 可以。`ToWordsCore` 暴露了 `setLocale()` 方法,接受任何实现 `LocaleInterface`(`{ config: LocaleConfig }`)的类。你的自定义语言环境可保留在本地代码中,无需分叉该包或提交 PR。 ``` import { ToWordsCore } from 'to-words'; import type { LocaleInterface, LocaleConfig } from 'to-words'; class BitcoinLocale implements LocaleInterface { config: LocaleConfig = { currency: { name: 'Bitcoin', plural: 'Bitcoins', symbol: '₿', fractionalUnit: { name: 'Satoshi', plural: 'Satoshis', symbol: 'sat' }, }, texts: { and: 'And', minus: 'Minus', only: 'Only', point: 'Point' }, numberWordsMapping: [ { number: 1, value: 'One' }, { number: 2, value: 'Two' }, // ... rest of the mapping, same structure as any built-in locale ], }; } const tw = new ToWordsCore(); tw.setLocale(BitcoinLocale); console.log(tw.convert(2.1, { currency: true })); // "Two Bitcoins And Ten Satoshis Only" ``` 最简单的起点是复制 [`src/locales/`](src/locales/) 中最接近的内置语言环境,并仅修改差异部分。
如何添加对新语言环境的支持? 请参考[贡献指南](#-contributing)部分。你需要创建一个实现 `LocaleInterface` 的语言环境文件并添加测试。
## 📋 更新日志 详见 [CHANGELOG.md](CHANGELOG.md) 了解详细变更记录。 ## 📄 许可证 [MIT](LICENSE)
标签:124种语言, Anchore, BigInt, Bun, CMS安全, Deno, GNU通用公共许可证, IPv6支持, JavaScript, MITM代理, Node.js, NPM包, OpenSSF, OSV-Scalibr, SEO, TypeScript, 前端, 国际化, 多语言, 安全插件, 安全评分, 序数, 数字转文字, 测试覆盖率, 生产环境, 程序员工具, 自动化攻击, 货币转换