A JavaScript/TypeScript library for spelling out numbers in Vietnamese. Lightweight and compatible with all modern browsers and older browsers like IE11.
- Demo: https://npm-spell-vn-number.vincentchu.work/
- Demo2: github-pages-demo.html
- Repository: https://github.com/vincentchu161/spell-vn-number
- Java Version: https://github.com/vincentchu161/spell-vn-number-java
npm install spell-vn-number
The library supports multiple module formats:
-
ESM (ECMAScript Modules): Default import/export syntax
import { spell } from 'spell-vn-number';
-
CommonJS (CJS): Node.js require syntax
const { spell } = require('spell-vn-number');
-
UMD (Universal Module Definition): Works in both browser and Node.js environments
<!-- From unpkg --> <script src="https://unpkg.com/spell-vn-number"></script> <!-- From jsDelivr --> <script src="https://cdn.jsdelivr.net/npm/spell-vn-number"></script> <script> // Available as global spellVnNumber console.log(spellVnNumber.spell('123')); </script>
-
TypeScript: Full TypeScript support with type definitions included
import { spell } from 'spell-vn-number';
console.log(spell('123456'));
// "Một trăm hai mươi ba nghìn bốn trăm năm mươi sáu"
console.log(spell('1,234.56'));
// "Một nghìn hai trăm ba mươi tư chấm năm mươi sáu"
console.log(spell('-1000000'));
// "Âm một triệu"
You can use the library directly in the browser via CDN:
<!-- From unpkg -->
<script src="https://unpkg.com/spell-vn-number"></script>
<!-- From jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/spell-vn-number"></script>
<script>
// Use the global spellVnNumber object
console.log(spellVnNumber.spell('123456'));
// "Một trăm hai mươi ba nghìn bốn trăm năm mươi sáu"
console.log(spellVnNumber.spell('9,007,199,254,740,992'))
// "Chín triệu không trăm lẻ bảy nghìn một trăm chín mươi chín tỷ hai trăm năm mươi tư triệu bảy trăm bốn mươi nghìn chín trăm chín mươi hai"
</script>
You can customize the spelling using the SpellerConfig
class and the spellVnNumber
function:
import { spellVnNumber, spellOrDefault, SpellerConfig } from 'spell-vn-number';
// 1.Method: spellVnNumber
const customConfig = new SpellerConfig({
separator: '-', // Change word separator // default: ' '
pointText: 'phẩy', // Change decimal point text // default: chấm
negativeText: 'âm', // Change negative text
keepOneZeroWhenAllZeros: true, // Keep one zero when all decimal digits are zeros
capitalizeInitial: true, // Capitalize the first letter of the spelled number (default: true)
specificText: {
oddText: 'lẻ', // Text for odd numbers
tenText: 'mười', // Text for ten
oneToneText: 'mốt', // Special text for digit 1
fourToneText: 'tư', // Special text for digit 4
fiveToneText: 'lăm' // Special text for digit 5
},
digitNames: {
'4': 'tư', // Custom name for digit 4 // default: bốn
'5': 'lăm' // Custom name for digit 5 // default: năm
},
unitNames: {
0: 'tỉ', // Custom name for billion // default: tỷ
2: 'ngàn' // Custom name for thousand // default: nghìn
}
});
console.log(spellVnNumber(customConfig, '123.45'));
// "Một-trăm-hai-mươi-ba-phẩy-tư-mươi-lăm" // default: "Một-trăm-hai-mươi-ba-phẩy-bốn-mươi-lăm"
console.log(spellVnNumber(customConfig, '123.4500'));
// "Một-trăm-hai-mươi-ba-phẩy-tư-mươi-lăm" // default: "Một-trăm-hai-mươi-ba-phẩy-bốn-mươi-lăm"
console.log(spellVnNumber(customConfig, '123.000'));
// "Một-trăm-hai-mươi-ba-phẩy-không"
// 2.Method: spellOrDefault
const input = '123456';
const config = { separator: ' ', pointText: 'phẩy' };
const defaultText = 'Invalid number';
console.log(spellOrDefault(input, config, defaultText));
// Output: "Một trăm hai mươi ba nghìn bốn trăm năm mươi sáu"
console.log(spellOrDefault('abc', config, defaultText));
// Output: "Invalid number"
// 3.Advanced Configuration Examples
const yourConfig1 = new SpellerConfig({separator: ' '});
const yourConfig2 = new SpellerConfig({separator: '-', pointText: 'phẩy', currencyUnit: 'đồng'});
/**
* Convenience function to spell a Vietnamese number with default value on error
* @param input Number to spell
* @param config Partial<SpellerConfig> to override default configuration
* @returns Vietnamese spelling of the number or defaultOnError if an error occurs
* @throws InvalidFormatError when input format is invalid (empty, null, undefined, a finite number, ...)
* @throws InvalidNumberError when number is invalid
* @throws Error for other unexpected errors
*/
export function spellNumber(input: InputNumber, config: SpellerConfig): string {
try {
return spellVnNumber(config, input);
} catch (err) {
// your handle error...
if (err instanceof InvalidFormatError) {
console.warn(err.name, err.message, '(', input, typeof input, ')');
return ''; // return empty!
} else if (err instanceof InvalidNumberError) {
console.warn('Số không hợp lệ:', input, '(', typeof input, ')');
} else {
console.error('Unexpected error with input:', input, '(', typeof input, '):', err);
}
return typeof input === "string" ? input : String(input);
}
}
// call/use...
spellNumber(1234, yourConfig1); // Một nghìn hai trăm ba mươi tư
spellNumber(1234.89, yourConfig2) // Một-nghìn-hai-trăm-ba-mươi-tư-phẩy-tám-mươi-chín đồng
You can customize how numbers are parsed by extending the SpellerConfig
class and overriding the parseNumberData
method:
import { SpellerConfig, spellVnNumber } from 'spell-vn-number';
class CustomSpellerConfig extends SpellerConfig {
parseNumberData(input: InputNumber): NumberData {
// Your custom parsing logic here
// For example, handle special number formats or add custom validation
// Default implementation
return super.parseNumberData(input);
}
}
// Usage
const customConfig = new CustomSpellerConfig();
console.log(spellVnNumber(customConfig, '123.45'));
// Uses your custom parsing logic
The default parseNumberData
implementation:
- Cleans and validates the input number
- Handles negative signs
- Splits the number into integral and fractional parts
- Trims redundant zeros
- Returns a
NumberData
object with:-
isNegative
: boolean indicating if the number is negative -
integralPart
: string containing the integral part -
fractionalPart
: string containing the fractional part
-
You can append a currency unit to the spelled number by setting the currencyUnit
option in the SpellerConfig
class. By default, this is an empty string, meaning no currency unit is appended. If a value is provided, it will be added to the end of the spelled number.
import { spellVnNumber, SpellerConfig } from 'spell-vn-number';
const configWithCurrency = new SpellerConfig({
currencyUnit: 'đồng',
});
console.log(spellVnNumber(configWithCurrency, '123456'));
// "Một trăm hai mươi ba nghìn bốn trăm năm mươi sáu đồng"
The library also exports utility functions that can be used independently:
Validates, normalizes, and cleans number inputs:
import { cleanInputNumber } from 'spell-vn-number';
// Default configuration
const config = {};
// Validate various input types
console.log(cleanInputNumber(123, config)); // "123"
console.log(cleanInputNumber('123.45', config)); // "123.45"
console.log(cleanInputNumber('-123', config)); // "-123"
// Handle scientific notation
console.log(cleanInputNumber(1.23e5, config)); // "123000"
console.log(cleanInputNumber(1.23e-5, config)); // "0.0000123"
// Handle BigInt values (added support)
console.log(cleanInputNumber(BigInt('9007199254740991'), config)); // "9007199254740991"
console.log(cleanInputNumber(BigInt('123456789012345678901234567890'), config)); // "123456789012345678901234567890"
// Automatically removes thousands separators
console.log(cleanInputNumber('1,234,567', { thousandSign: ',' })); // "1234567"
console.log(cleanInputNumber('–123', config)); // "-123" - normalizes unicode minus signs
console.log(cleanInputNumber(' 1 000 000 ', config)); // "1000000" - removes spaces
// Custom separators
const euroConfig = {
thousandSign: '.',
decimalPoint: ','
};
console.log(cleanInputNumber('1.234.567,89', euroConfig)); // "1234567,89"
// Throws InvalidNumberError for invalid inputs
try {
cleanInputNumber('123abc', config);
} catch (e) {
console.error(e.message); // "Invalid number format"
}
The library can be extended to support additional number formats:
// You can import and customize normalizeNumberString directly for advanced cases
import { normalizeNumberString } from 'spell-vn-number';
// Default behavior
normalizeNumberString('1 234,56'); // "1234,56"
// With custom options for different formats
normalizeNumberString('1 234,56', {
decimalPoint: ',', // Use comma as decimal separator
thousandSign: ' ' // Use space as thousands separator
}); // "1234.56"
Handles redundant zeros in number strings based on configuration:
import { trimRedundantZeros, SpellerConfig } from 'spell-vn-number';
// Default config (keepOneZeroWhenAllZeros: false)
const defaultConfig = new SpellerConfig();
console.log(trimRedundantZeros(defaultConfig, '00123')); // "123"
console.log(trimRedundantZeros(defaultConfig, '123.4560')); // "123.456"
console.log(trimRedundantZeros(defaultConfig, '000.000')); // "0." - all decimal zeros removed
// With custom configuration
const customConfig = new SpellerConfig({
keepOneZeroWhenAllZeros: true
});
console.log(trimRedundantZeros(customConfig, '123.4560')); // "123.456"
// With keepOneZeroWhenAllZeros: true
console.log(trimRedundantZeros(customConfig, '000.000')); // "0.0" - one zero kept after decimal
console.log(trimRedundantZeros(customConfig, '123.0000')); // "123.0" - one zero kept
- Spells numbers in Vietnamese
- Supports large numbers (up to infinity with unit repetition)
- Handles decimal numbers
- Handles negative numbers
- Intelligently handles redundant zeros (configurable behavior)
- Customizable spelling configuration
- Proper handling of Vietnamese spelling rules
- Special handling for specific digits (1, 4, 5) based on position
- Browser compatibility including older browsers like IE11
- Lightweight and performant (~3KB minified and gzipped)
- Financial Standards Support:
- Compliant with Vietnamese Accounting Standards (VAS)
- Financial number formatting with proper unit scaling
- Language Customization:
- Supports Vietnamese dialects and regional variations
- Customizable pronunciation and unit names
- Extensible for new language variants
The library is compatible with:
- All modern browsers (Chrome, Firefox, Safari, Edge)
- Internet Explorer 11
- Mobile browsers (iOS Safari, Android Chrome)
Convenience function to spell a number with default configuration.
Main function to spell a number with custom configuration.
Configuration class with the following properties:
-
separator
: Word separator (default: ' ') -
negativeSign
: Negative sign (default: '-') -
pointSign
: Decimal point (default: '.') -
thousandSign
: Thousands separator (default: ',') -
capitalizeInitial
: Capitalize the first letter of the spelled number (default: false) -
keepOneZeroWhenAllZeros
: Controls how to handle redundant zeros (default: false); When the fractional part is all zeros, it will always remain a zero.- When
true
: '000.000' -> '0.0' (spelled: không chấm không) - When
false
: '000.000' -> '0.' (spelled: không)
- When
-
negativeText
: Text for negative numbers (default: 'âm') -
pointText
: Text for decimal point (default: 'chấm') -
oddText
: Text for odd numbers (default: 'lẻ') -
digits
: Mapping of digits to their spellings - And various other properties for Vietnamese-specific spelling rules
The library also exports several utility functions:
-
trimRedundantZeros(config, numberStr)
: Intelligently formats numbers by:- Removing excess leading zeros from integral part (always keeping at least one '0')
- Handling fractional part according to configuration:
- When
keepOneZeroWhenAllZeros
is false (default): removes all trailing zeros from the fractional part and keeps just the decimal point if all decimal digits are zeros - When
keepOneZeroWhenAllZeros
is true: removes all trailing zeros from the fractional part, but keeps a single '0' after the decimal point when all decimal digits are zeros
- When
- Examples:
- '00123' → '123' (with any config)
- '00.00100' → '0.001' (with any config, non-zero decimal digits always have trailing zeros removed)
- '000.000' → '0.' (with default config, when all decimal digits are zeros)
- '000.000' → '0.0' (with keepOneZeroWhenAllZeros: true, keeps one zero after decimal point when all are zeros)
- '123.0000' → '123.' (with default config)
- '123.0000' → '123.0' (with keepOneZeroWhenAllZeros: true)
-
cleanInputNumber(input, config)
: Validates, normalizes and cleans number inputs with extensive format support:-
Input types supported:
- Numbers: Regular JavaScript numbers including integers, decimals and scientific notation
- Strings: Number strings with or without decimal points, commas, or spaces
- BigInt: For handling very large integers beyond JavaScript's Number.MAX_SAFE_INTEGER
-
Format handling:
- Scientific notation: Converts to standard decimal notation (e.g., 1.23e5 → '123000')
- Thousands separators: Automatically removes thousands separators (e.g., '1,234,567' → '1234567')
- Alternative minus signs: Normalizes Unicode dashes (en dash, em dash) to standard hyphen-minus
- Whitespace removal: Automatically removes spaces and non-breaking spaces
-
Configuration options:
-
thousandSign
: Custom thousands separator character (default: ',') -
decimalPoint
: Custom decimal point character (default: '.')
-
-
Error handling:
- Throws InvalidFormatError for null, undefined, NaN, Infinity
- Throws InvalidNumberError for strings that don't represent valid numbers
-
Browser compatibility:
- Works in all modern browsers
- Compatible with older browsers including IE11
-
Input types supported:
The library throws different types of errors:
-
InvalidFormatError
: When the input number format is invalid (empty, null, undefined, a finite number, ...) -
InvalidNumberError
: When the number contains invalid characters
- Name: vincentchu
- Email: contact@vincentchu.work
- Website: https://resume.vincentchu.work/
MIT