digit-to-words-nepali
Version:
A comprehensive TypeScript library for converting numbers to words in English and Nepali languages. Supports numbers up to 10^39 (Adanta Singhar), currency formatting, decimal handling with individual digit pronunciation, and BigInt. Zero dependencies, fu
188 lines (187 loc) • 8.51 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.digitToNepaliWords = exports.NepaliConverter = void 0;
const baseConverter_1 = require("./baseConverter");
const validationUtils_1 = require("../utils/validationUtils");
const scaleMappings_1 = require("../mappings/scaleMappings");
const numberMappings_1 = require("../mappings/numberMappings");
const englishConverter_1 = require("./englishConverter");
const cacheUtils_1 = require("../utils/cacheUtils");
const converterFactory_1 = require("../utils/converterFactory");
/**
* NepaliConverter
* Converts numbers to Nepali words with support for currency, decimals, and custom mappings.
* Optimized for readability, maintainability, and performance.
*/
class NepaliConverter extends baseConverter_1.BaseConverter {
constructor() {
super();
this.wordCache = (0, numberMappings_1.createNumberWordMap)();
this.scales = scaleMappings_1.nepaliScaleMappings;
}
/**
* Convert decimal digits individually to Nepali words.
* @param decimal - The decimal string (e.g., "33")
* @param config - Conversion configuration
* @returns Array of individual digit words
*/
convertDecimalDigitsIndividually(decimal, config) {
const digits = decimal.split('');
const words = [];
for (const digit of digits) {
const digitNum = parseInt(digit);
words.push(this.getWordMapping(digitNum, config.lang));
}
return words;
}
/**
* Convert a number to Nepali words.
* @param num - The number to convert
* @param config - Conversion configuration (all fields required)
*/
convert(num, config) {
this.setCustomMappings(config);
// Validate input
if (!(0, validationUtils_1.isValidNumber)(num)) {
throw new Error('Input must contain only valid digits');
}
// Use cache if no custom mappings
const hasCustomMappings = Object.keys(config.units).length > 0 || Object.keys(config.scales).length > 0;
if (!hasCustomMappings) {
const cacheKey = {
value: num.toString(),
lang: config.lang,
isCurrency: config.isCurrency,
includeDecimal: config.includeDecimal,
individualDecimalDigits: config.individualDecimalDigits,
currency: config.currency,
decimalSuffix: config.decimalSuffix,
currencyDecimalSuffix: config.currencyDecimalSuffix
};
const cachedResult = cacheUtils_1.conversionCache.get(cacheKey);
if (cachedResult)
return cachedResult;
}
// Split number into integer and decimal parts
const { integer, decimal } = (0, validationUtils_1.splitNumber)(num);
// Enforce maximum supported value (10^39 - 1) and max length (39 digits)
const MAX_SUPPORTED = BigInt('9'.repeat(39));
const intStr = integer.toString().replace(/^0+/, '') || '0';
if (integer > MAX_SUPPORTED || intStr.length > 39) {
throw new Error('Input exceeds maximum supported value (10^39 - 1)');
}
const words = [];
// Determine if input is zero or a fraction (e.g., 0.01)
const isZeroOrFraction = (typeof num === 'bigint' ? num === 0n : parseFloat(num.toString()) < 1 && parseFloat(num.toString()) > -1);
// Integer part conversion
if (integer === 0n) {
// Add "zero" if the original number was effectively zero before the decimal,
// or if there's no decimal part.
if (isZeroOrFraction || !decimal) {
// Only add zero if the decimal part isn't going to represent the only value (e.g., 0.01)
if (!decimal || parseInt(decimal || '0') === 0) {
words.push(this.getWordMapping(0, config.lang));
}
}
}
else {
this.processLargeNumber(integer, words, config);
}
// Decimal part conversion
if (config.includeDecimal && decimal) {
const decimalNum = parseInt(decimal || '0');
// Only add decimal suffix and value if the decimal part is non-zero
if (decimalNum !== 0) {
// If the integer part was zero, add it now before the decimal.
if (integer === 0n && words.length === 0) {
words.push(this.getWordMapping(0, config.lang));
}
words.push(config.isCurrency ? config.currencyDecimalSuffix : config.decimalSuffix);
// Use individual digits for non-currency or when explicitly configured
if (config.individualDecimalDigits) {
const individualDigits = this.convertDecimalDigitsIndividually(decimal, config);
words.push(...individualDigits);
}
else {
words.push(this.getWordMapping(decimalNum, config.lang));
}
}
else if (integer === 0n && words.length === 0) {
// If integer was 0 and decimal rounded to 0, ensure "zero" is output
words.push(this.getWordMapping(0, config.lang));
}
}
// Add currency prefix if needed and if currency string is not empty
if (config.isCurrency && config.currency) {
// Always add currency prefix if isCurrency is true, unless the result is empty (which shouldn't happen now)
if (words.length > 0) {
words.unshift(config.currency);
}
else {
// If somehow words is empty, output "Currency Zero"
words.push(config.currency, this.getWordMapping(0, config.lang));
}
}
// Final check: if words array is empty (e.g., input was 0.000), add zero.
// This might be redundant now but serves as a safeguard.
if (words.length === 0) {
words.push(this.getWordMapping(0, config.lang));
}
const result = {
words,
meta: {
originalNumber: num.toString(),
language: config.lang,
isCurrency: config.isCurrency
}
};
// Store in cache if no custom mappings
if (!hasCustomMappings) {
const cacheKey = {
value: num.toString(),
lang: config.lang,
isCurrency: config.isCurrency,
includeDecimal: config.includeDecimal,
individualDecimalDigits: config.individualDecimalDigits,
currency: config.currency,
decimalSuffix: config.decimalSuffix,
currencyDecimalSuffix: config.currencyDecimalSuffix
};
cacheUtils_1.conversionCache.set(cacheKey, result);
}
return result;
}
}
exports.NepaliConverter = NepaliConverter;
const digitToNepaliWords = (num, config = {}) => {
// If lang is 'en', delegate to EnglishConverter for API consistency
if (config.lang === 'en') {
return (0, englishConverter_1.digitToEnglishWords)(num, config);
}
// Use the converter factory to get a singleton instance
const converter = converterFactory_1.ConverterFactory.getInstance('NepaliConverter', () => new NepaliConverter());
// Set default language-specific config before merging user config
const langDefaults = {
currency: 'रुपैयाँ',
decimalSuffix: 'दशमलव',
currencyDecimalSuffix: 'पैसा',
};
const defaultConfig = {
lang: 'ne',
isCurrency: false,
includeDecimal: true,
individualDecimalDigits: true, // Default to individual digits
units: {},
scales: {},
...langDefaults, // Apply language defaults
...config // User config overrides defaults
};
// Override individualDecimalDigits based on currency setting if not explicitly set
if (config.individualDecimalDigits === undefined) {
defaultConfig.individualDecimalDigits = !defaultConfig.isCurrency;
}
const result = converter.convert(num, defaultConfig);
// Filter out empty strings that might result from empty currency/decimal suffixes
return result.words.filter((word) => word !== '').join(' ').trim();
};
exports.digitToNepaliWords = digitToNepaliWords;