История и эволюция библиотеки

Развитие веб-приложений в начале 2010-х годов привело к резкому росту требований к интернационализации интерфейсов. Если ранние сайты ограничивались несколькими статичными строками и примитивной подстановкой переводов, то современные SPA-приложения начали сталкиваться с гораздо более сложными задачами:

  • форматирование дат и времени с учётом локали;
  • корректное отображение валют;
  • склонение слов по правилам языка;
  • поддержка множественного числа;
  • локализованное форматирование чисел;
  • работа с часовыми поясами;
  • динамическая подстановка параметров;
  • единый механизм перевода как на клиенте, так и на сервере.

Проблема особенно обострилась после массового перехода к архитектуре Single Page Application. Библиотеки вроде React, Angular и Vue сделали интерфейсы динамическими, однако экосистема локализации оставалась фрагментированной. Разработчики использовали:

  • собственные JSON-словари;
  • самописные функции перевода;
  • отдельные библиотеки для pluralization;
  • Intl API напрямую;
  • серверные инструменты локализации.

Подобный подход быстро приводил к хаосу:

const messages = {
  en: {
    items: count => `${count} items`,
  },
  ru: {
    items: count => {
      if (count === 1) return `${count} товар`;
      if (count < 5) return `${count} товара`;
      return `${count} товаров`;
    }
  }
};

С ростом приложения количество подобных конструкций становилось неконтролируемым.


Появление стандарта ECMAScript Intl

Ключевым событием для будущего FormatJS стало внедрение объекта Intl в стандарт ECMAScript.

Интернационализация в JavaScript долгое время отсутствовала как полноценная часть языка. Форматирование дат выполнялось вручную или через сторонние библиотеки:

new Date().toLocaleString()

Поведение подобных методов отличалось между браузерами и не предоставляло гибкой настройки.

Ситуация изменилась после появления спецификации ECMAScript Internationalization API, известной как Intl.

Первые стабильные возможности появились в:

  • Intl.NumberFormat
  • Intl.DateTimeFormat
  • Intl.Collator

Пример:

const formatter = new Intl.NumberFormat('de-DE', {
  style: 'currency',
  currency: 'EUR'
});

console.log(formatter.format(1000));
// 1.000,00 €

Появление Intl стало фундаментом для целого поколения библиотек интернационализации.


Создание FormatJS

Библиотека FormatJS появилась как попытка построить полноценную экосистему интернационализации поверх стандарта Intl.

Проект был создан инженерами Yahoo. Главной целью стала унификация работы с локализацией для крупных JavaScript-приложений.

Ключевые задачи проекта:

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

Первые версии FormatJS представляли собой набор утилит для:

  • форматирования чисел;
  • дат;
  • относительного времени;
  • сообщений ICU.

Позже проект превратился в полноценный монорепозиторий.


Влияние ICU MessageFormat

Одной из важнейших частей эволюции FormatJS стала интеграция ICU MessageFormat.

ICU (International Components for Unicode) — международный стандарт локализации, используемый во множестве платформ:

  • Java;
  • Android;
  • iOS;
  • C++;
  • серверные приложения;
  • системы перевода.

ICU MessageFormat позволял описывать сложные языковые конструкции декларативно.

Пример:

