django-traduire
Documentation | PyPI | GitHub
Auto-translate django-modeltranslation fields.
Fill in content in one language, and django-traduire populates all the other language columns automatically — including long articles and rich text — whose headings, lists, tables and emphasis come back exactly where they were.
Install
pip install django-traduire # free Google backend, no API key needed
pip install django-traduire[deepl] # DeepL
pip install django-traduire[google] # Google Cloud Translation
pip install django-traduire[openai] # OpenAI / any compatible server
pip install django-traduire[all] # all backends
Quick start
# settings.py
INSTALLED_APPS = [
...
"modeltranslation",
"django_traduire",
]
LANGUAGES = [
("fr", "Francais"),
("de", "Deutsch"),
("en", "English"),
("it", "Italiano"),
]
TRADUIRE = {
"SOURCE_LANGUAGE": "fr",
}
That is the whole configuration: the default backend is the free Google endpoint, which needs no key and no billing account.
Translate in Python
from django_traduire import translate_instance
article = Article.objects.get(pk=1)
# article.title_fr = "Bonjour le monde"
translate_instance(article)
# article.title_de = "Hallo Welt"
# article.title_en = "Hello world"
# article.title_it = "Ciao mondo"
Long texts
Every provider caps the size of one request. A 20 000-character article sent as one string comes back truncated — or not at all.
django-traduire cuts it on the most natural boundary that fits (paragraph, then sentence, then word), translates the pieces, and glues them back exactly. Nothing to configure: the budget comes from the backend, and MAX_CHARS overrides it.
TRADUIRE = {
"MAX_CHARS": 2000, # smaller requests, for a provider that throttles
}
Rich text
A rich-text field is not plain text. Sent as-is, a provider strips the tags or translates them as words.
<!-- before -->
<h2>Le cacao</h2>
<p>Un secteur <strong>strategique</strong>, mais <em>fragile</em>.</p>
<!-- after, translated to English -->
<h2>Cocoa</h2>
<p>A <strong>strategic</strong> sector, but <em>fragile</em>.</p>
Headings (h1–h6), sections, lists, quotes, links and inline emphasis are preserved. <script>, <style>, <code> and <pre> are never sent to a provider. A sentence stays whole across a <strong>, so the translation reads as a sentence.
Tables of any size work. The document is never cut into slices — it is planned, and only the prose inside each block is sent. A table cell is its own request, so a 400-row table, a table nested in a table, or an entire article wrapped in one <div> all go through as many small requests as they need, instead of one the provider refuses.
<!-- a 400-row table: every cell translated, not one tag moved -->
<figure class="table"><table><tbody>
<tr><td>Cacao</td><td>450 kg/ha</td></tr>
<tr><td>Cashew</td><td>Production remains <em>stable</em> this year.</td></tr>
</tbody></table></figure>
The attributes a reader sees — alt, title, placeholder, aria-label — are translated too; href, src, class and id never are. Django and Jinja template syntax ({{ … }}, {% … %}) inside prose is left alone.
Nothing a provider returns is stored before it is checked: markup that does not nest, a tag the segment never carried, a damaged placeholder or an empty answer are all refused, and that segment falls back to translating its text nodes one by one — every tag preserved.
Rich text is detected automatically: from HTML_FIELDS, from the field class (CKEditor, TinyMCE, Quill…), then from the content itself. HTML_MODE forces the decision either way.
TRADUIRE = {
"HTML_MODE": "auto", # auto | always | never
"HTML_FIELDS": {"blog.Article": ["body"]}, # explicit is better than sniffed
"TRANSLATE_ATTRIBUTES": True, # alt, title, placeholder, aria-label
}
Management command
# Translate all registered models
python manage.py traduire
# One model, one field, overwriting what is there
python manage.py traduire blog.Article --fields body --overwrite
# Bound a run
python manage.py traduire blog.Article --limit 100
# Dry run
python manage.py traduire --dry-run
Admin integration
from django.contrib import admin
from modeltranslation.admin import TranslationAdmin
from django_traduire.admin import TraduireMixin
@admin.register(Article)
class ArticleAdmin(TraduireMixin, TranslationAdmin):
pass
This adds "Translate empty fields" and "Translate all fields (overwrite)" actions to the admin.
Auto-translate on save
TRADUIRE = {
...
"AUTO_TRANSLATE": True,
}
Every time a model is saved, empty translation columns are filled. Convenient for a small site; for long articles or many languages, call translate_instance() from a background task instead — a request that waits on a translation API is a request the user watches spin.
Backends
| Backend | Install | Key | Reads markup | Best for |
|---|---|---|---|---|
| Google (free) | pip install django-traduire |
none | via placeholders | getting started, side projects, wide coverage |
| DeepL | django-traduire[deepl] |
required | natively | European languages, highest quality |
| Google Cloud | django-traduire[google] |
required | natively | broadest coverage, contractual traffic |
| OpenAI | django-traduire[openai] |
required | natively | creative / contextual translations |
The free backend uses the public endpoint the Google Translate web page uses. It is not a contractual API: it can throttle by IP, so it ships with retries and rate_limit. For production traffic under an SLA, use one of the other three.
Writing a backend
from django_traduire.backends.base import BaseBackend
class MyBackend(BaseBackend):
max_chars = 5000 # characters one request accepts
max_texts = 20 # texts one request accepts
supports_html = False # True if the provider keeps markup
def translate_raw(self, texts, source, target, is_html=False):
return [my_api(text, source, target) for text in texts]
Chunking, request grouping and markup handling are inherited — one request in, one list out.
License
MIT - Paul Guindo / Altius Academy SNC.
Release files for django-traduire 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| django_traduire-0.3.0.tar.gz | 62.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| django_traduire-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 104.3 kB
Release files / django_traduire-0.3.0.tar.gz
| Download URL | django_traduire-0.3.0.tar.gz |
|---|---|
| Size | 62.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bf20d75927912d8c1b85e8a8910e02262c08bf5ab8a3c5bbcb46374a84ac5b35
|
|
BLAKE2b-256 checksum How to use checksums |
768cf90a9b43640962ed4b5bfeb11da5ac3f4890d822b1da70ce92999f190f62
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.
Transparency logRelease files / django_traduire-0.3.0-py3-none-any.whl
| Download URL | django_traduire-0.3.0-py3-none-any.whl |
|---|---|
| Size | 42.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
99518393e8387e6520072af08f762b18354cfeeddad0c8d8c405254fe9dacf93
|
|
BLAKE2b-256 checksum How to use checksums |
eaa9d3f4b4dd5a2dca68978d283641968b07752617621a5c014cdc1b83f526c6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 8, 2026.
Transparency log