FormatJS предоставляет строгую модель работы с сообщениями, основанную на ICU MessageFormat и типизации TypeScript. Центральная идея заключается в том, что каждое сообщение рассматривается как структурированный объект, а не как произвольная строка. Это позволяет проверять корректность параметров на этапе компиляции и снижает вероятность ошибок при интернационализации интерфейса.
Сообщение в FormatJS описывается через интерфейс
MessageDescriptor. Он задаёт минимальный контракт:
id — уникальный идентификатор сообщенияdefaultMessage — строка по умолчаниюdescription — контекст для переводчиков
(опционально)import { MessageDescriptor } from 'react-intl';
const message: MessageDescriptor = {
id: 'user.greeting',
defaultMessage: 'Hello, world',
description: 'Greeting shown on home page',
};
Типизация гарантирует, что id и
defaultMessage присутствуют, а дополнительные поля не
нарушают структуру.
FormatJS использует ICU MessageFormat как основу синтаксиса. Это означает, что сообщение может содержать не только текст, но и структурные элементы:
Пример базовой интерполяции:
const message = {
id: 'cart.items',
defaultMessage: 'You have {count} items',
};
Здесь count становится параметром сообщения, который
должен быть передан при рендеринге.
Одной из ключевых возможностей является строгая типизация аргументов, используемых внутри сообщений.
type Values = {
name: string;
};
const message = {
id: 'welcome.user',
defaultMessage: 'Hello, {name}',
};
При использовании через react-intl:
intl.formatMessage(message, { name: 'Alex' });
TypeScript может выводить тип name как обязательный
параметр.
ICU plural-формат автоматически превращает числовые параметры в управляемые ветвления:
const message = {
id: 'inbox.messages',
defaultMessage: 'You have {count, plural, one {# message} other {# messages}}',
};
Параметр count обязан быть числом:
type Values = {
count: number;
};
Особенность заключается в том, что TypeScript фиксирует тип
count как number, поскольку plural-синтаксис
ICU требует числовой дискриминант.
select используется для ветвления по строковым
значениям:
const message = {
id: 'user.status',
defaultMessage:
'{status, select, active {Active} inactive {Inactive} banned {Banned} other {Unknown}}',
};
type Values = {
status: 'active' | 'inactive' | 'banned' | string;
};
TypeScript может уточнять тип до union, если он явно задан в коде приложения.
FormatJS включает типизированные форматтеры для встроенных значений.
const message = {
id: 'price.label',
defaultMessage: 'Price: {price, number, currency}',
};
Типизация:
type Values = {
price: number;
};
const message = {
id: 'event.date',
defaultMessage: 'Date: {date, date, long}',
};
type Values = {
date: Date | number;
};
Функция defineMessages позволяет группировать сообщения
и сохранять типизацию на уровне объекта.
import { defineMessages } fr om 'react-intl';
const messages = defineMessages({
title: {
id: 'page.title',
defaultMessage: 'Dashboard',
},
greeting: {
id: 'page.greeting',
defaultMessage: 'Hello, {name}',
},
});
TypeScript выводит структуру:
{
title: MessageDescriptor;
greeting: MessageDescriptor;
}
При этом параметры каждого сообщения могут быть выведены отдельно при
использовании formatMessage.
defineMessage используется для одиночных сообщений и
позволяет сохранять строгую типизацию даже вне групп.
import { defineMessage } fr om 'react-intl';
const message = defineMessage({
id: 'button.submit',
defaultMessage: 'Submit',
});
Тип результата сохраняет структуру
MessageDescriptor.
IntlShape — основной интерфейс доступа к API
форматирования.
import { IntlShape } from 'react-intl';
function render(intl: IntlShape) {
return intl.formatMessage(
{
id: 'hello',
defaultMessage: 'Hello {name}',
},
{
name: 'User',
}
);
}
formatMessageformatDateformatNumberformatTimeКаждый метод строго типизирует входные параметры.
TypeScript может автоматически сопоставлять параметры с ICU-синтаксисом при использовании вспомогательных утилит.
type ExtractValues<T> = T extends { defaultMessage: string }
? Record<string, string | number | Date>
: never;
Такой подход используется в кастомных обёртках над FormatJS для усиления типовой безопасности.
Rich-text форматирование позволяет использовать React-элементы внутри сообщений:
const message = {
id: 'terms',
defaultMessage: 'Accept <b>terms</b> and <link>conditions</link>',
};
type Values = {
b: (chunks: string) => React.ReactNode;
link: (chunks: string) => React.ReactNode;
};
Это обеспечивает строгую проверку всех вставляемых компонентов.
Внутри FormatJS сообщения разбираются в AST (Abstract Syntax Tree).
Основной тип — MessageFormatElement.
Он включает:
literal — текстargument — переменнаяnumber, date, time —
форматированные аргументыplural — множественные формыselect — выборtype MessageFormatElement =
| LiteralElement
| ArgumentElement
| NumberElement
| DateElement
| TimeElement
| PluralElement
| SelectElement;
Эта структура используется внутри intl-messageformat для
парсинга строк сообщений.
При использовании TypeScript можно фиксировать список допустимых
id:
type MessageKeys = 'home.title' | 'home.subtitle';
type Messages = Record<MessageKeys, MessageDescriptor>;
Это исключает возможность обращения к несуществующим сообщениям.
В react-intl типизация сообщений тесно связана с
useIntl:
import { useIntl } from 'react-intl';
function Component() {
const intl = useIntl();
return intl.formatMessage({
id: 'title',
defaultMessage: 'Title',
});
}
Тип intl содержит строго определённые методы, каждый из
которых ожидает корректные значения ICU-параметров.
Главная цель типизации сообщений — предотвращение несоответствия между шаблоном и передаваемыми значениями.
Типовые ошибки, которые устраняются:
В реальных проектах часто создаются расширения:
type AppMessages = {
[K in string]: {
id: K;
defaultMessage: string;
description?: string;
};
};
Или строгая обёртка:
type TypedMessage<T extends string> = {
id: T;
defaultMessage: string;
};
FormatJS разделяет:
Это означает, что даже при строгой типизации окончательная проверка структуры происходит во время выполнения через парсер ICU.
Такая архитектура позволяет сохранять баланс между безопасностью и гибкостью локализации.