viernes, 29 de abril de 2011

Localización y globalización de aplicaciones .NET

Hola,
Después de un poco de tiempo de pausa voy a tratar el tema de la localización de nuestras aplicaciones, es decir que la interfaz y los mensajes estén disponibles en varios idiomas. Con esto doy por finalizado el desarrollo de la aplicación Wordpad y continuaré desarrollando proyectos más complejos.
Bueno al grano. Localizar una aplicación es mucho más que cambiar el texto de tu aplicación e incluye por ejemplo el formato de las fechas, el formato de los números o la dirección del texto. Un caso real, si una aplicación importa datos numéricos de archivos de texto separados por puntos y comas, en España el separador de los miles es el punto y el de los decimales es la coma. En Estados Unidos, por ejemplo, es al revés. Si no lo tienes presente y simplemente cambias la cultura de la aplicación de “es-ES” a “en-US” la importación de los datos será errónea. En el ejemplo anterior estoy utilizando un código para representar el lenguaje, o la cultura, que se usa en España, y es "es-ES" y otro para la de los Estados Unidos, y es "en-US". ¿Cuáles son las culturas soportadas por .NET? Muchas, si creamos una aplicación de consola en VB.NET veremos el listado:
Imports System.Globalization

Module Module1

Sub Main()
Dim cultura As CultureInfo
For Each cultura In CultureInfo.GetCultures(CultureTypes.InstalledWin32Cultures)
Console.WriteLine("Cultura: " & cultura.Name)
Console.WriteLine(vbTab & "Nombre: " & cultura.DisplayName)
Console.WriteLine(vbTab & "Símbolo de la moneda: " & cultura.NumberFormat.CurrencySymbol)
Console.WriteLine(vbTab & "Formato de la fecha: " & cultura.DateTimeFormat.FullDateTimePattern)
Next
End Sub


End Module

Cuando trabajamos con un formulario para Windows y queremos adaptarlo a la cultura con la que estamos trabajando hay varios pasos:
1) Si lo que queremos cambiar es el texto de los controles que aparecen en el formulario, aquí no hay que escribir ni una sola línea de código. Simplemente con seleccionar el formulario y cambiar la propiedad Localizable = true ya podremos seleccionar en la lista ‘Language’ el lenguaje con el que vamos a trabajar. Si no seleccionamos nada siempre trabajaremos con el formulario predeterminado. Seleccionado por ejemplo inglés en la propiedad ‘Language’ podremos cambiar el texto del castellano al inglés y este texto será el que aparecerá cuando la cultura será “en-US”. Ahora podemos cambiar el texto de los controles y hasta su posición y por detrás el diseñador de Visual Studio creará archivos de recursos para cada uno de los idiomas con los que trabajará nuestra aplicación. Pero no te olvides que si estás trabajando en el modo de localización los cambios que hagas no serán globales y sólo quedarán circunscritos al lenguaje con el que estés trabajando.
2) Si queremos mostrar mensajes distintos al usuario según la cultura en este caso habrá que crearse archivos de recursos para cada uno de los idiomas con los que nuestra aplicación trabajará y luego vía código acceder a al contenido de esos archivos. Por ejemplo, si la interfaz de nuestra aplicación está en español y en inglés y queremos que los mensajes que se muestre al usuario sean acordes a la cultura con la que el usuario trabaje, habrá que crearse 2 archivos de recursos, llamados en este caso WinAppSettings.resx (para el idioma predeterminado) y WinAppSettings.en.resx (para el idioma inglés). Es muy importante la estructura del fichero: [Nombre del fichero].[idioma].resx
Y luego ir cambiando el valor de cada cadena del archivo WinAppSettings.xx.resx, siendo xx el otro idioma con el va a trabajar la aplicación.
Pues bien para obtener la cadena de texto apropiada a la cultura con la que estamos trabajando podemos crear una función como la siguiente:
Public Function getMessage(ByVal cadenaTexto As String) As String
'Obtengo la cadena de texto del archivo de recursos
Return My.Resources.WinAppSettings.ResourceManager.GetString(cadenaTexto)
End Function

El paso más importante es si el usuario cambia la cultura de la aplicación. En este caso los controles se han de redibujar de nuevo. Esto se puede hacer de 3 formas según la complejidad que queramos para la aplicación.
a. Borrar el contenido de todos los controles con Me.Controls.Clear() y llamar al método InicializeComponent() para que los vuelva a dibujar.
Imports System.Threading
Imports System.Globalization

Public Class Form1

Private Sub SButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles SButton.Click
Thread.CurrentThread.CurrentCulture = New CultureInfo("es-ES")
Thread.CurrentThread.CurrentUICulture = New CultureInfo("es-ES")
Inicializar()
End Sub

Private Sub EButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles EButton.Click
Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
Thread.CurrentThread.CurrentUICulture = New CultureInfo("en-US")
Inicializar()
End Sub

Private Sub Inicializar()
'borro los controles
Me.Controls.Clear()
'los vuelvo a inicializar
InitializeComponent()
End Sub

End Class

b. Decirle al usuario que cuando reinicie la aplicación ésta ya aparecerá con la cultura seleccionada. Para ello la aplicación guardará la cultura seleccionada en una propiedad llamada ‘cultura’ en la configuración de la aplicación. Cuando se reinicie la aplicación de nuevo será en el método constructor en el que según el valor almacenado en la configuración de la aplicación cargará una cultura u otra.
Ejemplo del código en el método constructor de la clase Editor para que se carge la cultura definida en la configuración de la aplicación.
Public Class Editor

