Technology Experience

Contenuti gestiti da Igor Damiani
posts - 949, comments - 2741, trackbacks - 15120

My Links

News

  • Questo blog si propone di raccogliere riflessioni, teoriche e pratiche, su tutto quello che riguarda il world-computing che mi sta attorno: programmazione in .NET, software attuale e futuro, notizie provenienti dal web, tecnologia in generale, open-source.

    L'idea è quella di lasciare una sorta di patrimonio personale, una raccolta di idee che un giorno potrebbe farmi sorridere, al pensiero di dov'ero e cosa stavo facendo.

    10/05/2005,
    Milano

Archives

Post Categories

Generale

[MCAD.11] Localizzazione di un'applicazione e resources files

Nel sorgente che ho pubblicato, tutta l'applicazione è in lingua inglese. Su questo non c'è molto dire: quando abbiamo progetto la Windows Form (WF), abbiamo scritto le Label, i Button e i vari MessageBox in questa lingua. Se diamo un'occhiata alla function private void InitializeComponent(), vedremo che la property Text dei control è stata impostata sulla stringa esatta, così come compare a run-time. Ad esempio, ecco il codice che inizializza il btnCancel sulla WF.

// 
// btnCancel
// 
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnCancel.CausesValidation = false;
this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.btnCancel.Location = new System.Drawing.Point(264, 312);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Size = new System.Drawing.Size(56, 23);
this.btnCancel.TabIndex = 4;
this.btnCancel.Text = "Cancel";
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);

Drin, drin! Squilla il telefono: è il signor Mario Rossi, che trova molto utile il nostro programma, però lo vorrebbe in lingua italiana, perchè lui l'inglese non lo conosce, preferirebbe quindi vederlo in italiano. Cosa dobbiamo fare? Quali strumenti ci mette a disposizione .NET, il framework e l'IDE?

Dunque, cominciamo col dire che la localizzazione (secondo me), si divide essenzialmente in due parti:

  1. tradurre quello che possiamo tradurre a design-time sulla WF (Label, Button, titoli dei GroupBox, header delle ListView, etc. etc.)
  2. tradurre quello che compare nel codice  (MessageBox, messaggi segnalati da ErrorProvider, conferme, etc. etc.)

Vedremo come affrontare ciascuno di questi 2 punti per rendere la nostra applicazione veramente multilingua.

Tradurre con l'IDE la nostra WF
Apriamo il nostro progetto con VS.NET 2003, selezioniamo la nostra WF e diamo un'occhiata alla Property Window (PW). C'è una property chiamata Localizable, che per default vale false. Impostiamo a true. Adesso cerchiamo la property Language ed impostiamola su Italian (Italy).

Fatto questo, possiamo tranquillamente tradurre i nostri control sulla WF in lingua italiana. Prendiamo i Button, le Label e tutto quello che vogliamo, e impostiamo la property Text che deve apparire sui control della nostra WF. Se vogliamo tradurre in un'altra lingua, cambiamo la property Language, e ricominciamo daccapo. Adesso facciamo questa prova: impostiamo la property Language su una lingua che abbiamo già tradotto, e vedremo che anche a design-time l'IDE recupera le stringhe che avevamo inserito in precedenza e la WF viene modificata sotto il nostro naso secondo la lingua impostata. Forte, vero? Questo è già un grande passo in avanti, perchè abbiamo fatto capire a .NET che la WF supporta diverse lingue. Compiliamo il progetto così da creare il nuovo EXE: se proviamo ad eseguirlo, vedremo che verrà eseguito sempre in lingua inglese. Sembra che non sia cambiato nulla, vero? Effettivamente manca ancora qualcosa, ma la vedremo più tardi.

Noi che vogliamo diventare MCAD, vogliamo andare un po' più in profondità. Cosa è stato fatto da .NET? Apriamo ad esempio la directory che contiene l'EXE e vedremo diverse directory di supporto. Dato che abbiamo tradotto la nostra applicazione in Italian (Italy), vediamo che l'IDE ha creato una cartella it ed una cartella it-IT. Questa è la nomenclatura che .NET utilizza per definire le culture, ovvero quelle "regole" per la formattazione di numeri, date, cifre e così via. Ognuna di queste cartelle contiene degli assembly che contengono le stringhe che abbiamo tradotto.

E il codice? Come è cambiato il codice? Tutto il discorso, ancora una volta, è nella function private void InitializeComponent(). Vediamo com'è adesso il codice che inizializza il btnCancel.

