Installation
Install vue-i18next and its dependencies:
npm install vue-i18next i18next i18next-vue
Project Setup
Vue 3 Configuration
Create the i18next configuration:
// src/i18n/index.ts
import i18next from 'i18next'
import I18NextVue from 'i18next-vue'
import { App } from 'vue'
// Import translation files
import en from './locales/en.json'
import pl from './locales/pl.json'
import es from './locales/es.json'
import fr from './locales/fr.json'
export const SUPPORT_LOCALES = ['en', 'pl', 'es', 'fr']
export async function setupI18n(app: App) {
await i18next.init({
lng: localStorage.getItem('selectedLanguage') || 'en',
fallbackLng: 'en',
debug: process.env.NODE_ENV === 'development',
resources: {
en: { translation: en },
pl: { translation: pl },
es: { translation: es },
fr: { translation: fr }
},
interpolation: {
escapeValue: false // Vue already does escaping
},
react: {
useSuspense: false
}
})
app.use(I18NextVue, { i18next })
return i18next
}
Main Application Setup
// src/main.ts
import { createApp } from 'vue'
import { setupI18n } from './i18n'
import App from './App.vue'
async function initApp() {
const app = createApp(App)
await setupI18n(app)
app.mount('#app')
}
initApp()
Translation File Structure
Create translation files in the src/i18n/locales/
directory:
// src/i18n/locales/en.json
{
"homepage": {
"title": "Welcome to our website",
"description": "This is a multilingual Vue.js application",
"subtitle": "Built with vue-i18next"
},
"navigation": {
"home": "Home",
"about": "About",
"contact": "Contact",
"products": "Products"
},
"product": {
"name": "Product Name",
"price": "Price: {{price}}",
"available": "Available since: {{date}}",
"addToCart": "Add to Cart",
"inStock": "In Stock",
"outOfStock": "Out of Stock",
"description": "Product description with <strong>HTML</strong> content"
},
"cart": {
"title": "Shopping Cart",
"empty": "Your cart is empty",
"total": "Total: {{amount}}",
"checkout": "Checkout",
"items_zero": "No items",
"items_one": "{{count}} item",
"items_other": "{{count}} items"
},
"user": {
"welcome": "Welcome, {{name}}!",
"profile": "Profile",
"logout": "Logout",
"login": "Login",
"greeting": "Hello {{name}}, you have {{count}} message",
"greeting_plural": "Hello {{name}}, you have {{count}} messages"
},
"forms": {
"email": "Email",
"password": "Password",
"submit": "Submit",
"cancel": "Cancel",
"save": "Save",
"required": "This field is required",
"validation": {
"email": "Please enter a valid email",
"minLength": "Minimum {{count}} characters required"
}
},
"messages": {
"loading": "Loading...",
"error": "Something went wrong",
"success": "Operation completed successfully",
"confirm": "Are you sure?",
"networkError": "Network error occurred"
},
"datetime": {
"now": "just now",
"minuteAgo": "{{count}} minute ago",
"minuteAgo_plural": "{{count}} minutes ago",
"hourAgo": "{{count}} hour ago",
"hourAgo_plural": "{{count}} hours ago"
}
}
// src/i18n/locales/pl.json
{
"homepage": {
"title": "Witamy na naszej stronie",
"description": "To jest wielojęzyczna aplikacja Vue.js",
"subtitle": "Zbudowana z vue-i18next"
},
"navigation": {
"home": "Strona główna",
"about": "O nas",
"contact": "Kontakt",
"products": "Produkty"
},
"product": {
"name": "Nazwa produktu",
"price": "Cena: {{price}}",
"available": "Dostępne od: {{date}}",
"addToCart": "Dodaj do koszyka",
"inStock": "Na stanie",
"outOfStock": "Brak na stanie",
"description": "Opis produktu z <strong>HTML</strong> treścią"
},
"cart": {
"title": "Koszyk",
"empty": "Twój koszyk jest pusty",
"total": "Razem: {{amount}}",
"checkout": "Złóż zamówienie",
"items_zero": "Brak przedmiotów",
"items_one": "{{count}} przedmiot",
"items_few": "{{count}} przedmioty",
"items_many": "{{count}} przedmiotów",
"items_other": "{{count}} przedmiotów"
},
"user": {
"welcome": "Witaj, {{name}}!",
"profile": "Profil",
"logout": "Wyloguj",
"login": "Zaloguj",
"greeting": "Cześć {{name}}, masz {{count}} wiadomość",
"greeting_few": "Cześć {{name}}, masz {{count}} wiadomości",
"greeting_many": "Cześć {{name}}, masz {{count}} wiadomości",
"greeting_other": "Cześć {{name}}, masz {{count}} wiadomości"
},
"forms": {
"email": "Email",
"password": "Hasło",
"submit": "Wyślij",
"cancel": "Anuluj",
"save": "Zapisz",
"required": "To pole jest wymagane",
"validation": {
"email": "Wprowadź prawidłowy email",
"minLength": "Wymagane minimum {{count}} znaków"
}
},
"messages": {
"loading": "Ładowanie...",
"error": "Coś poszło nie tak",
"success": "Operacja zakończona pomyślnie",
"confirm": "Czy jesteś pewny?",
"networkError": "Wystąpił błąd sieci"
},
"datetime": {
"now": "przed chwilą",
"minuteAgo": "{{count}} minutę temu",
"minuteAgo_few": "{{count}} minuty temu",
"minuteAgo_many": "{{count}} minut temu",
"minuteAgo_other": "{{count}} minut temu",
"hourAgo": "{{count}} godzinę temu",
"hourAgo_few": "{{count}} godziny temu",
"hourAgo_many": "{{count}} godzin temu",
"hourAgo_other": "{{count}} godzin temu"
}
}
// src/i18n/locales/es.json
{
"homepage": {
"title": "Bienvenido a nuestro sitio web",
"description": "Esta es una aplicación Vue.js multiidioma",
"subtitle": "Construida con vue-i18next"
},
"navigation": {
"home": "Inicio",
"about": "Acerca de",
"contact": "Contacto",
"products": "Productos"
},
"product": {
"name": "Nombre del producto",
"price": "Precio: {{price}}",
"available": "Disponible desde: {{date}}",
"addToCart": "Añadir al carrito",
"inStock": "En stock",
"outOfStock": "Agotado",
"description": "Descripción del producto con contenido <strong>HTML</strong>"
},
"cart": {
"title": "Carrito de compras",
"empty": "Tu carrito está vacío",
"total": "Total: {{amount}}",
"checkout": "Finalizar compra",
"items_zero": "Sin artículos",
"items_one": "{{count}} artículo",
"items_other": "{{count}} artículos"
},
"user": {
"welcome": "¡Bienvenido, {{name}}!",
"profile": "Perfil",
"logout": "Cerrar sesión",
"login": "Iniciar sesión",
"greeting": "Hola {{name}}, tienes {{count}} mensaje",
"greeting_plural": "Hola {{name}}, tienes {{count}} mensajes"
},
"forms": {
"email": "Correo electrónico",
"password": "Contraseña",
"submit": "Enviar",
"cancel": "Cancelar",
"save": "Guardar",
"required": "Este campo es obligatorio",
"validation": {
"email": "Introduce un email válido",
"minLength": "Se requieren mínimo {{count}} caracteres"
}
},
"messages": {
"loading": "Cargando...",
"error": "Algo salió mal",
"success": "Operación completada exitosamente",
"confirm": "¿Estás seguro?",
"networkError": "Ocurrió un error de red"
},
"datetime": {
"now": "ahora mismo",
"minuteAgo": "hace {{count}} minuto",
"minuteAgo_plural": "hace {{count}} minutos",
"hourAgo": "hace {{count}} hora",
"hourAgo_plural": "hace {{count}} horas"
}
}
// src/i18n/locales/fr.json
{
"homepage": {
"title": "Bienvenue sur notre site web",
"description": "Ceci est une application Vue.js multilingue",
"subtitle": "Construite avec vue-i18next"
},
"navigation": {
"home": "Accueil",
"about": "À propos",
"contact": "Contact",
"products": "Produits"
},
"product": {
"name": "Nom du produit",
"price": "Prix: {{price}}",
"available": "Disponible depuis: {{date}}",
"addToCart": "Ajouter au panier",
"inStock": "En stock",
"outOfStock": "Rupture de stock",
"description": "Description du produit avec contenu <strong>HTML</strong>"
},
"cart": {
"title": "Panier",
"empty": "Votre panier est vide",
"total": "Total: {{amount}}",
"checkout": "Commander",
"items_zero": "Aucun article",
"items_one": "{{count}} article",
"items_other": "{{count}} articles"
},
"user": {
"welcome": "Bienvenue, {{name}} !",
"profile": "Profil",
"logout": "Déconnexion",
"login": "Connexion",
"greeting": "Salut {{name}}, vous avez {{count}} message",
"greeting_plural": "Salut {{name}}, vous avez {{count}} messages"
},
"forms": {
"email": "Email",
"password": "Mot de passe",
"submit": "Soumettre",
"cancel": "Annuler",
"save": "Sauvegarder",
"required": "Ce champ est requis",
"validation": {
"email": "Veuillez entrer un email valide",
"minLength": "Minimum {{count}} caractères requis"
}
},
"messages": {
"loading": "Chargement...",
"error": "Quelque chose s'est mal passé",
"success": "Opération terminée avec succès",
"confirm": "Êtes-vous sûr ?",
"networkError": "Erreur réseau survenue"
},
"datetime": {
"now": "à l'instant",
"minuteAgo": "il y a {{count}} minute",
"minuteAgo_plural": "il y a {{count}} minutes",
"hourAgo": "il y a {{count}} heure",
"hourAgo_plural": "il y a {{count}} heures"
}
}
Usage Examples
Basic Component Usage (Composition API)
<!-- HomePage.vue -->
<template>
<div class="homepage">
<h1>{{ $t('homepage.title') }}</h1>
<p>{{ $t('homepage.description') }}</p>
<small>{{ $t('homepage.subtitle') }}</small>
<LanguageSwitcher />
<ProductShowcase :products="products" />
<UserGreeting :user="currentUser" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { useI18n } from 'vue-i18next'
import LanguageSwitcher from './LanguageSwitcher.vue'
import ProductShowcase from './ProductShowcase.vue'
import UserGreeting from './UserGreeting.vue'
const { t } = useI18n()
const products = ref([
{
id: 1,
name: 'Sample Product',
price: 29.99,
availableDate: new Date('2023-01-15'),
inStock: true
}
])
const currentUser = ref({
name: 'John Doe',
messageCount: 3
})
</script>
Language Switcher Component
<!-- LanguageSwitcher.vue -->
<template>
<div class="language-switcher">
<label for="language-select">{{ $t('ui.selectLanguage') }}:</label>
<select
id="language-select"
:value="currentLanguage"
@change="changeLanguage">
<option
v-for="lang in availableLanguages"
:key="lang.code"
:value="lang.code">
{{ lang.name }}
</option>
</select>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18next'
const { i18n } = useI18n()
const availableLanguages = [
{ code: 'en', name: 'English' },
{ code: 'pl', name: 'Polski' },
{ code: 'es', name: 'Español' },
{ code: 'fr', name: 'Français' }
]
const currentLanguage = computed(() => i18n.language)
const changeLanguage = async (event: Event) => {
const target = event.target as HTMLSelectElement
const newLanguage = target.value
await i18n.changeLanguage(newLanguage)
localStorage.setItem('selectedLanguage', newLanguage)
}
</script>
Product Component with Complex Translations
<!-- ProductCard.vue -->
<template>
<div class="product-card">
<h3>{{ product.name }}</h3>
<p>{{ $t('product.price', { price: formatPrice(product.price) }) }}</p>
<p>{{ $t('product.available', { date: formatDate(product.availableDate) }) }}</p>
<!-- HTML content in translations -->
<div v-html="$t('product.description')"></div>
<div class="product-status">
<span :class="stockClass">
{{ product.inStock ? $t('product.inStock') : $t('product.outOfStock') }}
</span>
</div>
<button
@click="addToCart"
:disabled="!product.inStock"
class="btn btn-primary">
{{ $t('product.addToCart') }}
</button>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18next'
interface Product {
id: number
name: string
price: number
availableDate: Date
inStock: boolean
}
const props = defineProps<{
product: Product
}>()
const { t, i18n } = useI18n()
const stockClass = computed(() => ({
'in-stock': props.product.inStock,
'out-of-stock': !props.product.inStock
}))
const formatPrice = (price: number) => {
return new Intl.NumberFormat(i18n.language, {
style: 'currency',
currency: 'USD'
}).format(price)
}
const formatDate = (date: Date) => {
return new Intl.DateTimeFormat(i18n.language, {
year: 'numeric',
month: 'long',
day: 'numeric'
}).format(date)
}
const addToCart = () => {
console.log('Added to cart:', props.product.name)
}
</script>
User Greeting with Pluralization
<!-- UserGreeting.vue -->
<template>
<div class="user-greeting">
<h2>{{ $t('user.welcome', { name: user.name }) }}</h2>
<!-- Using i18next pluralization -->
<p>{{ $t('user.greeting', { name: user.name, count: user.messageCount }) }}</p>
<!-- Manual pluralization with conditions -->
<p v-if="user.messageCount === 0">{{ $t('messages.noMessages') }}</p>
<p v-else-if="user.messageCount === 1">{{ $t('messages.oneMessage') }}</p>
<p v-else>{{ $t('messages.multipleMessages', { count: user.messageCount }) }}</p>
<div class="user-actions">
<button @click="viewProfile" class="btn btn-secondary">
{{ $t('user.profile') }}
</button>
<button @click="logout" class="btn btn-outline">
{{ $t('user.logout') }}
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { useI18n } from 'vue-i18next'
interface User {
name: string
messageCount: number
}
const props = defineProps<{
user: User
}>()
const { t } = useI18n()
const viewProfile = () => {
console.log('Viewing profile for:', props.user.name)
}
const logout = () => {
console.log('Logging out:', props.user.name)
}
</script>
Shopping Cart with Pluralization
<!-- ShoppingCart.vue -->
<template>
<div class="shopping-cart">
<h2>{{ $t('cart.title') }}</h2>
<div v-if="items.length === 0" class="empty-cart">
{{ $t('cart.empty') }}
</div>
<div v-else class="cart-content">
<div class="cart-summary">
<!-- Using i18next pluralization with _zero, _one, _other -->
{{ $t('cart.items', { count: items.length }) }}
</div>
<div class="cart-items">
<div v-for="item in items" :key="item.id" class="cart-item">
<span>{{ item.name }}</span>
<span>{{ formatPrice(item.price) }}</span>
</div>
</div>
<div class="cart-total">
{{ $t('cart.total', { amount: formatPrice(getTotal()) }) }}
</div>
<button @click="checkout" class="btn btn-primary">
{{ $t('cart.checkout') }}
</button>
</div>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18next'
interface CartItem {
id: number
name: string
price: number
}
const props = defineProps<{
items: CartItem[]
}>()
const { t, i18n } = useI18n()
const getTotal = () => {
return props.items.reduce((total, item) => total + item.price, 0)
}
const formatPrice = (price: number) => {
return new Intl.NumberFormat(i18n.language, {
style: 'currency',
currency: 'USD'
}).format(price)
}
const checkout = () => {
console.log('Proceeding to checkout')
}
</script>
Translation Composable
// src/composables/useTranslations.ts
import { computed } from 'vue'
import { useI18n } from 'vue-i18next'
export function useTranslations() {
const { t, i18n } = useI18n()
const currentLanguage = computed(() => i18n.language)
const translate = (key: string, options?: any) => {
return t(key, options)
}
const changeLanguage = async (language: string) => {
await i18n.changeLanguage(language)
localStorage.setItem('selectedLanguage', language)
}
const formatCurrency = (amount: number, currency = 'USD') => {
return new Intl.NumberFormat(i18n.language, {
style: 'currency',
currency
}).format(amount)
}
const formatDate = (date: Date, options?: Intl.DateTimeFormatOptions) => {
return new Intl.DateTimeFormat(i18n.language, options).format(date)
}
const formatRelativeTime = (date: Date) => {
const now = new Date()
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000)
if (diffInSeconds < 60) {
return t('datetime.now')
} else if (diffInSeconds < 3600) {
const minutes = Math.floor(diffInSeconds / 60)
return t('datetime.minuteAgo', { count: minutes })
} else {
const hours = Math.floor(diffInSeconds / 3600)
return t('datetime.hourAgo', { count: hours })
}
}
return {
currentLanguage,
translate,
changeLanguage,
formatCurrency,
formatDate,
formatRelativeTime
}
}
SimpleLocalize Configuration
To manage translations with SimpleLocalize, you need to set up the CLI tool.
# macOS / Linux / Windows (WSL)
curl -s https://get.simplelocalize.io/2.9/install | bash
# Windows (PowerShell)
. { iwr -useb https://get.simplelocalize.io/2.9/install-windows } | iex;
# npm
npm install @simplelocalize/cli
# macOS / Linux / Windows (WSL)
curl -s https://get.simplelocalize.io/2.9/install | bash
# Windows (PowerShell)
. { iwr -useb https://get.simplelocalize.io/2.9/install-windows } | iex;
# npm
npm install @simplelocalize/cli
Create simplelocalize.yml
in your project root:
apiKey: YOUR_PROJECT_API_KEY
uploadFormat: single-language-json
uploadPath: ./src/i18n/locales/{lang}.json
uploadLanguageKey: en
uploadOptions:
- REPLACE_TRANSLATION_IF_FOUND
downloadPath: ./src/i18n/locales/{lang}.json
downloadLanguageKeys: ['en', 'pl', 'es', 'fr']
downloadFormat: single-language-json
Work with the CLI to manage your translations:
# Upload translations to SimpleLocalize
simplelocalize upload
# Download translations for all languages
simplelocalize download
# Build your Vue.js application
npm run build
Key Features
- i18next Ecosystem: Leverage the mature i18next ecosystem and plugins
- Advanced Pluralization: Support for complex plural rules across languages
- Interpolation: Variable interpolation with formatting options
- Namespaces: Organize translations into logical namespaces
- Context: Context-sensitive translations
- HTML Support: Safe HTML rendering in translations
- Lazy Loading: Load translations on demand
- Change Detection: Automatic re-rendering when language changes
Best Practices
1. Use Namespaces for Organization
// Organize translations by namespace
const i18n = createI18next({
ns: ['common', 'navigation', 'forms'],
defaultNS: 'common',
resources: {
en: {
common: { /* common translations */ },
navigation: { /* navigation translations */ },
forms: { /* form translations */ }
}
}
})
2. Handle Missing Translations
// Add missing key handler
const i18n = createI18next({
missingKeyHandler: (lng, ns, key) => {
console.warn(`Missing translation: ${key} for language: ${lng}`)
}
})
3. Use Context for Variations
{
"friend": "A friend",
"friend_male": "A boyfriend",
"friend_female": "A girlfriend"
}
Resources
- vue-i18next Documentation
- i18next Documentation
- i18next Pluralization
- SimpleLocalize CLI Documentation
Was this helpful?