Metadata-Version: 2.5
Name: django-traduire
Version: 0.2.0
Summary: Auto-translate django-modeltranslation fields — long texts and rich text included — with a free Google backend, DeepL, Google Cloud or OpenAI
Project-URL: Changelog, https://github.com/Altius-Academy-SNC/django-traduire/blob/main/CHANGELOG.md
Project-URL: Homepage, https://github.com/Altius-Academy-SNC/django-traduire
Project-URL: Documentation, https://altius-academy-snc.github.io/django-traduire
Project-URL: Repository, https://github.com/Altius-Academy-SNC/django-traduire
Author-email: Paul Guindo <paulguindo@altius-group.ch>
License-Expression: MIT
License-File: LICENSE
Keywords: deepl,django,google-translate,html,i18n,modeltranslation,multilingual,rich-text,translation
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: Django
Classifier: Framework :: Django :: 4.2
Classifier: Framework :: Django :: 5.0
Classifier: Framework :: Django :: 5.1
Classifier: Framework :: Django :: 6.0
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Internationalization
Requires-Python: >=3.10
Requires-Dist: django-modeltranslation>=0.18
Requires-Dist: django>=4.2
Provides-Extra: all
Requires-Dist: deepl>=1.16; extra == 'all'
Requires-Dist: google-cloud-translate>=3.12; extra == 'all'
Requires-Dist: openai>=1.0; extra == 'all'
Provides-Extra: deepl
Requires-Dist: deepl>=1.16; extra == 'deepl'
Provides-Extra: dev
Requires-Dist: deepl>=1.16; extra == 'dev'
Requires-Dist: google-cloud-translate>=3.12; extra == 'dev'
Requires-Dist: mypy; extra == 'dev'
Requires-Dist: openai>=1.0; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest-django; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Provides-Extra: docs
Requires-Dist: mkdocs-material; extra == 'docs'
Requires-Dist: mkdocstrings[python]; extra == 'docs'
Provides-Extra: google
Requires-Dist: google-cloud-translate>=3.12; extra == 'google'
Provides-Extra: openai
Requires-Dist: openai>=1.0; extra == 'openai'
Description-Content-Type: text/markdown

# django-traduire

**[Documentation](https://altius-academy-snc.github.io/django-traduire/)** | **[PyPI](https://pypi.org/project/django-traduire/)** | **[GitHub](https://github.com/Altius-Academy-SNC/django-traduire)**

Auto-translate [django-modeltranslation](https://github.com/deschler/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 and emphasis come back exactly where they were.

## Install

```bash
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

```python
# 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

```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.

```python
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.

```html
<!-- 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, tables, 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.

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.

```python
TRADUIRE = {
    "HTML_MODE": "auto",                          # auto | always | never
    "HTML_FIELDS": {"blog.Article": ["body"]},    # explicit is better than sniffed
}
```

## Management command

```bash
# 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

```python
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

```python
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

```python
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.
