Intl.NumberFormat — часть стандарта ECMAScript
Internationalization API (Intl), предназначенная для
локализованного форматирования чисел. Механизм поддерживает:
Поддержка Intl.NumberFormat присутствует в современных
браузерах и средах выполнения, однако:
Полифилл FormatJS обеспечивает одинаковое поведение API во всех окружениях.
@formatjs/intl-numberformatОсновной полифилл поставляется в пакете:
npm install @formatjs/intl-numberformat
Дополнительно устанавливаются locale-данные:
npm install @formatjs/intl-numberformat locale-data
Структура импорта:
import '@formatjs/intl-numberformat/polyfill';
import '@formatjs/intl-numberformat/locale-data/ru';
После подключения глобальный объект Intl.NumberFormat
получает полную реализацию спецификации.
Intl.NumberFormatНекоторые старые браузеры не содержат Intl вовсе:
if (!Intl || !Intl.NumberFormat) {
// требуется полифилл
}
Часто API существует, но не поддерживает современные возможности:
new Intl.NumberFormat('ru', {
notation: 'compact'
});
В старых движках такой код вызывает ошибку или игнорирует настройки.
Node.js с minimal ICU:
new Intl.NumberFormat('fr').format(1000);
Результат может оказаться:
1,000
вместо:
1 000
Полифилл загружает необходимые CLDR-данные независимо от ICU.
import '@formatjs/intl-numberformat/polyfill';
import '@formatjs/intl-numberformat/locale-data/en';
import '@formatjs/intl-numberformat/locale-data/ru';
Использование:
const formatter = new Intl.NumberFormat('ru');
console.log(formatter.format(1234567.89));
Результат:
1 234 567,89
FormatJS предоставляет условный импорт.
import {shouldPolyfill} from '@formatjs/intl-numberformat/should-polyfill';
async function setup() {
const unsupportedLocale = shouldPolyfill('ru');
if (unsupportedLocale) {
await import('@formatjs/intl-numberformat/polyfill-force');
await import(`@formatjs/intl-numberformat/locale-data/${unsupportedLocale}`);
}
}
polyfill и polyfill-forcepolyfillПодключает реализацию только при необходимости.
import '@formatjs/intl-numberformat/polyfill';
polyfill-forceВсегда заменяет встроенную реализацию.
import '@formatjs/intl-numberformat/polyfill-force';
Полезно:
Полифилл не включает все локали автоматически. Locale-данные импортируются отдельно.
import '@formatjs/intl-numberformat/locale-data/ru';
import '@formatjs/intl-numberformat/locale-data/en';
import '@formatjs/intl-numberformat/locale-data/de';
import '@formatjs/intl-numberformat/locale-data/fr';
async function loadLocale(locale) {
await import(`@formatjs/intl-numberformat/locale-data/${locale}`);
}
const formatter = new Intl.NumberFormat('ru');
formatter.format(1000000);
Результат:
1 000 000
const formatter = new Intl.NumberFormat('ru', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
formatter.format(12.5);
Результат:
12,50
const formatter = new Intl.NumberFormat('ru', {
style: 'currency',
currency: 'RUB'
});
formatter.format(1500);
Результат:
1 500,00 ₽
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
});
formatter.format(1500);
Результат:
$1,500.00
currencyDisplay: 'symbol'
$100
currencyDisplay: 'code'
USD 100
currencyDisplay: 'name'
100 US dollars
const formatter = new Intl.NumberFormat('ru', {
style: 'percent'
});
formatter.format(0.25);
Результат:
25 %
Поддержка compact notation появилась не во всех движках одновременно, поэтому полифилл особенно полезен для этой функции.
const formatter = new Intl.NumberFormat('ru', {
notation: 'compact',
compactDisplay: 'short'
});
formatter.format(1500000);
Результат:
1,5 млн
const formatter = new Intl.NumberFormat('ru', {
notation: 'compact',
compactDisplay: 'long'
});
formatter.format(1500000);
Результат:
1,5 миллиона
const formatter = new Intl.NumberFormat('en', {
notation: 'scientific'
});
formatter.format(123456);
Результат:
1.235E5
const formatter = new Intl.NumberFormat('en', {
notation: 'engineering'
});
formatter.format(123456);
Результат:
123.456E3
const formatter = new Intl.NumberFormat('ru', {
style: 'unit',
unit: 'kilometer'
});
formatter.format(15);
Результат:
15 км
const formatter = new Intl.NumberFormat('ru', {
style: 'unit',
unit: 'celsius'
});
formatter.format(25);
Результат:
25 °C
unitDisplay: 'short'
unitDisplay: 'narrow'
unitDisplay: 'long'
const formatter = new Intl.NumberFormat('ar', {
numberingSystem: 'arab'
});
formatter.format(123456);
const formatter = new Intl.NumberFormat('ru', {
maximumFractionDigits: 1
});
formatter.format(1.27);
Результат:
1,3
const formatter = new Intl.NumberFormat('ru', {
minimumFractionDigits: 3
});
formatter.format(1.2);
Результат:
1,200
const formatter = new Intl.NumberFormat('en', {
maximumSignificantDigits: 3
});
formatter.format(12345.678);
Результат:
12,300
Современная спецификация поддерживает formatRange.
const formatter = new Intl.NumberFormat('ru');
formatter.formatRange(10, 20);
Результат:
10–20
Полифилл обеспечивает поддержку даже в окружениях без реализации метода.
formatToPartsМетод разбивает строку на семантические части.
const formatter = new Intl.NumberFormat('ru', {
style: 'currency',
currency: 'RUB'
});
console.log(formatter.formatToParts(1234.5));
Результат:
[
{ type: 'integer', value: '1' },
{ type: 'group', value: ' ' },
{ type: 'integer', value: '234' },
{ type: 'decimal', value: ',' },
{ type: 'fraction', value: '50' },
{ type: 'literal', value: ' ' },
{ type: 'currency', value: '₽' }
]
formatToParts в интерфейсахconst parts = formatter.formatToParts(1234);
const html = parts.map(part => {
if (part.type === 'currency') {
return `<strong>${part.value}</strong>`;
}
return part.value;
}).join('');
resolvedOptionsВозвращает фактические настройки форматтера.
const formatter = new Intl.NumberFormat('ru', {
style: 'currency',
currency: 'RUB'
});
console.log(formatter.resolvedOptions());
Оптимизация загрузки:
async function ensureNumberFormat(locale) {
const unsupportedLocale = shouldPolyfill(locale);
if (!unsupportedLocale) {
return;
}
await import('@formatjs/intl-numberformat/polyfill-force');
await import(
`@formatjs/intl-numberformat/locale-data/${unsupportedLocale}`
);
}
const formatter = new Intl.NumberFormat('ru', {
style: 'currency',
currency: 'RUB'
});
function Price({ value }) {
return (
<span>
{formatter.format(value)}
</span>
);
}
react-intlreact-intl автоматически использует
Intl.NumberFormat.
<IntlProvider locale="ru">
<App />
</IntlProvider>
FormattedNumber<FormattedNumber
value={1500}
style="currency"
currency="RUB"
/>
import '@formatjs/intl-numberformat/polyfill-force';
import '@formatjs/intl-numberformat/locale-data/ru';
После этого форматирование работает одинаково на сервере и клиенте.
Для IE11 обычно требуется:
npm install @formatjs/intl-numberformat
и дополнительные полифиллы:
npm install core-js
FormatJS проектировался с учётом минимизации размера бандла.
import '@formatjs/intl-numberformat/locale-data/ru';
вместо:
import '@formatjs/intl-numberformat/locale-data/*';
Создание экземпляра Intl.NumberFormat — относительно
дорогая операция.
items.map(item => (
new Intl.NumberFormat('ru').format(item.price)
));
const formatter = new Intl.NumberFormat('ru');
items.map(item => formatter.format(item.price));
const cache = new Map();
function getFormatter(locale, options) {
const key = JSON.stringify([locale, options]);
if (!cache.has(key)) {
cache.set(
key,
new Intl.NumberFormat(locale, options)
);
}
return cache.get(key);
}
Полифилл FormatJS поддерживает:
format;formatToParts;formatRange;Подключение большого количества локалей увеличивает bundle size.
Некоторые сборщики требуют специальной настройки:
import(
`@formatjs/intl-numberformat/locale-data/${locale}`
);
Webpack может включить все локали в bundle.
const locales = {
ru: () =>
import('@formatjs/intl-numberformat/locale-data/ru'),
en: () =>
import('@formatjs/intl-numberformat/locale-data/en')
};
FormatJS стремится к полному соответствию спецификации ECMA-402, однако:
Полифилл помогает унифицировать поведение между платформами.
import '@formatjs/intl-numberformat/polyfill';
import '@formatjs/intl-numberformat/locale-data/en';
import '@formatjs/intl-numberformat/locale-data/ru';
async function bootstrap() {
await setupIntl();
startApplication();
}
function supportsCompact() {
try {
new Intl.NumberFormat('en', {
notation: 'compact'
});
return true;
} catch {
return false;
}
}
Типы поставляются вместе с пакетом.
const formatter: Intl.NumberFormat =
new Intl.NumberFormat('ru');
import {shouldPolyfill}
from '@formatjs/intl-numberformat/should-polyfill';
export async function setupNumberFormat(locale) {
const unsupportedLocale = shouldPolyfill(locale);
if (!unsupportedLocale) {
return;
}
await import(
'@formatjs/intl-numberformat/polyfill-force'
);
await import(
`@formatjs/intl-numberformat/locale-data/${unsupportedLocale}`
);
}
const priceFormatter =
new Intl.NumberFormat('ru', {
style: 'currency',
currency: 'RUB'
});
const percentFormatter =
new Intl.NumberFormat('ru', {
style: 'percent'
});
const compactFormatter =
new Intl.NumberFormat('ru', {
notation: 'compact'
});
console.log(priceFormatter.format(1999));
console.log(percentFormatter.format(0.25));
console.log(compactFormatter.format(1200000));
Результат:
1 999,00 ₽
25 %
1,2 млн