102 lines
3.1 KiB
TypeScript
102 lines
3.1 KiB
TypeScript
import { FrontendLocaleData, NumberFormat } from "../../data/translation";
|
|
import { round } from "./round";
|
|
|
|
export const numberFormatToLocale = (
|
|
localeOptions: FrontendLocaleData
|
|
): string | string[] | undefined => {
|
|
switch (localeOptions.number_format) {
|
|
case NumberFormat.comma_decimal:
|
|
return ["en-US", "en"]; // Use United States with fallback to English formatting 1,234,567.89
|
|
case NumberFormat.decimal_comma:
|
|
return ["de", "es", "it"]; // Use German with fallback to Spanish then Italian formatting 1.234.567,89
|
|
case NumberFormat.space_comma:
|
|
return ["fr", "sv", "cs"]; // Use French with fallback to Swedish and Czech formatting 1 234 567,89
|
|
case NumberFormat.system:
|
|
return undefined;
|
|
default:
|
|
return localeOptions.language;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Formats a number based on the user's preference with thousands separator(s) and decimal character for better legibility.
|
|
*
|
|
* @param num The number to format
|
|
* @param locale The user-selected language and number format, from `hass.locale`
|
|
* @param options Intl.NumberFormatOptions to use
|
|
*/
|
|
export const formatNumber = (
|
|
num: string | number,
|
|
localeOptions?: FrontendLocaleData,
|
|
options?: Intl.NumberFormatOptions
|
|
): string => {
|
|
const locale = localeOptions
|
|
? numberFormatToLocale(localeOptions)
|
|
: undefined;
|
|
|
|
// Polyfill for Number.isNaN, which is more reliable than the global isNaN()
|
|
Number.isNaN =
|
|
Number.isNaN ||
|
|
function isNaN(input) {
|
|
return typeof input === "number" && isNaN(input);
|
|
};
|
|
|
|
if (
|
|
localeOptions?.number_format !== NumberFormat.none &&
|
|
!Number.isNaN(Number(num)) &&
|
|
Intl
|
|
) {
|
|
try {
|
|
return new Intl.NumberFormat(
|
|
locale,
|
|
getDefaultFormatOptions(num, options)
|
|
).format(Number(num));
|
|
} catch (error) {
|
|
// Don't fail when using "TEST" language
|
|
// eslint-disable-next-line no-console
|
|
console.error(error);
|
|
return new Intl.NumberFormat(
|
|
undefined,
|
|
getDefaultFormatOptions(num, options)
|
|
).format(Number(num));
|
|
}
|
|
}
|
|
if (typeof num === "string") {
|
|
return num;
|
|
}
|
|
return `${round(num, options?.maximumFractionDigits).toString()}${
|
|
options?.style === "currency" ? ` ${options.currency}` : ""
|
|
}`;
|
|
};
|
|
|
|
/**
|
|
* Generates default options for Intl.NumberFormat
|
|
* @param num The number to be formatted
|
|
* @param options The Intl.NumberFormatOptions that should be included in the returned options
|
|
*/
|
|
const getDefaultFormatOptions = (
|
|
num: string | number,
|
|
options?: Intl.NumberFormatOptions
|
|
): Intl.NumberFormatOptions => {
|
|
const defaultOptions: Intl.NumberFormatOptions = {
|
|
maximumFractionDigits: 2,
|
|
...options,
|
|
};
|
|
|
|
if (typeof num !== "string") {
|
|
return defaultOptions;
|
|
}
|
|
|
|
// Keep decimal trailing zeros if they are present in a string numeric value
|
|
if (
|
|
!options ||
|
|
(!options.minimumFractionDigits && !options.maximumFractionDigits)
|
|
) {
|
|
const digits = num.indexOf(".") > -1 ? num.split(".")[1].length : 0;
|
|
defaultOptions.minimumFractionDigits = digits;
|
|
defaultOptions.maximumFractionDigits = digits;
|
|
}
|
|
|
|
return defaultOptions;
|
|
};
|