fluxer/packages/number_utils/src/NumberFormatting.tsx
2026-02-17 12:22:36 +00:00

205 lines
6.7 KiB
TypeScript

/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {
DEFAULT_COMPACT_MAX_FRACTION_DIGITS,
DEFAULT_NUMBER_FALLBACK,
DEFAULT_NUMBER_LOCALE,
} from '@fluxer/number_utils/src/NumberConstants';
import {getNumberFormatter} from '@fluxer/number_utils/src/NumberFormatterCache';
import {parseNumberInput} from '@fluxer/number_utils/src/NumberParsing';
import type {
BoundCompactNumberFormatOptions,
BoundCurrencyNumberFormatOptions,
CompactNumberFormatOptions,
CurrencyNumberFormatOptions,
INumberFormatter,
NumberFormatBaseOptions,
NumberFormatOptions,
NumberFormatterFactoryOptions,
NumberInput,
} from '@fluxer/number_utils/src/NumberTypes';
interface ResolvedNumberFormatBaseOptions {
locale: string;
fallbackValue: number;
}
interface CompactFormatOptionsInput {
maximumFractionDigits?: number;
minimumFractionDigits?: number;
}
interface CurrencyFormatOptionsInput {
currency: string;
numberFormatOptions?: Omit<Intl.NumberFormatOptions, 'style' | 'currency'>;
}
function resolveBaseOptions(options?: NumberFormatBaseOptions): ResolvedNumberFormatBaseOptions {
return {
locale: options?.locale ?? DEFAULT_NUMBER_LOCALE,
fallbackValue: options?.fallbackValue ?? DEFAULT_NUMBER_FALLBACK,
};
}
function resolveNumberFormatOptions(optionsOrLocale: NumberFormatOptions | string | undefined): NumberFormatOptions {
if (typeof optionsOrLocale === 'string') {
return {locale: optionsOrLocale};
}
return optionsOrLocale ?? {};
}
function resolveCompactFormatOptions(
optionsOrLocale: CompactNumberFormatOptions | string | undefined,
maximumFractionDigits: number | undefined,
): CompactNumberFormatOptions {
if (typeof optionsOrLocale === 'string') {
return {
locale: optionsOrLocale,
maximumFractionDigits,
};
}
if (optionsOrLocale === undefined) {
if (maximumFractionDigits === undefined) {
return {};
}
return {maximumFractionDigits};
}
if (maximumFractionDigits === undefined) {
return optionsOrLocale;
}
return {
...optionsOrLocale,
maximumFractionDigits: optionsOrLocale.maximumFractionDigits ?? maximumFractionDigits,
};
}
function resolveCurrencyFormatOptions(
optionsOrCurrency: CurrencyNumberFormatOptions | string,
locale: string | undefined,
): CurrencyNumberFormatOptions {
if (typeof optionsOrCurrency === 'string') {
return {
currency: optionsOrCurrency,
locale,
};
}
return optionsOrCurrency;
}
function formatNumberValue(
value: NumberInput,
resolvedOptions: ResolvedNumberFormatBaseOptions,
numberFormatOptions: Intl.NumberFormatOptions = {},
): string {
const parsedValue = parseNumberInput(value, resolvedOptions.fallbackValue);
return getNumberFormatter(resolvedOptions.locale, numberFormatOptions).format(parsedValue);
}
function buildCompactFormatOptions(options: CompactFormatOptionsInput): Intl.NumberFormatOptions {
const numberFormatOptions: Intl.NumberFormatOptions = {
notation: 'compact',
maximumFractionDigits: options.maximumFractionDigits ?? DEFAULT_COMPACT_MAX_FRACTION_DIGITS,
};
if (options.minimumFractionDigits !== undefined) {
numberFormatOptions.minimumFractionDigits = options.minimumFractionDigits;
}
return numberFormatOptions;
}
function buildCurrencyFormatOptions(options: CurrencyFormatOptionsInput): Intl.NumberFormatOptions {
return {
...options.numberFormatOptions,
style: 'currency',
currency: options.currency,
};
}
export function parseNumber(value: NumberInput, options: NumberFormatBaseOptions = {}): number {
const resolvedOptions = resolveBaseOptions(options);
return parseNumberInput(value, resolvedOptions.fallbackValue);
}
export function formatNumber(value: NumberInput, locale?: string): string;
export function formatNumber(value: NumberInput, options?: NumberFormatOptions): string;
export function formatNumber(value: NumberInput, optionsOrLocale: NumberFormatOptions | string = {}): string {
const options = resolveNumberFormatOptions(optionsOrLocale);
const resolvedOptions = resolveBaseOptions(options);
return formatNumberValue(value, resolvedOptions, options.numberFormatOptions);
}
export function formatCompactNumber(value: NumberInput, locale?: string, maximumFractionDigits?: number): string;
export function formatCompactNumber(value: NumberInput, options?: CompactNumberFormatOptions): string;
export function formatCompactNumber(
value: NumberInput,
optionsOrLocale: CompactNumberFormatOptions | string = {},
maximumFractionDigits?: number,
): string {
const options = resolveCompactFormatOptions(optionsOrLocale, maximumFractionDigits);
const resolvedOptions = resolveBaseOptions(options);
return formatNumberValue(value, resolvedOptions, buildCompactFormatOptions(options));
}
export function formatCurrency(value: NumberInput, currency: string, locale?: string): string;
export function formatCurrency(value: NumberInput, options: CurrencyNumberFormatOptions): string;
export function formatCurrency(
value: NumberInput,
optionsOrCurrency: CurrencyNumberFormatOptions | string,
locale?: string,
): string {
const options = resolveCurrencyFormatOptions(optionsOrCurrency, locale);
const resolvedOptions = resolveBaseOptions(options);
return formatNumberValue(value, resolvedOptions, buildCurrencyFormatOptions(options));
}
export function createNumberFormatter(options: NumberFormatterFactoryOptions = {}): INumberFormatter {
const resolvedOptions = resolveBaseOptions(options);
function parse(value: NumberInput): number {
return parseNumberInput(value, resolvedOptions.fallbackValue);
}
function format(value: NumberInput, numberFormatOptions: Intl.NumberFormatOptions = {}): string {
return formatNumberValue(value, resolvedOptions, numberFormatOptions);
}
function formatCompact(value: NumberInput, compactOptions: BoundCompactNumberFormatOptions = {}): string {
return formatNumberValue(value, resolvedOptions, buildCompactFormatOptions(compactOptions));
}
function formatCurrency(value: NumberInput, currencyOptions: BoundCurrencyNumberFormatOptions): string {
return formatNumberValue(value, resolvedOptions, buildCurrencyFormatOptions(currencyOptions));
}
return {
parse,
format,
formatCompact,
formatCurrency,
};
}