Translation of Optima Nutrition into different languages/locales takes place in three places
- In the backend Python code
- In the databooks
- In the web interface
Backend localization is needed for any strings that are hardcoded into the Python application. For example, error messages are generated by the backend code and need to be localized. Similarly, the databook may contain certain keywords (e.g. 'Population') that need to be localized when reading in the databook. Localization in the backend uses standard Python functionality provided by gettext
, and the locales are managed using babel
.
Strings that require translation are passed through a 'translation function' which is assigned to _
by convention. babel
scans through the package searching for strings occurring inside _
and then uses them to update the catalog files.
There are three sets of locale files
- The 'backend
.po
' files which are contained innutrition/locale
and are used by the Python code - The 'databook
.po
' files' which are contained ininputs
and store the translations for strings that appear in the databook. These are used when producing translated versions of the databooks - The 'frontend
.po
' files which are contained inclient/src/locales
and store the translations used for the web interface
In many cases, the same string appears in multiple places. For example, a string like 'Stunting' might appear in the databook. This string might then be needed by the backend code, when reading the databook and populating variables in the code. Finally, the same string might show up in the web interface and need translating there as well. Sometimes, the translated strings in the frontend are passed back into the backend - for example, if the frontend contains a table of input cells, and the user saves changes to the model inputs, these are sent back to the backend with the labels in the table, which may have been translated. Therefore, it is essential that all three sets of translation files are synchronized. Scripts are provided in the repository to automatically synchronize the translations.
To update strings after adding new strings in code, run the following commands. This creates nutrition/locale/nutrition.pot
python setup.py extract_messages
python setup.py update_catalog
To set up a new locale e.g., en
use:
python setup.py init_catalog -l en
This creates nutrition/locale/en/LC_MESSAGES/nutrition.po
.
To update translations (after modifying translated text)
python setup.py compile_catalog
For strings that only appear in the backend code (e.g., plot titles), the normal workflow for adding translations is:
- Add
_ = utils.get_translator(locale)
in the appropriate scope.utils.locale
contains the fallback/default locale. For RPCs, it is necessary to get the translator inside the RPC call, so that way different languages can be simultaneously used by multiple users. - For any string that needs to be translated, pass it through
_
for translation. For exampleprint("foo")
could becomeprint(_("foo"))
- Run the extract messages and update catalog steps above
- Edit the
.po
files to populate the translations - Compile the catalog
- Commit all files
Some strings also appear in the databook, such as program names or population names. These need to be localized in the databook templates as well as in the code, because the databooks are not generated programatically. The workflow for generating translated databooks is to start with a complete English template. Then, an Excel file containing translations is created.
English strings that will be translated are contained in inputs/translations.txt
. Running inputs/update_databook_catalogs.py
reads translations.txt
and adds/updates .po
files to subdirectories within the input folder. These po
files can be edited to perform the translations. translate.py
will then produce translated databooks.
The reference files are in inputs/en
. Remaining files are automatically generated using translate.py
. This script does the following for each file in en
:
- Copy file to locale folder e.g.
fr
- Finds and replaces all strings matched in
translations.txt
. Strings are matched against cell contents in their entirity. Note that this means that cell values contained inside formulas e.g.,=IF(...,"foo")
will not match"foo"
intranslations.txt
. This is required so that named cells e.g.=start_year
do not become=start_Année
and therefore break formalas. However, it may be necessary to change parts of the databook to move hardcoded strings to separate cells that are then referenced.
In this way, translate.py
produces translated versions of all of the en
files. To preserve the files as precisely as possible, the translation is carried out by remote-controlling Microsoft Excel. Therefore, this script can only be run on a Windows machine with Microsoft Excel installed.
For strings that appear in both the databook and in the code, it is essential that the same translations are used for both the databook and the code. Since the Excel files are translated using the .po
files in the inputs
folder, these same translations should be set in the other locale files. This is handled by the sync_backend_po.py
script, which reads translations.txt
and, for each English string in that file, if it is contained in a backend or frontend .po
file, the translations are overwritten with the translation from the databook .po
files.
The procedure for translating a string that appears in the databook and code is as follows - consider the example of 12-24 months
which appears in both the databook and in the code. The workflow to translate this string is
- Wrap the string in a translation function in the code i.e.
_('12-24 months)
- Extract messages and update the catalog so that the message id
12-24 months
appears in the backend.po
files - Add the translation for
12-24 months
totranslations.txt
- Run
update_databook_catalogs.py
which - Run
translate.py
which will do two things - it will generate translated databooks including the new string, and it will also overwrite the translation for12-24 months
in the.po
files - Compile the catalog and commit all files
Strings that are specific to the frontend (e.g., the text that appears on a button) appear in .vue
files. To add translations for these strings:
- Make a string in the
.vue
file using the$t
function - Use
npm run translate
to scan the.vue
files and update the catalogs - Fill in the translations in the catalog files
Translations are in src/locales
with one file for each locale. The locales themselves also need to appear in src/js/utils.js
in the list of available locales (this is how the locale will end up in the locale switcher). To add a new locale, add an entry to utils.js
, create a blank translation in src/locales
and then run npm run translate
To access the current locale, use import i18n from '../i18n'
in the scripts section. i18n.locale
is then accessible as the current locale in callbacks. Within the template, use $i18n.locale
- see LocaleSwitcher.vue
for an example. This allows the locale in the UI to be included in RPC calls so that the RPC can access a specific locale. For example, when creating a demo project, the frontend's locale is used to determine which databook is used to create the project.
Some software, such as Poedit, can assist with managing translations. However, Poedit only supports the .po
files used by the backend. Therefore, it is necessary to convert to/from the .po
format in order to use these tools. The workflow incorporating a .po
step is
- Make a string in the
.vue
file using the$t
function - Go into the
client
folder and runupdate_po_files.sh
which will first regenerate thejson
files based on thevue
files, and then generatepo
files - Use Poedit or anything else to modify the
po
files - Run
update_json_files.sh
to generate newjson
files from thepo
files - Commit the
json
files to the repository
As mentioned above, the sync_backend_po.py
script copies translations from the databook into the backend and frontend. However, it also copies translations from the backend into the frontend. Therefore, it is able to keep all of the .po
files synchronized. However, synchronizing the frontend also requires converting from and to json
files. The update_backend_po.sh
script automates all required steps, and should be run whenever any translations are changed.