Centro Assistenza

Angular i18next

L'internazionalizzazione (i18n) è essenziale per costruire applicazioni Angular globali. Questa guida ti mostra come implementare i18next in Angular con un caricamento corretto delle risorse, inizializzazione e modelli di utilizzo.

Perché i18next per Angular?

i18next è uno dei framework i18n più popolari, offrendo funzionalità potenti e flessibilità:

  • Indipendente dal framework - funziona perfettamente con Angular, React, Vue e altri.
  • Potente supporto per interpolazione, pluralizzazione e contesto.
  • Supporto per caricamento lazy e suddivisione del codice.
  • Supporto TypeScript con sicurezza di tipo.
  • Ampio ecosistema con plugin ed estensioni.

Installazione

Installa i18next usando npm o yarn:

npm install i18next

Ecco fatto! i18next è una libreria autonoma senza dipendenze specifiche per framework. Non hai bisogno di angular-i18next o di alcun altro wrapper - usa semplicemente i18next direttamente nella tua applicazione Angular.

Caricamento delle Risorse di Traduzione

Ci sono due approcci principali per caricare risorse di traduzione in Angular con i18next:

Importazioni Dinamiche (Consigliato)

Utilizzare importazioni dinamiche sfrutta la suddivisione del codice di Webpack per caricare le traduzioni su richiesta. Questo approccio è ideale per applicazioni con molte lingue o file di traduzione di grandi dimensioni.

Vantaggi:

  • Ottimizzazione delle dimensioni del pacchetto - carica solo le traduzioni necessarie.
  • Migliore prestazioni - tempo di caricamento iniziale più veloce.
  • Funziona perfettamente con SSR e prerendering.
  • Nessuna richiesta HTTP aggiuntiva necessaria.

