Библиотека FormatJS строится вокруг стандарта ECMAScript
Internationalization API (Intl) и предоставляет набор
инструментов для интернационализации JavaScript-приложений независимо от
используемого окружения. Благодаря модульной архитектуре FormatJS
совместим с браузерами, Node.js, React, React Native, серверным
рендерингом, статической генерацией и современными сборщиками.
Основные пакеты экосистемы:
| Пакет | Назначение |
|---|---|
react-intl |
Интеграция с React |
intl-messageformat |
Форматирование ICU-сообщений |
@formatjs/intl |
Polyfill-реализации Intl API |
@formatjs/cli |
Извлечение и компиляция переводов |
babel-plugin-formatjs |
Оптимизация и extraction сообщений |
@formatjs/ts-transformer |
Поддержка TypeScript |
FormatJS не привязан к конкретному UI-фреймворку. Большая часть функциональности работает на чистом JavaScript, а React-слой является отдельной надстройкой.
FormatJS активно использует встроенный API Intl.
Современные браузеры поддерживают большую часть возможностей:
Intl.DateTimeFormatIntl.NumberFormatIntl.RelativeTimeFormatIntl.PluralRulesIntl.DisplayNamesIntl.ListFormatОднако степень поддержки зависит от браузера и его версии.
| Браузер | Поддержка |
|---|---|
| Chrome | Полная |
| Firefox | Полная |
| Safari | Частичная в старых версиях |
| Edge | Полная |
| Internet Explorer 11 | Требуются polyfill |
Для старых браузеров необходимо подключать polyfill-пакеты.
npm install @formatjs/intl-pluralrules
npm install @formatjs/intl-relativetimeformat
npm install @formatjs/intl-numberformat
import '@formatjs/intl-pluralrules/polyfill';
import '@formatjs/intl-relativetimeformat/polyfill';
import '@formatjs/intl-numberformat/polyfill';
Для конкретных локалей:
import '@formatjs/intl-pluralrules/locale-data/ru';
import '@formatjs/intl-relativetimeformat/locale-data/ru';
Оптимальным подходом является динамическая загрузка только при отсутствии поддержки.
async function loadPolyfills() {
if (!Intl.PluralRules) {
await import('@formatjs/intl-pluralrules/polyfill');
await import('@formatjs/intl-pluralrules/locale-data/ru');
}
}
Такой подход уменьшает размер основного bundle.
FormatJS полностью поддерживается в Node.js.
Пример:
import {IntlMessageFormat} from 'intl-messageformat';
const message = new IntlMessageFormat(
'Привет, {name}',
'ru'
);
console.log(
message.format({name: 'Алексей'})
);
Node.js использует ICU (International Components for Unicode) для
работы Intl.
Существует два режима:
| Режим | Описание |
|---|---|
| small-icu | Только английская локаль |
| full-icu | Полная поддержка локалей |
console.log(
Intl.DateTimeFormat.supportedLocalesOf(['ru'])
);
Если результат пустой — ICU отсутствует.
npm install full-icu
NODE_ICU_DATA=node_modules/full-icu node app.js
Основной пакет для React — react-intl.
npm install react-intl
Базовый провайдер локализации:
import {IntlProvider} from 'react-intl';
<IntlProvider
locale="ru"
messages={messages}
>
<App />
</IntlProvider>
import {useIntl} from 'react-intl';
function Price() {
const intl = useIntl();
return (
<span>
{intl.formatNumber(1500, {
style: 'currency',
currency: 'RUB'
})}
</span>
);
}
FormatJS совместим с React 18:
react-intl корректно работает в
StrictMode.
<React.StrictMode>
<App />
</React.StrictMode>
FormatJS хорошо подходит для серверного рендеринга.
_app.jsimport {IntlProvider} from 'react-intl';
export default function App({Component, pageProps}) {
return (
<IntlProvider
locale={pageProps.locale}
messages={pageProps.messages}
>
<Component {...pageProps} />
</IntlProvider>
);
}
export async function getStaticProps({locale}) {
const messages = (
await import(`../lang/${locale}.json`)
).default;
return {
props: {
locale,
messages
}
};
}
В Next.js App Router FormatJS может использоваться внутри client-компонентов.
'use client';
import {IntlProvider} from 'react-intl';
Hydration mismatch возникает при различии локалей сервера и клиента.
Неправильно:
locale={navigator.language}
Правильно:
locale={serverLocale}
Remix поддерживает серверный рендеринг и потоковую передачу данных, поэтому FormatJS интегрируется без дополнительных адаптеров.
export async function loader() {
return json({
locale: 'ru',
messages
});
}
<IntlProvider
locale={data.locale}
messages={data.messages}
>
<Outlet />
</IntlProvider>
FormatJS подходит для SSG.
export const wrapRootElement = ({element}) => (
<IntlProvider
locale="ru"
messages={messages}
>
{element}
</IntlProvider>
);
Обычно используются:
/ru//en//de/Генерация страниц выполняется через createPages.
FormatJS можно использовать независимо от React.
import {IntlMessageFormat} from 'intl-messageformat';
const msg = new IntlMessageFormat(
'Товаров: {count}',
'ru'
);
msg.format({count: 5});
Обычно FormatJS применяется как слой ICU-форматирования внутри
vue-i18n.
import {IntlMessageFormat} from 'intl-messageformat';
Angular имеет собственную i18n-систему, однако FormatJS используется:
import {IntlMessageFormat} from 'intl-messageformat';
@Injectable()
export class I18nService {
format(message, values) {
return new IntlMessageFormat(
message,
'ru'
).format(values);
}
}
FormatJS интегрируется через обычные JavaScript-модули.
import {writable} from 'svelte/store';
export const locale = writable('ru');
import {IntlMessageFormat} from 'intl-messageformat';
React Native не всегда содержит полноценный Intl.
Особенно это касается:
npm install @formatjs/intl-getcanonicallocales
npm install @formatjs/intl-locale
npm install @formatjs/intl-pluralrules
import '@formatjs/intl-getcanonicallocales/polyfill';
import '@formatjs/intl-locale/polyfill';
import '@formatjs/intl-pluralrules/polyfill';
Hermes частично поддерживает Intl, но возможности
зависят от версии React Native.
Для старых версий часто требуется полный набор polyfill.
Electron использует Chromium и Node.js одновременно, поэтому FormatJS работает практически без ограничений.
import {IntlProvider} from 'react-intl';
import {IntlMessageFormat} from 'intl-messageformat';
FormatJS поддерживает TypeScript на уровне API.
import {IntlShape} from 'react-intl';
function formatPrice(intl: IntlShape) {
return intl.formatNumber(1000);
}
type MessageIds =
| 'app.title'
| 'menu.home';
FormatJS предоставляет TypeScript transformer.
npm install @formatjs/ts-transformer
FormatJS полностью совместим с Webpack.
npm install babel-plugin-formatjs
{
plugins: [
['formatjs', {
idInterpolationPattern:
'[sha512:contenthash:base64:6]'
}]
]
}
Vite поддерживает FormatJS без специальных адаптеров.
import {defineConfig} from 'vite';
export default defineConfig({
plugins: []
});
ESBuild поддерживает FormatJS через Babel-плагины или precompile.
Rollup работает через стандартную Babel-интеграцию.
FormatJS безопасен для SSR.
intl.formatDate(new Date());
Нельзя использовать глобальный singleton.
Неправильно:
const intl = createIntl(...);
Правильно:
function handleRequest(req) {
const intl = createIntl(...);
}
const cache = createIntlCache();
Современные edge-среды:
поддерживают большую часть Intl.
Некоторые edge-runtime не содержат:
if (Intl.RelativeTimeFormat) {
// supported
}
FormatJS может работать в Deno благодаря поддержке ES Modules.
import {IntlMessageFormat}
from 'npm:intl-messageformat';
Bun поддерживает большую часть Node.js API и Intl.
FormatJS работает без изменений в коде.
import {IntlMessageFormat}
from 'intl-messageformat';
const {
IntlMessageFormat
} = require('intl-messageformat');
<script src="
https://unpkg.com/react-intl
"></script>
<script src="
https://cdn.jsdelivr.net/npm/react-intl
"></script>
FormatJS реализует ICU Message syntax.
Поддерживаются:
'{count, plural,
one {# товар}
few {# товара}
many {# товаров}
}'
'{gender, select,
male {Он}
female {Она}
other {Они}
}'
FormatJS использует данные Unicode CLDR.
Поддерживаются:
const messages = await import(
`./lang/${locale}.json`
);
Каждая локаль может находиться в отдельном чанке.
FormatJS хорошо работает в:
Обычно создаётся отдельный пакет:
packages/i18n
export const messages = {
'app.title': 'Приложение'
};
FormatJS поддерживается в Jest.
global.Intl = Intl;
render(
<IntlProvider locale="ru">
<Component />
</IntlProvider>
);
Локаль должна быть фиксированной:
locale="en"
Иначе snapshots будут различаться.
cy.visit('/ru');
cy.contains('Главная');
export const decorators = [
(Story) => (
<IntlProvider locale="ru">
<Story />
</IntlProvider>
)
];
Каждый microfrontend может иметь собственный
IntlProvider.
Альтернативный вариант — единый провайдер в shell-приложении.
FormatJS полностью совместим с:
FormatJS может использоваться внутри custom elements.
class MyElement extends HTMLElement {
connectedCallback() {
const formatter =
new Intl.NumberFormat('ru');
}
}
Наиболее частые источники ошибок:
| Проблема | Причина |
|---|---|
| Missing locale data | Не подключён locale-data |
| Hydration mismatch | Разные локали |
| Unsupported Intl API | Старый браузер |
| ICU parsing error | Некорректный ICU-синтаксис |
| Different timezone | Сервер и клиент используют разные TZ |
console.log(Intl);
console.log(Intl.PluralRules);
console.log(Intl.RelativeTimeFormat);
if (!Intl.ListFormat) {
await import(
'@formatjs/intl-listformat/polyfill'
);
}
import(
`@formatjs/intl-pluralrules/locale-data/${locale}`
);
Локаль должна определяться на сервере и передаваться клиенту.
Компиляция ICU-сообщений во время build уменьшает runtime-издержки.
formatjs compile
Основные способы: