UNPKG

num-beauty

Version:

An ultra lightweight module for formatting numbers into human-friendly strings

871 lines (682 loc) 27.4 kB
# num-beauty An ultra lightweight module for formatting numbers into human-friendly strings. Features: - ✨ Basic number formatting with thousands and decimal separators - 🌎 Internationalization (41+ locales — full list below) - 📏 Custom decimal precision - 🔄 Multiple rounding modes - 🎭 Predefined and custom masks per locale - 🔤 Large number abbreviation (1,234,5671.23M) - 💾 Data size formatting (10241 KiB, 10485761 MiB) - 📊 Percentage formatting (0.550%, with locale-aware spacing) - 🔗 Fluent API with method chaining for elegant formatting - 🎯 Tree-shaking support for optimal bundle sizes - 🌐 Dynamic locale registration at runtime - ♿ Accessibility: conversion to screen-reader friendly speech - 🔄 Reverse parsing: convert formatted strings back to numbers (unbeautify) - 📋 Structured formatting: decompose numbers into parts for granular CSS styling (beautifyToParts) - ⚛️ React integration: hooks and components for declarative number formatting ![NPM Version](https://img.shields.io/npm/v/num-beauty) ![NPM Unpacked Size](https://img.shields.io/npm/unpacked-size/num-beauty) ![npm bundle size](https://img.shields.io/bundlephobia/min/num-beauty) ![Crates.io License](https://img.shields.io/crates/l/mit) ![GitHub last commit](https://img.shields.io/github/last-commit/menesesevandro/num-beauty) ## Installation ```bash # npm npm install num-beauty # yarn yarn add num-beauty # pnpm pnpm add num-beauty ``` ## Documentation Full documentation is available at [num-beauty.js.org](https://num-beauty.js.org). ## Loading Locales - Default: `en-US` is automatically available. - Other languages: load or register the locale before use. Example: ```typescript import { beautify } from 'num-beauty'; import { loadLocale } from 'num-beauty/locales/loader'; // en-US works without extra configuration console.log(beautify(1234.56)); // "1,234.56" // To use pt-BR, load it before use (async () => { await loadLocale('pt-BR'); console.log(beautify(1234.56, { locale: 'pt-BR' })); // "1.234,56" })(); ``` ## Supported Locales This project supports a wide set of locales across regions. Load a locale dynamically with `loadLocale(code)` or register your own. - Core (original): `en-US`, `pt-BR`, `es-ES` - Europe: `de-DE`, `fr-FR`, `it-IT`, `nl-NL`, `pl-PL`, `tr-TR`, `sv-SE`, `da-DK`, `nb-NO`, `fi-FI`, `cs-CZ`, `hu-HU`, `ro-RO`, `sk-SK`, `bg-BG`, `hr-HR`, `el-GR`, `uk-UA`, `sl-SI`, `lt-LT`, `lv-LV`, `et-EE`, `sr-RS`, `pt-PT`, `ca-ES`, `is-IS`, `ga-IE` - Asia & Middle East: `ja-JP`, `zh-CN`, `ko-KR`, `hi-IN`, `ar-SA`, `ru-RU`, `vi-VN`, `th-TH`, `id-ID`, `ms-MY`, `he-IL`, `ar-EG` - Africa & Oceania: `en-ZA`, `en-NG`, `en-AU`, `en-NZ`, `mi-NZ`, `en-KE`, `sw-KE`, `pt-AO` If you need a locale that isn't bundled, use `registerLocale` or `registerLocaleLoader` to add it at runtime. ## Tree-Shaking Support `num-beauty` is fully optimized for tree-shaking. You can import specific functions to reduce your bundle size: ```typescript // Import specific modules for better tree-shaking import { round } from 'num-beauty/round'; import { formatCurrency } from 'num-beauty/currency'; import { formatNumber } from 'num-beauty/format'; import { abbreviateNumber } from 'num-beauty/abbreviate'; import { applyMask } from 'num-beauty/mask'; import { formatBytes } from 'num-beauty/bytes'; import { formatPercentage } from 'num-beauty/percentage'; // Example usage console.log(round(1.235, 2, 'HALF_UP')); // "1.24" console.log(formatCurrency(1234.56, 'en-US', { currency: 'USD' })); // "$1,234.56" console.log(formatBytes(1048576)); // "1.00 MiB" console.log(formatPercentage(0.5)); // "50.00%" ``` This approach ensures you only include the code you actually use, resulting in smaller bundle sizes. ## Quick Guide ```typescript import { beautify } from 'num-beauty'; // Basic formatting console.log(beautify(1234.5678)); // "1,234.57" // Large numbers console.log(beautify(1234567.89)); // "1,234,567.89" console.log(beautify(1234567.89, { abbreviated: true })); // "1.23M" // Different locales (load before use) (async () => { await loadLocale('pt-BR'); console.log(beautify(1234567.89, { locale: 'pt-BR' })); // "1.234.567,89" console.log(beautify(1234567.89, { locale: 'pt-BR', abbreviated: true })); // "1,23 mi" })(); // Custom decimal precision console.log(beautify(1234.56789, { decimals: 4 })); // "1,234.5679" console.log(beautify(1234.56700, { decimals: 4, stripZeros: true })); // "1,234.567" // Negative numbers console.log(beautify(-1234567.89)); // "-1,234,567.89" console.log(beautify(-1234567.89, { abbreviated: true })); // "-1.23M" // Small numbers console.log(beautify(0.00123456, { decimals: 4 })); // "0.0012" // Scientific notation (automatically converted) console.log(beautify(1.23e7)); // "12,300,000.00" console.log(beautify(1.23e7, { abbreviated: true })); // "12.30M" // Stripping unnecessary zeros console.log(beautify(1234.50000, { stripZeros: true })); // "1,234.5" // Different rounding modes console.log(beautify(1.235, { decimals: 2, roundingMode: 'UP' })); // "1.24" console.log(beautify(1.235, { decimals: 2, roundingMode: 'DOWN' })); // "1.23" console.log(beautify(1.235, { decimals: 2, roundingMode: 'CEIL' })); // "1.24" console.log(beautify(1.235, { decimals: 2, roundingMode: 'FLOOR' })); // "1.23" console.log(beautify(1.235, { decimals: 2, roundingMode: 'HALF_UP' })); // "1.24" console.log(beautify(1.225, { decimals: 2, roundingMode: 'HALF_DOWN' })); // "1.22" console.log(beautify(1.225, { decimals: 2, roundingMode: 'HALF_EVEN' })); // "1.22" // Accessibility (screen-reader friendly) import { toAccessibleString } from 'num-beauty'; console.log(toAccessibleString('1.2M', { locale: 'pt-BR' })); // "um ponto dois milhões" console.log(toAccessibleString('R$ 12M', { locale: 'pt-BR' })); // "doze milhões de reais" console.log(toAccessibleString('$1.5k', { locale: 'en-US' })); // "one point five thousand dollars" // Reverse parsing (unbeautify) - convert formatted strings back to numbers import { unbeautify } from 'num-beauty'; console.log(unbeautify('$1,234.56')); // 1234.56 console.log(unbeautify('R$ 1.234,56', { locale: 'pt-BR' })); // 1234.56 console.log(unbeautify('1.5k')); // 1500 console.log(unbeautify('2.3M')); // 2300000 console.log(unbeautify('45.5%')); // 0.455 console.log(unbeautify('1.5 KB')); // 1536 console.log(unbeautify('(1,234.56)')); // -1234.56 (accounting format) // Structured formatting (beautifyToParts) - decompose numbers into parts for granular styling import { beautifyToParts } from 'num-beauty'; console.log(beautifyToParts(1234.56, { locale: 'en-US' })); // [ // { type: 'integer', value: '1,234' }, // { type: 'decimal', value: '.' }, // { type: 'fraction', value: '56' } // ] console.log(beautifyToParts(1234.56, { locale: 'pt-BR', currency: 'BRL' })); // [ // { type: 'currency', value: 'R$' }, // { type: 'integer', value: '1.234' }, // { type: 'decimal', value: ',' }, // { type: 'fraction', value: '56' } // ] ``` ## Fluent API (Builder Pattern) `num-beauty` provides a fluent API for method chaining, making it easy to build complex formatting operations: ```typescript import { Num, num } from 'num-beauty'; // Basic formatting num(1234.56).locale('pt-BR').format(); // "1.234,56" num(1234.56789).decimals(3).format(); // "1,234.568" num(1234.50).stripZeros().format(); // "1,234.5" // Currency formatting num(1234.56).locale('en-US').currency('USD').format(); // "$1,234.56" num(1234.56).locale('pt-BR').currency('BRL').format(); // "R$ 1.234,56" num(1234.56).currency('USD').showCode().format(); // "USD 1,234.56" num(1234.56).currency('USD').hideSymbol().format(); // "1,234.56" // Bytes formatting num(1048576).bytes().format(); // "1.00 MiB" num(1000000).bytes(false).format(); // "1.00 MB" num(1048576).bytes().bytesLongFormat().format(); // "1.00 Mebibyte" num(1536).bytes().decimals(1).stripZeros().format(); // "1.5 KiB" // Percentage formatting num(0.5).percentage().format(); // "50.00%" num(50).percentage(false).format(); // "50.00%" num(0.5).percentage().percentageSpace().format(); // "50.00 %" num(0.12345).percentage().decimals(3).format(); // "12.345%" // Abbreviated numbers num(1234).locale('en-US').abbreviated().format(); // "1.23k" num(1234567).locale('pt-BR').decimals(2).abbreviated().format(); // "1,23 mi" num(1000).locale('en-US').decimals(2).abbreviated().stripZeros().format(); // "1k" // Mask formatting num(12345678).mask('##.###.###').format(); // "12.345.678" num(12345678901).locale('pt-BR').mask('cpf').format(); // "123.456.789-01" num(12345678000190).locale('pt-BR').mask('cnpj').format(); // "12.345.678/0001-90" // Complex method chaining num(1234.5678) .locale('pt-BR') .decimals(3) .stripZeros() .format(); // "1.234,568" num(1234.56) .locale('pt-BR') .currency('EUR') .decimals(3) .format(); // "1.234,560 €" num(0.12345) .locale('pt-BR') .percentage() .decimals(1) .percentageSpace() .format(); // "12,3 %" // Accessibility - screen reader friendly text num(1200000).locale('pt-BR').decimals(1).abbreviated().toAccessible(); // "um ponto dois milhões" num(1234).locale('pt-BR').currency('BRL').decimals(0).toAccessible(); // "mil duzentos e trinta e quatro de reais" num(1500).locale('en-US').decimals(1).abbreviated().toAccessible(); // "one point five thousand" // Parsing (reverse of formatting) - convert formatted strings back to numbers Num.parse('$1,234.56'); // 1234.56 Num.parse('R$ 1.234,56', 'pt-BR'); // 1234.56 Num.parse('1.5k'); // 1500 Num.parse('2.3M'); // 2300000 Num.parse('45.5%'); // 0.455 Num.parse('1.5 KB'); // 1536 // Structured formatting (parts) - decompose formatted numbers for granular CSS styling num(1234.56).locale('pt-BR').toParts(); // [ // { type: 'integer', value: '1.234' }, // { type: 'decimal', value: ',' }, // { type: 'fraction', value: '56' } // ] num(1234.56).currency('USD').toParts(); // [ // { type: 'currency', value: '$' }, // { type: 'integer', value: '1,234' }, // { type: 'decimal', value: '.' }, // { type: 'fraction', value: '56' } // ] num(1500).abbreviated().toParts(); // [ // { type: 'integer', value: '1' }, // { type: 'decimal', value: '.' }, // { type: 'fraction', value: '5' }, // { type: 'unit', value: 'k' } // ] // Using constructor new Num(1234.56).locale('pt-BR').format(); // "1.234,56" // toString and valueOf const numInstance = num(1234.56).locale('pt-BR'); numInstance.toString(); // "1.234,56" numInstance.valueOf(); // 1234.56 String(numInstance); // "1.234,56" Number(numInstance); // 1234.56 ``` ## Plugin System `Num.extend()` enables third parties to bolt custom behavior onto the fluent API (wrappers, experimental formatters, telemetry hooks, etc.) without forking core. - Register once per process: `Num.extend(plugin)` is idempotent. - Plugins receive a safe context with helpers to read or patch the internal state, reuse the built-in services (`formatCurrency`, `round`, etc.) and spawn fresh instances via `createInstance`. - TypeScript users can augment the `Num` interface to describe new chainable methods. ```ts import { Num, num, type NumPlugin } from 'num-beauty'; import type { SupportedLocale } from 'num-beauty/locales'; declare module 'num-beauty' { interface Num { double(): this; forceLocale(locale: SupportedLocale, decimals?: number): this; } } const doublePlugin: NumPlugin = ({ Num, getState, patchState }) => { Num.prototype.double = function (this: Num) { const { value } = getState(this); patchState(this, { value: value * 2 }); return this; }; }; const forceLocalePlugin: NumPlugin = ({ Num, patchState }) => { Num.prototype.forceLocale = function (this: Num, locale: SupportedLocale, decimals = 2) { patchState(this, { locale, decimals }); return this; }; }; Num.extend(doublePlugin); Num.extend(forceLocalePlugin); num(21).double().valueOf(); // 42 num(1234.56).forceLocale('pt-BR', 3).format(); // "1.234,560" ``` `NumPluginContext` exposes: - `Num`: the class/prototype you can augment. - `createInstance(value)`: same behavior as `num(value)`. - `services`: references such as `round`, `formatNumber`, `formatCurrency`, etc. - `getState(instance)`: read-only snapshot of the fluent state (value, locale, decimals...). - `patchState(instance, patch)`: atomically update the internal state without touching private fields. Wrappers (React, Solid, server-side renderers) can now bundle their own helpers as small plugins instead of re-implementing the fluent internals. ## React Integration `num-beauty` provides first-class React support with hooks and components for declarative number formatting: ```bash # React is an optional peer dependency npm install num-beauty react ``` ### useNumBeauty Hook ```tsx import { useNumBeauty } from 'num-beauty/react'; function PriceDisplay({ price }: { price: number }) { const { formatted, parts } = useNumBeauty(price, { locale: 'en-US', currency: 'USD', }); return <span className="price">{formatted}</span>; // Output: $1,234.56 } // With parts for granular styling function StyledNumber({ value }: { value: number }) { const { parts } = useNumBeauty(value, { locale: 'pt-BR' }); return ( <span> {parts.map((part, i) => ( <span key={i} className={`num-${part.type}`}> {part.value} </span> ))} </span> ); } ``` ### NumDisplay Component ```tsx import { NumDisplay } from 'num-beauty/react'; // Basic usage <NumDisplay value={1234.56} locale="pt-BR" /> // Output: 1.234,56 // Currency formatting <NumDisplay value={1234.56} currency="USD" /> // Output: $1,234.56 // Styled parts for CSS customization <NumDisplay value={1234.56} currency="USD" styled className="price" /> // Output: // <span class="price" role="text"> // <span class="num-currency">$</span> // <span class="num-integer">1,234</span> // <span class="num-decimal">.</span> // <span class="num-fraction">56</span> // </span> // Custom rendering with renderPart <NumDisplay value={1234.56} currency="EUR" styled renderPart={(part, i) => ( <span key={i} className={`custom-${part.type}`} style={{ color: part.type === 'currency' ? 'green' : 'inherit', fontWeight: part.type === 'integer' ? 'bold' : 'normal' }} > {part.value} </span> )} /> // Bytes, percentage, abbreviated <NumDisplay value={1048576} bytes /> <NumDisplay value={0.5} percentage /> <NumDisplay value={1234567} abbreviated locale="pt-BR" /> ``` ### NumParts Component (Render Props) ```tsx import { NumParts } from 'num-beauty/react'; <NumParts value={1234.56} currency="USD"> {(part, index) => ( <span key={index} className={`part-${part.type}`} style={{ color: part.type === 'currency' ? 'green' : 'black' }} > {part.value} </span> )} </NumParts> // With Framer Motion for animations <NumParts value={count} abbreviated> {(part, index) => ( <motion.span key={index} initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: index * 0.1 }} > {part.value} </motion.span> )} </NumParts> ``` ### Available Methods - **`locale(code)`** - Set the locale for formatting - **`decimals(n)`** - Set the number of decimal places - **`abbreviated()`** - Enable number abbreviation (k, M, B, T) - **`stripZeros()`** - Remove trailing zeros from decimals - **`rounding(mode)`** - Set the rounding mode (UP, DOWN, CEIL, FLOOR, HALF_UP, HALF_DOWN, HALF_EVEN) - **`mask(pattern)`** - Apply a mask pattern - **`currency(code)`** - Format as currency - **`hideSymbol()`** - Hide currency symbol - **`showCode()`** - Show currency code instead of symbol - **`bytes(binary)`** - Format as bytes (binary=true for 1024, false for 1000) - **`bytesLongFormat()`** - Use long format for bytes (Megabytes instead of MB) - **`percentage(multiply)`** - Format as percentage (multiply=true to multiply by 100) - **`percentageSpace(addSpace)`** - Add space before % symbol - **`toAccessible()`** - Convert formatted number to screen-reader friendly text - **`format()`** - Execute formatting and return string - **`toString()`** - Alias for format() - **`valueOf()`** - Return the original numeric value - **`Num.parse(input, locale)`** - Static method to parse formatted strings back to numbers ```typescript // Predefined masks console.log(beautify(123456789, { locale: 'en-US', mask: 'ssn' })); // "123-45-6789" console.log(beautify(123456789, { locale: 'en-US', mask: 'ein' })); // "12-3456789" console.log(beautify(12345, { locale: 'en-US', mask: 'zip' })); // "12345" console.log(beautify(2345678901, { locale: 'en-US', mask: 'phone' })); // "+1 (234) 567-8901" // Custom masks console.log(beautify(123456, { mask: '##-##-##' })); // "12-34-56" ``` ## Usage Examples ### Currency Formatting ```typescript // US Dollar beautify(1234.56, { locale: 'en-US', currency: 'USD' }) // "$1,234.56" // Euro beautify(1234.56, { locale: 'es-ES', currency: 'EUR' }) // "1.234,56 €" // Brazilian Real beautify(1234.56, { locale: 'pt-BR', currency: 'BRL' }) // "R$ 1.234,56" ``` ### Bytes Formatting ```typescript import { formatBytes } from 'num-beauty'; // Binary base (default - 1024) formatBytes(1024) // "1.00 KiB" formatBytes(1048576) // "1.00 MiB" formatBytes(1073741824) // "1.00 GiB" // Decimal base (1000) formatBytes(1000, { binary: false }) // "1.00 KB" formatBytes(1000000, { binary: false }) // "1.00 MB" // Custom precision formatBytes(1536, { decimals: 1 }) // "1.5 KiB" formatBytes(1536, { decimals: 0 }) // "2 KiB" // Strip zeros formatBytes(2048, { stripZeros: true }) // "2 KiB" // Long format formatBytes(1024, { longFormat: true }) // "1.00 Kibibyte" formatBytes(2048, { longFormat: true }) // "2.00 Kibibytes" // Different locales formatBytes(1536, { locale: 'pt-BR' }) // "1,50 KiB" formatBytes(1536, { locale: 'es-ES' }) // "1,50 KiB" // Real world examples formatBytes(524288) // "512.00 KiB" (image file) formatBytes(157286400, { decimals: 1 }) // "150.0 MiB" (video file) formatBytes(5368709120, { decimals: 1 }) // "5.0 GiB" (large database) ``` ### Percentage Formatting ```typescript import { formatPercentage } from 'num-beauty'; // Basic formatting (multiplies by 100 by default) formatPercentage(0.5) // "50.00%" formatPercentage(0.25) // "25.00%" formatPercentage(0.12345, { decimals: 1 }) // "12.3%" // Without multiplication formatPercentage(50, { multiply: false }) // "50.00%" formatPercentage(12.5, { multiply: false }) // "12.50%" // Custom precision formatPercentage(0.5, { decimals: 0 }) // "50%" formatPercentage(0.12345, { decimals: 3 }) // "12.345%" // Strip zeros formatPercentage(0.5, { stripZeros: true }) // "50%" formatPercentage(0.125, { stripZeros: true }) // "12.5%" // Different locales formatPercentage(0.5, { locale: 'en-US' }) // "50.00%" formatPercentage(0.5, { locale: 'pt-BR' }) // "50,00 %" formatPercentage(0.5, { locale: 'es-ES' }) // "50,00 %" // Custom spacing formatPercentage(0.5, { addSpace: true }) // "50.00 %" formatPercentage(0.5, { locale: 'pt-BR', addSpace: false }) // "50,00%" // Real world examples formatPercentage(0.0525, { decimals: 2 }) // "5.25%" (interest rate) formatPercentage(0.15, { decimals: 0 }) // "15%" (tax rate) formatPercentage(0.30, { stripZeros: true }) // "30%" (discount) formatPercentage(-0.05, { decimals: 1 }) // "-5.0%" (loss) ``` ### Dynamic Locale Registration You can register custom locales or override existing ones at runtime: ```typescript import { registerLocale, hasLocale, getRegisteredLocales, beautify } from 'num-beauty'; // Check if a locale exists hasLocale('ja-JP') // false // Register Japanese locale registerLocale('ja-JP', { masks: { phone: '###-####-####', postal: '###-####' }, currencies: { JPY: { symbol: '¥', position: 'before' } }, units: [ ['', ''], ['千', '千'], // thousand ['万', '万'], // ten thousand ['億', '億'] // hundred million ] }); // Use the registered locale beautify(1234567, { locale: 'ja-JP' }) // "1,234,567" formatCurrency(50000, 'ja-JP', { currency: 'JPY' }) // "¥ 50,000.00" beautify(1000000, { locale: 'ja-JP', abbreviated: true }) // "1 万" // Override existing locale registerLocale('en-US', { units: [ ['', ''], ['thousand', 'thousand'], ['million', 'million'], ['billion', 'billion'] ] }); beautify(1000, { locale: 'en-US', abbreviated: true }) // "1thousand" // List all registered locales getRegisteredLocales() // ['en-US', 'pt-BR', 'es-ES', 'de-DE', 'fr-FR', 'ja-JP'] ### Lazy Loading (On-Demand Locales) By default, all built-in locales are registered at initialization for backward compatibility. For applications that need to optimize initial bundle size or load locales on-demand (e.g., based on user preferences), `num-beauty` provides a lazy loading system: ```typescript import { loadLocale, isLocaleLoaded, preloadLocales, registerLocaleLoader } from 'num-beauty/locales/loader'; import { formatCurrency } from 'num-beauty'; // Check if a locale is loaded isLocaleLoaded('pt-BR') // true (built-in locales are pre-registered) // Load a locale dynamically (useful for custom locales) await loadLocale('pt-BR'); formatCurrency(1234.56, 'pt-BR', { currency: 'BRL' }) // "R$ 1.234,56" // Preload multiple locales in parallel await preloadLocales(['es-ES', 'de-DE', 'fr-FR']); // Register a custom locale loader registerLocaleLoader('it-IT', async () => ({ locale: { masks: {}, currencies: { EUR: { symbol: '€', position: 'after' } }, units: [ ['k', 'k'], ['M', 'M'], ['mld', 'mld'], // billions in Italian ['bln', 'bln'] // trillions in Italian ] } })); // Load and use the custom locale await loadLocale('it-IT'); formatCurrency(1234.56, 'it-IT', { currency: 'EUR' }) // "1.234,56 €" ``` **Benefits of Lazy Loading:** - **Reduced Initial Bundle**: Only core functionality is bundled initially (~6.67 KB gzipped for full library) - **On-Demand Loading**: Locales loaded only when needed (366-483 B gzipped per locale) - **Custom Locales**: Register and load locales from external sources without bundling - **React Integration**: Works seamlessly with React components and hooks **React Example:** ```typescript import { loadLocale } from 'num-beauty/locales/loader'; import { useNumBeauty } from 'num-beauty/react'; import { useEffect, useState } from 'react'; function CurrencyDisplay({ amount }: { amount: number }) { const [locale, setLocale] = useState('en-US'); const { formatCurrency } = useNumBeauty(); // Load locale when user changes preference useEffect(() => { loadLocale(locale).catch(console.error); }, [locale]); return ( <div> <select onChange={(e) => setLocale(e.target.value)}> <option value="en-US">English (US)</option> <option value="pt-BR">Português (BR)</option> <option value="es-ES">Español</option> </select> <p>{formatCurrency(amount, locale, { currency: 'USD' })}</p> </div> ); } ``` ### Abbreviations by Locale ```typescript const number = 1234567.89; // en-US beautify(number, { locale: 'en-US', abbreviated: true }) // "1.23M" // pt-BR beautify(number, { locale: 'pt-BR', abbreviated: true }) // "1,23 mi" // es-ES beautify(number, { locale: 'es-ES', abbreviated: true }) // "1,23M" // de-DE beautify(number, { locale: 'de-DE', abbreviated: true }) // "1,23 Mio." // fr-FR beautify(number, { locale: 'fr-FR', abbreviated: true }) // "1,23 M" ``` ### Advanced Masks ```typescript // Custom mask with prefix beautify(123456, { mask: 'ID: ###-###' }) // "ID: 123-456" // Credit card with masking beautify(1234567890123456, { mask: '**** **** **** ####' }) // "**** **** **** 3456" // Product code formatting beautify(123456789, { mask: 'PRD-####-####-#' }) // "PRD-1234-5678-9" ``` ### Special Cases ```typescript // Very small numbers beautify(0.00001234, { decimals: 6 }) // "0.000012" // Scientific notation beautify(1.23e7, { abbreviated: true }) // "12.30M" // Zero padding beautify(1.2, { decimals: 3, stripZeros: false }) // "1.200" ``` ## Options - `locale`: String (default: 'en-US') - The locale to use for formatting - `decimals`: Number (default: 2) - Number of decimal places - `abbreviated`: Boolean (default: false) - Whether to use abbreviated format - `stripZeros`: Boolean (default: false) - Remove unnecessary zeros from decimal part - `roundingMode`: String (default: 'HALF_UP') - Rounding mode to use - `mask`: String | PredefinedMask - Formatting mask to apply ### Predefined Masks by Locale (examples) #### American English (en-US) - `ssn` - Social Security Number: ###-##-#### - `ein` - Employer ID Number: ##-####### - `zip` - ZIP Code: ##### - `phone` - Phone: (###) ###-#### - `tax-id` - Tax ID (SSN): ###-##-#### #### Brazilian Portuguese (pt-BR) - `cpf` - CPF: ###.###.###-## - `cnpj` - CNPJ: ##.###.###/####-## - `cep` - CEP: #####-### - `phone` - Phone: (##) #####-#### - `tax-id` - Tax ID (CPF): ###.###.###-## #### Spanish (es-ES) - `nif` - NIF: ########-# - `nie` - NIE: #-######## - `phone` - Phone: (###) ### ### You can also create custom masks using the `#` character as a digit placeholder. ### Rounding Modes - `UP` - Rounds away from zero - `DOWN` - Rounds toward zero - `CEIL` - Rounds toward positive infinity - `FLOOR` - Rounds toward negative infinity - `HALF_UP` - Rounds to nearest, ties away from zero - `HALF_DOWN` - Rounds to nearest, ties toward zero - `HALF_EVEN` - Rounds to nearest, ties to even neighbor ## Formats by Locale ### American English (en-US) — formats - **Separators:** - Decimal: period (.) - Thousands: comma (,) - **Abbreviations:** k, m, b, t - **Masks:** - SSN: `123-45-6789` - EIN: `12-3456789` - ZIP: `12345` - Phone: `+1 (234) 567-8901` - Credit Card: `1234 5678 9012 3456` ### Brazilian Portuguese (pt-BR) — formats - **Separators:** - Decimal: comma (,) - Thousands: period (.) - **Abbreviations:** mil, mi, bi, tri - **Masks:** - CPF: `123.456.789-01` - CNPJ: `12.345.678/0001-99` - CEP: `12345-678` - Phone: `(11) 99999-8888` - Credit Card: `1234 5678 9012 3456` ### Spanish (es-ES) — formats - **Separators:** - Decimal: comma (,) - Thousands: period (.) - **Abbreviations:** mil, M, MM, B ## Runtime Compatibility `num-beauty` is tested and guaranteed to work across multiple JavaScript runtimes: | Runtime | Versions | Status | |---------|----------|--------| | Node.js | 18.x, 20.x, 22.x | ✅ Fully Supported | | Deno | 1.x, 2.x | ✅ Fully Supported | | Bun | latest | ✅ Fully Supported | | Browsers | Modern (ES2020+) | ✅ Fully Supported | ### Testing Locally ```bash # Test with Node.js npm run test:runtime # Test with all runtimes (requires Deno and Bun installed) npm run test:runtime:all # Test with Deno deno run --allow-read tests/runtime/test-runtime.mjs # Test with Bun bun tests/runtime/test-runtime.mjs ``` See [tests/runtime/README.md](tests/runtime/README.md) for more details. ## Contributing 1. Fork the project 2. Create your feature branch (`git checkout -b feature/MyFeature`) 3. Commit your changes (`git commit -am 'Adding a feature'`) 4. Push to the branch (`git push origin feature/MyFeature`) 5. Create a Pull Request ## License MIT