Welcome | Get started | Dive | Contribute | Topics | Reference | Changes | More

Introduction to multilingual database content

One feature of Lino is its built-in support for single-table multilingual database content. This document explains what it is.

Note that this is not about internationalization (i18n). Internationalization is when the front end can speak different languages. Lino uses the existing Django techniques about Internationalization.

When to use MLDBC

Imagine a Canadian company that wants to print catalogues and price offers in two different languages, either English or French, depending on the customer’s preferred language. They don’t want to maintain different product tables because it is one company, one accounting, and prices are the same in French and in English. They need a Products table like this:

Designation (en)

Designation (fr)

Category

Price

ID

Chair

Chaise

Accessories

29.95

1

Table

Table

Accessories

89.95

2

Monitor

Écran

Hardware

19.95

3

Mouse

Souris

Accessories

2.95

4

Keyboard

Clavier

Accessories

4.95

5

Now imagine that your application is being used not only in Canada but also in the United States. Your US customers don’t want to have a “useless” column for the French designation of their products.

This is where you want multi-lingual database content. In that case you would simply

  • use a babel field instead of Django’s CharField for every translatable field

  • and set the languages attribute to "en" for your US customer and to "en fr" for your Canadian customer.

babel field

A special database field that behaves like a normal Django field but actually generates a series of fields in the database model, one for each language of the site.

Babel fields are implemented by BabelCharField and BabelTextField defined in module lino.utils.mldbc.fields. The former creates single-line CharField while The latter creates multi-line TextField.

An example

Go to the lino_book.projects.mldbc demo project:

$ go mldbc

Make sure that the demo database is initialized:

$ python manage.py prep

Open the interactive Django shell:

$ pm shell
>>> from lino.api.doctest import *
>>> Product = rt.models.mldbc.Product

You can print a catalog in different languages:

>>> print(', '.join([str(p) for p in Product.objects.all()]))
Chair, Table, Monitor, Mouse, Keyboard, Consultation
>>> from django.utils import translation
>>> with translation.override('fr'):
...     print(', '.join([str(p) for p in Product.objects.all()]))
Chaise, Table, Ecran, Souris, Clavier, Consultation

Here is how we got the above table:

>>> from lino.api import rt
>>> rt.show(mldbc.Products)
==================== ================== ============= ============
 Designation          Designation (fr)   Category      Price
-------------------- ------------------ ------------- ------------
 Chair                Chaise             Accessories   29,95
 Table                Table              Accessories   89,95
 Monitor              Ecran              Hardware      19,95
 Mouse                Souris             Accessories   2,95
 Keyboard             Clavier            Accessories   4,95
 Consultation         Consultation       Service       59,95
 **Total (6 rows)**                                    **207,70**
==================== ================== ============= ============

Screenshots

The screenshots on the left have been taken on a server with languages = ['en'], those on the right on a server with languages = ['de','fr'].

../../_images/babel1a.jpg ../../_images/babel1b.jpg ../../_images/babel2a.jpg ../../_images/babel2b.jpg ../../_images/babel3a.jpg ../../_images/babel3b.jpg

The settings.py file

from lino.projects.std.settings import *


class Site(Site):

    title = "MLDBC Tutorial"

    demo_fixtures = ['demo']

    languages = 'en fr'

    def get_installed_plugins(self):
        yield super(Site, self).get_installed_plugins()
        # yield 'lino.modlib.system'
        yield 'lino_book.projects.mldbc'

    def setup_menu(self, user_type, main, ar=None):
        m = main.add_menu("products", "Products")
        m.add_action('mldbc.Products')
        super(Site, self).setup_menu(user_type, main)


SITE = Site(globals())

DEBUG = True

This is where you specify the languages setting.

The models.py file

from lino.api import dd
from lino import mixins

from django.utils.translation import gettext_lazy as _


class Categories(dd.ChoiceList):
    verbose_name = _("Category")
    verbose_name_plural = _("Categories")


Categories.add_item("01", _("Hardware"), 'hardware')
Categories.add_item("02", _("Service"), 'service')
Categories.add_item("03", _("Accessories"), 'accessories')
Categories.add_item("04", _("Software"), 'software')


class Product(mixins.BabelNamed):

    price = dd.PriceField(_("Price"), blank=True, null=True)

    category = Categories.field(blank=True, null=True)

    class Meta:
        verbose_name = 'Product'
        verbose_name_plural = 'Products'


class Products(dd.Table):
    model = Product
    sort_order = ['name']
    column_names = "name category price *"
    auto_fit_column_widths = True

    detail_layout = """
    id price category
    name
    """

    insert_layout = """
    name
    category price
    """

In case you wonder what a choicelist is, see Introduction to choicelists.

The demo fixture

# -*- coding: UTF-8 -*-

from __future__ import unicode_literals

from lino.api import dd

Product = dd.resolve_model('mldbc.Product')


def P(en, de, fr, category, price):
    return Product(price=price,
                   category=category,
                   **dd.babel_values('name', en=en, de=de, fr=fr))


def objects():
    yield P("Chair", "Stuhl", "Chaise", '03', '29.95')
    yield P("Table", "Tisch", "Table", '03', '89.95')
    # doctests fail with non-ascii text, so we need to cheat:
    # yield P("Monitor", "Bildschirm", "Écran", '01', '19.95')
    yield P("Monitor", "Bildschirm", "Ecran", '01', '19.95')
    yield P("Mouse", "Maus", "Souris", '03', '2.95')
    yield P("Keyboard", "Tastatur", "Clavier", '03', '4.95')
    yield P("Consultation", "Beratung", "Consultation", '02', '59.95')

Note how the application developer doesn’t know which languages will be set at runtime.

Of course the fixture above supposes a single person who knows all the languages, but that’s just because we are simplifying. In reality you can do it as sophisticated as you want, reading the content from different sources.

BabelFields and migrations

BabelFields cause the database structure to change when a server administrator locally changes the languages setting of a Lino site.

That’s why the application developer does not provide Django migrations for their product. See Data migrations à la Lino and Django migrations on a Lino site.