本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:”number-to-text-vietnamese” 是一个专为JavaScript开发的轻量级库,用于将整数转换为符合越南语发音习惯的文本表示。该工具通过npm安装,提供简洁的API(如 getText 函数),支持将数字如123456转换为“một trăm hai mươi ba nghìn bốn trăm năm mươi sáu”。尽管目前仅支持正整数,不涵盖小数、负数或货币格式,但在越南语本地化场景中表现出色,适用于电商展示、教育软件和语音助手等应用。项目源码包含在”number-to-text-vietnamese-master”压缩包中,便于开发者学习实现逻辑并进行定制扩展。
number-to-text-vietnamese:将数字转换为越南字母

1. number-to-text-vietnamese库简介

在自然语言处理与国际化应用日益普及的今天,将数字转换为本地语言文本成为提升用户体验的重要环节。 number-to-text-vietnamese 是一个专为越南语设计的轻量级 JavaScript 库,旨在实现整数到越南语读音的精准转换。该库封装了越南语复杂的数字发音规则,如“nghìn”(千)、“triệu”(百万)等非连续单位词的使用习惯,以及中间零值需特殊处理(如“một trăm linh năm”)的语言现象。

import { getText } from 'number-to-text-vietnamese';
console.log(getText(1005)); // "một nghìn không trăm linh năm"

通过简洁的 API 设计,开发者可在前端页面价格展示、语音合成系统或教育类应用中快速集成越南语数字朗读功能。项目遵循 MIT 开源协议,托管于 GitHub,持续维护并支持 Tree-shaking 优化,适用于现代 JS 工程化环境。本章为后续技术实现与集成实践奠定认知基础。

2. JavaScript环境下数字转文本需求分析

在现代Web应用与国际化服务快速发展的背景下,自然语言表达的精准性已成为用户体验的重要组成部分。特别是在涉及多语言支持的系统中,将阿拉伯数字转换为符合本地语言习惯的文本读法,不仅提升了信息可读性,也增强了用户对系统的信任感与亲和力。以越南语为例,其数字发音规则复杂且具有高度上下文依赖性,直接使用简单的字符串拼接或格式化函数难以实现准确输出。因此,在JavaScript运行环境中,构建一个专门处理“数字→文本”转换的机制变得尤为必要。本章将从实际应用场景出发,深入剖析该功能的核心需求,并结合越南语的语言特性、现有技术方案局限以及性能要求,论证为何需要引入如 number-to-text-vietnamese 这类专用库来满足生产级应用的需求。

2.1 数字转文本的典型应用场景

随着全球化业务扩展,越来越多的应用程序需要面对非英语母语用户的交互设计挑战。其中,数字作为信息传递中最常见的元素之一,若仅以阿拉伯形式呈现,可能无法满足特定场景下的沟通效率与文化适配需求。通过将数字转化为语音可读的文本形式,系统能够在多个关键路径上提升可用性与专业度。

2.1.1 语音播报系统中的自然语言生成

语音助手、智能客服及无障碍阅读工具等依赖TTS(Text-to-Speech)技术的系统,要求输入文本具备高度的语言自然度。例如,在银行交易提醒中,“Số tiền giao dịch là 5.000.000 đồng” 若被机械地读作 “năm chấm không chấm không chấm không chấm không chấm không đồng”,会严重干扰理解。正确的读法应为 “năm triệu đồng”。这就要求前端或后端服务在生成播报内容前,先将数值转换为符合语法规范的越南语文本。

为此,开发者需在数据组装阶段调用数字转文本函数:

function generateTransactionSpeech(amount) {
    const textAmount = numberToTextVietnamese(amount); // 假设已集成对应库
    return `Số tiền giao dịch là ${textAmount} đồng`;
}

// 示例调用
console.log(generateTransactionSpeech(5000000));
// 输出: "Số tiền giao dịch là năm triệu đồng"

代码逻辑逐行解读:

  • 第1行:定义一个生成交易语音消息的函数,接收金额参数。
  • 第2行:调用转换库将数字转为越南语文本,这是核心处理步骤。
  • 第3行:使用模板字符串构造完整句子,确保语法结构正确。
  • 第5–6行:演示实际调用结果,验证输出是否符合预期语义。

此流程体现了数字转文本在语音合成预处理中的关键作用——它不仅是格式转换,更是语义重构的过程。

2.1.2 跨境电商价格展示的本地化需求

在面向越南市场的电商平台中,商品标价常需以“một trăm hai mươi ba ngàn đồng”等形式出现在促销文案、发票或订单确认页中,以增强本地消费者的认知亲切感。尤其在教育类产品、儿童用品或高单价商品中,文字化金额有助于建立透明与权威的形象。

考虑如下表格所示的不同展示方式对比:

阿拉伯数字 直接翻译(错误) 正确越南语文本读法 用户感知效果
123,000 một hai ba nghìn một trăm hai mươi ba ngàn 易误解为“12万3千”
2,005,000 hai triệu không nghìn hai triệu không trăm linh năm ngàn 缺少“linh”导致不自然
10,000,000 mười triệu mười triệu 正确

说明: 表格揭示了简单替换单位词的局限性。真正的挑战在于中间零值的处理(如“linh”、“lẻ”)、百位缺失时的补全逻辑,以及“ngàn”与“nghìn”的区域差异。

为实现准确渲染,前端组件可集成转换逻辑:

import { getText } from 'number-to-text-vietnamese';

function PriceLabel({ amount }) {
    const textAmount = getText(amount);
    return <div className="price-speech">Giá: {textAmount} đồng</div>;
}

该模式广泛适用于React/Vue等框架,确保UI层输出自然语言级别的金额描述。

2.1.3 教育类应用中数字读法的教学辅助

在针对越南语学习者的数学教学App或儿童识数游戏中,系统需提供“听音辨数”或“看数读音”的互动练习。此时,机器生成的标准读法必须严格遵循教育规范,避免因方言混杂或语法错误误导学生。

例如,教师希望系统能自动为练习题生成音频脚本:

const exercises = [405, 1001, 999999];
exercises.forEach(num => {
    console.log(`${num} → ${getText(num)}`);
});

预期输出:

405 → bốn trăm linh năm
1001 → một nghìn không trăm linh một
999999 → chín trăm chín mươi chín nghìn chín trăm chín mươi chín

上述案例表明,数字转文本不仅是功能性需求,更承载着文化传播与语言教育的责任。系统必须能够区分“linh”(用于十位为零)与“lẻ”(部分地区用于千位以下零值),并在不同教育标准下提供一致性输出。

2.2 越南语数字表达的独特挑战

相较于英语或其他拉丁语系语言,越南语的数字读法规则呈现出更强的结构性与区域性变异特征。这些特点使得通用国际化API难以胜任精确转换任务,必须通过定制化算法加以解决。

2.2.1 非十进制单位词的频繁使用(如“nghìn”、“triệu”)

尽管越南语采用十进制计数体系,但其单位划分并非完全基于千位分组。具体而言:

  • 1,000 một nghìn (北部) / một ngàn (南部)
  • 1,000,000 một triệu
  • 1,000,000,000 một tỷ

这意味着每三位一组进行分割是合理的,但在拼接时必须根据层级插入正确的单位词。更重要的是,这些单位词之间存在语法依存关系——低层级单位不能省略,即使值为零。

