UNPKG

@mahmud002/number-to-words-converter

Version:

A simple utility to convert numbers to their word representations in English and Persian, and vice versa. Includes Persian numerals conversion.

450 lines (394 loc) 9.01 kB
const ONES = [ '', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen', ] const TENS = ['', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety'] const SCALES = ['', 'thousand', 'million', 'billion', 'trillion'] // Persian number arrays const PERSIAN_ONES = [ '', 'یک', 'دو', 'سه', 'چهار', 'پنج', 'شش', 'هفت', 'هشت', 'نه', 'ده', 'یازده', 'دوازده', 'سیزده', 'چهارده', 'پانزده', 'شانزده', 'هفده', 'هجده', 'نوزده', ] const PERSIAN_TENS = ['', '', 'بیست', 'سی', 'چهل', 'پنجاه', 'شصت', 'هفتاد', 'هشتاد', 'نود'] const PERSIAN_HUNDREDS = [ '', 'صد', 'دویست', 'سیصد', 'چهارصد', 'پانصد', 'ششصد', 'هفتصد', 'هشتصد', 'نهصد', ] const PERSIAN_SCALES = ['', 'هزار', 'میلیون', 'میلیارد', 'تریلیون'] // Persian numerals mapping const PERSIAN_NUMERALS: { [key: string]: string } = { '۰': '0', '۱': '1', '۲': '2', '۳': '3', '۴': '4', '۵': '5', '۶': '6', '۷': '7', '۸': '8', '۹': '9', } // Persian numerals mapping (reverse) const ENGLISH_TO_PERSIAN_NUMERALS: { [key: string]: string } = { '0': '۰', '1': '۱', '2': '۲', '3': '۳', '4': '۴', '5': '۵', '6': '۶', '7': '۷', '8': '۸', '9': '۹', } // English number words to numeric mapping const ENGLISH_WORD_TO_NUMBER: { [key: string]: number } = { zero: 0, one: 1, two: 2, three: 3, four: 4, five: 5, six: 6, seven: 7, eight: 8, nine: 9, ten: 10, eleven: 11, twelve: 12, thirteen: 13, fourteen: 14, fifteen: 15, sixteen: 16, seventeen: 17, eighteen: 18, nineteen: 19, twenty: 20, thirty: 30, forty: 40, fifty: 50, sixty: 60, seventy: 70, eighty: 80, ninety: 90, hundred: 100, thousand: 1000, million: 1000000, billion: 1000000000, trillion: 1000000000000, } // Persian number words to numeric mapping const PERSIAN_WORD_TO_NUMBER: { [key: string]: number } = { صفر: 0, یک: 1, دو: 2, سه: 3, چهار: 4, پنج: 5, شش: 6, هفت: 7, هشت: 8, نه: 9, ده: 10, یازده: 11, دوازده: 12, سیزده: 13, چهارده: 14, پانزده: 15, شانزده: 16, هفده: 17, هجده: 18, نوزده: 19, بیست: 20, سی: 30, چهل: 40, پنجاه: 50, شصت: 60, هفتاد: 70, هشتاد: 80, نود: 90, صد: 100, دویست: 200, سیصد: 300, چهارصد: 400, پانصد: 500, ششصد: 600, هفتصد: 700, هشتصد: 800, نهصد: 900, هزار: 1000, میلیون: 1000000, میلیارد: 1000000000, تریلیون: 1000000000000, } // English numerals to string mapping const ENGLISH_NUMERALS: { [key: string]: string } = { '0': 'zero', '1': 'one', '2': 'two', '3': 'three', '4': 'four', '5': 'five', '6': 'six', '7': 'seven', '8': 'eight', '9': 'nine', } function convertChunk(num: number): string { if (num === 0) return '' let words = '' // Handle hundreds if (num >= 100) { words += ONES[Math.floor(num / 100)] + ' hundred ' num %= 100 } // Handle tens and ones if (num > 0) { if (words !== '') { words += 'and ' } if (num < 20) { words += ONES[num] } else { words += TENS[Math.floor(num / 10)] if (num % 10 > 0) { words += '-' + ONES[num % 10] } } } return words.trim() } function convertPersianChunk(num: number): string { if (num === 0) return '' let words = '' // Handle hundreds if (num >= 100) { words += PERSIAN_HUNDREDS[Math.floor(num / 100)] + ' ' num %= 100 } // Handle tens and ones if (num > 0) { if (words !== '') { words += 'و ' } if (num < 20) { words += PERSIAN_ONES[num] } else { words += PERSIAN_TENS[Math.floor(num / 10)] if (num % 10 > 0) { words += ' و ' + PERSIAN_ONES[num % 10] } } } return words.trim() } export function numberToWords(num: number): string { if (num === 0) return 'zero' if (num < 0) return 'negative ' + numberToWords(Math.abs(num)) let words = '' let scaleIndex = 0 while (num > 0) { const chunk = num % 1000 if (chunk > 0) { const chunkWords = convertChunk(chunk) words = chunkWords + (SCALES[scaleIndex] ? ' ' + SCALES[scaleIndex] + ' ' : '') + words } num = Math.floor(num / 1000) scaleIndex++ } return words.trim().toLowerCase() } export function numberToPersianWords(input: string | number): string { // Convert Persian numerals to English numerals if string input let num: number if (typeof input === 'string') { num = parseInt( input .split('') .map((char) => PERSIAN_NUMERALS[char] || char) .join('') ) } else { num = input } if (isNaN(num)) { throw new Error('Invalid number input') } if (num === 0) return 'صفر' if (num < 0) return 'منفی ' + numberToPersianWords(Math.abs(num)) let words = '' let scaleIndex = 0 let hasValue = false while (num > 0) { const chunk = num % 1000 if (chunk > 0) { const chunkWords = convertPersianChunk(chunk) if (hasValue) { words = chunkWords + ' ' + PERSIAN_SCALES[scaleIndex] + ' و ' + words } else { words = chunkWords + (PERSIAN_SCALES[scaleIndex] ? ' ' + PERSIAN_SCALES[scaleIndex] : '') + words hasValue = true } } num = Math.floor(num / 1000) scaleIndex++ } return words.trim() } export function wordsToNumber(words: string): number { // Remove extra spaces and convert to lowercase words = words.toLowerCase().trim().replace(/\s+/g, ' ') // Handle negative numbers if (words.startsWith('negative ')) { return -wordsToNumber(words.slice(9)) } let total = 0 let currentNumber = 0 // Split into words and process each const wordArray = words.split(/[\s-]+/) for (let i = 0; i < wordArray.length; i++) { const word = wordArray[i] if (word === 'and') continue const value = ENGLISH_WORD_TO_NUMBER[word] if (value === undefined) { throw new Error(`Unknown word: ${word}`) } if (value === 100) { currentNumber = (currentNumber || 1) * value } else if (value >= 1000) { currentNumber = (currentNumber || 1) * value total += currentNumber currentNumber = 0 } else { currentNumber += value } } return total + currentNumber } export function persianWordsToNumber(words: string): number { // Handle negative numbers if (words.startsWith('منفی ')) { return -persianWordsToNumber(words.slice(5)) } let total = 0 let currentNumber = 0 // Split into words and process each const wordArray = words.split(/\s+و\s+|\s+/) for (let i = 0; i < wordArray.length; i++) { const word = wordArray[i].trim() const value = PERSIAN_WORD_TO_NUMBER[word] if (value === undefined) { throw new Error(`Unknown word: ${word}`) } if (value >= 1000) { currentNumber = (currentNumber || 1) * value total += currentNumber currentNumber = 0 } else { currentNumber += value } } return total + currentNumber } export function stringToNumber(input: string): number { // Try parsing as a regular number first const num = Number(input) if (!isNaN(num)) { return num } // Convert Persian numerals to English numerals const englishNumerals = input .split('') .map((char) => PERSIAN_NUMERALS[char] || char) .join('') const numFromPersian = Number(englishNumerals) if (!isNaN(numFromPersian)) { return numFromPersian } // Try parsing as English words try { return wordsToNumber(input) } catch (e) { // Try parsing as Persian words try { return persianWordsToNumber(input) } catch (e2) { throw new Error('Invalid number format') } } } export function numberToString(num: number, format: 'english' | 'persian' = 'english'): string { if (format === 'persian') { return numberToPersianWords(num) } return numberToWords(num) } export function numberToPersianNumerals(num: number): string { return num .toString() .split('') .map((digit) => ENGLISH_TO_PERSIAN_NUMERALS[digit] || digit) .join('') } export function persianNumeralsToNumber(persianNumerals: string): number { const englishNumerals = persianNumerals .split('') .map((char) => PERSIAN_NUMERALS[char] || char) .join('') return parseInt(englishNumerals) } export default { numberToWords, numberToPersianWords, wordsToNumber, persianWordsToNumber, stringToNumber, numberToString, numberToPersianNumerals, persianNumeralsToNumber, }