Что такое FormatJS

FormatJS — набор библиотек для интернационализации (i18n) и локализации (l10n) JavaScript-приложений. Основная задача экосистемы — корректное отображение текста, дат, времени, чисел, валют и сообщений на разных языках и в разных регионах.

FormatJS предоставляет:

  • форматирование сообщений;
  • поддержку множеества языков;
  • перевод интерфейсов;
  • pluralization;
  • локализацию дат и чисел;
  • интеграцию с React;
  • инструменты извлечения переводов;
  • поддержку стандарта ICU MessageFormat.

Экосистема активно использует возможности Intl API, встроенного в JavaScript.


Основные проблемы интернационализации

При разработке многоязычных приложений возникают типовые сложности:

Различия форматов дат

new Date().toLocaleDateString('en-US')
// 5/28/2026

new Date().toLocaleDateString('de-DE')
// 28.5.2026

Различия форматов валют

new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
}).format(1000)

Результат:

$1,000.00

Для Германии:

1.000,00 $

Склонения и множественные формы

Русский язык:

  • 1 товар
  • 2 товара
  • 5 товаров

Английский язык:

  • 1 item
  • 2 items

Порядок слов в предложении

Разные языки имеют разные синтаксические конструкции. Простая конкатенация строк становится непригодной.

Плохой подход:

'Hello ' + username

Архитектура FormatJS

Экосистема состоит из нескольких пакетов.

Основные библиотеки

Пакет Назначение
react-intl Интернационализация React-приложений
intl-messageformat Форматирование ICU-сообщений
@formatjs/intl Полифиллы Intl API
@formatjs/cli Инструменты командной строки
babel-plugin-formatjs Извлечение сообщений
eslint-plugin-formatjs Проверка правил локализации

Intl API как основа FormatJS

FormatJS строится поверх стандартного JavaScript API Intl.

Основные классы:

API Назначение
Intl.DateTimeFormat Форматирование даты
Intl.NumberFormat Форматирование чисел
Intl.RelativeTimeFormat Относительное время
Intl.ListFormat Форматирование списков
Intl.PluralRules Правила множественного числа

Пример:

const formatter = new Intl.DateTimeFormat('ru-RU')

formatter.format(new Date())

FormatJS упрощает использование этих механизмов и объединяет их в единую систему.


ICU MessageFormat

Ключевая технология FormatJS — стандарт ICU MessageFormat.

ICU позволяет:

  • вставлять переменные;
  • склонять слова;
  • работать с множественными формами;
  • выбирать варианты текста;
  • форматировать даты и числа внутри строки.

Простые сообщения

Подстановка переменных

import {IntlMessageFormat} from 'intl-messageformat'

const msg = new IntlMessageFormat(
  'Привет, {name}!',
  'ru'
)

msg.format({
  name: 'Алексей'
})

Результат:

Привет, Алексей!

Форматирование чисел

const msg = new IntlMessageFormat(
  'Цена: {price, number}',
  'ru'
)

msg.format({
  price: 1500
})

Результат:

Цена: 1 500

Форматирование валют

const msg = new IntlMessageFormat(
  'Стоимость: {price, number, ::currency/USD}',
  'ru'
)

msg.format({
  price: 199
})

Результат:

Стоимость: 199,00 $

Форматирование даты

const msg = new IntlMessageFormat(
  'Дата: {date, date, long}',
  'ru'
)

msg.format({
  date: new Date()
})

Форматирование времени

const msg = new IntlMessageFormat(
  'Время: {time, time, short}',
  'ru'
)

msg.format({
  time: new Date()
})

Pluralization

Pluralization — одна из наиболее сложных задач локализации.

Английский язык

const msg = new IntlMessageFormat(
  `{count, plural,
    one {# item}
    other {# items}
  }`,
  'en'
)

Русский язык

const msg = new IntlMessageFormat(
  `{count, plural,
    one {# товар}
    few {# товара}
    many {# товаров}
    other {# товара}
  }`,
  'ru'
)

Примеры:

msg.format({ count: 1 })
1 товар
msg.format({ count: 3 })
3 товара
msg.format({ count: 10 })
10 товаров

Sel ect-конструкции

Позволяют выбирать текст по условию.

const msg = new IntlMessageFormat(
  `{gender, select,
    male {Он вошёл}
    female {Она вошла}
    other {Они вошли}
  }`,
  'ru'
)

Вложенные конструкции

ICU поддерживает сложные комбинации.

