Internazionalizzazione delle app WinForms/WPF — la pratica di resx, satellite assembly e cambio cultura

· · CSharp, .NET, WinForms, WPF, Internazionalizzazione, Localizzazione, Risorse, UI, Sviluppo Windows, Consulenza tecnica

“Vogliamo usare la stessa app anche nelle sedi estere”, “sono aumentati i membri dello staff di lingua straniera nello stabilimento, quindi vogliamo mettere anche l’interfaccia in inglese” — come requisito, l’internazionalizzazione di un’app desktop Windows è semplice, ma non appena ci si mette al lavoro emergono continuamente punti su cui è facile esitare: “come si usa resx”, “in WPF ci sono più approcci e non è chiaro quale sia quello giusto”, “è possibile cambiare lingua mentre l’app è in esecuzione”.

In questo articolo, pensando ad app aziendali WinForms/WPF, analizziamo nell’ordine in cui più spesso sorgono dubbi pratici: le fondamenta dell’internazionalizzazione in .NET (culture e fallback delle risorse), il meccanismo di resx e satellite assembly, gli approcci realistici per ciascun framework, il cambio a runtime e infine gli aspetti che vanno oltre le stringhe (formato, layout, RTL).

1. Prima la conclusione (tabella decisionale)

L’approccio all’internazionalizzazione dipende dalla combinazione di framework e requisiti.

Situazione Approccio consigliato Motivo
WinForms, schermate incentrate su etichette/pulsanti Localizable = true sul form + resx per lingua (approccio designer) Le stringhe dei controlli e le loro dimensioni/posizioni possono essere adattate per ogni lingua, tutto restando in Visual Studio
WinForms/WPF in comune, con molti messaggi o testi condivisi resx condivisi (Resources.resx + Resources.ja.resx ecc.) + classe tipizzata generata automaticamente Le stringhe possono essere referenziate dal codice in modo type-safe. Schermate e logica possono condividere gli stessi testi
WPF resx condivisi + binding con x:Static L’approccio LocBaml non è utilizzabile in WPF su .NET (Core)1. Con l’approccio resx si condivide la stessa conoscenza usata in WinForms
Il cambio lingua a runtime è obbligatorio resx + meccanismo di notifica delle modifiche (oppure rigenerazione della schermata) Prima ancora di scegliere l’approccio, verificare come requisito se sia davvero necessario il cambio senza riavvio (capitolo 6)
La traduzione viene affidata a terzi Progettazione basata su resx, consegna tramite tabella di corrispondenza chiave-traduzione Il resx è XML e quindi può essere consegnato così com’è, ma allegare informazioni di contesto (a quale schermata e a quale pulsante si riferisce) è ciò che determina la qualità

Scriviamo subito le conclusioni.

  • CurrentCulture (formato) e CurrentUICulture (lingua delle risorse) sono cose distinte. Questa distinzione è alla base di tutta la progettazione dell’internazionalizzazione (capitolo 2).
  • Le risorse sono organizzate come “risorsa in lingua neutra + satellite assembly per lingua”, e il fallback è automatico. La ricerca avviene nell’ordine ja-JPja → lingua neutra, quindi anche se manca una traduzione l’app non si blocca e viene mostrata la lingua predefinita2.
  • In WinForms, la soluzione pratica è combinare l’approccio designer (Localizable = true) con i resx condivisi. Le schermate seguono l’approccio designer, mentre message box e testi comuni vengono raccolti nei resx condivisi (capitolo 4).
  • L’approccio LocBaml presentato nella walkthrough ufficiale di WPF non è utilizzabile1 su .NET (Core). Oggi la prima scelta pratica è l’approccio resx + x:Static (o binding) (capitolo 5).
  • Per il cambio lingua a runtime, la prima scelta è “applicare al riavvio successivo”. Le risorse della schermata già visualizzata non vengono sostituite automaticamente (capitolo 6).
  • La traduzione delle stringhe è solo metà dell’internazionalizzazione. L’altra metà consiste nell’adattare il layout alla lunghezza delle stringhe tradotte, nel formato di date e numeri, nei font e nel supporto alle lingue scritte da destra a sinistra (RTL) (capitolo 7).

