• Stars
    star
    118
  • Rank 299,923 (Top 6 %)
  • Language
    Python
  • License
    MIT License
  • Created almost 10 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

An application to manage translations in Django models

django-linguist

Build Status

django-linguist is a Django application for flexible model translations.

Here a few principles that define this application in comparaison to others applications:

  • Translations are stored in single one table and you can also use a different one per model
  • No "one i18n table per model", say "goodbye" to nightmares :)
  • No more painful migrations
  • Not tied to model class names, you are free to use your own identifiers
  • No ORM query hacks, it does not patch anything and it will be easier for you to upgrade your Django
  • No magic, it uses metaclasses and mixins and everything is explicit
  • Dead simple to plug in an existing project
  • Django admin ready

If you are looking for a "one-i18n-table-per-model" way, django-parler is an awesome alternative.

Installation

$ pip install django-linguist

In your settings.py, add linguist to INSTALLED_APPS:

INSTALLED_APPS = (
    # Your other apps here
    'linguist',
)

Then synchronize database:

# >= Django 1.7
$ python manage.py migrate linguist

# < Django 1.7
$ python manage.py syncdb

That's all.

Configuration

Models

In three steps:

  1. Add linguist.metaclasses.ModelMeta to your model as metaclass
  2. Add linguist.mixins.ManagerMixin to your model manager
  3. Add linguist settings in your model's Meta

Don't worry, it's fairly simple:

from django.db import models
from django.utils.translation import gettext_lazy as _

from linguist.metaclasses import ModelMeta as LinguistMeta
from linguist.mixins import ManagerMixin as LinguistManagerMixin


class PostManager(LinguistManagerMixin, models.Manager):
    pass


class Post(models.Model, meta=LinguistMeta):
    title = models.CharField(max_length=255)
    body = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    objects = PostManager()

    class Meta:
        verbose_name = _('post')
        verbose_name_plural = _('posts')
        linguist = {
            'identifier': 'can-be-anything-you-want',
            'fields': ('title', 'body'),
            'default_language': 'fr',
        }

The linguist meta requires:

  • identifier: a unique identifier for your model (can be anything you want)
  • fields: list or tuple of model fields to translate

And optionally requires:

  • default_language: the default language to use
  • default_language_field: the field that contains the default language to use (see below)
  • decider: the translation model to use instead of the default one (see below)

That's all. You're ready.

Default language per instance

Sometimes, you need to define default language at instance level. Linguist supports this feature via the default_language_field option. Add a field in your model that will store the default language then simply give the field name to Linguist.

Let's take an example:

from django.db import models
from django.utils.translation import gettext_lazy as _

from linguist.metaclasses import ModelMeta as LinguistMeta
from linguist.mixins import ManagerMixin as LinguistManagerMixin


class PostManager(LinguistManagerMixin, models.Manager):
    pass


class Post(models.Model, meta=LinguistMeta):
    title = models.CharField(max_length=255)
    body = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    lang = models.CharField(max_length=5, default='en')
    objects = PostManager()

    class Meta:
        verbose_name = _('post')
        verbose_name_plural = _('posts')
        linguist = {
            'identifier': 'can-be-anything-you-want',
            'fields': ('title', 'body'),
            'default_language': 'en',
            'default_language_field': 'lang',
        }

Custom table for translations

By default, Linguist stores translations into linguist.models.Translation table. So in a single one table. If you need to use another table for a specific model, Linguist provides a way to override this behavior: use deciders.

That's really easy to implement.

You can do it in three steps:

  • Create a model that inherits from linguist.models.base.Translation
  • Don't forget to define it as concrete (abstract = False in Meta)
  • Give this model to Linguist meta decider option

This example will show you the light:

from django.db import models
from django.utils.translation import gettext_lazy as _

from linguist.metaclasses import ModelMeta as LinguistMeta
from linguist.mixins import ManagerMixin as LinguistManagerMixin
from linguist.models.base import Translation


# Our Post model decider
class PostTranslation(Translation):
    class Meta:
        abstract = False


class PostManager(LinguistManagerMixin, models.Manager):
    pass


class Post(models.Model, meta=LinguistMeta):
    title = models.CharField(max_length=255)
    body = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    objects = PostManager()

    class Meta:
        verbose_name = _('post')
        verbose_name_plural = _('posts')
        linguist = {
            'identifier': 'can-be-anything-you-want',
            'fields': ('title', 'body'),
            'default_language': 'fr',
            'decider': PostTranslation,
        }

django.contrib.admin

Simply use linguist.admin.TranslatableModelAdmin class:

from django.contrib import admin
from linguist.admin import TranslatableModelAdmin
from .models import Post


class PostAdmin(TranslatableModelAdmin):
    list_display = ('title', 'body', 'created_at')

admin.site.register(Post, PostAdmin)

Bonus! You can display instance's languages in list_display via the languages_column property provided by the admin class:

from django.contrib import admin
from linguist.admin import TranslatableModelAdmin
from .models import Post


class PostAdmin(TranslatableModelAdmin):
    list_display = ('title', 'body', 'languages_column', 'created_at')

admin.site.register(Post, PostAdmin)

How it works

Linguist adds virtual language fields to your models. For the example above, if we have en, fr and it in settings.LANGUAGES, it dynamically adds the following fields in Post model:

  • Post.title_en
  • Post.title_fr
  • Post.title_it
  • Post.body_en
  • Post.body_fr
  • Post.body_it

These fields are virtuals. They don't exist in Post table. There are wrappers around linguist.Translation model. All translations will be stored in this table.

When you set/get post.title, Linguist will use the current active language and will set/get the correct field for this language. For example, if your default language is English (en), then Post.title will refer to post.title_en.

The ModelMixin enhance your model with the following properties and methods:

instance.linguist_identifier (read-only property)
Your model identifier defined in the related translation class. Shortcut pointing on instance._linguist.identifier.
instance.default_language (read-write property)
The default language to use. Shortcut pointing on instance._linguist.default_language.
instance.translatable_fields (read-only property)
Translatable fields defined in the related translation class. Shorcut pointing on instance._linguist.fields.
instance.available_languages (read-only property)
Available languages for this instance (content translated in these languages).
instance.cached_translations_count (read-only property)
Returns the number of cached translations. Each time you set a new language and set content on translatable fields, a cache is created for each language and field. It will be used to create Translation objets at instance saving.
instance.active_language()
Set the current active language for the instance.
instance.clear_translations_cache()
Remove all cached translations. Be aware, any content you set will be dropped. So no translation will be created/updated at saving.
# Let's create a new Post
>>> post = Post()

# Set English content
>>> post.activate_language('en')
>>> post.title = 'Hello'

# Now set French content
>>> post.activate_language('fr')
>>> post.title = 'Bonjour'

# Be sure everything works as expected for English
>>> post.activate_language('en')
>>> post.title
Hello

# And now for French
>>> post.activate_language('fr')
>>> post.title
Bonjour

# Sweet! Save translations!
>>> post.save()

Preloading

To improve performances, you can preload/prefetch translations.

For a queryset (your queryset must inherit from Linguist manager/queryset):

>>> Post.objects.with_translations()

For a list of objects (all your objects must inherit from Linguist model):

>>> from linguist.helpers import prefetch_translations
>>> posts = list(Post.objects.all())
>>> prefetch_translations(posts)

For an instance (it must inherit from Linguist model):

>>> post = Post.objects.first()
>>> post.prefetch_translations()

All translations will be cached in instances. Database won't be hit anymore.

This preloading system takes three parameters:

  • field_names: list of translatable field names to filter on
  • languages: list of languages to filter on
  • populate_missing: boolean if you want to populate cache for missing translations (defaults to True)
  • chunks_length: chunk limit for SELECT IN ids for translations

For example, we only want to prefetch post titles in English without populating missing translations with an empty string:

>>> Post.objects.with_translations(field_names=['title'], languages=['en'], populate_missing=False)

It works the same for:

  • QuerySet with_translations()
  • Helper prefetch_translations()
  • Instance method prefetch_translations()

What does "populating missing translations" mean?

Simple. By default, when you prefetch translations, instances cache will be populated with empty strings for all supported languages (see settings). For example, if you have en, fr and it as supported languages and only have English translations, if you try to access other languages, an empty string will be returned without any database hit:

>>> Post.objects.with_translations()
>>> post.title_fr # no database hit here because
''

Now, if you explicitly set populate_missing to False, if a translation is not found, it will be fetched from database.

>>> Post.objects.with_translations(populate_missing=False)
>>> post.title_fr # database hit here
''

Development

# Don't have pip?
$ sudo easy_install pip

# Don't already have virtualenv?
$ sudo pip install virtualenv

# Clone and install dependencies
$ git clone https://github.com/ulule/django-linguist.git
$ cd django-linguist
$ make devenv

# Enable virtual environment.
$ source .venv/bin/activate

# Launch tests
$ make test

# Launch example project
$ make serve

More Repositories

1

limiter

Dead simple rate limit middleware for Go.
Go
2,027
star
2

deepcopier

simple struct copying for golang
Go
447
star
3

loukoum

A simple SQL Query Builder
Go
320
star
4

python-logstash-formatter

python JSON log formatter with a logstash compatible schema
Python
158
star
5

django-safety

Generic Django application for safer user accounts.
Python
142
star
6

paging

A small set of utilities to paginate your data in Go
Go
140
star
7

django-courriers

A generic application to manage your newsletters
Python
86
star
8

django-badgify

A reusable application to create your own badge engine using Django
Python
83
star
9

gostorages

A unified interface to manipulate storage engine (file system, s3, etc.) for Go
Go
70
star
10

geoipfix

A Go service (HTTP+RPC) to retrieve location information
Go
48
star
11

limiter-examples

Examples to use limiter
Go
38
star
12

django-separatedvaluesfield

Custom field for Django to separate multiple values in database with a separator and retrieve them as list
Python
26
star
13

go

Ulule bookshelf for Go programing language
22
star
14

gokvstores

A collection of custom key/value storage backends for Go
Go
21
star
15

picfit-go

A Go client library for generating URLs with picfit
Go
14
star
16

makroud

A high level SQL Connector
Go
11
star
17

django-metasettings

A reusable Django application to control the currency rate and favorite language code, inspired by etsy
Python
9
star
18

django-discussions

Messaging system for your users
Python
9
star
19

dekiteru

Dekiteru is a tool that check if a service is ready to use
Go
4
star
20

python-mangopay

A client library for interacting with the Mangopay API
Python
4
star
21

pybbm

A fork of pybbm designed for high traffic websites
Python
4
star
22

ulule-rs

Rust API bindings for the Ulule HTTP API.
Rust
1
star
23

mover

Move data from a database to another
Go
1
star
24

helloapp

Simple hello world application to deploy on kubernetes
Go
1
star