Localize a Vue app: complete guide with Localingos
Vue 3 with the Composition API + vue-i18n is the de facto i18n stack in 2026, and it pairs cleanly with Localingos's automated translation pipeline. This guide takes a fresh Vue 3 app to fully localized in about 20 minutes, covering setup, runtime usage, pluralization across CLDR rules, lazy locale loading, and CI integration.
Step 1 — Install
npm install vue-i18n@9
npm install -g @localingos/cli
localingos login
Vue 3 needs vue-i18n@9 (or later) — earlier versions only support Vue 2.
Step 2 — Wire vue-i18n
src/i18n/index.ts:
import { createI18n } from 'vue-i18n';
import en from './en.json';
import es from './es.json';
import de from './de.json';
import fr from './fr.json';
export const i18n = createI18n({
legacy: false, // use Composition API
locale: detectLocale(),
fallbackLocale: 'en',
messages: { en, es, de, fr },
});
function detectLocale(): string {
const saved = localStorage.getItem('locale');
if (saved) return saved;
return (navigator.language || 'en').split('-')[0];
}
src/main.ts:
import { createApp } from 'vue';
import App from './App.vue';
import { i18n } from './i18n';
createApp(App).use(i18n).mount('#app');
src/i18n/en.json — your source of truth:
{
"welcome": "Welcome, {name}",
"cart": "no items in cart | one item in cart | {count} items in cart"
}
Note the pipe syntax for vue-i18n's pluralization — zero | one | other.
Step 3 — Configure Localingos
localingos.json:
{
"projectId": "your-project-id",
"source": { "path": "src/i18n/en.json", "locale": "en" },
"targets": {
"path": "src/i18n/{locale}.json",
"locales": ["es", "de", "fr", "ja", "pt-BR", "zh-CN", "ko"]
},
"placeholders": ["{name}"]
}
Important: vue-i18n uses {variable} (single curly braces) by default, NOT {{variable}} like react-i18next. Make sure your placeholders config matches.
localingos sync
Translations land in src/i18n/. Commit them.
Step 4 — Use in components
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
const { t, locale } = useI18n();
defineProps<{ userName: string; itemCount: number }>();
</script>
<template>
<header>
<h1>{{ t('welcome', { name: userName }) }}</h1>
<p>{{ t('cart', itemCount) }}</p>
</header>
</template>
vue-i18n picks the right plural variant based on itemCount and the active locale. Russian's three forms, Arabic's six — handled automatically as long as Localingos has supplied the variants (which it does, per CLDR).
Step 5 — Language switcher
<script setup lang="ts">
import { useI18n } from 'vue-i18n';
const { locale } = useI18n();
const LOCALES = { en: 'English', es: 'Español', de: 'Deutsch', fr: 'Français' };
function switchLocale(newLocale: string) {
locale.value = newLocale;
localStorage.setItem('locale', newLocale);
}
</script>
<template>
<select :value="locale" @change="e => switchLocale((e.target as HTMLSelectElement).value)">
<option v-for="(label, code) in LOCALES" :key="code" :value="code">
{{ label }}
</option>
</select>
</template>
Step 6 — Lazy loading locales
If you ship 10+ languages, bundling every locale into the initial JS is wasteful. Switch to dynamic imports:
import { createI18n } from 'vue-i18n';
const SUPPORTED = ['en', 'es', 'de', 'fr', 'ja', 'pt-BR', 'zh-CN', 'ko'];
export const i18n = createI18n({
legacy: false,
locale: 'en',
fallbackLocale: 'en',
messages: {},
});
export async function loadLocale(locale: string) {
if (!SUPPORTED.includes(locale)) return;
const messages = await import(`./locales/${locale}.json`);
i18n.global.setLocaleMessage(locale, messages.default);
i18n.global.locale.value = locale;
}
// On app start
loadLocale(detectLocale());
Each locale becomes its own chunk; only the active one is fetched on demand.
Step 7 — Automate sync in CI
# .github/workflows/i18n.yml
name: i18n-sync
on:
push: { branches: [main], paths: ['src/i18n/en.json'] }
jobs:
sync:
runs-on: ubuntu-latest
permissions: { contents: write, pull-requests: write }
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm install -g @localingos/cli
- run: localingos sync
env: { LOCALINGOS_API_KEY: '${{ secrets.LOCALINGOS_API_KEY }}' }
- uses: peter-evans/create-pull-request@v6
with:
branch: i18n/auto-sync
title: 'chore(i18n): sync translations'
commit-message: 'chore(i18n): sync translations'
Production checklist
- SSR-compatible. vue-i18n works with Nuxt out of the box; for Vite + custom SSR setups, ensure i18n state is per-request (not shared across requests).
- RTL support. Bind
dirto<html>based on the active locale:'ar', 'he', 'fa'are RTL. - Number/date formatting. vue-i18n has built-in helpers (
$n(),$d()) — much cleaner than rolling Intl.NumberFormat by hand. - Type-safe keys. Generate types from
en.jsonsot('missingKey')is a compile error.
Wrap up
Your Vue 3 app now handles 56 locales with pluralization, lazy loading, and persistent user preference. Translations stay current through CI. Adding the next language is one entry in localingos.json and a redeploy.
Free tier covers 5,000 words — typical Vue app string corpus with room to spare.