Skip to main content

Small Django model translation helpers with admin integration.

Project description

dj-i18n-translate

PyPI version Python versions Django versions License

Small Django model translation helpers for Django 5.2+ and Django 6.x.

Use this package when your source model keeps the default language fields, and each translated language should live in a separate related row.

Contents

Install

pip install dj-i18n-translate

Optional integrations:

pip install "dj-i18n-translate[drf]"
pip install "dj-i18n-translate[ninja]"
pip install "dj-i18n-translate[google]"

Install combined extras when needed:

pip install "dj-i18n-translate[drf,ninja,google]"

Add the package to INSTALLED_APPS:

INSTALLED_APPS = [
    # ...
    "dj_i18n_translate",
    "catalog",
]

Basic setup

Start with a normal Django model. Keep the default language on the source model. Create the translation model at module scope so Django migrations can discover it.

# catalog/models.py
from django.db import models
from dj_i18n_translate.factory import create_translation_model
from dj_i18n_translate.models import TranslatableMixin


class Category(TranslatableMixin, models.Model):
    name = models.CharField(max_length=255)
    description = models.TextField(blank=True)

    def __str__(self):
        return self.name


CategoryTranslation = create_translation_model(
    Category,
    fields=["name", "description"],
)

Run migrations normally:

python manage.py makemigrations
python manage.py migrate

The generated model contains:

  • i18n_source: foreign key to the source model.
  • i18n_language: indexed language code.
  • one nullable copy of each translated field.
  • a unique constraint for i18n_source and i18n_language.

Only concrete non-relation fields can be translated.

Custom related name

By default, translations are available through category.translations. Override that when the name conflicts with your model.

CategoryTranslation = create_translation_model(
    Category,
    fields=["name", "description"],
    related_name="localized",
)

Usage:

category.localized.create(
    i18n_language="vi",
    name="Chu de",
    description="Noi dung tieng Viet",
)

Custom table name

By default, Django generates the translation table name from the app label and translation model name. Override it with db_table when you need a fixed table name.

CategoryTranslation = create_translation_model(
    Category,
    fields=["name", "description"],
    db_table="catalog_category_i18n",
)

Custom manager

TranslatableMixin already provides a manager with with_translations(). If your model needs a custom manager, build it from TranslatableQuerySet.

from django.db import models
from dj_i18n_translate.models import TranslatableMixin, TranslatableQuerySet


class CategoryManager(models.Manager.from_queryset(TranslatableQuerySet)):
    def published(self):
        return self.filter(is_published=True)


class Category(TranslatableMixin, models.Model):
    objects = CategoryManager()
    name = models.CharField(max_length=255)
    is_published = models.BooleanField(default=True)

Usage:

categories = Category.objects.published().with_translations("vi")

Decorator shortcut

Use the decorator only when you do not need a named CategoryTranslation variable in the module.

from django.db import models
from dj_i18n_translate.decorators import translatable
from dj_i18n_translate.models import TranslatableMixin


@translatable(fields=["title"])
class Article(TranslatableMixin, models.Model):
    title = models.CharField(max_length=255)

Create and update translations

Create translations through the generated model:

category = Category.objects.create(
    name="Keyboard themes",
    description="Visual resources for keyboard apps.",
)

CategoryTranslation.objects.create(
    i18n_source=category,
    i18n_language="vi",
    name="Chu de ban phim",
    description="Tai nguyen hinh anh cho ung dung ban phim.",
)

For imports or sync jobs, use update_or_create():

CategoryTranslation.objects.update_or_create(
    i18n_source=category,
    i18n_language="fr",
    defaults={
        "name": "Themes clavier",
        "description": "Ressources visuelles pour les apps clavier.",
    },
)

Read translations

Single object

category.get_translated_field("name", "vi")
category.get_translated_field("description", "vi")

If no translation exists, get_translated_field() returns the source field value by default.

category.get_translated_field("name", "fr")  # falls back to category.name
category.get_translated_field("name", "fr", fallback=False)  # returns None

Use translate() when you need the translation row itself:

translation = category.translate("vi")
if translation:
    print(translation.name)

Lists and views

Prefetch translations before rendering lists.

# catalog/views.py
from django.shortcuts import render
from .models import Category


def category_list(request):
    language = request.GET.get("lang", "en")
    categories = Category.objects.with_translations(language).order_by("id")
    rows = [
        {
            "name": category.get_translated_field("name", language),
            "description": category.get_translated_field("description", language),
        }
        for category in categories
    ]

    return render(
        request,
        "catalog/category_list.html",
        {"rows": rows, "language": language},
    )

Template usage:

{% for row in rows %}
  <h2>{{ row.name }}</h2>
  <p>{{ row.description }}</p>
{% endfor %}

If the language is resolved later, prefetch all translations:

categories = Category.objects.with_translations().order_by("id")

Django admin

Manual translation inline

Use TranslatableAdminMixin when editors should type translations manually. This does not call any machine translator.

Setup:

# catalog/admin.py
from django.contrib import admin
from dj_i18n_translate.admin import TranslatableAdminMixin
from .models import Category


@admin.register(Category)
class CategoryAdmin(TranslatableAdminMixin, admin.ModelAdmin):
    list_display = ["id", "name"]

Usage:

  1. Open a Category in Django admin.
  2. Add one inline row per target language.
  3. Save the source object.

Inline layout options

@admin.register(Category)
class CategoryAdmin(TranslatableAdminMixin, admin.ModelAdmin):
    translation_inline_stacked = True
    translation_inline_collapse = False

Automatic translation

Use AutoTranslateAdminMixin when selected admin rows should be translated by a configured translator.

Custom translator setup

Create a translator class:

# catalog/translators.py
from dj_i18n_translate.translators import BaseTranslator


class ExampleTranslator(BaseTranslator):
    def translate(self, text, source_language, target_language):
        return f"{text} ({target_language})"

    def get_supported_languages(self):
        return ["en", "vi", "fr"]

Configure it:

# settings.py
DJ_I18N_TRANSLATE_DEFAULT_LANGUAGE = "en"
DJ_I18N_TRANSLATE_SUPPORTED_LANGUAGES = ["en", "vi", "fr"]
DJ_I18N_TRANSLATE_TRANSLATOR = "catalog.translators.ExampleTranslator"

Register the admin:

from django.contrib import admin
from dj_i18n_translate.admin import AutoTranslateAdminMixin
from .models import Category


@admin.register(Category)
class CategoryAdmin(AutoTranslateAdminMixin, admin.ModelAdmin):
    list_display = ["id", "name"]

Usage:

  1. Select rows in the changelist.
  2. Run the Translate selected objects action.
  3. Translation rows are created or updated for configured target languages.

DJ_I18N_TRANSLATE_DEFAULT_LANGUAGE is skipped as a target language.

Batch translator

Implement translate_batch() for fewer provider calls.

class ExampleBatchTranslator(BaseTranslator):
    def translate_batch(self, texts, source_language, target_language):
        return [f"{text} ({target_language})" for text in texts]

    def get_supported_languages(self):
        return ["en", "vi", "fr"]

BaseTranslator bridges translate() and translate_batch(), so a translator can implement either one.

Google Translate setup

Install the extra:

pip install "dj-i18n-translate[google]"

Configure the built-in adapter:

DJ_I18N_TRANSLATE_TRANSLATOR = "dj_i18n_translate.translators.GoogleTranslator"
DJ_I18N_TRANSLATE_GOOGLE_PROJECT_ID = "my-gcp-project"
DJ_I18N_TRANSLATE_GOOGLE_LOCATION = "global"
DJ_I18N_TRANSLATE_GOOGLE_CREDENTIALS_FILE = "/path/to/service-account.json"

If DJ_I18N_TRANSLATE_GOOGLE_CREDENTIALS_FILE is not set, Google Application Default Credentials are used.

DRF integration

Install the extra:

pip install "dj-i18n-translate[drf]"

Add DRF to INSTALLED_APPS if your project does not already have it:

INSTALLED_APPS = [
    # ...
    "rest_framework",
    "dj_i18n_translate",
]

Serializer setup

# catalog/api.py
from rest_framework import serializers
from dj_i18n_translate.drf import TranslatableSerializerMixin
from .models import Category


