n2words
Version:
Convert numbers to words in 70+ languages with zero dependencies. Supports BigInt, decimals, and browser/Node.js environments.
275 lines (230 loc) • 10.6 kB
JavaScript
/**
* Gujarati (India) language converter
*
* CLDR: gu-IN | Gujarati as used in India
*
* Key features:
* - Indian numbering system (હજાર, લાખ, કરોડ)
* - Gujarati script
* - 3-2-2 grouping pattern (last 3 digits, then groups of 2)
* - Complete word forms for 0-99
* - Per-digit decimal reading
*/
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 (Indian Rupee)
// ============================================================================
// Rupee: singular/plural
const RUPEE = 'રૂપિયો'
const RUPEES = 'રૂપિયા'
// Paisa: singular/plural
const PAISA = 'પૈસો'
const PAISE = 'પૈસા'
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 (0-999)
* @returns {string} Gujarati 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 Gujarati 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} Gujarati 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
const segments = []
segments.push(Number(n % 1000n))
let temp = n / 1000n
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) {
words.push(buildSegment(segment))
}
else {
words.push(BELOW_HUNDRED[segment])
}
if (i > 0 && SCALE_WORDS[i]) {
words.push(SCALE_WORDS[i])
}
}
return words.join(' ')
}
/**
* Converts the fractional digits to per-digit Gujarati words.
* @param {string} decimalPart - Decimal digits as a string
* @returns {string} Gujarati words for each digit
*/
function decimalPartToWords(decimalPart) {
// Per-digit decimal reading
const digits = []
for (const char of decimalPart) {
const d = parseInt(char, 10)
digits.push(d === 0 ? ZERO : BELOW_HUNDRED[d])
}
return digits.join(' ')
}
/**
* Converts a numeric value to Gujarati words.
* @param {number | string | bigint} value - The numeric value to convert
* @returns {string} The number in Gujarati words
*/
function toCardinal(value) {
const { isNegative, integerPart, decimalPart } = parseCardinalValue(value)
// The fraction is spelled digit by digit, so only the integer part has a ceiling.
checkMax(integerPart, cardinalMax)
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 Gujarati ordinal words.
*
* Gujarati ordinals: First 6 are irregular, then add -મું suffix.
* @param {bigint} n - Positive integer to convert
* @returns {string} Gujarati 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 Gujarati 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(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 Gujarati currency words (Indian Rupee).
* @param {number | string | bigint} value - The currency amount to convert
* @returns {string} The amount in Gujarati 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: rupees, cents: paise } = parseCurrencyValue(value)
checkMax(rupees, currencyMax)
// Build result
let result = ''
if (isNegative) result = NEGATIVE + ' '
// Rupees part - show if non-zero, or if no paise
if (rupees > 0n || paise === 0n) {
result += integerToWords(rupees)
// Singular for 1 rupee, plural otherwise
result += ' ' + (rupees === 1n ? RUPEE : RUPEES)
}
// Paise part
if (paise > 0n) {
if (rupees > 0n) {
result += ' '
}
result += integerToWords(paise)
// Singular for 1 paisa, plural otherwise
result += ' ' + (paise === 1n ? PAISA : PAISE)
}
return result
}
// ============================================================================
// Exports
// ============================================================================
export { toCardinal, toOrdinal, toCurrency }