Small Django model translation helpers with admin integration.
Project description
dj-i18n-translate
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
- Basic setup
- Create and update translations
- Read translations
- Django admin
- Automatic translation
- DRF integration
- Django Ninja integration
- Settings
- Example project
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_sourceandi18n_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:
- Open a
Categoryin Django admin. - Add one inline row per target language.
- 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:
- Select rows in the changelist.
- Run the
Translate selected objectsaction. - 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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1420493e7ac1e893220d50456965b67e1691fb1a739c34c5ef07a2c5e940f979
|
|
| MD5 |
ea48c455a3c76d3f13ca4c9e1bb0be28
|
|
| BLAKE2b-256 |
8e3f942174cb6c80f3ef34ddcbe36c5d1c8573851b7baaa43bc59ce04e93ffdd
|
Provenance
The following attestation bundles were made for dj_i18n_translate-0.2.0.tar.gz:
Publisher:
release.yml on himkit/dj-i18n-translate
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dj_i18n_translate-0.2.0.tar.gz -
Subject digest:
1420493e7ac1e893220d50456965b67e1691fb1a739c34c5ef07a2c5e940f979 - Sigstore transparency entry: 2135620566
- Sigstore integration time:
-
Permalink:
himkit/dj-i18n-translate@83cedc94f786692be256fb3efb74d973d23a3ae0 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/himkit
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@83cedc94f786692be256fb3efb74d973d23a3ae0 -
Trigger Event:
release
-
Statement type:
File details
Details for the file dj_i18n_translate-0.2.0-py3-none-any.whl.
File metadata
- Download URL: dj_i18n_translate-0.2.0-py3-none-any.whl
- Upload date:
- Size: 15.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
769d4da6c77cb2bbe3093c2c20467cfc39bf2f41216c5af6fa66a630454cb31e
|
|
| MD5 |
fa520931ab53b838810a6c2971e5a50d
|
|
| BLAKE2b-256 |
ee674405492d83e4db92d3ad4a0d75bb34e83308380ed77bfd3208d4bc04b27e
|
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
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dj_i18n_translate-0.2.0-py3-none-any.whl -
Subject digest:
769d4da6c77cb2bbe3093c2c20467cfc39bf2f41216c5af6fa66a630454cb31e - Sigstore transparency entry: 2135620607
- Sigstore integration time:
-
Permalink:
himkit/dj-i18n-translate@83cedc94f786692be256fb3efb74d973d23a3ae0 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/himkit
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@83cedc94f786692be256fb3efb74d973d23a3ae0 -
Trigger Event:
release
-
Statement type: