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_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.
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.0.tar.gz (17.1 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.0-py3-none-any.whl (15.1 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: dj_i18n_translate-0.2.0.tar.gz
  • Upload date:
  • Size: 17.1 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.0.tar.gz
Algorithm Hash digest
SHA256 1420493e7ac1e893220d50456965b67e1691fb1a739c34c5ef07a2c5e940f979
MD5 ea48c455a3c76d3f13ca4c9e1bb0be28
BLAKE2b-256 8e3f942174cb6c80f3ef34ddcbe36c5d1c8573851b7baaa43bc59ce04e93ffdd

See more details on using hashes here.

Provenance

The following attestation bundles were made for dj_i18n_translate-0.2.0.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.0-py3-none-any.whl.

File metadata

File hashes

Hashes for dj_i18n_translate-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 769d4da6c77cb2bbe3093c2c20467cfc39bf2f41216c5af6fa66a630454cb31e
MD5 fa520931ab53b838810a6c2971e5a50d
BLAKE2b-256 ee674405492d83e4db92d3ad4a0d75bb34e83308380ed77bfd3208d4bc04b27e

See more details on using hashes here.

Provenance

The following attestation bundles were made for dj_i18n_translate-0.2.0-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