2. Le fondamenta — la differenza tra CurrentCulture e CurrentUICulture

Nelle culture (impostazioni di zona culturale) di .NET esistono due proprietà con ruoli diversi3.

Proprietà Cosa determina Esempio
CultureInfo.CurrentCulture Il formato di date, numeri e valuta Visualizzare 2026/07/07 oppure 7/7/2026
CultureInfo.CurrentUICulture In quale lingua caricare le risorse Mostrare “保存” oppure “Save” sul pulsante

Queste due proprietà possono essere impostate in modo indipendente. Ad esempio, una combinazione come “schermata in inglese, ma date e valuta in formato giapponese” (un requisito frequente nelle app aziendali usate in Giappone da personale di lingua straniera) si esprime naturalmente con CurrentUICulture = en e CurrentCulture = ja-JP.

Entrambi i valori predefiniti derivano dalle impostazioni del sistema operativo, quindi “se non si fa nulla, si segue il sistema operativo” è il punto di partenza. Quando si vuole cambiare la lingua in modo esplicito per l’intera applicazione, non si imposta per singolo thread, ma si sostituisce il valore predefinito.

using System.Globalization;

// Sostituisce la cultura predefinita per tutti i thread creati successivamente
// (da eseguire prima della creazione del thread UI, cioè prima di Application.Run)
CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("en"); // lingua delle risorse
CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("ja-JP"); // formato

Assegnare un valore a Thread.CurrentThread.CurrentUICulture ha effetto solo su quel thread. Nelle app aziendali che attraversano più thread con async/await o elaborazioni in background, cambiare il valore predefinito tramite DefaultThreadCurrentUICulture / DefaultThreadCurrentCulture riduce il rischio di errori3.

Va inoltre osservato che il formato usato per log, output su file e interscambio tra sistemi non deve seguire la cultura dell’utente, ma va fissato a CultureInfo.InvariantCulture. Per la progettazione complessiva del formato delle date, si veda “Progettazione di data/ora e fuso orario nelle app aziendali”.

3. Il meccanismo delle risorse — resx, satellite assembly e fallback

La localizzazione in .NET segue un modello “hub-and-spoke”. Le risorse della lingua predefinita (neutra) sono incorporate nell’assembly principale, mentre le risorse per ciascuna lingua vengono separate in DLL distinte dette satellite assembly4.

Dopo la build, i resx per lingua producono una struttura di output simile a questa:

MyApp.exe                     ← corpo principale (include le risorse neutre)
MyApp.dll
ja/MyApp.resources.dll        ← satellite assembly in giapponese
en/MyApp.resources.dll        ← satellite assembly in inglese

A runtime, il resource manager, partendo da CurrentUICulture, cerca le risorse nell’ordine cultura specifica (ja-JP) → cultura neutra (ja) → predefinita (assembly principale)2. Grazie a questo fallback automatico, anche se manca una singola traduzione l’app non si blocca e viene mostrata la stringa nella lingua predefinita. Detto altrimenti, se in una schermata che dovrebbe essere in inglese compare improvvisamente del giapponese, è il segnale di una traduzione mancante, e può essere usato come criterio di test (capitolo 8).

Nella pratica è utile tenere presenti questi due punti:

  • Dichiarare tramite l’attributo NeutralResourcesLanguage quale cultura corrisponde alla lingua predefinita: in questo modo, quando ci si trova già nella cultura predefinita, si evita la ricerca inutile della satellite assembly, e il punto finale del fallback risulta chiaro4.
  • Le satellite assembly funzionano solo se distribuite insieme all’assembly principale, quindi è necessario includere le cartelle delle lingue nella progettazione dell’installer o della distribuzione tramite copia (capitolo 8).

4. La pratica in WinForms — approccio designer e resx condivisi

WinForms offre due approcci, che nella pratica si usano insieme.

4.1 Le schermate con l’approccio designer (Localizable = true)

Impostando Localizable a true nelle proprietà del form, passando la proprietà Language alla lingua di destinazione (ad esempio l’inglese) e poi modificando il Text di etichette e pulsanti, Visual Studio genera automaticamente resx per lingua come Form1.en.resx5.