Public Sub New()
If Not (Thread.CurrentThread.CurrentCulture.Name = My.Settings.cultura) Then
'asignar el idioma guardado en My.Settings.Cultura
' al hilo que ejecuta el programa

Thread.CurrentThread.CurrentCulture = New CultureInfo(My.Settings.cultura)
Thread.CurrentThread.CurrentUICulture = New CultureInfo(My.Settings.cultura)
'asigno el tipo de separador para los números decimales ya que la aplicación va a importar datos numéricos de un mismo fichero .txt externo que es independiente a la cultura seleccionada
Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator = ","
Thread.CurrentThread.CurrentCulture.NumberFormat.NumberGroupSeparator = "."

End If
' Llamada necesaria para el diseñador.
InitializeComponent()

End Sub
...
Otro ejemplo, si queremos mostrar un mensaje al usuario indicando la cantidad de dinero que dispone su cuenta corriente y que según la cultura se muestre el símbolo de la moneda predeterminada de su país (lógicamente el código correcto tendría un módulo conversor que utilizase un servicio web para obtener el tipo de cambio vigente entre las diferentes monedas).
Imports System.Globalization

Public Class Form1
Private Sub InfoButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles InfoButton.Click
Dim c As New Cuenta
c.Importe = 1000
System.Threading.Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
MessageBox.Show(c.Importe.ToString("c"))
System.Threading.Thread.CurrentThread.CurrentCulture = New CultureInfo("es-ES")
MessageBox.Show(c.Importe.ToString("c"))
End Sub
End Class

Public Class Cuenta
Private numero As Double
Public Property Importe As Double
Get
Return numero
End Get
Set(ByVal value As Double)
numero = value
End Set
End Property
End Class


c. Si el hilo de ejecución actual no tiene la cultura deseada creo un nuevo hilo de ejecución y mató al hilo actual. Lógicamente cuando se ejecute el nuevo hilo de ejecución en el método constructor asignaré la cultura del nuevo hilo al valor de mi propiedad 'idioma' en este caso (o cultura, el nombre es indiferente, en este caso le he llamado idioma).
Private Sub configuraIdioma(ByVal idioma As String)
My.Settings.cultura = idioma
If Not (Thread.CurrentThread.CurrentCulture.Name = idioma) Then
Call nuevoHilo()
End If
End Sub

Private Sub nuevoHilo()
'cierro el formulario para que se vuelva a abrir ya con el idioma y la cultura correcta
'
'creo un nuevo hilo de ejecución para enlazar con el método nuevoForm() que arranca
'otra instancia diferente de Editor
Dim hilo As New Thread(AddressOf nuevoForm)
hilo.SetApartmentState(ApartmentState.STA)
hilo.Start()
'cierro la aplicación y el hilo actual
Application.Exit()
End Sub

Private Sub nuevoForm()
'el hilo tendrá un retraso de 0.5 segundos
Thread.Sleep(500)
'inicio la aplicación
Application.Run(New Editor)
End Sub
Bueno aquí cuelgo un ejemplo de código de una aplicación para Windows Forms para ver el tema de la Localización. La interfaz y los mensajes están disponibles en español e inglés y así se puede ver el código para cambiar de una a otra cultura la aplicación. A parte la aplicación permite importar el contenido de un fichero de texto separado por puntos y comas a un DataGridView y, una vez validado, permite exportar el contenido del DataGridView a un fichero .xml con una opción de LINQ bastante guapa que nos ayudará a crear archivos xml de forma fácil: literales. Además cuelgo un vídeo donde se podrá analizar las claves del proceso con más tranquilidad.

A día de hoy estoy liado con temas de control de gestión y contabilidad de costes pero tengo cosillas interesantes que iré colgando poco a poco a ver si retomo de nuevo el mundillo de la programación. De hecho si hay ganas por aprender y hay constancia el resultado será óptimo. Esto se contrapone a cuando estudié en la EUETIB, donde no aprendí nada y estaba super desmotivado. Pero uno ha se saber aprender de los errores y las ganas de trabajar no te las puede quitar nadie y menos gente que sabe menos que tú.
Pues bien el próximo vídeo será de consultas SQL en ABAP y explicaré como dejar listo el SAP Netweaver para poder hacer consultas a las tablas que vienen de ejemplo (explicaré el FLIGHT DATA MODEL) y entender un poco lo que son los Dominios, los ‘Data Elements’ y las Tablas. En teoría usando un ERP como éste se pueden obtener cosas guapas como cuadros de mando instantáneos, así como cuentas de pérdidas y ganancias agrupadas por CeCos (centros de coste) que proporcionan información muy útil para llevar una contabilidad analítica y saber con antelación donde estamos ganando y donde perdiendo dinero. SAP es por ahora el ERP líder del mercado, y tengo compañeros que les encanta. En cambio otros consideran que es muy difícil adaptarlo a algunos tipos de empresas, el módulo fiscal -que todo buen programa de contabilidad ha de tener- deja bastante que desear ya que no se actualiza a los cambios normativos y sobre todo, no consolida. Eso sí con dinero San Pedro canta. Como vemos hay opiniones para todos los gustos.