Package: django-linguist
django-linguistis aDjangoapplication 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 modelNo “one i18n table per model”, say “goodbye” to nightmares :)No more painful migrationsNot tied to model class names, you are free to use your own identifiersNo ORM query hacks, it does not patch anything and it will be easier for you to upgrade your DjangoNo magic, it uses metaclasses and mixins and everything is explicitDead simple to plug in an existing projectDjango admin readyIf you are looking for a “one-i18n-table-per-model” way,django-parleris
an awesome alternative.Installation$pipinstalldjango-linguistIn yoursettings.py, addlinguisttoINSTALLED_APPS:INSTALLED_APPS=(# Your other apps here'linguist',)Then synchronize database:# >= Django 1.7$pythonmanage.pymigratelinguist# < Django 1.7$pythonmanage.pysyncdbThat’s all.ConfigurationModelsIn three steps:Addlinguist.metaclasses.ModelMetato your model as metaclassAddlinguist.mixins.ManagerMixinto your model managerAddlinguistsettings in your model’s MetaDon’t worry, it’s fairly simple:fromdjango.dbimportmodelsfromdjango.utils.translationimportgettext_lazyas_fromlinguist.metaclassesimportModelMetaasLinguistMetafromlinguist.mixinsimportManagerMixinasLinguistManagerMixinclassPostManager(LinguistManagerMixin,models.Manager):passclassPost(models.Model,meta=LinguistMeta):title=models.CharField(max_length=255)body=models.TextField()created_at=models.DateTimeField(auto_now_add=True)objects=PostManager()classMeta:verbose_name=_('post')verbose_name_plural=_('posts')linguist={'identifier':'can-be-anything-you-want','fields':('title','body'),'default_language':'fr',}Thelinguistmeta requires:identifier: a unique identifier for your model (can be anything you want)fields: list or tuple of model fields to translateAnd optionally requires:default_language: the default language to usedefault_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 instanceSometimes, you need to define default language at instance level. Linguist
supports this feature via thedefault_language_fieldoption. 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:fromdjango.dbimportmodelsfromdjango.utils.translationimportgettext_lazyas_fromlinguist.metaclassesimportModelMetaasLinguistMetafromlinguist.mixinsimportManagerMixinasLinguistManagerMixinclassPostManager(LinguistManagerMixin,models.Manager):passclassPost(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()classMeta: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 translationsBy default, Linguist stores translations intolinguist.models.Translationtable. 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: usedeciders.That’s really easy to implement.You can do it in three steps:Create a model that inherits fromlinguist.models.base.TranslationDon’t forget to define it as concrete (abstract = Falsein Meta)Give this model to Linguist metadecideroptionThis example will show you the light:fromdjango.dbimportmodelsfromdjango.utils.translationimportgettext_lazyas_fromlinguist.metaclassesimportModelMetaasLinguistMetafromlinguist.mixinsimportManagerMixinasLinguistManagerMixinfromlinguist.models.baseimportTranslation# Our Post model deciderclassPostTranslation(Translation):classMeta:abstract=FalseclassPostManager(LinguistManagerMixin,models.Manager):passclassPost(models.Model,meta=LinguistMeta):title=models.CharField(max_length=255)body=models.TextField()created_at=models.DateTimeField(auto_now_add=True)objects=PostManager()classMeta:verbose_name=_('post')verbose_name_plural=_('posts')linguist={'identifier':'can-be-anything-you-want','fields':('title','body'),'default_language':'fr','decider':PostTranslation,}django.contrib.adminSimply uselinguist.admin.TranslatableModelAdminclass:fromdjango.contribimportadminfromlinguist.adminimportTranslatableModelAdminfrom.modelsimportPostclassPostAdmin(TranslatableModelAdmin):list_display=('title','body','created_at')admin.site.register(Post,PostAdmin)Bonus! You can display instance’s languages inlist_displayvia thelanguages_columnproperty provided by the admin class:fromdjango.contribimportadminfromlinguist.adminimportTranslatableModelAdminfrom.modelsimportPostclassPostAdmin(TranslatableModelAdmin):list_display=('title','body','languages_column','created_at')admin.site.register(Post,PostAdmin)How it worksLinguist adds virtual language fields to your models. For the example above, if
we haveen,franditinsettings.LANGUAGES, it
dynamically adds the following fields inPostmodel:Post.title_enPost.title_frPost.title_itPost.body_enPost.body_frPost.body_itThese fields are virtuals. They don’t exist inPosttable. There are
wrappers aroundlinguist.Translationmodel. All translations will be stored
in this table.When you set/getpost.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), thenPost.titlewill refer topost.title_en.TheModelMixinenhance your model with the following properties and methods:instance.linguist_identifier(read-onlyproperty)Your model identifier defined in the related translation class.
Shortcut pointing oninstance._linguist.identifier.instance.default_language(read-writeproperty)The default language to use.
Shortcut pointing oninstance._linguist.default_language.instance.translatable_fields(read-onlyproperty)Translatable fields defined in the related translation class.
Shorcut pointing oninstance._linguist.fields.instance.available_languages(read-onlyproperty)Available languages for this instance (content translated in these languages).instance.cached_translations_count(read-onlyproperty)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 createTranslationobjets 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.titleHello# And now for French>>>post.activate_language('fr')>>>post.titleBonjour# Sweet! Save translations!>>>post.save()PreloadingTo 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):>>>fromlinguist.helpersimportprefetch_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 onlanguages: list of languages to filter onpopulate_missing: boolean if you want to populate cache for missing translations (defaults toTrue)chunks_length: chunk limit for SELECT IN ids for translationsFor 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:QuerySetwith_translations()Helperprefetch_translations()Instance methodprefetch_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 (seesettings). For example, if
you haveen,franditas 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 setpopulate_missingtoFalse, 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?$sudoeasy_installpip# Don't already have virtualenv?$sudopipinstallvirtualenv# Clone and install dependencies$gitclonehttps://github.com/ulule/django-linguist.git$cddjango-linguist$makedevenv# Enable virtual environment.$source.venv/bin/activate# Launch tests$maketest# Launch example project$makeserve