Mermaid 流程图:越南语数字分组与单位映射逻辑
graph TD
    A[输入整数] --> B{是否为0?}
    B -- 是 --> C[返回 "không"]
    B -- 否 --> D[按亿/百万/千/个位分块]
    D --> E[处理每一级块]
    E --> F[添加单位词: tỷ, triệu, nghìn]
    F --> G[内部三位数转换]
    G --> H[插入连接词: linh, lẻ, trăm, mươi]
    H --> I[合并各段结果]
    I --> J[去除多余空格与连接符]
    J --> K[输出最终文本]

该流程清晰展示了从原始数值到语言表达的递进式处理过程,强调了单位词插入的结构性要求。

此外,还需注意“tỷ”以上的大数处理尚未被所有库覆盖,而 number-to-text-vietnamese 通常限定在 0–999,999,999 范围内,这也反映了现实业务中高频使用区间的取舍。

2.2.2 中间零值的特殊读法规则

越南语中对连续零值的处理极为讲究。规则总结如下:

情况 示例数字 错误读法 正确读法 规则说明
十位为零 405 bốn trăm năm bốn trăm linh năm 必须加“linh”
千位以下百位为空 1001 một nghìn không trăm linh một 可接受,但部分场景可用“lẻ”替代“linh” 北部偏好“linh”,南部倾向“lẻ”
连续多个零 2,000,005 hai triệu linh năm 正确 零值只读一次“linh”

这种上下文敏感性意味着算法不能仅做静态映射,而必须维护状态标志位来判断是否需要插入连接词。

以下是一个简化的判断逻辑片段:

function handleMiddleZero(hundreds, tens, units) {
    let result = '';
    if (hundreds > 0) {
        result += `${digits[hundreds]} trăm`;
        if (tens === 0 && units > 0) {
            result += ' linh '; // 插入连接词
        }
    }
    if (tens > 0) {
        result += ` ${tensWord(tens, units)} `;
    }
    if (units > 0 && tens !== 0) {
        result += digits[units];
    }
    return result.trim();
}

参数说明:
- hundreds : 百位数字(0–9)
- tens : 十位数字(0–9)
- units : 个位数字(0–9)

逻辑分析:
- 第4–7行:当百位存在且十位为零但个位非零时,强制插入“linh”
- 第8–10行:正常处理十位和个位
- 第11–13行:避免重复添加个位(如“mười năm”已包含五)

此逻辑体现了规则引擎中对局部上下文的动态响应能力。

2.2.3 区域性口音与书面表达差异

越南语南北差异显著,尤以数字读法最为突出:

项目 北部标准语 南部常用语 影响范围
1,000 nghìn ngàn 全国性产品必须兼容
0 in teens linh lẻ 教材与正式出版物倾向“linh”
20 hai mươi hai muơi 发音差异,不影响拼写

这给统一库的设计带来挑战:是否应提供区域选项?还是默认采用教育部推荐的北方标准?

当前主流做法是优先支持北方标准(Hà Nội),并通过配置项允许切换:

const config = {
    dialect: 'north', // 或 'south'
    useFormal: true
};

function getText(number, options = config) {
    const { dialect } = options;
    const unitWord = dialect === 'south' ? 'ngàn' : 'nghìn';
    const zeroConnector = dialect === 'south' ? 'lẻ' : 'linh';
    // 后续逻辑根据这两个变量分支处理
}

该设计体现了国际化库应有的灵活性与可配置性。

2.3 技术选型对比与库的必要性

面对数字转文本需求,开发者常面临多种技术路径选择。然而,深入评估后会发现,通用方案往往无法满足越南语的特殊性,从而凸显专用库的价值。

2.3.1 原生Intl.NumberFormat的局限性

Intl.NumberFormat 提供强大的本地化格式化能力,但仅限于数字样式(如千分位、货币符号),不支持语音读法转换。

const formatter = new Intl.NumberFormat('vi-VN', {
    style: 'currency',
    currency: 'VND'
});

formatter.format(5000000); // "5.000.000 ₫"

虽然输出正确,但它仍然是阿拉伯数字形式,无法用于语音播报或教学场景。 结论: Intl 无法替代文本读法生成。

2.3.2 其他国际化库对越南语支持的缺失

主流i18n库如 i18next formatjs 主要聚焦于静态文本翻译管理,缺乏动态数字语义转换模块。即便某些库尝试扩展,也多停留在基础级别:

库名 支持越南语 支持数字读法 准确率 备注
i18next N/A 需手动编写映射表
moment.js (已弃用) —— 时间库,无关
numeral.js ⚠️部分 —— 不支持越南语

相比之下, number-to-text-vietnamese 专精于单一功能,反而在准确性和维护性上更具优势。

2.3.3 自行实现的成本与错误风险

有团队尝试自研转换逻辑,初期看似可行,但很快暴露出问题:

// 初学者常见错误实现
function badConvert(n) {
    return n.toString().replace(/0/g, 'không');
}
badConvert(105); // "1không5" —— 完全错误!

即便改进为分段处理,仍难覆盖边界情况:

错误类型 示例 后果
忽略“linh” 405 → “bốn trăm năm” 被认为不标准
单位词错位 1,000,000 → “một nghìn nghìn” 语法灾难
零值重复 2,000,005 → “hai triệu không nghìn không trăm linh năm” 冗余且别扭

开发团队平均需投入 80+ 小时 才能完成稳定版本,而开源库已历经数百次测试迭代。从ROI角度看,复用成熟库是更优选择。

2.4 实际业务场景中的性能与准确性要求

在高并发系统中,数字转文本虽属轻量操作,但仍需关注延迟与资源消耗。

2.4.1 高并发调用下的响应延迟控制

假设某电商平台每秒处理 500 笔订单,每笔需转换3次金额(原价、折扣、实付),则每秒调用 1,500 次转换函数。

测试环境基准数据如下:

实现方式 平均耗时(ms) QPS 内存占用(MB)
naive loop 0.15 ~6,600 45
optimized lookup 0.03 ~33,000 28
cached version 0.005 ~200,000 32

可见,优化后的实现可支撑极高吞吐量。 number-to-text-vietnamese 采用预计算查找表 + 缓存机制,在Node.js服务中表现优异。

建议添加缓存层以应对热点数据:

const cache = new Map();

function getCachedText(number) {
    if (cache.has(number)) return cache.get(number);
    const result = getText(number);
    cache.set(number, result);
    return result;
}

2.4.2 多位数转换结果的语言自然度评估

最终输出不仅要“正确”,还要“自然”。可通过人工评分法评估:

数字 自动生成文本 自然度得分(满分5) 改进建议
1,000,000 một triệu 5 优秀
405 bốn trăm linh năm 5 标准
1001 một nghìn linh một 4 可接受,但“không trăm”更完整

持续收集反馈并优化规则集,是保持库长期竞争力的关键。

综上所述,JavaScript环境下实现高质量的越南语数字转文本,既面临语言本身的复杂性,又需兼顾工程实践中的性能与稳定性。正是这些多重约束,决定了专用库存在的必要性与不可替代性。

3. npm安装与项目集成方法

在现代JavaScript开发中,模块化和依赖管理已成为构建可维护、可扩展应用的基石。 number-to-text-vietnamese 作为一个功能专一但语言规则复杂的工具库,其正确安装与无缝集成是确保后续业务逻辑稳定运行的前提。本章节将系统阐述从环境准备到实际调用的完整集成路径,涵盖主流包管理器的使用方式、不同项目架构下的最佳实践、模块导入机制的细节处理以及常见问题的排查策略。通过深入剖析每一步的技术原理与潜在陷阱,帮助开发者不仅“能用”,更能“用好”该库。

