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.six import with_metaclass
from django.utils.translation import ugettext_lazy as _

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


class PostManager(LinguistManagerMixin, models.Manager):
    pass


class Post(with_metaclass(LinguistMeta, models.Model)):
    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.six import with_metaclass
from django.utils.translation import ugettext_lazy as _

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


class PostManager(LinguistManagerMixin, models.Manager):
    pass


class Post(with_metaclass(LinguistMeta, models.Model)):
    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.six import with_metaclass
from django.utils.translation import ugettext_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(with_metaclass(LinguistMeta, models.Model)):
    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

Compatibility

  • python 2.7: Django 1.8, 1.9, 1.10
  • Python 3.4: Django 1.8, 1.9, 1.10
  • Python 3.5: Django 1.8, 1.9, 1.10


django-linguist

Build Status

django-linguist Django 应用灵活的模型翻译。

这里有一些与其他应用程序相比较的定义此应用程序的原则:

  • 翻译存储在单个表格中,您也可以使用每个模型不同的表单
  • 没有每个模型一个i18n表,对恶梦说再见:)
  • 没有更多痛苦的迁移
  • 不限于模型类名称,您可以自由使用自己的标识符
  • 没有ORM查询黑客,它没有补丁,更容易升级你的Django
  • 没有魔法,它使用元类和混合,一切都是显式的
  • 很简单,可以插入现有的项目
  • Django管理员准备好

如果您正在寻找one-i18n-table-per-model方式, django-parler 是 一个很棒的选择。

安装

$ pip install django-linguist

settings.py 中,将语言学家添加到 INSTALLED_APPS

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

然后同步数据库:

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

# < Django 1.7 $ python manage.py syncdb

就是这样。

配置

型号

三个步骤:

  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
不用担心,这很简单:

from django.db import models
from django.utils.six import with_metaclass
from django.utils.translation import ugettext_lazy as _

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

class PostManager(LinguistManagerMixin, models.Manager): pass

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

