Centro de ayuda

Angular i18next

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

¿Por qué i18next para Angular?

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

  • Independiente del framework: funciona perfectamente con Angular, React, Vue y otros.
  • Potente soporte para interpolación, pluralización y contexto.
  • Soporte para carga diferida (lazy loading) 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 del framework. No necesitas angular-i18next ni ningún otro envoltorio; simplemente usa i18next directamente en tu aplicación Angular.

Carga de recursos de traducción

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

Importaciones dinámicas (Recomendado)

El uso de importaciones dinámicas aprovecha la división de código de Webpack para cargar las 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 prerenderizado.
  • No requiere 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>> = {};

  // Load all namespaces for the given language in parallel
  await Promise.all(
    NAMESPACES.map(async (namespace) => {
      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 de backend HTTP carga las traducciones a través de solicitudes HTTP desde un servidor o CDN. Esto es útil cuando deseas actualizar las traducciones sin reconstruir tu aplicación.

Ventajas:

  • Actualiza las traducciones sin necesidad de volver a desplegar.
  • Carga traducciones desde una CDN externa.
  • Útil para la gestión de traducciones en tiempo de ejecución.

Desventajas:

  • Las solicitudes HTTP adicionales ralentizan la carga inicial.
  • Requiere conectividad de red.
  • Configuración de 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 una configuración adicional para SSR.

¿Qué enfoque elegir?

Usa importaciones dinámicas cuando:

  • Busques un tamaño de paquete y rendimiento óptimos.
  • Las traducciones formen parte de tu proceso de compilación.
  • Necesites soporte para SSR/prerenderizado.

Usa backend HTTP cuando:

  • Necesites actualizar las traducciones sin volver a desplegar.
  • Las traducciones se gestionen externamente.
  • Dispongas de una 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 renderice.

Uso de 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 renderizarse. 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 evita que aparezcan claves de traducción 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é es importante esto?

  • La función se ejecuta en el contexto de inyección durante el inicio de la aplicación.
  • Evita 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, prerenderizado 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 prerenderizado.
  • Usa fallbackLng para manejar las traducciones faltantes con elegancia.
  • Configura espacios de nombres (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 las 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 pure: 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í misma.

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 dar formato y combinar múltiples valores en una sola cadena de traducción.

En el código del 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 aplicación: '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 (p. ej., common.json, auth.json, dashboard.json) para una mejor mantenibilidad.

Maneja la pluralización

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

Espera a la inicialización

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

Seguridad de tipos para claves de traducción

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

Define un tipo que extraiga todas las claves posibles 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 tipos en las 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 con IntelliSense y errores en tiempo de compilación para claves no válidas tanto en el código TypeScript como en las 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 las claves de traducción con seguridad de tipos:

  • 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, detectando errores tipográficos antes de la 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 por completo durante la compilación.
  • Autogenerado a partir de tus archivos JSON reales: no se requiere mantenimiento manual de tipos.

Detección y cambio de idioma

Implementa una 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 aplicación se inicia comprobando 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 comprueba primero localStorage (preferencia del usuario), luego recurre al idioma del navegador (navigator.language) y finalmente utiliza el idioma predeterminado de la aplicación.

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');

    await Promise.all(
      NAMESPACES.map(async (namespace) => {
        const currentResources = i18next.default.getResourceBundle(
          lang,
          namespace,
        );
        // Check if any namespace is missing for this language
        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() comprueba si los recursos ya están cargados usando getResourceBundle(). Si faltan, los importa dinámicamente y los añade usando addResourceBundle(). Esto evita solicitudes de red redundantes mientras asegura que todas las traducciones necesarias estén disponibles.

Detalles clave de implementación:

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

Automatiza la traducción con IA

Gestionar las traducciones manualmente en varios idiomas consume mucho tiempo y es propenso a errores. Ahí es donde entra la localización potenciada por IA.

¿Por qué l10n.dev?

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

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

Flujo de trabajo simple

  1. Exporta tu archivo JSON de idioma base (p. ej., en/common.json)
  2. Súbelo a l10n.dev y selecciona los idiomas de destino.
  3. La IA traduce con conciencia del contexto y formato adecuado.
  4. Descarga los archivos traducidos listos para usar en tu aplicación 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?