const msg = new IntlMessageFormat(
  `{count, plural,
    one {{gender, select,
      male {Он добавил # файл}
      female {Она добавила # файл}
      other {Добавлен # файл}
    }}
    other {{gender, select,
      male {Он добавил # файлов}
      female {Она добавила # файлов}
      other {Добавлено # файлов}
    }}
  }`,
  'ru'
)

react-intl

Наиболее популярная часть FormatJS — библиотека react-intl.

Установка:

npm install react-intl

IntlProvider

IntlProvider хранит текущую локаль и словарь переводов.

import {IntlProvider} fr om 'react-intl'

<IntlProvider
  locale="ru"
  messages={{
    hello: 'Привет'
  }}
>
  <App />
</IntlProvider>

Компонент FormattedMessage

import {FormattedMessage} fr om 'react-intl'

<FormattedMessage id="hello" />

Результат:

Привет

Передача параметров

<FormattedMessage
  id="welcome"
  values={{
    name: 'Иван'
  }}
/>

Сообщение:

{
  welcome: 'Добро пожаловать, {name}'
}

Форматирование чисел в React

import {FormattedNumber} from 'react-intl'

<FormattedNumber value={1000} />

Форматирование валют

<FormattedNumber
  value={199}
  style="currency"
  currency="USD"
/>

Форматирование даты

import {FormattedDate} from 'react-intl'

<FormattedDate
  value={new Date()}
  year="numeric"
  month="long"
  day="2-digit"
/>

Форматирование времени

import {FormattedTime} from 'react-intl'

<FormattedTime value={new Date()} />

Relative Time

import {FormattedRelativeTime} from 'react-intl'

<FormattedRelativeTime
  value={-1}
  unit="day"
/>

Результат:

1 день назад

Использование useIntl

Хук предоставляет доступ к API интернационализации.

import {useIntl} from 'react-intl'

function Product() {
  const intl = useIntl()

  return (
    <div>
      {intl.formatMessage({
        id: 'title'
      })}
    </div>
  )
}

defineMessages

Позволяет централизованно описывать сообщения.

import {defineMessages} from 'react-intl'

export default defineMessages({
  hello: {
    id: 'app.hello',
    defaultMessage: 'Привет'
  }
})

defaultMessage

defaultMessage — текст по умолчанию.

{
  id: 'button.save',
  defaultMessage: 'Сохранить'
}

Если перевод отсутствует, используется значение defaultMessage.


Идентификаторы сообщений

Хорошая практика — использовать namespace-структуру.

Пример:

auth.login.title
auth.login.button
profile.settings.language
cart.total

Хранение переводов

Наиболее распространённый формат — JSON.

ru.json

{
  "app.title": "Магазин",
  "cart.total": "Итого"
}

en.json

{
  "app.title": "Store",
  "cart.total": "Total"
}

Переключение локали

<IntlProvider
  locale={locale}
  messages={messages[locale]}
>
  <App />
</IntlProvider>

Динамическая загрузка переводов

Для больших приложений переводы загружаются лениво.

async function loadLocale(locale) {
  return import(`./locales/${locale}.json`)
}

Code Splitting

Часто локали разделяются по чанкам.

locales/
  en.json
  ru.json
  de.json

Webpack/Vite создают отдельные файлы.


Извлечение переводов

FormatJS CLI умеет автоматически находить сообщения.

Установка:

npm install @formatjs/cli --save-dev

Команда extract

formatjs extract "src/**/*.{js,jsx,ts,tsx}"

CLI анализирует код и извлекает:

defineMessages()
<FormattedMessage />
intl.formatMessage()

Генерация переводов

formatjs extract \
  "src/**/*.{ts,tsx}" \
  --out-file lang/en.json

Babel Plugin

babel-plugin-formatjs помогает:

  • извлекать сообщения;
  • удалять defaultMessage из production;
  • оптимизировать ICU.

ESLint Plugin

eslint-plugin-formatjs проверяет:

  • наличие id;
  • корректность ICU;
  • запрет строк без локализации;
  • консистентность сообщений.

Полифиллы

Некоторые браузеры не поддерживают части Intl.

FormatJS предоставляет полифиллы:

npm install @formatjs/intl-pluralrules
npm install @formatjs/intl-relativetimeformat

Поддержка старых браузеров

Особенно важны:

  • Safari старых версий;
  • Internet Explorer;
  • Android WebView.

Серверный рендеринг

FormatJS совместим с SSR.

Поддерживаются:

  • Next.js;
  • Remix;
  • Express SSR;
  • Gatsby.

SSR-пример

<IntlProvider
  locale="ru"
  messages={messages}
>
  <App />
</IntlProvider>

На сервере и клиенте должна использоваться одинаковая локаль.


Производительность

FormatJS активно кеширует форматтеры.

