The languages of a Lino site

The lino.core.site.Site.languages attribute is documented in the Lino Hosting Guide.

This document adds explanations for developers.

>>> import lino
>>> lino.startup('lino.projects.std.settings_test')

Developer reference

>>> from django.utils import translation
>>> from lino.core.site import TestSite as Site
>>> import json
>>> from pprint import pprint

Application code usually specifies Site.languages as a single string with a space-separated list of language codes. The Site will analyze this string during instantiation and convert it into a tuple of LanguageInfo objects.

The following examples use the TestSite class just to show certain things that apply also to "real" Sites.

>>> SITE = Site(languages="en fr de")
>>> print(SITE.languages)  
(LanguageInfo(django_code='en', name='en', index=0, suffix=''),
 LanguageInfo(django_code='fr', name='fr', index=1, suffix='_fr'),
 LanguageInfo(django_code='de', name='de', index=2, suffix='_de'))
>>> SITE = Site(languages="de-ch de-be")
>>> print(SITE.languages)  
(LanguageInfo(django_code='de-ch', name='de_CH', index=0, suffix=''), LanguageInfo(django_code='de-be', name='de_BE', index=1, suffix='_de_BE'))

If we have more than one locale of a same language on a same Site (e.g. 'en-us' and 'en-gb') then it is not allowed to specify just 'en'. But otherwise it is allowed to just say "en", which will mean "the English variant used on this Site".

>>> site = Site(languages="en-us fr de-be de")
>>> print(site.languages)  
(LanguageInfo(django_code='en-us', name='en_US', index=0, suffix=''),
 LanguageInfo(django_code='fr', name='fr', index=1, suffix='_fr'),
 LanguageInfo(django_code='de-be', name='de_BE', index=2, suffix='_de_BE'),
 LanguageInfo(django_code='de', name='de', index=3, suffix='_de'))
>>> pprint(site.language_dict)
{'de': LanguageInfo(django_code='de', name='de', index=3, suffix='_de'),
 'de_BE': LanguageInfo(django_code='de-be', name='de_BE', index=2, suffix='_de_BE'),
 'en': LanguageInfo(django_code='en-us', name='en_US', index=0, suffix=''),
 'en_US': LanguageInfo(django_code='en-us', name='en_US', index=0, suffix=''),
 'fr': LanguageInfo(django_code='fr', name='fr', index=1, suffix='_fr')}
>>> site.language_dict['de']
LanguageInfo(django_code='de', name='de', index=3, suffix='_de')
>>> site.language_dict['de_BE']
LanguageInfo(django_code='de-be', name='de_BE', index=2, suffix='_de_BE')
>>> site.language_dict['de'] == site.language_dict['de_BE']
False
>>> site.language_dict['en'] == site.language_dict['en_US']
True
>>> site.language_dict['en']
LanguageInfo(django_code='en-us', name='en_US', index=0, suffix='')
>>> site.language_dict['en']
LanguageInfo(django_code='en-us', name='en_US', index=0, suffix='')
>>> site.language_dict['fr']
LanguageInfo(django_code='fr', name='fr', index=1, suffix='_fr')
>>> pprint(site.django_settings['LANGUAGES'])  
[('de', 'German'), ('fr', 'French')]

Note that Lino automatically sets USE_L10N to True when you specify languages.

>>> site.django_settings['USE_L10N']
True
>>> pprint(site.django_settings['LANGUAGE_CODE'])
'en-us'

When you leave languages at its default value None, Lino will set the default language "en" at startup. But there is a difference between None and "en": None will cause USE_L10N to be False because this is what we want when we don't worry about languages.

Lino's default language is "en" and not "en-us" because Django has no entry in LANGUAGES for this language code, and because we also reduce the LANGUAGES setting to the languages that are needed. Django 3.0.3 system check complained with "(translation.E004) You have provided a value for the LANGUAGE_CODE setting that is not in the LANGUAGES setting."

