Changelog¶
All notable changes to django-traduire are listed here. The format follows Keep a Changelog, and the project follows Semantic Versioning.
[0.3.0] - 2026-09-08¶
The release about rich text that actually survives the trip.
v0.2 translated rich text by cutting the document on top-level tag
boundaries. A document built around a single element — a table, a
<figure>, a <div> wrapper, anything an editor emits — has exactly one
top-level element, so the whole thing was handed to the provider as one
oversized request. Providers answer that with a truncation, an error, or
scrambled rows. Tables were the common case, and they were the case that broke.
v0.3 stops slicing documents. It plans them.
Changed¶
- Rich text is now translated segment by segment, never sliced. The
document is parsed once; structural markup is set aside and only the prose
inside each block — a heading, a list item, a table cell — is sent. Every
request therefore fits the provider's budget by construction, whatever the
document looks like. A 100-row table, a table nested in a table, a 30-level
<div>stack and a 20 000-character article all go through as many small requests as they need. supports_htmlbackends changed shape. DeepL, Google Cloud and OpenAI now receive one block of prose at a time with its inline markup left in (Un secteur <strong>vital</strong>.) instead of whole document slices. Structural tags never reach a provider at all.- Provider answers are verified before they are stored. Markup that does
not nest, or a tag the segment never carried, is refused and the segment
falls back to translating its text nodes one by one — every tag preserved.
A provider that invents
<script>can no longer get it into a field the template renders as safe. split_html()cuts deeper when it has to. An element larger than the budget is opened up and cut inside instead of being returned whole. It is no longer used by the translation pipeline and is kept as a public utility.
Added¶
- Attributes a reader sees are translated:
alt,title,placeholder,aria-label,aria-description,<option label>,<th abbr>,<table summary>. Only the value is rewritten — quoting style, attribute order and every other attribute are left exactly as the editor wrote them.href,src,classandidare never touched. NewTRANSLATE_ATTRIBUTESsetting, on by default. - Django and Jinja template syntax is protected.
{{ user.name }},{% if x %}and{# … #}sitting inside prose are set aside like markup instead of being translated into nonsense. django_traduire.htmltokens— the tokenizer, extracted into its own module. It round-trips a document byte for byte: tags, attributes, comments, doctypes and processing instructions come back exactly as they were written. and­survive. v0.2 decoded them to raw code points, which reads the same in a browser but confuses the next person to open the editor.- More tags understood:
<figure>,<figcaption>,<details>,<summary>,<dialog>,<colgroup>,<optgroup>. More tags left alone:<template>,<iframe>,<canvas>,<object>,<noscript>. - Placeholders are recognised even when the provider rewrites the brackets —
Google returns full-width
[[0]]for CJK targets, and most providers add spaces.
Fixed¶
- A table, or any document wrapped in one element, is no longer sent as a single oversized request. This is the bug this release exists for.
- An empty answer from a provider no longer wipes the text it was meant to translate; the original is kept.
- A segment with more inline tags than any provider will return in order now falls back to text-node translation instead of being silently skipped.
looks_like_html()no longer mistakesa < bfor markup, and now recognises every HTML5 element rather than a short hand-picked list.- A request is never cut through a tag.
chunk_text()cuts on word boundaries, and a tag carries spaces of its own —<a href="/x" title="a b">could be split in two.
[0.2.0] - 2026-08-20¶
The release about long texts and rich text.
Added¶
- Automatic chunking of long fields (
django_traduire.chunking). Every provider caps the size of a request; a 20 000-character article used to come back truncated, or not at all. Text is now cut on the most natural boundary that fits — paragraph, then sentence, then word — translated piece by piece, and glued back exactly:"".join(chunks) == original. - HTML-aware translation (
django_traduire.richtext). Rich text keeps its structure:<h1>…<h6>,<p>,<ul>/<li>,<table>,<blockquote>, and the inline formatting inside them (<strong>,<em>,<b>,<i>,<a href>,<br>). Two strategies, picked automatically: - providers that read markup (DeepL, Google Cloud, OpenAI) receive HTML fragments cut on top-level tag boundaries;
- providers that do not (the free Google endpoint) receive the sentence with
its inline tags replaced by placeholders, restored afterwards. A sentence
stays whole across a
<strong>, instead of being translated in fragments. <script>,<style>,<code>,<pre>,<kbd>,<samp>,<var>,<svg>and<math>are never sent to a provider.GoogleFreeBackend— Google Translate with no API key, no billing account and no extra dependency (django_traduire.backends.google_free). It is now the default backend, sopip install django-traduiretranslates out of the box. It talks to the public endpoint the Google Translate web page uses: not a contractual API, rate-limited by IP, withretriesandrate_limitoptions to stay polite. For contractual traffic, keep DeepL, Google Cloud or OpenAI.- Column overflow is handled instead of crashing. A translation longer than
the column's
max_length— very common when translating aCharField(200)title into German — is truncated on a word boundary and logged. Configurable withON_TOO_LONG("truncate","skip","error"). - New settings:
HTML_MODE,HTML_FIELDS,MAX_CHARS,ON_TOO_LONG,FAIL_SILENTLY. - New arguments:
fields=ontranslate_instance(),fields=andlimit=ontranslate_queryset(),--fieldsand--limitonmanage.py traduire. - Named exceptions:
TraduireError,ConfigurationError,BackendError. Settings are validated when they are read, with a message that says what to fix.
Changed¶
- Backend API: a backend now implements
translate_raw(texts, source, target, is_html=False)— one request to the provider — instead oftranslate_batch(). Chunking, grouping and markup handling live inBaseBackendand are inherited for free.translate_batch()remains the public entry point for callers. Custom backends must rename their method. - Default backend is now
GoogleFreeBackend(wasDeepLBackend, which could never work without anauth_key). AUTO_TRANSLATEis connected from the app config instead of at import time, and a provider failure can no longer break asave().- Importing
django_traduireno longer importsmodeltranslation, so the package can be imported before Django settings are configured. - The package is checked with
ruff,ruff format,mypyandpytestwith warnings promoted to errors — all four with zero output.
Fixed¶
- Long articles silently coming back empty or truncated.
- Rich text losing its tags, or having them translated as words.
translate_instance()failing the whole save when one translation overflowed its column.- A translation identical to the source was dropped instead of being stored, so the field was translated again on every run.
[0.1.0] - 2026¶
- First release: DeepL, Google Cloud and OpenAI backends,
translate_instance,translate_queryset,manage.py traduire, admin actions,AUTO_TRANSLATE.