I18n and l10n - adaptive-learning/flocs GitHub Wiki

Decision: Language is determined purely from URL, no cookies, session state nor browser setting are used. This approach is friendly to search engines and obeys the "explicit is better than implicit" rule. It is recommended e.g. by Google.

How to test localization

Language is determined automatically from URL. The domains for languages are set in LANGUAGE_DOMAINS dictionary in flocs.settings.py. During development you can access Czech localization at http://localhost:8000/ and English localization at http://en.localhost:8000/ (You can switch easily switch between the two by a language dropdown button in the menu. It's set such that the current path is preserved, but there is one small caveat: if you have been logged, Django will require you to log again for a different domain.)

You can also force the current language, which may be useful for (unit) tests (or for testing in a console). At frontend use flocs.locales.localesService.setLanguage, at backend use django.utils.translation.activate (both functions take a language code as the only argument, i.e. 'cs' or 'en').

In [1]: from django.utils.translation import activate
In [2]: activate('cs')

Frontend

We are using angular-translate for frontend localization. For i18n in templates, just type a translation key (which may be the wanted text in English) and add translate directive to the element containing the text.

<a ui-sref="home" translate>TITLE</a>

If the translated text is not alone in the template, you must wrap the text into a <span translate> element. If you need to translate text in parameters, use translate filter: <input placeholder="{{'USERNAME' | translate}}"/>.

Then provide translations in src/common/locales/locale-*.cst.js. Keep the locale files sorted by keys.

.constant('localeEn', {
  LOG_IN: 'Log in',
  TITLE: 'Adaptive programming',
  ...
});

(Note: If the translated text is short (a few words), the key should match the English translation. Currently, we are using capitalized keys, but the other possibility we might discuss and use instead is to only capitalize the keys which are different from the English translation (because the text is long) and this approach would enable to omit English translations for the non-capitalized keys as the key is used if the translation is not found.)

If you need some advanced features, such as pluralization or interpolation, look in the angular-translate user guide.

Blockly

Blockly uses slightly different approach to the localization, it requires that the messages it are stored in global Blockly.Msg object, which maps keys to translated text. Both English and Czech versions are created in src/common/locales/blockly-messages.srv.js. There are two factories: enBlocklyMessages and csBlocklyMessages and they are mostly copies of the original Blockly message files, our additions are at the bottoms of the factories:

  messages = {}
  ...
  messages.MAZE_CHECK_COLOR = 'robot %1 na políčku %2 barvy';
  messages.MAZE_CHECK_PATH = "je cesta %1";
  messages.MAZE_MOVE_FORWARD = 'krok vpřed';
  ...
  return messages;

For consistency, we use these message factories (and not the angular-translate) for all Blockly messages, e.g. texts in new blocks defined in flocs.workspace.blocklyService and toolbox categories names in flocs.workspace.blocksXml.

Backend

Django contains support for localization of texts in templates and in code using popular gettext tool. As we have a fat client, there has been no need to do that so far and there may never be the need for that, but if there is, you can read how to do it in Django documentation. However, we need multilingual models.

Multilingual models

Multilingual models are models with a field which content depend on a user language, e.g. task titles and instruction texts. We have decide not to create these multilingual fields manually, but to use modeltranslation library. This library uses non-invasive registration approach (interceptor pattern), which means that to set a field as a multilingual, the model source code is not changed at all. Example of a registration:

from modeltranslation import translator
from .instructions_model import InstructionsModel

@translator.register(InstructionsModel)
class InstructionsTranslationOptions(translator.TranslationOptions):
    fields = ('text',)
    fallback_values = '[MISSING TRANSLATION]'

This sets field text of an InstructionsModel as a multilingual, but we also need to tell the modeltranslation library about this registration code, which is done by MODELTRANSLATION_TRANSLATION_FILES option in settings.py. To actually change the model, you need to run python manage.py makemigrations followed by python manage.py migrate.

When properly configured, the migration will create a special field for each from supported languages (specified by LANGUAGES option in settings.py, currently Czech and English), so there will be fields text_cs, text_en and there will also be text getter which takes into account the current language:

In [1]: InstructionsModel.objects.first().text
Out[1]: 'Your task is to get the robot to the treasure box.'    

In [2]: from django.utils.translation import activate
In [3]: activate('cs')

In [4]: InstructionsModel.objects.first().text
Out[4]: 'Úkolem hry je dostat robota k truhle s pokladem.'

In [5]: InstructionsModel.objects.first().text_en
Out[5]: 'Your task is to get the robot to the treasure box.'

Translations can be provided in a fixture as follows:

"model": "practice.instructionsmodel",
"fields": {
    "flow_factor": 255,
    "text_en": "Your task is to get the robot to the treasure box.",
    "text_cs": "Úkolem hry je dostat robota k truhle s pokladem."
}

The registration approach can be a little bit tricky and it's important that registered models only contain model data and model related logic (see modeltranslations docs for details).