Esempio: Caricamento con Importazioni Dinamiche

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`);
}

Questo approccio importa file di traduzione come moduli ES, consentendo a Webpack di suddividerli automaticamente in chunk separati che vengono caricati su richiesta.

i18next-http-backend

Il plugin HTTP backend carica le traduzioni tramite richieste HTTP da un server o CDN. Questo è utile quando vuoi aggiornare le traduzioni senza ricompilare la tua app.

Vantaggi:

  • Aggiorna le traduzioni senza ridistribuzione.
  • Carica traduzioni da un CDN esterno.
  • Utile per la gestione delle traduzioni in tempo reale.

Svantaggi:

  • Richieste HTTP aggiuntive rallentano il caricamento iniziale.
  • Richiede connettività di rete.
  • Configurazione SSR più complessa.

Esempio: Caricamento con HTTP Backend

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,
});

Questo approccio recupera i file di traduzione da un server durante l'esecuzione. Nota che questo richiede una configurazione aggiuntiva per SSR.

Quale Approccio Scegliere?

Usa Importazioni Dinamiche quando:

  • Vuoi dimensioni del pacchetto e prestazioni ottimali.
  • Le traduzioni fanno parte del tuo processo di build.
  • Hai bisogno di supporto SSR/prerendering.

Usa HTTP Backend quando:

  • Hai bisogno di aggiornare le traduzioni senza ridistribuzione.
  • Le traduzioni sono gestite esternamente.
  • Hai un CDN per servire i file di traduzione.

Inizializzazione Corretta di i18next

Inizializzare correttamente i18next è cruciale per evitare errori di runtime e garantire che le traduzioni siano disponibili quando la tua app viene renderizzata.

Utilizzare provideAppInitializer

La funzione provideAppInitializer di Angular (introdotta in Angular 18) garantisce che i18next sia completamente inizializzato prima che l'applicazione inizi a renderizzare. La funzione fornita viene eseguita durante l'avvio dell'app, e l'inizializzazione non è completata fino a quando la Promise non si risolve. Questo previene che le chiavi di traduzione appaiano al posto del testo tradotto.

Esempio: Configurazione dell'App Initializer

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

Perché Questo È Importante:

  • La funzione viene eseguita nel contesto di iniezione durante l'avvio dell'applicazione.
  • Previene il flickering o la visualizzazione delle chiavi di traduzione al caricamento iniziale.
  • Garantisce che le traduzioni siano disponibili in tutti i componenti fin dall'inizio.
  • Funziona correttamente con SSR, prerendering e rendering lato client.

Funzione di Inizializzazione di i18next

La funzione di inizializzazione gestisce il caricamento delle risorse e la configurazione delle impostazioni di i18next.

Esempio: Inizializzazione di 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,
    },
  });
}

Punti Chiave:

  • Carica solo la lingua corrente nel browser per prestazioni ottimali.
  • Carica tutte le lingue durante SSR per supporto al prerendering.
  • Usa fallbackLng per gestire le traduzioni mancanti in modo elegante.
  • Configura i namespace per una migliore organizzazione.

Pipe di Traduzione

La pipe di traduzione di Angular fornisce un modo pulito per utilizzare le traduzioni nei template.

Implementazione della Pipe

Esempio: Pipe di Traduzione

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

Il pipe è contrassegnato come puro: false per garantire che si aggiorni quando la lingua cambia. Questo è importante perché i cambiamenti di lingua non modificano la chiave di traduzione stessa.

Utilizzo del Translation Pipe

Nei Template:

<!-- 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}}." -->

L'interpolazione consente di inserire valori dinamici nelle tue traduzioni. Puoi passare variabili, utilizzare i pipe di Angular per la formattazione e combinare più valori in una singola stringa di traduzione.

Nel Codice 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' });
}

Per l'uso nei template, la sintassi del pipe è pulita e leggibile. Per l'accesso programmatico, chiama t() direttamente. Questo è utile per mostrare notifiche, impostare titoli dinamici o gestire traduzioni nella logica del componente.

Migliori Pratiche per Angular i18next

Usa Chiavi Significative

Usa la notazione a punti che riflette la struttura della tua app: 'auth:login.title' invece di 'loginTitle' o testo grezzo.

Organizza con Namespace

Dividi le traduzioni in namespace logici (ad es., common.json, auth.json, dashboard.json) per una migliore manutenibilità.

Gestisci la Pluralizzazione

Usa il supporto integrato per la pluralizzazione di i18next per gestire correttamente le forme singolari/plurali in tutte le lingue.

Aspetta l'Inizializzazione

Assicurati sempre che i18next sia inizializzato prima di rendere la tua app utilizzando provideAppInitializer() per eseguire l'inizializzazione durante l'avvio dell'app.

Sicurezza dei Tipi per le Chiavi di Traduzione

Usa TypeScript per estrarre le chiavi di traduzione direttamente dai tuoi file JSON per una completa sicurezza dei tipi. Invece di mantenere manualmente le definizioni dei tipi, sfrutta typeof di TypeScript e le importazioni dinamiche per generare automaticamente i tipi dai tuoi file di traduzione effettivi.

Definisci un tipo che estrae tutte le possibili chiavi dai tuoi file di traduzione JSON utilizzando un tipo helper NestedKeys ricorsivo.

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 il metodo transform del pipe per accettare TranslationKey invece di string. Questo fornisce sicurezza dei tipi nei template e garantisce che possano essere utilizzate solo chiavi di traduzione valide.

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

Ora ottieni il completamento automatico completo di IntelliSense e errori di compilazione per chiavi non valide sia nel codice TypeScript che nei template.

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

Vantaggi delle Chiavi di Traduzione Sicure:

  • Completamento automatico nel tuo IDE per tutte le chiavi di traduzione disponibili in tutti i namespace.
  • Errori di compilazione se usi una chiave non esistente, catturando errori di battitura prima dell'esecuzione.
  • Supporto per il refactoring - rinomina le chiavi in modo sicuro in tutto il tuo codice.
  • Zero sovraccarico di runtime - i tipi vengono completamente rimossi durante la compilazione.
  • Generato automaticamente dai tuoi file JSON effettivi - nessuna manutenzione manuale dei tipi richiesta.

Rilevamento e Cambio di Lingua

Implementa un rilevamento intelligente della lingua e consenti agli utenti di cambiare lingua a runtime con caricamento automatico delle risorse.

Determina la lingua dell'utente da utilizzare quando la tua app si avvia controllando più fonti in ordine di priorità.

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;
}

Questo approccio controlla prima localStorage (preferenza dell'utente), poi torna alla lingua del browser (navigator.language), e infine predefinisce alla lingua predefinita dell'app.

Cambia lingua a runtime mentre carica automaticamente le risorse di traduzione su richiesta.

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

Quando si usano importazioni dinamiche, le traduzioni vengono caricate su richiesta. Il metodo switchLanguage() controlla se le risorse sono già caricate usando getResourceBundle(). Se mancano, le importa dinamicamente e le aggiunge usando addResourceBundle(). Questo previene richieste di rete ridondanti garantendo che tutte le traduzioni necessarie siano disponibili.

Dettagli Chiave di Implementazione:

  • Controlla prima localStorage per ripristinare la lingua preferita dell'utente.
  • Usa navigator.language come fallback per la preferenza di lingua del browser.
  • Carica le risorse mancanti dinamicamente con addResourceBundle().

Automatizza la Traduzione con l'AI

Gestire le traduzioni manualmente attraverso più lingue è dispendioso in termini di tempo e soggetto a errori. È qui che entra in gioco la localizzazione potenziata dall'AI.

Perché l10n.dev?

l10n.dev è un servizio di traduzione potenziato dall'AI specificamente progettato per i file JSON di i18next:

  • Preserva la struttura JSON, le chiavi e gli oggetti annidati.
  • Mantiene correttamente i segnaposto come {{name}}, {{count}}.
  • Genera automaticamente forme plurali per tutte le lingue.
  • Gestisce il contesto e l'interpolazione in modo intelligente.
  • Supporta tutte le funzionalità di i18next, inclusi i namespace.

Flusso di Lavoro Semplice

  1. Esporta il tuo file JSON della lingua base (ad es., en/common.json)
  2. Carica su l10n.dev e seleziona le lingue target.
  3. L'AI traduce con consapevolezza del contesto e formattazione corretta.
  4. Scarica i file tradotti pronti per essere utilizzati nella tua app Angular.

Risparmia ore di lavoro di traduzione manuale ed evita errori comuni come segnaposto rotti o forme plurali errate.

Pronto a semplificare il tuo flusso di lavoro Angular i18n?