>>> site = Site()
>>> print(site.languages)
(LanguageInfo(django_code='en', name='en', index=0, suffix=''),)
>>> pprint(site.django_settings['LANGUAGES'])
[('en', 'English')]
>>> 'USE_L10N' in site.django_settings
False
>>> 'LANGUAGE_CODE' in site.django_settings
False

API documentation

The SITE object contains other information about the language distribution.

class Site
get_default_language(self)

The django code of the default language to use in every lino.utils.mldbc.fields.LanguageField.

LANGUAGE_CHOICES

A tuple in the format expected by Django's Field.choices attribute, used e.g. by LanguageField. Its content is automatically populated from languages and application code should not change its value.

get_language_info(self, code)

Use this in Python fixtures or tests to test whether a Site instance supports a given language. code must be a Django-style language code.

On a site with only one locale of a language (and optionally some other languages), you can use only the language code to get a tuple of LanguageInfo objects.

>>> from lino.core.site import TestSite as Site
>>> Site(languages="en-us fr de-be de").get_language_info('en')
LanguageInfo(django_code='en-us', name='en_US', index=0, suffix='')

On a site with two locales of a same language (e.g. 'en-us' and 'en-gb'), the simple code 'en' yields that first variant:

>>> site = Site(languages="en-us en-gb")
>>> print(site.get_language_info('en'))
LanguageInfo(django_code='en-us', name='en_US', index=0, suffix='')
resolve_languages(self, languages)

This is used by UserType.

Examples:

>>> from lino.core.site import TestSite as Site
>>> lst = Site(languages="en fr de nl et pt").resolve_languages('en fr')
>>> [i.name for i in lst]
['en', 'fr']

You may not specify languages that don't exist on this site:

>>> Site(languages="en fr de").resolve_languages('en nl')
Traceback (most recent call last):
...
Exception: Unknown language code 'nl' (must be one of ['en', 'fr', 'de'])
str2kw(self, name, txt, **kw)

Return a dictionary that maps the internal field names for babelfield name to their respective translation of the given lazy translatable string txt.

>>> from django.utils.translation import gettext_lazy as _
>>> from lino.core.site import TestSite as Site
>>> site = Site(languages='de fr es')
>>> site.str2kw('name', _("January")) == {'name_fr': 'janvier', 'name': 'Januar', 'name_es': 'Enero'}
True
>>> site = Site(languages='fr de es')
>>> site.str2kw('name', _("January")) == {'name_de': 'Januar', 'name': 'janvier', 'name_es': 'Enero'}
True
babelkw(self, name, **kw)

Return a dict with appropriate resolved field names for a BabelField name and a set of hard-coded values.

This function is no longer recommended to use for application code. When some application code uses:

babelkw("fieldname", en="foo", de="Foo", fr="Phou", ...)

this code should be replaced by:

str2kw(_("foo"))

and after this change you must run inv mm, which will add "foo" as a translatable string to the .po files. And then you must use poedit to add the formerly hard-coded translations ("Foo" for de and "Phou" for fr) in the the .po files.

But this function is still being used in Python fixtures (where it makes sense).

You have some hard-coded multilingual content in a fixture: >>> from lino.core.site import TestSite as Site >>> kw = dict(de="Hallo", en="Hello", fr="Salut")

The field names where this info gets stored depends on the Site's languages distribution.

>>> Site(languages="de-be en").babelkw('name',**kw) == {'name_en': 'Hello', 'name': 'Hallo'}
True
>>> Site(languages="en de-be").babelkw('name',**kw) == {'name_de_BE': 'Hallo', 'name': 'Hello'}
True
>>> Site(languages="en-gb de").babelkw('name',**kw) == {'name_de': 'Hallo', 'name': 'Hello'}
True
>>> Site(languages="en").babelkw('name',**kw) == {'name': 'Hello'}
True
>>> Site(languages="de-be en").babelkw('name',de="Hallo",en="Hello") == {'name_en': 'Hello', 'name': 'Hallo'}
True

