Setting up i18n framework (vue-i18n) for Vue application
Vue I18n is the official i18n package for Vue.js. Version 9.x works with Vue 3 Composition API and supports both Options API and <script setup>. Has built-in support for ICU Message Format, lazy loading translations and SSR via Nuxt i18n.
Installation
npm install vue-i18n@9
Plugin setup
// src/i18n/index.ts
import { createI18n } from 'vue-i18n'
import ru from './locales/ru.json'
import en from './locales/en.json'
export type MessageSchema = typeof ru
const i18n = createI18n<[MessageSchema], 'ru' | 'en'>({
legacy: false, // Use Composition API
locale: 'ru',
fallbackLocale: 'en',
messages: { ru, en },
datetimeFormats: {
ru: {
short: { day: 'numeric', month: 'short', year: 'numeric' },
long: { day: 'numeric', month: 'long', year: 'numeric', weekday: 'long' },
},
en: {
short: { month: 'short', day: 'numeric', year: 'numeric' },
long: { month: 'long', day: 'numeric', year: 'numeric', weekday: 'long' },
},
},
numberFormats: {
ru: {
currency: { style: 'currency', currency: 'RUB', maximumFractionDigits: 0 },
decimal: { style: 'decimal', minimumFractionDigits: 2 },
},
en: {
currency: { style: 'currency', currency: 'USD' },
decimal: { style: 'decimal', minimumFractionDigits: 2 },
},
},
})
export default i18n
// src/main.ts
import { createApp } from 'vue'
import i18n from './i18n'
import App from './App.vue'
createApp(App).use(i18n).mount('#app')
Translation files
// src/i18n/locales/ru.json
{
"nav": {
"catalog": "Каталог",
"cart": "Корзина | Корзина ({n} товар) | Корзина ({n} товаров)",
"account": "Личный кабинет"
},
"product": {
"addToCart": "В корзину",
"inStock": "В наличии",
"outOfStock": "Нет в наличии",
"price": "Цена"
},
"form": {
"required": "Поле обязательно",
"email": "Введите корректный email",
"submit": "Отправить"
}
}
Vue I18n supports simplified pluralization syntax via |:
-
'no items | {n} item | {n} items'— 3 forms for English
Or full ICU via messageResolver mode.
Using in components
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
const { t, n, d, locale, availableLocales } = useI18n()
const props = defineProps<{
product: {
title: string
price: number
inStock: boolean
createdAt: Date
}
cartCount: number
}>()
</script>
<template>
<article>
<h2>{{ product.title }}</h2>
<!-- Format number as currency -->
<p>{{ n(product.price, 'currency') }}</p>
<!-- Availability -->
<span>{{ product.inStock ? t('product.inStock') : t('product.outOfStock') }}</span>
<!-- Date -->
<time>{{ d(product.createdAt, 'short') }}</time>
<!-- Pluralization in nav -->
<span>{{ t('nav.cart', cartCount, { n: cartCount }) }}</span>
<button>{{ t('product.addToCart') }}</button>
</article>
</template>
Lazy loading translations
// src/i18n/index.ts — only base messages on startup
const i18n = createI18n({
legacy: false,
locale: 'ru',
fallbackLocale: 'ru',
messages: { ru }, // only ru on initialization
})
// Loading additional locales
const loadedLocales = new Set(['ru'])
export async function loadLocale(locale: string): Promise<void> {
if (loadedLocales.has(locale)) return
const messages = await import(`./locales/${locale}.json`)
i18n.global.setLocaleMessage(locale, messages.default)
loadedLocales.add(locale)
}
// src/router/index.ts
router.beforeEach(async (to) => {
const locale = to.params.locale as string || 'ru'
await loadLocale(locale)
i18n.global.locale.value = locale as 'ru' | 'en' | 'de'
})
Nuxt I18n (SSR)
npm install @nuxtjs/i18n
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxtjs/i18n'],
i18n: {
locales: [
{ code: 'ru', language: 'ru-RU', file: 'ru.json', name: 'Русский' },
{ code: 'en', language: 'en-US', file: 'en.json', name: 'English' },
{ code: 'de', language: 'de-DE', file: 'de.json', name: 'Deutsch' },
],
defaultLocale: 'ru',
lazy: true,
langDir: 'locales/',
strategy: 'prefix_except_default',
detectBrowserLanguage: {
useCookie: true,
cookieKey: 'locale',
redirectOn: 'root',
},
},
})
<!-- pages/catalog/[slug].vue -->
<script setup lang="ts">
const { t, locale } = useI18n()
const localePath = useLocalePath()
const switchLocalePath = useSwitchLocalePath()
</script>
<template>
<nav>
<!-- Localized link -->
<NuxtLink :to="localePath('/catalog')">{{ t('nav.catalog') }}</NuxtLink>
<!-- Language switcher -->
<NuxtLink v-for="lang in ['ru', 'en', 'de']" :key="lang"
:to="switchLocalePath(lang)">
{{ lang.toUpperCase() }}
</NuxtLink>
</nav>
</template>
TypeScript: typed keys
// Auto-completion for translation keys
import { useI18n } from 'vue-i18n'
import type { MessageSchema } from '@/i18n'
const { t } = useI18n<{ message: MessageSchema }>()
t('nav.catalog') // OK
t('nav.nonexistent') // TypeScript Error
Timeframe
Basic vue-i18n setup with 2 languages — 1 day. Nuxt i18n with lazy loading, localized routes and SEO tags — 2–3 days.







