Налаштування i18n-фреймворка (react-intl) для веб-програми
React Intl — частина проекту FormatJS. Відмінність від i18next: react-intl строго слідує стандарту ICU Message Format, що дає потужний синтаксис для складних випадків: плюралізація, вибір за значенням, форматування дат/чисел/валюти — все через єдиний стандарт без кастомних помічників.
Установка
npm install react-intl
Для TypeScript — типи включені в пакет.
Ініціалізація
// i18n/messages/ru.ts
export const ru = {
'nav.catalog': 'Каталог',
'nav.cart': '{count, plural, one {Корзина ({count} товар)} few {Корзина ({count} товара)} many {Корзина ({count} товаров)} other {Корзина ({count} товаров)}}',
'product.price': '{price, number, ::currency/RUB}',
'product.added': 'Товар добавлен {date, date, medium}',
'order.status': '{status, select, pending {Ожидает} paid {Оплачен} cancelled {Отменён} other {Неизвестно}}',
'welcome': 'Добро пожаловать, {name}!',
}
// i18n/messages/en.ts
export const en = {
'nav.catalog': 'Catalog',
'nav.cart': '{count, plural, one {Cart ({count} item)} other {Cart ({count} items)}}',
'product.price': '{price, number, ::currency/USD}',
'product.added': 'Product added {date, date, medium}',
'order.status': '{status, select, pending {Pending} paid {Paid} cancelled {Cancelled} other {Unknown}}',
'welcome': 'Welcome, {name}!',
}
// App.tsx
import { IntlProvider } from 'react-intl'
import { ru } from '@/i18n/messages/ru'
import { en } from '@/i18n/messages/en'
const messages = { ru, en }
function App({ locale = 'ru' }: { locale: string }) {
return (
<IntlProvider
locale={locale}
messages={messages[locale as keyof typeof messages]}
defaultLocale="ru"
onError={(err) => {
// Не падати при відсутніх ключах у розробці
if (err.code !== 'MISSING_TRANSLATION') throw err
}}
>
<Router />
</IntlProvider>
)
}
Використання в компонентах
import { FormattedMessage, FormattedNumber, FormattedDate, useIntl } from 'react-intl'
// Простий текст
function Greeting({ name }: { name: string }) {
return <FormattedMessage id="welcome" values={{ name }} />
}
// Плюралізація
function CartIcon({ count }: { count: number }) {
return (
<span>
<FormattedMessage id="nav.cart" values={{ count }} />
</span>
)
// count=1: "Корзина (1 товар)"
// count=5: "Корзина (5 товаров)"
}
// Числа та валюта
function Price({ value }: { value: number }) {
return (
<FormattedNumber
value={value}
style="currency"
currency="RUB"
maximumFractionDigits={0}
/>
)
// ru: "14 990 ₽"
}
// Дата
function ProductDate({ date }: { date: Date }) {
return <FormattedDate value={date} year="numeric" month="long" day="numeric" />
// ru: "28 марта 2026 г."
}
// select — статуси, ролі, категорії
function OrderStatus({ status }: { status: string }) {
return <FormattedMessage id="order.status" values={{ status }} />
}
useIntl хук для імперативного використання
function SearchInput() {
const intl = useIntl()
return (
<input
type="search"
placeholder={intl.formatMessage({ id: 'search.placeholder' })}
aria-label={intl.formatMessage({ id: 'search.label' })}
/>
)
}
defineMessages: безпечна типізація
import { defineMessages, useIntl } from 'react-intl'
// Оголошуємо повідомлення як константи — IDE автодоповнення та TypeScript знайдуть помилки
const messages = defineMessages({
title: {
id: 'catalog.title',
defaultMessage: 'Каталог товаров',
description: 'Заголовок сторінки каталогу',
},
empty: {
id: 'catalog.empty',
defaultMessage: 'Товары не найдены',
},
})
function CatalogPage() {
const intl = useIntl()
return <h1>{intl.formatMessage(messages.title)}</h1>
}
Асинхронне завантаження повідомлень
// Повідомлення — окремі чанки для кожної мови
async function loadMessages(locale: string): Promise<Record<string, string>> {
switch (locale) {
case 'ru': return (await import('@/i18n/messages/ru')).ru
case 'en': return (await import('@/i18n/messages/en')).en
case 'de': return (await import('@/i18n/messages/de')).de
default: return (await import('@/i18n/messages/ru')).ru
}
}
// У компоненті
function LocalizedApp({ locale }: { locale: string }) {
const [messages, setMessages] = useState<Record<string, string> | null>(null)
useEffect(() => {
loadMessages(locale).then(setMessages)
}, [locale])
if (!messages) return <PageLoader />
return (
<IntlProvider locale={locale} messages={messages}>
<App />
</IntlProvider>
)
}
Вилучення повідомлень для перекладачів
FormatJS надає CLI для автоматичного збору всіх id з коду:
npx @formatjs/cli-lib extract \
'src/**/*.{ts,tsx}' \
--out-file src/i18n/extracted.json \
--id-interpolation-pattern '[sha512:contenthash:base64:6]'
Передайте отриманий JSON перекладачу або завантажте в Crowdin / Phrase.
Відносний час
import { FormattedRelativeTime } from 'react-intl'
function TimeAgo({ timestamp }: { timestamp: number }) {
const diff = Math.round((timestamp - Date.now()) / 1000) // секунди
return (
<FormattedRelativeTime
value={diff}
unit="second"
updateIntervalInSeconds={30} // автообновлення
/>
)
// "-5 хвилин тому", "вчора", "3 дні тому"
}
Терміни
Установка, налаштування IntlProvider, переклад базового інтерфейсу (100–200 рядків) на 2 мови — 1–2 дні. З асинхронним завантаженням повідомлень, налаштуванням CLI-екстракції та CI-перевіркою повноти перекладів — 3 дні.