// 
// btnCancel
// 
this.btnCancel.AccessibleDescription = resources.GetString("btnCancel.AccessibleDescription");
this.btnCancel.AccessibleName = resources.GetString("btnCancel.AccessibleName");
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)(resources.GetObject("btnCancel.Anchor")));
this.btnCancel.BackgroundImage = ((System.Drawing.Image)(resources.GetObject("btnCancel.BackgroundImage")));
this.btnCancel.CausesValidation = false;
this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.btnCancel.Dock = ((System.Windows.Forms.DockStyle)(resources.GetObject("btnCancel.Dock")));
this.btnCancel.Enabled = ((bool)(resources.GetObject("btnCancel.Enabled")));
this.errMsg.SetError(this.btnCancel, resources.GetString("btnCancel.Error"));
this.btnCancel.FlatStyle = ((System.Windows.Forms.FlatStyle)(resources.GetObject("btnCancel.FlatStyle")));
this.btnCancel.Font = ((System.Drawing.Font)(resources.GetObject("btnCancel.Font")));
this.errMsg.SetIconAlignment(this.btnCancel, ((System.Windows.Forms.ErrorIconAlignment)(resources.GetObject("btnCancel.IconAlignment"))));
this.errMsg.SetIconPadding(this.btnCancel, ((int)(resources.GetObject("btnCancel.IconPadding"))));
this.btnCancel.Image = ((System.Drawing.Image)(resources.GetObject("btnCancel.Image")));
this.btnCancel.ImageAlign = ((System.Drawing.ContentAlignment)(resources.GetObject("btnCancel.ImageAlign")));
this.btnCancel.ImageIndex = ((int)(resources.GetObject("btnCancel.ImageIndex")));
this.btnCancel.ImeMode = ((System.Windows.Forms.ImeMode)(resources.GetObject("btnCancel.ImeMode")));
this.btnCancel.Location = ((System.Drawing.Point)(resources.GetObject("btnCancel.Location")));
this.btnCancel.Name = "btnCancel";
this.btnCancel.RightToLeft = ((System.Windows.Forms.RightToLeft)(resources.GetObject("btnCancel.RightToLeft")));
this.btnCancel.Size = ((System.Drawing.Size)(resources.GetObject("btnCancel.Size")));
this.btnCancel.TabIndex = ((int)(resources.GetObject("btnCancel.TabIndex")));
this.btnCancel.Text = resources.GetString("btnCancel.Text");
this.btnCancel.TextAlign = ((System.Drawing.ContentAlignment)(resources.GetObject("btnCancel.TextAlign")));
this.btnCancel.Visible = ((bool)(resources.GetObject("btnCancel.Visible")));
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);

E' molto, molto più complesso rispetto a prima. Cosa è successo? Innanzitutto, credo che buona parte del codice scritto automaticamente dall'IDE sia superfluo. Il succo del discorso è questo: nulla è più deciso a design-time, il valore delle property vengono lette immediatamente dal file di risorsa corrispondente alla lingua. Questa regola vale anche in fase di progettazione. Ad esempio, il codice carica la property BackgroundImage usando l'identificativo btnCancel.BackgroundImage. Il codice carica la property Font usando l'identificativo btnCancel.Font. E così via per ciascuna property, compresa la Text che è utilissima perchè rappresenta quello che l'utente vede sulla WF mentre usa l'applicazione. Dicevo che buona parte del codice è superfluo, perchè se sappiamo che sui Button non gestiremo mai alcuna immagine, possiamo omettere la BackgroundImage. Non ho provato (purtroppo), quindi potrei dire qualche inesattezza, però mi sembra di intuire che per ogni Language posso avere un TabIndex, una Location, un TextAlign diverso, permettendo veramente di avere una WF molto diversa tra la lingua italiana e altre lingue che magari necessitano di "impaginazioni" molto diverse da come siamo abituati noi.

Chi ha aperto il codice della private void InitializeComponent(), avrà visto che la prima riga di tale function riporta quanto segue:

System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(frmMain));

In breve, viene inizializzato un oggetto resources di tipo System.Resources.ResourceManager. Tale oggetto viene appunto utilizzato per prelevare i dati dai files di risorsa usando una stringa identificativa come abbiamo visto prima. Vedremo qualche dettaglio in più la prossima volta.

Adesso, ciliegina sulla torta. Aprite il form frmMain e correggete il codice della Main() come segue:

static void Main() 
{
    
// riga che abbiamo aggiunto per impostare la Culture
    
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
    Application.Run(
new frmMain());
}

La riga che abbiamo aggiunto non fa altro che impostare la Culture del thread corrente, ovvero l'applicazione che sta per essere eseguita. La Study Guide di Lorenzo dice che l'espressione Thread.CurrentThread.CurrentCulture ritorna il valore impostato nelle Regional Options del pannello di controllo di Windows. Facciamo una prova? Se avete tradotto l'applicazione nelle lingue Italian (Italy) e English (United States), provate a "switchare" da una lingua all'altra e vedrete che la UI del software viene cambiata in base alla selezione corrente.

Questa è la prima parte: se è vero che la UI viene tradotta, così non è ovviamente per le MessageBox, o per i tooltip visualizzati tramite l'ErrorProvider. Per questo dovremo finalmente scrivere qualche riga di codice, che approfondirà in parte quello che è stato fatto oggi.

powered by IMHO 1.2

Print | posted on martedì 12 luglio 2005 15:01 | Filed Under [ MCAD ]

Feedback

Gravatar

# re: [MCAD.11] Localizzazione di un'applicazione e resources files

bell'idea, anche se bisognerebbe forse aprire una sezione dedicata...
12/07/2005 18:05 | Lorenzo Barbieri
Gravatar

# [MCAD.12] Completamento della localizzazione della nostra applicazione

13/07/2005 19:36 | Technology Experience
Gravatar

# re: [MCAD.11] Localizzazione di un'applicazione e resources files

Ciao Igor

per usare i thread devi inserire all'inizio

using System.Threading;

altrimenti ricevereai un errore in fase di compilazione.

Ciao
Danilo
06/10/2005 17:26 | Danilo Piran
Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET