Dealing With Translated User Interfaces

Last edited on

Most professional software comes with translations for user-visible texts such that users can continue using a familiar language. This poses an additional challenge for automated UI tests since user-visible texts can no longer be used to identify objects in case the test suite is to be executed with different translations for the user interfaces.

Translations In The Wild

In practice, it’s not uncommon to see a huge increase of size of an object map when dealing with translated user interfaces. For instance, here’s a small snippet taken from the object map used for testing the ‘Qt’ addressbook example shipped with Squish for Qt (we’re using the Python scripting language here):

address_Book_Add_Dialog = {"type": "Dialog", "windowTitle": "Address Book - Add"}
address_Book_Add_Forename_QLabel = {"text": "Forename:", "type": "QLabel", "window": address_Book_Add_Dialog}
address_Book_Add_Surname_QLabel = {"text": "Surname:", "type": "QLabel", "window": address_Book_Add_Dialog}
address_Book_Add_Phone_QLabel = {"text": "Phone:", "type": "QLabel", "window": address_Book_Add_Dialog}
address_Book_Add_OK_QPushButton = {"text": "OK", "type": "QPushButton", "window": address_Book_Add_Dialog}
address_Book_Add_Cancel_QPushButton = {"text": "Cancel", "type": "QPushButton", "window": address_Book_Add_Dialog}
names.py

This defines a name for a dialog used for adding new entries to the Addressbook.

If the test suite is now extended with new tests automating the same dialog, but in a different language (say: German), it’s not unlikely to end up with an object map like this:

address_Book_Add_Dialog = {"type": "Dialog", "windowTitle": "Address Book - Add"}
address_Book_Add_Forename_QLabel = {"text": "Forename:", "type": "QLabel", "window": address_Book_Add_Dialog}
address_Book_Add_Surname_QLabel = {"text": "Surname:", "type": "QLabel", "window": address_Book_Add_Dialog}
address_Book_Add_Phone_QLabel = {"text": "Phone:", "type": "QLabel", "window": address_Book_Add_Dialog}
address_Book_Add_OK_QPushButton = {"text": "OK", "type": "QPushButton", "window": address_Book_Add_Dialog}
address_Book_Add_Cancel_QPushButton = {"text": "Cancel", "type": "QPushButton", "window": address_Book_Add_Dialog}
adressbuch_Hinzufügen_Dialog = {"type": "Dialog", "windowTitle": "Adressbuch - Hinzufügen"}
adressbuch_Hinzufügen_Vorname_QLabel = {"text": "Vorname:", "type": "QLabel", "window": adressbuch_Hinzufügen_Dialog}
adressbuch_Hinzufügen_Nachname_QLabel = {"text": "Nachname:", "type": "QLabel", "window": adressbuch_Hinzufügen_Dialog}
adressbuch_Hinzufügen_Telefon_QLabel = {"text": "Telefon:", "type": "QLabel", "window": adressbuch_Hinzufügen_Dialog}
adressbuch_Hinzufügen_OK_QPushButton = {"text": "OK", "type": "QPushButton", "window": adressbuch_Hinzufügen_Dialog}
adressbuch_Hinzufügen_Abbrechen_QPushButton = {"text": "Abbrechen", "type": "QPushButton", "window": adressbuch_Hinzufügen_Dialog}
names.py

Note how the object map doubled in size. Furthermore, it not only became larger but also introduced a significant amount of duplication; changing the structure of the dialog used for adding entries (e.g. by replacing QLabel controls with different kinds of controls) will require updating multiple object names.

Making Object Names Language Agnostic

In some cases, this issue can be addressed by reconfiguring Squish to not use properties which hold user-visible text when generating object names (see the chapter Object Name Generation in the Squish manual for more information). Instead, internal properties like id or the like might be available which can be used instead. However, this has a number of downsides:

Scripting To The Rescue

In case internal identifiers are not available, a better approach might be to factor out user-visible texts into a separate module (a ‘dictionary’) in which each English text is mapped to a translation. This translation can be performed by a function which we will call tr (short of translate) here.

The first step is to augment the original object map such that the tr function is defined and used for all user visible texts:

def tr(text):
    return text


address_Book_Add_Dialog = {"type": "Dialog", "windowTitle": tr("Address Book - Add")}
address_Book_Add_Forename_QLabel = {"text": tr("Forename:"), "type": "QLabel", "window": address_Book_Add_Dialog}
address_Book_Add_Surname_QLabel = {"text": tr("Surname:"), "type": "QLabel", "window": address_Book_Add_Dialog}
address_Book_Add_Phone_QLabel = {"text": tr("Phone:"), "type": "QLabel", "window": address_Book_Add_Dialog}
address_Book_Add_OK_QPushButton = {"text": tr("OK"), "type": "QPushButton", "window": address_Book_Add_Dialog}
address_Book_Add_Cancel_QPushButton = {"text": tr("Cancel"), "type": "QPushButton", "window": address_Book_Add_Dialog}
names.py

Notice how the tr function takes one argument and merely returns it: it has no effect whatsoever. It’s used to mark all user-visible texts though. We can now move this function to a separate file (say english.py) and import it from there:

def tr(text):
    return text
english.py
from english import tr
address_Book_Add_Dialog = {"type": "Dialog", "windowTitle": tr("Address Book - Add")}
address_Book_Add_Forename_QLabel = {"text": tr("Forename:"), "type": "QLabel", "window": address_Book_Add_Dialog}
address_Book_Add_Surname_QLabel = {"text": tr("Surname:"), "type": "QLabel", "window": address_Book_Add_Dialog}
address_Book_Add_Phone_QLabel = {"text": tr("Phone:"), "type": "QLabel", "window": address_Book_Add_Dialog}
address_Book_Add_OK_QPushButton = {"text": tr("OK"), "type": "QPushButton", "window": address_Book_Add_Dialog}
address_Book_Add_Cancel_QPushButton = {"text": tr("Cancel"), "type": "QPushButton", "window": address_Book_Add_Dialog}
names.py

The benefit is that we can now provide alternative definitions of the tr function for other languages. For instance, the german translation might be:

def tr(text):
    return {
        'Address Book - Add': 'Adressbuch - Hinzufügen',
        'Forename:': 'Vorname:',
        'Surname:': 'Nachname:',
        'Phone:': 'Telefon:',
        'OK': 'OK',
        'Cancel': 'Abbrechen',
    }[text]
german.py

By simply using from german import tr in our object map, all object names will then use the tr function suitable for translating english texts to german and thus the tests can be used when using the addressbook example program with german translations.