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

FormatJS в типичном фронтенд-проекте не существует изолированно — он становится частью цепочки сборки, где сообщения извлекаются, компилируются и подгружаются на этапе выполнения. Архитектура интеграции строится вокруг трёх стадий: извлечение (extraction), компиляция (compilation) и использование (runtime consumption).

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


Структура локализационного пайплайна

Типичный поток обработки сообщений выглядит следующим образом:

  1. Исходный код Используются компоненты react-intl или функции formatMessage, где сообщения описываются через id, defaultMessage, description.

  2. Извлечение сообщений На этапе сборки Babel-плагин анализирует AST и собирает все сообщения в промежуточный файл (обычно JSON или POT-подобный формат).

  3. Обработка через CLI @formatjs/cli агрегирует, валидирует и нормализует сообщения, объединяет дубликаты, проверяет наличие описаний и корректность ICU-синтаксиса.

  4. Финальная компиляция Сообщения преобразуются в оптимизированный формат, готовый для загрузки в браузере или на сервере.

  5. Runtime Приложение загружает соответствующий язык и передаёт сообщения в IntlProvider.


Интеграция через Babel

Основной способ внедрения FormatJS в сборку — использование Babel-плагина.

Установка

npm install babel-plugin-formatjs

Конфигурация Babel

{
  "plugins": [
    [
      "formatjs",
      {
        "idInterpolationPattern": "[sha512:contenthash:base64:6]",
        "ast": true,
        "extractFromFormatMessageCall": true,
        "additionalFunctionNames": ["defineMessages", "formatMessage"]
      }
    ]
  ]
}

Поведение плагина

Babel-плагин выполняет статический анализ кода:

  • извлекает id и defaultMessage
  • валидирует ICU-строки
  • генерирует структурированные JSON-файлы
  • предотвращает дублирование сообщений
  • поддерживает hash-based id генерацию

Использование CLI в процессе сборки

Инструмент @formatjs/cli отвечает за агрегацию и обработку сообщений.

Установка

npm install @formatjs/cli

Базовый сценарий извлечения

npx formatjs extract "src/**/*.{ts,tsx,js,jsx}" --out-file messages.json

Компиляция сообщений

npx formatjs compile messages.json --out-file compiled.json

Интеграция через npm scripts

{
  "scripts": {
    "extract": "formatjs extract 'src/**/*.{ts,tsx}' --out-file messages.json",
    "compile": "formatjs compile messages.json --out-file src/i18n/compiled.json",
    "i18n": "npm run extract && npm run compile"
  }
}

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

Webpack-интеграция строится вокруг Babel-loader и post-processing шага.

Базовая конфигурация

module.exports = {
  module: {
    rules: [
      {
        test: /\.[jt]sx?$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            plugins: [
              [
                "formatjs",
                {
                  idInterpolationPattern: "[sha512:contenthash:base64:6]"
                }
              ]
            ]
          }
        }
      }
    ]
  }
};

Разделение локализаций по чанкам

В сложных приложениях локализация часто разбивается по роутам:

  • en/common.json
  • en/dashboard.json
  • en/settings.json

Webpack позволяет загружать их динамически:

import("i18n/en/dashboard.json").then(messages => {
  // инициализация IntlProvider
});

Оптимизация загрузки

Webpack позволяет:

  • выносить локализацию в отдельные чанки
  • использовать SplitChunksPlugin
  • кешировать языковые пакеты

Это критично для приложений с большим количеством языков.


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

Vite не требует сложной конфигурации Babel, но требует явного подключения плагинов.

Базовая настройка

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [
    react({
      babel: {
        plugins: [
          [
            "formatjs",
            {
              idInterpolationPattern: "[sha512:contenthash:base64:6]"
            }
          ]
        ]
      }
    })
  ]
});

Особенности Vite-интеграции

  • быстрый HMR не ломает extraction
  • сообщения извлекаются только в build-режиме при корректной настройке
  • рекомендуется отдельный prebuild-скрипт для formatjs extract

Рекомендуемый подход

