Skip to main content

Vue.js I18n Internationalization

Introduction

In today's interconnected world, building applications that cater to users from different regions and languages is becoming increasingly important. Internationalization (i18n) is the process of designing and preparing your application to support various languages and regions without engineering changes. Localization (l10n) is the process of adapting your application to a specific locale or language.

Vue I18n is the internationalization plugin for Vue.js that makes implementing multilingual support in your Vue applications straightforward and efficient. This guide will walk you through setting up and using Vue I18n in your projects.

Getting Started with Vue I18n

Installation

First, let's install Vue I18n in your Vue project:

bash
# Using npm
npm install vue-i18n@next

# Using yarn
yarn add vue-i18n@next

Note: We're using the @next tag to ensure compatibility with Vue 3. If you're using Vue 2, you should install vue-i18n@8.

Basic Setup

To set up Vue I18n in your application, you need to create an instance of createI18n and install it as a plugin in your Vue application:

javascript
// src/i18n/index.js
import { createI18n } from 'vue-i18n'

// Import your language files
import enMessages from './locales/en.json'
import esMessages from './locales/es.json'
import frMessages from './locales/fr.json'

const i18n = createI18n({
// Use the locale from browser if available, fallback to 'en'
locale: navigator.language.split('-')[0] || 'en',
fallbackLocale: 'en',
messages: {
en: enMessages,
es: esMessages,
fr: frMessages
}
})

export default i18n

Then, in your main.js file:

javascript
// 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')

Creating Translation Files

Create separate JSON files for each language you want to support:

javascript
// src/i18n/locales/en.json
{
"welcome": "Welcome to our application!",
"greeting": "Hello, {name}!",
"nav": {
"home": "Home",
"about": "About",
"contact": "Contact"
},
"errors": {
"notFound": "Page not found",
"serverError": "Server error occurred"
}
}
javascript
// src/i18n/locales/es.json
{
"welcome": "¡Bienvenido a nuestra aplicación!",
"greeting": "¡Hola, {name}!",
"nav": {
"home": "Inicio",
"about": "Acerca de",
"contact": "Contacto"
},
"errors": {
"notFound": "Página no encontrada",
"serverError": "Se produjo un error en el servidor"
}
}

Using Vue I18n in Templates

Basic Translation

Once you've set up Vue I18n, you can use the $t method in your templates to translate text:

html
<template>
<div>
<h1>{{ $t('welcome') }}</h1>
<p>{{ $t('greeting', { name: 'John' }) }}</p>
</div>
</template>

Output (if locale is 'en'):

Welcome to our application!
Hello, John!

Output (if locale is 'es'):

¡Bienvenido a nuestra aplicación!
¡Hola, John!

Using in JavaScript

You can also use translations in your JavaScript code:

html
<script>
export default {
methods: {
showGreeting(name) {
alert(this.$t('greeting', { name }))
}
},
computed: {
welcomeMessage() {
return this.$t('welcome')
}
}
}
</script>

Using the Composition API

If you're using Vue 3's Composition API, you'll need to use the useI18n composable:

html
<template>
<div>
<h1>{{ t('welcome') }}</h1>
<p>{{ t('greeting', { name: 'John' }) }}</p>
</div>
</template>

<script setup>
import { useI18n } from 'vue-i18n'

const { t, locale, availableLocales } = useI18n()

// You can change the locale programmatically
function changeLanguage(lang) {
locale.value = lang
}
</script>

Advanced Features

Plural Translation

Vue I18n supports pluralization using the $tc function:

javascript
// en.json
{
"cart": "no items | one item | {count} items"
}
html
<template>
<div>
<p>{{ $tc('cart', 0) }}</p>
<p>{{ $tc('cart', 1) }}</p>
<p>{{ $tc('cart', 10, { count: 10 }) }}</p>
</div>
</template>

Output:

no items
one item
10 items

Date and Number Formatting

Vue I18n also provides date and number formatting capabilities:

html
<template>
<div>
<p>{{ $d(new Date(), 'long') }}</p>
<p>{{ $n(1234.56, 'currency') }}</p>
</div>
</template>

<script>
export default {
created() {
// Define date and number formats
this.$i18n.setDateTimeFormat('en', {
short: {
year: 'numeric', month: 'short', day: 'numeric'
},
long: {
year: 'numeric', month: 'long', day: 'numeric',
weekday: 'long', hour: 'numeric', minute: 'numeric'
}
})

this.$i18n.setNumberFormat('en', {
currency: {
style: 'currency', currency: 'USD'
}
})
}
}
</script>

Practical Example: Language Switcher

Let's create a language switcher component that allows users to change the application language:

