Como traducir aplicaciones Qt4

Este es un post que me prometí a hacer en un post anterior. Soy un gran admirador del toolkit Qt4 y su binding para Python PyQt4 (ó PySide), es realmente excelente y se pueden lograr cosas asombrosas con él! muchos creen de manera errónea que se trata de un toolkit para hacer interfaces gráficas, pero esto no es así, las GUI son solo una parte del toolkit. Entre otras funcionalidades tenemos sockets, OpenGL, y todo un sistema para traducir nuestras aplicaciones que hacemos utilizando Qt4 y mucho más.

En este post vamos a ver como traducir una aplicación hecha en Python y PyQt4.

Comencemos

Vamos a considerar el caso de una aplicación que está totalmente escrita en código, es decir, la UI no está hecha con Qt4 Designer. Pero no teman, si lo hacen con Designer es aún más fácil! 😉

Supongamos que tenemos un script Python con nuestra aplicacion llamado demo.py como el siguiente:

#!/usr/bin/env python
#-*- encoding: utf-8 -*-

"""
DEMO APLICATION DEMOSTRATING Qt4 Translations

This is part of my blog post:
    https://ralgozino.wordpress.com/2011/06/25/como-traducir-aplicaciones-qt4/
"""

import sys, os
from PyQt4 import QtCore, QtGui

__author__ = "Ramiro Algozino"
__email__ = "algozino@gmail.com"
__copyright__ = "copyright (C) 2011 Ramiro Algozino"
__license__ = "GPLv3"
__version__ = "1.0"

class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        QtGui.QMainWindow.__init__(self)

        self.setWindowTitle(self.trUtf8("Qt4 i18n Demo"))
        self.setWindowIcon(QtGui.QIcon.fromTheme("config-language"))
        self.w = QtGui.QWidget()
        self.setCentralWidget(self.w)

        self.layout = QtGui.QVBoxLayout(self.w)

        self.label = QtGui.QLabel(
            self.trUtf8("This is a text that can be translated to any language using the features of Qt4 Toolkit.")
        )
        self.label.setWordWrap(True)
        self.button = QtGui.QPushButton(
            self.trUtf8("Dialog")
        )
        self.button.clicked.connect(self.dialog)

        self.layout.addWidget(self.label)
        self.layout.addWidget(self.button)

    def dialog(self):
        QtGui.QMessageBox.information(
            self,
            self.trUtf8("Dialog Title"),
            self.trUtf8("This is the dialog descriptive text."),
            QtGui.QMessageBox.Ok | QtGui.QMessageBox.Cancel
        )

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    # Get the locale settings
    locale = unicode(QtCore.QLocale.system().name())
    # Apply the translations that we made.
    translator=QtCore.QTranslator()
    translator.load(os.path.join(os.path.abspath(
        os.path.dirname(__file__)),
        "demo_" + locale))
    app.installTranslator(translator)
    # This is to make Qt use locale configuration; i.e. Standard Buttons
    # in your system's language.
    qtTranslator=QtCore.QTranslator()
    qtTranslator.load("qt_" + locale,
                    QtCore.QLibraryInfo.location(
                    QtCore.QLibraryInfo.TranslationsPath)
                    )
    app.installTranslator(qtTranslator);

    mw = MainWindow()
    mw.show()
    sys.exit(app.exec_())

Les recomiendo que lo bajen (apretando en el botoncito view source en la parte superior derecha del código) y lo miren detenidamente.  Si lo ejecutamos, obtenemos esta ventana:

Ventana Demo
Ventana Generada

Y presionando sobre el botón que dice “Dialog” nos muestra un dialogo estándar de información con un botón “Aceptar” y un botón “Cancelar” como este:

Demo Dialog
Dialogo de Información

Ahora bien, resluta que nuestro programa despega y tenemos usuarios de varias partes del mundo que quieren usarlo, pero no todo el mundo entiende Inglés (y aún menos Español) asi que nos gustaría traducirlo. Para hacer esto tenemos varias opciones:

  1. Creamos otra copia de demo.py y le cambiamos todos los strings al idioma que queramos.
  2. Usamos Gnome gettext.
  3. Usamos las capacidades de traducción de Qt4 (Linguist).

Obvio que vamos a usar la última opción..