<span class="pl-k">class</span> <span class="pl-en">Meta</span>:
    verbose_name <span class="pl-k">=</span> _(<span class="pl-s"><span class="pl-pds">&#39;</span>post<span class="pl-pds">&#39;</span></span>)
    verbose_name_plural <span class="pl-k">=</span> _(<span class="pl-s"><span class="pl-pds">&#39;</span>posts<span class="pl-pds">&#39;</span></span>)
    linguist <span class="pl-k">=</span> {
        <span class="pl-s"><span class="pl-pds">&#39;</span>identifier<span class="pl-pds">&#39;</span></span>: <span class="pl-s"><span class="pl-pds">&#39;</span>can-be-anything-you-want<span class="pl-pds">&#39;</span></span>,
        <span class="pl-s"><span class="pl-pds">&#39;</span>fields<span class="pl-pds">&#39;</span></span>: (<span class="pl-s"><span class="pl-pds">&#39;</span>title<span class="pl-pds">&#39;</span></span>, <span class="pl-s"><span class="pl-pds">&#39;</span>body<span class="pl-pds">&#39;</span></span>),
        <span class="pl-s"><span class="pl-pds">&#39;</span>default_language<span class="pl-pds">&#39;</span></span>: <span class="pl-s"><span class="pl-pds">&#39;</span>fr<span class="pl-pds">&#39;</span></span>,
    }</pre></div>

语言学家元要求:

  • 标识符:您的模型的唯一标识符(可以是任何您想要的)
  • fields :要转换的模型字段的列表或元组

还可以要求:

  • default_language :使用的默认语言
  • default_language_field :包含要使用的默认语言的字段(见下文)
  • decider :使用的翻译模型而不是默认的(见下文)
这就是所有。你准备好了。

每个实例的默认语言

有时,您需要在实例级别定义默认语言。语言学家 通过 default_language_field 选项支持此功能。添加一个字段 在您的模型中,将存储默认语言,然后简单地给出该字段 名字给语言学家。

举一个例子:

from django.db import models
from django.utils.six import with_metaclass
from django.utils.translation import ugettext_lazy as _

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

class PostManager(LinguistManagerMixin, models.Manager): pass

class Post(with_metaclass(LinguistMeta, models.Model)): 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()

<span class="pl-k">class</span> <span class="pl-en">Meta</span>:
    verbose_name <span class="pl-k">=</span> _(<span class="pl-s"><span class="pl-pds">&#39;</span>post<span class="pl-pds">&#39;</span></span>)
    verbose_name_plural <span class="pl-k">=</span> _(<span class="pl-s"><span class="pl-pds">&#39;</span>posts<span class="pl-pds">&#39;</span></span>)
    linguist <span class="pl-k">=</span> {
        <span class="pl-s"><span class="pl-pds">&#39;</span>identifier<span class="pl-pds">&#39;</span></span>: <span class="pl-s"><span class="pl-pds">&#39;</span>can-be-anything-you-want<span class="pl-pds">&#39;</span></span>,
        <span class="pl-s"><span class="pl-pds">&#39;</span>fields<span class="pl-pds">&#39;</span></span>: (<span class="pl-s"><span class="pl-pds">&#39;</span>title<span class="pl-pds">&#39;</span></span>, <span class="pl-s"><span class="pl-pds">&#39;</span>body<span class="pl-pds">&#39;</span></span>),
        <span class="pl-s"><span class="pl-pds">&#39;</span>default_language<span class="pl-pds">&#39;</span></span>: <span class="pl-s"><span class="pl-pds">&#39;</span>en<span class="pl-pds">&#39;</span></span>,
        <span class="pl-s"><span class="pl-pds">&#39;</span>default_language_field<span class="pl-pds">&#39;</span></span>: <span class="pl-s"><span class="pl-pds">&#39;</span>lang<span class="pl-pds">&#39;</span></span>,
    }</pre></div>

用于翻译的自定义表

默认情况下,语言学家将翻译存储在 linguist.models.Translation 中 表。所以在一个单一的表。如果您需要使用另一个表格来指定 模型,语言学家提供了一种覆盖这种行为的方法:使用决策器

这很容易实现。

您可以通过三个步骤进行:

  • 创建一个继承自 linguist.models.base.Translation
  • 的模型
  • 不要忘记在Meta中定义具体( abstract = False
  • 将此模型提供给语言学家meta decider 选项

此示例将向您显示指示灯:

from django.db import models
from django.utils.six import with_metaclass
from django.utils.translation import ugettext_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(with_metaclass(LinguistMeta, models.Model)): title = models.CharField(max_length=255) body = models.TextField() created_at = models.DateTimeField(auto_now_add=True) objects = PostManager()

<span class="pl-k">class</span> <span class="pl-en">Meta</span>:
    verbose_name <span class="pl-k">=</span> _(<span class="pl-s"><span class="pl-pds">&#39;</span>post<span class="pl-pds">&#39;</span></span>)
    verbose_name_plural <span class="pl-k">=</span> _(<span class="pl-s"><span class="pl-pds">&#39;</span>posts<span class="pl-pds">&#39;</span></span>)
    linguist <span class="pl-k">=</span> {
        <span class="pl-s"><span class="pl-pds">&#39;</span>identifier<span class="pl-pds">&#39;</span></span>: <span class="pl-s"><span class="pl-pds">&#39;</span>can-be-anything-you-want<span class="pl-pds">&#39;</span></span>,
        <span class="pl-s"><span class="pl-pds">&#39;</span>fields<span class="pl-pds">&#39;</span></span>: (<span class="pl-s"><span class="pl-pds">&#39;</span>title<span class="pl-pds">&#39;</span></span>, <span class="pl-s"><span class="pl-pds">&#39;</span>body<span class="pl-pds">&#39;</span></span>),
        <span class="pl-s"><span class="pl-pds">&#39;</span>default_language<span class="pl-pds">&#39;</span></span>: <span class="pl-s"><span class="pl-pds">&#39;</span>fr<span class="pl-pds">&#39;</span></span>,
        <span class="pl-s"><span class="pl-pds">&#39;</span>decider<span class="pl-pds">&#39;</span></span>: PostTranslation,
    }</pre></div>

django.contrib.admin

只需使用 linguist.admin.TranslatableModelAdmin 类:

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)

奖金!您可以通过 list_display 来显示实例的语言 由admin类提供的 languages_column 属性:

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)

工作原理

语言学家将虚拟语言字段添加到您的模型中。对于上面的例子,如果 我们在 settings.LANGUAGES 中有 en fr it Post 模型中动态添加以下字段:

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

这些字段是虚拟的。它们不存在于 Post 表中。有 包装在 linguist.Translation 模型。所有翻译将被存储 在这个表格中。

当您设置/获取 post.title 时,语言学家将使用当前的活动语言 并将为此语言设置/获取正确的字段。例如,如果你的 默认语言为英文( en ),则 Post.title 将引用 post.title_en ModelMixin 使用以下属性和方法增强您的模型:

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()

预载

为了提高性能,您可以预加载/预取翻译。

对于查询器(您的查询器必须继承自语言学家经理/查询器):

>>> Post.objects.with_translations()

对于对象列表(所有对象必须从语言学家模型中继承):

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

对于实例(它必须从语言学家模型继承):

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

所有翻译将被缓存在实例中。数据库不再被打了。

此预加载系统需要三个参数:

  • field_names :要在
  • 上过滤的可翻译字段名称列表
  • languages :要在
  • 上过滤的语言列表
  • populate_missing :如果您要为缺少的翻译填充缓存,则为boolean(默认为 True
  • chunks_length :SELECT IN ids用于翻译的块限制

例如,我们只想用英文预取帖子标题,而不填写错误 具有空字符串的翻译:

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

它的作用相同:

  • QuerySet with_translations()
  • Helper prefetch_translations()
  • 实例方法 prefetch_translations()

填充缺少的翻译是什么意思?

简单。默认情况下,当您预取转换时,实例缓存将被填充 使用所有支持的语言的空字符串(请参阅 settings )。例如,如果 您有 en fr it 作为支持的语言,只有英文 翻译,如果您尝试访问其他语言,将返回一个空字符串 没有任何数据库命中:

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

现在,如果您将 populate_missing 显式设置为 False ,如果翻译 没有找到,它将从数据库中获取。

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

开发

# 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

兼容性

  • python 2.7:Django 1.8,1.9,1.10
  • Python 3.4:Django 1.8,1.9,1.10
  • Python 3.5:Django 1.8,1.9,1.10




相关问题推荐