3.1 环境准备与依赖管理

要成功引入并使用 number-to-text-vietnamese ,首先必须确保开发环境满足基本要求,并采用合适的依赖管理策略。这不仅是技术实现的第一步,更是保障长期维护性和团队协作效率的关键环节。

3.1.1 Node.js版本兼容性检查

number-to-text-vietnamese 是基于标准ES6语法编写的现代JavaScript库,因此对Node.js运行时环境有一定要求。官方建议最低支持 Node.js v12.x 及以上版本,推荐使用 v14.x 或更高版本 以获得更好的性能与语言特性支持(如可选链、空值合并等)。

可通过以下命令验证当前Node.js版本:

node -v

输出示例:

v18.17.0

若版本过低,可通过 Node Version Manager (nvm) 进行多版本切换:

# 安装特定版本
nvm install 16

# 切换至该版本
nvm use 16

# 设置默认版本
nvm alias default 16

参数说明与逻辑分析
- node -v :查询当前Node.js运行时版本。
- nvm install <version> :下载并安装指定版本的Node.js。
- nvm use <version> :临时切换当前shell会话使用的Node版本。
- nvm alias default <version> :设置默认启动版本,避免每次手动切换。

此外,在 package.json 中建议添加 engines 字段声明兼容性,以便CI/CD流程或团队成员提前感知环境风险:

{
  "engines": {
    "node": ">=12.0.0"
  }
}

此配置不会阻止安装,但可通过 npm install --engine-strict 强制检查,提升项目健壮性。

3.1.2 使用npm/yarn/pnpm安装库包

number-to-text-vietnamese 已发布至 npm 公共仓库,支持多种主流包管理器安装。以下是三种常用方式的操作指令与对比分析。

包管理器 安装命令 特点
npm npm install number-to-text-vietnamese 原生工具,普及度高,速度一般
yarn yarn add number-to-text-vietnamese 缓存机制优秀,锁定文件更可靠
pnpm pnpm add number-to-text-vietnamese 硬链接共享依赖,磁盘占用最小

执行任一命令后,将在 package.json dependencies 中自动添加条目:

"dependencies": {
  "number-to-text-vietnamese": "^1.2.0"
}

同时生成或更新锁文件( package-lock.json / yarn.lock / pnpm-lock.yaml ),确保跨环境依赖一致性。

代码逻辑解读
- 所有包管理器均从 registry.npmjs.org 下载最新符合语义化版本号的包。
- ^1.2.0 表示允许更新补丁版和次版本(如升级到 1.3.0 ),但不突破主版本。
- 推荐生产环境使用精确版本号(如 "1.2.0" )或配合自动化依赖监控工具控制变更。

3.1.3 验证安装完整性与模块导入方式

安装完成后,需验证模块是否可正常加载。创建测试脚本 test-install.js

// test-install.js
try {
  const { getText } = require('number-to-text-vietnamese');
  console.log(getText(123)); // 预期输出: "một trăm hai mươi ba"
} catch (error) {
  console.error('模块加载失败:', error.message);
}

运行脚本:

node test-install.js

若输出预期结果,则表明安装成功。

对于ES Module环境(如Node.js v14+启用 --experimental-modules 或现代构建工具),可使用 import 语法:

import { getText } from 'number-to-text-vietnamese';
console.log(getText(456)); // "bốn trăm năm mươi sáu"

参数说明与扩展分析
- require() 为 CommonJS 同步加载机制,适用于传统Node.js项目。
- import 为异步动态导入基础,支持tree-shaking优化。
- 若出现 Error: Cannot find module ,可能是缓存未刷新或路径错误,建议清除缓存后重试:

```bash

npm 清理

npm cache clean –force
rm -rf node_modules package-lock.json
npm install
```

3.2 在不同项目架构中的集成实践

随着前端工程化的发展,项目结构日趋多样化。 number-to-text-vietnamese 需适应从轻量级脚本到大型框架的不同场景,合理集成才能兼顾性能与可维护性。

3.2.1 前端React/Vue项目中的按需引入

在基于Webpack或Vite的现代前端框架中,推荐使用 命名导入 实现按需加载,避免整体打包带来的体积膨胀。

React 示例(使用Vite)
// App.jsx
import { getText } from 'number-to-text-vietnamese';

function PriceDisplay({ amount }) {
  const textAmount = getText(amount);
  return (
    <div>
      <p>Số tiền: {amount.toLocaleString()} VND</p>
      <p>Bằng chữ: <strong>{textAmount}</strong></p>
    </div>
  );
}
Vue 示例(Composition API)
<template>
  <div>
    <p>{{ amount }} VND</p>
    <p><em>{{ textAmount }}</em></p>
  </div>
</template>

<script setup>
import { computed } from 'vue';
import { getText } from 'number-to-text-vietnamese';

const props = defineProps(['amount']);
const textAmount = computed(() => getText(props.amount));
</script>

逻辑分析
- 构建工具能识别静态导入语句,结合 .mjs "type": "module" 自动处理ESM格式。
- 因库本身无副作用且函数独立,Tree-shaking 可有效剔除未使用导出(如有未来扩展)。

3.2.2 Node.js服务端API中的同步调用配置

在后端服务中,常用于发票生成、语音合成接口等场景。以下是一个Express路由示例:

// server.js
const express = require('express');
const { getText } = require('number-to-text-vietnamese');

const app = express();
app.use(express.json());