Si han programado interfaces gráficas con PyQt4 anteriormente notarán algunas sutilezas en el código del script demo.py. Lo primero que les puede llamar la atención es que los botones Aceptar y Cancelar están en español, mientras que el resto de la interfaz está en Inglés. Si miramos detenidamente el código, cuando se crea el objeto aplicación vemos unas lineas que seguramente ustedes no han usado (ya que están leyendo este post):

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    # Get the locale settings
    locale = unicode(QtCore.QLocale.system().name())
    # Apply the translations that we made.
    translator=QtCore.QTranslator()
    translator.load(os.path.join(os.path.abspath(
        os.path.dirname(__file__)),
        "demo_" + locale))
    app.installTranslator(translator)
    # This is to make Qt use locale configuration; i.e. Standard Buttons
    # in your system's language.
    qtTranslator=QtCore.QTranslator()
    qtTranslator.load("qt_" + locale,
                    QtCore.QLibraryInfo.location(
                    QtCore.QLibraryInfo.TranslationsPath)
                    )
    app.installTranslator(qtTranslator);

    mw = MainWindow()
    mw.show()
    sys.exit(app.exec_())

Puntualmente presten atención a estas líneas:

    # Get the locale settings
    locale = unicode(QtCore.QLocale.system().name())
    # Apply the translations that we made.
    translator=QtCore.QTranslator()
    translator.load(os.path.join(os.path.abspath(
        os.path.dirname(__file__)),
        "demo_" + locale))
    app.installTranslator(translator)
    # This is to make Qt use locale configuration; i.e. Standard Buttons
    # in your system's language.
    qtTranslator=QtCore.QTranslator()
    qtTranslator.load("qt_" + locale,
                    QtCore.QLibraryInfo.location(
                    QtCore.QLibraryInfo.TranslationsPath)
                    )
    app.installTranslator(qtTranslator);

En la primer línea de este conjunto que les resalto obtenemos la configuración regional local del sistema operativo, en mi caso, la variable locale queda con el valor es_AR. Luego creamos dos objetos, uno llamado translator y otro llamado qtTranslator, que son instancias del objeto QTranslator que a su vez pertence al paquete QtCore.

Fijensé que cuando llamamos al método load() del objeto qtTranslator, le pasamos como primer parámetro el string “qt_” concatenado con nuestra variable locale. Con esto lo que logramos es cargar en el objeto las traducciones estándar que trae Qt4; que están todas en los archivos qt_<nombre del locale>.qm en la carpeta /usr/share/qt4/translations/

Y después con el método installTranslator() del objeto app le asociamos el qtTranslator y, por lo tanto, las traducciones a nuestra aplicación.

Es decir, hasta acá, Qt nos traduce todo los textos que genera el toolkit al idioma del locale seteado, como por ejemplo, el texto de los botones estándar de los diálogos, “Aceptar” en vez de “OK”, “Cancelar” en vez de “Cancel”, etc.

Hasta acá tenemos traducida la mitad de nuestra aplicación, Qt se encarga solito de traducirnos muchas cosas, pero los strings que están en el código fuente siguen intactos.

Si se fijan, en vez de pasarle la string a los métodos de Qt le pasamos el resultado de un método llamado trUtf8. Para hacerlo más gráfico veamos un  ejemplo:

Para setear el título de la ventana, lo estándar sería hacer algo así:

self.setWindowTitle("string con el titulo de la ventana")

Pero en cambio, lo hacemos así:

self.setWindowTitle(
    self.trUtf8("string con el titulo de la ventana")
)

El método trUtf8(), lo que hace es marcar esas strings para ser traducidas y, a la hora de generar la interfaz, se encarga de buscar las traducciones en un archivo y reemplazarla por su equivalente en el idioma correcto. ¿Cómo pasa toda esta magia? Si recuerdan, en el código teniamos dos objetos traductores, uno llamado translataor y otro llamado qtTranslator. Como bien sospecharan, si el objeto qrTranslator lo usamos para instalar las traducciones del toolkit el objeto translator lo vamos a usar para instalar nuestras traducciones. 🙂

Fijensé que al método load() de translator le pasamos como primer argumento ahora la string “demo_” concatenada con la variable locale. Esto lo que va a hacer es cargar las traducciones del archivo demo_<locale>, en mi caso, demo_es_AR y aplicarselas a nuestra aplicación. Ahora veremos como crear este archivo.

Generando las Traducciones

Como parte de Qt4 tenemos una aplicación llamada “Qt4 Linguist”, que la pueden encontrar en el menú “Programación” del menú de Aplicaciones de GNOME, esta aplicación es un entorno de desarrollo para traducciones. Trabaja con unos archivos xml con extensión .ts o con archivos .ui de Designer y genera unos archivos compilados con extensión .qm que contienen las traducciones. En nuestro caso vamos a trabajar con archivos .ts

Por otro lado, con PyQt4 tenemos una aplicación llamada pylupdate4, que nos genera a partir de nuestro código fuente el archivo .ts necesario para trabajar con Linguist 🙂 . Se encarga de sacar todas las strings de nuestro programa y dejarlas listas para que las trabajemos desde Linguist. Ejecutemoslo sobre nuestra aplicación demo:

user@machine:~$ pylupdate4 -verbose demo.py -ts demo_es.ts
Updating 'demo.ts'...
    Found 5 source texts (5 new and 0 already existing)

Ahora tenemos un archivo demo.ts, abramos este archivo desde Linguist. Lo primero que nos pregunta Linguist es a que idioma vamos a traducir el archivo, en nuestro caso nos ofrece Spanish de Argentine, le damos Aceptar y nos encontramos con una ventana como esta:

Qt Linguist
Qt Linguist

En la parte central de Linguist tenemos una lista de todos los strings que están en el archivo demo_es.ts y en la parte inferior tenemos los campos para ingresar la traducción del string que tenemos seleccionado. En el campo “Spanish translation” vamos traduciendo cada uno de los strings y una vez que terminamos, guardamos los cambios y nos dirigimos al menú File y seleccionamos la opción “Release as…”. Guardamos el archivo release en la misma carpeta que nuestro script bajo el nombre demo_es.qm. Esto nos genera el archivo compilado con las traducciones propiamente dichas.

Ahora ejecutamos nuestro script y podemos ver los cambios :D:

Demo en español
Diálogo en español
Diálogo en español

Y eso es todo.. ya tenemos nuestra aplicación traducida! 😀 Ahora solo falta agregar un archivo con cada una de las traducciones que queramos soportar y aplicarlas según corresponda.

Observaciones

  • Es importante aplicar las traducciones antes de instanciar la ventana principal. Si cargamos las traducciones después de haber creado la ventana estas no se aplican.
  • pylupdate4 tiene problemas con los strings multilineas como este:
no_traduce = "Este es \
un string \
multilinea"
  • Podemos cargar cualquier archivo de traducciones que querramos. En el código de ejemplo cargamos detectando la configuración por defecto de idioma, pero tranquilamente podríamos cargarlas siempre desde un mismo archivo, por ejemplo:
translator.load(
    os.path.join(os.path.abspath(
        os.path.dirname(__file__)),
        "archivo_traducciones.qm"
    )
  • Si nuestro sistema tiene un locale como es_AR, pero no tenemos traduccion para nuestro locale, Qt4 intenta aplicar la traducción para el locale es antes de caer en la traducción por defecto. Esto es, si tenemos un archivo demo_es_AR.qm Qt aplicará esas traducciones, si no lo tenemos, pero si tenemos un archivo demo_es.qm aplicará ese archivo y si no dejará los strings como están en el código.
Resumiendo…

Los pasos a seguir para que nuestra aplicación sea traducible son los siguientes:

  1. Pasar todos nuestros strings por la función self.trUtf8()
  2. Ejecutar pylupdate4 -verbose script.py -ts script.ts para obtener el archivo de traducciones
  3. Editar las traducciones con Qt4 Linguist
  4. Hacer el release del archivo qm desde Linguist.
  5. Usar QTranslator en nuestro objeto aplicación para cargar las traducciones y detectar automáticamente el locale.
  6. Profit!
Anuncios

5 pensamientos en “Como traducir aplicaciones Qt4

  1. Pingback: Traduciendo QtQR :-) « Cerebro.toBlog()

  2. Hola!

    He seguido los pasos, pero no consigo que me cargue las traducciones. He generado la interfaz con QTDesigner.

    self.label.setText(QtGui.QApplication.translate(“CredentialsQT”, “Usuario:”, None, QtGui.QApplication.UnicodeUTF8))

    Cargo el fichero QM con las traducciones editadas en QTLinguist, pero no se aplican. He comprobado que el archivo existe antes de cargarlo.

    Gracias!

  3. Hola Iván!

    Creo que el problema está en que estás usando QApplication.translate en vez de self.trUtf8(). Recuerda que self hace referencia a una instancia de QMainWindow..

    No estoy seguro cómo funciona Application.translate .

    Saludos!

  4. Por fin he conseguido solucionarlo. El problema era el contexto de las palabras traducidas.

    Mi clase se llamaba “Ui_Credentials” y me script “Credentials.py”. La utilidad de QtDesigner me añade automáticamente el prefijo “Ui_” a las clases.

    La solución que he encontrado es cambiar el nombre de mi script y añadirle también el prefijo “Ui_”.

    Gracias por la ayuda!

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s