Interfaz de usuario multilingüe: un ejecutable, N idiomas (primera parte)
En una de las grandes ironías de la vida, la documentación sobre el interfaz de usuario multilingüe (MUI) no está disponibe en español. Un tema extenso como este no se puede discutir a fondo en un sólo artículo, así que en esta entrega hablaremos del problema de localización y los primeros pasos necesarios para separar nuestro código de los datos utilizados para desplegar en pantalla.
Un programa de ejemplo
1: #include <windows.h>
2:
3: int main(int argc, WCHAR** argv)
4: {
5: ::MessageBox(NULL, L"Hello world!", L"Sample App", MB_OK);
6: return 0;
7: }
El clásico "Hola mundo" con sabor a Windows. Este programa despliega un cuadro de diálogo con título "Sample App" y el mensaje "Hello world!". Para un programa limitado a usarse en un idioma este método puede ser suficiente - aunque esto puede acarrear problemas si tenemos que desplegar el mismo mensaje en diferentes secciones del programa. Con unos pequeños cambios podemos resolver este problema.
1: #define MENSAJE L"Hello world!"
2: #define TITULO L"Sample App"
3:
4: #include <windows.h>
5:
6: int main(int argc, WCHAR** argv)
7: {
8: ::MessageBox(NULL, MENSAJE, TITULO, MB_OK);
9: return 0;
10: }
Esta factorización es clave, y nos abre un gran número de posibilidades. Con esta perspectiva nos damos cuenta que podemos separar los mensajes que aparecen en el interfaz de usuario de nuestro código. Un primer paso sería mover todos los mensajes a su propio archivo de encabezado. Una desventaja de este método es que necesitamos recompilar nuestro código para cada idioma.
Windows y Visual Studio nos proporcionan una solución que va aún más lejos, con el uso de archivos de recursos (página no disponible en español). De esta forma podemos guardar texto, imágenes, cuadros de diálogo o hasta recursos arbitrarios en un archivo separado de nuestro código.
resource.h
1: #define IDS_TITULO 1001
2: #define IDS_MENSAJE 1002
resource.rc
1: #include "resource.h"
2:
3: STRINGTABLE
4: BEGIN
5: IDS_TITULO "Hello world!"
6: IDS_MENSAJE "Sample App"
7: END
main.cpp
1: #include <windows.h>
2: #include "resource.h"
3:
4: #define BUFFER_MAX 100
5:
6: int main(int argc, WCHAR** argv)
7: {
8: WCHAR mensaje[BUFFER_MAX];
9: WCHAR titulo[BUFFER_MAX];
10: ::LoadString(::GetModuleHandle(NULL), IDS_MENSAJE, mensaje, BUFFER_MAX);
11: ::LoadString(::GetModuleHandle(NULL), IDS_TITULO, titulo, BUFFER_MAX);
12: ::MessageBox(NULL, mensaje, titulo, MB_OK);
13: return 0;
14: }
Nuestro código se complica un poco, pero ahora tenemos la flexibilidad de reconstruir nuestra aplicación para cualquier idioma con sólo compilar los archivos .RC, o con un poco de ingenio se pueden compilar todos los idiomas a partir de una base de código utilizando "makefiles" en una estructura de directorios que separe el código en común de los archivos de recursos. (Documentación de LoadString).
Ahora ya es posible que los expertos en localización modifiquen los archivos .rc sin preocuparse por el código. Y los desarrolladores pueden trabajar en la lógica del programa sin preocuparse por mantener la traducción sincronizada. Hasta aquí todo parece funcionar bien, ¿pero no sería mejor que incluso el código y los recursos fueran independientes después de la instalación? En la próxima entrega veremos como MUI nos permite hacer este cambio.