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 dir to <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.json so t('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.