Working with translated strings

If you want to save multi-lingual data into an I18nCharField or I18nTextField, you need to wrap it as an LazyI18nString first. Also, if you read data from such a field, you will always get an LazyI18nString back.

An LazyI18nString is a representation of a string that exists in multiple languages. Under the hood, it is just a dictionary that maps languages to values.

So why don’t we just use dictionaries? This is where the “lazy” comes into play: As soon as you try to evaluate an LazyI18nString as a normal string, it will magically transform into a normal string – based on the currently active language. This means that e.g. if you get a value from an I18nCharField and pass it to a template, the template will cast the value to a string and you do not need to do anything to make it work.

This behaviour is intentionally very similar to the gettext_lazy method from Django’s translation layer.

However, when you deal with such strings in python code, you should know how they behave. Therefore, we have a number of examples for you on this page.

To create a LazyI18nString, we can input a simple string:

>>> naive = LazyI18nString('Naive untranslated string')
>>> naive
<LazyI18nString: 'Naive untranslated string'>

Or we can provide a dictionary with multiple translations:

>>> translated = LazyI18nString(
...     {'en': 'English String', 'de': 'Deutscher String'}
... )

We can use the localize method to get the string in a specific language:

>>> translated.localize('de')
'Deutscher String'

>>> translated.localize('en')
'English String'

If we try a locale that does not exist for the string, we might get a it either in a similar locale or in the system’s default language:

>>> translated.localize('de-AT')
'Deutscher String'

>>> translated.localize('zh')
'English String'

>>> naive.localize('de')
'Naive untranslated string'


This is an important property of LazyI18nString: As long as there is any non-empty value for any language, you will rather get a result in the wrong language than an empty result. This makes it “safe” to use if your data is only partially translated.

If we cast a LazyI18nString to str, localize will be called with the currently active language:

>>> from django.utils import translation
>>> str(translated)
'English String'
>>> translation.activate('de')
>>> str(translated)
'Deutscher String'

Formatting also works as expected:

>>> translation.activate('de')
>>> '{}'.format(translated)
'Deutscher String'

If we want to modify all translations inside a LazyI18nString we can do so using the map method:

>>> s: s.replace('String','Text'))
>>> translation.activate('de')
>>> str(translated)
'Deutscher Text'

There is also a way to construct a hybrid object that takes its data from gettext but behaves like an LazyI18nString. The use case for this is very rare, it basically only is useful when defining default values for internationalized form fields in the codebase.

>>> from django.utils.translation import gettext_noop
>>> LazyI18nString.from_gettext(gettext_noop('Hello'))
<LazyI18nString: <LazyGettextProxy: 'Hello'>>