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¶
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 setup.cfg. Also ensure that the relevant data files from that package will be included when your package is built:
[options.entry_points]
reahl.translations =
i18nexamplebootstrap = reahl.doc.examples.tutorial.i18nexamplebootstrap.i18nexamplebootstrapmessages
[options.package_data]
* =
*/LC_MESSAGES/*.mo
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 setup.cfg 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.