В экосистеме FormatJS сообщения обычно описываются в формате ICU Message Syntax и компилируются во время выполнения приложения. Такой подход удобен на этапе разработки, однако в крупных приложениях возникают дополнительные издержки:
Предкомпиляция сообщений решает эти проблемы путём преобразования ICU-строк в сериализованные AST-структуры ещё на этапе сборки.
Пример обычного сообщения:
{
"title": "Hello, {name}!"
}
После предкомпиляции сообщение превращается в массив токенов:
{
"title": [
{
"type": 0,
"value": "Hello, "
},
{
"type": 1,
"value": "name"
},
{
"type": 0,
"value": "!"
}
]
}
Во время выполнения FormatJS больше не требуется разбирать ICU-строку — библиотека сразу использует готовое AST-представление.
Внутри FormatJS используется пакет:
@formatjs/icu-messageformat-parser
Он преобразует ICU Message Syntax в AST.
Этапы обработки:
IntlMessageFormat.Схема обработки:
ICU Message
↓
Parser
↓
AST
↓
Serialized JSON
↓
Runtime Formatting
Сообщение:
Hello, {name}!
После парсинга превращается в AST:
[
{
type: 0,
value: 'Hello, '
},
{
type: 1,
value: 'name'
},
{
type: 0,
value: '!'
}
]
Типы токенов:
| Type | Назначение |
|---|---|
| 0 | обычный текст |
| 1 | переменная |
| 6 | plural |
| 5 | select |
| 8 | tag |
Основной CLI-инструмент:
npm install --save-dev @formatjs/cli
Проверка установки:
npx formatjs --help
Структура проекта:
src/
locales/
en.json
ru.json
Содержимое файла:
{
"hello": "Hello, {name}!"
}
Компиляция:
npx formatjs compile src/locales/en.json --out-file dist/en.json
Результат:
{
"hello": [
{
"type": 0,
"value": "Hello, "
},
{
"type": 1,
"value": "name"
},
{
"type": 0,
"value": "!"
}
]
}
npx formatjs compile-folder src/locales compiled-locales
Структура после обработки:
compiled-locales/
en.json
ru.json
<IntlProvider
locale="en"
messages={messages}
>
<App />
</IntlProvider>
import messages from './compiled-locales/en.json';
<IntlProvider
locale="en"
messages={messages}
>
<App />
</IntlProvider>
Код приложения не меняется — изменяется только структура входных данных.
Без предкомпиляции:
Runtime:
ICU string → parse → AST → format
С предкомпиляцией:
Build:
ICU string → parse → AST
Runtime:
AST → format
Наиболее тяжёлая часть — разбор ICU — переносится из браузера в этап сборки.
После перехода на AST можно удалить runtime-парсер из клиентской сборки.
Это уменьшает:
Особенно заметен эффект в:
Обычные ICU-строки требуют дополнительных runtime-зависимостей.
AST-представление позволяет bundler’у эффективнее выполнять:
Webpack, Rollup и Vite работают значительно эффективнее с уже скомпилированными сообщениями.
FormatJS поддерживает дополнительную оптимизацию.
Пример:
npx formatjs compile src/locales/en.json \
--ast \
--out-file dist/en.json
Минифицированное AST:
[
{
"type": 0,
"value": "Hello "
},
{
"type": 1,
"value": "name"
}
]
Размер JSON-файлов уменьшается особенно заметно при больших plural-конструкциях.
Исходное сообщение:
{
"items": "{count, plural, =0 {No items} one {# item} other {# items}}"
}
Предкомпилированная версия:
{
"items": [
{
"type": 6,
"value": "count",
"options": {
"=0": {
"value": [
{
"type": 0,
"value": "No items"
}
]
},
"one": {
"value": [
{
"type": 7
},
{
"type": 0,
"value": " item"
}
]
},
"other": {
"value": [
{
"type": 7
},
{
"type": 0,
"value": " items"
}
]
}
}
}
]
}
Plural-конструкции особенно выигрывают от предкомпиляции, поскольку их парсинг достаточно дорогой.
FormatJS предоставляет Babel-плагин:
npm install --save-dev babel-plugin-formatjs
Конфигурация:
module.exports = {
plugins: [
[
'formatjs',
{
ast: true
}
]
]
};
Теперь сообщения компилируются автоматически во время transpilation.
Типизация AST-сообщений:
import {MessageFormatElement} from 'react-intl';
type Messages = Record<
string,
MessageFormatElement[]
>;
Использование:
import messages from './compiled/en.json';
const localized: Messages = messages;
Пример build-скрипта:
{
"scripts": {
"i18n:compile": "formatjs compile-folder src/locales src/compiled-locales"
}
}
Интеграция в pipeline:
{
"scripts": {
"build": "npm run i18n:compile && vite build"
}
}
Пример через npm scripts:
{
"scripts": {
"prebuild": "formatjs compile-folder src/locales compiled",
"build": "webpack"
}
}
Команда:
formatjs compile-folder input output
Поддерживает:
Пример:
npx formatjs compile-folder lang compiled-lang --ast
--formatПозволяет изменять структуру результата.
Пример:
npx formatjs compile src/en.json \
--format simple
Возможные форматы:
| Формат | Назначение |
|---|---|
| simple | упрощённая структура |
| smartling | интеграция Smartling |
| crowdin | интеграция Crowdin |
При серверном рендеринге предкомпиляция особенно важна.
Без AST:
Request
↓
Server parses ICU
↓
HTML render
С AST:
Request
↓
Ready AST
↓
HTML render
Это уменьшает:
Предкомпилированные AST-файлы удобно кэшировать:
CDN
Browser Cache
Memory Cache
SSR Cache
Поскольку сообщения становятся статическими JSON-структурами, их можно эффективно хранить и переиспользовать.
Пример lazy loading:
const messages = await import(
`./compiled/${locale}.json`
);
В сочетании с AST это даёт:
В мобильной среде преимущества особенно заметны:
AST уменьшает количество runtime-операций и ускоряет startup приложения.
Программный API:
const {compile} = require('@formatjs/cli-lib');
const result = compile(
{
hello: 'Hello {name}'
},
{
ast: true
}
);
Часто используется отдельный pipeline:
Extract Messages
↓
Translate
↓
Compile AST
↓
Build Application
Пример CI-команды:
npm run i18n:compile
FormatJS валидирует ICU-синтаксис.
Ошибка:
Expected "}" but end of input found
Пример некорректного сообщения:
Hello {name
Преимущество предкомпиляции — ошибки обнаруживаются до production.
Пример проверки:
formatjs compile-folder lang compiled \
--throws
Сборка завершится ошибкой при некорректных сообщениях.
При тысячах сообщений рекомендуется:
Пример:
locales/
dashboard/
profile/
admin/
Часто применяется:
en.a1f2c3.json
ru.b8d7e2.json
Это улучшает:
| Характеристика | Runtime ICU | Precompiled AST |
|---|---|---|
| Парсинг в браузере | Да | Нет |
| Размер runtime | Больше | Меньше |
| Скорость startup | Ниже | Выше |
| SSR производительность | Ниже | Выше |
| Проверка ошибок | Частично | На build-этапе |
| Оптимизация bundler | Ограничена | Лучше |
Практически необходима при:
AST-файлы:
Поэтому исходные ICU-сообщения обычно хранят отдельно от compiled-версий.
src/
locales/
raw/
en.json
ru.json
compiled/
en.json
ru.json
ICU Messages
↓
Translation Platform
↓
FormatJS Compile
↓
AST JSON
↓
Bundler
↓
Production Build
Исходные файлы:
{
"welcome": "Welcome, {name}"
}
Compiled:
{
"welcome": [
{
"type": 0,
"value": "Welcome, "
},
{
"type": 1,
"value": "name"
}
]
}
Редактирование выполняется только в raw-локалях.
react-intl полностью поддерживает AST-сообщения:
<IntlProvider messages={compiledMessages}>
Дополнительная конфигурация не требуется.
При тысячах сообщений предкомпиляция позволяет:
На слабых устройствах разница может составлять сотни миллисекунд.
Типичная схема:
packages/
ui/
shared-i18n/
admin/
mobile/
Пакет shared-i18n содержит:
Это позволяет переиспользовать переводы между приложениями.