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