Il punto di forza di questo approccio è che non solo le stringhe, ma anche dimensioni e posizione dei controlli vengono salvate per ciascuna lingua. Il problema per cui il giapponese “登録” diventa in inglese “Register” e fuoriesce dal pulsante può essere corretto nel designer, lingua per lingua. D’altra parte, nascono anche costi di manutenzione: i resx crescono in numero pari a lingue × form, e ogni modifica al layout di un form richiede una verifica in tutte le lingue. Se si prevede che il numero di lingue aumenterà, conviene dare priorità alle contromisure di layout descritte più avanti (capitolo 7), progettando schermate che “non si rompono anche senza aggiustamenti”.

4.2 Messaggi e testi comuni con resx condivisi

Il testo dei message box, delle finestre di conferma, delle barre di stato e in generale le stringhe usate dal codice vanno raccolte in un Resources.resx comune al progetto, aggiungendo per ogni lingua file come Resources.en.resx. Dal resx viene generata automaticamente una classe tipizzata, quindi dal codice si può fare riferimento alle stringhe senza rischio di errori di battitura nelle chiavi.

// Si accede tramite la classe tipizzata generata automaticamente.
// La stringa restituita cambia automaticamente in base a CurrentUICulture
MessageBox.Show(
    Properties.Resources.ConfirmDeleteMessage,
    Properties.Resources.ConfirmTitle,
    MessageBoxButtons.YesNo);

La regola più importante qui è non scrivere direttamente nel codice le stringhe mostrate all’utente. Il grosso del lavoro di internazionalizzazione non è introdurre il meccanismo, ma “recuperare le stringhe scritte direttamente e disperse nel codice”. Anche mentre si opera con una sola lingua, passare comunque attraverso i resx ha valore proprio per questo motivo.

5. La pratica in WPF — resx come prima scelta, non LocBaml

L’internazionalizzazione in WPF è un ambito in cui le informazioni tendono a confondersi facilmente. La walkthrough della documentazione ufficiale descrive un approccio in cui si imposta UICulture nel file di progetto per generare risorse BAML, per poi inserire le traduzioni con lo strumento LocBaml; tuttavia, LocBaml è uno strumento di esempio dedicato esclusivamente al WPF di .NET Framework e non funziona nel WPF di .NET (Core)1. Non è più un approccio da scegliere oggi per un nuovo progetto.

La prima scelta pratica consiste nel referenziare da XAML lo stesso approccio a resx condivisi usato in WinForms.

<!-- Impostare il modificatore di accesso del resx su Public e farvi riferimento con x:Static -->
<Window xmlns:res="clr-namespace:MyApp.Properties"
        Title="{x:Static res:Resources.MainWindowTitle}">
    <Button Content="{x:Static res:Resources.SaveButton}" />
</Window>

Il vantaggio di questo approccio è che il meccanismo delle risorse (satellite assembly, fallback) è del tutto identico a quello di WinForms, permettendo di gestire i testi in modo centralizzato anche in soluzioni miste. Poiché x:Static è un riferimento statico risolto all’avvio, se serve il cambio a runtime bisogna evolvere verso binding con notifica delle modifiche (capitolo 6).

Sul lato XAML, impostare anche l’attributo xml:lang sull’elemento radice fa sì che funzionino correttamente le funzionalità di WPF dipendenti dalla lingua, come la sillabazione, il fallback dei font e la visualizzazione dei numeri1.

Per la scelta stessa tra WinForms e WPF, si veda “Tabella decisionale per la scelta tra WinForms, WPF e WinUI”.

6. Cambio lingua a runtime — “applicare al riavvio successivo” come prima scelta

