Библиотека FormatJS предоставляет набор инструментов для локализации интерфейсов JavaScript-приложений. В экосистеме Vue она чаще всего используется совместно с библиотеками:
vue-intlvue-i18n + FormatJS APIreact-intl-подобными обёртками для Vue@formatjs/intl-*Основная задача FormatJS — унификация работы с:
FormatJS опирается на стандарт Intl, встроенный в
JavaScript.
Для Vue-проекта обычно используются следующие пакеты:
npm install vue-intl @formatjs/intl
Дополнительно могут понадобиться:
npm install @formatjs/intl-pluralrules
npm install @formatjs/intl-relativetimeformat
npm install @formatjs/intl-numberformat
Для Vue 3 чаще применяется собственная интеграция через Composition API.
Типичная структура каталогов:
src/
├── i18n/
│ ├── messages/
│ │ ├── en.json
│ │ ├── ru.json
│ │ └── kz.json
│ ├── index.js
│ └── formatter.js
├── components/
└── App.vue
Файлы переводов:
{
"app.title": "Dashboard",
"user.greeting": "Hello, {name}",
"cart.items": "{count, plural, =0 {No items} one {# item} other {# items}}"
}
Во Vue часто создаётся глобальный объект форматирования.
import { createIntl, createIntlCache } from '@formatjs/intl'
const cache = createIntlCache()
export function createFormatter(locale, messages) {
return createIntl(
{
locale,
messages
},
cache
)
}
import en from './messages/en.json'
import ru from './messages/ru.json'
import { createFormatter } from './formatter'
const messages = {
en,
ru
}
const locale = navigator.language.startsWith('ru')
? 'ru'
: 'en'
export const intl = createFormatter(
locale,
messages[locale]
)
import { createApp } from 'vue'
import App from './App.vue'
import { intl } from './i18n'
const app = createApp(App)
app.provide('intl', intl)
app.mount('#app')
<script setup>
import { inject } from 'vue'
const intl = inject('intl')
const title = intl.formatMessage({
id: 'app.title'
})
</script>
<template>
<h1>{{ title }}</h1>
</template>
Главный API библиотеки — formatMessage.
intl.formatMessage({
id: 'app.title'
})
С параметрами:
intl.formatMessage(
{
id: 'user.greeting'
},
{
name: 'Alex'
}
)
Результат:
Hello, Alex
FormatJS использует ICU-синтаксис.
{
"welcome": "Welcome, {name}"
}
intl.formatMessage(
{ id: 'welcome' },
{ name: 'John' }
)
{
"notifications": "{count, plural, =0 {Нет уведомлений} one {# уведомление} few {# уведомления} many {# уведомлений} other {# уведомления}}"
}
intl.formatMessage(
{ id: 'notifications' },
{ count: 5 }
)
{
"gender.message": "{gender, select, male {Он} female {Она} other {Они}} вошёл в систему"
}
intl.formatMessage(
{ id: 'gender.message' },
{ gender: 'female' }
)
{
"complex": "{count, plural, one {{gender, select, male {Он} female {Она} other {Они}} добавил файл} other {{gender, select, male {Он} female {Она} other {Они}} добавили # файлов}}"
}
intl.formatDate(new Date())
intl.formatDate(new Date(), {
year: 'numeric',
month: 'long',
day: 'numeric'
})
Для русской локали:
12 марта 2026 г.
intl.formatTime(new Date(), {
hour: '2-digit',
minute: '2-digit'
})
intl.formatDateTimeRange(
new Date('2026-01-01'),
new Date('2026-01-15')
)
intl.formatNumber(1000000)
intl.formatNumber(2500, {
style: 'currency',
currency: 'USD'
})
intl.formatNumber(0.75, {
style: 'percent'
})
intl.formatNumber(1500000, {
notation: 'compact'
})
Результат:
1.5M
intl.formatRelativeTime(-1, 'day')
Результат:
yesterday
intl.formatRelativeTime(-5, 'minute')
intl.formatRelativeTime(2, 'week')
intl.formatList([
'Vue',
'React',
'Angular'
])
Результат:
Vue, React, and Angular
intl.formatDisplayName('US', {
type: 'region'
})
import { inject } fr om 'vue'
export function useIntl() {
return inject('intl')
}
<script setup>
import { useIntl } from '@/i18n/useIntl'
const intl = useIntl()
const price = intl.formatNumber(1999, {
style: 'currency',
currency: 'EUR'
})
</script>
import { ref, computed } from 'vue'
import en from './messages/en.json'
import ru from './messages/ru.json'
import { createFormatter } from './formatter'
const locale = ref('ru')
const dictionaries = {
en,
ru
}
export function useI18n() {
const intl = computed(() => {
return createFormatter(
locale.value,
dictionaries[locale.value]
)
})
function setLocale(newLocale) {
locale.value = newLocale
}
return {
locale,
intl,
setLocale
}
}
<script setup>
import { useI18n } from '@/i18n/store'
const { setLocale } = useI18n()
</script>
<template>
<button @click="setLocale('ru')">
RU
</button>
<button @click="setLocale('en')">
EN
</button>
</template>
async function loadMessages(locale) {
const messages = await import(
`./messages/${locale}.json`
)
return messages.default
}
async function setLocale(locale) {
const messages = await loadMessages(locale)
intl.value = createFormatter(
locale,
messages
)
}
{
"auth.login": "Login",
"auth.logout": "Logout",
"dashboard.title": "Dashboard"
}
messages/
├── auth/
│ ├── en.json
│ └── ru.json
├── dashboard/
│ ├── en.json
│ └── ru.json
import authEn from './auth/en.json'
import dashboardEn from './dashboard/en.json'
export default {
...authEn,
...dashboardEn
}
FormatJS поддерживает JSX-подобные конструкции.
{
"article": "Read <link>documentation</link>"
}
intl.formatMessage(
{
id: 'article'
},
{
link: chunks => `<a href="/docs">${chunks}</a>`
}
)
function checkMissingKeys(messages, requiredKeys) {
return requiredKeys.filter(
key => !messages[key]
)
}
FormatJS предоставляет CLI-инструменты.
npm install --save-dev @formatjs/cli
formatjs extract "src/**/*.{js,vue}"
formatjs compile-folder lang compiled-lang
npm install --save-dev babel-plugin-formatjs
module.exports = {
plugins: [
[
'formatjs',
{
idInterpolationPattern:
'[sha512:contenthash:base64:6]'
}
]
]
}
type Messages = {
'app.title': string
'auth.login': string
}
function t(
id: keyof Messages
) {
return intl.formatMessage({ id })
}
При SSR необходимо:
export default defineNuxtPlugin(() => {
const locale = 'ru'
const intl = createFormatter(
locale,
messages[locale]
)
return {
provide: {
intl
}
}
})
Некоторые браузеры не поддерживают весь API Intl.
import '@formatjs/intl-pluralrules/polyfill'
import '@formatjs/intl-relativetimeformat/polyfill'
createIntlCache() уменьшает количество повторных
операций форматирования.
const cache = createIntlCache()
Компиляция ICU-сообщений во время сборки значительно ускоряет runtime.
Загрузка только активного языка уменьшает размер bundle.
const intl = createIntl(
{
locale: 'ru',
messages,
onError(error) {
console.error(error)
}
},
cache
)
function translate(id) {
return (
messages.ru[id]
|| messages.en[id]
|| id
)
}
import { createIntl } from '@formatjs/intl'
test('formats message', () => {
const intl = createIntl({
locale: 'en',
messages: {
hello: 'Hello'
}
})
expect(
intl.formatMessage({
id: 'hello'
})
).toBe('Hello')
})
/ru/dashboard
/en/dashboard
{
path: '/:locale/dashboard',
component: Dashboard
}
const locale = navigator.language
const supported = ['ru', 'en', 'kz']
const locale = supported.includes(
navigator.language
)
? navigator.language
: 'en'
import { defineStore } from 'pinia'
export const useCartStore = defineStore(
'cart',
{
actions: {
getMessage(intl) {
return intl.formatMessage(
{
id: 'cart.title'
}
)
}
}
}
)
{
"validation.required": "Поле обязательно",
"validation.email": "Некорректный email"
}
errors.push(
intl.formatMessage({
id: 'validation.required'
})
)
import messages from './messages/ru.json'
export default defineConfig({
build: {
sourcemap: false
}
})
| Возможность | FormatJS | vue-i18n |
|---|---|---|
| ICU MessageFormat | Да | Частично |
| Нативный Intl API | Да | Да |
| Экосистема React | Сильная | Нет |
| Гибкость | Высокая | Средняя |
| Простота интеграции | Ниже | Выше |
| Низкоуровневый контроль | Да | Ограничен |
FormatJS особенно эффективен в проектах, где необходимы: