FormatJS в типичном фронтенд-проекте не существует изолированно — он становится частью цепочки сборки, где сообщения извлекаются, компилируются и подгружаются на этапе выполнения. Архитектура интеграции строится вокруг трёх стадий: извлечение (extraction), компиляция (compilation) и использование (runtime consumption).
Ключевой момент заключается в том, что исходный код приложения содержит декларативные сообщения, а итоговые локализационные данные формируются автоматически через инструменты сборки.
Типичный поток обработки сообщений выглядит следующим образом:
Исходный код Используются компоненты
react-intl или функции formatMessage, где
сообщения описываются через id,
defaultMessage, description.
Извлечение сообщений На этапе сборки Babel-плагин анализирует AST и собирает все сообщения в промежуточный файл (обычно JSON или POT-подобный формат).
Обработка через CLI @formatjs/cli
агрегирует, валидирует и нормализует сообщения, объединяет дубликаты,
проверяет наличие описаний и корректность ICU-синтаксиса.
Финальная компиляция Сообщения преобразуются в оптимизированный формат, готовый для загрузки в браузере или на сервере.
Runtime Приложение загружает соответствующий
язык и передаёт сообщения в IntlProvider.
Основной способ внедрения FormatJS в сборку — использование Babel-плагина.
npm install babel-plugin-formatjs
{
"plugins": [
[
"formatjs",
{
"idInterpolationPattern": "[sha512:contenthash:base64:6]",
"ast": true,
"extractFromFormatMessageCall": true,
"additionalFunctionNames": ["defineMessages", "formatMessage"]
}
]
]
}
Babel-плагин выполняет статический анализ кода:
id и defaultMessageИнструмент @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
{
"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-интеграция строится вокруг 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.jsonen/dashboard.jsonen/settings.jsonWebpack позволяет загружать их динамически:
import("i18n/en/dashboard.json").then(messages => {
// инициализация IntlProvider
});
Webpack позволяет:
SplitChunksPluginЭто критично для приложений с большим количеством языков.
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]"
}
]
]
}
})
]
});
formatjs extract{
"scripts": {
"build": "vite build",
"prebuild": "formatjs extract 'src/**/*.{ts,tsx}' --out-file messages.json"
}
}
Next.js требует особого подхода из-за SSR и гибридной архитектуры.
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>
);
}
На сервере:
pagePropsexport async function getServerSideProps({ locale }) {
const messages = await import(`../i18n/${locale}.json`);
return {
props: {
messages: messages.default
}
};
}
В монорепозиториях FormatJS часто выносится в отдельный пакет:
packages/
app/
ui/
i18n/
i18n/
src/
messages/
en.json
ru.json
scripts/
extract.js
compile.js
npm run -w i18n compile
npm run -w app build
FormatJS поддерживает строгую проверку ICU-строк.
{name}idnpx formatjs compile messages.json --strict
В крупных приложениях полная пересборка локализации становится дорогой операцией.
Используются подходы:
FormatJS активно опирается на ICU Message Format.
Пример:
{
"id": "cart.items",
"defaultMessage": "В корзине {count, plural, one {# товар} few {# товара} many {# товаров} other {# товаров}}"
}
На этапе сборки:
Для оптимизации загрузки часто применяют ленивую подгрузку языков:
export async function loadLocale(locale) {
const messages = await import(`../i18n/${locale}.json`);
return messages.default;
}
В связке с bundler’ом это позволяет:
В реальных системах FormatJS интеграция почти всегда расширяется:
npx formatjs extract --out-file messages.json
npx formatjs compile messages.json --out-file messages.compiled.json
Далее через скрипты генерируются типы:
export type MessageIds =
| "cart.items"
| "user.greeting";
Типичный pipeline включает шаги:
Пример GitHub Actions шага:
- name: Extract messages
run: npm run i18n
- name: Validate ICU
run: npx formatjs compile messages.json --strict
В продакшене критичны:
id (hash vs human-readable)Сильные сборочные конфигурации стремятся перенести максимум логики на build-time, оставляя runtime только рендеринг и выбор локали.