html
<template>
<div class="language-switcher">
<select v-model="currentLocale">
<option v-for="locale in availableLocales" :key="locale" :value="locale">
{{ languageNames[locale] }}
</option>
</select>
</div>
</template>

<script>
export default {
data() {
return {
languageNames: {
en: 'English',
es: 'Español',
fr: 'Français'
}
}
},
computed: {
currentLocale: {
get() {
return this.$i18n.locale
},
set(newLocale) {
this.$i18n.locale = newLocale
// Optionally save to localStorage
localStorage.setItem('userLocale', newLocale)
}
},
availableLocales() {
return Object.keys(this.$i18n.messages)
}
},
created() {
// Load saved locale from localStorage if available
const savedLocale = localStorage.getItem('userLocale')
if (savedLocale && this.availableLocales.includes(savedLocale)) {
this.currentLocale = savedLocale
}
}
}
</script>

<style scoped>
.language-switcher {
margin: 1rem 0;
}
</style>

Using the Composition API:

html
<template>
<div class="language-switcher">
<select v-model="currentLocale">
<option v-for="locale in availableLocales" :key="locale" :value="locale">
{{ languageNames[locale] }}
</option>
</select>
</div>
</template>

<script setup>
import { useI18n } from 'vue-i18n'
import { computed, onMounted, ref } from 'vue'

const { locale, availableLocales } = useI18n()

const languageNames = {
en: 'English',
es: 'Español',
fr: 'Français'
}

const currentLocale = computed({
get() {
return locale.value
},
set(newLocale) {
locale.value = newLocale
localStorage.setItem('userLocale', newLocale)
}
})

onMounted(() => {
const savedLocale = localStorage.getItem('userLocale')
if (savedLocale && availableLocales.includes(savedLocale)) {
currentLocale.value = savedLocale
}
})
</script>

Real-world Application: Contact Form

Let's create a multilingual contact form to demonstrate a more complete example:

html
<template>
<div class="contact-form">
<h2>{{ $t('contact.title') }}</h2>
<p>{{ $t('contact.description') }}</p>

<form @submit.prevent="submitForm">
<div class="form-group">
<label for="name">{{ $t('contact.form.name') }}</label>
<input
id="name"
v-model="formData.name"
:placeholder="$t('contact.form.namePlaceholder')"
required
/>
<span class="error" v-if="errors.name">{{ $t(errors.name) }}</span>
</div>

<div class="form-group">
<label for="email">{{ $t('contact.form.email') }}</label>
<input
id="email"
type="email"
v-model="formData.email"
:placeholder="$t('contact.form.emailPlaceholder')"
required
/>
<span class="error" v-if="errors.email">{{ $t(errors.email) }}</span>
</div>

<div class="form-group">
<label for="message">{{ $t('contact.form.message') }}</label>
<textarea
id="message"
v-model="formData.message"
:placeholder="$t('contact.form.messagePlaceholder')"
rows="4"
required
></textarea>
<span class="error" v-if="errors.message">{{ $t(errors.message) }}</span>
</div>

<button type="submit">{{ $t('contact.form.submit') }}</button>
</form>

<div v-if="submitted" class="success-message">
{{ $t('contact.successMessage') }}
</div>
</div>
</template>

<script>
export default {
data() {
return {
formData: {
name: '',
email: '',
message: ''
},
errors: {
name: '',
email: '',
message: ''
},
submitted: false
}
},
methods: {
validateForm() {
let isValid = true
this.errors = {
name: '',
email: '',
message: ''
}

if (!this.formData.name.trim()) {
this.errors.name = 'contact.validation.nameRequired'
isValid = false
}

if (!this.formData.email.trim()) {
this.errors.email = 'contact.validation.emailRequired'
isValid = false
} else if (!this.isValidEmail(this.formData.email)) {
this.errors.email = 'contact.validation.emailInvalid'
isValid = false
}

if (!this.formData.message.trim()) {
this.errors.message = 'contact.validation.messageRequired'
isValid = false
} else if (this.formData.message.length < 10) {
this.errors.message = 'contact.validation.messageLength'
isValid = false
}

return isValid
},
isValidEmail(email) {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
return re.test(String(email).toLowerCase())
},
submitForm() {
if (this.validateForm()) {
// Here you would normally send the form data to your backend
console.log('Form submitted:', this.formData)
this.submitted = true

// Reset form after submission
this.formData = {
name: '',
email: '',
message: ''
}
}
}
}
}
</script>

Our translation files for the form would look like:

