Inicio > Delphi, Multiidioma > Aplicación multiidioma con GNU Gettext

Aplicación multiidioma con GNU Gettext

He probado algunos sistema de traducción para programas Delphi, entre ellos multiidioma2he de destacar los componentes de TsiLang Component Suite , que me parecen muy buenos y completos.

Estos días necesitaba un paquete gratuíto y revisando páginas y foros he llegado a GNU Gettext for Delphi and C++ Builder. Me parece un paquete muy sencillo de utilizar y por lo que he probado hasta ahora, cómodo y eficaz.

En esta entrada voy a explicar desde cero los pasos que he seguido para conseguir una aplicación multiidioma.

PREPARATIVOS

(1) GNU Gettext

Descargar desde la página «GNU Gettext for Delphi and C++ Builder» el programa de instalación. También se puede encontrar la última versión en SourceForge.

Una vez descargado el ficchero e instalado tendremos lo necesario para generar nuestros ficheros de traducción e integrarlos en el programa.

NOTA: Hay una versión especial compatible con Delphi 2009 que podemos descargar desde aquí, si estamos utilizando esta versión de Delphi..

Si se ha instalado correctamente, nos pedirá reiniciar el sistema.
Una vez instalado, si desde el explorador de Windows pulsamos Click derecho con el ratón sobre una carpeta, nos debería aparecer una nueva opción «Extract Translations to Template«.
generar_traducciones

(2) Poedit for Windows

Necesitaremos una herramienta para poder traducir (ya seamos nosotros o una tercera persona) los ficheros de traducción a los diferentes idiomas. Esto está pensado para que esa posible tercera persona (que puede no tener Dephi instalado)  pueda traducir un fichero sólo con esta herramienta (y el fichero .po como veremos después).

