{"id":26518798,"url":"https://github.com/ulule/django-linguist","last_synced_at":"2025-03-21T10:02:28.332Z","repository":{"id":24104748,"uuid":"27492508","full_name":"ulule/django-linguist","owner":"ulule","description":"An application to manage translations in Django models","archived":false,"fork":false,"pushed_at":"2024-08-09T11:47:26.000Z","size":394,"stargazers_count":118,"open_issues_count":1,"forks_count":8,"subscribers_count":9,"default_branch":"master","last_synced_at":"2024-08-09T13:07:46.852Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ulule.png","metadata":{"files":{"readme":"README.rst","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-12-03T15:04:52.000Z","updated_at":"2024-08-09T11:47:29.000Z","dependencies_parsed_at":"2022-08-19T07:40:55.265Z","dependency_job_id":null,"html_url":"https://github.com/ulule/django-linguist","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ulule%2Fdjango-linguist","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ulule%2Fdjango-linguist/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ulule%2Fdjango-linguist/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ulule%2Fdjango-linguist/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ulule","download_url":"https://codeload.github.com/ulule/django-linguist/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244776331,"owners_count":20508506,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-03-21T10:02:27.427Z","updated_at":"2025-03-21T10:02:28.314Z","avatar_url":"https://github.com/ulule.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"django-linguist\n===============\n\n.. image:: https://secure.travis-ci.org/ulule/django-linguist.png?branch=master\n    :alt: Build Status\n    :target: http://travis-ci.org/ulule/django-linguist\n\n`django-linguist`_ is a Django_ application for flexible model translations.\n\nHere a few principles that define this application in comparaison to others applications:\n\n* Translations are stored in single one table and you can also use a different one per model\n* No \"one i18n table per model\", say \"goodbye\" to nightmares :)\n* No more painful migrations\n* Not tied to model class names, you are free to use your own identifiers\n* No ORM query hacks, it does not patch anything and it will be easier for you to upgrade your Django\n* No magic, it uses metaclasses and mixins and everything is explicit\n* Dead simple to plug in an existing project\n* Django admin ready\n\nIf you are looking for a \"one-i18n-table-per-model\" way, `django-parler`_ is\nan awesome alternative.\n\nInstallation\n------------\n\n.. code-block:: bash\n\n    $ pip install django-linguist\n\nIn your ``settings.py``, add ``linguist`` to ``INSTALLED_APPS``:\n\n.. code-block:: python\n\n    INSTALLED_APPS = (\n        # Your other apps here\n        'linguist',\n    )\n\nThen synchronize database:\n\n.. code-block:: bash\n\n    # \u003e= Django 1.7\n    $ python manage.py migrate linguist\n\n    # \u003c Django 1.7\n    $ python manage.py syncdb\n\nThat's all.\n\nConfiguration\n-------------\n\nModels\n~~~~~~\n\nIn three steps:\n\n1. Add ``linguist.metaclasses.ModelMeta`` to your model as metaclass\n2. Add ``linguist.mixins.ManagerMixin`` to your model manager\n3. Add ``linguist`` settings in your model's Meta\n\nDon't worry, it's fairly simple:\n\n.. code-block:: python\n\n    from django.db import models\n    from django.utils.translation import gettext_lazy as _\n\n    from linguist.metaclasses import ModelMeta as LinguistMeta\n    from linguist.mixins import ManagerMixin as LinguistManagerMixin\n\n\n    class PostManager(LinguistManagerMixin, models.Manager):\n        pass\n\n\n    class Post(models.Model, meta=LinguistMeta):\n        title = models.CharField(max_length=255)\n        body = models.TextField()\n        created_at = models.DateTimeField(auto_now_add=True)\n        objects = PostManager()\n\n        class Meta:\n            verbose_name = _('post')\n            verbose_name_plural = _('posts')\n            linguist = {\n                'identifier': 'can-be-anything-you-want',\n                'fields': ('title', 'body'),\n                'default_language': 'fr',\n            }\n\nThe ``linguist`` meta requires:\n\n* ``identifier``: a unique identifier for your model (can be anything you want)\n* ``fields``: list or tuple of model fields to translate\n\nAnd optionally requires:\n\n* ``default_language``: the default language to use\n* ``default_language_field``: the field that contains the default language to use (see below)\n* ``decider``: the translation model to use instead of the default one (see below)\n\nThat's all. You're ready.\n\nDefault language per instance\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nSometimes, you need to define default language at instance level. Linguist\nsupports this feature via the ``default_language_field`` option. Add a field\nin your model that will store the default language then simply give the field\nname to Linguist.\n\nLet's take an example:\n\n.. code-block:: python\n\n    from django.db import models\n    from django.utils.translation import gettext_lazy as _\n\n    from linguist.metaclasses import ModelMeta as LinguistMeta\n    from linguist.mixins import ManagerMixin as LinguistManagerMixin\n\n\n    class PostManager(LinguistManagerMixin, models.Manager):\n        pass\n\n\n    class Post(models.Model, meta=LinguistMeta):\n        title = models.CharField(max_length=255)\n        body = models.TextField()\n        created_at = models.DateTimeField(auto_now_add=True)\n        lang = models.CharField(max_length=5, default='en')\n        objects = PostManager()\n\n        class Meta:\n            verbose_name = _('post')\n            verbose_name_plural = _('posts')\n            linguist = {\n                'identifier': 'can-be-anything-you-want',\n                'fields': ('title', 'body'),\n                'default_language': 'en',\n                'default_language_field': 'lang',\n            }\n\nCustom table for translations\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nBy default, Linguist stores translations into ``linguist.models.Translation``\ntable. So in a single one table. If you need to use another table for a specific\nmodel, Linguist provides a way to override this behavior: use *deciders*.\n\nThat's really easy to implement.\n\nYou can do it in three steps:\n\n* Create a model that inherits from ``linguist.models.base.Translation``\n* Don't forget to define it as concrete (``abstract = False`` in Meta)\n* Give this model to Linguist meta ``decider`` option\n\nThis example will show you the light:\n\n.. code-block:: python\n\n\n    from django.db import models\n    from django.utils.translation import gettext_lazy as _\n\n    from linguist.metaclasses import ModelMeta as LinguistMeta\n    from linguist.mixins import ManagerMixin as LinguistManagerMixin\n    from linguist.models.base import Translation\n\n\n    # Our Post model decider\n    class PostTranslation(Translation):\n        class Meta:\n            abstract = False\n\n\n    class PostManager(LinguistManagerMixin, models.Manager):\n        pass\n\n\n    class Post(models.Model, meta=LinguistMeta):\n        title = models.CharField(max_length=255)\n        body = models.TextField()\n        created_at = models.DateTimeField(auto_now_add=True)\n        objects = PostManager()\n\n        class Meta:\n            verbose_name = _('post')\n            verbose_name_plural = _('posts')\n            linguist = {\n                'identifier': 'can-be-anything-you-want',\n                'fields': ('title', 'body'),\n                'default_language': 'fr',\n                'decider': PostTranslation,\n            }\n\ndjango.contrib.admin\n~~~~~~~~~~~~~~~~~~~~\n\nSimply use ``linguist.admin.TranslatableModelAdmin`` class:\n\n.. code-block:: python\n\n    from django.contrib import admin\n    from linguist.admin import TranslatableModelAdmin\n    from .models import Post\n\n\n    class PostAdmin(TranslatableModelAdmin):\n        list_display = ('title', 'body', 'created_at')\n\n    admin.site.register(Post, PostAdmin)\n\n\nBonus! You can display instance's languages in ``list_display`` via the\n``languages_column`` property provided by the admin class:\n\n.. code-block:: python\n\n    from django.contrib import admin\n    from linguist.admin import TranslatableModelAdmin\n    from .models import Post\n\n\n    class PostAdmin(TranslatableModelAdmin):\n        list_display = ('title', 'body', 'languages_column', 'created_at')\n\n    admin.site.register(Post, PostAdmin)\n\nHow it works\n------------\n\nLinguist adds virtual language fields to your models. For the example above, if\nwe have ``en``, ``fr`` and ``it`` in ``settings.LANGUAGES``, it\ndynamically adds the following fields in ``Post`` model:\n\n* ``Post.title_en``\n* ``Post.title_fr``\n* ``Post.title_it``\n* ``Post.body_en``\n* ``Post.body_fr``\n* ``Post.body_it``\n\nThese fields are virtuals. They don't exist in ``Post`` table. There are\nwrappers around ``linguist.Translation`` model. All translations will be stored\nin this table.\n\nWhen you set/get ``post.title``, Linguist will use the current active language\nand will set/get the correct field for this language. For example, if your\ndefault language is English (``en``), then ``Post.title`` will refer to ``post.title_en``.\n\nThe ``ModelMixin`` enhance your model with the following properties and methods:\n\n``instance.linguist_identifier`` (*read-only* property)\n    Your model identifier defined in the related translation class.\n    Shortcut pointing on ``instance._linguist.identifier``.\n\n``instance.default_language`` (*read-write* property)\n    The default language to use.\n    Shortcut pointing on ``instance._linguist.default_language``.\n\n``instance.translatable_fields`` (*read-only* property)\n    Translatable fields defined in the related translation class.\n    Shorcut pointing on ``instance._linguist.fields``.\n\n``instance.available_languages`` (*read-only* property)\n    Available languages for this instance (content translated in these languages).\n\n``instance.cached_translations_count`` (*read-only* property)\n    Returns the number of cached translations. Each time you set a new language\n    and set content on translatable fields, a cache is created for each language\n    and field. It will be used to create ``Translation`` objets at instance saving.\n\n``instance.active_language()``\n    Set the current active language for the instance.\n\n``instance.clear_translations_cache()``\n    Remove all cached translations. Be aware, any content you set will be dropped.\n    So no translation will be created/updated at saving.\n\n.. code-block:: python\n\n    # Let's create a new Post\n    \u003e\u003e\u003e post = Post()\n\n    # Set English content\n    \u003e\u003e\u003e post.activate_language('en')\n    \u003e\u003e\u003e post.title = 'Hello'\n\n    # Now set French content\n    \u003e\u003e\u003e post.activate_language('fr')\n    \u003e\u003e\u003e post.title = 'Bonjour'\n\n    # Be sure everything works as expected for English\n    \u003e\u003e\u003e post.activate_language('en')\n    \u003e\u003e\u003e post.title\n    Hello\n\n    # And now for French\n    \u003e\u003e\u003e post.activate_language('fr')\n    \u003e\u003e\u003e post.title\n    Bonjour\n\n    # Sweet! Save translations!\n    \u003e\u003e\u003e post.save()\n\nPreloading\n----------\n\nTo improve performances, you can preload/prefetch translations.\n\nFor a queryset (your queryset must inherit from Linguist manager/queryset):\n\n.. code-block:: python\n\n    \u003e\u003e\u003e Post.objects.with_translations()\n\nFor a list of objects (all your objects must inherit from Linguist model):\n\n.. code-block:: python\n\n    \u003e\u003e\u003e from linguist.helpers import prefetch_translations\n    \u003e\u003e\u003e posts = list(Post.objects.all())\n    \u003e\u003e\u003e prefetch_translations(posts)\n\nFor an instance (it must inherit from Linguist model):\n\n.. code-block:: python\n\n    \u003e\u003e\u003e post = Post.objects.first()\n    \u003e\u003e\u003e post.prefetch_translations()\n\nAll translations will be cached in instances. Database won't be hit anymore.\n\nThis preloading system takes three parameters:\n\n* ``field_names``: list of translatable field names to filter on\n* ``languages``: list of languages to filter on\n* ``populate_missing``: boolean if you want to populate cache for missing translations (defaults to ``True``)\n* ``chunks_length``: chunk limit for SELECT IN ids for translations\n\nFor example, we only want to prefetch post titles in English without populating missing\ntranslations with an empty string:\n\n.. code-block:: python\n\n    \u003e\u003e\u003e Post.objects.with_translations(field_names=['title'], languages=['en'], populate_missing=False)\n\nIt works the same for:\n\n* QuerySet ``with_translations()``\n* Helper ``prefetch_translations()``\n* Instance method ``prefetch_translations()``\n\n**What does \"populating missing translations\" mean?**\n\nSimple. By default, when you prefetch translations, instances cache will be populated\nwith empty strings for all supported languages (see  ``settings``). For example, if\nyou have ``en``, ``fr`` and ``it`` as supported languages and only have English\ntranslations, if you try to access other languages, an empty string will be returned\nwithout any database hit:\n\n.. code-block:: python\n\n    \u003e\u003e\u003e Post.objects.with_translations()\n    \u003e\u003e\u003e post.title_fr # no database hit here because\n    ''\n\nNow, if you explicitly set ``populate_missing`` to ``False``, if a translation\nis not found, it will be fetched from database.\n\n.. code-block:: python\n\n    \u003e\u003e\u003e Post.objects.with_translations(populate_missing=False)\n    \u003e\u003e\u003e post.title_fr # database hit here\n    ''\n\nDevelopment\n-----------\n\n.. code-block:: bash\n\n    # Don't have pip?\n    $ sudo easy_install pip\n\n    # Don't already have virtualenv?\n    $ sudo pip install virtualenv\n\n    # Clone and install dependencies\n    $ git clone https://github.com/ulule/django-linguist.git\n    $ cd django-linguist\n    $ make devenv\n\n    # Enable virtual environment.\n    $ source .venv/bin/activate\n\n    # Launch tests\n    $ make test\n\n    # Launch example project\n    $ make serve\n\n.. _django-linguist: https://github.com/ulule/django-linguist\n.. _Django: http://djangoproject.com\n.. _django-parler: https://github.com/edoburu/django-parler\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fulule%2Fdjango-linguist","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fulule%2Fdjango-linguist","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fulule%2Fdjango-linguist/lists"}