DEV Community

Cover image for Vue-elingual: "Teaching Your App to Speak Multiple Languages"
Nerando Johnson
Nerando Johnson

Posted on • Updated on

Vue-elingual: "Teaching Your App to Speak Multiple Languages"

Vue-elingual: Teaching Your App to Speak Multiple Languages

It is said that: If you talk to a man in a language he understands, that goes to his head. If you talk to him in his language, that goes to his heart. ~ Nelson Mandela, software is and should always be accessible, thus the interfaces created so that it can be used in any language. There are various approaches to this, the primary and most recognized approaches are i18n and l10n(localization), they aren't the same but are used interchangeably. For this deep dive, we are going to focus on i18n. The easiest way to explain or define this term is this, internationalization enables a piece of software to handle multiple language environments, localization enables a piece of software to support a specific regional language environment.

Overview

This guide will show you how to implement internationalization in your Vue 3 application using vue-i18n. You'll learn how to structure translations, handle dynamic content, and manage language switching - all within the Vue 3 composition API context and all explained in true developer style.

1. Basic Setup and Simple Translations


<!-- App.vue -->
<template>
  <div>
    <h1>{{ t('welcome') }}</h1>
    <p v-if="isLoading">{{ t('loading') }}</p>
  </div>
</template>

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

const { t } = useI18n()
</script>
Enter fullscreen mode Exit fullscreen mode
// i18n.js
import { createI18n } from 'vue-i18n'

const i18n = createI18n({
  locale: 'en', // default language
  fallbackLocale: 'en', // fallback for when things go wrong
  messages: {
    en: {
      welcome: 'Welcome to the Matrix',
      errors: {
        404: 'Page is hiding in another castle',
        500: 'Server is having a moment'
      },
      loading: 'Convincing electrons to do work...'
    },
    es: {
      welcome: 'Bienvenido a la Matrix',
      errors: {
        404: 'La página está escondida en otro castillo',
        500: 'El servidor está teniendo un momento'
      },
      loading: 'Convenciendo a los electrones de trabajar...'
    }
  }
})

// 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')
Enter fullscreen mode Exit fullscreen mode

2. Language Switching in Vue 3: The Polyglot Button


<!-- LocaleSwitcher.vue -->
<template>
  <div class="lang-switcher">
    <select v-model="locale">
      <option 
        v-for="(name, lang) in languages" 
        :key="lang" 
        :value="lang"
      >
        {{ name }}
      </option>
    </select>
  </div>
</template>

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

const { locale, t } = useI18n()

const languages = {
  en: "🇬🇧 English (Compile-time)",
  es: "🇪🇸 Español (Tiempo de compilación)"
}

const switchLanguage = (lang) => {
  locale.value = lang
  console.log(t('debug.langSwitch', { lang }))
}
</script>
Enter fullscreen mode Exit fullscreen mode

3. Dynamic Values and Interpolation: When Your App Gets Personal


<!-- DynamicContent.vue -->
<template>
  <div>
    <p>{{ t('greetings.morning', { name: developerName }) }}</p>
    <p>{{ t('bugs.count', bugCount) }}</p>
  </div>
</template>

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

const { t } = useI18n()
const developerName = ref('Code Wizard')
const bugCount = ref(42)
</script>
Enter fullscreen mode Exit fullscreen mode
// translations/messages.js
export default {
  en: {
    greetings: {
      morning: "Good morning {name}, have you tried turning it off and on again?",
      night: "Still coding at {time}? Same here!",
      weekend: "It's {day}! Time to debug in pajamas!"
    },
    bugs: {
      count: "You have {count} bugs to fix | You have {count} bugs to fix | Coffee needed: {count} bugs detected"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Handling Pluralization: Because Zero is Special


<!-- PluralExample.vue -->
<template>
  <div>
    <p>{{ t('debug.bugs', bugCount) }}</p>
    <p>{{ t('coffee.cups', coffeeCount) }}</p>
  </div>
</template>

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

const { t } = useI18n()
const bugCount = ref(0)
const coffeeCount = ref(3)
</script>
Enter fullscreen mode Exit fullscreen mode
// translations/plural.js
export default {
  en: {
    debug: {
      bugs_zero: "No bugs found (suspicious...)",
      bugs_one: "Found one bug (there's definitely more)",
      bugs_other: "Found {count} bugs (and counting...)"
    },
    coffee: {
      cups_zero: "Emergency: No coffee remaining!",
      cups_one: "Last coffee warning!",
      cups_other: "{count} cups of coffee remaining"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

5. Organizing Translations with Namespaces: The Developer's Survival Kit


// translations/developer-life.js
export const developerLife = {
  en: {
    statusMessages: {
      compiling: "Converting caffeine to code...",
      debugging: "Playing hide and seek with bugs...",
      deploying: "Crossing fingers and deploying...",
      success: "It works! Don't touch anything!",
      error: "Error: Success condition not found"
    },
    excuses: {
      deadline: "The deadline was more of a suggestion",
      bug: "It's not a bug, it's an undocumented feature",
      testing: "But it works on my machine!"
    }
  }
}

// translations/error-messages.js
export const errorMessages = {
  en: {
    errors: {
      network: "Internet decided to take a coffee break",
      database: "Database is practicing social distancing",
      validation: "Your code is valid but my heart says no"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

6. Composition API Integration: Creating a Developer-Friendly Translation Composable


<!-- TranslationExample.vue -->
<template>
  <div>
    <button @click="alert(getRandomExcuse())">
      Generate Developer Excuse
    </button>
    <p>{{ debugStatus.message }}</p>
  </div>
</template>

<script setup>
import { useDevTranslations } from '@/composables/useDevTranslations'

const { getRandomExcuse, debugStatus } = useDevTranslations()
</script>
Enter fullscreen mode Exit fullscreen mode
// composables/useDevTranslations.js
import { useI18n } from 'vue-i18n'
import { computed } from 'vue'

export function useDevTranslations() {
  const { t, locale } = useI18n()

  const getRandomExcuse = () => {
    const excuses = ['deadline', 'bug', 'testing']
    return t(`excuses.${excuses[Math.floor(Math.random() * excuses.length)]}`)
  }

  const getCoffeeStatus = (cups) => {
    return t('coffee.cups', cups)
  }

  const debugStatus = computed(() => ({
    message: t('statusMessages.debugging'),
    excuse: getRandomExcuse()
  }))

  return {
    getRandomExcuse,
    getCoffeeStatus,
    debugStatus,
    currentLocale: locale
  }
}
Enter fullscreen mode Exit fullscreen mode

Quick Start Example


<!-- App.vue -->
<template>
  <div>
    <LocaleSwitcher />
    <h1>{{ t('title') }}</h1>
    <p>{{ t('welcome') }}</p>
  </div>
</template>

<script setup>
import LocaleSwitcher from './components/LocaleSwitcher.vue'
import { useI18n } from 'vue-i18n'

const { t } = useI18n()
</script>
Enter fullscreen mode Exit fullscreen mode

Best Practices for Vue 3 i18n

  1. Modular Organization: Keep translations in separate files by feature/module or sort translations like you sort your coffee cups - by importance.
  2. Type Safety: Use TypeScript with vue-i18n for better type checking or because future you will thank present you.
  3. Lazy Loading: Load language files on demand to improve initial load time or like one should brew coffee - on demand.
  4. Composition API: Use composables to encapsulate translation logic
  5. State Management: Consider using Pinia to manage language preferences or because global state is like coffee - best when managed properly.
  6. Testing: Write tests for your translations using Vue Test Utils or they'll test your patience.

Additional Resources

Top comments (0)