https://github.com/ulule/django-linguist
An application to manage translations in Django models
https://github.com/ulule/django-linguist
Last synced: over 1 year ago
JSON representation
An application to manage translations in Django models
- Host: GitHub
- URL: https://github.com/ulule/django-linguist
- Owner: ulule
- License: mit
- Created: 2014-12-03T15:04:52.000Z (over 11 years ago)
- Default Branch: master
- Last Pushed: 2024-08-09T11:47:26.000Z (almost 2 years ago)
- Last Synced: 2024-08-09T13:07:46.852Z (almost 2 years ago)
- Language: Python
- Homepage:
- Size: 385 KB
- Stars: 118
- Watchers: 9
- Forks: 8
- Open Issues: 1
-
Metadata Files:
- Readme: README.rst
- License: LICENSE
Awesome Lists containing this project
README
django-linguist
===============
.. image:: https://secure.travis-ci.org/ulule/django-linguist.png?branch=master
:alt: Build Status
:target: http://travis-ci.org/ulule/django-linguist
`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
------------
.. code-block:: bash
$ pip install django-linguist
In your ``settings.py``, add ``linguist`` to ``INSTALLED_APPS``:
.. code-block:: python
INSTALLED_APPS = (
# Your other apps here
'linguist',
)
Then synchronize database:
.. code-block:: bash
# >= 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:
.. code-block:: python
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:
.. code-block:: python
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:
.. code-block:: python
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:
.. code-block:: python
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:
.. code-block:: python
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.
.. code-block:: python
# 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):
.. code-block:: python
>>> Post.objects.with_translations()
For a list of objects (all your objects must inherit from Linguist model):
.. code-block:: python
>>> from linguist.helpers import prefetch_translations
>>> posts = list(Post.objects.all())
>>> prefetch_translations(posts)
For an instance (it must inherit from Linguist model):
.. code-block:: python
>>> 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:
.. code-block:: python
>>> 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:
.. code-block:: python
>>> 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.
.. code-block:: python
>>> Post.objects.with_translations(populate_missing=False)
>>> post.title_fr # database hit here
''
Development
-----------
.. code-block:: bash
# 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
.. _django-linguist: https://github.com/ulule/django-linguist
.. _Django: http://djangoproject.com
.. _django-parler: https://github.com/edoburu/django-parler