In the following example babelkw attributes the keyword de to the first language variant:

>>> Site(languages="de-ch de-be").babelkw('name',**kw) == {'name': 'Hallo'}
True
args2kw(self, name, *args)

Takes the basename of a BabelField and the values for each language. Returns a dict mapping the actual fieldnames to their values.

field2kw(self, obj, name, **known_values)

Return a dict with all values of the BabelField name in the given object obj. The dict will have one key for each languages.

Examples:

>>> from lino.core.site import TestSite as Site
>>> from lino.utils import AttrDict
>>> def testit(site_languages):
...     site = Site(languages=site_languages)
...     obj = AttrDict(site.babelkw(
...         'name', de="Hallo", en="Hello", fr="Salut"))
...     return site,obj
>>> site, obj = testit('de en')
>>> site.field2kw(obj, 'name') == {'de': 'Hallo', 'en': 'Hello'}
True
>>> site, obj = testit('fr et')
>>> site.field2kw(obj, 'name') == {'fr': 'Salut'}
True
field2args(self, obj, name)

Return a list of the babel values of this field in the order of this Site's Site.languages attribute.

babelitem(self, *args, **values)

Given a dictionary with babel values, return the value corresponding to the current language.

This is available in templates as a function tr.

>>> kw = dict(de="Hallo", en="Hello", fr="Salut")
>>> from lino.core.site import TestSite as Site
>>> from django.utils import translation

A Site with default language "de":

>>> site = Site(languages="de en")
>>> tr = site.babelitem
>>> with translation.override('de'):
...    print(tr(**kw))
Hallo
>>> with translation.override('en'):
...    print(tr(**kw))
Hello

If the current language is not found in the specified values, then it returns the site's default language:

>>> with translation.override('jp'):
...    print(tr(en="Hello", de="Hallo", fr="Salut"))
Hallo

Testing detail: default language should be "de" in our example, but we are playing here with more than one Site instance while Django knows only one "default language" which is the one specified in lino.projects.docs.settings.

Another way is to specify an explicit default value using a positional argument. In that case the language's default language doesn'n matter:

>>> with translation.override('jp'):
...    print(tr("Tere", de="Hallo", fr="Salut"))
Tere
>>> with translation.override('de'):
...     print(tr("Tere", de="Hallo", fr="Salut"))
Hallo

You may not specify more than one default value:

>>> tr("Hello", "Hallo")
Traceback (most recent call last):
...
ValueError: ('Hello', 'Hallo') is more than 1 default value.
babelattr(self, obj, attrname, default=NOT_PROVIDED, language=None)

Return the value of the specified babel field attrname of obj in the current language.

This is to be used in multilingual document templates. For example in a document template of a contract you may use the following expression:

babelattr(self.type, 'name')

This will return the correct value for the current language.

Examples:

>>> from __future__ import unicode_literals
>>> from django.utils import translation
>>> from lino.core.site import TestSite as Site
>>> from lino.utils import AttrDict
>>> def testit(site_languages):
...     site = Site(languages=site_languages)
...     obj = AttrDict(site.babelkw(
...         'name', de="Hallo", en="Hello", fr="Salut"))
...     return site, obj
>>> site,obj = testit('de en')
>>> with translation.override('de'):
...     print(site.babelattr(obj,'name'))
Hallo
>>> with translation.override('en'):
...     print(site.babelattr(obj,'name'))
Hello

If the object has no translation for a given language, return the site's default language. Two possible cases:

The language exists on the site, but the object has no translation for it:

>>> site,obj = testit('en es')
>>> with translation.override('es'):
...     print(site.babelattr(obj, 'name'))
Hello

Or a language has been activated which doesn't exist on the site:

>>> with translation.override('fr'):
...     print(site.babelattr(obj, 'name'))
Hello
class LanguageInfo

A named tuple with four fields:

django_code

How Django calls this language

name

How Lino calls it

index

The position in the Site.languages tuple

suffix

The suffix to append to babel fields for this language