javascript
// en.json
{
"contact": {
"title": "Contact Us",
"description": "Have a question or feedback? We'd love to hear from you!",
"form": {
"name": "Name",
"namePlaceholder": "Enter your name",
"email": "Email",
"emailPlaceholder": "Enter your email",
"message": "Message",
"messagePlaceholder": "What would you like to tell us?",
"submit": "Send Message"
},
"validation": {
"nameRequired": "Name is required",
"emailRequired": "Email is required",
"emailInvalid": "Please enter a valid email address",
"messageRequired": "Message is required",
"messageLength": "Message must be at least 10 characters long"
},
"successMessage": "Thank you for your message! We will get back to you soon."
}
}
javascript
// es.json
{
"contact": {
"title": "Contáctenos",
"description": "¿Tiene alguna pregunta o comentario? ¡Nos encantaría saber de usted!",
"form": {
"name": "Nombre",
"namePlaceholder": "Introduce tu nombre",
"email": "Correo electrónico",
"emailPlaceholder": "Introduce tu correo electrónico",
"message": "Mensaje",
"messagePlaceholder": "¿Qué te gustaría decirnos?",
"submit": "Enviar Mensaje"
},
"validation": {
"nameRequired": "El nombre es obligatorio",
"emailRequired": "El correo electrónico es obligatorio",
"emailInvalid": "Por favor, introduce una dirección de correo electrónico válida",
"messageRequired": "El mensaje es obligatorio",
"messageLength": "El mensaje debe tener al menos 10 caracteres"
},
"successMessage": "¡Gracias por tu mensaje! Nos pondremos en contacto contigo pronto."
}
}

Best Practices for i18n

When working with internationalization in Vue.js, consider these best practices:

  1. Separate translation files: Keep your translation files organized by language and possibly by feature.

  2. Use namespacing: Group related translations under namespaces to avoid conflicts and improve organization.

  3. Leverage built-in pluralization: Use Vue I18n's pluralization features for handling singular and plural forms.

  4. Handle dynamic content: Use parameters to insert dynamic content into your translations.

  5. Consider text direction: Some languages read from right to left (RTL), so consider using CSS classes to handle text direction.

html
<template>
<div :dir="$i18n.locale === 'ar' ? 'rtl' : 'ltr'">
{{ $t('welcome') }}
</div>
</template>
  1. Lazy load translations: For large applications, consider lazy loading translations to improve performance.

  2. Handle formatting consistently: Use Vue I18n's date, time, and number formatting to ensure consistent output.

  3. Test with pseudo-localization: Before translating to many languages, test with pseudo-localization to ensure your UI can handle different text lengths.

Language Detection and User Preferences

To provide the best user experience, you might want to automatically detect the user's preferred language:

javascript
// i18n/index.js
import { createI18n } from 'vue-i18n'
import en from './locales/en.json'
import es from './locales/es.json'
import fr from './locales/fr.json'

// Detect user's preferred language
function detectLanguage() {
// Check localStorage first for user preference
const savedLocale = localStorage.getItem('userLocale')
if (savedLocale && ['en', 'es', 'fr'].includes(savedLocale)) {
return savedLocale
}

// Check browser language
const browserLang = navigator.language.split('-')[0]
if (['en', 'es', 'fr'].includes(browserLang)) {
return browserLang
}

// Default to English
return 'en'
}

export const i18n = createI18n({
locale: detectLanguage(),
fallbackLocale: 'en',
messages: {
en,
es,
fr
}
})

Workflow for Translators

For real projects, you might need to work with professional translators. Here's a workflow you might follow:

  1. Extract your base language strings into a format translators can work with (like JSON or XLSX).

  2. Send the files to translators with clear context about where each string is used.

  3. Import the translated files back into your application.

  4. Consider using specialized translation management systems (TMS) for larger projects.

You can use tools like BabelEdit or POEditor to manage translations.

Summary

Vue I18n is a powerful tool that makes it easy to create multilingual Vue applications. In this guide, we've covered:

  1. Setting up Vue I18n in your Vue.js project
  2. Creating translation files for different languages
  3. Using translations in templates and JavaScript
  4. Advanced features like pluralization and formatting
  5. Creating a language switcher
  6. Building a complete multilingual contact form
  7. Best practices for internationalization

By implementing proper internationalization from the start, you ensure your application can reach a global audience and provide a better user experience for non-native speakers.

Additional Resources

Exercises

  1. Basic Translation: Create a simple Vue component with a greeting message and translate it into three different languages.

  2. Language Switcher: Build a language switcher component that allows users to change languages and stores their preference in localStorage.

  3. Plural Forms: Create a shopping cart summary that correctly handles pluralization (e.g., "1 item", "2 items").

  4. Date Formatting: Display today's date in different formats based on the selected locale.

  5. Complete Application: Build a small application (like a todo list) with full i18n support, including form validation messages, placeholders, and button text.

By completing these exercises, you'll gain practical experience with Vue I18n and be ready to implement internationalization in your own projects.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)