src/Formatter.js
import baseLogger from './logger.js';
import { getCleanedCode } from './utils.js';
function parseFormatStr(formatStr) {
let formatName = formatStr.toLowerCase().trim();
const formatOptions = {};
if (formatStr.indexOf('(') > -1) {
const p = formatStr.split('(');
formatName = p[0].toLowerCase().trim();
const optStr = p[1].substring(0, p[1].length - 1);
// extra for currency
if (formatName === 'currency' && optStr.indexOf(':') < 0) {
if (!formatOptions.currency) formatOptions.currency = optStr.trim();
} else if (formatName === 'relativetime' && optStr.indexOf(':') < 0) {
if (!formatOptions.range) formatOptions.range = optStr.trim();
} else {
const opts = optStr.split(';');
opts.forEach((opt) => {
if (!opt) return;
const [key, ...rest] = opt.split(':');
const val = rest
.join(':')
.trim()
.replace(/^'+|'+$/g, ''); // trim and replace ''
if (!formatOptions[key.trim()]) formatOptions[key.trim()] = val;
if (val === 'false') formatOptions[key.trim()] = false;
if (val === 'true') formatOptions[key.trim()] = true;
// eslint-disable-next-line no-restricted-globals
if (!isNaN(val)) formatOptions[key.trim()] = parseInt(val, 10);
});
}
}
return {
formatName,
formatOptions,
};
}
function createCachedFormatter(fn) {
const cache = {};
return function invokeFormatter(val, lng, options) {
const key = lng + JSON.stringify(options);
let formatter = cache[key];
if (!formatter) {
formatter = fn(getCleanedCode(lng), options);
cache[key] = formatter;
}
return formatter(val);
};
}
class Formatter {
constructor(options = {}) {
this.logger = baseLogger.create('formatter');
this.options = options;
this.formats = {
number: createCachedFormatter((lng, opt) => {
const formatter = new Intl.NumberFormat(lng, { ...opt });
return (val) => formatter.format(val);
}),
currency: createCachedFormatter((lng, opt) => {
const formatter = new Intl.NumberFormat(lng, { ...opt, style: 'currency' });
return (val) => formatter.format(val);
}),
datetime: createCachedFormatter((lng, opt) => {
const formatter = new Intl.DateTimeFormat(lng, { ...opt });
return (val) => formatter.format(val);
}),
relativetime: createCachedFormatter((lng, opt) => {
const formatter = new Intl.RelativeTimeFormat(lng, { ...opt });
return (val) => formatter.format(val, opt.range || 'day');
}),
list: createCachedFormatter((lng, opt) => {
const formatter = new Intl.ListFormat(lng, { ...opt });
return (val) => formatter.format(val);
}),
};
this.init(options);
}
/* eslint no-param-reassign: 0 */
init(services, options = { interpolation: {} }) {
const iOpts = options.interpolation;
this.formatSeparator = iOpts.formatSeparator
? iOpts.formatSeparator
: iOpts.formatSeparator || ',';
}
add(name, fc) {
this.formats[name.toLowerCase().trim()] = fc;
}
addCached(name, fc) {
this.formats[name.toLowerCase().trim()] = createCachedFormatter(fc);
}
format(value, format, lng, options = {}) {
const formats = format.split(this.formatSeparator);
const result = formats.reduce((mem, f) => {
const { formatName, formatOptions } = parseFormatStr(f);
if (this.formats[formatName]) {
let formatted = mem;
try {
// options passed explicit for that formatted value
const valOptions =
(options && options.formatParams && options.formatParams[options.interpolationkey]) ||
{};
// language
const l = valOptions.locale || valOptions.lng || options.locale || options.lng || lng;
formatted = this.formats[formatName](mem, l, {
...formatOptions,
...options,
...valOptions,
});
} catch (error) {
this.logger.warn(error);
}
return formatted;
// eslint-disable-next-line no-else-return
} else {
this.logger.warn(`there was no format function for ${formatName}`);
}
return mem;
}, value);
return result;
}
}
export default Formatter;