|
|
|
// prettyBytes taken from https://github.com/sindresorhus/pretty-bytes
|
|
|
|
|
|
|
|
/* eslint-disable no-param-reassign */
|
|
|
|
const BYTE_UNITS = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
|
|
|
|
|
|
|
const BIBYTE_UNITS = ["B", "kiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
|
|
|
|
|
|
|
|
const BIT_UNITS = ["b", "kbit", "Mbit", "Gbit", "Tbit", "Pbit", "Ebit", "Zbit", "Ybit"];
|
|
|
|
|
|
|
|
const BIBIT_UNITS = ["b", "kibit", "Mibit", "Gibit", "Tibit", "Pibit", "Eibit", "Zibit", "Yibit"];
|
|
|
|
|
|
|
|
/*
|
|
|
|
Formats the given number using `Number#toLocaleString`.
|
|
|
|
- If locale is a string, the value is expected to be a locale-key (for example: `de`).
|
|
|
|
- If locale is true, the system default locale is used for translation.
|
|
|
|
- If no value for locale is specified, the number is returned unmodified.
|
|
|
|
*/
|
|
|
|
const toLocaleString = (number, locale, options) => {
|
|
|
|
let result = number;
|
|
|
|
if (typeof locale === "string" || Array.isArray(locale)) {
|
|
|
|
result = number.toLocaleString(locale, options);
|
|
|
|
} else if (locale === true || options !== undefined) {
|
|
|
|
result = number.toLocaleString(undefined, options);
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
|
|
|
function prettyBytes(number, options) {
|
|
|
|
if (!Number.isFinite(number)) {
|
|
|
|
throw new TypeError(`Expected a finite number, got ${typeof number}: ${number}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
options = {
|
|
|
|
bits: false,
|
|
|
|
binary: false,
|
|
|
|
...options,
|
|
|
|
};
|
|
|
|
|
|
|
|
// eslint-disable-next-line no-nested-ternary
|
|
|
|
const UNITS = options.bits ? (options.binary ? BIBIT_UNITS : BIT_UNITS) : options.binary ? BIBYTE_UNITS : BYTE_UNITS;
|
|
|
|
|
|
|
|
if (options.signed && number === 0) {
|
|
|
|
return ` 0 ${UNITS[0]}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
const isNegative = number < 0;
|
|
|
|
// eslint-disable-next-line no-nested-ternary
|
|
|
|
const prefix = isNegative ? "-" : options.signed ? "+" : "";
|
|
|
|
|
|
|
|
if (isNegative) {
|
|
|
|
number = -number;
|
|
|
|
}
|
|
|
|
|
|
|
|
let localeOptions;
|
|
|
|
|
|
|
|
if (options.minimumFractionDigits !== undefined) {
|
|
|
|
localeOptions = { minimumFractionDigits: options.minimumFractionDigits };
|
|
|
|
}
|
|
|
|
|
|
|
|
if (options.maximumFractionDigits !== undefined) {
|
|
|
|
localeOptions = { maximumFractionDigits: options.maximumFractionDigits, ...localeOptions };
|
|
|
|
}
|
|
|
|
|
|
|
|
if (number < 1) {
|
|
|
|
const numberString = toLocaleString(number, options.locale, localeOptions);
|
|
|
|
return `${prefix + numberString} ${UNITS[0]}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
const exponent = Math.min(
|
|
|
|
Math.floor(options.binary ? Math.log(number) / Math.log(1024) : Math.log10(number) / 3),
|
|
|
|
UNITS.length - 1,
|
|
|
|
);
|
|
|
|
number /= (options.binary ? 1024 : 1000) ** exponent;
|
|
|
|
|
|
|
|
if (!localeOptions) {
|
|
|
|
number = number.toPrecision(3);
|
|
|
|
}
|
|
|
|
|
|
|
|
const numberString = toLocaleString(Number(number), options.locale, localeOptions);
|
|
|
|
|
|
|
|
const unit = UNITS[exponent];
|
|
|
|
|
|
|
|
return `${prefix + numberString} ${unit}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
function duration(durationInSeconds, i18next) {
|
|
|
|
const mo = Math.floor(durationInSeconds / (3600 * 24 * 31));
|
|
|
|
const d = Math.floor((durationInSeconds % (3600 * 24 * 31)) / (3600 * 24));
|
|
|
|
const h = Math.floor((durationInSeconds % (3600 * 24)) / 3600);
|
|
|
|
const m = Math.floor((durationInSeconds % 3600) / 60);
|
|
|
|
const s = Math.floor(durationInSeconds % 60);
|
|
|
|
|
|
|
|
const moDisplay = mo > 0 ? mo + i18next.t("common.months") : "";
|
|
|
|
const dDisplay = d > 0 ? d + i18next.t("common.days") : "";
|
|
|
|
const hDisplay = h > 0 && mo === 0 ? h + i18next.t("common.hours") : "";
|
|
|
|
const mDisplay = m > 0 && mo === 0 && d === 0 ? m + i18next.t("common.minutes") : "";
|
|
|
|
const sDisplay = s > 0 && mo === 0 && d === 0 && h === 0 ? s + i18next.t("common.seconds") : "";
|
|
|
|
|
|
|
|
return (moDisplay + dDisplay + hDisplay + mDisplay + sDisplay).replace(/,\s*$/, "");
|
|
|
|
}
|
|
|
|
|
|
|
|
function relativeDate(date, formatter) {
|
|
|
|
const cutoffs = [60, 3600, 86400, 86400 * 7, 86400 * 30, 86400 * 365, Infinity];
|
|
|
|
const units = ["second", "minute", "hour", "day", "week", "month", "year"];
|
|
|
|
|
|
|
|
const delta = Math.round((date.getTime() - Date.now()) / 1000);
|
|
|
|
const unitIndex = cutoffs.findIndex((cutoff) => cutoff > Math.abs(delta));
|
|
|
|
const divisor = unitIndex ? cutoffs[unitIndex - 1] : 1;
|
|
|
|
|
|
|
|
return formatter.format(Math.floor(delta / divisor), units[unitIndex]);
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
i18n: {
|
|
|
|
defaultLocale: "en",
|
|
|
|
locales: ["en"],
|
|
|
|
},
|
|
|
|
serializeConfig: false,
|
|
|
|
use: [
|
|
|
|
{
|
|
|
|
init: (i18next) => {
|
|
|
|
i18next.services.formatter.add("bytes", (value, lng, options) =>
|
|
|
|
prettyBytes(parseFloat(value), { locale: lng, ...options }),
|
|
|
|
);
|
|
|
|
|
|
|
|
i18next.services.formatter.add("rate", (value, lng, options) => {
|
|
|
|
const k = options.binary ? 1024 : 1000;
|
|
|
|
const sizes = options.bits
|
|
|
|
? options.binary
|
|
|
|
? BIBIT_UNITS
|
|
|
|
: BIT_UNITS
|
|
|
|
: options.binary
|
|
|
|
? BIBYTE_UNITS
|
|
|
|
: BYTE_UNITS;
|
|
|
|
|
|
|
|
if (value === 0) return `0 ${sizes[0]}/s`;
|
|
|
|
|
|
|
|
const dm = options.decimals ? options.decimals : 0;
|
|
|
|
|
|
|
|
const i = options.binary ? 2 : Math.floor(Math.log(value) / Math.log(k));
|
|
|
|
|
|
|
|
const formatted = new Intl.NumberFormat(lng, { maximumFractionDigits: dm, minimumFractionDigits: dm }).format(
|
|
|
|
parseFloat(value / k ** i),
|
|
|
|
);
|
|
|
|
|
|
|
|
return `${formatted} ${sizes[i]}/s`;
|
|
|
|
});
|
|
|
|
|
|
|
|
i18next.services.formatter.add("percent", (value, lng, options) =>
|
|
|
|
new Intl.NumberFormat(lng, { style: "percent", ...options }).format(parseFloat(value) / 100.0),
|
|
|
|
);
|
|
|
|
i18next.services.formatter.add("date", (value, lng, options) =>
|
|
|
|
new Intl.DateTimeFormat(lng, { ...options }).format(new Date(value)),
|
|
|
|
);
|
|
|
|
i18next.services.formatter.add("relativeDate", (value, lng, options) =>
|
|
|
|
relativeDate(new Date(value), new Intl.RelativeTimeFormat(lng, { ...options })),
|
|
|
|
);
|
|
|
|
i18next.services.formatter.add("duration", (value, lng) => duration(value, i18next));
|
|
|
|
},
|
|
|
|
type: "3rdParty",
|
|
|
|
},
|
|
|
|
],
|
|
|
|
};
|