IntlPull
Tutorial
18 min read

Localización de Vue.js: Guía Completa de Vue i18n (2026)

Domina la localización de Vue.js con vue-i18n. Tutorial paso a paso que cubre configuración, traducciones dinámicas, pluralización, carga perezosa y despliegue en producción.

IntlPull Team
IntlPull Team
03 Feb 2026, 11:44 AM [PST]
On this page
Summary

Domina la localización de Vue.js con vue-i18n. Tutorial paso a paso que cubre configuración, traducciones dinámicas, pluralización, carga perezosa y despliegue en producción.

El Problema

Has construido una hermosa aplicación Vue. Ahora tu equipo de producto dice "necesitamos soportar español, francés y alemán."

¿Por dónde empiezas? ¿Cómo:

  • Extraer todas esas cadenas codificadas?
  • Cambiar idiomas sin recargas de página?
  • Manejar plurales de manera diferente en cada idioma?
  • Cargar traducciones de forma perezosa para mantener tu bundle pequeño?

Esta guía recorre todo, usando vue-i18n, la biblioteca oficial de localización de Vue.

Lo Que Construirás

Al final de este tutorial, tendrás una aplicación Vue que:

  • ✅ Cambia idiomas instantáneamente
  • ✅ Maneja plurales correctamente en más de 40 idiomas
  • ✅ Formatea fechas, números y monedas por locale
  • ✅ Carga traducciones de forma perezosa para rendimiento
  • ✅ Funciona con Vue Router para URLs amigables con SEO
  • ✅ Se sincroniza con un sistema de gestión de traducciones

Stack tecnológico:

  • Vue 3 (Composition API)
  • vue-i18n v9
  • Vite
  • TypeScript (opcional pero recomendado)

Paso 1: Instalar vue-i18n

Primero, instala la biblioteca:

Terminal
npm install vue-i18n@9

¿Por qué vue-i18n? Es el plugin oficial de i18n para Vue, mantenido por el equipo de Vue. Tiene:

  • Más de 5M de descargas semanales
  • Soporte integrado para la Composition API de Vue 3
  • Soporte SSR para Nuxt
  • Formato de mensaje ICU (igual que React/Angular)

Paso 2: Crear Archivos de Traducción

Crea una carpeta locales con tus archivos de traducción:

src/
├── locales/
│   ├── en.json
│   ├── es.json
│   └── fr.json
└── i18n.ts

en.json:

JSON
1{
2  "nav": {
3    "home": "Home",
4    "about": "About",
5    "pricing": "Pricing"
6  },
7  "hero": {
8    "title": "Build apps faster",
9    "subtitle": "Ship features your users love",
10    "cta": "Get started"
11  },
12  "product": {
13    "addToCart": "Add to cart",
14    "itemCount": "No items | {n} item | {n} items",
15    "price": "{amount} per month"
16  }
17}

es.json:

JSON
1{
2  "nav": {
3    "home": "Inicio",
4    "about": "Acerca de",
5    "pricing": "Precios"
6  },
7  "hero": {
8    "title": "Crea aplicaciones más rápido",
9    "subtitle": "Lanza funciones que tus usuarios aman",
10    "cta": "Empezar"
11  },
12  "product": {
13    "addToCart": "Añadir al carrito",
14    "itemCount": "Sin artículos | {n} artículo | {n} artículos",
15    "price": "{amount} por mes"
16  }
17}

Consejos de estructura de claves:

  1. Anidar por característica (nav, hero, product) no por página
  2. Usar claves semánticas (addToCart) no posiciones de UI (button_1)
  3. Formato consistente en todos los idiomas

Paso 3: Configurar vue-i18n

Crear src/i18n.ts:

TypeScript
1import { createI18n } from 'vue-i18n';
2import en from './locales/en.json';
3import es from './locales/es.json';
4import fr from './locales/fr.json';
5
6export const i18n = createI18n({
7  legacy: false, // Usar modo Composition API
8  locale: 'en', // Idioma predeterminado
9  fallbackLocale: 'en', // Respaldo si falta traducción
10  messages: {
11    en,
12    es,
13    fr,
14  },
15  // Habilitar advertencias en desarrollo
16  missingWarn: import.meta.env.DEV,
17  fallbackWarn: import.meta.env.DEV,
18});

