How to Translate a Vue.js App: Complete Guide

Learn how to localize your Vue.js app using vue-i18n. This comprehensive guide covers string translation, pluralization, date formatting, and best practices for Vue.js app translation.

  • date icon

    Tuesday, Nov 18, 2025

How to Translate a Vue.js App: Complete Guide

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:

  1. Install vue-i18n (version 9 for Vue 3, version 8 for Vue 2)
  2. Create translation JSON files for each language
  3. Configure vue-i18n with your messages
  4. Use $t in templates or useI18n in Composition API
  5. Handle pluralization with special keys
  6. Format dates and numbers using built-in formatters
  7. Allow users to switch languages
  8. 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:

View AZbox Plans and Pricing

Blog

Latest Posts

Discover our latest articles and updates.

Why Translations Have Always Been a Problem in Software Development
date icon

Sunday, Dec 21, 2025

Why Translations Have Always Been a Problem in Software Development

For decades, software developers have struggled with translations and localization. What should be a straightforward pro

Read More
Cómo Traducir una App Flutter con Azbox: Guía Completa
date icon

Saturday, Dec 20, 2025

Cómo Traducir una App Flutter con Azbox: Guía Completa

Traducir tu app Flutter es esencial para llegar a una audiencia global. Azbox proporciona un potente SDK de Flutter que

Read More
How to Translate a Flutter App with Azbox: Complete Guide
date icon

Saturday, Dec 20, 2025

How to Translate a Flutter App with Azbox: Complete Guide

Translating your Flutter app is essential for reaching a global audience. Azbox provides a powerful Flutter SDK that sim

Read More
cta-image

Start Global Growth Today

Join hundreds of successful companies already using AZbox to reach customers worldwide. Start with a free trial, no credit card required.

Get Started - It's Free