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
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
Android Localization: Complete Guide to Expanding Your App to New Markets
date icon

Saturday, Dec 13, 2025

Android Localization: Complete Guide to Expanding Your App to New Markets

Android runs on many devices, in many regions. To reach the majority of users, make sure your app handles text, audio fi

Read More
Call to action background

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