UNPKG

n2words

Version:

Convert numbers to words in 70+ languages with zero dependencies. Supports BigInt, decimals, and browser/Node.js environments.

287 lines (238 loc) 11.2 kB
/** * Bangla (Bangladesh) language converter * * CLDR: bn-BD | Bengali as used in Bangladesh * * Key features: * - Indian numbering system (হাজার, লাখ, কোটি) * - Bangla script (Bengali) * - 3-2-2 grouping pattern (last 3 digits, then groups of 2) * - Complete word forms for 0-99 */ import { parseCardinalValue } from './utils/parse-cardinal.js' import { parseCurrencyValue } from './utils/parse-currency.js' import { parseOrdinalValue } from './utils/parse-ordinal.js' import { checkMax } from './utils/check-max.js' import { indian } from './utils/scale.js' // ============================================================================ // Vocabulary // ============================================================================ const ZERO = 'শূন্য' const NEGATIVE = 'মাইনাস' const DECIMAL_SEP = 'দশমিক' const HUNDRED = 'শত' // ============================================================================ // Ordinal Vocabulary // ============================================================================ // Ordinal suffix (adds to cardinal for numbers >= 7) const ORDINAL_SUFFIX = 'তম' // Special ordinals for first few numbers (1-6 have irregular forms) const ORDINAL_SPECIAL = ['', 'প্রথম', 'দ্বিতীয়', 'তৃতীয়', 'চতুর্থ', 'পঞ্চম', 'ষষ্ঠ'] // ============================================================================ // Currency Vocabulary (Bangladeshi Taka) // ============================================================================ // Taka: singular/plural (same form used in Bengali) const TAKA = 'টাকা' // Paisa: singular/plural (same form used in Bengali) const PAISA = 'পয়সা' const BELOW_HUNDRED = [ 'শূন্য', 'এক', 'দুই', 'তিন', 'চার', 'পাঁচ', 'ছয়', 'সাত', 'আট', 'নয়', 'দশ', 'এগারো', 'বারো', 'তেরো', 'চৌদ্দ', 'পনেরো', 'ষোল', 'সতেরো', 'আঠারো', 'উনিশ', 'বিশ', 'একুশ', 'বাইশ', 'তেইশ', 'চব্বিশ', 'পঁচিশ', 'ছাব্বিশ', 'সাতাশ', 'আঠাশ', 'উনত্রিশ', 'ত্রিশ', 'একত্রিশ', 'বত্রিশ', 'তেত্রিশ', 'চৌত্রিশ', 'পঁয়ত্রিশ', 'ছত্রিশ', 'সাঁইত্রিশ', 'আটত্রিশ', 'উনচল্লিশ', 'চল্লিশ', 'একচল্লিশ', 'বেয়াল্লিশ', 'তেতাল্লিশ', 'চুয়াল্লিশ', 'পঁয়তাল্লিশ', 'ছেচল্লিশ', 'সাতচল্লিশ', 'আটচল্লিশ', 'উনপঞ্চাশ', 'পঞ্চাশ', 'একান্ন', 'বাহান্ন', 'তিপ্পান্ন', 'চুয়ান্ন', 'পঞ্চান্ন', 'ছাপ্পান্ন', 'সাতান্ন', 'আটান্ন', 'উনষাট', 'ষাট', 'একষট্টি', 'বাষট্টি', 'তেষট্টি', 'চৌষট্টি', 'পঁয়ষট্টি', 'ছেষট্টি', 'সাতষট্টি', 'আটষট্টি', 'ঊনসত্তর', 'সত্তর', 'একাত্তর', 'বাহাত্তর', 'তেহাত্তর', 'চুয়াত্তর', 'পঁচাত্তর', 'ছিয়াত্তর', 'সাতাত্তর', 'আটাত্তর', 'উনআশি', 'আশি', 'একাশি', 'বিরাশি', 'তিরাশি', 'চুরাশি', 'পঁচাশি', 'ছিয়াশি', 'সাতাশি', 'আটাশি', 'উননব্বই', 'নব্বই', 'একানব্বই', 'বিরানব্বই', 'তিরানব্বই', 'চুরানব্বই', 'পঁচানব্বই', 'ছিয়ানব্বই', 'সাতানব্বই', 'আটানব্বই', 'নিরানব্বই', ] // Scale words: index 0 = units (empty), 1 = thousand, 2 = lakh, 3 = crore, etc. const SCALE_WORDS = ['', 'হাজার', 'লাখ', 'কোটি', 'আরব', 'খরব', 'নীল', 'পদ্ম', 'শঙ্খ'] // 3-2-2 Indian grouping: a 3-digit base segment, then 2 digits per scale word // (SCALE_WORDS[0] = '' is the units slot). Past the table the scale word is // dropped, which collapses the magnitude — so cap there. export const cardinalMax = indian(SCALE_WORDS.length) export const ordinalMax = indian(SCALE_WORDS.length) export const currencyMax = indian(SCALE_WORDS.length) // ============================================================================ // Segment Building // ============================================================================ /** * Builds words for a 0-999 segment. * @param {number} n - Segment value in the range 0-999 * @returns {string} Bengali words for the segment */ function buildSegment(n) { if (n === 0) return '' if (n < 100) return BELOW_HUNDRED[n] const hundreds = Math.trunc(n / 100) const remainder = n % 100 if (remainder === 0) { return BELOW_HUNDRED[hundreds] + ' ' + HUNDRED } return BELOW_HUNDRED[hundreds] + ' ' + HUNDRED + ' ' + BELOW_HUNDRED[remainder] } // ============================================================================ // Conversion Functions // ============================================================================ /** * Converts a non-negative integer to Bengali words. * * Uses BigInt modulo for segment extraction (faster than string slicing). * South Asian 3-2-2 grouping: first 3 digits, then groups of 2. * @param {bigint} n - Non-negative integer to convert * @returns {string} Bengali words */ function integerToWords(n) { if (n === 0n) return ZERO // Fast path: numbers < 1000 (direct lookup) if (n < 1000n) { return buildSegment(Number(n)) } // Extract segments using BigInt modulo // First segment is 3 digits (thousands), rest are 2 digits (lakhs, crores, etc.) // Segments stored least-significant first const segments = [] // First segment: last 3 digits segments.push(Number(n % 1000n)) let temp = n / 1000n // Remaining segments: 2 digits each (lakh = 100k, crore = 10M, etc.) while (temp > 0n) { segments.push(Number(temp % 100n)) temp = temp / 100n } // Build result string (process from most-significant to least) const words = [] for (let i = segments.length - 1; i >= 0; i--) { const segment = segments[i] if (segment === 0) continue if (i === 0) { // First segment (units place) can be 0-999 words.push(buildSegment(segment)) } else { // Other segments are 0-99 words.push(BELOW_HUNDRED[segment]) } // Add scale word if not the units segment if (i > 0 && SCALE_WORDS[i]) { words.push(SCALE_WORDS[i]) } } return words.join(' ') } /** * Converts the fractional digits string to Bengali words. * @param {string} decimalPart - The fractional digits (e.g. '05') * @returns {string} Bengali words for the decimal part */ function decimalPartToWords(decimalPart) { let result = '' let i = 0 while (i < decimalPart.length && decimalPart[i] === '0') { if (result) result += ' ' result += ZERO i++ } const remainder = decimalPart.slice(i) if (remainder) { if (result) result += ' ' result += integerToWords(BigInt(remainder)) } return result } /** * Converts a numeric value to Bengali words. * @param {number | string | bigint} value - The numeric value to convert * @returns {string} The number in Bengali words */ function toCardinal(value) { const { isNegative, integerPart, decimalPart } = parseCardinalValue(value) // Both the integer part and the decimal's significant digits are spelled via // the scale builder, so both must clear the ceiling. checkMax(integerPart, cardinalMax, decimalPart) let result = '' if (isNegative) { result = NEGATIVE + ' ' } result += integerToWords(integerPart) if (decimalPart) { result += ' ' + DECIMAL_SEP + ' ' + decimalPartToWords(decimalPart) } return result } // ============================================================================ // ORDINAL: toOrdinal(value) // ============================================================================ /** * Converts a positive integer to Bengali ordinal words. * * Bengali ordinals: First 6 are irregular, then add -তম suffix. * @param {bigint} n - Positive integer to convert * @returns {string} Bengali ordinal words */ function integerToOrdinal(n) { // Special ordinals for 1-6 if (n >= 1n && n <= 6n) { return ORDINAL_SPECIAL[Number(n)] } // For 7 and above, add suffix to cardinal const cardinal = integerToWords(n) return cardinal + ORDINAL_SUFFIX } /** * Converts a numeric value to Bengali ordinal words. * @param {number | string | bigint} value - The numeric value to convert (positive integer) * @returns {string} The number as ordinal words * @throws {TypeError} If value is not a valid numeric type * @throws {RangeError} If value is negative, zero, or has a decimal part * @example * toOrdinal(1) // 'প্রথম' * toOrdinal(2) // 'দ্বিতীয়' * toOrdinal(3) // 'তৃতীয়' * toOrdinal(10) // 'দশতম' */ function toOrdinal(value) { const integerPart = parseOrdinalValue(value) // Ordinals build on the cardinal speller, so they share its ceiling. checkMax(integerPart, ordinalMax) return integerToOrdinal(integerPart) } // ============================================================================ // CURRENCY: toCurrency(value, options?) // ============================================================================ /** * Converts a numeric value to Bengali currency words (Bangladeshi Taka). * @param {number | string | bigint} value - The currency amount to convert * @returns {string} The amount in Bengali currency words * @throws {TypeError} If value is not a valid numeric type * @throws {Error} If value is not a valid number format * @example * toCurrency(42.50) // 'বেয়াল্লিশ টাকা পঞ্চাশ পয়সা' * toCurrency(1) // 'এক টাকা' * toCurrency(0.01) // 'এক পয়সা' */ function toCurrency(value) { const { isNegative, dollars: taka, cents: paisa } = parseCurrencyValue(value) checkMax(taka, currencyMax) // Build result let result = '' if (isNegative) result = NEGATIVE + ' ' // Taka part - show if non-zero, or if no paisa if (taka > 0n || paisa === 0n) { result += integerToWords(taka) + ' ' + TAKA } // Paisa part if (paisa > 0n) { if (taka > 0n) { result += ' ' } result += integerToWords(paisa) + ' ' + PAISA } return result } // ============================================================================ // Exports // ============================================================================ export { toCardinal, toOrdinal, toCurrency }