В экосистеме FormatJS работа с единицами измерения опирается на
стандарты ECMAScript Internationalization API (ECMA-402), прежде всего
на Intl.NumberFormat. Основная идея заключается в том, что
единица измерения не является частью строки, а описывается через
семантические параметры форматирования, что позволяет локали
самостоятельно определять корректное представление.
FormatJS предоставляет надстройки над нативным API через пакеты
react-intl, @formatjs/intl и
polyfill-реализации, сохраняя совместимость с ICU MessageFormat.
Базовый механизм работы с единицами реализуется через
Intl.NumberFormat с опцией:
new Intl.NumberFormat('ru-RU', {
style: 'unit',
unit: 'kilometer'
}).format(5);
Ключевые элементы:
style: 'unit' — включает режим форматирования
единицunit — тип измерения (kilometer, byte, second, liter и
др.)ru-RU, en-US) — определяет
грамматику и формат выводаРезультат зависит от языка:
ru-RU → 5 кмen-US → 5 kmFormatJS использует этот же механизм внутри своих API, включая
formatNumber.
Система единиц в CLDR (Unicode Common Locale Data Repository), используемая FormatJS, определяет:
km, MB, h)kilometers, megabytes)Например:
new Intl.NumberFormat('en-GB', {
style: 'unit',
unit: 'liter',
unitDisplay: 'long'
}).format(3);
Результат:
3 litresПараметр unitDisplay управляет формой:
short — компактная запись (3 l)narrow — максимально короткая (3l)long — полная форма (3 litres)Нативный API не выполняет автоматическое масштабирование величин. FormatJS также не включает встроенный универсальный конвертер, но предоставляет основу для построения логики автоматического выбора единиц на уровне приложения.
Автоматический выбор обычно строится вокруг следующих принципов:
Intl.NumberFormatТипичный подход — построение таблицы порогов:
const units = [
{ unit: 'byte', threshold: 1 },
{ unit: 'kilobyte', threshold: 1024 },
{ unit: 'megabyte', threshold: 1024 ** 2 },
{ unit: 'gigabyte', threshold: 1024 ** 3 }
];
Алгоритм выбора:
function formatBytes(value, locale) {
let selected = units[0];
for (const u of units) {
if (value >= u.threshold) {
selected = u;
}
}
const scaled = value / selected.threshold;
return new Intl.NumberFormat(locale, {
style: 'unit',
unit: selected.unit,
maximumFractionDigits: 1
}).format(scaled);
}
Поведение:
512 → 512 bytes2048 → 2 KB5_242_880 → 5 MBБолее сложные системы используют логарифмическое масштабирование:
function formatAutoSize(value, locale) {
const step = 1024;
const labels = ['byte', 'kilobyte', 'megabyte', 'gigabyte'];
const index = Math.floor(Math.log(value) / Math.log(step));
const clamped = Math.max(0, Math.min(index, labels.length - 1));
const scaled = value / Math.pow(step, clamped);
return new Intl.NumberFormat(locale, {
style: 'unit',
unit: labels[clamped],
maximumFractionDigits: 2
}).format(scaled);
}
Автоматический выбор единиц часто применяется к временным интервалам:
function formatDuration(seconds, locale) {
const units = [
{ unit: 'second', limit: 60 },
{ unit: 'minute', limit: 3600 },
{ unit: 'hour', limit: Infinity }
];
let value = seconds;
let selected = units[0];
for (const u of units) {
if (seconds >= u.limit) selected = u;
}
if (selected.unit === 'minute') value = seconds / 60;
if (selected.unit === 'hour') value = seconds / 3600;
return new Intl.NumberFormat(locale, {
style: 'unit',
unit: selected.unit,
maximumFractionDigits: 1
}).format(value);
}
В react-intl форматирование выполняется через
formatNumber:
import { useIntl } from 'react-intl';
function Component({ distance }) {
const intl = useIntl();
return intl.formatNumber(distance, {
style: 'unit',
unit: 'kilometer'
});
}
Автоматический выбор единиц реализуется до вызова
formatNumber, поскольку сам FormatJS не выполняет
конвертацию значений.
В сложных интерфейсах выбор единиц зависит от:
Пример условной логики:
function formatDistance(valueMeters, locale, system = 'metric') {
if (system === 'imperial') {
const miles = valueMeters / 1609.34;
return new Intl.NumberFormat(locale, {
style: 'unit',
unit: 'mile',
maximumFractionDigits: 1
}).format(miles);
}
const km = valueMeters / 1000;
return new Intl.NumberFormat(locale, {
style: 'unit',
unit: 'kilometer',
maximumFractionDigits: 1
}).format(km);
}
FormatJS опирается на Intl.PluralRules, что влияет
на:
Пример:
1 kilometer2 kilometers5 kilometersВ русском языке:
1 километр2 километра5 километровЭто происходит автоматически при использовании
style: 'unit'.
Автоматический выбор единиц в FormatJS требует внешнего слоя логики:
Типовые архитектуры включают:
formatBytes,
formatDuration)Распространённая структура:
function createUnitFormatter(locale, units) {
return function(value) {
const selected = selectUnit(value, units);
const scaled = value / selected.factor;
return new Intl.NumberFormat(locale, {
style: 'unit',
unit: selected.unit,
maximumFractionDigits: 2
}).format(scaled);
};
}
Такой подход позволяет:
Некоторые значения не имеют прямого соответствия в CLDR:
В таких случаях применяется fallback:
new Intl.NumberFormat('en', {
style: 'unit',
unit: 'liter', // фиктивное соответствие
unitDisplay: 'narrow'
}).format(value);
или отказ от unit:
new Intl.NumberFormat('en').format(value);
При больших значениях применяется:
maximumFractionDigits: 0
minimumFractionDigits: 0
или динамическое округление:
const precision = value > 1000 ? 0 : 2;
FormatJS поддерживает ICU-синтаксис:
import { defineMessage } from 'react-intl';
const message = defineMessage({
defaultMessage: '{value} kilometers',
description: 'Distance display'
});
Но автоматический выбор единиц в ICU не выполняется — он реализуется через параметры форматирования чисел.