class CategorySerializer(TranslatableSerializerMixin, serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ["id", "name", "description"]

List API usage

from rest_framework.generics import ListAPIView


class CategoryListAPIView(ListAPIView):
    serializer_class = CategorySerializer

    def get_queryset(self):
        return Category.objects.with_translations().order_by("id")

Request translated output:

GET /api/categories?lang=vi
Accept-Language: vi

The query parameter wins over the header.

ViewSet usage

from rest_framework import viewsets
from dj_i18n_translate.drf import TranslatableViewSetMixin


class CategoryViewSet(
    TranslatableViewSetMixin,
    viewsets.ReadOnlyModelViewSet,
):
    queryset = Category.objects.order_by("id")
    serializer_class = CategorySerializer

Django Ninja integration

Install the extra:

pip install "dj-i18n-translate[ninja]"

Schema setup

# catalog/api.py
from ninja import NinjaAPI
from dj_i18n_translate.ninja import create_translatable_schema
from dj_i18n_translate.ninja import prefetch_translations
from .models import Category


api = NinjaAPI()

CategorySchema = create_translatable_schema(
    Category,
    name="CategorySchema",
    fields=["id", "name", "description"],
)

Endpoint usage

@api.get("/categories", response=list[CategorySchema])
def categories(request):
    queryset = Category.objects.order_by("id")
    return prefetch_translations(queryset)

Request translated output:

GET /api/categories?lang=vi
Accept-Language: vi

create_translatable_schema() keeps the normal Ninja model schema behavior and adds resolvers only for selected translated fields.

Settings

All settings are optional.

DJ_I18N_TRANSLATE_DEFAULT_LANGUAGE = "en"
DJ_I18N_TRANSLATE_SUPPORTED_LANGUAGES = ["en", "vi", "fr"]
DJ_I18N_TRANSLATE_RELATED_NAME = "translations"
DJ_I18N_TRANSLATE_TRANSLATOR = "catalog.translators.ExampleTranslator"
DJ_I18N_TRANSLATE_LANGUAGE_QUERY_PARAM = "lang"
DJ_I18N_TRANSLATE_LANGUAGE_HEADER = "Accept-Language"
DJ_I18N_TRANSLATE_LANGUAGE_ALIASES = {"in": "id"}
DJ_I18N_TRANSLATE_GOOGLE_PROJECT_ID = "my-gcp-project"
DJ_I18N_TRANSLATE_GOOGLE_LOCATION = "global"
DJ_I18N_TRANSLATE_GOOGLE_CREDENTIALS_FILE = "/path/to/service-account.json"
Setting Default Used for
DEFAULT_LANGUAGE "en" Source language and admin auto-translate skip target.
SUPPORTED_LANGUAGES [] Target languages for auto-translate.
RELATED_NAME "translations" Default source-to-translation related name.
TRANSLATOR None Import path for automatic translation.
LANGUAGE_QUERY_PARAM "lang" Request query parameter checked first.
LANGUAGE_HEADER "Accept-Language" Request header checked after the query parameter.
LANGUAGE_ALIASES {"in": "id"} Target language code aliases for translator providers.
GOOGLE_PROJECT_ID None Google Cloud project for Google Translate.
GOOGLE_LOCATION "global" Google Translate location.
GOOGLE_CREDENTIALS_FILE None Optional Google service-account file.

Example project

Runnable examples and snapshots live in example/README.md.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dj_i18n_translate-0.2.1.tar.gz (18.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dj_i18n_translate-0.2.1-py3-none-any.whl (15.2 kB view details)

Uploaded Python 3

File details

Details for the file dj_i18n_translate-0.2.1.tar.gz.

File metadata

  • Download URL: dj_i18n_translate-0.2.1.tar.gz
  • Upload date:
  • Size: 18.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for dj_i18n_translate-0.2.1.tar.gz
Algorithm Hash digest
SHA256 60bac0c31174fbec421c282911cc0bc5787ecc721fd613048b40d6887f7539e2
MD5 4b410d57415649b9e2198aba32cb4bb4
BLAKE2b-256 c96de79474076a89c41461850221a3ef79418827cbff668d428cfa9a10ca1514

See more details on using hashes here.

Provenance

The following attestation bundles were made for dj_i18n_translate-0.2.1.tar.gz:

Publisher: release.yml on himkit/dj-i18n-translate

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dj_i18n_translate-0.2.1-py3-none-any.whl.

File metadata

File hashes

Hashes for dj_i18n_translate-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 ba8f53670b7de9a7e6a5e2d1d47bdc0cd298c6fa5bf284f1d3295f74d225cf6e
MD5 22cf8619886986b772cf5da7f816097d
BLAKE2b-256 20c747d11c385c5536eb97bfe8bb127e62e2a1f5637a14a7f9b0923326c1d9f8

See more details on using hashes here.

Provenance

The following attestation bundles were made for dj_i18n_translate-0.2.1-py3-none-any.whl:

Publisher: release.yml on himkit/dj-i18n-translate

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page