Quando il requisito prevede di far scegliere la lingua nella schermata delle impostazioni, bisogna scegliere tra due opzioni per il momento in cui applicare il cambio.

  • Opzione A: applicare al riavvio successivo (consigliata). Si salva la lingua selezionata in un file di configurazione e si imposta DefaultThreadCurrentUICulture prima di Application.Run all’avvio (in WPF, prima dell’avvio di Application). L’implementazione è semplice e non si verificano strutturalmente problemi legati alla ricostruzione della schermata (perdita di dati in corso di inserimento, doppia registrazione degli event handler, ecc.).
  • Opzione B: applicazione immediata. Anche cambiando CurrentUICulture, le stringhe delle schermate già visualizzate non vengono sostituite automaticamente. In WinForms con l’approccio designer occorre riapplicare le risorse ai form aperti tramite ComponentResourceManager.ApplyResources; in WPF occorre fornire le stringhe tramite binding con notifica delle modifiche. In entrambi i casi, l’implementazione presuppone che “tutte le schermate siano predisposte per riapplicare le risorse”.

Nelle app aziendali, dato che la frequenza con cui si cambia lingua è normalmente bassa (di solito una volta, all’introduzione), spesso il costo di implementazione e test dell’opzione B non è giustificato: si consiglia di adottare l’opzione A come impostazione predefinita, riservando l’opzione B solo ai casi in cui sia davvero un requisito necessario. Poiché l’impostazione della lingua è specifica per utente, il luogo appropriato per salvarla è sotto %LOCALAPPDATA%. Per la scelta di dove collocare le impostazioni, si veda la tabella decisionale in “Gestione della configurazione nelle app aziendali Windows”.

7. Internazionalizzazione oltre le stringhe — layout, formati, font, RTL

Anche a traduzione completata, resta ancora metà del lavoro di internazionalizzazione.

  • L’espansione e la contrazione del layout. A parità di significato, la lunghezza dei testi varia molto da una lingua all’altra (giapponese “保存” → tedesco “Speichern”). Un layout con coordinate e dimensioni fisse si rompe a ogni traduzione, quindi in WinForms il metodo consigliato ufficialmente è combinare TableLayoutPanel e AutoSize per ottenere un layout che segua la lunghezza delle stringhe6. In WPF, poiché il sistema di layout è già di per sé flessibile, evitare larghezze fisse è sufficiente per gran parte dei casi. Conviene progettare questo aspetto insieme alle contromisure per gli ambienti ad alto DPI (“Guida al supporto high DPI in WinForms”, “Guida al supporto high DPI in WPF”), così da evitare rilavorazioni.
  • Il formato di date, numeri e valuta. Per la visualizzazione a schermo ci si affida a CurrentCulture, riducendo dal codice delle schermate l’uso di ToString("yyyy/MM/dd") con formati fissati. Al contrario, file, log e dati di interscambio vanno fissati su InvariantCulture (capitolo 2).
  • I font. Va verificato che i caratteri della lingua di destinazione siano inclusi nel font. Quando si aggiungono cinese (semplificato/tradizionale) o coreano, mantenere una specifica di font pensata per il giapponese può produrre glifi innaturali (il cosiddetto problema dei glifi CJK). Centralizzare in un unico punto la specifica del font dell’interfaccia rende più semplice modificarla quando si aggiunge una lingua.
  • Le lingue scritte da destra a sinistra (RTL). Per il supporto ad arabo ed ebraico, oltre alla traduzione delle stringhe serve invertire il layout della schermata da sinistra a destra. In WinForms si controlla tramite le proprietà RightToLeft / RightToLeftLayout, in WPF tramite la proprietà FlowDirection. Aggiungere il supporto RTL a posteriori comporta una revisione di tutte le schermate, quindi se esiste anche solo la possibilità di includere una lingua RTL, va inserita nei requisiti fin dall’inizio.
  • Il testo contenuto in immagini e icone. Le stringhe incorporate in un’immagine non cambiano tramite il meccanismo delle risorse. È più sicuro evitare immagini con testo incorporato e usare invece una combinazione di immagine più etichetta di testo.

Per le decisioni di progettazione UI in generale, si veda anche “Tabella decisionale per la progettazione UX delle app Windows”.

8. Gestione operativa — consegna delle traduzioni, rilevamento delle lacune, distribuzione