¿Por qué legacy: false? Esto habilita el soporte de Composition API. Si estás usando Vue 3, siempre establece esto.


Paso 4: Agregar a Tu Aplicación Vue

En main.ts:

TypeScript
1import { createApp } from 'vue';
2import App from './App.vue';
3import { i18n } from './i18n';
4
5const app = createApp(App);
6app.use(i18n);
7app.mount('#app');

Eso es todo. Ahora cada componente tiene acceso a las traducciones.


Paso 5: Usar Traducciones en Componentes

Uso Básico (Composition API)

VUE
1<script setup>
2import { useI18n } from 'vue-i18n';
3
4const { t } = useI18n();
5</script>
6
7<template>
8  <nav>
9    <a href="/">{{ $t('nav.home') }}</a>
10    <a href="/about">{{ $t('nav.about') }}</a>
11    <a href="/pricing">{{ $t('nav.pricing') }}</a>
12  </nav>
13
14  <section>
15    <h1>{{ $t('hero.title') }}</h1>
16    <p>{{ $t('hero.subtitle') }}</p>
17    <button>{{ $t('hero.cta') }}</button>
18  </section>
19</template>

Con Interpolación

Pasar variables usando el segundo argumento:

VUE
1<script setup>
2const { t } = useI18n();
3const price = 29;
4</script>
5
6<template>
7  <p>{{ $t('product.price', { amount: `$${price}` }) }}</p>
8  <!-- Salida: "$29 por mes" -->
9</template>

Traducción:

JSON
1{
2  "product": {
3    "price": "{amount} por mes"
4  }
5}

Paso 6: Manejar Pluralización

Diferentes idiomas tienen diferentes reglas de plural. El inglés tiene 2 formas (1 item, 2 items). El polaco tiene 3. ¡El árabe tiene 6!

vue-i18n maneja esto automáticamente usando la sintaxis de pipe:

JSON
1{
2  "product": {
3    "itemCount": "Sin artículos | {n} artículo | {n} artículos"
4  }
5}

En tu componente:

VUE
1<script setup>
2const { t } = useI18n();
3const count = ref(0);
4</script>
5
6<template>
7  <p>{{ $t('product.itemCount', count) }}</p>
8  <!-- count=0: "Sin artículos" -->
9  <!-- count=1: "1 artículo" -->
10  <!-- count=5: "5 artículos" -->
11</template>

Para plurales complejos, usa formato de mensaje ICU:

JSON
1{
2  "cart": {
3    "summary": "{count, plural, =0 {Tu carrito está vacío} one {# artículo en el carrito} other {# artículos en el carrito}}"
4  }
5}

Paso 7: Formatear Fechas, Números, Monedas

vue-i18n incluye formateadores Intl:

VUE
1<script setup>
2import { useI18n } from 'vue-i18n';
3
4const { t, n, d } = useI18n();
5const price = 1299.99;
6const releaseDate = new Date('2026-01-15');
7</script>
8
9<template>
10  <!-- Formato de números -->
11  <p>{{ n(price, 'currency') }}</p>
12  <!-- en: "$1,299.99" -->
13  <!-- es: "1.299,99 €" -->
14
15  <!-- Formato de fechas -->
16  <p>{{ d(releaseDate, 'long') }}</p>
17  <!-- en: "January 15, 2026" -->
18  <!-- es: "15 de enero de 2026" -->
19</template>

Configurar formatos en i18n.ts:

TypeScript
1export const i18n = createI18n({
2  // ... otras opciones
3  numberFormats: {
4    en: {
5      currency: {
6        style: 'currency',
7        currency: 'USD',
8      },
9    },
10    es: {
11      currency: {
12        style: 'currency',
13        currency: 'EUR',
14      },
15    },
16  },
17  datetimeFormats: {
18    en: {
19      short: { year: 'numeric', month: 'short', day: 'numeric' },
20      long: { year: 'numeric', month: 'long', day: 'numeric' },
21    },
22    es: {
23      short: { year: 'numeric', month: 'short', day: 'numeric' },
24      long: { year: 'numeric', month: 'long', day: 'numeric' },
25    },
26  },
27});

Paso 8: Cambiar Idiomas

Crear un componente selector de idioma:

VUE
1<script setup>
2import { useI18n } from 'vue-i18n';
3
4const { locale, availableLocales } = useI18n();
5
6const changeLanguage = (lang: string) => {
7  locale.value = lang;
8  // Persistir en localStorage
9  localStorage.setItem('user-locale', lang);
10};
11</script>
12
13<template>
14  <div class="language-switcher">
15    <button
16      v-for="lang in availableLocales"
17      :key="lang"
18      :class="{ active: locale === lang }"
19      @click="changeLanguage(lang)"
20    >
21      {{ lang.toUpperCase() }}
22    </button>
23  </div>
24</template>

Detectar idioma preferido del usuario al cargar la aplicación:

TypeScript
1// En i18n.ts
2function getInitialLocale(): string {
3  // 1. Verificar localStorage
4  const saved = localStorage.getItem('user-locale');
5  if (saved) return saved;
6
7  // 2. Verificar idioma del navegador
8  const browserLang = navigator.language.split('-')[0];
9  if (['en', 'es', 'fr'].includes(browserLang)) {
10    return browserLang;
11  }
12
13  // 3. Predeterminado
14  return 'en';
15}
16
17export const i18n = createI18n({
18  locale: getInitialLocale(),
19  // ...
20});

Paso 9: Carga Perezosa de Traducciones

Cargar todos los idiomas por adelantado aumenta el tamaño de tu bundle. Carga perezosa de idiomas bajo demanda:

TypeScript
1// i18n.ts
2import { createI18n } from 'vue-i18n';
3
4export const i18n = createI18n({
5  legacy: false,
6  locale: 'en',
7  fallbackLocale: 'en',
8  messages: {
9    en: {}, // Comenzar vacío
10  },
11});
12
13// Cargar locale dinámicamente
14export async function loadLocale(locale: string) {
15  // Verificar si ya está cargado
16  if (i18n.global.availableLocales.includes(locale)) {
17    return;
18  }
19
20  // Carga perezosa del archivo de locale
21  const messages = await import(`./locales/${locale}.json`);
22  i18n.global.setLocaleMessage(locale, messages.default);
23}
24
25// Cambiar idioma con carga perezosa
26export async function setLocale(locale: string) {
27  await loadLocale(locale);
28  i18n.global.locale.value = locale;
29  localStorage.setItem('user-locale', locale);
30}

Uso:

VUE
1<script setup>
2import { setLocale } from '@/i18n';
3
4const switchLanguage = async (lang: string) => {
5  await setLocale(lang);
6};
7</script>

Resultado: Solo el inglés se carga inicialmente. Español/francés se cargan cuando el usuario los selecciona.


Paso 10: URLs Amigables con SEO con Vue Router

Para SEO, usa URLs basadas en locale:

  • example.com/en/products
  • example.com/es/productos

Configurar Vue Router:

TypeScript
1import { createRouter, createWebHistory } from 'vue-router';
2import { setLocale } from './i18n';
3
4const routes = [
5  {
6    path: '/:locale',
7    component: () => import('./layouts/LocaleLayout.vue'),
8    children: [
9      { path: '', name: 'home', component: () => import('./views/Home.vue') },
10      { path: 'products', name: 'products', component: () => import('./views/Products.vue') },
11      { path: 'about', name: 'about', component: () => import('./views/About.vue') },
12    ],
13  },
14  // Redirigir raíz al locale predeterminado
15  { path: '/', redirect: '/en' },
16];
17
18const router = createRouter({
19  history: createWebHistory(),
20  routes,
21});
22
23// Actualizar locale en cambio de ruta
24router.beforeEach(async (to) => {
25  const locale = to.params.locale as string;
26  if (locale && ['en', 'es', 'fr'].includes(locale)) {
27    await setLocale(locale);
28  }
29});
30
31export default router;

LocaleLayout.vue:

VUE
1<script setup>
2import { useRoute } from 'vue-router';
3import { watchEffect } from 'vue';
4import { useI18n } from 'vue-i18n';
5
6const route = useRoute();
7const { locale } = useI18n();
8
9// Sincronizar locale desde URL
10watchEffect(() => {
11  const urlLocale = route.params.locale as string;
12  if (urlLocale && locale.value !== urlLocale) {
13    locale.value = urlLocale;
14  }
15});
16</script>
17
18<template>
19  <div>
20    <router-view />
21  </div>
22</template>

Agregar etiquetas hreflang para SEO:

