FormatJS — набор библиотек для интернационализации JavaScript-приложений. Основная задача библиотеки — организация локализации интерфейса, форматирование дат, времени, чисел, валют и сообщений в соответствии с региональными настройками пользователя.
Экосистема FormatJS включает несколько пакетов:
react-intl — интернационализация React-приложений;intl-messageformat — форматирование сообщений ICU;@formatjs/intl — полифилы для API
Intl;babel-plugin-formatjs — извлечение и оптимизация
сообщений;@formatjs/cli — инструменты командной строки;@formatjs/ts-transformer — интеграция с
TypeScript.Библиотека построена вокруг стандарта ECMAScript Internationalization
API (Intl) и активно использует ICU Message Syntax.
Наиболее распространённый вариант использования — пакет
react-intl.
Установка через npm:
npm install react-intl
Установка через yarn:
yarn add react-intl
Установка через pnpm:
pnpm add react-intl
После установки становятся доступны:
FormatJS использует встроенный объект Intl. Современные
браузеры поддерживают его по умолчанию, однако старые среды могут
требовать полифилы.
Проверка поддержки:
console.log(Intl);
Проверка конкретного API:
console.log(Intl.RelativeTimeFormat);
При отсутствии поддержки потребуется подключение дополнительных модулей.
Для Internet Explorer и старых мобильных браузеров используется пакет:
npm install @formatjs/intl-pluralrules
Дополнительно могут понадобиться:
npm install @formatjs/intl-relativetimeformat
npm install @formatjs/intl-numberformat
npm install @formatjs/intl-datetimeformat
Пример инициализации:
import '@formatjs/intl-pluralrules/polyfill';
import '@formatjs/intl-relativetimeformat/polyfill';
import '@formatjs/intl-pluralrules/locale-data/ru';
import '@formatjs/intl-relativetimeformat/locale-data/ru';
Для английской локали:
import '@formatjs/intl-pluralrules/locale-data/en';
Типичная структура:
src/
├── i18n/
│ ├── messages/
│ │ ├── en.json
│ │ └── ru.json
│ ├── config.js
│ └── provider.jsx
├── components/
└── app.jsx
| Файл | Назначение |
|---|---|
en.json |
Английские переводы |
ru.json |
Русские переводы |
config.js |
Конфигурация локалей |
provider.jsx |
Подключение IntlProvider |
{
"app.title": "Application",
"menu.home": "Home",
"menu.profile": "Profile"
}
{
"app.title": "Приложение",
"menu.home": "Главная",
"menu.profile": "Профиль"
}
Главный компонент библиотеки — IntlProvider.
import React from 'react';
import ReactDOM from 'react-dom/client';
import { IntlProvider } from 'react-intl';
import ruMessages from './i18n/messages/ru.json';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<IntlProvider locale="ru" messages={ruMessages}>
<App />
</IntlProvider>
);
IntlProvider выполняет несколько задач:
Текущая локаль приложения.
<IntlProvider locale="ru">
Объект переводов.
<IntlProvider messages={messages}>
Локаль по умолчанию.
<IntlProvider
locale="ru"
defaultLocale="en"
>
Обработчик ошибок локализации.
<IntlProvider
onEr ror={(error) => {
console.error(error);
}}
>
Наиболее популярный компонент библиотеки.
import { FormattedMessage } from 'react-intl';
function Header() {
return (
<h1>
<FormattedMessage id="app.title" />
</h1>
);
}
defaultMessage используется как резервный текст.
<FormattedMessage
id="menu.home"
defaultMessage="Home"
/>
Если перевод отсутствует, будет показан текст из
defaultMessage.
FormatJS использует ICU-синтаксис.
Пример параметров:
{
"welcome": "Добро пожаловать, {name}"
}
Использование:
<FormattedMessage
id="welcome"
values={{ name: 'Алексей' }}
/>
Результат:
Добро пожаловать, Алексей
import { useState } from 'react';
import { IntlProvider } from 'react-intl';
import ru from './ru.json';
import en from './en.json';
const messages = {
ru,
en
};
function App() {
const [locale, setLocale] = useState('ru');
return (
<IntlProvider
locale={locale}
messages={messages[locale]}
>
<Main />
</IntlProvider>
);
}
const locale = navigator.language;
Пример:
console.log(navigator.language);
Результат:
ru-RU
Часто используются только короткие коды языка.
const locale = navigator.language.split('-')[0];
Результат:
ru
Пример:
import en from './messages/en.json';
import ru from './messages/ru.json';
export const messages = {
en,
ru
};
export const defaultLocale = 'en';
Существует несколько подходов к именованию ключей.
{
"header.logo": "Логотип",
"header.profile": "Профиль"
}
{
"auth.login.title": "Авторизация",
"auth.login.button": "Войти"
}
{
"loginButton": "Войти"
}
На крупных проектах предпочтительна feature-based организация.
Для извлечения сообщений используется:
npm install babel-plugin-formatjs --save-dev
{
"plugins": [
[
"formatjs",
{
"idInterpolationPattern": "[sha512:contenthash:base64:6]",
"ast": true
}
]
]
}
Плагин решает несколько задач:
npm install @formatjs/cli --save-dev
Пример команды:
formatjs extract "src/**/*.{js,jsx,ts,tsx}" \
--out-file lang/en.json
formatjs compile lang/en.json \
--out-file compiled/en.json
Установка типов:
npm install --save-dev @types/react
Сам react-intl уже содержит встроенные типы.
import { FormattedMessage } from 'react-intl';
export function Title() {
return (
<FormattedMessage id="app.title" />
);
}
type MessageIds =
| 'app.title'
| 'menu.home'
| 'menu.profile';
import { useIntl } from 'react-intl';
export function useTranslate() {
const intl = useIntl();
return (id: string) => intl.formatMessage({ id });
}
Для уменьшения размера bundle локали часто загружаются динамически.
async function loadLocale(locale) {
switch (locale) {
case 'ru':
return import('./messages/ru.json');
case 'en':
return import('./messages/en.json');
default:
return import('./messages/en.json');
}
}
const [messages, setMessages] = useState(null);
useEffect(() => {
loadLocale(locale).then((module) => {
setMessages(module.default);
});
}, [locale]);
if (!messages) {
return <div>Loading...</div>;
}
Установка:
npm install react-intl
Настройка дополнительных плагинов обычно не требуется.
Пример vite.config.js:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()]
});
Установка:
npm install react-intl
Пример использования:
import { IntlProvider } from 'react-intl';
export default function App({ Component, pageProps }) {
return (
<IntlProvider locale="ru">
<Component {...pageProps} />
</IntlProvider>
);
}
FormatJS поддерживает SSR без дополнительной конфигурации.
Пример:
import { renderToString } from 'react-dom/server';
Библиотека использует Context API, поэтому серверная локализация работает прозрачно.
Хук предоставляет доступ к API форматирования.
import { useIntl } from 'react-intl';
function Profile() {
const intl = useIntl();
return (
<h1>
{intl.formatMessage({
id: 'profile.title'
})}
</h1>
);
}
intl.formatNumber(1000);
Формат валюты:
intl.formatNumber(1000, {
style: 'currency',
currency: 'RUB'
});
intl.formatDate(new Date());
С дополнительными параметрами:
intl.formatDate(new Date(), {
year: 'numeric',
month: 'long',
day: 'numeric'
});
В development-режиме FormatJS показывает предупреждения:
В production часть проверок отключается для повышения производительности.
<IntlProvider
onEr ror={(error) => {
if (error.code === 'MISSING_TRANSLATION') {
return;
}
console.error(error);
}}
>
Ошибка:
Missing locale data
Причина — не подключены данные локали для полифила.
<FormattedMessage id="unknown.key" />
Результат:
[React Intl] Missing message
Неверный шаблон:
{
"msg": "Hello {name"
}
Правильный вариант:
{
"msg": "Hello {name}"
}
Рекомендуется всегда указывать резервный текст:
<FormattedMessage
id="button.save"
defaultMessage="Save"
/>
Оптимальный подход:
i18n/
messages/
hooks/
providers/
utils/
Пример:
messages/
auth/
dashboard/
profile/
Такой подход упрощает поддержку больших приложений.
Пример:
<IntlProvider
locale={locale}
messages={messages}
defaultLocale="en"
onEr ror={() => {}}
>
<App />
</IntlProvider>
В production обычно отключается вывод предупреждений в консоль.
import React from 'react';
import ReactDOM from 'react-dom/client';
import { IntlProvider } from 'react-intl';
import messages from './messages/ru.json';
import App from './App';
ReactDOM.createRoot(
document.getElementById('root')
).render(
<IntlProvider
locale="ru"
messages={messages}
defaultLocale="en"
>
<App />
</IntlProvider>
);