Centro assistenza

Angular i18next

L'internazionalizzazione (i18n) è essenziale per creare applicazioni Angular globali. Questa guida ti mostra come implementare i18next in Angular con il corretto caricamento delle risorse, l'inizializzazione e i pattern di utilizzo.

Perché i18next per Angular?

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

  • Agnostico rispetto al framework: funziona perfettamente con Angular, React, Vue e altri.
  • Potente interpolazione, pluralizzazione e supporto al contesto.
  • Supporto per caricamento lazy e suddivisione del codice.
  • Supporto TypeScript con sicurezza dei tipi.
  • Ampio ecosistema con plugin ed estensioni.

Installazione

Installa i18next usando npm o yarn:

npm install i18next

Tutto qui! i18next è una libreria standalone senza dipendenze specifiche del framework. Non hai bisogno di angular-i18next o di altri wrapper: usa semplicemente i18next direttamente nella tua applicazione Angular.

Caricamento delle risorse di traduzione

Esistono due approcci principali per caricare le risorse di traduzione in Angular con i18next:

Importazioni dinamiche (consigliato)

L'uso delle 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 della dimensione del bundle: carica solo le traduzioni necessarie.
  • Prestazioni migliori: 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>> = {};

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

Questo approccio importa i 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. È utile quando vuoi aggiornare le traduzioni senza ricostruire la tua app.

Vantaggi:

  • Aggiorna le traduzioni senza ridistribuzione.
  • Carica le traduzioni da una CDN esterna.
  • Utile per la gestione delle traduzioni a runtime.

Svantaggi:

  • Le 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 a runtime. Nota che ciò richiede una configurazione aggiuntiva per SSR.

Quale approccio scegliere?

Usa le importazioni dinamiche quando:

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

Usa l'HTTP backend quando:

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

Corretta inizializzazione di i18next

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

Utilizzo di provideAppInitializer

La funzione provideAppInitializer di Angular (introdotta in Angular 18) assicura che i18next sia completamente inizializzato prima che l'applicazione inizi il rendering. La funzione fornita viene eseguita durante il bootstrap dell'app e l'inizializzazione non si completa finché la Promise non viene risolta. Ciò impedisce 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é è importante:

  • La funzione viene eseguita nel contesto di iniezione durante l'avvio dell'applicazione.
  • Previene lo sfarfallio o la visualizzazione delle chiavi di traduzione al caricamento iniziale.
  • Assicura 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 il supporto al prerendering.
  • Usa fallbackLng per gestire le traduzioni mancanti con eleganza.
  • 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);
  }
}

La pipe è contrassegnata come pure: false per assicurare che si aggiorni quando la lingua cambia. Questo è importante perché i cambi di lingua non modificano la chiave di traduzione stessa.

Utilizzo della pipe di traduzione

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 ti consente di inserire valori dinamici nelle tue traduzioni. Puoi passare variabili, usare 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'utilizzo nei template, la sintassi della pipe è pulita e leggibile. Per l'accesso programmatico, chiama t() direttamente. Questo è utile per mostrare notifiche, impostare titoli dinamici o gestire le traduzioni nella logica del componente.

Best practice per Angular i18next

Usa chiavi significative

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

Organizza con i namespace

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

Gestisci la pluralizzazione

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

Attendi l'inizializzazione

Assicurati sempre che i18next sia inizializzato prima di renderizzare la tua app usando provideAppInitializer() per eseguire l'inizializzazione durante il bootstrap 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 sicurezza dei tipi completa. 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 usando un tipo helper ricorsivo NestedKeys.

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

Modifica il metodo transform della pipe per accettare TranslationKey invece di string. Questo fornisce sicurezza dei tipi nei template e assicura che possano essere usate 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 l'autocompletamento completo di IntelliSense ed errori in fase 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 type-safe:

  • Autocompletamento nell'IDE per tutte le chiavi di traduzione disponibili in tutti i namespace.
  • Errori in fase di compilazione se usi una chiave inesistente, individuando i refusi prima del runtime.
  • Supporto al refactoring: rinomina le chiavi in sicurezza in tutto il codebase.
  • Zero overhead a runtime: i tipi vengono completamente rimossi durante la compilazione.
  • Generati automaticamente dai tuoi file JSON effettivi: nessuna manutenzione manuale dei tipi richiesta.

Rilevamento e cambio lingua

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

Determina la lingua dell'utente da usare all'avvio dell'app 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 utente), poi ricade sulla lingua del browser (navigator.language) e infine utilizza la lingua predefinita dell'app.

Cambia lingua a runtime caricando 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');

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

Quando usi le 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(). Ciò previene richieste di rete ridondanti assicurando che tutte le traduzioni necessarie siano disponibili.

Dettagli di implementazione chiave:

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

Automatizza la traduzione con l'IA

Gestire le traduzioni manualmente in più lingue richiede tempo ed è soggetto a errori. È qui che entra in gioco la localizzazione basata su AI.

Perché l10n.dev?

l10n.dev è un servizio di traduzione basato su AI progettato specificamente 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 contesto e 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 (es. en/common.json)
  2. Caricalo su l10n.dev e seleziona le lingue di destinazione.
  3. L'IA traduce con consapevolezza del contesto e formattazione corretta.
  4. Scarica i file tradotti pronti per l'uso 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 di i18n in Angular?