Infine, gli aspetti operativi una volta che il meccanismo è stato messo in piedi.

  • Allegare il contesto quando si consegnano le traduzioni. Consegnare solo le chiavi del resx e il testo originale non garantisce una qualità stabile per le parole brevi come “OK” o “Apri”. È realistico scambiarsi una tabella di corrispondenza (ad esempio un foglio di calcolo) con chiave, testo originale e traduzione, corredata dell’informazione “su quale schermata e per quale scopo viene mostrata”, e riportare i risultati nel resx dopo averli ricevuti.
  • Individuare le traduzioni mancanti tramite il fallback. Grazie al fallback delle risorse, una traduzione mancante si manifesta come “compare la lingua predefinita mescolata nella schermata di quella lingua”2. Inserire in CI un controllo semplice (uno script o un test) che confronti l’elenco delle chiavi dei resx per lingua con il resx neutro permette di rilevare meccanicamente le lacune prima del rilascio.
  • Includere le satellite assembly nella distribuzione. Se non si distribuiscono le cartelle delle lingue come ja/ en/, per quella lingua scatta il fallback e si ottiene la situazione “ho tradotto ma non si vede”. La dimenticanza della cartella di una lingua durante una copia manuale è un incidente classico, quindi va inclusa l’intera struttura delle directory nell’installer (MSI/MSIX ecc.). Nella distribuzione ClickOnce, per impostazione predefinita tutte le satellite assembly vengono incluse nel deployment7. Per la scelta complessiva del metodo di distribuzione, si veda “Come scegliere il metodo di distribuzione per le app Windows”.

L’internazionalizzazione delle app desktop si basa sul meccanismo collaudato di resx e satellite assembly, e il meccanismo in sé non è complesso. Ciò che determina il successo nella pratica è una progettazione che distingue CurrentCulture da CurrentUICulture, la disciplina di non scrivere direttamente nel codice le stringhe mostrate all’utente, un layout che regge alla lunghezza delle stringhe dopo la traduzione, e la valutazione del requisito “il cambio immediato è davvero necessario?”. In WPF, senza farsi trascinare dalle vecchie informazioni sull’approccio LocBaml, oggi la soluzione pratica è costruire tutto su base resx.

La scelta dell’approccio per internazionalizzare un’app esistente e la stima dell’impatto, così come il supporto multilingua di schermate, report e dati di interscambio in vista di un’espansione verso sedi estere, richiedono spesso una valutazione basata sullo stato attuale del codice: in caso di dubbio, non esitate a contattarci.

Articoli correlati

Aree di consulenza correlate

Godo Kaisha Komura Soft si occupa di consulenza tecnica su progettazione e implementazione del supporto multilingua per app WinForms/WPF, e sulla scelta dell’approccio e la valutazione dell’impatto quando si internazionalizza a posteriori un’app esistente.

Riferimenti

  1. Microsoft Learn, WPF Globalization and Localization Overview. Sulla generazione delle satellite assembly tramite l’impostazione UICulture nel file di progetto, sul ruolo di xml:lang sull’elemento radice, e sul fatto che LocBaml sia uno strumento di esempio dedicato esclusivamente al WPF di .NET Framework e non funzioni nel WPF di .NET.  2 3 4

  2. Microsoft Learn, Retrieve resources in .NET apps. Sul processo di fallback delle risorse (la ricerca avviene nell’ordine cultura specifica → cultura neutra → risorsa predefinita dell’assembly principale).  2 3

  3. Microsoft Learn, CultureInfo.CurrentUICulture Property. Su come CurrentUICulture venga usata dal resource manager per la ricerca delle risorse specifiche per cultura, sul fatto che il valore predefinito derivi dalla lingua dell’interfaccia utente del sistema operativo, e sull’impostazione del valore predefinito per tutti i thread tramite DefaultThreadCurrentUICulture 2

  4. Microsoft Learn, Resources in .NET apps. Sulla creazione e il packaging delle risorse resx, sul modello hub-and-spoke con assembly principale e satellite assembly, e sull’attributo NeutralResourcesLanguage 2

  5. Microsoft Learn, Walkthrough: Localizing a Hybrid Application. Sulla procedura per impostare su true la proprietà Localizable nel designer di Windows Form e cambiare la proprietà Language per generare resx per lingua (ad esempio Form1.es-ES.resx), e sull’esempio di impostazione di CurrentUICulture prima della chiamata a InitializeComponent

  6. Microsoft Learn, How to: Design a Windows Forms Layout that Responds Well to Localization. Su come usare TableLayoutPanel per creare un layout in cui le dimensioni dei controlli seguano le variazioni di lunghezza della stringa della proprietà Text dovute alla localizzazione. 

  7. Microsoft Learn, Localize ClickOnce applications. Sul fatto che includere tutte le satellite assembly in un unico deployment sia il comportamento predefinito di Visual Studio, e su come a runtime venga usata la satellite assembly corrispondente alla cultura del sistema operativo. 