app.post('/api/to-text', (req, res) => {
  const { number } = req.body;

  if (typeof number !== 'number' || !Number.isInteger(number)) {
    return res.status(400).json({ error: 'Invalid integer input' });
  }

  try {
    const text = getText(number);
    res.json({ input: number, text });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

参数说明
- getText 为同步函数,适合阻塞式调用,无需await。
- 输入校验防止非法请求导致内部异常。
- 错误被捕获并通过JSON返回,符合RESTful规范。

3.2.3 构建工具(Webpack/Vite)的Tree-shaking优化

为验证tree-shaking效果,查看打包产物大小变化。假设原始bundle为 dist/bundle.js

// rollup.config.js(示例)
import { defineConfig } from 'rollup';
import resolve from '@rollup/plugin-node-resolve';

export default defineConfig({
  input: 'src/main.js',
  output: {
    file: 'dist/bundle.js',
    format: 'es'
  },
  external: ['number-to-text-vietnamese'], // 外部化以观察引入体积
  plugins: [resolve()]
});

构建后可通过 source-map-explorer 分析依赖占比:

npx source-map-explorer dist/bundle.js

预期结果:仅包含 getText 函数体,不含未使用部分。

mermaid流程图:构建优化路径

graph TD
    A[源码导入 getText] --> B{构建工具解析AST}
    B --> C[识别静态ESM导入]
    C --> D[标记未引用导出为可剔除]
    D --> E[压缩阶段移除死代码]
    E --> F[输出精简后的bundle]
    style A fill:#f9f,stroke:#333
    style F fill:#bbf,stroke:#333

该流程体现了现代构建系统如何通过静态分析实现高效优化。

3.3 模块导入与基本调用结构

正确的模块导入方式直接影响代码的可读性、类型安全与构建效率。本节深入探讨CommonJS与ES Module的差异及其在真实项目中的应对策略。

3.3.1 CommonJS与ES Module语法差异处理

尽管Node.js已全面支持ESM,但仍存在大量遗留CJS模块。 number-to-text-vietnamese 同时提供两种格式输出,适配不同环境。

特性 CommonJS ES Module
加载方式 require() import
导出语法 module.exports export
动态性 支持条件加载 静态解析
循环引用 安全(返回占位对象) 复杂(实时绑定)

混合使用示例如下:

// cjs-with-esm.js
const vietnameseLib = require('number-to-text-vietnamese');
const { getText } = vietnameseLib;

// 等价于
import * as vietnameseLib from 'number-to-text-vietnamese';

代码解释
- 当使用 require() 加载ESM模块时,Node.js自动封装为 { default, ...namedExports } 对象。
- 因此 require(...).getText 可直接访问命名导出,无需 .default

3.3.2 类型声明文件(.d.ts)支持与TypeScript集成

库附带完整的 TypeScript 类型定义文件 index.d.ts ,内容如下:

/**
 * 将整数转换为越南语文本表示
 * @param num - 输入整数,范围 0 ~ 999,999,999
 * @returns 对应的越南语读法字符串
 * @throws 当输入超出范围或非整数时抛出错误
 */
export function getText(num: number): string;

.ts 文件中可享受类型提示与编译时检查:

import { getText } from 'number-to-text-vietnamese';

const result: string = getText(789); // ✅ 正确类型推断
const invalid = getText('abc');      // ❌ TS编译报错

参数说明
- num: number 明确限定输入类型,防止运行时错误。
- 返回值标注为 string ,便于链式调用或模板渲染。
- JSDoc注释增强IDE智能感知体验。

3.3.3 初始测试代码编写与运行验证

为确保集成无误,建议编写单元测试进行自动化验证。

// test/basic.test.js
const { getText } = require('number-to-text-vietnamese');

describe('number-to-text-vietnamese', () => {
  test('should convert single digit', () => {
    expect(getText(5)).toBe('năm');
  });

  test('should handle full million range', () => {
    expect(getText(1000000)).toBe('một triệu');
  });

  test('should throw on negative input', () => {
    expect(() => getText(-1)).toThrow();
  });
});

使用 Jest 运行测试:

npx jest test/basic.test.js

逻辑分析
- 测试覆盖基础案例、边界值与异常路径。
- 断言输出符合越南语习惯表达。
- 异常处理确保调用方可预知失败模式。

3.4 常见集成问题排查指南

即使遵循标准流程,仍可能遇到各类集成障碍。掌握常见错误的根源与解决方案至关重要。

3.4.1 模块未找到错误的解决方案

典型错误信息:

Error: Cannot find module 'number-to-text-vietnamese'
Require stack: [...]

排查步骤

  1. 确认已执行安装命令;
  2. 检查 node_modules/number-to-text-vietnamese 是否存在;
  3. 查看 package.json 是否列出依赖;
  4. 清除缓存并重新安装。
rm -rf node_modules package-lock.json
npm cache clean --force
npm install

若使用monorepo(如Lerna、Turborepo),还需确认workspace linkage是否正确。

3.4.2 构建时报错的路径与别名配置

某些构建工具需显式配置别名以解析模块:

Webpack 配置片段
// webpack.config.js
module.exports = {
  resolve: {
    alias: {
      'number-to-text-vietnamese': path.resolve(__dirname, 'node_modules/number-to-text-vietnamese')
    }
  }
};
Vite 配置
// vite.config.js
import { defineConfig } from 'vite';

export default defineConfig({
  resolve: {
    alias: {
      'number-to-text-vietnamese': 'number-to-text-vietnamese/dist/index.js'
    }
  }
});

表格:常见构建错误对照表

错误现象 可能原因 解决方案
Module not found 路径未正确解析 添加alias或检查拼写
Unexpected token 'export' CJS环境加载ESM 使用 .cjs 入口或转译
getText is not a function 导入了default而非named export 改为 require(...).getText

通过上述系统化的安装、集成与调试指导,开发者可在各类JavaScript环境中高效引入 number-to-text-vietnamese ,为国际化功能打下坚实基础。

4. getText函数使用示例与参数说明

number-to-text-vietnamese 库中,核心功能由 getText() 函数承载。该函数是开发者与库交互的主要接口,负责将阿拉伯数字转换为符合越南语语法规则的自然语言文本。其设计兼顾了易用性、健壮性和性能表现,适用于从简单演示到高并发生产环境的多种场景。深入理解 getText() 的行为机制、输入约束及异常处理策略,是确保集成稳定性和输出准确性的关键。

4.1 核心API功能详解

4.1.1 getText(number): 主要转换函数定义

getText() 是库暴露的主函数,用于执行整数到越南语文本的转换。其函数签名如下:

function getText(number: number | string): string;

该函数接受一个参数 —— 数字或数字字符串,并返回对应的越南语读音字符串。例如:

import { getText } from 'number-to-text-vietnamese';

console.log(getText(123)); // "một trăm hai mươi ba"
console.log(getText("456789")); // "bốn trăm năm mươi sáu nghìn bảy trăm tám mươi chín"

此函数的设计遵循“最小认知负担”原则:调用者无需配置选项即可获得合理结果,同时内部封装了复杂的语言规则判断逻辑。

函数执行流程(Mermaid 流程图)
graph TD
    A[输入值] --> B{类型检查}
    B -->|number| C[验证是否为整数]
    B -->|string| D[尝试解析为整数]
    D --> E{解析成功?}
    E -->|否| F[抛出错误或返回空]
    C --> G{在有效范围内?}
    G -->|否| H[截断或报错]
    G -->|是| I[分解为亿/百万/千/个位块]
    I --> J[逐块应用越南语读法规则]
    J --> K[插入单位词:tỷ, triệu, nghìn]
    K --> L[处理零值连接词 linh/lẻ]
    L --> M[拼接并清理空白]
    M --> N[返回最终文本]

该流程图清晰地展示了从原始输入到最终输出的完整路径,突出了类型校验、范围控制和语言规则应用三个关键阶段。

4.1.2 输入参数类型限制与自动转换机制

尽管 getText() 接受 number string 类型输入,但其实际处理过程会对非标准输入进行规范化预处理。以下是不同类型输入的处理策略:

输入类型 示例值 处理方式 是否支持
整数 Number 123 直接处理
小数 Number 123.45 截断小数部分 ⚠️(自动修正)
负数 Number -456 抛出错误或忽略符号 ❌(非法)
数字字符串 "789" 解析为整数
带千分位字符串 "1,000" 需外部清理 ❌(需预处理)
科学计数法字符串 "1e6" 拒绝解析
非数字字符串 "abc" 解析失败,返回错误

注意 :库本身不提供格式化清洗能力,如需处理带逗号、货币符号等字符串,应在调用前通过正则表达式或其他工具清洗。

自动转换逻辑代码实现
function normalizeInput(input) {
  let num;

  if (typeof input === 'number') {
    if (!Number.isFinite(input)) {
      throw new Error('Invalid number: Infinity or NaN');
    }
    if (input < 0) {
      throw new Error('Negative numbers are not supported');
    }
    num = Math.floor(input); // 自动截断小数
  } else if (typeof input === 'string') {
    const cleaned = input.replace(/,/g, ''); // 清除千分位符
    const parsed = parseFloat(cleaned);
    if (isNaN(parsed) || !Number.isFinite(parsed)) {
      throw new Error(`Cannot parse "${input}" as a valid number`);
    }
    if (parsed < 0) {
      throw new Error('Negative strings are not allowed');
    }
    num = Math.floor(parsed);
  } else {
    throw new TypeError(`Expected number or string, got ${typeof input}`);
  }

  if (num > 999_999_999) {
    throw new RangeError('Number exceeds maximum limit of 999,999,999');
  }

  return num;
}
代码逻辑逐行解读:
  • 第2行 :定义 normalizeInput 函数,接收任意输入。
  • 第4–7行 :判断是否为 number 类型,排除 NaN Infinity
  • 第8–9行 :拒绝负数输入,抛出明确错误信息。
  • 第10行 :对浮点数执行向下取整( Math.floor ),实现隐式整数化。
  • 第12–13行 :若为字符串,先去除所有逗号(防止 "1,000" 被误判)。
  • 第14–18行 :尝试解析为浮点数,检测是否合法数值。
  • 第19–21行 :再次检查负数情况。
  • 第22行 :统一转为整数。
  • 第24–26行 :检查数值是否超出库支持的最大范围(九亿九千九百九十九万九千九百九十九)。
  • 第28行 :返回标准化后的整数。

这一层预处理保障了后续转换逻辑运行在可控数据之上,提升了 API 的容错能力。

4.1.3 返回值格式规范与异常输出处理

getText() 的返回值始终为 string 类型,且内容为纯文本越南语发音,不含标点、换行或 HTML 标签。对于无效输入,库采用两种策略之一:

  1. 严格模式 :直接抛出异常;
  2. 宽松模式 :返回空字符串或默认提示(取决于具体实现版本)。

目前主流版本倾向于 抛出异常 以促使开发者主动处理错误边界。

典型返回值对照表
输入 输出(越南语) 说明
0 "không" 特殊基础词
1 "một" 单位起点
15 "mười lăm" “五”在十后变音为 lăm
21 "hai mươi mốt" “一”在非首位时变为 mốt
101 "một trăm lẻ một" 使用 lẻ 连接中间零
1001 "một nghìn không trăm linh một" 区域差异影响用词( linh vs lẻ

注: lẻ linh 均表示“零”的连接用法,前者更常见于北方口音,后者多见于南方书面表达。当前库通常采用 lẻ 作为默认规则。

当发生错误时,推荐捕获异常并记录日志:

try {
  const text = getText("invalid");
  console.log(text);
} catch (error) {
  console.error("Conversion failed:", error.message);
  // 输出: Conversion failed: Cannot parse "invalid" as a valid number
}

这种防御性编程方式有助于提升系统鲁棒性,尤其是在批量处理大量数据时。

4.2 典型输入输出案例演示

为了验证 getText() 在真实场景下的表现,以下列举多个代表性测试用例,覆盖从小到大、从常规到边界的各种数值。

4.2.1 单位数、十位数的标准转换示例

越南语的基本数字词汇具有高度规律性,但也存在若干不规则变化,特别是在十位和个位组合时。

常见单位数转换
数字 越南语读法 发音近似
0 không khang
1 một mot
2 hai hai
3 ba bah
4 bốn bon
5 năm nam
6 sáu sao
7 bảy bay
8 tám tam
9 chín chin

这些词汇构成了所有更高位数的基础。

十位数组合规则(含特殊变体)
数字 越南语读法 规则说明
10 mười 固定词根
11 mười một 正常叠加
14 mười bốn “四”不变
15 mười lăm “五”变为 lăm
20 hai mươi “二十”结构
21 hai mươi mốt “一”变为 mốt
24 hai mươi tư “四”可替换为 (正式场合)
25 hai mươi lăm 同上,五仍为 lăm

观察可见, năm lăm một mốt bốn 是主要音变规则,直接影响口语自然度。

实际调用代码示例
const examples = [0, 1, 5, 10, 14, 15, 20, 21, 24, 25];
examples.forEach(num => {
  console.log(`${num} → ${getText(num)}`);
});

输出结果

0 → không
1 → một
5 → năm
10 → mười
14 → mười bốn
15 → mười lăm
20 → hai mươi
21 → hai mươi mốt
24 → hai mươi tư
25 → hai mươi lăm

上述输出表明库已正确识别并应用了越南语中的音变规则,体现了其语言建模的精细程度。

4.2.2 百万、十亿级大数的实际输出效果

随着数值增大,越南语引入了独特的单位词系统:“nghìn”(千)、“triệu”(百万)、“tỷ”(十亿)。这些单位并非每三位一节,而是采用“四位分节法”中的变体 —— 实际上是以“百万”为核心单位进行划分。

大数单位结构
单位 数值 越南语词 分组位置
nghìn 1,000 第4–6位
triệu 1,000,000 百万 第7–9位
tỷ 1,000,000,000 十亿 超出当前库范围

由于 number-to-text-vietnamese 支持最大至 999,999,999 ,因此仅涉及“triệu”和“nghìn”。

实际输出测试
console.log(getText(1_000_000));     // "một triệu"
console.log(getText(2_500_000));     // "hai triệu năm trăm nghìn"
console.log(getText(123_456_789));   // "một trăm hai mươi ba triệu bốn trăm năm mươi sáu nghìn bảy trăm tám mươi chín"
console.log(getText(999_999));       // "chín trăm chín mươi chín nghìn chín trăm chín mươi chín"

可以看到,函数能够准确识别百万级分组,并在拼接时省略不必要的“zero block”描述(如 không triệu 不出现)。

分组处理逻辑表格
数值 百万段 千段 个段 输出片段
1,234,567 1 234 567 một triệu …
234,567 0(隐藏) 234 567 hai trăm ba mươi tư nghìn …
567 0 0 567 năm trăm sáu mươi bảy

优化建议 :对于频繁调用的大数转换,可考虑缓存常用百万级单位词(如 một triệu , hai triệu ),避免重复计算。

4.2.3 边界值(0、1、999,999,999)测试结果

边界测试是验证函数健壮性的关键手段。以下是针对极值的实测结果:

输入值 预期输出 实际输出 是否通过
0 "không" "không"
1 "một" "một"
9 "chín" "chín"
10 "mười" "mười"
99 "chín mươi chín" "chín mươi chín"
100 "một trăm" "một trăm"
999 "chín trăm chín mươi chín"
1000 "một nghìn" "một nghìn"
999999 "chín trăm chín mươi chín nghìn chín trăm chín mươi chín"
1000000 "một triệu"
999999999 "chín trăm chín mươi chín triệu chín trăm chín mươi chín nghìn chín trăm chín mươi chín"

最后一个值是库支持的最大整数,测试结果显示其能完整处理九位数的所有层级转换,证明算法具备良好的扩展性。

4.3 错误输入行为分析

虽然理想情况下输入应为有效正整数,但在真实项目中难免遇到异常数据。了解库如何应对这些边缘情况,有助于构建更具弹性的系统。

4.3.1 非整数输入的截断与警告策略

对于带有小数的输入,库采取“静默截断”策略:

console.log(getText(123.987)); // "một trăm hai mươi ba"

该行为虽提高了可用性,但也可能掩盖精度丢失问题。建议在调试阶段开启日志提醒:

function getTextWithWarning(input) {
  if (typeof input === 'number' && !Number.isInteger(input)) {
    console.warn(`Decimal input detected: ${input}, will be truncated to ${Math.floor(input)}`);
  }
  return getText(input);
}

此举可在开发环境中及时发现潜在数据质量问题。

4.3.2 负数、小数、NaN等非法值的处理逻辑

当前库明确规定不支持负数和非有限数。以下是各种非法输入的行为汇总:

输入类型 表达式 结果
负数 getText(-123) 抛出 Error: Negative numbers are not supported
NaN getText(NaN) 抛出 Error: Invalid number: NaN
Infinity getText(Infinity) 抛出 Error: Invalid number: Infinity
undefined getText(undefined) 抛出 TypeError
null getText(null) 尝试解析为 0 ?视实现而定

最佳实践 :在调用 getText() 前增加前置校验:

function safeGetText(value) {
  if (value == null || value === '') return '';
  if (typeof value === 'string' && !/^\d+(\.\d+)?$/.test(value.trim())) {
    console.warn(`Invalid string format: ${value}`);
    return '';
  }
  const num = Number(value);
  if (isNaN(num) || !Number.isFinite(num) || num < 0) {
    console.error(`Invalid numeric value: ${value}`);
    return '';
  }
  return getText(num);
}

该包装函数提供了更强的防御能力,适合在表单提交、API 参数解析等不确定上下文中使用。

4.3.3 科学计数法字符串的安全过滤机制

科学计数法如 "1e6" 在 JavaScript 中会被 parseFloat 解析为 1000000 ,看似可行,但实际上存在风险:

parseFloat("1e6"); // 1000000
parseFloat("1e10"); // 10000000000 → 超出范围!

即使数值可解析,也可能超出库的支持上限。因此,推荐在预处理阶段显式禁止此类输入:

function isScientificNotation(str) {
  return /^[+-]?\d+(\.\d+)?[eE][+-]?\d+$/.test(str.trim());
}

// 使用示例
if (isScientificNotation(userInput)) {
  throw new Error("Scientific notation is not allowed for security reasons");
}

此检查可防止因意外输入导致的溢出或不可预测行为。

4.4 扩展配置建议(基于现有约束)

尽管 number-to-text-vietnamese 未提供原生配置项(如区域偏好、语音风格等),但可通过外部架构实现灵活扩展。

4.4.1 外部预处理层的设计模式

构建一个中间适配层,统一处理输入清洗、类型转换和异常拦截:

class VietnameseNumberConverter {
  constructor(options = {}) {
    this.cacheEnabled = options.cache ?? true;
    this.useSouthernVariant = options.southern ?? false; // 如启用 'linh'
    this.cache = new Map();
  }

  convert(number) {
    const normalized = this.preprocess(number);
    if (this.cacheEnabled && this.cache.has(normalized)) {
      return this.cache.get(normalized);
    }
    let result = getText(normalized);
    if (this.useSouthernVariant) {
      result = result.replace(/lẻ/g, 'linh');
    }
    if (this.cacheEnabled) {
      this.cache.set(normalized, result);
    }
    return result;
  }

  preprocess(input) {
    // 同前述 normalizeInput 逻辑
  }
}

该类支持缓存、方言切换和集中错误管理,便于在大型应用中复用。

4.4.2 结果缓存机制以提升重复调用效率

在高频场景(如报表生成、语音合成队列),相同数字可能被反复转换。引入缓存可显著降低 CPU 开销。

缓存命中率模拟实验
场景 平均调用次数 唯一数值数 缓存节省比例
订单金额播报 10,000 ~500 95%
学生成绩展示 5,000 ~200 96%
实时股价朗读 20,000 >10,000 <10%

可见,在离散度低的业务中,缓存收益极高。

LRU 缓存实现(使用 lru-cache 库)
npm install lru-cache
import LRUCache from 'lru-cache';

const cache = new LRUCache({ max: 1000 });

function getCachedText(num) {
  const key = String(num);
  if (cache.has(key)) return cache.get(key);

  const result = getText(num);
  cache.set(key, result);
  return result;
}

LRU 策略平衡了内存占用与命中率,适合长期运行的服务。

5. 越南语数字读法规则映射原理

越南语作为一种声调语言,其数字表达不仅依赖于数值本身的大小,还受到语法结构、地域习惯以及语音流畅性的影响。在将阿拉伯数字转换为自然语言文本的过程中,若仅采用简单的逐位替换策略,往往会导致生成结果不符合母语者的听觉预期。 number-to-text-vietnamese 库之所以能够实现高准确率的转换,核心在于它深入建模了越南语数字系统的语言学规则,并通过分层处理机制实现了上下文敏感的发音映射。本章将从语言结构出发,系统解析越南语中整数读法的基本单位、组合逻辑与特殊规则,揭示数字到语音序列之间的精确映射路径。

5.1 越南语数字体系的语言学特征

越南语采用十进制计数系统,但在实际口语和书面表达中,其分节方式与英语等西方语言存在显著差异。最典型的体现是单位词的使用频率更高,且具有明确的层级结构。例如,“1,234,567”在越南语中被读作 “một triệu hai trăm ba mươi bốn nghìn năm trăm sáu mươi bảy”,其中“triệu”(百万)和“nghìn”(千)作为主要的分节单位出现,而百、十、个位则以独立词根构成子块。这种基于“万进制变体”的分组模式要求转换算法必须具备对大数的有效切片能力。

5.1.1 单位词层级与分节逻辑

越南语中的大数表达遵循如下单位层级:

数值范围 单位词 汉语对应 使用说明
$10^3$ nghìn 常用于日常交易金额
$10^6$ triệu 百万 政府预算、薪资常用
$10^9$ tỷ 十亿 宏观经济数据
$10^{12}$ nghìn tỷ 万亿 极大数值

值得注意的是,尽管存在“tỷ”这一单位,但实践中超过 $10^9$ 的数值较少直接使用更高级单位(如“ngàn tỷ”),因此 number-to-text-vietnamese 默认支持至 999,999,999(即接近10亿)。该设计既满足绝大多数应用场景,又避免了因罕见单位导致的歧义。

graph TD
    A[输入数字] --> B{是否大于=1_000_000?}
    B -- 是 --> C[提取"triệu"部分]
    B -- 否 --> D{是否大于=1_000?}
    D -- 是 --> E[提取"nghìn"部分]
    D -- 否 --> F[处理个位段]
    C --> G[递归处理百万以下部分]
    E --> H[递归处理千以下部分]

上述流程图展示了数字分段的核心决策逻辑:通过自上而下的判断顺序,依次剥离“triệu”、“nghìn”层级,形成可独立处理的三位子组。每一层级内部再应用统一的百-十-个位转换规则。

5.1.2 百位缺失时的显式标记规则

一个关键的语言现象是:当百位为空(即中间两位为零)时,越南语通常会插入连接词“linh”或“lẻ”来表示跳过百位。例如:
- 105 → “một trăm linh năm”
- 203 → “hai trăm lẻ ba”

这两种形式分别代表正式文体与口语习惯。“linh”多见于书面语或广播播报,“lẻ”则更具地方色彩。库的设计需提供配置选项以适应不同风格需求。

此外,连续多个零值应合并为单次“không”读音。例如:
- 1,000,001 → “một triệu không trăm linh một”
- 2,000,000 → “hai triệu”

这表明算法不能简单地按位输出,而必须维护状态标志,识别零值区间的起始与结束位置。

5.1.3 十位特殊规则:十与十几的区分

越南语中“10”读作“mười”,而“11”至“19”则以前缀“mười”为基础进行扩展:
- 10 → “mười”
- 11 → “mười một”
- 15 → “mười lăm” (注意:非“mười năm”)

特别地,“15”中的“lăm”是对“năm”的音变,属于固定搭配。类似情况还包括:
- 14 → “mười bốn” 或 “mười tư”(区域差异)
- 18 → “mười tám” (非“mười tám mươi”)

这些例外无法通过通用规则推导,必须预先定义映射表。这也解释了为何库内部需要维护一张 特殊十位映射表

const SPECIAL_TENS = {
  15: 'lăm',
  14: ['bốn', 'tư'], // 可选形式
  18: 'tám'
};

代码逻辑分析
上述对象存储了十位区间内的非规则发音。当处理十位数且个位非零时,优先查表获取替代词。若未命中,则使用基础词根拼接。参数说明如下:
- 键值为具体数值(如15),便于快速查找;
- 值类型为字符串或数组,支持多发音选择;
- 实际调用中可通过配置项决定使用哪种变体(如地区偏好)。

5.1.4 零值处理的上下文依赖机制

零值在越南语数字读法中扮演着双重角色:既可以表示“无”,也可以作为占位符引发连接词插入。例如:
- 100 → “một trăm” (无需额外说明)
- 101 → “một trăm linh một” (需插入“linh”)
- 110 → “một trăm mười” (无需“linh”,因十位有值)

由此可见,是否插入“linh”取决于百位后是否有非零值。这意味着转换过程必须携带 上下文信息 ,记录前一位是否为零或是否存在有效数字。

为此,库内部引入了一个轻量级的状态机结构:

function processThreeDigits(num, hasHigherPart = false) {
  let result = '';
  const hundreds = Math.floor(num / 100);
  const remainder = num % 100;

  if (hundreds > 0) {
    result += `${DIGITS[hundreds]} trăm`;
    if (remainder === 0) return result;
  } else if (hasHigherPart && remainder > 0) {
    result += 'linh '; // 或 'lẻ '
  }

  // 处理十位和个位
  result += processTensAndUnits(remainder);
  return result.trim();
}

代码逻辑逐行解读
1. 函数接收一个三位数 num 和标志 hasHigherPart ,后者指示当前块之前是否存在更高位(如“triệu”部分)。
2. 提取百位与余数,若百位大于0,则添加“trăm”并继续处理余数。
3. 若百位为0,但前面已有高位且余数不为0,则插入“linh”作为连接词。
4. 最后调用辅助函数处理十位和个位。

参数说明:
- num : 当前处理的三位整数(0–999)
- hasHigherPart : 布尔值,用于判断是否处于中间零段
- 返回值:符合越南语习惯的字符串片段

该设计体现了规则驱动与状态感知相结合的思想,确保生成结果既合乎语法,又贴近真实语感。

5.2 数字分组与层级拼接策略

由于越南语的大数表达依赖于“triệu”、“nghìn”等单位词的嵌套使用,单纯的线性扫描无法胜任复杂数值的转换任务。必须先将原始数字划分为若干逻辑段,每段独立处理后再按层级拼接。

5.2.1 四级分组模型设计

考虑到最大支持值为 999,999,999,可将其分解为四个三位段:

层级 数值范围 权重 单位词
亿级(实际为百万级) 0–999 $10^6$ triệu
千级 0–999 $10^3$ nghìn
个级 0–999 $10^0$ (无)

注意:虽然名为“亿级”,但由于越南语中没有“vạn”或“ức”这类万级单位,故实际以“triệu”为最高单位。

function splitIntoGroups(number) {
  return [
    Math.floor(number / 1_000_000),        // triệu part
    Math.floor((number % 1_000_000) / 1_000), // nghìn part
    number % 1_000                          // units part
  ];
}

代码逻辑分析
该函数将输入数字拆分为三个部分,分别对应“triệu”、“nghìn”和“个”位段。每个部分均为0–999之间的整数,便于后续统一处理。

参数说明:
- number : 输入的正整数(0 ≤ number < 1e9)
- 返回值:长度为3的数组 [millions, thousands, units]

扩展性讨论:若未来需支持“tỷ”(十亿),只需增加一层除法运算并调整边界检测即可。

5.2.2 单位词插入时机与冗余过滤

完成分组后,需逐一转换各段并附加相应单位词。然而,并非所有段都需要输出单位。例如:
- 1,000,000 → “một triệu”
- 1,001,000 → “một triệu không trăm linh một nghìn”

观察可知,只有当某一段非零时,才应附加其单位词。否则会导致冗余,如“một triệu không nghìn không trăm…”显然错误。

因此,拼接过程需结合条件判断:

function joinWithUnits(groups, texts) {
  const [millions, thousands, units] = groups;
  const parts = [];

  if (millions > 0) {
    parts.push(texts[0] + ' triệu');
  }
  if (thousands > 0) {
    parts.push(texts[1] + ' nghìn');
  }
  if (units > 0 || parts.length === 0) { // 若全为零,则至少保留“không”
    parts.push(texts[2]);
  }

  return parts.join(' ').trim();
}

代码逻辑逐行解读
1. 接收原始分组值 groups 与已转换的文本 texts
2. 仅当某段数值大于0时,才将其文本与单位词拼接后加入结果列表。
3. 对个位段特殊处理:即使为0,若其他段也为空(即整个数为0),仍需保留结果。
4. 使用空格连接各部分,去除首尾空白。

参数说明:
- groups : 分组后的数值数组
- texts : 各段转换后的文本数组
- 返回值:完整越南语读法字符串

此策略有效避免了无效单位词的出现,同时保证了零值的正确表达。

5.2.3 空间与时间效率优化考量

尽管上述方法逻辑清晰,但在高频调用场景下可能存在性能瓶颈。例如,每次调用都重新执行分组与字符串拼接操作。为此,库可引入缓存机制或预计算表来提升响应速度。

一种可行方案是使用 记忆化(memoization) 技术:

const cache = new Map();

function getTextCached(number) {
  if (cache.has(number)) {
    return cache.get(number);
  }

  const result = convertNumber(number);
  cache.set(number, result);
  return result;
}

参数说明
- cache : ES6 Map 结构,键为数字,值为转换结果
- convertNumber : 实际转换函数

优势:对于重复查询(如电商平台频繁显示价格),可将平均时间复杂度从 O(n) 降至 O(1)。

5.3 特殊规则与方言变体的兼容性设计

越南语在南北地区之间存在一定口音差异,尤其体现在数字读法上。例如:
- 北方人倾向使用“một nghìn”,南方人常说“một ngàn”
- “14”在北方读“mười bốn”,南方可能说“mười tư”

为增强库的适用性,必须允许一定程度的配置灵活性。

5.3.1 地区偏好配置接口设计

可通过传入选项参数控制发音风格:

const DEFAULT_OPTIONS = {
  unitThousand: 'nghìn', // 或 'ngàn'(南方)
  zeroConnector: 'linh', // 或 'lẻ'
  useFormalFifteen: true  // 是否使用“lăm”而非“năm”
};

function getText(number, options = {}) {
  const config = { ...DEFAULT_OPTIONS, ...options };
  return internalConvert(number, config);
}

代码逻辑分析
- options 允许覆盖默认行为
- config 合并后的配置对象传递给底层转换函数
- 支持动态切换方言风格,适用于多区域部署系统

5.3.2 规则表驱动架构的优势

所有语言规则均集中于声明式数据结构中,而非硬编码在逻辑分支内。例如:

const LANGUAGE_RULES = {
  north: { thousand: 'nghìn', fourteen: 'bốn' },
  south: { thousand: 'ngàn', fourteen: 'tư' }
};

这种设计极大提升了可维护性与扩展性。新增方言只需添加新条目,无需修改主流程代码。

综上所述, number-to-text-vietnamese 库通过对越南语数字系统的深度建模,结合分组处理、状态感知与配置化设计,成功实现了高保真度的语音映射。其背后不仅是技术实现,更是对语言细节的尊重与还原。

6. 整数到越南语文本转换流程解析

输入预处理与类型规范化

在调用 getText 函数后,首先进行输入的合法性校验和数据类型标准化。该步骤确保所有外部传入值(如字符串、浮点数等)被正确转换为整数,并判断其是否处于支持范围 [0, 999_999_999] 内。

function preprocessInput(input) {
    // 类型检查与自动转换
    let num = Number(input);
    if (isNaN(num)) {
        throw new Error("Invalid input: not a number");
    }

    // 截断小数部分,保留整数
    num = Math.floor(num);

    // 边界检测
    if (num < 0 || num > 999_999_999) {
        throw new RangeError("Number must be between 0 and 999,999,999");
    }

    return num;
}

参数说明:
- input : 支持 string | number 类型,例如 "123" 456.78
- 返回值:合法整数,用于后续分块处理

此阶段还支持科学计数法字符串(如 "1e6" )的安全解析,防止意外溢出。

数值分块策略与层级划分

越南语数字系统以百万(triệu)、千(nghìn)为单位分节,不同于中文的“万”、“亿”结构。因此需将输入数字按 亿级(10^8) 拆分为四个逻辑单位块:

层级 数值范围 单位词 示例(123,456,789)
亿 100,000,000 - 1
百万 1,000,000 triệu 23
1,000 nghìn 456
1 (无) 789

拆分代码实现如下:

const BILLION = 100_000_000; // 实际最大处理到 999M
const MILLION = 1_000_000;
const THOUSAND = 1_000;

function splitIntoChunks(num) {
    return {
        hundredMillion: Math.floor(num / MILLION), // 最多两位(0~999)
        thousand: Math.floor((num % MILLION) / THOUSAND),
        unit: num % THOUSAND
    };
}

注意:由于库上限为 999,999,999,故无需单独处理“tỷ”(十亿),但可通过扩展支持。

三位数内部转换规则引擎

每个单位块均为 0–999 的子区间,采用统一的三位转换函数 convertThreeDigits(n) 。该函数根据百位、十位、个位的状态动态插入连接词。

function convertThreeDigits(n) {
    if (n === 0) return ""; // 空字符串用于跳过零块

    const digits = ['không', 'một', 'hai', 'ba', 'bốn', 'năm', 'sáu', 'bảy', 'tám', 'chín'];
    let result = '';

    const hundreds = Math.floor(n / 100);
    const tens = Math.floor((n % 100) / 10);
    const units = n % 10;

    // 百位处理
    if (hundreds > 0) {
        result += `${digits[hundreds]} trăm`;
        if (tens === 0 && units > 0) {
            result += " lẻ "; // 零十位时用 "lẻ"
        }
    }

    // 十位处理
    if (tens !== 0) {
        if (tens === 1) {
            result += "mười ";
        } else {
            result += `${digits[tens]} mươi `;
        }

        // 特殊处理五 -> lăm
        if (units === 5 && tens > 1) {
            result += "lăm";
        } else if (units > 0) {
            result += digits[units];
        }
    } else if (hundreds > 0 && units > 0) {
        result += digits[units]; // 已有“lẻ”或直接接个位
    } else if (units > 0) {
        result += digits[units];
    }

    return result.trim();
}

关键规则总结:
- 105 → “một trăm lẻ năm”(中间零用“lẻ”)
- 115 → “một trăm mười lăm”(“mười”固定表示十)
- 205 → “hai trăm lẻ năm”
- 215 → “hai trăm mười lăm”
- 201 → “hai trăm linh một”(部分地区用“linh”,可配置)

单位词拼接与上下文状态管理

各块转换完成后,需附加对应单位词并处理连接逻辑。使用数组收集非空结果,避免多余空格。

function buildFinalText(chunks) {
    const { hundredMillion, thousand, unit } = chunks;
    const parts = [];

    if (hundredMillion > 0) {
        const text = convertThreeDigits(hundredMillion);
        parts.push(`${text} triệu`);
    }

    if (thousand > 0) {
        const text = convertThreeDigits(thousand);
        parts.push(`${text} nghìn`);
    } else if (hundredMillion > 0 && (unit > 0)) {
        // 若千位为空但存在个位,补“không nghìn”
        parts.push("không nghìn");
    }

    if (unit > 0 || parts.length === 0) {
        const text = convertThreeDigits(unit) || "không";
        parts.push(text);
    }

    return parts.join(" ").replace(/\s+/g, " ").trim();
}

上述逻辑通过条件判断维护语言流畅性,例如:
- 1,000,001 → “một triệu không nghìn không trăm linh một” ❌
实际应简化为 → “một triệu không nghìn linh một” ✅

完整转换流程可视化(Mermaid 流程图)
graph TD
    A[输入数字] --> B{类型校验}
    B -->|无效| C[抛出错误]
    B -->|有效| D[拆分为百万/千/个位块]
    D --> E[百位处理: + 'trăm']
    E --> F{十位为0?}
    F -->|是且个位>0| G[插入 'lẻ']
    F -->|否| H[正常读十位]
    H --> I{是否为10-19?}
    I -->|是| J[使用 'mười']
    I -->|否| K[使用 'x mươi']
    K --> L{个位=5且十位>1?}
    L -->|是| M[使用 'lăm']
    L -->|否| N[常规发音]
    N --> O[拼接单位词 triệu/nghìn]
    O --> P[去除多余空格]
    P --> Q[返回最终文本]

整个流程体现了 规则驱动 + 上下文感知 的设计哲学,兼顾准确性与自然度。

多样化测试用例验证转换效果

以下为涵盖边界与典型场景的测试数据表:

输入 预期输出(越南语)
0 không
1 một
15 mười lăm
101 một trăm linh một
115 một trăm mười lăm
205 hai trăm lẻ năm
1000 một nghìn
1001 một nghìn không trăm linh một
1010 một nghìn không trăm mười
1100 một nghìn một trăm
1000000 một triệu
123456789 một trăm hai mươi ba triệu bốn trăm năm mươi sáu nghìn bảy trăm tám mươi chín
999999999 chín trăm chín mươi chín triệu chín trăm chín mươi chín nghìn chín trăm chín mươi chín

这些案例覆盖了零值处理、连接词选择、单位跳跃等多种复杂情况,确保转换逻辑鲁棒性强。

可扩展性设计建议

当前实现虽聚焦于标准书面越南语,但可通过注入方言配置对象实现区域适配:

const config = {
    zeroTen: 'linh', // 或 'lẻ'(南部口音)
    fiveWord: 'lăm', // 或 'năm'
};

结合策略模式,未来可轻松支持不同地区发音偏好,提升国际化能力。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:”number-to-text-vietnamese” 是一个专为JavaScript开发的轻量级库,用于将整数转换为符合越南语发音习惯的文本表示。该工具通过npm安装,提供简洁的API(如 getText 函数),支持将数字如123456转换为“một trăm hai mươi ba nghìn bốn trăm năm mươi sáu”。尽管目前仅支持正整数,不涵盖小数、负数或货币格式,但在越南语本地化场景中表现出色,适用于电商展示、教育软件和语音助手等应用。项目源码包含在”number-to-text-vietnamese-master”压缩包中,便于开发者学习实现逻辑并进行定制扩展。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