{count, plural,
  =0 {Нет сообщений}
  one {# сообщение}
  few {# сообщения}
  many {# сообщений}
  other {# сообщения}
}

Подобный подход радикально отличался от ручных if/else.

FormatJS сделал ICU центральным механизмом локализации JavaScript-приложений.


Развитие React и появление React Intl

Настоящий рост популярности FormatJS начался вместе с распространением React.

React изменил подход к разработке интерфейсов:

  • компонентная архитектура;
  • декларативность;
  • повторное использование UI;
  • динамический рендеринг.

Старые системы локализации плохо вписывались в React-компоненты. Требовалась библиотека, которая:

  • поддерживает JSX;
  • интегрируется с Virtual DOM;
  • обновляет переводы реактивно;
  • позволяет использовать локализацию внутри компонентов.

Так появился пакет react-intl.

Пример раннего использования:

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

Внутри компонентов:

<FormattedMessage
  id="app.welcome"
  defaultMessage="Welcome, {name}"
  values={{ name: 'John' }}
/>

react-intl стал одной из первых крупных систем локализации, полностью адаптированных под React-архитектуру.


Эволюция архитектуры FormatJS

Со временем библиотека перестала быть просто React-решением.

Экосистема начала делиться на независимые пакеты:

Пакет Назначение
react-intl React-интеграция
intl-messageformat ICU MessageFormat
intl-relativeformat Относительное время
intl-numberformat Полифиллы чисел
intl-datetimeformat Форматирование дат
babel-plugin-react-intl Извлечение переводов
@formatjs/cli CLI-инструменты

Подобная модульность решила сразу несколько проблем:

  • уменьшение размеров бандлов;
  • независимые обновления;
  • поддержка Node.js;
  • поддержка React Native;
  • использование без React.

Проблема поддержки браузеров

Важной частью истории FormatJS стала борьба с несовместимостью браузеров.

Несмотря на существование стандарта Intl, ранние версии браузеров поддерживали его частично:

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

Особенно проблемными были:

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

FormatJS начал активно развивать систему полифиллов.

Пример:

import '@formatjs/intl-pluralrules/polyfill';
import '@formatjs/intl-pluralrules/locale-data/ru';

Это позволило использовать единый API независимо от окружения.


Переход к современным Intl API

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

Появились:

  • Intl.RelativeTimeFormat
  • Intl.PluralRules
  • Intl.ListFormat
  • Intl.DisplayNames

FormatJS быстро внедрял поддержку новых возможностей.

Пример относительного времени:

const rtf = new Intl.RelativeTimeFormat('ru', {
  numeric: 'auto'
});

rtf.format(-1, 'day');
// вчера

Ранее подобное поведение приходилось реализовывать вручную.


Развитие системы extraction

Одной из ключевых проблем локализации всегда оставалось управление переводами.

Ранние проекты хранили строки прямо в коде:

t('Hello world')

Однако крупные приложения сталкивались с трудностями:

  • потерянные переводы;
  • дублирование;
  • отсутствие идентификаторов;
  • рассинхронизация переводчиков и разработчиков.

FormatJS предложил систему автоматического извлечения сообщений.

Пример:

<FormattedMessage
  id="profile.title"
  defaultMessage="User profile"
/>

CLI-инструменты могли автоматически извлекать все сообщения:

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

Это стало важным шагом к промышленной локализации frontend-приложений.


Появление hooks API

После выхода React Hooks библиотека начала адаптироваться к новым практикам.

Ранее основным подходом были HOC и компоненты:

injectIntl(Component)

или

<FormattedMessage />

Позже появился hook useIntl.

Пример:

const intl = useIntl();

intl.formatMessage({
  id: 'menu.settings'
});

Это значительно улучшило:

  • читаемость кода;
  • работу с TypeScript;
  • интеграцию с функциональными компонентами;
  • производительность.

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

Рост популярности TypeScript оказал серьёзное влияние на развитие FormatJS.

Ранние версии библиотеки создавались преимущественно для JavaScript. Однако с распространением TypeScript потребовалась:

  • строгая типизация сообщений;
  • автодополнение;
  • безопасная работа с параметрами;
  • контроль отсутствующих переводов.

Появились типизированные подходы:

intl.formatMessage(
  {
    id: 'user.greeting',
    defaultMessage: 'Hello {name}'
  },
  {
    name: 'Alex'
  }
);

Со временем экосистема начала включать:

  • генерацию типов;
  • поддержку TS transformer;
  • типизированные message descriptors.

Развитие производительности

На ранних этапах ICU MessageFormat считался достаточно тяжёлым.

Парсинг сообщений:

{count, plural, one {# item} other {# items}}

требовал:

  • синтаксического анализа;
  • построения AST;
  • интерпретации правил.

В больших приложениях это создавало проблемы производительности.

FormatJS начал внедрять:

  • предварительную компиляцию сообщений;
  • кеширование;
  • AST-оптимизации;
  • tree-shaking;
  • lazy loading локалей.

Пример предварительной компиляции:

formatjs compile messages.json

Это уменьшало нагрузку во время выполнения.


Поддержка серверного рендеринга

С распространением SSR и Next.js библиотека адаптировалась под серверный рендеринг.

Основные проблемы SSR:

  • синхронизация локали;
  • гидратация;
  • передача словарей;
  • различия Node.js и браузера.

FormatJS постепенно стал поддерживать:

  • Node.js Intl API;
  • серверное форматирование;
  • изоморфные приложения;
  • динамическую загрузку переводов.

Это сделало библиотеку пригодной для enterprise-уровня.


FormatJS как экосистема

Современный FormatJS — уже не просто библиотека, а крупная инфраструктура интернационализации.

Экосистема включает:

  • React-интеграцию;
  • CLI-инструменты;
  • Babel-плагины;
  • полифиллы;
  • AST-компиляторы;
  • parser ICU;
  • tooling для CI/CD;
  • системы оптимизации бандлов.

Архитектура проекта стала ориентироваться на:

  • модульность;
  • стандартизацию;
  • совместимость со спецификациями ECMAScript;
  • поддержку современных сборщиков;
  • интеграцию с monorepo;
  • server/client parity.

Влияние на современную frontend-разработку

FormatJS существенно повлиял на способы локализации JavaScript-приложений.

До его появления локализация часто воспринималась как второстепенная задача. После распространения React Intl многие практики стали индустриальным стандартом:

  • ICU MessageFormat;
  • декларативные переводы;
  • extraction workflow;
  • централизованные словари;
  • plural rules;
  • runtime formatting;
  • locale-aware rendering.

Многие современные библиотеки заимствовали идеи FormatJS:

  • component-based localization;
  • typed messages;
  • CLI extraction;
  • AST compilation;
  • polyfill architecture.

Современное состояние проекта

Современные версии FormatJS ориентированы на:

  • ECMAScript Intl;
  • React 18+;
  • TypeScript;
  • SSR/SSG;
  • edge-runtime;
  • tree-shaking;
  • минимизацию runtime overhead.

Особое внимание уделяется:

  • совместимости со стандартами;
  • уменьшению размера бандла;
  • производительности;
  • удобству CI-интеграции;
  • автоматизации локализации.

FormatJS остаётся одной из самых влиятельных библиотек интернационализации в JavaScript-экосистеме и фактически сформировал современный подход к локализации frontend-приложений.