Multilingual Controls in C#, easy!

Download demo Multilingual

Introduccion

Una manera rapida de proporcionar aplicaciones en multiples idiomas en C# es utilizar Culture y Embedded Resources.  Pero aun mas facil con MultilingualForm y MultilingualUserControl.

Es posible crear archivos .resx que contengan las definiciones para cada idioma y el neutral. Luego ResourceManager permite acceder a las entradas de un recurso para una determinada cultura de la siguiente manera:

CultureInfo ci = Thread.CurrentThread.CurrentCulture;
Assembly ass = Assembly.GetExecutingAssembly();
ResourceManager rm = new ResourceManager(this.GetType().Namespace, ass);
string res = rm.GetString("tt_hello", ci);

Que hace ResourceManager

ResourceManager recupera el recurso indicando el Assembly ass (donde fue embebido) y un string que representa el namespace + el nombre del recurso. Aqui es importante tener en cuenta que existe una convencion respecto al nombre del recurso a fin de permitirle a ResourceManager ubicar el recurso adecuado a la cultura que se utiliza en string res = rm.GetString(«tt_hello», ci);

Internamente ResourceManager en cada invocacion de GetString llama a una funcion privada:

protected virtual string GetResourceFileName (CultureInfo culture)

Esta funcion le proporciona entonces en base a la cultura enviada como parametro en GetString, el nombre/ruta correcto del recurso almacenado en el assembly, con esto obtiene el recurso llamando a Assembly.GetManifestResourceStream(filename) y luego extrae el ResourceSet, necesario para buscar el valor «tt_hello».

image

Para comprender mejor que hace esta clase y la forma en que gestiona los recursos pueden ver el codigo fuente Ximian para Mono en htp://www.koders.com/csharp/fid91F04E676D783C1D0FBADB4F18E62EB002099753.aspx

Por ejemplo veamos para una la siguiente aplicacion llamada Multilingual como gestionar los recursos:

image

En el caso de este proyecto se agregaron tres archivos de recursos. Como se puede ver el nombre Res.en-US.resx es requerido para identificar que es un recurso para la cultura en-US. Es necesario tambien definir otro recurso llamado Res.resx que será luego el recurso correspondiente al lenguaje neutro.

Entonces para recalcar, un recurso debe tener un nombre que corresponda con el sisguiente esquema: aaaa.xx-XX.resx, siendo aaaa un nombre cualquiera, en este caso Res, y xx-XX la cultura a la cual responde el recurso.

Es importante destacar que ResourceManager carga un recurso especificamente del basename indicado y del assembly apuntado. Es decir si en Multilingual ejecuto la siguiente instruccion:

Assembly ass = Assembly.GetExecutingAssembly();
ResourceManagerrm = newResourceManager(«Multilingual.TestForm.Res», ass);

ResourceManager carga el recurso en la ruta «Multilingual.TestForm.Res» e ignora los que se encuentran en «Multilingual.Res». Es decir que si utilizamos una arquitectura de plug-ins podemos incluir recursos dentro de la DLL de nuestro plug-in y de esta manera llevar un diccionario indipendiente de ser requerido.

Entonces una vez inicializado ResourceManager para poder traducir cada control de un Form o UserControl se debe tener en el metodo OnLoad todas las llamadas GetStrings necesarios para asignarles los valores extraidos de los recursos:

btn_hello.Text = rm.GetString(«tt_hello», ci);

Esto resulta engorroso en formularios extensos y con profundidad.

Como hacerlo mas facil

Bien si tubieramos un par de clases que heredan de UserControl o de Form que obtenga en OnLoad el CultureInfo actual, luego recorra recursivamente el arbol completo de controles hijos que posee, identificando aquellos que tengan declarada la propiedad string Text, que verifique sea != String.Empty. y busque en el recurso cual es el valor para esa entrada de texto y cambiarlo si existiese.

Incluso porque no… manejar tanto recursos especificos del dominio como los superiores, dado el caso de que una definicion no exista en el local que la busque en  los del nivel superior.

Bueno esto es lo que hace la clase MultilingualForm y MultilingualUserControl. Las hice para no preocuparme por esta funcionoladidad y solo poner en tiempo de diseño los textos correctos en cada control por ejemplo:

image

Cada control traducido por el metodo DoTranslate será referenciado en una HashTable que lleva el control de los Text originales y de esta manera permite la traduccion inmediata sin necesidad de reinicializar los controles (para que recuperen sus textos anteriores).

Como pueden ver en el ejemplo solo se requiere llamar a DoTranslate para que todos los controles adopten los textos definidos en el recurso para la cultura seleccionada.

Un Form que hereda de MultilingualForm no necesita codigo extra para manejar la traduccion de sus controles.

Aquí se puede ver como MultilingualForm o MultilingualUserControl recorre el arbol de controles identificando controles candidatos:

   1: private void TranslateControls(Control pc)
   2: {
   3:     if (this.resourceManager != null)
   4:     {
   5:         foreach (Control c in pc.Controls)
   6:         {
   7:             if (HasTextProperty(c))
   8:             {
   9:                 if (c.Text != "")
  10:                 {
  11:                     if (transTable.Contains(c))
  12:                     {
  13:                         if (this.resourceManager.GetString((string)transTable[c], Culture) != String.Empty)
  14:                         {
  15:                             c.Text = this.resourceManager.GetString((string)transTable[c], Culture);
  16:                         }
  17:                     }
  18:                     else
  19:                     {
  20:                         if (this.resourceManager.GetString(c.Text, Culture) != String.Empty)
  21:                         {
  22:                             transTable.Add(c, c.Text);
  23:                             c.Text = this.resourceManager.GetString(c.Text, Culture);
  24:                         }
  25:                     }
  26:                 }
  27:             }
  28:             if (c.Controls.Count > 0) TranslateControls(c);
  29:         }
  30:     }
  31: }

A tener en cuenta

Siempre utilizar recursos nombrado: Res.xx-XX.resx donde xx-XX es la cultura.

Download demo

Multilingual

Saludos,
Alejandro

Deja un comentario