Vue i18n is the standard solution for internationalization (i18n) in Vue.js applications. In this guide, I'll show you how to set up Vue i18n in Vue 3, Nuxt 3, and with various build tools.
What is Vue i18n?#
Vue i18n is a plugin developed by Intlify that enables you to make your Vue application multilingual. It supports:
- Vue 2 and Vue 3
- Composition API and Options API
- Pluralization
- Date and number formatting
- Lazy loading of translations
Vue 3 i18n Installation#
Install with npm/yarn#
npm install vue-i18n@9
# or
yarn add vue-i18n@9
Basic Setup in Vue 3#
// src/i18n/index.js
import { createI18n } from 'vue-i18n'
const messages = {
en: {
welcome: 'Welcome',
greeting: 'Hello, {name}!'
},
de: {
welcome: 'Willkommen',
greeting: 'Hallo, {name}!'
}
}
const i18n = createI18n({
locale: 'en',
fallbackLocale: 'en',
messages
})
export default i18n
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import i18n from './i18n'
const app = createApp(App)
app.use(i18n)
app.mount('#app')
Using Vue i18n in Templates#
The $t Function#
<template>
<h1>{{ $t('welcome') }}</h1>
<p>{{ $t('greeting', { name: 'Max' }) }}</p>
</template>
With Composition API (useI18n)#
<script setup>
import { useI18n } from 'vue-i18n'
const { t, locale } = useI18n()
// Change language
function changeLanguage(lang) {
locale.value = lang
}
</script>
<template>
<h1>{{ t('welcome') }}</h1>
<button @click="changeLanguage('en')">English</button>
<button @click="changeLanguage('de')">Deutsch</button>
</template>
Vue i18n Change Language#
There are several approaches to dynamically change the language:
Global#
import i18n from '@/i18n'
// Change language
i18n.global.locale.value = 'en'
In a Component#
<script setup>
import { useI18n } from 'vue-i18n'
const { locale, availableLocales } = useI18n()
</script>
<template>
<select v-model="locale">
<option v-for="lang in availableLocales" :key="lang" :value="lang">
{{ lang }}
</option>
</select>
</template>
Nuxt i18n / Nuxt 3 i18n#
For Nuxt projects, there's the official @nuxtjs/i18n module.
Installation#
npm install @nuxtjs/i18n
Nuxt 3 Configuration#
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxtjs/i18n'],
i18n: {
locales: [
{ code: 'en', iso: 'en-US', file: 'en.json' },
{ code: 'de', iso: 'de-DE', file: 'de.json' }
],
defaultLocale: 'en',
lazy: true,
langDir: 'locales/',
strategy: 'prefix_except_default'
}
})
Nuxt i18n with Vue Router#
// locales/en.json
{
"home": "Home",
"about": "About Us"
}
<!-- pages/index.vue -->
<script setup>
const { t } = useI18n()
const localePath = useLocalePath()
</script>
<template>
<nav>
<NuxtLink :to="localePath('/')">{{ t('home') }}</NuxtLink>
<NuxtLink :to="localePath('/about')">{{ t('about') }}</NuxtLink>
</nav>
</template>
Vue i18n with Vite#
Vite Plugin Vue i18n#
For better performance and build optimization:
npm install @intlify/unplugin-vue-i18n
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'
import { resolve } from 'path'
export default defineConfig({
plugins: [
vue(),
VueI18nPlugin({
include: resolve(__dirname, './src/locales/**')
})
]
})
Translate your Vue i18n files
@:linked keys and {named} placeholders survive the round trip.
Structuring Translation Files#
Separate JSON Files per Language#
src/
locales/
en.json
de.json
fr.json
// locales/en.json
{
"nav": {
"home": "Home",
"products": "Products",
"contact": "Contact"
},
"auth": {
"login": "Log In",
"logout": "Log Out",
"register": "Register"
}
}
Lazy Loading (for Large Projects)#
// i18n/index.js
import { createI18n } from 'vue-i18n'
const i18n = createI18n({
locale: 'en',
fallbackLocale: 'en',
messages: {}
})
export async function loadLocaleMessages(locale) {
const messages = await import(`./locales/${'${locale}'}.json`)
i18n.global.setLocaleMessage(locale, messages.default)
return messages
}
export default i18n
Vue i18n Pluralization#
{
"items": "no items | one item | {count} items"
}
<template>
<p>{{ $t('items', 0) }}</p> <!-- no items -->
<p>{{ $t('items', 1) }}</p> <!-- one item -->
<p>{{ $t('items', 5) }}</p> <!-- 5 items -->
</template>
Vue i18n with Vue Router#
For multilingual URLs:
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import i18n from '@/i18n'
const routes = [
{
path: '/:locale',
children: [
{ path: '', name: 'home', component: () => import('@/views/Home.vue') },
{ path: 'about', name: 'about', component: () => import('@/views/About.vue') }
]
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
router.beforeEach((to, from, next) => {
const locale = to.params.locale
if (locale && i18n.global.availableLocales.includes(locale)) {
i18n.global.locale.value = locale
}
next()
})
export default router
Vue i18next as an Alternative#
If you already use i18next in other projects:
npm install i18next i18next-vue
import i18next from 'i18next'
import I18NextVue from 'i18next-vue'
i18next.init({
lng: 'en',
resources: {
en: { translation: { welcome: 'Welcome' } }
}
})
app.use(I18NextVue, { i18next })
Best Practices#
1. Organize Translation Keys#
{
"common": {
"save": "Save",
"cancel": "Cancel"
},
"pages": {
"home": {
"title": "Home"
}
}
}
2. TypeScript Support#
// i18n.d.ts
import { DefineLocaleMessage } from 'vue-i18n'
import en from './locales/en.json'
type MessageSchema = typeof en
declare module 'vue-i18n' {
export interface DefineLocaleMessage extends MessageSchema {}
}
3. SEO for Multilingual Pages#
<script setup>
import { useI18n } from 'vue-i18n'
import { useHead } from '@vueuse/head'
const { t, locale } = useI18n()
useHead({
htmlAttrs: { lang: locale.value },
title: t('pages.home.title'),
link: [
{ rel: 'alternate', hreflang: 'de', href: 'https://example.com/de/' },
{ rel: 'alternate', hreflang: 'en', href: 'https://example.com/en/' }
]
})
</script>
Frequently Asked Questions (FAQ)#
How do I change the language in Vue i18n?#
Use locale.value = 'en' in the Composition API or i18n.global.locale.value = 'en' globally. Both methods reactively update the language throughout the entire application.
What's the difference between Vue i18n and i18next?#
Vue i18n is specifically designed for Vue and offers deep integration with Vue features like Composition API and Reactivity. i18next is framework-agnostic and can be used in React, Angular, or Vanilla JS. For pure Vue projects, Vue i18n is the better choice.
Does Vue i18n support TypeScript?#
Yes, Vue i18n offers full TypeScript support. With the DefineLocaleMessage interface, you can define typed translation keys that enable auto-completion and type checking.
How do I load translations dynamically (Lazy Loading)?#
Use dynamic imports: const messages = await import('./locales/' + locale + '.json') and then i18n.global.setLocaleMessage(locale, messages.default). This significantly reduces the initial bundle size.
Conclusion#
Vue i18n is the best choice for internationalization in Vue.js projects. With Vue 3 and the Composition API, setup has become even easier. For Nuxt projects, @nuxtjs/i18n offers seamless integration with additional features like automatic routing.
When translating your Vue i18n JSON files, it's important that placeholders like {name} and ICU format are preserved. shipglobal.dev automatically handles Vue i18n file formats and preserves all variable syntax.
Related Articles#
- Vue i18n in JavaScript - Translate Outside Templates
- React i18next Placeholder Guide
- Android i18n JSON Guide