Sei scelte che hanno mantenuto semplice un piccolo livello i18n in Electron
Un progetto i18n pratico per Electron: preferenze della lingua, cataloghi tipizzati, fallback, formattazione Intl e sincronizzazione tra renderer e main process.

Aggiungere il giapponese a un'app Electron esistente ci ha costretti a rispondere a domande che un catalogo di messaggi non può risolvere. Quale lingua prevale quando il sistema operativo e la preferenza salvata non coincidono? Che cosa accade prima che il renderer comunichi la scelta al main process? Come si evita che le finestre native usino una lingua diversa dall'interfaccia React?
Abbiamo mantenuto volutamente piccola la prima versione. Supporta inglese e giapponese, usa l'inglese come fallback e si affida a TypeScript e a una breve suite di test anziché a un framework di internazionalizzazione più ampio. Sei decisioni iniziali hanno reso questo livello più facile da comprendere.
1. Salva una preferenza, non soltanto una lingua
Una lingua salvata come en o ja non può rappresentare chi desidera che l'app segua macOS. Anche usare la lingua di sistema come valore iniziale presenta lo stesso problema: dopo il salvataggio di una lingua specifica, un successivo cambio della lingua di macOS non avrebbe più effetto.
Abbiamo modellato separatamente la scelta dell'utente e la lingua effettiva:
type AppLocale = "en" | "ja";
type AppLocalePreference = "system" | AppLocale;
const resolveAppLocale = (preference: AppLocalePreference, languages: readonly string[]): AppLocale =>
preference === "system" ? detectSystemLocale(languages) : preference;
Soltanto la preferenza system consulta navigator.languages. Una scelta manuale prevale sempre, anche quando è diversa da macOS. Prima di usare il valore salvato lo convalidiamo; se manca, è danneggiato o proviene da una versione non più supportata, torniamo a system.
Conta anche l'ordine di navigator.languages. Selezioniamo la prima lingua supportata nell'elenco delle preferenze, anziché cercare il giapponese in qualsiasi posizione e ignorare un inglese indicato prima. Se nessuna voce è supportata, l'app usa l'inglese.
Separare i due valori assegna a ogni stato un solo compito. La preferenza registra la richiesta dell'utente; la lingua indica che cosa deve mostrare l'interfaccia in quel momento.
2. Mantieni leggibili le scelte della lingua dopo una selezione errata
Il menu della lingua mostra sempre queste etichette:
- English
- 日本語
Sono autonimi, cioè i nomi che ogni lingua usa per se stessa. Tradurre entrambe le etichette nella lingua corrente crea un problema di ripristino evitabile. Chi passa a una lingua che non sa leggere potrebbe perdere anche l'etichetta utile per tornare indietro.
Traduciamo invece System default, perché descrive il comportamento dell'app e non il nome di una lingua. I test fissano i due autonimi in entrambi i cataloghi, così una normale modifica alle traduzioni non può cambiarli in seguito.
3. Usa il catalogo inglese come contratto TypeScript
Il catalogo inglese era già la cosa più vicina a un inventario completo dei testi dell'app, quindi abbiamo usato le sue chiavi come contratto per tutte le lingue supportate.
export const enMessages = {
"settings.language.title": "Language",
"settings.language.english": "English",
"settings.language.japanese": "日本語",
} as const;
export type MessageKey = keyof typeof enMessages;
const catalogs = {
en: enMessages,
ja: jaMessages,
} as const satisfies Record<AppLocale, Record<MessageKey, string>>;
Il contratto del catalogo intercetta due errori comuni durante lo sviluppo. Un componente non può richiedere una chiave inesistente o scritta male e una lingua non può omettere una chiave presente in inglese. Se si aggiunge un messaggio inglese senza la corrispondente versione giapponese, il controllo dei tipi fallisce vicino alla modifica del catalogo.
Le chiavi tipizzate non garantiscono la qualità di una traduzione, ma trasformano la completezza strutturale in un normale controllo del compilatore. Per un'app con due lingue e messaggi semplici era sufficiente.
4. Mantieni sia il fallback a runtime sia il controllo di completezza
La completezza verificata in compilazione non elimina la necessità di un fallback a runtime. Vecchi dati locali, una combinazione inattesa di build o un futuro catalogo dinamico possono comunque produrre un valore mancante. Restituire il messaggio inglese è più sicuro che interrompere l'intera schermata.
Non abbiamo però permesso al fallback di nascondere il lavoro incompleto. Il controllo dei tipi richiede lo stesso insieme di chiavi in ogni catalogo e i test confrontano messaggi rappresentativi, sostituzioni ed etichette stabili. La resilienza a runtime e il riscontro durante lo sviluppo risolvono problemi diversi, perciò il livello conserva entrambi.
L'interpolazione è volutamente limitata a semplici valori con nome, come {count}. Regole plurali, genere grammaticale e selezione complessa dei messaggi giustificherebbero ICU MessageFormat o una libreria dedicata. Aggiungere questi meccanismi prima che i testi ne abbiano bisogno renderebbe il sistema attuale più difficile da esaminare senza risolvere un problema reale.
5. Formatta date e numeri fuori dal catalogo dei messaggi
Le sole etichette tradotte non rendono locale un'interfaccia se date e numeri seguono ancora convenzioni sbagliate. I componenti ricevono la lingua effettiva dell'app e passano il relativo tag BCP 47 ai formattatori della piattaforma.
const getIntlLocale = (locale: AppLocale) => (locale === "ja" ? "ja-JP" : "en-US");
const label = new Intl.DateTimeFormat(getIntlLocale(locale), {
year: "numeric",
month: "2-digit",
day: "2-digit",
}).format(date);
Tenere Intl.DateTimeFormat e Intl.NumberFormat fuori dalla funzione dei messaggi impedisce al catalogo di diventare anche un livello generale di formattazione. Inoltre rende visibile la dipendenza dalla lingua proprio dove vengono mostrati data, ora, dimensione di un file o conteggio.
La formattazione destinata alle persone deve restare separata dall'output per le macchine. Una data localizzata può contenere barre o altri caratteri inadatti al nome di un file. Per i nomi esportati usiamo schemi prevedibili e sicuri, mentre riserviamo i formati localizzati al testo mostrato all'utente.
6. Sincronizza la scelta del renderer con il main process
Un'app Electron ha più superfici testuali. React mostra gran parte dell'interfaccia, ma il main process controlla finestre native, richieste legate all'acquisizione e altre interazioni con il sistema operativo. Completare il catalogo del renderer non significava avere localizzato l'intera app.
Il renderer invia sia la preferenza salvata sia la lingua effettiva tramite un endpoint IPC tipizzato:
type AppLocaleSyncRequest = {
preference: "system" | "en" | "ja";
locale: "en" | "ja";
};
await window.electron.appLocale.syncPreference({
preference,
locale,
});
Inviare entrambi i valori ne conserva il significato. Il main process può usare subito la lingua effettiva e sapere comunque se proviene da macOS o da una scelta manuale. Prima della prima sincronizzazione, ricava un valore predefinito prudente da app.getLocale() di Electron e torna all'inglese se la lettura non è disponibile.
Sincronizziamo al montaggio del provider e ogni volta che la finestra torna in primo piano. La seconda sincronizzazione è utile durante lo sviluppo, quando il main process può ricaricarsi e perdere il proprio stato in memoria mentre il renderer resta attivo. Un errore IPC non blocca mai il renderer: lascia soltanto i testi nativi sul fallback sicuro fino alla sincronizzazione successiva.
La sincronizzazione tra renderer e main process ha cambiato anche la nostra definizione di completamento. Ora controlliamo separatamente i testi React, quelli del main process e quelli gestiti da macOS. Una schermata delle impostazioni localizzata non può compensare una conferma di uscita in inglese che si trova fuori dal renderer.
Test che proteggono i confini
La prima serie di test si concentra sui confini di stato e di processo, anziché creare snapshot di ogni schermata tradotta. Verifica che:
ja-JPvenga risolto in giapponese quando è la prima preferenza di sistema supportata.- Una lingua scelta manualmente prevalga su quella di sistema.
- Valori salvati mancanti o non supportati tornino in sicurezza alla preferenza di sistema.
- Ogni lingua rispetti l'insieme di chiavi del catalogo inglese.
Englishe日本語restino invariati in entrambe le interfacce.- Date e numeri usino la lingua effettiva dell'app.
- Il main process riceva sia la preferenza sia la lingua effettiva.
I test dei componenti coprono comunque gli stati visualizzati più importanti, ma questi controlli più piccoli intercettano gran parte delle regressioni vicino alle funzioni che definiscono il comportamento. Sono anche abbastanza rapidi da essere eseguiti a ogni modifica.
Anche un piccolo livello i18n richiede confini chiari
L'aspetto utile di questo progetto non è la quantità di codice. È la separazione tra preferenza e lingua, ripristino e completezza, messaggi e formattazione, renderer e main process.
Questi confini consentono di sostituire i singoli elementi in seguito. Una terza o quarta lingua potrebbe giustificare cataloghi generati. Una grammatica più complessa potrebbe richiedere messaggi ICU. Nessuna delle due modifiche obbliga a ridefinire il significato della preferenza salvata o quale processo controlla i testi nativi.
Abbiamo costruito questo livello i18n mentre aggiungevamo il giapponese al flusso Action Capture di Shotomatic, un'app per Mac che sviluppiamo. Gli esempi di implementazione qui sopra sono semplificati rispetto all'app distribuita, ma il modello di stato e il confine tra processi corrispondono al codice che usiamo.
Articoli correlati
Altri articoliCome automatizzare gli screenshot dei siti web con Playwright
Usa Playwright e JavaScript per automatizzare gli screenshot di una pagina, pagine intere, viste mobile e batch di URL con nuovi tentativi.

Come automatizzare gli screenshot dei siti web con Puppeteer
Usa Puppeteer e JavaScript per automatizzare gli screenshot di una pagina, pagine intere, batch di URL e viewport mobile con uno script riutilizzabile.

Automatizzare gli screenshot sul Mac dalla riga di comando
Usa cicli di screencapture in macOS per acquisire lo schermo intero, un'area fissa o pagine successive, con nomi sicuri e indicazioni per risolvere i problemi.

Cosa controllare prima di acquistare un libro di testo digitale per studiare offline
Prima dell'acquisto, verifica dispositivi offline, file portatili, limiti di stampa, durata dell'accesso, esportazione delle note e accessibilità.

Trasforma i clic in una guida chiara, passaggio dopo passaggio
Action Capture registra ogni clic come passaggio ordinato. Puoi poi modificare ed esportare la guida sul Mac.