Translating your Vue.js app is essential for reaching a global audience. vue-i18n is the official internationalization plugin for Vue.js, providing a powerful and flexible solution for managing translations. This guide will walk you through the process of translating a Vue.js app using vue-i18n.
Understanding Vue.js Localization with vue-i18n
vue-i18n is the standard internationalization library for Vue.js. It provides Vue components, directives, and a composition API for handling translations, pluralization, and formatting dates, numbers, and currencies.
Step 1: Install Dependencies
For Vue 3:
npm install vue-i18n@9
For Vue 2:
npm install vue-i18n@8
Or with yarn:
yarn add vue-i18n@9 # Vue 3
yarn add vue-i18n@8 # Vue 2
Step 2: Create Translation Files
Create a locales directory in your src folder and add translation files for each language:
src/locales/en.json (English - default):
{
"app": {
"welcome": "Welcome to our app!",
"title": "My Vue.js App"
},
"button": {
"submit": "Submit",
"cancel": "Cancel",
"delete": "Delete"
},
"error": {
"network": "Network error. Please try again.",
"notFound": "Page not found"
},
"items": {
"count": "{count} items",
"count_zero": "No items",
"count_one": "{count} item",
"count_other": "{count} items"
},
"user": {
"greeting": "Hello, {name}! You have {count} messages.",
"greeting_zero": "Hello, {name}! You have no messages.",
"greeting_one": "Hello, {name}! You have {count} message.",
"greeting_other": "Hello, {name}! You have {count} messages."
}
}
src/locales/es.json (Spanish):
{
"app": {
"welcome": "¡Bienvenido a nuestra aplicación!",
"title": "Mi Aplicación Vue.js"
},
"button": {
"submit": "Enviar",
"cancel": "Cancelar",
"delete": "Eliminar"
},
"error": {
"network": "Error de red. Por favor, inténtalo de nuevo.",
"notFound": "Página no encontrada"
},
"items": {
"count": "{count} elementos",
"count_zero": "No hay elementos",
"count_one": "{count} elemento",
"count_other": "{count} elementos"
},
"user": {
"greeting": "¡Hola, {name}! Tienes {count} mensajes.",
"greeting_zero": "¡Hola, {name}! No tienes mensajes.",
"greeting_one": "¡Hola, {name}! Tienes {count} mensaje.",
"greeting_other": "¡Hola, {name}! Tienes {count} mensajes."
}
}
src/locales/fr.json (French):
{
"app": {
"welcome": "Bienvenue dans notre application!",
"title": "Mon Application Vue.js"
},
"button": {
"submit": "Soumettre",
"cancel": "Annuler",
"delete": "Supprimer"
},
"error": {
"network": "Erreur réseau. Veuillez réessayer.",
"notFound": "Page non trouvée"
},
"items": {
"count": "{count} éléments",
"count_zero": "Aucun élément",
"count_one": "{count} élément",
"count_other": "{count} éléments"
},
"user": {
"greeting": "Bonjour, {name}! Vous avez {count} messages.",
"greeting_zero": "Bonjour, {name}! Vous n'avez aucun message.",
"greeting_one": "Bonjour, {name}! Vous avez {count} message.",
"greeting_other": "Bonjour, {name}! Vous avez {count} messages."
}
}
Step 3: Configure vue-i18n (Vue 3)
Create an i18n configuration file:
src/i18n.js:
import { createI18n } from 'vue-i18n';
import en from './locales/en.json';
import es from './locales/es.json';
import fr from './locales/fr.json';
const messages = {
en,
es,
fr
};
const i18n = createI18n({
locale: 'en', // Default locale
fallbackLocale: 'en', // Fallback locale
messages,
legacy: false, // Use Composition API mode
globalInjection: true // Enable global $t
});
export default i18n;
Or with TypeScript (src/i18n.ts):
import { createI18n } from 'vue-i18n';
import en from './locales/en.json';
import es from './locales/es.json';
import fr from './locales/fr.json';
const messages = {
en,
es,
fr
} as const;
const i18n = createI18n({
locale: 'en',
fallbackLocale: 'en',
messages,
legacy: false,
globalInjection: true
});
export default i18n;
Step 4: Initialize i18n in Your App (Vue 3)
src/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');
Or with TypeScript (src/main.ts):
import { createApp } from 'vue';
import App from './App.vue';
import i18n from './i18n';
const app = createApp(App);
app.use(i18n);
app.mount('#app');
Step 5: Configure vue-i18n (Vue 2)
For Vue 2, the setup is slightly different:
src/i18n.js:
import Vue from 'vue';
import VueI18n from 'vue-i18n';
import en from './locales/en.json';
import es from './locales/es.json';
import fr from './locales/fr.json';
Vue.use(VueI18n);
const messages = {
en,
es,
fr
};
const i18n = new VueI18n({
locale: 'en',
fallbackLocale: 'en',
messages
});
export default i18n;
src/main.js (Vue 2):
import Vue from 'vue';
import App from './App.vue';
import i18n from './i18n';
new Vue({
i18n,
render: h => h(App)
}).$mount('#app');
Step 6: Use Translations in Templates
Use the $t function in templates:
Before:
<template>
<h1>Welcome to our app!</h1>
</template>
After:
<template>
<h1>{{ $t('app.welcome') }}</h1>
</template>
Step 7: Use Translations in Script (Composition API - Vue 3)
Use the useI18n composable:
<template>
<h1>{{ t('app.welcome') }}</h1>
</template>
<script setup>
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
</script>
Step 8: Use Translations in Script (Options API)
<template>
<h1>{{ welcomeMessage }}</h1>
</template>
<script>
export default {
computed: {
welcomeMessage() {
return this.$t('app.welcome');
}
}
};
</script>
Step 9: String Interpolation with Variables
Pass variables to translations:
translation.json:
{
"user": {
"greeting": "Hello, {name}!"
}
}
Component:
<template>
<p>{{ $t('user.greeting', { name: userName }) }}</p>
</template>
<script setup>
import { ref } from 'vue';
const userName = ref('John');
</script>
Or with Composition API:
<template>
<p>{{ t('user.greeting', { name: userName }) }}</p>
</template>
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const userName = ref('John');
</script>
Step 10: Pluralization
vue-i18n supports pluralization using special keys:
translation.json:
{
"items": {
"count": "{count} items",
"count_zero": "No items",
"count_one": "{count} item",
"count_other": "{count} items"
}
}
Component:
<template>
<p>{{ $t('items.count', count, { count }) }}</p>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(5);
</script>
Or with Composition API:
<template>
<p>{{ t('items.count', count, { count }) }}</p>
</template>
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const count = ref(5);
</script>
Step 11: Format Dates
Use the $d function or d method:
Template:
<template>
<p>{{ $d(date, 'long') }}</p>
</template>
<script setup>
import { ref } from 'vue';
const date = ref(new Date());
</script>
Composition API:
<template>
<p>{{ d(date, 'long') }}</p>
</template>
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
const { d } = useI18n();
const date = ref(new Date());
</script>
Configure date formats in i18n:
const i18n = createI18n({
locale: 'en',
messages,
datetimeFormats: {
en: {
short: {
year: 'numeric',
month: 'short',
day: 'numeric'
},
long: {
year: 'numeric',
month: 'long',
day: 'numeric',
weekday: 'long'
}
},
es: {
short: {
year: 'numeric',
month: 'short',
day: 'numeric'
},
long: {
year: 'numeric',
month: 'long',
day: 'numeric',
weekday: 'long'
}
}
}
});
Step 12: Format Numbers
Use the $n function or n method:
Template:
<template>
<p>Number: {{ $n(number) }}</p>
<p>Currency: {{ $n(amount, 'currency') }}</p>
</template>
<script setup>
import { ref } from 'vue';
const number = ref(1234.56);
const amount = ref(1234.56);
</script>
Composition API:
<template>
<p>Number: {{ n(number) }}</p>
<p>Currency: {{ n(amount, 'currency') }}</p>
</template>
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
const { n } = useI18n();
const number = ref(1234.56);
const amount = ref(1234.56);
</script>
Configure number formats in i18n:
const i18n = createI18n({
locale: 'en',
messages,
numberFormats: {
en: {
currency: {
style: 'currency',
currency: 'USD'
}
},
es: {
currency: {
style: 'currency',
currency: 'EUR'
}
}
}
});
Step 13: Change Language Programmatically
Allow users to switch languages:
Composition API:
<template>
<div>
<button @click="changeLanguage('en')">English</button>
<button @click="changeLanguage('es')">Español</button>
<button @click="changeLanguage('fr')">Français</button>
</div>
</template>
<script setup>
import { useI18n } from 'vue-i18n';
const { locale } = useI18n();
const changeLanguage = (lang) => {
locale.value = lang;
localStorage.setItem('language', lang);
};
</script>
Options API:
<template>
<div>
<button @click="changeLanguage('en')">English</button>
<button @click="changeLanguage('es')">Español</button>
<button @click="changeLanguage('fr')">Français</button>
</div>
</template>
<script>
export default {
methods: {
changeLanguage(lang) {
this.$i18n.locale = lang;
localStorage.setItem('language', lang);
}
}
};
</script>
Step 14: Language Detection and Persistence
Load saved language preference on app start:
src/i18n.js:
import { createI18n } from 'vue-i18n';
import en from './locales/en.json';
import es from './locales/es.json';
import fr from './locales/fr.json';
const messages = {
en,
es,
fr
};
// Get saved language or detect browser language
const getInitialLocale = () => {
const saved = localStorage.getItem('language');
if (saved && messages[saved]) return saved;
const browserLang = navigator.language.split('-')[0];
return messages[browserLang] ? browserLang : 'en';
};
const i18n = createI18n({
locale: getInitialLocale(),
fallbackLocale: 'en',
messages,
legacy: false,
globalInjection: true
});
export default i18n;
Step 15: Handle Right-to-Left (RTL) Languages
For RTL languages like Arabic and Hebrew:
<template>
<div :dir="textDirection" :lang="currentLocale">
<!-- Your app content -->
</div>
</template>
<script setup>
import { computed, watch } from 'vue';
import { useI18n } from 'vue-i18n';
const { locale } = useI18n();
const currentLocale = computed(() => locale.value);
const isRTL = computed(() => ['ar', 'he', 'fa'].includes(locale.value));
const textDirection = computed(() => isRTL.value ? 'rtl' : 'ltr');
watch(locale, (newLocale) => {
document.documentElement.dir = isRTL.value ? 'rtl' : 'ltr';
document.documentElement.lang = newLocale;
}, { immediate: true });
</script>
Best Practices
1. Use Descriptive Translation Keys
Bad:
{
"msg1": "Submit"
}
Good:
{
"button": {
"submit": "Submit"
}
}
2. Organize by Feature
{
"auth": {
"login": "Login",
"logout": "Logout"
},
"dashboard": {
"title": "Dashboard"
}
}
3. Use Namespaces
For large apps, split translations into namespaces:
i18n.js:
import commonEN from './locales/en/common.json';
import authEN from './locales/en/auth.json';
const messages = {
en: {
common: commonEN,
auth: authEN
}
};
Usage:
<template>
<p>{{ $t('auth.login') }}</p>
</template>
4. Provide Fallbacks
const i18n = createI18n({
fallbackLocale: 'en',
missingWarn: false, // Disable warnings in production
fallbackWarn: false
});
5. Use Rich Text Formatting
<template>
<p v-html="$t('welcome', { link: '<a href="/about">here</a>' })"></p>
</template>
Note: Be careful with v-html for security. Sanitize user input.
6. Test String Lengths
Some languages are longer than others. Design your UI to accommodate:
- German and Finnish: 30-50% longer than English
- Asian languages: May need more vertical space
Common Pitfalls
1. Forgetting to Install vue-i18n
Always install the correct version:
npm install vue-i18n@9 # Vue 3
npm install vue-i18n@8 # Vue 2
2. Not Using Composition API Mode (Vue 3)
For Vue 3, use legacy: false:
const i18n = createI18n({
legacy: false, // Important for Vue 3
// ...
});
3. Hardcoding Format Strings
Bad:
<p>${{ amount.toFixed(2) }}</p>
Good:
<p>{{ $n(amount, 'currency') }}</p>
4. Not Handling Missing Translations
Provide fallbacks and handle missing keys gracefully.
Advanced: Lazy Loading Translations
Load translations on demand:
const loadLocaleMessages = async (locale) => {
const messages = await import(`./locales/${locale}.json`);
i18n.global.setLocaleMessage(locale, messages.default);
return messages.default;
};
// Usage
await loadLocaleMessages('es');
i18n.global.locale.value = 'es';
Advanced: TypeScript Support
For better TypeScript support:
src/types/vue-i18n.d.ts:
import { DefineLocaleMessage } from 'vue-i18n';
declare module 'vue-i18n' {
export interface DefineLocaleMessage {
app: {
welcome: string;
title: string;
};
button: {
submit: string;
cancel: string;
delete: string;
};
// ... other message types
}
}
Advanced: Using with Vue Router
Integrate with Vue Router for locale-based routing:
import { createRouter, createWebHistory } from 'vue-router';
import { useI18n } from 'vue-i18n';
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/:locale?',
component: Layout,
children: [
{
path: '',
name: 'home',
component: Home
}
]
}
]
});
router.beforeEach((to, from, next) => {
const locale = to.params.locale || 'en';
const { locale: i18nLocale } = useI18n();
if (i18nLocale.value !== locale) {
i18nLocale.value = locale;
}
next();
});
Advanced: Using with Pinia (State Management)
Store locale in Pinia:
import { defineStore } from 'pinia';
import { useI18n } from 'vue-i18n';
export const useLocaleStore = defineStore('locale', {
state: () => ({
currentLocale: 'en'
}),
actions: {
setLocale(locale) {
this.currentLocale = locale;
const { locale: i18nLocale } = useI18n();
i18nLocale.value = locale;
localStorage.setItem('language', locale);
}
}
});
Using with Nuxt.js
For Nuxt.js applications, vue-i18n is built-in:
nuxt.config.js:
export default {
modules: ['@nuxtjs/i18n'],
i18n: {
locales: ['en', 'es', 'fr'],
defaultLocale: 'en',
vueI18n: {
fallbackLocale: 'en',
messages: {
en: require('./locales/en.json'),
es: require('./locales/es.json'),
fr: require('./locales/fr.json')
}
}
}
};
Conclusion
Localizing your Vue.js app with vue-i18n is straightforward when you follow these steps:
- Install
vue-i18n(version 9 for Vue 3, version 8 for Vue 2) - Create translation JSON files for each language
- Configure vue-i18n with your messages
- Use
$tin templates oruseI18nin Composition API - Handle pluralization with special keys
- Format dates and numbers using built-in formatters
- Allow users to switch languages
- Test thoroughly in all supported languages
By following these practices, you’ll create an app that provides a native experience for users worldwide, significantly expanding your potential user base.
Streamline Your Vue.js Localization Workflow
Managing translations for multiple languages can become complex as your app grows. Consider using a translation management platform to:
- Collaborate with translators
- Keep translations in sync with your codebase
- Automate the translation workflow
- Maintain consistency across all languages
- Generate translation files automatically
- Integrate with your CI/CD pipeline
Ready to take your Vue.js app global? Explore AZbox’s localization platform and streamline your translation workflow: