Centro de Ayuda

Angular i18next

La internacionalización (i18n) es esencial para construir aplicaciones Angular globales. Esta guía te muestra cómo implementar i18next en Angular con carga de recursos adecuada, inicialización y patrones de uso.

¿Por qué i18next para Angular?

i18next es uno de los frameworks de i18n más populares, ofreciendo potentes características y flexibilidad:

  • Independiente del framework - funciona sin problemas con Angular, React, Vue y otros.
  • Potente interpolación, pluralización y soporte de contexto.
  • Soporte para carga diferida y división de código.
  • Soporte para TypeScript con seguridad de tipos.
  • Gran ecosistema con plugins y extensiones.

Instalación

Instala i18next usando npm o yarn:

npm install i18next

¡Eso es todo! i18next es una biblioteca independiente sin dependencias específicas de framework. No necesitas angular-i18next ni ningún otro wrapper - solo usa i18next directamente en tu aplicación Angular.

Carga de Recursos de Traducción

Hay dos enfoques principales para cargar recursos de traducción en Angular con i18next:

Importaciones Dinámicas (Recomendado)

Usar importaciones dinámicas aprovecha la división de código de Webpack para cargar traducciones bajo demanda. Este enfoque es ideal para aplicaciones con muchos idiomas o archivos de traducción grandes.

Ventajas:

  • Optimización del tamaño del paquete - carga solo las traducciones necesarias.
  • Mejor rendimiento - tiempo de carga inicial más rápido.
  • Funciona perfectamente con SSR y prerendering.
  • No se requieren solicitudes HTTP adicionales.

Ejemplo: Carga con Importaciones Dinámicas

private async loadLanguageResources(
  lang: string,
): Promise<Record<string, Record<string, unknown>>> {
  const languageResources: Record<string, Record<string, unknown>> = {};

  for (const namespace of NAMESPACES) {
    const translationModule = await this.loadJson(lang, namespace);
    languageResources[namespace] = translationModule.default;
  }
  return languageResources;
}

private async loadJson(
  language: string,
  namespace: string,
): Promise<{ default: Record<string, unknown> }> {
  // Webpack automatically code-splits this into separate chunks
  return import(`../../i18n/${language}/${namespace}.json`);
}

Este enfoque importa archivos de traducción como módulos ES, permitiendo que Webpack los divida automáticamente en fragmentos separados que se cargan bajo demanda.

i18next-http-backend

El plugin backend HTTP carga traducciones a través de solicitudes HTTP desde un servidor o CDN. Esto es útil cuando quieres actualizar traducciones sin reconstruir tu aplicación.

Ventajas:

  • Actualiza traducciones sin necesidad de redeploy.
  • Carga traducciones desde un CDN externo.
  • Útil para gestión de traducciones en tiempo de ejecución.

Desventajas:

  • Solicitudes HTTP adicionales ralentizan la carga inicial.
  • Requiere conectividad a la red.
  • Configuración SSR más compleja.

Ejemplo: Carga con Backend HTTP

import HttpBackend from 'i18next-http-backend';

await init({
  lng: initialLanguage,
  fallbackLng: DEFAULT_LANGUAGE,
  backend: {
    loadPath: '/assets/i18n/{{lng}}/{{ns}}.json',
  },
  use: [HttpBackend],
  ns: NAMESPACES,
  defaultNS: DEFAULT_NAMESPACE,
});

Este enfoque obtiene archivos de traducción de un servidor en tiempo de ejecución. Ten en cuenta que esto requiere configuración adicional para SSR.

¿Qué Enfoque Elegir?

Usa Importaciones Dinámicas cuando:

  • Quieres un tamaño de paquete y rendimiento óptimos.
  • Las traducciones son parte de tu proceso de construcción.
  • Necesitas soporte para SSR/prerendering.

Usa Backend HTTP cuando:

  • Necesitas actualizar traducciones sin redeploy.
  • Las traducciones se gestionan externamente.
  • Tienes un CDN para servir archivos de traducción.

Inicialización Adecuada de i18next

Inicializar i18next correctamente es crucial para evitar errores en tiempo de ejecución y asegurar que las traducciones estén disponibles cuando tu aplicación se renderiza.

Usando provideAppInitializer

La función provideAppInitializer de Angular (introducida en Angular 18) asegura que i18next esté completamente inicializado antes de que la aplicación comience a renderizar. La función proporcionada se ejecuta durante el arranque de la aplicación, y la inicialización no se completa hasta que la Promesa se resuelve. Esto previene que las claves de traducción aparezcan en lugar del texto traducido.

Ejemplo: Configuración del Inicializador de la Aplicación

export const appConfig: ApplicationConfig = {
  providers: [
    // ... other providers
    provideAppInitializer(initializeI18n),
    // ... more providers
  ],
};

Por qué esto es importante:

  • La función se ejecuta en el contexto de inyección durante el inicio de la aplicación.
  • Previene parpadeos o mostrar claves de traducción en la carga inicial.
  • Asegura que las traducciones estén disponibles en todos los componentes desde el principio.
  • Funciona correctamente con SSR, prerendering y renderizado del lado del cliente.

Función de Inicialización de i18next

La función de inicialización maneja la carga de recursos y la configuración de los ajustes de i18next.

Ejemplo: Inicialización de i18next

public async initializeI18n(): Promise<void> {
  const initialLanguage = this.getInitialLanguage();
  const resources: Record<string, Record<string, Record<string, unknown>>> = {};

  if (isPlatformBrowser(this.platformId)) {
    // Browser: Only load the specific language needed
    const languageResources = await this.loadLanguageResources(initialLanguage);
    resources[initialLanguage] = languageResources;
  } else {
    // SSR: Load all languages for prerendering
    for (const lang of SUPPORTED_LANGUAGES) {
      const languageResources = await this.loadLanguageResources(lang);
      resources[lang] = languageResources;
    }
  }

  await init({
    lng: initialLanguage,
    fallbackLng: DEFAULT_LANGUAGE,
    resources,
    ns: NAMESPACES,
    defaultNS: DEFAULT_NAMESPACE,
    interpolation: {
      escapeValue: false,
    },
  });
}

Puntos Clave:

  • Carga solo el idioma actual en el navegador para un rendimiento óptimo.
  • Carga todos los idiomas durante SSR para soporte de prerendering.
  • Usa fallbackLng para manejar traducciones faltantes de manera elegante.
  • Configura namespaces para una mejor organización.

Pipe de Traducción

El pipe de traducción de Angular proporciona una forma limpia de usar traducciones en plantillas.

Implementación del Pipe

Ejemplo: Pipe de Traducción

import { Pipe, PipeTransform } from '@angular/core';
import { t } from 'i18next';

@Pipe({
  name: 't',
  standalone: true,
  pure: false, // Need to update when language changes
})
export class TranslatePipe implements PipeTransform {
  transform(key: string, options?: Record<string, unknown>): string {
    return t(key, options);
  }
}

El pipe está marcado como puro: false para asegurar que se actualice cuando cambie el idioma. Esto es importante porque los cambios de idioma no modifican la clave de traducción en sí.

Uso del Pipe de Traducción

En Plantillas:

<!-- With namespace -->
<p>{{ "common:welcomeMessage" | t }}</p>

<!-- With nested keys -->
<p>{{ "dashboard.title" | t }}</p>

<!-- Simple interpolation -->
<p>{{ "greeting" | t: { name: userName } }}</p>
<!-- Result: "Hello, John!" from greeting: "Hello, {{name}}!" -->

<!-- With Angular pipes -->
<p>{{ "price" | t: { amount: 29.99 | currency } }}</p>
<!-- Result: "Price: $29.99" from: "Price: {{amount}}" -->

<!-- Multiple variables -->
<p>{{ "updated" | t: { date: lastModified | date, user: currentUser } }}</p>
<!-- Result: "Updated on Jan 15, 2024 by Alice" from: "Updated on {{date}} by {{user}}." -->

La interpolación te permite insertar valores dinámicos en tus traducciones. Puedes pasar variables, usar pipes de Angular para formatear, y combinar múltiples valores en una sola cadena de traducción.

En Código de Componente:

import { Component } from '@angular/core';
import { t } from 'i18next';

@Component({
  selector: 'app-dashboard',
  template: `
    <h1>{{ title }}</h1>
    <p>{{ welcomeMsg }}</p>
  `,
})
export class DashboardComponent {
  title = t('dashboard.title');
  welcomeMsg = t('greeting', { name: 'John' });
}

Para el uso en plantillas, la sintaxis del pipe es limpia y legible. Para el acceso programático, llama a t() directamente. Esto es útil para mostrar notificaciones, establecer títulos dinámicos, o manejar traducciones en la lógica del componente.

Mejores Prácticas para Angular i18next

Usa Claves Significativas

Usa notación de puntos que refleje la estructura de tu app: 'auth:login.title' en lugar de 'loginTitle' o texto sin procesar.

Organiza con Espacios de Nombres

Divide las traducciones en espacios de nombres lógicos (por ejemplo, common.json, auth.json, dashboard.json) para una mejor mantenibilidad.

Maneja la Pluralización

Usa el soporte de pluralización incorporado de i18next para manejar correctamente las formas singular/plural en todos los idiomas.

Espera la Inicialización

Asegúrate siempre de que i18next esté inicializado antes de renderizar tu app usando provideAppInitializer() para ejecutar la inicialización durante el arranque de la app.

Seguridad de Tipo para Claves de Traducción

Usa TypeScript para extraer claves de traducción directamente de tus archivos JSON para una seguridad de tipo completa. En lugar de mantener manualmente definiciones de tipo, aprovecha typeof de TypeScript e importaciones dinámicas para generar automáticamente tipos a partir de tus archivos de traducción reales.

Define un tipo que extraiga todas las posibles claves de tus archivos de traducción JSON usando un tipo auxiliar NestedKeys recursivo.

import common from './en/common.json'; // defaultNS: DEFAULT_NAMESPACE,
import auth from './en/auth.json'; // namespaces

type NestedKeys<T, Prefix extends string = ''> = T extends object
  ? {
      [K in keyof T & (string | number)]: T[K] extends object
        ?
            | NestedKeys<T[K], `${Prefix}${K & (string | number)}.`>
            | `${Prefix}${K & (string | number)}`
        : `${Prefix}${K & (string | number)}`;
    }[keyof T & (string | number)]
  : never;

export type TranslationKey =
  | `common:${NestedKeys<typeof common>}`
  | `auth:${NestedKeys<typeof auth>}`;

Cambia el método transform del pipe para aceptar TranslationKey en lugar de string. Esto proporciona seguridad de tipo en plantillas y asegura que solo se puedan usar claves de traducción válidas.

import { Pipe, PipeTransform } from '@angular/core';
import { t } from 'i18next';

import { TranslationKey } from '../../i18n/translation-key';

@Pipe({
  name: 't',
  standalone: true,
  pure: false, // Need to update when language changes
})
export class TranslatePipe implements PipeTransform {
  transform(key: TranslationKey, options?: Record<string, unknown>): string {
    return t(key, options);
  }
}

Ahora obtienes autocompletado completo de IntelliSense y errores en tiempo de compilación para claves inválidas tanto en código TypeScript como en plantillas.

t('common:greeting');       // ✅ Autocomplete
t('auth:login.title');      // ✅ Valid
t('invalid.key');           // ❌ IDE shows error

{{ "common:greeting" | t }} // ✅ Valid in templates
{{ "invalid.key" | t }}     // ❌ IDE shows error

Beneficios de Claves de Traducción Seguras en Tipo:

  • Autocompletado en tu IDE para todas las claves de traducción disponibles en todos los espacios de nombres.
  • Errores en tiempo de compilación si usas una clave inexistente, capturando errores tipográficos antes de tiempo de ejecución.
  • Soporte para refactorización - renombra claves de forma segura en toda tu base de código.
  • Cero sobrecarga en tiempo de ejecución - los tipos se eliminan completamente durante la compilación.
  • Generados automáticamente a partir de tus archivos JSON reales - no se requiere mantenimiento manual de tipos.

Detección y Cambio de Idioma

Implementa detección de idioma inteligente y permite a los usuarios cambiar de idioma en tiempo de ejecución con carga automática de recursos.

Determina el idioma del usuario a usar cuando tu app se inicia verificando múltiples fuentes en orden de prioridad.

private getInitialLanguage(): string {
  if (isPlatformBrowser(this.platformId)) {
    // Try to get language from localStorage (user preference)
    const savedLang = localStorage.getItem('language');
    if (savedLang) {
      return savedLang;
    }

    // Fall back to browser language
    return navigator.language ?? DEFAULT_LANGUAGE;
  }

  // Default language for SSR
  return DEFAULT_LANGUAGE;
}

Este enfoque verifica localStorage primero (preferencia del usuario), luego retrocede al idioma del navegador (navigator.language), y finalmente se establece en el idioma predeterminado de la app.

Cambia de idioma en tiempo de ejecución mientras cargas automáticamente los recursos de traducción bajo demanda.

public async switchLanguage(
  language: string
): Promise<void> {
  // In browser, check if we need to load the translation first
  if (isPlatformBrowser(this.platformId)) {
    const i18next = await import('i18next');

    // Check if any namespace is missing for this language
    for (const namespace of NAMESPACES) {
      const currentResources = i18next.default.getResourceBundle(lang, namespace);

      if (!currentResources) {
        // Translation not loaded yet, load it dynamically
        const translationModule = await this.loadJson(lang, namespace);
        i18next.default.addResourceBundle(
          lang,
          namespace,
          translationModule.default
        );
      }
    }
  }

  // Switch to the new language
  await changeLanguage(lang);

  // Save preference to localStorage
  if (isPlatformBrowser(this.platformId)) {
    localStorage.setItem('language', lang);
  }
}

Al usar importaciones dinámicas, las traducciones se cargan bajo demanda. El método switchLanguage() verifica si los recursos ya están cargados usando getResourceBundle(). Si faltan, los importa dinámicamente y los agrega usando addResourceBundle(). Esto previene solicitudes de red redundantes mientras asegura que todas las traducciones necesarias estén disponibles.

Detalles Clave de Implementación:

  • Verifica localStorage primero para restaurar el idioma preferido del usuario.
  • Usa navigator.language como respaldo para la preferencia de idioma del navegador.
  • Carga recursos faltantes dinámicamente con addResourceBundle().

Automatiza la Traducción con IA

Gestionar traducciones manualmente en múltiples idiomas es una tarea que consume tiempo y propensa a errores. Ahí es donde entra la localización impulsada por IA.

¿Por qué l10n.dev?

l10n.dev es un servicio de traducción impulsado por IA diseñado específicamente para archivos JSON de i18next:

  • Preserva la estructura JSON, claves y objetos anidados.
  • Mantiene los marcadores de posición como {{name}}, {{count}} correctamente.
  • Genera automáticamente formas plurales para todos los idiomas.
  • Maneja el contexto y la interpolación de manera inteligente.
  • Soporta todas las características de i18next, incluidos los espacios de nombres.

Flujo de Trabajo Simple

  1. Exporta tu archivo JSON del idioma base (por ejemplo, en/common.json)
  2. Sube a l10n.dev y selecciona los idiomas de destino.
  3. La IA traduce con conciencia del contexto y formateo adecuado.
  4. Descarga archivos traducidos listos para usar en tu app de Angular.

Ahorra horas de trabajo de traducción manual y evita errores comunes como marcadores de posición rotos o formas plurales incorrectas.

¿Listo para optimizar tu flujo de trabajo de i18n en Angular?