Tengo que traducir (perdón, internacionalizar) una aplicación que vamos a subir a producción en el próximo mes.
Antes de continuar escribiendo sobre como soportar internacionalización en nuestras aplicaciones, hay varios conceptos que requieren ser comentados para una posterior mayor comprensión de este post.
Cuando comienzo a buscar información en Internet, lo primero que me llama la atención es que encuentro las palabras Internacionalización, Globalización, Localización, Lenguaje, Cultura, UI Cultura, etc, y lo cierto es que todas ellas me parecen que estén hablando de lo mismo y no logro encontrar diferencias claras entre ellas.
La internacionalización es el proceso de creación de aplicaciones internacionales. Esto es, aplicaciones que soportan distintos lenguajes y culturas. Es decir, yo “internacionalizo”, tú “internacionalizas”, etc.
Hablo de lenguajes y culturas porque aunque podría parecerlo, no son lo mismo. De hecho, la palabra cultura abarca muchos más aspectos de los inicialmente podríamos sospechar. Por ejemplo, está claro que el idioma Español se habla en España y en México, pero está claro que el uso del idioma no es el mismo en España y en México. Por ejemplo, podrías traducir “compañero” por “compi” (en español) y por “güey” (en mexicano). De este modo y ciñéndonos exclusivamente al idioma, bien podríamos soportar el idioma Español para ambos países (ambos utilizan la palabra “compañero” y aquí paz y después gloria) o bien soportamos el idioma Español-España (compi) y Español-México (güey). Sin duda esta última aproximación es más profesional y mucho más cercana al usuario. Pues bien, esta diferenciación es lo que ASP.NET denomina UICulture (cultura de la interfaz de usuario), mientras que por otro lado tenemos la Culture, que lo que determina ya no es el idioma de la interfaz de usuario, sino el formato de moneda, formato de fecha y hora y cualquier otra propiedad relativa a la configuración regional de la aplicación.
En nuestro caso, y después de haber leído lo aquí expuesto, llegamos a la conclusión de que necesitamos Español-España y Español-México tanto para el lenguaje como para la cultura.
Por otro lado, la palabra localización hace mención a la traducción de la interfaz de usuario a un lenguaje concreto. Se dice que localizamos recursos cuando seleccionamos, en tiempo de ejecución, un determinado conjunto de literales (almacenados normalmente en ficheros de recursos .resx) para su utilización en la interfaz de usuario. Como ya hemos dicho antes, a esto ASP.NET lo llama UICulture.
Y la palabra globalización justo viene a resolver las diferencias en la cultura actual del usuario. Por ejemplo, moneda, fecha, etc. Es decir, globalizamos cuando mostramos una fecha a un usuario en el formato adecuado según la cultura actual seleccionado y a lo que ASP.NET llama Culture.
Cómo localizar y globalizar páginas .aspx no es el propósito de este post. En cambio, lo que pretendemos resolver en como localizar y globalizar código Javascript de cliente. Esto lo digo porque en la parte del servidor, ASP.NET nos ofrece una sintaxis clara y una colección de recursos fácilmente utilizables, sin embargo, cuando de traducir un simple alert("Hola Mundo"); comienzan a surgir los problemas.
Localizar ficheros .js
ASP.NET soporta la localización y globalización de ficheros .js a través del control ScriptManager.
Aunque hay otras alternativas válidas como http://madskristensen.net/post/Localize-text-in-JavaScript-files-in-ASPNET.aspx o http://www.aspsnippets.com/Articles/Localizing-string-messages-in-JavaScript-using-ASP.Net-Localization.aspx yo voy a tirar por la versión oficial que me parece más conveniente.
Al respecto de embeber recursos web en un ensamblado, puedes visitar esta otra entrada http://panicoenlaxbox.blogspot.com/2010/12/webresourceaxd-y-scriptresourceaxd.html donde hablo de ello.
Lo que queremos en esta entrada es localizar y globalizar Javascript.
Para localizar Javascript a través de ScriptManager hay que hace lo siguiente:
· Agregar a nuestro ensamblado referencias a:
o System.Web
o System.Web.Extensions
· Importar el espacio de nombres System.Web.UI en el fichero Assembly.info.
· Crear un recurso embebido en nuestro ensamblado con nuestro código Javascript. Por ejemplo, panicoenlaxbox.js
· Marcar el recurso como embebido en el ensamblado y agregar la instrucción
Assembly: WebResource oportuna en el fichero Assembly.info.
· Agregar un fichero de recursos panicoenlaxbox.resx que representará la UICulture neutral y otros tantos para el resto de culturas de interfaz de usuario: panicoenlaxbox.en.resx, panicoenlaxbox.fr.resx, etc. ((lo cierto es que el nombre del fichero de recursos no tiene por qué ser igual al fichero .js, pero es una buena convención)
· Agregar la instrucción Assembly: ScriptResource oportuna en el fichero Assembly.info para que se genere automáticamente una clase Javascript en el cliente a partir del fichero de recursos localizado.
· Establecer a True la propiedad EnableScriptLocalization de nuestro control ScriptManager.
· Cargar el recurso embebido con el control ScriptManager con la siguiente instrucción:
<asp:ScriptReference Assembly="NombreEnsamblado" Name=" NombreEnsamblado.NombreFichero.js" />
<Assembly: WebResource("NombreEnsamblado.panicoenlaxbox.js", "text/javascript")>
<Assembly: ScriptResource("NombreEnsamblado.panicoenlaxbox.js", " NombreEnsamblado.panicoenlaxbox ", "MiEspacioDeNombres.MiClaseJavascript")>
En la instrucción Assembly:ScriptResource:
· El primer parámetro es el la ruta al fichero .js,
· El segundo parámetro es la ruta al fichero .resx (sin extensión)
· El tercero es el nombre de clase javascript que se creará con el contenido del fichero de recursos, que puede ser tan sencillo como “Mensajes” o tan complicada como “MiEmpresa.MiAplicacion.Mensajes”.
Haciendo esto veremos que, si el código del fichero panicoenlaxbox.js era tan sencillo como esto:
var panicoenlaxbox = " panicoenlaxbox.js";
alert("debug " + panicoenlaxbox);
Ahora pasará a tener esto cuando lo veamos en el cliente (fijarse como se ha inyectado el wrapper javascript para el fichero de recursos):
// Name: RecursosJs.panicoenlaxbox.js
// Assembly: RecursosJs
// Version: 1.0.0.0
// FileVersion: 1.0.0.0
var panicoenlaxbox = " panicoenlaxbox.js";
alert("debug " + panicoenlaxbox);
Type.registerNamespace('TAB.MSS.Mensajes');
TAB.MSS.Mensajes ={
"HolaMundo":"Hola Mundo!!"
};
if(typeof(Sys)!=='undefined')Sys.Application.notifyScriptLoaded();
Además de todo esto, en nuestro página .aspx tendremos que establecer la propiedad UICulture = “auto” en la directiva Page, para que tome la primera cultura disponible en el navegador (enviada a nuestra página a través de cabeceras http), o bien forzar la cultura desde código de servidor.
¿Y si tengo que localizar más de un fichero javascript pero comparten el mismo fichero de recursos?
Lo que quiero decir en este punto es que no es necesario una relación 1:1 entre 1 fichero .js y 1 fichero .resx.
La solución pasa por incluir siempre un fichero .js con su .resx asociado (WebResource y ScriptResource en Assembly.info) y el resto de ficheros .js que se aprovechen del wrapper creado para el primero. Lo que hay que tener en cuenta es que este primer fichero .js siempre tiene que ir en todas las páginas (tarea sencilla si por ejemplo tenemos un ScriptManager en nuestra página .master).
¿Y sí no tengo un fichero .js pero quiero traducir igualmente código javascript?
Pues, aunque esté vacío, tendrás que crear un fichero .js en tu ensamblado y servirlo.
Globalizar javascript
Cuando hablamos de globalizar Javascript, estamos hablando de la posibilidad de trabajar en cliente con una cultura específica que nos facilite desde cliente el formato de monedas, fechas, etc.
Siendo así, si establecemos la propiedad EnableScriptGlobalization a True de nuestro control ScriptManager, veremos cómo se inyecta el siguiente código en nuestra página (variando siempre en función de la cultura actual).
{
"name":"en-US",
"numberFormat":
{
"CurrencyDecimalDigits":2,
"CurrencyDecimalSeparator":".",
"IsReadOnly":false,
"CurrencyGroupSizes":[3],
"NumberGroupSizes":[3],
"PercentGroupSizes":[3],
"CurrencyGroupSeparator":",",
"CurrencySymbol":"$",
"NaNSymbol":"NaN",
"CurrencyNegativePattern":0,
"NumberNegativePattern":1,
"PercentPositivePattern":0,
"PercentNegativePattern":0,
"NegativeInfinitySymbol":"-Infinity",
"NegativeSign":"-",
"NumberDecimalDigits":2,
"NumberDecimalSeparator":".",
"NumberGroupSeparator":",",
"CurrencyPositivePattern":0,
"PositiveInfinitySymbol":"Infinity",
"PositiveSign":"+",
"PercentDecimalDigits":2,
"PercentDecimalSeparator":".",
"PercentGroupSeparator":",",
"PercentSymbol":"%",
"PerMilleSymbol":"\u2030",
"NativeDigits":["0","1","2","3","4","5","6","7","8","9"],
"DigitSubstitution":1
},
"dateTimeFormat":
{
"AMDesignator":"AM",
"Calendar":
{
"MinSupportedDateTime":"@-62135568000000@",
"MaxSupportedDateTime":"@253402300799999@",
"AlgorithmType":1,
"CalendarType":1,
"Eras":[1],
"TwoDigitYearMax":2029,
"IsReadOnly":false
},
"DateSeparator":"/",
"FirstDayOfWeek":0,
"CalendarWeekRule":0,
"FullDateTimePattern":"dddd, MMMM dd, yyyy h:mm:ss tt",
"LongDatePattern":"dddd, MMMM dd, yyyy",
"LongTimePattern":"h:mm:ss tt",
"MonthDayPattern":"MMMM dd",
"PMDesignator":"PM",
"RFC1123Pattern":"ddd, dd MMM yyyy HH\':\'mm\':\'ss \'GMT\'",
"ShortDatePattern":"M/d/yyyy",
"ShortTimePattern":"h:mm tt",
"SortableDateTimePattern":"yyyy\'-\'MM\'-\'dd\'T\'HH\':\'mm\':\'ss",
"TimeSeparator":":",
"UniversalSortableDateTimePattern":"yyyy\'-\'MM\'-\'dd HH\':\'mm\':\'ss\'Z\'",
"YearMonthPattern":"MMMM, yyyy",
"AbbreviatedDayNames":["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],
"ShortestDayNames":["Su","Mo","Tu","We","Th","Fr","Sa"],
"DayNames":["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],
"AbbreviatedMonthNames":["Jan","Feb","Mar","Apr","May","Jun","Jul",
"Aug","Sep","Oct","Nov","Dec",""],
"MonthNames":["January","February","March","April","May","June","July",
"August","September","October","November","December",""],
"IsReadOnly":false,
"NativeCalendarName":"Gregorian Calendar",
"AbbreviatedMonthGenitiveNames":["Jan","Feb","Mar","Apr","May","Jun","Jul",
"Aug","Sep","Oct","Nov","Dec",""],
"MonthGenitiveNames":["January","February","March","April","May","June","July",
"August","September","October","November","December",""]
}
}';
Como vemos se ha volcado en cliente un objeto con todo lo necesario para trabajar con la cultura actual, pero ¿Cómo trabajo ahora con este objeto? Pues, ASP.NET AJAX que es muy agradecido nos ofrece también toda una API de cliente (o framework, perdón) para llevar a cabo casi cualquier operación. Además ha extendido los objetos básicos de Javascript (Date, Number, etc.) con métodos útiles para llevar a cabo automáticamente tareas de globalización.
Puedes ver toda la referencia de cliente de ASP.NET AJAX en http://msdn.microsoft.com/en-us/library/bb397536.aspx
Aunque estudiar esta referencia ya escapa al propósito de este post, te adelanto como mostrar un alert con la moneda actual.
alert(String.localeFormat("{0:c}", 12345.67));
Un saludo!
¡Hola! Si estan interesados en localizar web software, PC software, móvil software o cualqier otro tipo de software, reccomendo con calor esta rápida y intuitiva herramienta de localización: http://poeditor.com/.
ResponderEliminar