Internationalization

Modern applications must be able to handle different languages. Internationalization (i18n) in pycerberus refers to validating locale-dependent input data (e.g. different decimal separator characters) as well as validation errors in different languages. The former aspect is not yet covered by default but you should be able to write custom validators easily.

All messages from validators included in pycerberus can be translated in different languages using the standard gettext library. The language of validation error messages will be chosen depending on the locale which is given in the state dictionary,

i18n support in pycerberus is a bit broader than just translating existing error messages. i18n becomes interesting when you write your own validators (based on the ones that come with pycerberus) and your translations need to play along with the built-in ones:

  • Translate only the messages you defined, keep the existing pycerberus translations.
  • If you don’t like the existing pycerberus translations, you can define your own without even changing a single line or file in pycerberus.
  • Specify additional translation options per validator class (e.g. a different gettext domain or a different directory where your translations are stored).
  • Even though pycerberus uses the well-known gettext mechanism to retrieve translations, you can use any other source as well (e.g. a database or a XML file).

All i18n support in pycerberus aims to provide custom validators with a nice, simple-to-use API while maintaining the flexibility that serious applications need.

Get translated error messages

If you want to get translated error messages from a validator, you set the correct ‘’context’‘. formencode looks for a key named ‘locale’ in the context dictionary:

validator = IntegerValidator()
validator.process('foo', context={'locale': 'en'}) # u'Please enter a number.'
validator.process('foo', context={'locale': 'de'}) # u'Bitte geben Sie eine Zahl ein.'

Internal gettext details

Usually you don’t have to know much about how pycerberus uses gettext internally. Just for completeness: The default domain is ‘pycerberus’. By default translations (.mo files) are loaded from pycerberus.locales, with a fall back to the system-wide locale dir ‘’/usr/share/locale’‘.

Translate your custom messages

To translate messages from a custom validator, you need to declare them in the messages() method and mark the message strings as translatable:

from pycerberus.api import Validator
from pycerberus.i18n import _

class MyValidator(Validator):
    def messages(self):
        return {
                'foo': _('A message.'),
                'bar': _('Another message.'),
               }

    # your validation logic ...

Afterwards you just have to start the usual gettext process. I always use Babel because it provides the very convenient pybabel tool which simplifies the workflow a lot:

  • Collect the translatable strings in a po template (.pot) file, e.g. pybabel extract . --output=mymessages.pot
  • Create the initial po file for your new locale (only needed once): pybabel init --domain=pycerberus --input-file=mymessages.pot --locale=<locale ID> --output-dir=locales/
  • After every change to a translatable string in your source code, you need to recreate the pot file (see first step) and update the po file for your locale: pybabel update --domain=pycerberus --input-file=mymessages.pot --output-dir=locales/
  • Translate the messages for every locale.
  • Compile the final po file into a mo file, e.g. pybabel compile --domain=pycerberus --directory=locales/

Override existing messages and translations

Assume your custom validator is a subclass of a built-in validator but you don’t like the built-in translation. Of course you can replace pycerberus’ mo files directly. However there is also another way where you don’t have to change pycerberus itself:

class CustomValidatorThatOverridesTranslations(Validator):

    def messages(self):
        return {'empty': _('My custom message if the value is empty'),
                'custom': _('A custom message')}

    # ...

This validator will use a different message for the ‘empty’ error and you can define custom translations for this key in your own .po files.

Modify gettext options (locale dir, domain)

The gettext library is configurable, e.g. in which directory your .mo files are located and which domain (.mo filename) should be used. In pycerberus this is configurable by validator:

class ValidatorWithCustomGettextOptions(Validator):

    def messages(self):
        return {'custom': _('A custom message')}

    def translation_parameters(self, context):
        return {'domain': 'myapp', 'localedir': '/home/foo/locale'}

    # ...

These translation parameters are passed directly to the ‘’gettext’’ call so you can read about the available options in the gettext documentation. Your parameter will be applied for all messages which were declared in your validator class (but not in others). So you can modify the parameters for your own validator but keep all the existing parameters (and translations) for built-in validators.

Retrieve translations from a different source (e.g. database)

Sometimes you don’t want to use gettext. For instance you could store translations in a relational database so that your users can update the messages themselves without fiddling with gettext tools:

class ValidatorWithNonGettextTranslation(FrameworkValidator):

    def messages(self):
        return {'custom': _('A custom message')}

    def translate_message(self, key, native_message, translation_parameters, context):
        # fetch the translation for 'native_message' from somewhere
        translated_message = get_translation_from_db(native_message)
        return translated_message

You can use this mechanism to plug in arbitrary translation systems into gettext. Your translation mechanism is (again) only applied to keys which were defined by your specific validator class. If you want to use your translation system also for keys which were defined by built-in validators, you need to re-define these keys in your class as shown in the previous section.