Traduciendo strings compuestos

Éste es el primer artículo de una serie en la que veremos que aún cuando hemos separado los recursos del código, hay que considerar como definimos y armamos los recursos para que nuestro programa se pueda traducir correctamente a otros idiomas.

Una de las situaciones más comunes y más difícil de traducir ocurre cuando tenemos un mensaje compuesto de varios strings.  Tomemos como ejemplo un programa que toma el nombre de un vehículo y un color y los combina para formar un nuevo string.

    1: for (int i = 0; i < ARRAYSIZE(vehicles); i++)
    2: {
    3:     for (int j = 0; j < ARRAYSIZE(colors); j++)
    4:     {
    5:         size_t newStringLength = wcslen(vehicles[i]) + wcslen(colors[j]) + 2;
    6:         LPWSTR coloredVehicle = new WCHAR[newStringLength];
    7:         wcsncpy_s(coloredVehicle, newStringLength, colors[j], _TRUNCATE);
    8:         wcsncat_s(coloredVehicle, newStringLength, L" ", _TRUNCATE);
    9:         wcsncat_s(coloredVehicle, newStringLength, vehicles[i], _TRUNCATE);
   10:  
   11:         printf("%S\n", coloredVehicle);
   12:  
   13:         delete[] coloredVehicle;
   14:     }
   15: }

Asumiendo que los arreglos de colores y vehículos contienen los nombres en inglés obtenemos como resultado los strings “red car”, “green car”, “blue car”, “red boat”, “green boat”, etc.

Supongamos que nuestro plan incluía traducir estos strings y que fácilmente podemos sustituirlos por el equivalente en español.  Corremos el programa y obtenemos lo siguiente: “rojo automóvil”, “verde automóvil”, “azul automóvil”, “rojo barco”, “verde barco”,etc.

Viendo esto podríamos pensar que estamos en una situación donde es necesario modificar el código y ejecutar instrucciones distintas dependiendo del idioma.  En realidad, hay una solución más sencilla, que consiste en utilizar un tercer string para indicar como se combinan los dos anteriores.  La función que nos permite hacer esto es FormatMessage (o alguna de sus variantes utilizadas por las clases de Strings).

    1: for (int i = 0; i < ARRAYSIZE(vehicles); i++)
    2: {
    3:     for (int j = 0; j < ARRAYSIZE(colors); j++)
    4:     {
    5:         size_t newStringLength = wcslen(vehicles[i]) + wcslen(colors[j]) + 2;
    6:         LPWSTR coloredVehicle = new WCHAR[newStringLength];
    7:         DWORD_PTR args[2];
    8:         args[0] = (DWORD_PTR)colors[j];
    9:         args[1] = (DWORD_PTR)vehicles[i];
   10:         ::FormatMessage(
   11:             FORMAT_MESSAGE_FROM_STRING | FORMAT_MESSAGE_ARGUMENT_ARRAY,
   12:             coloredVehicleFormat,
   13:             0 /* ignored */,
   14:             0 /* ignored */,
   15:             coloredVehicle,
   16:             newStringLength,
   17:             (va_list*)args);
   18:  
   19:         printf("%S\n", coloredVehicle);
   20:  
   21:         delete[] coloredVehicle;
   22:     }
   23: }

En este caso “coloredVehicleFormat” también debe ser un string obtenido de los recursos del ejecutable.  En inglés le damos el valor de “%1!s! %2!s!” y en español “%2!s! %1!s!” con lo que obtenemos los resultados esperados.  Y de nuevo es posible compartir el mismo código para ambos idiomas.

Esto aún no es una solución general, pero es lo suficientemente buena para la mayoría de los casos.  Un claro ejemplo de que esto no es suficiente es agregando “motocicleta” o “patines” a la lista de vehículos.  ¿Qué pasa ahora con la concordancia?