mastermunj/to-words
GitHub: mastermunj/to-words
将数字与货币金额转换为单词,支持多语言与超大数,适用于前端与后端的文本化展示场景。
Stars: 129 | Forks: 86
# to-words
[](https://www.npmjs.com/package/to-words)
[](https://www.npmjs.com/package/to-words)
[](https://github.com/mastermunj/to-words/actions)
[](https://securityscorecards.dev/viewer/?uri=github.com/mastermunj/to-words)
[](https://codecov.io/gh/mastermunj/to-words)
[](https://bundlephobia.com/package/to-words)
[](https://www.typescriptlang.org/)
[](https://github.com/mastermunj/to-words/blob/main/LICENSE)
[](https://www.npmjs.com/package/to-words)
[](https://github.com/mastermunj/to-words/actions/workflows/bun.yml)
[](https://github.com/mastermunj/to-words/actions/workflows/deno.yml)
[](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
```
{{ words }}
```
### 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
);
}
```
### 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)。
## ❓ 常见问题
## 📋 更新日志
详见 [CHANGELOG.md](CHANGELOG.md) 了解详细变更记录。
## 📄 许可证
[MIT](LICENSE)
{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 })}
如何处理大于 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` 的语言环境文件并添加测试。标签:124种语言, Anchore, BigInt, Bun, CMS安全, Deno, GNU通用公共许可证, IPv6支持, JavaScript, MITM代理, Node.js, NPM包, OpenSSF, OSV-Scalibr, SEO, TypeScript, 前端, 国际化, 多语言, 安全插件, 安全评分, 序数, 数字转文字, 测试覆盖率, 生产环境, 程序员工具, 自动化攻击, 货币转换