Multilingual and multi-regional applications

The tutorial.i18nexamplebootstrap example changes the simple address book example so that the user can select the language displayed on the user interface. The chosen locale also formats the display of dates.

Make your component translatable

Add a Nav to choose a different locale

Add a Nav containing all locales supported by your application to the main Navbar. Call with_languages() to populate your Nav with the available locales.

class AddressBookPage(HTML5Page):
    def __init__(self, view):
        super().__init__(view)
        self.body.use_layout(Container())

        layout = ResponsiveLayout('md', colour_theme='dark', bg_scheme='primary')
        navbar = Navbar(view, css_id='my_nav').use_layout(layout)
        navbar.layout.set_brand_text(_('Address book'))
        navbar.layout.add(Nav(view).with_languages())

        self.body.add_child(navbar)
        self.body.add_child(AddressBookPanel(view))

Mark the messages that need to be translated

Create a global Catalogue in your module and assign it to the variable name _ (underscore):

_ = Catalogue('reahl-doc')


Wrap all strings destined for the user interface in a call to the Catalogue similar to the brand text in AddressBookPage above.

Each _() call marks the relevant string so it can be found by tools that search your source. These tools compile a catalogue of strings that need translation. You will later add translated versions for these messages in the same catalogue.

When creating the Catalogue, pass it the name of the catalogue where it should search for translations to strings wrapped in _(). By convention the catalogue is named the same as your component.

The Address class contains more examples:

class Address(Base):
    __tablename__ = 'i18nexamplebootstrap_address'

    id            = Column(Integer, primary_key=True)
    email_address = Column(UnicodeText)
    name          = Column(UnicodeText)
    added_date    = Column(Date)

    fields = ExposedNames()
    fields.name = lambda i: Field(label=_('Name'), required=True)
    fields.email_address = lambda i: EmailField(label=_('Email'), required=True)

    def save(self):
        self.added_date = datetime.date.today()
        Session.add(self)

    events = ExposedNames()
    events.save = lambda i: Event(label=_('Save'), action=Action(i.save))

Dealing with plural messages

In some languages there are many distinct plural forms that differ depending on the number of items you are talking about.

Use ngettext() for messages that need plural translations. It takes the English singular and plural form and returns the correct plural form for the target language, given the number of items:

class AddressBookPanel(Div):
    def __init__(self, view):
        super().__init__(view)

        number_of_addresses = Session.query(Address).count()
        self.add_child(H(view, 1, text=_.ngettext('Address', 'Addresses', number_of_addresses)))

        self.add_child(AddressForm(view))

        for address in Session.query(Address).all():
            self.add_child(AddressBox(view, address))

Other customisations

Many other things can be customised depending on the chosen locale by using the Babel library directly. Obtain the current locale string from _.current_locale.

Format the date inside an AddressBox for the current locale:

class AddressBox(Widget):
    def __init__(self, view, address):
        super().__init__(view)
        formatted_date = babel.dates.format_date(address.added_date, locale=_.current_locale)
        self.add_child(P(view, text='%s: %s (%s)' % (address.name, address.email_address, formatted_date)))

Making it possible for others to translate your component

Create the i18nexamplebootstrapmessages package in the top level directory of the example (don’t forget the __init__.py). It will house your message catalogue. List it as entry point in your pyproject.toml. Also ensure that the relevant data files from that package will be included when your package is built:

[project.entry-points."reahl.translations"]
i18nexamplebootstrap = "reahl.doc.examples.tutorial.i18nexamplebootstrap.i18nexamplebootstrapmessages"

[tool.setuptools]
py-modules = ["i18nexamplebootstrap"]

Once your “translations” package is ready, extract the catalogue from your source code by running:

reahl extractmessages

This stores your original messages inside i18nexamplebootstrapmessages/i18nexamplebootstrap.

Note

Remember to run python -m pip install --no-deps -e . after changing the pyproject.toml file.

Add translated messages

Before you can start translating messages to another language, add a locale for it to the package housing all your translations.

Here we add the af locale (for the Afrikaans language) to i18nexamplebootstrap by running (from the i18nexamplebootstrap directory):

reahl addlocale af i18nexamplebootstrap

This creates the file i18nexamplebootstrap/af/LC_MESSAGES/i18nexamplebootstrap.po which you must edit to supply the translated versions for the af locale.

Once done, compile the catalogue so that it can be used by a running application:

reahl compiletranslations

Supplying translations to another component

A component can supply translations for use by another component. First, make your component dependent on the one to which it supplies translations.

Then, the process is exactly the same as before:
  • add a <translations> package in your component to hold the translated messages;

  • add the new locale to your new component, stating the catalogue name for which the locale is intended:

reahl addlocale fr theothercomponentcataloguename
  • translate the messages in the text file created; and

  • compile the messages as usual.

Warning

If you add a locale that is not included by Reahl itself, you need to supply translations for all the Reahl components you use.

Updating translations

When your software changes, the messages used in it also change. Extract the new set of original messages as usual:

reahl extractmessages

Then update your already-translated list of messages using the new list:

reahl mergetranslations

Finally, edit the merged messages to supply translated versions of new messages before compiling them as before.