Multilingual database content¶
One feature of Lino is its built-in support for single-table multilingual database content. This tutorial explains what it is.
Note that this is not about internationalization (i18n).
Internationalization is when the front end can speak different
languages. Lino has nothing to add to the existing Django techniques about
that's why we deliberately didn't add
lino.modlib.users and front end
translation in this tutorial.
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:
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
BabelCharFieldinstead of Django's
CharFieldfor every translatable field and
"en"for your US customer and to
"en fr"for your Canadian customer.
If you have installed a contributor environment (see Set up a contributor environment), then you can run the following examples on your computer.
$ go mldbc
Make sure that the demo database is initialized:
$ python manage.py prep
Now open the interactive Django shell:
$ python manage.py shell
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** ==================== ================== ============= ============
Using the web interface¶
$ go mldbc $ python manage.py prep $ python manage.py runserver Analyzing Tables... Analyze 0 slave tables... Discovering choosers for model fields... Analyzing Tables... Analyze 0 slave tables... Discovering choosers for model fields... Watching for file changes with StatReloader Performing system checks... System check identified no issues (0 silenced). November 05, 2019 - 07:32:06 Django version 2.2.6, using settings 'lino_book.projects.mldbc.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C.
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'].
from lino.projects.std.settings import * class Site(Site): title = "MLDBC Tutorial" demo_fixtures = ['demo'] languages = 'en fr' def get_installed_apps(self): yield super(Site, self).get_installed_apps() # yield 'lino.modlib.system' yield 'lino_book.projects.mldbc' def setup_menu(self, user_type, main): 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
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 site
maintainer 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.