FormatJS представляет собой набор библиотек для интернационализации JavaScript-приложений, где ключевая идея заключается в строгом разделении сообщений, форматирования и локализации данных. В контексте TypeScript это приводит к необходимости описывать типы для сообщений, параметров форматирования, локалей и runtime-объектов Intl.
Типизация в экосистеме FormatJS строится вокруг нескольких фундаментальных слоёв:
Каждый слой влияет на конечную строгость проверки локализации и предотвращение ошибок на этапе компиляции.
Основной тип, используемый для описания интернационализируемых строк,
— MessageDescriptor. Он определяет структуру сообщения,
которое передаётся в форматтеры или компоненты.
Типовая структура:
import { MessageDescriptor } from 'react-intl';
const message: MessageDescriptor = {
id: 'user.profile.title',
defaultMessage: 'User profile',
description: 'Title of the user profile page'
};
Ключевые поля:
id — уникальный идентификатор сообщенияdefaultMessage — fallback-строкаdescription — пояснение для переводчиковВ больших системах часто вводится строгая модель ID:
type MessageIds =
| 'auth.login.title'
| 'auth.login.button'
| 'dashboard.welcome';
const msg: MessageDescriptor = {
id: 'auth.login.title' satisfies MessageIds,
defaultMessage: 'Login'
};
Такой подход предотвращает появление «плавающих» строковых идентификаторов и обеспечивает контроль на уровне компилятора.
FormatJS активно использует интерполяцию значений в сообщениях ICU-формата. В TypeScript это требует строгого описания параметров.
Пример сообщения:
const message = {
id: 'greeting',
defaultMessage: 'Hello, {name}!'
};
Типизация параметров:
type GreetingValues = {
name: string;
};
Использование с форматтером:
import { IntlShape } from 'react-intl';
function formatGreeting(intl: IntlShape, values: GreetingValues) {
return intl.formatMessage(
{ id: 'greeting', defaultMessage: 'Hello, {name}!' },
values
);
}
В современных архитектурах вводится слой генерации типов сообщений:
type AppMessages = {
greeting: { name: string };
unread: { count: number };
};
И использование через обобщения:
function format<K extends keyof AppMessages>(
key: K,
values: AppMessages[K]
) {
return key;
}
ICU MessageFormat поддерживает множественные формы, которые требуют строгой типизации числовых параметров.
Пример:
const message = {
id: 'cart.items',
defaultMessage: 'You have {count, plural, one {# item} other {# items}}'
};
Типизация:
type CartValues = {
count: number;
};
Важно учитывать:
numberDate | numberКлючевая сущность в react-intl — IntlShape, описывающая
runtime API.
Основные методы:
intl.formatMessage
intl.formatDate
intl.formatNumber
intl.formatTime
interface IntlShape {
formatMessage: <T extends Record<string, any>>(
descriptor: MessageDescriptor,
values?: T
) => string;
}
Это позволяет связывать сообщение и параметры через generic.
intl.formatNumber(1234.56, {
style: 'currency',
currency: 'USD'
});
TypeScript-тип:
type NumberFormatOptions = Intl.NumberFormatOptions;
Под капотом используется стандартный
Intl.NumberFormatOptions.
intl.formatDate(new Date(), {
year: 'numeric',
month: 'long',
day: '2-digit'
});
Типизация:
type DateFormatOptions = Intl.DateTimeFormatOptions;
В современных проектах FormatJS часто используется генерация типов из исходных сообщений.
{
"app.title": "Application",
"app.welcome": "Welcome {name}"
}
export interface IntlMessages {
'app.title': void;
'app.welcome': {
name: string;
};
}
Это позволяет:
FormatJS включает инструменты компиляции сообщений через Babel-плагины, где TypeScript играет роль слоя валидации поверх трансформаций.
Пример конфигурации:
module.exports = {
plugins: [
[
'formatjs',
{
removeDefaultMessage: false,
extractFromFormatMessageCall: true
}
]
]
};
TypeScript помогает гарантировать, что:
id всегда строкаdefaultMessage соответствует ICU синтаксисуimport { FormattedMessage } fr om 'react-intl';
<FormattedMessage
id="welcome"
defaultMessage="Welcome {name}"
values={{ name: 'Alex' }}
/>;
Типизация:
interface FormattedMessageProps<T = Record<string, any>> {
id: string;
defaultMessage?: string;
values?: T;
}
type WelcomeValues = {
name: string;
};
type WelcomeProps = {
values: WelcomeValues;
};
Локаль в FormatJS обычно представлена строкой, но в строгих системах вводится union:
type Locale = 'en' | 'ru' | 'de' | 'fr';
Использование:
function setLocale(locale: Locale) {
return locale;
}
Это предотвращает передачу некорректных регионов и снижает runtime-ошибки.
Fallback используется при отсутствии перевода:
type FallbackConfig = {
defaultLocale: Locale;
messages: Record<string, string>;
};
Пример:
const config: FallbackConfig = {
defaultLocale: 'en',
messages: {}
};
В масштабных системах вводится единая модель:
type IntlDictionary = Record<string, Record<string, any>>;
И специализированные утилиты:
type ExtractValues<T> = T extends Record<string, infer V> ? V : never;
Это позволяет:
FormatJS не полностью предотвращает runtime-ошибки, поэтому вводятся дополнительные типы:
type IntlError =
| 'MISSING_MESSAGE'
| 'INVALID_FORMAT'
| 'MISSING_VALUE';
Обработка:
function handleIntlError(error: IntlError) {
return error;
}
Вся система типов формируется как композиция:
В результате формируется строгая модель интернационализации, где каждая строка, параметр и формат подчинены контрактам TypeScript.