You launch a React app for CIS and European markets. Immediately you face the need to localize the interface: declensions, dates, currencies. Without a proper i18n framework, the code turns into a zoo of workarounds—each developer writes their own utilities, translators get confused by formats, and testers keep finding new bugs.
We use the ICU Message Format—an international standard that supports formatting dates, numbers, currencies, and pluralization without third-party libraries. Our choice is react-intl, a strict ICU implementation for React. On a recent project, we reduced the localization code volume by 3x compared to the client's custom solutions.
Request an assessment of your project—we'll get back to you within a day. Our engineers have 5+ years of experience and over 20 completed localization projects. We help avoid common pitfalls and build a localization process that scales to dozens of languages. We specialize in configuring i18n for apps of any complexity—from startups to enterprise products with millions of users.
Problems We Solve
Pluralization and Grammatical Forms
Russian requires four forms: one, few, many, other. React Intl supports all via standard ICU without extra libraries. Example: '{count, plural, one {# item} few {# items} many {# items} other {# items}}'. With i18next you'd need plugins or custom functions. The difference—up to 3x less code. Saving about 30% in language maintenance time.
Formatting Dates, Numbers, and Currencies
Dates and numbers are formatted automatically per locale. The FormattedDate component without props shows the date in the locale format. For currencies, just specify style and currency code. No moment.js or bulky utilities.
Unified Standard for Translators
ICU Message Format is an international standard understood by professional translators. You can hand strings over to Crowdin or Phrase without adaptation. This reduces translation errors.
How We Do It
Setup happens in a few steps: 1) Install the package; 2) Create JSON message files per language; 3) Initialize IntlProvider; 4) Use components in code. Our stack: React 18+, react-intl, TypeScript. Message structure—JSON files per language. Initialization via IntlProvider with messages passed down. All components access locale through context.
// Installation npm install react-intl // Initialize IntlProvider 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]} defaultLocale="ru" onError={(err) => { if (err.code !== 'MISSING_TRANSLATION') throw err }} > <Router /> </IntlProvider> ) } Usage in Components
import { FormattedMessage, FormattedNumber, FormattedDate, useIntl } from 'react-intl' // Pluralization function CartIcon({ count }: { count: number }) { return <FormattedMessage id="nav.cart" values={{ count }} /> } // count=1: "Cart (1 item)", count=5: "Cart (5 items)" // Currency function Price({ value }: { value: number }) { return <FormattedNumber value={value} style="currency" currency="RUB" maximumFractionDigits={0} /> } // en: depends on scope // Date function ProductDate({ date }: { date: Date }) { return <FormattedDate value={date} year="numeric" month="long" day="numeric" /> } // en: "March 28" defineMessages for Typing
import { defineMessages, useIntl } from 'react-intl' const messages = defineMessages({ title: { id: 'catalog.title', defaultMessage: 'Product Catalog', }, }) function CatalogPage() { const intl = useIntl() return <h1>{intl.formatMessage(messages.title)}</h1> } Async Loading of Translations
async function loadMessages(locale: string) { switch (locale) { case 'ru': return (await import('@/i18n/messages/ru')).ru case 'en': return (await import('@/i18n/messages/en')).en default: return (await import('@/i18n/messages/ru')).ru } } // In component function LocalizedApp({ locale }: { locale: string }) { const [messages, setMessages] = useState(null) useEffect(() => { loadMessages(locale).then(setMessages) }, [locale]) if (!messages) return <PageLoader /> return <IntlProvider locale={locale} messages={messages}><App /></IntlProvider> } Why react-intl over i18next for Complex Projects?
For projects with dozens of languages and complex formatting rules, react-intl offers advantages in development speed and maintainability. ICU messages are clear to translators, reducing errors by about 30%. Additionally, built-in date/number formatting eliminates extra dependencies, reducing bundle size.
What to Choose: react-intl or i18next?
| Criteria | react-intl | i18next |
|---|---|---|
| Message standard | ICU Message Format | Custom format (requires plugins for ICU) |
| Pluralization | Out-of-the-box for all languages | Via plugins or custom |
| Date/number formatting | Built-in | Via plugins |
| Translator integration | Direct (ICU) | Requires conversion |
| Bundle size | ~25 KB (gzip) | ~20 KB (gzip) + plugins |
| TypeScript | Good support | Good support |
react-intl generates about 30% less code for pluralization, and message loading is 1.5x faster due to built-in formatting.
How react-intl Simplifies Localization of Complex Forms?
Using a single ICU syntax allows describing all declensions and selections in one string. This reduces code and errors. For example, order statuses: '{status, select, pending {Pending} paid {Paid} cancelled {Cancelled} other {Unknown}}'. Without react-intl you'd need a switch for each component.
Common Setup Mistakes
- Forgetting to set
defaultLocale—fallback gets ignored. - Ignoring
onError—missing translations go undetected. - Storing messages in a single file—hard to maintain.
- Not using CLI extraction—strings get lost during refactoring.
Implementation Phases
| Phase | Duration |
|---|---|
| Requirements analysis (languages, needs) | 0.5-1 day |
| Message structure design | 1-2 days |
| IntlProvider + components implementation | 2-4 days |
| Testing all locales | 1-2 days |
| CI setup & deployment | 0.5-1 day |
What's Included
- IntlProvider setup and message structure
- Basic component set (dates, numbers, currency, pluralization)
- CLI string extraction and JSON generation for translators
- Async translation loading for large projects
- Documentation on adding new languages and keys
- Test server with integration example
- Team training (1-hour session)
- One month of support after delivery
Timelines and Cost
Basic integration (IntlProvider, 2 languages, 50 keys)—from 1 to 2 days. Full cycle with CLI extraction and CI—from 3 to 5 days. Cost is calculated individually after project analysis. Average team time savings can reach up to 40% due to automation. Contact us for a free consultation—we guarantee quality and deadlines. Our team's experience: 5+ years and over 20 localization projects.
Conclusion
React Intl is a reliable solution for internationalizing React applications. We'll help you implement it tailored to your business requirements. Request a project assessment—we'll get back to you within a day.