Descargamos el programa desde la página (http://www.poedit.net/) y lo nstalaremos.

Una vez realizados estos dos pasos previos, crearemos un pequeño proyecto de ejemplo donde realizar las pruebas.

Para ello, desde Delphi comenzamos una nueva aplicación y en el formulario generaremos algo similar a esto:

TRADUCCIÓN

(a) Preparar el  proyecto.

Una vez tenemos nuestra aplicación funcionando debemos preparnos para comentar lo necesario para traducción.
(1) Lo primero es añadir la unit gnugettext.pas a nuestro proyecto. Las units que usesn traducciones deberán tenerla en el uses.

NOTA: Si habéis realizado la instalación estandard debería estar en:
«c:\Archivos de programa\dxgettext\gnugettext.pas»

(2) Añadir al OnCreate de nuestros formularios el siguiente código:

// Traducir el formulario
TranslateComponent(Self);

(3) Preparar la estructura de directorios para organizar las traducciones. Dentro del directorio de la aplicación crearemos una carpeta llamada locale y dentro de ella un árbol de directorios similar al que se ve en la imagen.

directorios1

En este ejemplos, yo voy a utilizar 4 idiomas (es, en, fr, ca); Contando que el original va a ser el Español(es) debemos crear la estructura para los otros 3 (inglés, francés y catalán). Nos quedará un arbol de directorios como este:

directorios2

(b) Generar fichero de traducciones (.po)

(1) Para generar el fichero de traducciones del proyecto se debe extraer todas las cadena «traducibles» y almacenarlas en un fichero; Para ello basta con que, desde el explorador de Windows, pulsemos Click derecho sobre la carpeta del proyecto y seleccionemos la opción «Extract Translations to template».
Esto generará un fichero default.po en el directorio de la aplicación.

generar_traducciones

(2) Copiaremos este fichero dentro de los directorios LC_MESSAGES; Tantas copias como directorios tengamos. Después de eso tendremos varios ficheros .po; Uno en el directorio de la aplicación y uno dentro de cada carpeta de traducción:

appdir/appTest.exe
appdir/default.po
appdir/locale/en/LC_MESSAGES/default.po (Traducciones al inglés)
appdir/locale/fr/LC_MESSAGES/default.po (Traducciones al francés)
appdir/locale/ca/LC_MESSAGES/default.po (Traducciones al catalán)

(c) Traducir los ficheros.

En este punto ya se pueden abrir cada uno de los ficheros .po y traducirlo utilizando poedit. Una vez abierto el fichero con poedit, veremos una pantalla como esta:


Una vez finalizada la traducción de palabras y cerrado el programa veremos que en cada directorio aparecen 2 ficheros; El  default.po (original) y el de traducción llamado default.mo.
Repetimos esto con todos los ficheros de traducción.

(d) Cómo utilizar las traducciones en nuestro programa

Una vez los ficheros de traducción estén generados, para utilizarlos desde la aplicación añadirenmos el siguiente código a los botones de activación de los idiomas.

botones_idiomas

// para el catalán
UseLanguage('ca');
RetranslateComponent(Self);
 
// para el inglés
UseLanguage('en');
RetranslateComponent(Self);
...

Si ejecutamos la aplicación veremos que al pulsar los botones cambia es aspecto del formulario.

Para finalizar, aun nos queda un detalle, que es, que la cadena que aparece al pulsar el botón «Hola Mundo!»  no aparece traducida.

Para ello utilizaremos la función de traducción  _().
La llamada que actualmente está así:

MessageDlg('Hola', mtInformation, [mbOK], 0);

Pasará a colocarse así:

MessageDlg(_('Hola'), mtInformation, [mbOK], 0);

PASOS FINALES

Con eso nuestro programa debería quedar traducido completamente. Si movemos el EXE y la carpeta locale a cualquier otro sitio veremos que el programa funciona correctamente. Si sólo movemos el EXE, el efecto es que no aparace nada traducido.

Queda un último detalle, que es integrar las traducciones en el EXE, para que todo quede en el ejecutable y no debamos preocuparnos del directorio locale.

integrar_traducciones

Basta con pulsar click derecho sobre el EXE de nuestra aplicación desde el explorador de Windows, Seleccionar la opción «Embeb Translations» y seleccionar los ficheros de traducción que queremos integrar.

integrar_traducciones2

Con esto debería ser todo.

UPDATE: (20/04/2011)

Pues haciendo pruebas he llegado a obtener el mismo proble que comenta Sil en sus comentarios (en mi caso utilizando Delphi XE). Se trata de que al intentar añadir las traducciones al exe se obtiene un error con la referencia: «6637DB2E-62E1-4A60-AC19-C23867046A89».

Revisando por Internet, parece que se solventa sustituyendo unas constantes que hay en gnugettext.pas.

(1) Buscar esta sección en la unit gnugettext.pas:

  // DetectionSignature: used solely to detect gnugettext usage by assemble
  DetectionSignature: array[0..35] of AnsiChar='2E23E563-31FA-4C24-B7B3-90BE720C6B1A';
  // Embedded Header Begin Signature (without dynamic prefix written by assemble)
  BeginHeaderSignature: array[0..35] of AnsiChar='BD7F1BE4-9FCF-4E3A-ABA7-3443D11AB362';
  // Embedded Header End Signature (without dynamic prefix written by assemble)
  EndHeaderSignature: array[0..35] of AnsiChar='1C58841C-D8A0-4457-BF54-D8315D4CF49D';
  // Assemble Prefix (do not put before the Header Signatures!)
  SignaturePrefix: array[0..2] of AnsiChar='DXG'; // written from assemble

(2) Y sustituirlas por estas:

  // DetectionSignature: used solely to detect gnugettext usage by assemble
  DetectionSignature: array[0..35] of AnsiChar='6637DB2E-62E1-4A60-AC19-C23867046A89';
  // Embedded Header Begin Signature (without dynamic prefix written by assemble)
  BeginHeaderSignature: array[0..35] of AnsiChar='';
  // Embedded Header End Signature (without dynamic prefix written by assemble)
  EndHeaderSignature: array[0..35] of AnsiChar='';
  // Assemble Prefix (do not put before the Header Signatures!)
  SignaturePrefix: array[0..16] of AnsiChar='#0#0#0#0#0#0#0#0';//'DXG'; // written from assemble

Con este ambio ha desaparecido el error y se integran perfectamente las traducciones.

Espero que haya sido útil.

Espero comentarios, sugerencias, errores,… y demás.

Vota este post
  1. David
    miércoles, 5 de mayo de 2010 a las 09:39 | #1

    Hola Neftali.

    Enhorabuena por tu trabajo sobre Delphi aquí y en otros sitios.

    Unas preguntas sobre este sistema:

    1. He probado el sistema con una aplicación sencilla y funciona ok. Pero cuando lo pruebo sobre una un poco más grande al generar el mo, me arroja el error msgfmt: found 1 fatal error context separator within string. Parece un error en el formato de alguna cadena pero como puedo saber donde está, teniendo en cuenta que hay «millones» de cadenas para traducir.

    2. Por otro lado me surge otra duda. Que ocurre cuando en nuestra aplicación agrgamos un nuevo botón,label… a un form. como podemos añadir ese texto a cada uno de los archivos po. Yo no he visto la forma de eliminar, modificar el texto original dentro de un archivo po con el PoEdit.

    Gracias un saludo.

  2. Neftalí
    miércoles, 5 de mayo de 2010 a las 12:14 | #2

    @David
    Hola David.

    En cuanto al primer punto, la verdad es que no me ha pasado hasta ahora. Habría que ver los ficheros o tal vez revisar en los foros de PoEdit (http://www.poedit.net/support.php).

    En cuanto al tema de ampliar la traducción cuando se actualiza el programa, ya está contemplado y es bastante sencillo.
    Puedes revisar cómo hacerlo aquí (http://dybdahl.dk/dxgettext/docs/beta/online/merging.html), pero la idea es crear un nuevo template y realizar un «Merge» con una opción que aparece en el menú contextual.

    Un saludo.

  3. David
    miércoles, 5 de mayo de 2010 a las 14:23 | #3

    Gracias lo probaré… aunque también estoy evaluando los TsiLang Component Suite, que tienen muy buena pinta.

  4. Neftalí
    jueves, 6 de mayo de 2010 a las 08:42 | #4

    @David
    Los TsiLang los he utilizado en proyectos comerciales y funcionan muy bien.
    Es un paquete muy completo, y la gran ventaja no es sólo cómo traduce, sino todas las herramientas añadidas al propio componente para facilitar las traducciones.
    El diccionario es fantástico, puesto que se integra muy bien y posees uno o varios diccionarios que compartes entre todas las aplicaciones, incluso entre un grupo de programadores; De esta forma cuando vas a iniciar una nueva traducción, lo primero es pedirle al diccionario que traduzca lo que ya conoce, de esa forma muchas cadenas ya las obtienes sin trabajo ninguno. Posteriormmente todas las traducciones nuevas se añaden a ese diccionario para ir cada vez haciéndolo más grande.
    El programa de traduccion (interface) también es muy cómo, incluso la herramienta que se provee a los traductores.
    Recuerdo que alguna versión permitía interrogar a algunos webservices para obtener traducciones online.

    Es una de las más completas que he probado, sino la que más, pero no es libre. Para aplicaciones que se van a distribuir con código no es válido, puesto que los componentes son de pago; Para aplicaciones pequeñas también me parece demasiado completo/complejo; Por estas dos razones busqué algo libre y más sencillo.

  5. david
    lunes, 10 de mayo de 2010 a las 10:47 | #5

    Otra opción de los TSILang es la de Traslate source> CONST declaration que da soporte para los RESOURCESTRING lo cual a mi juicio te ahorra mucho trabajo para traducir y encontrar los mensajes (msgbox, showmessage…),desde luego estos Tsilang parece que valen lo que cuestan y que es la forma mas rapida, completa de implementar un solución multiidioma a las aplicaciones.

  6. Neftalí
    lunes, 10 de mayo de 2010 a las 11:50 | #6

    Para aplicaciones grandes, estoy de acuerdo contigo que son los mejores.

    Un saludo.

  7. hclander
    jueves, 17 de junio de 2010 a las 13:50 | #7

    Hola,
    Precisamente estoy con un proyecto bastante grande y estoy buscando una solución para Internacionalizacion…
    La verdad es que estoy hecho un lío y no se cual usar. Estoy probando
    varias alternativas y me gustaría saber cual me recomendais.
    Ya veo que todos valorais bastante bien el TSILang… pero ? alguien ha probado el Paquete opensource Dklang ( http://www.dk-soft.org/products/dklang/ ) o el EMS Advanced Localizer Component Suite ?
    ¿ Pros ? ¿Contras?
    Un saludo.

  8. Terry Yapt
    martes, 22 de junio de 2010 a las 09:50 | #8

    Muy bueno Neftalí, me has ahorrado mucho trabajo.

    Muchas gracias.

  9. kaki
    viernes, 30 de julio de 2010 a las 13:38 | #9

    Muy bueno este aporte. gracias por el tutorial. solo una pregunta lo he probado con un menu y me traduce los subitems pero el item principal no. por ejemplo item principal Archivo que contiene dos subitems Abrir y Cerrar. cuando traduzco al inglés File – Open – Close. siempre se mantiene Archivo en lugar de cambiar a File

  10. Neftalí
    viernes, 30 de julio de 2010 a las 14:57 | #10

    @kaki
    Hola kaki.
    Pues la verdad es que no me ha pasado nada similar. ¿No tienes ningun caracterr extraño, ni nada que esté haciendo que «salte» esa traducción?

  11. Sil
    lunes, 9 de agosto de 2010 a las 21:38 | #11

    Hola, consulta estoy usando esta libreria con el builder 2009. Al momento de hacer «embed translations» me muestra el siguiente error:
    Pach code «6637DB2E-62E1-4A60-AC19-C23867046A89» was not found in .exe file. Are you sure the .exe file has been compiled with the correct libraries? ..aceptar.

    Cambie el .pas por el compitible con D2009. Y el .exe compila correctamente. Venia usando estas librerias correctamente con builder 6 pero por temas de trabajo con caracteres unicode tuve que mudar todo a builder 2009 y se presento este lema..

    Alguien me podra ayudar por favor necesito solucionar esto urgente.
    GRACIAS!

  12. Neftalí
    martes, 10 de agosto de 2010 a las 10:32 | #12

    @Sil
    Hola Sil. Me suena que hay por ahí una versión especial complatible con Dephi2009 de este componente; Tal vez sea debido a estos problemas. Busca por la web.

    Un saludo.

  13. Sil
    martes, 10 de agosto de 2010 a las 14:25 | #13

    Hola Neftalí, gracias por tu respuesta! Ahora estoy usando esa funcion gnugettext.pas compatible con delphi 2009 … pero nose a alguien le paso algo similar..
    :( Help. Gracias.

  14. Neftalí
    miércoles, 20 de abril de 2011 a las 11:30 | #14

    @Sil
    Hola.

    Pues haciendo pruebas he llegado a obtener el mismo proble que comenta Sil en sus comentarios (en mi caso utilizando Delphi XE). Revisando por Internet, parece que se solventa sustituyendo unas constantes que hay en gnugettext.pas.

    (1) Buscar esta sección en la unit gnugettext.pas:

      // DetectionSignature: used solely to detect gnugettext usage by assemble
      DetectionSignature: array[0..35] of AnsiChar='2E23E563-31FA-4C24-B7B3-90BE720C6B1A';
      // Embedded Header Begin Signature (without dynamic prefix written by assemble)
      BeginHeaderSignature: array[0..35] of AnsiChar='BD7F1BE4-9FCF-4E3A-ABA7-3443D11AB362';
      // Embedded Header End Signature (without dynamic prefix written by assemble)
      EndHeaderSignature: array[0..35] of AnsiChar='1C58841C-D8A0-4457-BF54-D8315D4CF49D';
      // Assemble Prefix (do not put before the Header Signatures!)
      SignaturePrefix: array[0..2] of AnsiChar='DXG'; // written from assemble

    (2) Y sustituirlas por estas:

      // DetectionSignature: used solely to detect gnugettext usage by assemble
      DetectionSignature: array[0..35] of AnsiChar='6637DB2E-62E1-4A60-AC19-C23867046A89';
      // Embedded Header Begin Signature (without dynamic prefix written by assemble)
      BeginHeaderSignature: array[0..35] of AnsiChar='';
      // Embedded Header End Signature (without dynamic prefix written by assemble)
      EndHeaderSignature: array[0..35] of AnsiChar='';
      // Assemble Prefix (do not put before the Header Signatures!)
      SignaturePrefix: array[0..16] of AnsiChar='#0#0#0#0#0#0#0#0';//'DXG'; // written from assemble

    Con este ambio ha desaparecido el error y se integran perfectamente las traducciones.

    Un saludo.

  15. lunes, 20 de junio de 2011 a las 03:28 | #15

    Hola:

    En principio agradecerte tus aportes Neftali, desde que me inicie en delphi me han ayudado mucho los de la web clubdelphi jeje no se si te sonara un tal maxinitto jeejejejejej… bueno pues eso en uno de los foros de esa web encontre esta pag y con esto de los lenguajes y ami me funciona de lujo, pero cnd cambio las constantes el error desaparece pero luego muevo el .exe a otro lado y no funcionan las traducciones, despues lo vuelvo a mover a la carpeta donde tengo los locale y si me funcionan, mmm ¿alguna sugerencia?. gracias y saludos.

  16. Neftalí
    lunes, 20 de junio de 2011 a las 07:49 | #16

    @Tejadon
    Hola.
    Supongo que debes mover los ficheros de traducción también ¿no?
    Si no mueves los ficheros generados no los encuentra y no genera la traducción.
    Desde hace tiempo, trabajo con IniLang2 (http://www.inilang2.000a.biz/); Es bastante sencillo y está adaptado por un compañero del Club.

    Un saludo.

  17. lunes, 20 de junio de 2011 a las 16:11 | #17

    @Neftalí pero ese componente no era de pago?

  18. lunes, 20 de junio de 2011 a las 16:22 | #18

    @Neftalí Amm otra cosilla que no viene al caso pero bueno, tengo una aplicacion cliente-servidor, y al enviar un texto desde el servidor al cliente me aparece en carateres raros como parecido a simbolos chinos mezclado con numeros sabes a que se debe eso, uso delphi 2009 y uso los socket predeterminados que trae, tambien conozco los indi pero preferi estos, gracias.

  19. lunes, 20 de junio de 2011 a las 16:26 | #19

    @Neftalí perdona por tanto mensaje jeje mira aqui te adjunto una foto xk no se si me explique bien http://imageshack.us/photo/my-images/585/errorjc.jpg/

  20. Neftalí
    martes, 21 de junio de 2011 a las 08:18 | #20

    @Tejadon
    No.
    IniLang2 está basado en un componente IniLang que puedes encontrar en Torry y que es gratuíto. A partir de ese, este programador lo ha ampliado y mejorado y de ahí el IniLang2.

  21. Neftalí
    martes, 21 de junio de 2011 a las 08:19 | #21

    @Tejadon
    Creo que estas preguntas es más sencillo (y práctico) que las hagas en los foros (http://www.clubdelphi.com).
    Así a primeras se me ocurre que puede tener algo que ver con Unicode, pero habría que saber más cosas.

    Un saludo.

  22. Duilio Juan Isola
    sábado, 25 de junio de 2011 a las 11:37 | #22

    Hola Neftali. He hecho pruebas y me funciona correctamente hasta que decido quitar un componente (Componente.Free).
    Al retraducir la aplicación salta un error que he seguido hasta:

    procedure TTP_Retranslator.Execute;

    if ppinil then begin
    SetWideStrProp(item.obj, ppi, newValue);

    Cuando llega aqui TComponent(item.obj).Name=» y la instrucción falla.

    He buscado información sobre este fallo y no encuentro absolutamente nada.

  23. Neftalí
    lunes, 27 de junio de 2011 a las 09:50 | #23

    @Duilio Juan Isola
    Hola.
    ¿A qué componente te refieres de los que hemos hablado?

  24. Ariel
    lunes, 14 de enero de 2013 a las 05:42 | #24

    Estoy utilizando este componente en un proyecto Delphi 7, funciona maravillosamente, y ahora mismo estoy migrando este proyecto a Delphi XE pero en cuanto adiciono gnugettext al uses y ejecuto me genera Stack Overflow y no hay manera que funciona.

    Alguna idea por favor ?

  25. Neftalí
    lunes, 14 de enero de 2013 a las 10:12 | #25

    @Ariel
    Habría que tener más datos.
    No se si el componente tiene versión para XE.
    ¿Si lo usas en un proyecto en blanco te pasa esto también?

  26. Blue
    viernes, 20 de septiembre de 2013 a las 12:21 | #26

    Yo estoy migrando una aplicacion de 2006 a XE4, y me pasa lo mismo.Me falla algunos componentes al traducirlos.Encontre una revision nueva,(220), pero tampoco funciona :(

  1. Sin trackbacks aún.