Skip to content

Translator API

django_traduire.translator

Translate the django-modeltranslation fields of a model instance.

The public entry points are :func:translate_instance and :func:translate_queryset. Everything about request size, chunking and markup is handled by the backend layer; what happens here is deciding what to translate, and writing the result back without overflowing a column.

translate_instance(instance, source_language=None, target_languages=None, overwrite=False, fields=None)

Translate the modeltranslation fields of one instance.

Parameters:

Name Type Description Default
instance Model

A model instance registered with modeltranslation.

required
source_language str | None

Source language code. Defaults to TRADUIRE["SOURCE_LANGUAGE"] or LANGUAGE_CODE.

None
target_languages Sequence[str] | None

Target language codes. Defaults to TRADUIRE["TARGET_LANGUAGES"] or every other LANGUAGES entry.

None
overwrite bool

Overwrite translations that are already filled in.

False
fields Sequence[str] | None

Restrict the run to these field names.

None

Returns:

Name Type Description
dict dict[str, str]

{column_name: translated_text} for every column written.

Raises:

Type Description
TypeError

If instance is None.

BackendError

If a provider failed and FAIL_SILENTLY is False.

Source code in src/django_traduire/translator.py
def translate_instance(
    instance: Model,
    source_language: str | None = None,
    target_languages: Sequence[str] | None = None,
    overwrite: bool = False,
    fields: Sequence[str] | None = None,
) -> dict[str, str]:
    """Translate the modeltranslation fields of one instance.

    Args:
        instance: A model instance registered with modeltranslation.
        source_language: Source language code. Defaults to
            ``TRADUIRE["SOURCE_LANGUAGE"]`` or ``LANGUAGE_CODE``.
        target_languages: Target language codes. Defaults to
            ``TRADUIRE["TARGET_LANGUAGES"]`` or every other ``LANGUAGES`` entry.
        overwrite: Overwrite translations that are already filled in.
        fields: Restrict the run to these field names.

    Returns:
        dict: ``{column_name: translated_text}`` for every column written.

    Raises:
        TypeError: If ``instance`` is None.
        BackendError: If a provider failed and ``FAIL_SILENTLY`` is False.
    """
    if instance is None:
        raise TypeError("translate_instance() expects a model instance, got None")
    source = source_language or get_source_language()
    targets = list(target_languages) if target_languages else get_target_languages()
    names = list(fields) if fields else get_translation_fields(type(instance))
    if not names or not targets:
        return {}

    jobs = _collect(instance, names, source, targets, overwrite)
    if not jobs:
        return {}
    return _apply(instance, _run(jobs, source, bool(get_setting("FAIL_SILENTLY"))))

translate_queryset(queryset, source_language=None, target_languages=None, overwrite=False, fields=None, limit=MAX_INSTANCES_PER_RUN)

Translate every instance of a queryset.

Parameters:

Name Type Description Default
queryset QuerySet

The queryset to walk. It is read with .iterator().

required
source_language str | None

Source language code.

None
target_languages Sequence[str] | None

Target language codes.

None
overwrite bool

Overwrite translations that are already filled in.

False
fields Sequence[str] | None

Restrict the run to these field names.

None
limit int

Hard bound on the instances walked, defaults to :data:MAX_INSTANCES_PER_RUN.

MAX_INSTANCES_PER_RUN

Returns:

Name Type Description
int int

Number of instances that received at least one translation.

Raises:

Type Description
ValueError

If limit is not a positive integer.

Source code in src/django_traduire/translator.py
def translate_queryset(  # noqa: PLR0917
    queryset: QuerySet,
    source_language: str | None = None,
    target_languages: Sequence[str] | None = None,
    overwrite: bool = False,
    fields: Sequence[str] | None = None,
    limit: int = MAX_INSTANCES_PER_RUN,
) -> int:
    """Translate every instance of a queryset.

    Args:
        queryset: The queryset to walk. It is read with ``.iterator()``.
        source_language: Source language code.
        target_languages: Target language codes.
        overwrite: Overwrite translations that are already filled in.
        fields: Restrict the run to these field names.
        limit: Hard bound on the instances walked, defaults to
            :data:`MAX_INSTANCES_PER_RUN`.

    Returns:
        int: Number of instances that received at least one translation.

    Raises:
        ValueError: If ``limit`` is not a positive integer.
    """
    if queryset is None:
        raise TypeError("translate_queryset() expects a queryset, got None")
    if not isinstance(limit, int) or limit <= 0:
        raise ValueError(f"limit must be a positive integer, got {limit!r}")

    count = 0
    for index, instance in enumerate(queryset.iterator()):
        if index >= limit:
            logger.warning("Stopping after %s instances (limit reached)", limit)
            break
        if translate_instance(
            instance,
            source_language=source_language,
            target_languages=target_languages,
            overwrite=overwrite,
            fields=fields,
        ):
            count += 1
    return count

get_backend() cached

Instantiate the configured backend, once per process.

Raises:

Type Description
ConfigurationError

If TRADUIRE["BACKEND"] cannot be imported or instantiated with TRADUIRE["BACKEND_OPTIONS"].

Source code in src/django_traduire/translator.py
@lru_cache(maxsize=1)
def get_backend():
    """Instantiate the configured backend, once per process.

    Raises:
        ConfigurationError: If ``TRADUIRE["BACKEND"]`` cannot be imported or
            instantiated with ``TRADUIRE["BACKEND_OPTIONS"]``.
    """
    path = get_setting("BACKEND")
    module_path, _, class_name = str(path).rpartition(".")
    if not module_path or not class_name:
        raise ConfigurationError(f"TRADUIRE['BACKEND'] must be a dotted path, got {path!r}")
    try:
        module = import_module(module_path)
    except ImportError as error:
        raise ConfigurationError(
            f"Cannot import backend module {module_path!r}: {error}"
        ) from error
    backend_class = getattr(module, class_name, None)
    if backend_class is None:
        raise ConfigurationError(f"Module {module_path!r} has no attribute {class_name!r}")

    options = get_setting("BACKEND_OPTIONS")
    if not isinstance(options, dict):
        raise ConfigurationError(
            f"TRADUIRE['BACKEND_OPTIONS'] must be a dict, got {type(options).__name__}"
        )
    backend = backend_class(**options)
    backend.max_chars = get_max_chars(backend.max_chars)
    backend.translate_attributes = get_translate_attributes()
    return backend

get_translation_fields(model)

Return the field names modeltranslation registered for model.

Source code in src/django_traduire/translator.py
def get_translation_fields(model) -> list[str]:
    """Return the field names ``modeltranslation`` registered for ``model``."""
    # Deferred: modeltranslation reads Django settings at import time, so it
    # cannot be imported while this package is being imported.
    from modeltranslation.translator import NotRegistered
    from modeltranslation.translator import translator as mt_translator

    if model is None:
        raise TypeError("get_translation_fields() expects a model class")
    try:
        options = mt_translator.get_options_for_model(model)
    except NotRegistered:
        return []
    return list(getattr(options, "fields", []))