Создание Intl.NumberFormat дорого:

new Intl.NumberFormat()

Поэтому FormatJS переиспользует экземпляры.


Кэширование вручную

const formatter = new Intl.NumberFormat('ru')

Повторное использование быстрее создания новых объектов.


Локализация URL

Часто используется структура:

/ru/products
/en/products
/de/products

FormatJS не управляет роутингом напрямую, но легко интегрируется с роутерами.


Локализация контента

Переводится:

  • интерфейс;
  • ошибки;
  • уведомления;
  • email;
  • метаданные;
  • SEO-контент.

Локализация ошибок

{
  "auth.invalidPassword": "Неверный пароль"
}

Работа с timezone

<FormattedDate
  value={new Date()}
  timeZone="Europe/Moscow"
/>

Локализация списков

new Intl.ListFormat('ru').format([
  'JavaScript',
  'TypeScript',
  'React'
])

Результат:

JavaScript, TypeScript и React

RelativeTimeFormat

new Intl.RelativeTimeFormat('ru').format(-5, 'minute')

Результат:

5 минут назад

DisplayNames

new Intl.DisplayNames(['ru'], {
  type: 'language'
}).of('en')

Результат:

английский

Типичные ошибки

Конкатенация строк

Плохо:

'Hello ' + name

Правильно:

Hello {name}

Жёстко зашитые строки

Плохо:

<button>Сохранить</button>

Правильно:

<FormattedMessage id="button.save" />

Игнорирование pluralization

Плохо:

`${count} товаров`

Правильно:

{count, plural, ...}

Масштабирование локализации

В больших проектах появляются:

  • десятки локалей;
  • тысячи переводов;
  • системы TMS;
  • автоматическая синхронизация.

Интеграция с TMS

FormatJS используется вместе с:

  • Crowdin;
  • Lokalise;
  • Phrase;
  • Transifex.

Организация структуры проекта

Пример:

src/
  locales/
    en.json
    ru.json

  i18n/
    messages.ts
    provider.tsx

Типизация переводов

В TypeScript часто типизируют ключи.

type MessageIds =
  | 'app.title'
  | 'cart.total'

Генерация типов

Крупные проекты автоматически генерируют типы из JSON-переводов.


React Native

react-intl работает и в React Native, но требует дополнительных polyfill.


Форматирование единиц измерения

new Intl.NumberFormat('ru', {
  style: 'unit',
  unit: 'kilometer'
}).format(10)

Результат:

10 км

Compact Notation

new Intl.NumberFormat('ru', {
  notation: 'compact'
}).format(1200000)

Результат:

1,2 млн

Локализация на сервере Node.js

Node.js поддерживает Intl из коробки.

Пример:

import {IntlMessageFormat} from 'intl-messageformat'

Отличия i18n и l10n

Термин Значение
i18n Интернационализация
l10n Локализация

Интернационализация — подготовка приложения к разным языкам.

Локализация — адаптация под конкретный регион.


Преимущества FormatJS

Стандартизированный подход

Используется ICU и Intl.

Гибкость

Работает вне React.

Масштабируемость

Подходит для enterprise-приложений.

Поддержка pluralization

Особенно важна для славянских языков.

Интеграция с современным стеком

Поддерживаются:

  • React;
  • Next.js;
  • TypeScript;
  • SSR;
  • code splitting.

Ограничения FormatJS

Сложность ICU

Синтаксис MessageFormat непрост:

{count, plural, one {...} other {...}}

Большие словари переводов

При плохой организации JSON-файлы быстро разрастаются.

Необходимость дисциплины

Все строки должны проходить через систему локализации.


Сравнение с альтернативами

Библиотека Особенности
FormatJS ICU, мощная pluralization
i18next Гибкость, множество плагинов
LinguiJS Компактность
Polyglot.js Простота
vue-i18n Решение для Vue

Когда выбирают FormatJS

FormatJS особенно эффективен в:

  • enterprise-проектах;
  • SaaS-платформах;
  • международных сервисах;
  • e-commerce;
  • CRM-системах;
  • приложениях с SSR;
  • сложных многоязычных интерфейсах.

Эволюция экосистемы

FormatJS развивается вместе с JavaScript Intl API.

Появляются:

  • новые стандарты ECMAScript;
  • новые типы форматирования;
  • улучшения производительности;
  • поддержка новых языков и регионов.

Основная идея FormatJS

Все пользовательские строки должны:

  • храниться централизованно;
  • быть независимыми от кода;
  • поддерживать pluralization;
  • учитывать локаль пользователя;
  • использовать ICU MessageFormat;
  • форматироваться через Intl API.