Articoli recenti con gli stessi tag per approfondire argomenti vicini.

Queste pagine collocano l’argomento in un contesto più ampio di servizi e decisioni.

L’articolo è direttamente collegato ai servizi seguenti.

Domande frequenti

Domande che ricorrono nelle consulenze sull’argomento dell’articolo.

È possibile internazionalizzare in un secondo momento un'app esistente sviluppata solo in giapponese?
Sì, è possibile, ma la maggior parte del lavoro non consiste nell'"introdurre il meccanismo delle risorse", bensì nel "censire e sostituire le stringhe scritte direttamente nel codice". È necessario fare l'inventario non solo delle stringhe visualizzate a schermo, ma anche di message box, log, messaggi di eccezione, report e stringhe salvate nel database per la visualizzazione: la portata del lavoro dipende più dalla dispersione delle stringhe che dal numero di schermate. Se fin dalla fase di nuovo sviluppo si passa attraverso i resx anche solo per le stringhe mostrate all'utente, il costo di una futura internazionalizzazione si riduce di un ordine di grandezza.
È accettabile affidarsi solo alla traduzione automatica?
Per strumenti interni o soluzioni provvisorie, partire dalla traduzione automatica è realistico. Tuttavia, nelle app aziendali abbondano parole brevi su pulsanti e menu (ad esempio "Registra", "Annulla") ed è un ambito in cui, senza contesto, il tasso di traduzioni errate della traduzione automatica aumenta. Come minimo, si consiglia di far passare almeno un ciclo di revisione con qualcuno che usa realmente quella lingua e che possa provare le schermate. Ancora prima della qualità della traduzione, è più importante garantire in anticipo, con TableLayoutPanel e AutoSize, che le stringhe più lunghe risultanti dalla traduzione non rompano il layout.
Bisogna tradurre anche i log e i messaggi di errore?
La prassi consolidata è tradurre i messaggi mostrati sullo schermo dell'utente e non tradurre (cioè fissare la lingua di) i contenuti scritti nei file di log o nell'event log. I log vengono letti dagli sviluppatori e dagli operatori, non dagli utenti, e log in più lingue distruggono la ricercabilità durante le indagini. Anche il formato (data, numeri) dei log va fissato a CultureInfo.InvariantCulture. Progettare l'app distinguendo CurrentCulture e CurrentUICulture rende naturale questa separazione.
Oltre alla lingua di visualizzazione del sistema operativo, conviene permettere di cambiare la lingua solo per l'app?
Per l'espansione verso sedi estere, o per app aziendali usate in Giappone da personale che parla altre lingue, avere un'impostazione della lingua interna all'app ha un grande valore. Un compromesso ragionevole è una struttura a due livelli: per impostazione predefinita si segue la lingua di visualizzazione del sistema operativo (valore predefinito di CurrentUICulture), e si sovrascrive solo quando l'utente la seleziona esplicitamente nella schermata delle impostazioni. Specificare che il cambio si applica "al riavvio successivo" è la scelta più sicura, ed è anche la più semplice sia da implementare sia da spiegare in fase operativa.

Profilo dell’autore

Pagina di presentazione dell’autore dell’articolo.

Go Komura

Rappresentante di KomuraSoft LLC

Specializzato nello sviluppo di software Windows, nella consulenza tecnica e nell’analisi dei malfunzionamenti, soprattutto nei progetti con sistemi esistenti e guasti difficili da riprodurre.

Torna al blog