vue-i18n
vue-i18n copied to clipboard
Allow format number with any locale, and not the only described in numberFormats object
Clear and concise description of the problem
In version 8 I was able to format numbers with <i18n-n> component to any locale I specify in locale property. All I had to do is to generate proper options like this:
new Intl.NumberFormat(this.locale, {key: 'currency', style: 'currency'}).resolvedOptions()
in numberFormats object I had only one locale with the 'currency' property.
During migration to version 9 I faced the problem that the number are not formatted. Only when I added additional locales inside numberFormats numbers became formatted. This is not handy since I want to have a universal tool and be able to format numbers even for locales where I don't have any translations. With the minimum numberFormats configuration, like this:
numberFormats: {
en: {
currency: {
style: 'currency',
currencyDisplay: 'symbol',
minimumFractionDigits: 0,
maximumFractionDigits: 2
}
}
}
Suggested solution
I'm not familiar with the JS build tools, but I debugged this file node_modules/vue-i18n/dist/vue-i18n.global.js and found that there is no option to format a number unless you did not specify required locale in numberFormats object.
Little stack trace:
there is a declaration of const NumberFormat, inside its setup method renderFormatter call, one of arguments is i18n[NumberPartsSymbol](...args), which is equivalent for numberParts function. numberParts function has a call of number function. It's purpose to return parts of a number mainly made with Intl.NumberFormat.
So the number function has a loop through locales, and it contains a requested locale and a fallback locale. The problem is that there is a condition to break the loop only if there is a declared locale in numberFormats object.
Here is a snippet (I added a comments with capital letters):
// implementation of `number` function
function number(context, ...args) {
const { numberFormats, unresolving, fallbackLocale, onWarn, localeFallbacker } = context;
const { __numberFormatters } = context;
if (!Availabilities.numberFormat) {
onWarn(getWarnMessage$1(CoreWarnCodes.CANNOT_FORMAT_NUMBER));
return MISSING_RESOLVE_VALUE;
}
const [key, value, options, overrides] = parseNumberArgs(...args);
const missingWarn = isBoolean(options.missingWarn)
? options.missingWarn
: context.missingWarn;
const fallbackWarn = isBoolean(options.fallbackWarn)
? options.fallbackWarn
: context.fallbackWarn;
const part = !!options.part;
const locale = isString(options.locale) ? options.locale : context.locale;
const locales = localeFallbacker(context, // eslint-disable-line @typescript-eslint/no-explicit-any
fallbackLocale, locale);
if (!isString(key) || key === '') {
return new Intl.NumberFormat(locale, overrides).format(value);
}
// resolve format
let numberFormat = {};
let targetLocale;
let format = null;
let from = locale;
let to = null;
const type = 'number format';
for (let i = 0; i < locales.length; i++) {
targetLocale = to = locales[i];
if (locale !== targetLocale &&
isTranslateFallbackWarn(fallbackWarn, key)) {
onWarn(getWarnMessage$1(CoreWarnCodes.FALLBACK_TO_NUMBER_FORMAT, {
key,
target: targetLocale
}));
}
// for vue-devtools timeline event
if (locale !== targetLocale) {
const emitter = context.__v_emitter;
if (emitter) {
emitter.emit("fallback" /* FALBACK */, {
type,
key,
from,
to,
groupId: `${type}:${key}`
});
}
}
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// THE LAST LOCALE IN locales ARRAY IS FALLBACK LOCALE
// THIS MEANS IF YOU DON'T HAVE LOCALE DECLARED IN numberFormats WITH THE EXPECTED KEY,
// YOU WON'T EXIT THE LOOP AND IN THE END FALLBACK LOCALE WILL BE CHOSEN
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
numberFormat =
numberFormats[targetLocale] || {};
format = numberFormat[key];
if (isPlainObject(format))
break;
handleMissing(context, key, targetLocale, missingWarn, type); // eslint-disable-line @typescript-eslint/no-explicit-any
from = to;
}
// checking format and target locale
if (!isPlainObject(format) || !isString(targetLocale)) {
return unresolving ? NOT_REOSLVED : key;
}
let id = `${targetLocale}__${key}`;
if (!isEmptyObject(overrides)) {
id = `${id}__${JSON.stringify(overrides)}`;
}
let formatter = __numberFormatters.get(id);
if (!formatter) {
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// HERE IN overrides I HAVE AN OBJECT GENERATED FROM Intl.NumberFormat(this.locale, this.format).resolvedOptions(),
// SO IT'S ABSOLUTELY NOT NECESSARY TO HAVE ANYTHING DECLARED IN numberFormats,
// ALL THOSE minimumFractionDigits: 0 and maximumFractionDigits: 2 ARE TAKEN FROM Intl.
// BUT EVEN WITH THE CORRECT LOCALE-SPECIFIC OBJECT IN new Intl.NumberFormat MAIN IS THE FIRST ARGUMENT - targetLocale
// BECAUSE NEXT 2 EXAMPLES GENERATE DIFFERENT PARTS
// new Intl.NumberFormat('en', {"style":"currency","currency":"USD","currencyDisplay":"symbol","currencySign":"standard","minimumIntegerDigits":1,"minimumFractionDigits":0,"maximumFractionDigits":0,"useGrouping":"auto","notation":"standard","signDisplay":"auto","roundingMode":"halfExpand","roundingIncrement":1,"trailingZeroDisplay":"auto","roundingPriority":"auto"}).formatToParts(147000.10)
// new Intl.NumberFormat('uk', {"style":"currency","currency":"USD","currencyDisplay":"symbol","currencySign":"standard","minimumIntegerDigits":1,"minimumFractionDigits":0,"maximumFractionDigits":0,"useGrouping":"auto","notation":"standard","signDisplay":"auto","roundingMode":"halfExpand","roundingIncrement":1,"trailingZeroDisplay":"auto","roundingPriority":"auto"}).formatToParts(147000.10)
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
formatter = new Intl.NumberFormat(targetLocale, assign({}, format, overrides));
__numberFormatters.set(id, formatter);
}
return !part ? formatter.format(value) : formatter.formatToParts(value);
}
Alternative
Maybe it's possible to configure format options in a different way? I see that inside generated object with Intl.NumberFormat().resolvedOptions() many properties are set to auto
Additional context
No response
Validations
- [X] Read the Contributing Guidelines
- [X] Read the Documentation
- [X] Check that there isn't already an issue that request the same feature to avoid creating a duplicate.