TypeScript
1// En tu router o layout
2const addHreflangTags = (locale: string) => {
3  const head = document.querySelector('head');
4  const existingTags = head?.querySelectorAll('link[rel="alternate"]');
5  existingTags?.forEach(tag => tag.remove());
6
7  const locales = ['en', 'es', 'fr'];
8  locales.forEach(lang => {
9    const link = document.createElement('link');
10    link.rel = 'alternate';
11    link.hreflang = lang;
12    link.href = `https://example.com/${lang}${route.path}`;
13    head?.appendChild(link);
14  });
15};

Paso 11: Soporte de TypeScript

Obtener autocompletado para claves de traducción:

TypeScript
1// types/i18n.d.ts
2import en from '@/locales/en.json';
3
4type MessageSchema = typeof en;
5
6declare module 'vue-i18n' {
7  export interface DefineLocaleMessage extends MessageSchema {}
8}

Ahora TypeScript dará error si usas t('invalid.key'):

TypeScript
const { t } = useI18n();
t('nav.home'); // ✅ Válido
t('nav.invalid'); // ❌ Error de TypeScript

Paso 12: Optimización de Producción

1. División de Código

Dividir traducciones por ruta:

TypeScript
1// En lugar de cargar todas las claves
2const messages = await import('./locales/en.json');
3
4// Cargar solo lo que necesitas
5const common = await import('./locales/common/en.json');
6const products = await import('./locales/products/en.json');
7
8i18n.global.setLocaleMessage('en', {
9  ...common.default,
10  ...products.default,
11});

2. Precargar Siguiente Idioma

Si la mayoría de los usuarios cambian de inglés a español, precarga español:

TypeScript
1// Precargar probable siguiente idioma
2if (locale.value === 'en') {
3  setTimeout(() => loadLocale('es'), 2000);
4}

3. Compilar Mensajes

vue-i18n puede precompilar mensajes para tiempo de ejecución más rápido:

Terminal
npm install @intlify/unplugin-vue-i18n -D

vite.config.ts:

TypeScript
1import { defineConfig } from 'vite';
2import vue from '@vitejs/plugin-vue';
3import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
4import { resolve } from 'path';
5
6export default defineConfig({
7  plugins: [
8    vue(),
9    VueI18nPlugin({
10      include: resolve(__dirname, './src/locales/**'),
11      compositionOnly: true,
12    }),
13  ],
14});

Paso 13: Conectar con IntlPull

Gestionar archivos JSON manualmente no escala. Usa un Sistema de Gestión de Traducciones (TMS):

Instalar CLI de IntlPull:

Terminal
npm install -D @intlpullhq/cli

Configurar:

JSON
1// .intlpull.json
2{
3  "projectId": "your-project-id",
4  "sourceLanguage": "en",
5  "targetLanguages": ["es", "fr", "de"],
6  "format": "json",
7  "outputDir": "src/locales"
8}

Flujo de trabajo:

  1. Subir tus traducciones fuente:
Terminal
npx @intlpullhq/cli upload
  1. Enviar a IntlPull para traducción:
Terminal
npx @intlpullhq/cli upload
  1. Descargar traducciones:
Terminal
npx @intlpullhq/cli download
  1. Auto-sincronizar en CI/CD:
YAML
1# .github/workflows/i18n.yml
2name: Sync Translations
3on:
4  schedule:
5    - cron: '0 */6 * * *' # Cada 6 horas
6
7jobs:
8  sync:
9    runs-on: ubuntu-latest
10    steps:
11      - uses: actions/checkout@v3
12      - run: npm install
13      - run: npx @intlpullhq/cli download
14      - run: |
15          git config user.name "i18n-bot"
16          git add src/locales
17          git commit -m "chore: sync translations" || exit 0
18          git push

Beneficios:

  • ✅ Los traductores trabajan en una UI, no en JSON
  • ✅ La memoria de traducción ahorra costos
  • ✅ Pre-traducción IA para nuevas claves
  • ✅ Flujos de trabajo de aprobación para calidad
  • ✅ Cero participación del desarrollador después de la configuración

Errores Comunes

1. Olvidar Esperar Cargas Perezosas

TypeScript
1// ❌ Malo - el locale puede no estar cargado
2locale.value = 'es';
3
4// ✅ Bueno - esperar por ello
5await setLocale('es');

2. Cadenas Codificadas en Componentes