{
  "scripts": {
    "build": "vite build",
    "prebuild": "formatjs extract 'src/**/*.{ts,tsx}' --out-file messages.json"
  }
}

Интеграция с Next.js

Next.js требует особого подхода из-за SSR и гибридной архитектуры.

Базовый принцип

  • сообщения должны быть доступны и на сервере, и на клиенте
  • локализация должна сериализоваться в HTML payload

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

import { IntlProvider } from "react-intl";
import messages from "../i18n/compiled/en.json";

export default function App({ Component, pageProps }) {
  return (
    <IntlProvider locale="en" messages={messages}>
      <Component {...pageProps} />
    </IntlProvider>
  );
}

SSR и гидратация

На сервере:

  • выбирается язык по cookie или headers
  • подгружаются соответствующие сообщения
  • передаются в pageProps
export async function getServerSideProps({ locale }) {
  const messages = await import(`../i18n/${locale}.json`);

  return {
    props: {
      messages: messages.default
    }
  };
}

Оптимизация для Next.js

  • разделение переводов по locale
  • использование dynamic import
  • минимизация JSON payload

Монорепозитории и shared-i18n слой

В монорепозиториях FormatJS часто выносится в отдельный пакет:

packages/
  app/
  ui/
  i18n/

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

  • единый источник сообщений
  • переиспользование ключей
  • централизованная проверка ICU

Пример структуры

i18n/
  src/
    messages/
      en.json
      ru.json
  scripts/
    extract.js
    compile.js

Сборка в workspace

npm run -w i18n compile
npm run -w app build

Валидация сообщений на этапе сборки

FormatJS поддерживает строгую проверку ICU-строк.

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

  • несоответствие параметров {name}
  • некорректные plural rules
  • отсутствующие id
  • дублирование ключей

Пример строгой проверки

npx formatjs compile messages.json --strict

Инкрементальная обработка сообщений

В крупных приложениях полная пересборка локализации становится дорогой операцией.

Используются подходы:

  • кеширование AST анализа Babel
  • инкрементальный extract
  • разделение по модулям
  • хранение hash-based id

Работа с ICU-сообщениями в сборке

FormatJS активно опирается на ICU Message Format.

Пример:

{
  "id": "cart.items",
  "defaultMessage": "В корзине {count, plural, one {# товар} few {# товара} many {# товаров} other {# товаров}}"
}

На этапе сборки:

  • проверяется корректность plural rules
  • валидируются переменные
  • нормализуется AST сообщения

Код-сплиттинг локализаций

Для оптимизации загрузки часто применяют ленивую подгрузку языков:

export async function loadLocale(locale) {
  const messages = await import(`../i18n/${locale}.json`);
  return messages.default;
}

В связке с bundler’ом это позволяет:

  • уменьшить initial bundle size
  • ускорить first paint
  • кэшировать языки отдельно

Кастомизация pipeline под проект

В реальных системах FormatJS интеграция почти всегда расширяется:

  • кастомные Babel-плагины
  • постобработка JSON
  • интеграция с CI (проверка отсутствующих переводов)
  • генерация типов для TypeScript

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

npx formatjs extract --out-file messages.json
npx formatjs compile messages.json --out-file messages.compiled.json

Далее через скрипты генерируются типы:

export type MessageIds =
  | "cart.items"
  | "user.greeting";

CI/CD интеграция

Типичный pipeline включает шаги:

  • extract сообщений
  • проверка ICU валидности
  • сравнение с базовой веткой
  • компиляция
  • публикация артефактов

Пример GitHub Actions шага:

- name: Extract messages
  run: npm run i18n

- name: Validate ICU
  run: npx formatjs compile messages.json --strict

Производственные особенности интеграции

В продакшене критичны:

  • стабильность id (hash vs human-readable)
  • кеширование переводов
  • минимизация runtime parsing
  • предкомпиляция ICU AST

Сильные сборочные конфигурации стремятся перенести максимум логики на build-time, оставляя runtime только рендеринг и выбор локали.