Internacionalización con Harbour

Esta entrada se publicó originalmente en Harbour Magazine, mi publicación sobre el lenguaje de programación Harbour.

Este es un artículo de Quim Ferrer para Harbour Magazine.

Introducción

Aquellos que venimos desarrollando aplicaciones, programas o utilidades desde hace ya algún tiempo, tendemos -algunos- a dejar para el final, casi después de la documentación, el tema de la traducción de nuestros programas a otros idiomas.

Excusas como ya lo haré (futuro inconcreto), no tengo aún demanda o es muy costoso en tiempo y recursos, son por lo general las motivaciones para posponer ‘sine die’ el tema de la traducción.

Desde hace bastante tiempo, existen herramientas generalmente del mundo GNU como es gettext https://www.gnu.org/software/gettext que nos han facilitado la transición por el mundo de las traducciones, organizando el trabajo a realizar, para que pueda ser multidisciplinar y colaborativo.

Las GNU Tools nos permiten automatizar tareas para nuestros desarrollos, principalmente :

  • Extraer cadenas alojadas en variables de nuestro código fuente
  • Agrupar dichas cadenas como definiciones en una plantilla
  • Generar a partir de la plantilla anterior un archivo para cada idioma a traducir
  • Obtener un archivo binario resultado de la compilación de los archivos de traducciones

A nivel técnico, el sistema de traducciones GNU-gettext se basa en la construcción y mantenimiento de los siguientes tipos de archivos :

  • POT. Plantilla de objeto portable. Archivo que se obtiene cuando se extraen textos de una aplicación. Es el formato de texto para enviar a los traductores.
  • PO. Objeto portable. Archivo de texto recibido de los traductores. Incluye los textos originales y su correspondencia con las traducciones.
  • MO. Objeto Máquina. Archivo compilado (binario) obtenido a partir del archivo *.po para cada idioma de la traducción.

El uso de este sistema de traducciones gettext de forma masiva por parte de sitios web multilingües, ha facilitado la adopción ‘de facto’ como un estándar de traducción para varios lenguajes de programación, por ejemplo en PHP.

Antecedentes

Uno de los principales problemas para la difusión (o evangelización) de Harbour es la falta de documentación ‘comprensible’ para su público potencial. Los excelentes desarrolladores de Harbour dotan al compilador de funcionalidades increíbles en relación a una audiencia que considera Harbour una mera ‘traducción’ del CA-Clipper de los años 90. Pero estas ‘extensiones’ fabulosas, no llegan a veces, a sus potenciales usuarios.

Normalmente buscar este tipo de información requiere seguir muy de cerca el grupo de desarrollo de Harbour y profundizar en su código fuente.

Una de las primeras aproximaciones que descubrí para abordar el tema de las traducciones en Harbour, fué el excelente trabajo de investigación de José Luis, editor de Harbour Magazine en una entrada de su blog https://www.alanit.com/2003/07/i18n-en-xharbour

En aquellos tiempos xHarbour disponía de las primeras herramientas para la internacionalización y en dicho blog, se describe la problemática desde el punto de vista del desarrollador en Harbour que además, quiera utilizar recursos en forma de *.rc, *.dll, etc.

José Luis nos comenta una herramienta llamada hbdict.exe, que es la encargada de extraer los literales tratados con la función i18n. De dicha herramienta, no tengo constancia que esté portada a Harbour ni podamos disponer de ella, si alguien dispone de información, será un placer incluirla en esta documentación.

El siguiente trabajo de investigación me lleva a la guía de uso de la utilidad make de Harbour, llamada hbmk2.exe. Leyendo los innumerables flags de que dispone, presto atención a dos opciones significativas :

  • -hbl[=<output>] nombre-de-archivo .hbl resultante. macro %{hb_lng} es aceptada en nombre-de-archivo.
  • -lng=<languages> lista de idiomas a ser reemplazados en %{hb_lng} macros en archivos .pot/.po y nombres de archivos y salida .hbl/.po. Lista separada por comas: -lng=en,hu-HU,de

En este punto es donde me doy cuenta por primera vez la relación entre Harbour y los formatos pot/po. La conclusión es evidente, Harbour no trata con el formato compilado *.mo ya que dispone de su propio formato, el *.hbl (HarBour Language?) A nivel binario son muy parecidos y desconozco el motivo por el cual los desarrolladores de Harbour optaron por un formato propio y no producir el formato estándar *.mo

Para utilizar otros sistemas make, harbour/bin dispone de la utilidad hbi18n.exe que también es capaz de generar salida *.hbl a partir de *.po

Cómo puede Harbour aprovechar GNU-gettext ?

Con esta pequeña guía, pretendo facilitar la labor a otros desarrolladores que quieran implementar multilenguaje en sus aplicaciones.

  • Para empezar, definimos una macro para poder implementar cambios globales de forma unitaria. En este ejemplo, transformo también la cadena a UTF8
#define _txt( x ) hb_UTF8ToStr( hb_i18n_gettext(x) )
  • Preparar nuestro código Harbour
@ 2,1 SAY _txt(“Editar”)
  • En Fivewin :
REDEFINE SAY VAR _txt(“Editar”) ID 200 OF oDlg
  • Compilar fuente para obtener salida en formato *.pot
harbour -m -n -j app.prg
  • Descargar poedit, herramienta gratuita de edición y traducción : https://poedit.net/download
  • Ejecutar poedit y crear nueva traducción, Archivo-> Nueva desde archivo POT/PO. La primera vez que ejecutemos poedit, nos pedirá el idioma base de traducción.
  • Elegir la plantilla app.pot generada en el proceso de compilación de Harbour. Poedit nos pregunta por el idioma de traducción
  • Empezar con las traducciones:
  • Guardar traducción, por ejemplo para idioma inglés en.po
  • Vemos que también genera el archivo *.mo que no vamos a utilizar
  • Una vez finalizada(s) la(s) traducción(es) es el momento de generar el binario del idioma o idiomas que cargaremos en nuestra aplicación. Para ello disponemos del superpoder de hbmk2.exe, utilidad make de Harbour.
hbmk2 -hbl en.po
  • La utilidad crea el archivo en.hbl a partir de en.po
  • Ya sólo nos queda implementarlo en nuestra aplicación :
cFile := hb_MemoRead( “en.hbl” )if hb_i18n_Check( cFile )   hb_i18n_Set( hb_i18n_RestoreTable(cFile) )endif

Con este procedimiento, todos los literales en código fuente quedan traducidos y si queremos cambiar de idioma en tiempo de ejecución, basta con apuntar a otro set de idioma, llamando a las instrucciones anteriores.

Cada vez que exista un cambio en nuestro código, solamente habrá que generar de nuevo el archivo de plantilla *.pot, abrir cada archivo de idioma *.po y desde la opción del menú de poedit, Catálogo->Actualizar desde archivo POT. Los cambios anteriores permanecen intactos y las nuevas entradas quedan pendientes de traducir, con facilidad para buscarlas.

Os dejo un enlace a github https://github.com/QuimFerrer/i18n con código de ejemplo y uso de un script make hbmk2, para producir la compilación en múltiples idiomas automáticamente.

Para terminar, os animo a investigar, mejorar y comentar, experiencias que tengáis en la internacionalización y traducción de vuestras creaciones