Translates Django model fields in a JSONField
using a registration approach.
- Uses one
django.contrib.postgres.JSONField
(PostgreSQL jsonb field) for every record. - Django 1.9, 1.10, 1.11 for now
- PostgreSQL >= 9.4 and Psycopg2 >= 2.5.4.
- Add
'modeltrans'
your list ofINSTALLED_APPS
. - Add a list of available languages to your
settings.py
:AVAILABLE_LANGUAGES = ('en', 'nl', 'de', 'fr')
- Add a
translation.py
in each app you want to translate models for. - For each model you want to translate, create a
TransltionOptions
object and register the model using that object:
# models.py
from django.db import models
class Blog(models.Model):
title = models.CharField(max_length=255)
body = models.TextField(null=True)
# translation.py
from modeltrans.translator import TranslationOptions, translator
from .models import Blog, Category
class BlogTranslationOptions(TranslationOptions):
fields = ('title', 'body')
translator.register(Blog, BlogTranslationOptions)
- Run
./manage.py makemigrations
to add thei18n
JSONField to each model containing translations. - Each method now has some extra virtual fields. In the example above:
title_nl
,title_de
, ... allow getting/setting the specific languagestitle_i18n
follows the currently active translation in Django, and falls back to the default language:
>>> b = Blog.objects.create(title='Falcon', title_nl='Valk')
>>> b.title
'Falcon'
>>> b.title_nl
'Valk'
>>> b.title_i18n
'Falcon'
>>> from django.utils.translation import override
>>> with override('nl'):
... b.title_i18n
...
'Valk'
# translations are stored in the field `i18n` in each model:
>>> b.i18n
{u'title_nl': u'Valk'}
# if a translation is not available, None is returned.
>>> print(b.title_de)
None
# fallback to the default language
>>> with override('de'):
... b.title_i18n
'Falcon'
# now, if we set the German tranlation, it it is returned from title_i18n:
>>> b.title_de = 'Falk'
>>> with override('de'):
... b.title_i18n
'Falk'
This is how to migrate from django-modeltranslation (version 0.12.1) to django-modeltrans:
- Make sure you have a recent backup available!
- Add
modeltrans
to yourINSTALLED_APPS
- Copy the django-modeltranslation registrations to use django-modeltrans alongside it, while disabling the virtual fields for now:
# if this whas your configuration:
from modeltranslation.translator import translator, TranslationOptions
from .models import Blog
class BlogTranslationOptions(TranslationOptions):
fields = ('name', 'title', )
translator.register(Blog, BlogTranslationOptions)
# this is what you add:
def i18n_migrate():
from modeltrans import translator, TranslationOptions
translator.set_create_virtual_fields(False)
class BlogTranslationOptions(TranslationOptions):
fields = ('name', 'title', )
translator.register(Blog, BlogTranslationOptions)
# and make sure it gets executed
i18n_migrate()
-
Run
./manage.py makemigrations <apps>
. This will create the migration adding thei18n
-fields required by django-modeltrans. Apply them with./manage.py migrate
-
We need to create a migration to copy the values of the translated fields into the newly created
i18n
-field. django-modeltrans provides a management command to do that:./manage.py i18n_makemmigrations <apps>
-
Now, remove django-modeltranslation by:
- Removing the translation registrations for
modeltranslation
from yourtranslation.py
's. Also remove the function around the new registrations. - Remove the use of
modeltranslation.admin.TranslationAdmin
in youradmin.py
's
Run
./manage.py makemigrations <apps>
. This will remove the translated fields from your registered models. You can now safely remove the linetranslator.set_create_virtual_fields(False)
and let django-modeltrans add the virtual fields to your models. - Removing the translation registrations for
-
Update your code and cleanup:
- Remove
modeltranslation
fromINSTALLED_APPS
. - Use
<field>_i18n
field names for places where you would use<field>
with django-modeltranslation. Less magic, but explicit is better than implicit!
- Remove
'''
tox
Running the tests only for the current environment, use make test
Some concepts and code from https://github.com/deschler/django-modeltranslation, which is in turn inspired by https://github.com/zmathew/django-linguo
We started this solution at Zostera because we did not like:
- The way django-modeltranslation adds one field per language (and thus requires a migration when adding language)
- The unpredictability of the original field.
Since JSONB is supported by Postgres now, we developed this approach.
- django-nence
Also uses a
jsonb
PostgreSQL field, but has a bunch of customQuerySet
andModel
methods to get translated values. It also requires one to inherit from aTranslationModel
. - django-i18nfield
Stores JSON in a
TextField
, so does not allow lookup, searching or ordering by the translated fields.