Ejecutar una verificación de lint:

Terminal
npx eslint . --rule 'no-literal-string: error'

O usar la verificación de estado de IntlPull:

Terminal
npx @intlpullhq/cli status

3. No Manejar Claves Faltantes

Siempre establecer un respaldo:

TypeScript
1createI18n({
2  fallbackLocale: 'en',
3  missing: (locale, key) => {
4    console.warn(`Traducción faltante: ${key} en ${locale}`);
5    return key; // Mostrar clave en lugar de vacío
6  },
7});

4. Ignorar Idiomas RTL

Si soportas árabe/hebreo, agrega CSS RTL:

VUE
1<template>
2  <div :dir="locale === 'ar' || locale === 'he' ? 'rtl' : 'ltr'">
3    <!-- Tu aplicación -->
4  </div>
5</template>

Bonus de Nuxt 3

Si estás usando Nuxt 3, usa @nuxtjs/i18n:

Terminal
npm install @nuxtjs/i18n@next

nuxt.config.ts:

TypeScript
1export default defineNuxtConfig({
2  modules: ['@nuxtjs/i18n'],
3  i18n: {
4    locales: [
5      { code: 'en', file: 'en.json' },
6      { code: 'es', file: 'es.json' },
7      { code: 'fr', file: 'fr.json' },
8    ],
9    defaultLocale: 'en',
10    strategy: 'prefix', // URLs: /en/products, /es/productos
11    langDir: 'locales/',
12    lazy: true, // Carga perezosa de traducciones
13    detectBrowserLanguage: {
14      useCookie: true,
15      cookieKey: 'i18n_redirected',
16    },
17  },
18});

Uso en componentes:

VUE
<script setup>
const { t, locale, setLocale } = useI18n();
</script>

Misma API que vue-i18n, pero con SSR, enrutamiento automático y SEO integrado.


Benchmarks de Rendimiento

Comparación de tamaño de bundle (build de producción):

ConfiguraciónBundle InicialDespués de Carga Perezosa
Todos los locales eager450 KB450 KB
Carga perezosa de locales180 KB230 KB (cuando se necesita)
Mejora60% más pequeño~50% más pequeño

Rendimiento en tiempo de ejecución:

  • Búsqueda de traducción: < 0.1ms
  • Cambio de idioma: < 5ms (en caché), < 100ms (carga perezosa)
  • Impacto insignificante en el rendimiento de la aplicación

Lista de Verificación: ¿Está Tu Aplicación Vue Lista para i18n?

  • ✅ Todas las cadenas de cara al usuario usan la función t()
  • ✅ Sin concatenación de cadenas (usar interpolación)
  • ✅ Los plurales usan sintaxis de pipe o formato ICU
  • ✅ Fechas/números usan formateadores d() y n()
  • ✅ Traducciones cargadas de forma perezosa por ruta
  • ✅ SEO: etiquetas hreflang + URLs de locale
  • ✅ Tipos de TypeScript para autocompletado
  • ✅ Locale de respaldo configurado
  • ✅ CI/CD sincroniza traducciones automáticamente
  • ✅ TMS (IntlPull) integrado para escala

Próximos Pasos

Ahora tienes una configuración de localización de Vue lista para producción. Aquí está qué hacer a continuación:

  1. Agregar más idiomas: Solo crea nuevos archivos JSON y agrégalos a availableLocales
  2. Configurar IntlPull: Automatizar flujo de trabajo de traducción
  3. Monitorear cobertura: Usar análisis de IntlPull para rastrear progreso de traducción
  4. Optimizar bundle: División de código por ruta si traducciones > 100KB
  5. Prueba A/B: Medir tasas de conversión por idioma

¿Quieres saltarte el trabajo manual? Prueba la plantilla inicial de Vue de IntlPull. Viene con vue-i18n preconfigurado, flujos de trabajo CI/CD y actualizaciones OTA.


Lectura Adicional

Construir aplicaciones Vue multilingües no tiene que ser doloroso. Con vue-i18n y el flujo de trabajo correcto, puedes enviar productos globales más rápido.

¿Preguntas? Únete a nuestro Discord o contáctanos.

Tags
vue
vue-i18n
localization
internationalization
vue3
composition-api
IntlPull Team
IntlPull Team
Engineering

Building tools to help teams ship products globally. Follow us for more insights on localization and i18n.