Java i18n Properties Encoding
Índice:
- 1. Introducción
- 2. Exposición
- 3. Proposición
- 4. Soluciones
- 5. Consideraciones
- 6. Code Draft
- 7. Proyecto en SourceForge
- 8. Knowledge Request
- 9. Nota del autor
Introducción
Versión de Java en el momento de escribir este artículo 1.6.0_22.
IDE utilizado, Eclipse Helios SR1 (última release hasta el momento).
Voy a exponer el tema de la internacionalización en java mediante ficheros .properties, y su problemática, que siempre supone algunos quebraderos de cabeza para aquellos que sigen las instrucciones originales de (lo que era) SUN (ahora Oracle) al respecto.
Una vez quede expuesto el problema a lo largo de este documento, no nos quedaremos ahí, sino que como es habitual en Autentia, voy a proponer una manera de enfrentarse a él y como novedad voy a cargar el código fuente en sourceforge a modo de proyecto, para que todos podamos colaborar en mejorarlo o pedir cámbios, que pueden servir para otros en el futuro.
El problema tiene su origen en la política/filosofía de mantener siempre compatibilidad hacia atrás entre versiones anteriores. Si nos remontamos a la versión 1.3.1_20 (última para Windows, o a la versión 1.3.1_25 última para Solaris, sendas tuvieron su EOL (End Of Life) el 25 de Octubre de 2004) nos encontramos que podemos bajar una versión “English” y luego un paquete distinto de internacionalización. Esta versión “English” que es la base, venía codificada a ISO-8859-1.
Aunque parezca prehistoria hablar de Java 1.3, aún existen contextos en los que se utiliza. En mi vida profesional podría enumerar varios proyectos en Bancos que siguen en producción basándose en ésta. Por eso cuando creo librerías me gusta que sean compatibles hacia atrás 🙂
Del siguiente link podéis descargar cualquier versión que haya existido, tanto JDK, JRE como la Documentation: Oracle Tech Network
Exposición
Por defecto los ficheros .properties se codifican con el encoding ISO-8859-1, suponiendo un trabajo adicional codificar por ejemplo caracteres en Japonés u otro lenguaje que salga de esa codificación.
Por ejemplo, para escribir en castellano, inglés y chino la palabra “Añadir” tendríamos que escribir en nuestros ficheros .properties:
Encoding ISO-8859-1
language_es.properties => Castellano: A\u00F1adir
language_en.properties => Inglés: Add
language_zh.properties => Chino: \u6DFB\u52A0
A los sufridos expertos en la materia seguro que les viene a la mente algún plugin de su IDE que les hace el trabajo sucio como Eclipse RBE o la aplicación incluida en la máquina virtual de Java: native2ascii tool.
¿A caso no es esto un problema que nos resta tiempo cada vez que queramos añadir idiomas a cualquier aplicación?, en mi humilde opinión SI. Creo que es un trabajo muy costoso, que además termina en algo que si no utilizamos un IDE o ayuda contextual es casi inmantenible.
Imaginemos por ejemplo un fichero lleno de:
\u6DFB\u52A0, A\u00F1adir, …
Y que tengamos que cambiar algunos textos de ese fichero o simplemente crear nuevos idiomas con el mismo modus operandi… seguro que nos viene a la cabeza aquello de: ¡¡NOOOO!!
Segun Sun (ahora Oracle): Los caracteres que se salen del enconding ISO-8859-1 debemos codificarlos así dentro del fichero .properties puesto que java al cargar el fichero como un objeto Properties lo interpreta con el susodicho encoding, y cito textualmente:
API JDK 1.6: java.util.Properties
class java.util.Properties
The load(Reader) / store(Writer, String) methods load and store properties from and to a character based stream in a simple line-oriented format specified below. The load(InputStream) / store(OutputStream, String) methods work the same way as the load(Reader)/store(Writer, String) pair, except the input/output stream is encoded in ISO 8859-1 character encoding. Characters that cannot be directly represented in this encoding can be written using Unicode escapes ; only a single ‘u’ character is allowed in an escape sequence. The native2ascii tool can be used to convert property files to and from other character encodings.
¡Be water my friend!
Proposición
¿No sería mejor simplemente tener los ficheros .properties en el encoding que nosotros deseáramos y tuviéramos la opción de indicárselo a Java para que lo tratase correctamente?
Nos evitaríamos así tener que codificar los caracteres del mismo. Es decir para escribir “Añadir” no escribiríamos “A\u00F1adir” sino simplemente “Añadir”, o para escribirlo en chino no escribiremos “\u6DFB\u52A0” sino “”. Siendo muchísimo más fácil y humano mantener ese fichero de idioma.
Veamos cómo quedaría el fichero anteriormente expuesto por ejemplo codificado en UTF-8:
Encoding ISO-8859-1
language_es.properties => Castellano: A\u00F1adir
language_en.properties => Inglés: Add
language_zh.properties => Chino: \u6DFB\u52A0
Encoding UTF-8
language_es.properties => Castellano: Añadir
language_en.properties => Inglés: Add
language_zh.properties => Chino:
Visto así casi que podríamos mandar el fichero .properties al traductor, en muchos casos llamado: Google, y de apellido: Translator. Sigue siendo un trabajo tedioso, pero al no tener que convertir: Añadir a A\u00F1adir, insisto que simplemente es mucho más humano y mantenible por terceros.
Veamos a continuación que para nuestra tranquilidad sí es posible.
Soluciones
Solución 1 – “Sólo para valientes”
La primera forma de atacar al problema es pasar en tercera de los .properties y utilizar ficheros XML y un api tipo JDom por ejemplo. Adaptando nuestro código. Esto no está contemplado en este documento, lo expongo simplemente como una idea.
Nota para Java 1.7
New Feature – Language-level XML:
Language-level XML support would add support for literal XML construction, data conversion, navigation, and streaming.
Parece ser que variantes a la solución 1 serán más factibles a partir de JDK 1.7, y sobre todo por el incremento de rendimiento que parece tendrá la 1.7 respecto de la 1.6/1.5
Solución 2 – “Lucha mínima”
La segunda forma de atacar al problema es cargar el fichero .properties indicando el enconding en el momento de su lectura, en el que está formateado y trasformarlo al encoding deseado.
Nota: hay ocasiones en las que desconocemos el enconding del fichero .properties, bien porque no lo hemos creado nosotros, o bien porque no tenemos un entorno de desarrollo (IDE tipo Eclipse) o aplicación tipo Notepad++ que nos lo diga…
Si desconocemos el enconding del fichero .properties, para acometer la solución 2 nos encontramos con un problema, puesto que Java no dispone de un mecanismo para averiguar el encoding de un fichero, esto es así por la propia naturaleza del encoding.
Aunque existen algoritmos para intentar averiguarlo, por ejemplo:
Mozilla UniversalCharsetDetection y su implementación JCharDet
CodeHaus GessEnc
Básicamente funcionan leyendo los caracteres del fichero e intentando casar el conjunto en un encoding que los soporte, o también apoyándose en el BOM.
Estos algoritmos, aunque tienden a una precisión altísima, casi del 100%, seguro que en un idioma muy concreto o menos extendido nos encontramos alguna sorpresa. Aún así las tendremos en consideración en nuestra pequeña librería.
Consideraciones
Hasta aquí parece que la solución es, si tenemos un fichero .properties con encoding ISO-8859-1, cogemos nuestro IDE favorito y le cambiamos el encoding a UTF-8 y utilizamos esta librería que estamos creando. La respuesta es NO.
Si cambiamos con en el eclipse o cualquier otro IDE el encoding tendremos que el fichero se convierte en algo repleto de caracteres extraños, de nuevo esto es por la propia naturaleza del encoding.
Por ejemplo en Eclipse sería, en las opciones del fichero:
Encoding original ISO-8859-1
Encoding cambiado manualmente UTF-8
Para tener un fichero correcto tenemos que crear un fichero nuevo desde 0, y estando vacío asignarle el encoding y escribir su contenido. O copiar el contenido de otro y arreglar todos los caracteres extraños aparecidos.
Entonces podremos utilizar este API correctamente
Code Draft
El api que acompaña a este documento dispondrá de varias opciones para hacer la carga de los ficheros .properties, ésta es mi propuesta:
Sin nuestro api tenemos el método original de Java:
ResourceBundle.getBundle(..)
Con nuestro api:
1. “El menos adecuado” – Cargar el fichero .properties asumiendo que su enconding original es ISO 8859-1 (Java .properties default) y tratándolo como de un UTF-8:
ResourceBundleEx.getBundle(..)
Esto nos permite trabajar con los .properties originales, sin cambiarles el enconding, pero nos evita que se muestren caracteres extraños, estando el límite en todo lo que el UTF-8 soporte.
Advertencia: Pueden no mostrarse correctamente algunos caracteres.
2. “El más adecuado” – Cargar el fichero .properties habiendo establecido su encoding a cualquiera que nosotros indiquemos y lo trataremos como el que nos convenga (clase Charset)
ResourceBundleEx.getBundle(.., “ISO-8859-1”, “UTF-8”)
ResourceBundleEx.getBundle(.., Charset, Charset)
Esta opción es la mas custom, nos permite trabajar con un .properties al que le hayamos cambiado el encoding como nos haya parecido.
3. “No incluido en el api de este tutorial” – Cargar el fichero .properties detectando su encoding automáticamente mediante algún algoritmo de detección y tratándolo como el enconding que nosotros indiquemos (clase Charset)
ResourceBundleEx.getBundle(.., “UTF-8”)
ResourceBundleEx.getBundle(.., Charset)
Con esta opción nos la jugamos con un algoritmo de detección automática de encoding, que como hemos visto no son 100% fiables por la naturaleza propia del encoding, pero es tan válida como la opción 2.
Proyecto en SourceForge
Disponible en: http://sourceforge.net/projects/javai18npropenc/
Para bajar el código fuente con subversion: https://javai18npropenc.svn.sourceforge.net/svnroot/javai18npropenc
Dejo para descargar las 2 librerías y sus respectivos proyectos de test donde utilizamos el api a modo de ejemplo sobre ficheros .properties con diferentes encondings para cada caso.
i18npropenc13.jar -> versión para JDK 1.3, 1.4 y 1.5
i18npropenc16.jar -> versión para JDK 1.6
Knowledge Request
Todo esto ha sido creado con la intención de compartir conocimiento y apoyaros en vuestros proyectos, espero que mediante los comentarios en esta página y los del propio proyecto de sourceforge:
Expreséis todas vuestras dudas.
Hagáis feature-requests.
Notifiquéis bugs 🙂
Y sobre todo espero que os sea de utilidad el código.