How to Create and Update Translation Modules
About this document
This document describes the process of creating and updating translation modules in Openbravo, including the Core's module. It is designed as how-to document to be read from the beginning to the end, but it also provides useful information to be consulted as a reference guide.
Translations are distributed using modules, that's why it is important to have a clear idea of the Modularity Concepts, specially the process of creating and packing modules. Related to that, it is interesting to read the Naming guidelines for modules.
It is also highly recommended to read the Translation Engine Concepts to clear understand the way Openbravo manages translations.
Creating a translation module from scratch
The estimated effort for creating a translation module is directly proportional to the number of strings into the module to translate.
For example, in the Core's module, which is by far the biggest module available in the Openbravo distribution, there are more than 15800 strings to be translated. These strings can be just words, sentences with less than 10 words or larger paragraphs.
Some of the strings are very easy to translate, specially the ones belonging to messages or elements, however other strings will require a bit more effort to locate its context and provide an accurate translation.
So estimating an average of one minute per string gives us about 30-35 days (of a translator working tirelessly in the Openbravo's Core translation 8 hours/day. Apart from that we should include a few hours more to the usual tasks of creating and packaging the module, testing the installation, etc.
Besides the above, an estimation of 10 days more, should be considered as the estimated effort of translating the remaining Openbravo 3 distribution modules.
Updating a translation module
In the case of updating a translation module the effort is usually much lower, because we just need to translate the new strings that have been added to the module or the ones that have changed. As you can imagine the rest of the translation is kept.
So in this task, apart from the time invested in translating the strings, we obviously need to add a few hours to install the updated version of the module we are going to translate, and to package and test the new version of the translation.
Creating a new translation module from scratch
Create module definition
As you can imagine, the first thing we need to do is to create the translation module definition into the Application Dictionary.
Logged as System Administrator role, we select the Application Dictionary || Module window from the Application menu and we create a new record trying to follow the Naming guidelines for modules.
Translation modules are a special kind of modules. They have to be marked as Is translation module in the Module window and they must define the translation language at the Module Language field.
No other contents than translations are allowed in translation modules. A translation module can only contain the translation for one module. For example, in the screenshot below we are creating a Spanish (Spain) translation module for the User Interface Application module whose declared language is English (USA).
Apart from checking the Is translation module and setting the Module Language, we need to add a dependency to the module and version we are translating. In the example, our User Interface Application Translation Spanish (Spain) module in version 1.0.5 depends on the User Interface Application module version 2.1.0.
Once done it don't forget to register the module!
Prepare strings to be translated
Logged as the System Administrator role, we select the General Setup || Application || Language window from the Application menu. Now find the language on which we want to create the translation module and check the System Language checkbox field. This checkbox allows this language to be selected in the user interface (Change role popup) in the next login.
Each time we want to create a translation for a new language we need to prepare the strings to be translated. There is an automatic process, called Verify Languages (available as well at the Language window), in which the ERP creates a copy of the original strings of all the modules available in the system from their base languages to the desire translation language.
For example, in the case of creating a Spanish (Spain) translation for the core's module, whose base language is English (USA), the system will copy all the original English strings to the Spanish translation. And the same is extended to the rest of the modules installed in the system with their respective base languages.
So it is time to press the Verify Languages button. After several seconds, the application will display the number of records created. If this number is equal to 0 means that something was wrong, and the probable cause is that we have forgotten to check the System Language field.
Export the translation
We are ready to export the translation, i.e., to get the XML files that we need to translate. This is an automatic process available at the General Setup || Application || Import/Export Translations window.
As System Administrator role, select the translation files we want to export and press the Export button. The process takes several seconds to export all the XML files so be patient.
Inside the attachments folder of your Openbravo instance (which is defined at the Openbravo.properties file), you will find a new directory called lang, and inside it a new one with the lowercase two-letter ISO-639 language code and the uppercase two-letter ISO-3166 country code separatted by an underbar character ('_'). Example: /home/openbravo/attachments/lang/es_ES
Inside this directory you will find the translation files for all the available modules into the instance. The core's translation files are stored directly into the root directory, the rest of the modules have their own folder named as their java packages. So we just need to find the folder of the original module we want to translate (not the translation module) and get its XML files.
Translate the module
The translation of the module can be directly done inside the exported XML files, which is a convinient method for modules with few strings to be translated, or using the openbravo2po tool which is the recommended way for modules with a lot of strings, like Core.
Translate directly into the XML files
If we choose this method, we just need to open each XML file that is inside the module's directory we want to translate and edit it. It is very important to open these files with a text editor (like Vim, Emacs, gEdit, notepad...), never using a word processor (like LibreOffice Writer or Microsoft Word) because they can break the XML structure.
The text we need to change for making a translation is the content of each value tag. It's not necessary to edit any attribute because they will be automatically updated when importing and exporting the files into the ERP in a next step.
If you need more information about the structure of the translation files in Openbravo, you can visit the Translation concepts article.
Translate using openbravo2po
openbravo2po is a Java tool that transforms the Openbravo XML translation files into .po files (gettext catalog). PO (Portable Object) is a very popular format for storing and translating software. The great advantage of using PO files is that there are a lot of editors that help the user in the translation process.
Clone the mercurial repository with the command:
Compile the openbravo2po with the command:
Run the openbravo2po tests to check the tool is ready with the command:
Note: To run this you may need to copy the ant-junit.jar in the lib folder of your $ANT_HOME
If you want more information about this tool, you can read the User documentation for openbravo2po
Generating PO files
openbravo2po is a command line tool based on Apache Ant. This means you will need to enter ant commands each time you want to work with it. This may sound a bit complex, but don't worry because it's very easy!
The ant command for generating PO files from the Openbravo's XML files is runXML2PO. The command accepts the following options:
- inpFold. It is the input folder, which represents the folder where the original XML files are stored. The absolute path is required.
- outFold. It is the output folder, which represents the folder where the generated PO files will be saved. The absolute path is required.
- msgStr. If set to yes the output of the PO file will show an empty translated string (msgstr in the PO notation) when the translated text is the same as the original one into the XML document; otherwise it will directly copy the original string to the translated string. This is an important feature that will allow PO editors to detect the strings that are not yet translated, so it's highly recommended to always set it to yes
So, inside the openbravo2po root directory, we should execute the command:
ant runXML2PO -DinpFold=<directory containing module's XML files> -DoutFold=<directory to save the PO files> -DmsgStr=yes
ant runXML2PO -DinpFold=/home/openbravo/attachments/lang/es_ES/org.openbravo.client.application -DoutFold=/tmp/PO_directory -DmsgStr=yes
Important information if you are translating Core
Openbravo's Core provides an additional file called buildStructure.xml. This file contains the information related to the names of the different stages through which the Openbravo build process passes, and the error and warning messages that can be shown during a rebuild. You should also need to translate this file, however it is not compatible with the openbravo2po software. So before generating the PO files using the runXML2PO command, you must be sure the buildStructure.xml file is not inside the XML directory, otherwise the openbravo2po will fail.
Editing PO files
Now we are ready for editing the generated PO files. As we saw in the Translate directly into the XML files section, we can directly make the translation using a plain text editor. In this case we just need to translate the content of the msgid string, which represents the original text, into the msgstr entry for all the available .po files. Example:
However editing the .po files using a text editor doesn't seem to provide an advantage compared to translating XML files. The great benefit of working with PO files is that we can use PO editors, which are specialized software with tools that facilitate the task of translating. There are a large number of cross platform PO editors to choose from, and most of them distributed under a free software license: PoEdit, gted (Eclipse plugin), PO Mode for Emacs, Lokalize (part of KDE), OmegaT, etc. I recommend you to try some of them to find the perfect for you.
PoEdit is an editor that has a large number of features and a very intuitive user interface. It provides a project manager that is very useful when we work on several modules at the same time. For creating a new project we just need to specify the folder where the PO files are stored:
PoEdit inspects all the .po files inside the selected directory and provides useful statistics about the status of each file, i.e., the number of total strings, the ones that are pending to be translated, etc.
So we just need to open the files with untranslated strings by doble clicking on them and start translating. By default the untranslated strings are always displayed at the top of the editor. In the lower right corner it's shown the comments window that can give interesting information to provide an accurate translation.
Generating the Openbravo XML files from PO files
Once we have completed the translation of all the strings in the PO files for the module, it is time to transform them back to XML files. As you can imagine this process is driven again by the openbravo2po tool.
In this case the ant command we must use is runPO2XML. The available options are quite similar to the runXML2PO command:
- inpFold. It is the input folder, which represents the folder where the translated PO files are stored. It requires an absolute path.
- outFold. It is the output folder, which represents the folder where the generated XML files will be saved. We can specify here the module's folder inside the attachments directory overwritting the untranslated XML files. It requires an absolute path.
So, inside the openbravo2po root directory, we should execute the command:
ant runPO2XML -DinpFold=<directory containing PO files> -DoutFold=<directory to save the XML files>
ant runPO2XML -DinpFold= /tmp/PO_directory -DoutFold=/home/openbravo/attachments/lang/es_ES/org.openbravo.client.application
Import and Export the translation
After the translation is completed, or even in the middle of a translation process, is a good practice to import the XML files into the ERP to review the translation in context.
Finally, once we feel the translation is OK, we should export it again. Doing that we ensure the XML files have the final structure, with all the attributes properly set.
Moreover, if we are working with a Source Code Management (highly recommended), the process of importing/exporting the XML files will help us a lot when updating translations. The diff will show us only the translation differences between the old version and the new one, but not changes in the structure of the XML file.
For importing/exporting the translation you can use the General Setup || Application || Import/Export Translations window as we previously saw in the Export the Translation section.
As you can imagine, all the XML files to be imported must be inside their correspondent module's folder of your attachments directory, overwriting the original XML files exported at the beginning of this process.
Now, at the Import/Export Translations window, we can select the language used for the translations and press the Import button. We wait a few seconds to complete the process and we press now the Export button, that will export the XML files again.
Note: During this process it is highly recommended to keep a backup of your translated XML files.
Specific information if you are translating Core
Openbravo's Core provides an additional file called buildStructure.xml. This file contains the information related to the names of the different stages through which the Openbravo build process passes, and the error and warning messages that can be shown during a rebuild. You should also need to translate this file, however it is not compatible with the openbravo2po software, so you will need to manually edit it using a text editor (not a word processor)
The structure of this file is a bit different compared to the standard Openbravo XML translation files, but it is also very easy to understand. You just need to translate the content of all the attributes that start with translated, like translatedName, translatedErrorMessage, etc. This is an example of the buildstructure.xml file translated into Spanish.
<?xml version='1.0' ?> <BuildTranslation> <language>es_ES</language> <mainStepTranslations> <mainStepTranslation code="RB11" originalName="Initial Build Validation" translatedName="Validación de la construcción del sistema" translatedErrorMessage="La validación ha fallado. El sistema no se ha modificado y continua estable, pero los problemas descritos en la parte inferior de la ventana deberían resolverse (ya sea desinstalando los módulos afectados, o resolviendo los problemas de la forma descrita), y una nueva construcción del sistema debería iniciarse."> <stepTranslations/> </mainStepTranslation> <mainStepTranslation code="RB20" originalName="Build" translatedName="Construcción del sistema" translatedSuccessMessage="La construcción del sistema se ha completado con éxito." translatedWarningMessage="Se produjeron alertas durante la compilación. La aplicación se ejecutará, pero debería comprobarlas para ver si son importantes. Ir a <a href="http://wiki.openbravo.com/wiki/ERP/2.50/Update_Tips" target="_blank" class="MessageBox_TextLink"> este enlace </a> para más información. <b>Ahora debe reiniciar el contenedor de servlets</b> para que los cambios tengan efecto." translatedErrorMessage="Ha ocurrido un error durante la construcción del sistema. Para saber qué pasos realizar a continuación, vaya a <a href="http://wiki.openbravo.com/wiki/ERP/2.50/Update_Tips" target="_blank" class="MessageBox_TextLink">este enlace</a>"> <stepTranslations> <stepTranslation code="RB12" originalName="Database update" translatedName="Actualización de la base de datos"/> <stepTranslation code="RB31" originalName="Reference data" translatedName="Datos de referencia"/> <stepTranslation code="RB43" originalName="Compilation" translatedName="Compilación"/> </stepTranslations> </mainStepTranslation> </mainStepTranslations> </BuildTranslation>
If you are using openbravo2po remember that, before generating the PO files using the runXML2PO command, you must be sure the buildStructure.xml file is not inside the XML directory, otherwise the openbravo2po will fail.
All the XML files exported through the Import/Export window and the buildstructure.xml represents all the available user interface strings in the ERP. If we translate all these files we will have a fully translated application. However, the ERP includes some other strings not related to the UI that can be also translated. Inside this group, called Masterdata, we include: country names, currencies, units of measure and month names. All this data is not exported into the XML files, however this does not mean we can't not translated it.
The way for translating masterdata is creating a system level dataset that only contains the translated strings for countries, currencies, units of measure and months. We will now explain the specific details for the masterdata translation dataset, but if you need general information about creating datasets you can read the How to create a Dataset article.
As System Administrator we create a new record inside the Dataset window for our core's translation module. It is important to define this dataset at System only level to ensure it will be automatically applied when we install the module.
As you can see in the screenshot, the tables to be included are: AD_Month_Trl, C_Country_Trl, C_Currency_Trl and C_UOM_Trl. All of them have a filter clause that uses the language column, in the example es_ES.
Now our Core's translation module has a dataset, so we must remember to check the Has reference data flag into the module's definition
The dataset definition is ready, but it's pending the translation itself. For translating this dataset we have two possibilities:
- In the ERP, as System Administrator, we can go to the Country Region and City, Currency, Unit of Measure and Month windows and translate the correspondand record inside the Translation tab (recommended method).
When finished export the dataset using the Export Reference Data button at the Dataset window.
- Export the dataset with the untranslated strings and edit the XML file using a text editor. The dataset XML file will be stored inside the referencedata/standard directory of your translation module.
As you probably know, Openbravo is a distribution of modules, including Core. That means that for having a fully translated application, you must translate all the modules that are part of the distribution. At the time this document has been written, the list contain the following modules: Query/List Widget, JSON Datasource, User Interface Application, Widget Collection, User Interface Client Kernel, Advanced Payables and Receivables, HTML Widget, Orders Awaiting Delivery, Smartclient, User Interface Selector, Workspace & Widgets, Payment Report, Integration with Google APIs, OpenID Service Integration and Core.
It is a good idea to create a translation pack that covers all these modules. Later on we can include this pack into our Localization Pack.
Packaging the translation module
The process of packaging a translation module is similar to the standard packaging process with just one important consideration: we need to copy the translated XML files to the correspondent module's referencedata/translation folder within your module folder.
The summarized process is:
- Core only: If we have translated the masterdata dataset directly in the ERP, we need to export it through the Export Reference Data button at the Dataset window.
- Export the database running
- Copy the translated XML files to the translation module's referencedata/translation folder
The structure of the translation module's directory should be:
<translation module java package name> ├── referencedata │ ├── standard │ │ └── Masterdata.xml (only for core) │ └── translation │ └── es_ES │ ├── AD_ELEMENT_TRL_es_ES.xml │ ├── AD_FIELDGROUP_TRL_es_ES.xml │ ├── AD_FIELD_TRL_es_ES.xml │ ├── AD_FORM_TRL_es_ES.xml │ ├── AD_MENU_TRL_es_ES.xml │ ├── AD_MESSAGE_TRL_es_ES.xml │ ├── AD_MODULE_TRL_es_ES.xml │ ├── AD_PROCESS_PARA_TRL_es_ES.xml │ ├── AD_PROCESS_TRL_es_ES.xml │ ├── AD_REFERENCE_TRL_es_ES.xml │ ├── AD_REF_LIST_TRL_es_ES.xml │ ├── AD_TAB_TRL_es_ES.xml │ ├── AD_TEXTINTERFACES_TRL_es_ES.xml │ ├── AD_WF_NODE_TRL_es_ES.xml │ ├── AD_WINDOW_TRL_es_ES.xml │ ├── AD_WORKFLOW_TRL_es_ES.xml │ ├── buildStructureTrl.xml │ ├── C_DOCTYPE_TRL_es_ES.xml │ ├── CONTRIBUTORS_es_ES.xml │ ├── OBKMO_WIDGET_CLASS_TRL_es_ES.xml │ ├── OBUIAPP_PARAMETER_TRL_es_ES.xml │ ├── OBUISEL_SELECTOR_FIELD_TRL_es_ES.xml │ └── OBUISEL_SELECTOR_TRL_es_ES.xml └── src-db └── database ├── model │ ├── functions │ ├── sequences │ ├── tables │ ├── triggers │ └── views └── sourcedata ├── AD_DATASET_TABLE.xml (only for core) ├── AD_DATASET.xml (only for core) ├── AD_MODULE_DEPENDENCY.xml └── AD_MODULE.xml
- Package the module as usual running
ant package.module -Dmodule=<java package name>
- Finally remember to publish the module in the Central Repository.
Updating translation modules
First of all let's comment the obvious considerations for updating a translation module:
- We need to install/update in our instance the last version of the original module and its translation modules. For doing that, as usual, we can use the Modularity#Updating_Modules Module Management window that will automatically update the module. If the system doesn't find the last version of the modules, check the minium maturity status your instance is accepting when doing a scan for updates inside the Settings tab.
- The already translated strings for this module are kept. Only the new or modified ones will be untranslated, so all the previous effort done is not lost.
- Before packaging the translation module remember to update the First Version dependency to the new version of the translated module as we saw in the Create module definition chapter. Apart from that, it is also a good practice to include a good description of the changes of this new version inside the Update Information field of the Module window, like for example “Updated translation to Openbravo 3.0MP8”
The main steps for updating a translation module are almost the same as when Creating a new translation module from scratch. The only difference is that we don't need to declare the System Language and to run the Verify Languages process. The rest of the process is exactly the same, starting by the Export the translation section.
Tips and Tricks
This section tries to provide a set of useful translation tips and tricks. Take into account that some of this tricks may require development knownledges.
Po editors like PoEdit have interesting features that helps translators. For example, one of its interesting features is the Translation Memory, that tries to automatically translate new strings based on the previous translations found in the project. When we are editing a PO file using PO edit, we can use the Automatically translate using TM option at the Catalog menu. This process will translate for us the pending strings looking at its translation database. The automatically translated strings have the fuzzy status to help you review them.
In the screenshot bellow PoEdit has automatically translated to Spanish the Amount string:
If you are translating directly into the XML files you can also have a manual translation memory using the Grep tool. grep command can search for matching strings (or regular expresions) into a set of files.
The following example finds all the ocurrences of the string “Exempt Amount” in all .xml files in the directory
openbravo@por0828:~/XML_files$ fgrep -i "Exempt Amount" *.xml AD_ELEMENT_TRL_es_ES.xml: <value column="Name" isTrl="Y" original="Exempt Amount">Importe exento</value> AD_TEXTINTERFACES_TRL_es_ES.xml: <value column="Text" isTrl="Y" original="Exempt Amount">Importe exento</value>
Finding the context
Making a good translation requires to know the exact context where the string we are translating appears. Unfortunatelly the context in the XML file is not clear at all, and sometimes we will need to dive into the application to get the exact context. Here you have a list of tips for finding it:
- The first obvious thing you must take into account is the file your translating. AD_MENU_TRL represents the Application menu entries, AD_MESSAGE_TRL has all the messages, AD_PROCESS_TRL is in charge of the process and reports, AD_PROCESS_PARA_TRL is the process parameters, etc. You will find of this information in the Translation Engine Concepts article.
- If you are translating using PO files you can find information related to the string you are translating at the comments section. As you can see in the previous screenshot, the PoEdit comment window displays the name, description and help for the string we are currently translating.
- The Openbravo Linked Items feature can give you all the places where a record is used. In the bellow screenshot I want to get the places where the element with the name “General Ledger Currency Credit” is used. In this case linked items tell me that the element is only used in one column, so I can navigate to this column and later on navigate to the related field, that will give me the window where it's used.
- All the records has an ID. In the XML file this ID is shown as an attribute of the row element. In the PO files it is stored as a comment, that can be easily displayed in your PO editor comments window. If we search this ID on the database it can provide us important information.
For example, I want to get more information about the message "Business Partner has no location defined." with the ID "04EEC6B52A7B4C2CACA1F767217FAFB7". If I run the following SQL query into the database, it will provide the search key of this message:
With this information I can use the grep command to find the places in the module's source code where the message is used:
$ fgrep -r NoBPLocation src* src/org/openbravo/erpCommon/ad_callouts/SE_Order_BPartner.java: message.append(Utility.messageBD(this, "NoBPLocation", vars.getLanguage()));
Now I know it is used in a callout, so I just need to find in the Application Dictionary the places where this callout is used using the Linked Items feature commented before.