В экосистеме FormatJS форматирование телефонных номеров не является
встроенной задачей ядра библиотеки, поскольку основная цель FormatJS —
интернационализация текстов, сообщений и числовых значений через ICU
MessageFormat и API уровня Intl. Поэтому работа с телефонными номерами
строится на сочетании FormatJS-подхода и специализированных библиотек,
чаще всего — libphonenumber-js, с последующей интеграцией в
форматирование UI через компоненты и сообщения.
Телефонные номера не относятся к числовым значениям в смысле
Intl.NumberFormat. Их структура зависит от страны, правил
набора, наличия кода страны, длины национального номера и локальных
особенностей отображения.
Ключевая особенность:
Поэтому в приложениях с FormatJS телефонные номера рассматриваются как отдельный тип данных, который форматируется до передачи в UI.
Базовая модель хранения — международный стандарт E.164:
+Пример:
+447911123456
+77011234567
+14155552671
Любое форматирование в UI должно быть производным от этого значения.
Библиотека libphonenumber-js выполняет три ключевые
функции:
Базовый сценарий форматирования:
import { parsePhoneNumberFromString } from 'libphonenumber-js';
const phone = parsePhoneNumberFromString('+447911123456');
const formatted = phone.formatInternational();
// +44 7911 123456
Для локального формата:
phone.formatNational();
// 07911 123456
В приложениях, использующих FormatJS, телефон обычно проходит через слой представления, где применяется локаль интерфейса.
Пример компонента:
import { useIntl } from 'react-intl';
import { parsePhoneNumberFromString } from 'libphonenumber-js';
function PhoneNumber({ value }) {
const intl = useIntl();
const phone = parsePhoneNumberFromString(value);
if (!phone) return null;
const formatted =
intl.locale === 'en-GB'
? phone.formatNational()
: phone.formatInternational();
return formatted;
}
Здесь FormatJS отвечает за определение локали, а библиотека телефонных номеров — за форматирование.
Телефонные номера не имеют ICU-формата, поэтому их локализация строится вручную через правила:
Используется для интерфейсов с глобальной аудиторией:
+1 415 555 2671
+44 7911 123456
Используется для локализованных интерфейсов:
07911 123456
(415) 555-2671
Выбор формата зависит от локали FormatJS:
function formatPhone(phoneNumber, locale) {
const phone = parsePhoneNumberFromString(phoneNumber);
if (!phone) return phoneNumber;
const country = locale === 'en-GB' ? 'GB' : 'US';
return phone.formatNational({ countryCallingCode: country })
|| phone.formatInternational();
}
FormatJS активно использует ICU MessageFormat, однако телефонные номера не поддерживаются напрямую как тип форматирования. Вместо этого применяется подстановка строк.
Пример шаблона:
import { defineMessages, useIntl } from 'react-intl';
const messages = defineMessages({
contact: {
id: 'user.contact',
defaultMessage: 'Контактный номер: {phone}',
},
});
Использование:
const intl = useIntl();
intl.formatMessage(messages.contact, {
phone: formattedPhone,
});
Форматирование происходит заранее, до передачи в ICU.
Перед сохранением номера необходимо привести его к E.164:
const phone = parsePhoneNumberFromString(inputValue);
if (phone) {
const normalized = phone.number; // E.164
}
Типичная схема:
Валидация выполняется через isValid():
const phone = parsePhoneNumberFromString(value);
const isValid = phone?.isValid();
Дополнительно:
phone.getType();
// 'MOBILE' | 'FIXED_LINE' | ...
Частая проблема — конфликт между отображением и вводом.
Рекомендуемая схема:
const [value, setValue] = useState('');
const onB lur = () => {
const phone = parsePhoneNumberFromString(value);
if (phone) {
setValue(phone.formatInternational());
}
};
При фокусе часто возвращают “сырой” ввод:
const onFo cus = () => {
const phone = parsePhoneNumberFromString(value);
if (phone) {
setValue(phone.number);
}
};
В контексте FormatJS важно учитывать, что react-intl не
управляет форматированием данных, а только их отображением.
Поэтому телефонные номера:
Это отличает их от дат и чисел, которые могут обрабатываться через
<FormattedNumber> или
<FormattedDate>.
Для улучшения UX часто применяется маска ввода:
+7 (___) ___-__-__
Но маска не должна считаться источником истины. Она используется только для визуального ввода.
Пример с простым форматированием:
function applyMask(value) {
return value
.replace(/[^\d+]/g, '')
.replace(/(\d{1,3})(\d{3})(\d{3})(\d{2})(\d{2})/,
'+$1 ($2) $3-$4-$5');
}
Однако при наличии libphonenumber-js маски обычно
заменяются полноценным парсингом.
Типичные сценарии ошибок:
const phone = parsePhoneNumberFromString(value);
if (!phone) {
// fallback: raw input
}
if (!phone.isValid()) {
// отображение ошибки валидации
}
Разные регионы влияют на:
Пример:
(415) 555-267107911 12345606 12 34 56 78Эти различия полностью управляются метаданными
libphonenumber-js, а не FormatJS.
В многоязычных системах с FormatJS телефонный номер обычно проходит через несколько слоёв:
Пример полной цепочки:
const raw = '+14155552671';
const phone = parsePhoneNumberFromString(raw);
const display = phone.formatInternational();
intl.formatMessage(
{ id: 'contact' },
{ phone: display }
);
При массовом отображении списков пользователей форматирование может быть дорогим.
Оптимизация:
const cache = new Map();
function getFormatted(phone) {
if (cache.has(phone)) return cache.get(phone);
const parsed = parsePhoneNumberFromString(phone);
const formatted = parsed?.formatInternational() || phone;
cache.set(phone, formatted);
return formatted;
}
Для таких случаев FormatJS-слой не применяется, используется raw rendering.
В отличие от чисел и валют:
Intllocale-sensitive formatting APIЭто делает их отдельной категорией данных в архитектуре интернационализации на базе FormatJS.