Internationalisation (django-parler)

Le framework intègre django-parler pour la traduction des modèles. Les champs traduisibles sont stockés dans des tables *Translation séparées, ce qui permet un support multi-langue sans modifier le schéma principal.

Configuration

Module : updo.settings

INSTALLED_APPS = [
    ...
    'parler',
    ...
]

LANGUAGES = [
    ('en', 'English'),
    ('fr', 'Français'),
    ('ar', 'العربية'),
]

PARLER_LANGUAGES = {
    None: (
        {'code': 'en'},
        {'code': 'fr'},
        {'code': 'ar'},
    ),
    'default': {
        'fallbacks': ['en'],
        'hide_untranslated': False,
    },
}
  • fallbacks : si une traduction n’existe pas dans la langue active, django-parler utilise l’anglais comme fallback.

  • hide_untranslated : False signifie que les objets sans traduction dans la langue courante restent visibles (avec le contenu fallback).

Modèles de base traduisibles

Module : src.module.tenant.infrastructure.base_models

class src.module.tenant.infrastructure.base_models.TranslatableSharedModel(TranslatableModel)

Modèle abstrait pour les entités traduisibles dans le schéma public. Combine TranslatableModel de django-parler avec TenantMeta.tenant_type = "shared" pour le routage du database router.

Utilisé par les données de référence (Country, Currency, etc.) et les modèles globaux (Role, Department du Tenant Context, Policy).

from parler.models import TranslatedFields
from src.module.tenant.infrastructure.base_models import TranslatableSharedModel

class Country(TranslatableSharedModel, ERPBaseModel):
    code = models.CharField(max_length=2, unique=True)
    translations = TranslatedFields(
        name=models.CharField(max_length=100),
    )
class src.module.tenant.infrastructure.base_models.TranslatableTenantSpecificModel(TranslatableModel)

Modèle abstrait pour les entités traduisibles dans le schéma tenant. Combine TranslatableModel avec TenantMeta.tenant_type = "tenant_specific" et le même garde-fou save() que TenantSpecificModel (auto-population de tenant_id, vérification du contexte tenant).

from parler.models import TranslatedFields
from src.module.tenant.infrastructure.base_models import TranslatableTenantSpecificModel

class Product(TranslatableTenantSpecificModel, ERPBaseModel):
    sku = models.CharField(max_length=50, unique=True)
    translations = TranslatedFields(
        name=models.CharField(max_length=255),
        description=models.TextField(blank=True),
    )

Modèles traduits — Données de référence

Ces modèles héritent de TranslatableSharedModel et stockent leurs champs traduisibles dans des tables *Translation distinctes.

Modèle

Champs traduits

Table de traduction

Country

name

ref_countries_translation

Currency

name

ref_currencies_translation

UnitOfMeasureCategory

name

ref_uom_categories_translation

UnitOfMeasure

name

ref_units_of_measure_translation

Department (ref)

name

ref_departments_translation

Modèles traduits — Tenant Context

Modèle

Champs traduits

Table de traduction

Role

display_name, description

mt_tenant_role_translation

Department (tenant)

display_name, description

mt_department_translation

Note

Role et Department conservent un champ name non traduit comme identifiant programmatique (utilisé dans les JSON de rôles, les conditions ABAC, etc.). Le champ display_name est la version traduisible destinée à l’affichage.

Modèles traduits — Authorization Context

Modèle

Champs traduits

Table de traduction

Policy

display_name, description

mt_policy_translation

AttributeDefinition

display_name, description

mt_attribute_definition_translation

Note

Policy.name et AttributeDefinition.name restent des champs non traduits. Ils servent d’identifiant technique (logs, évaluation ABAC). Les champs display_name et description sont traduits pour l’UI.

Utilisation dans le code

Créer un objet avec traduction

from src.share_kernel.models import Country

country = Country(code="FR", code_alpha3="FRA")
country.set_current_language("en")
country.name = "France"
country.set_current_language("fr")
country.name = "France"
country.set_current_language("ar")
country.name = "فرنسا"
country.save()

Ou avec create_translation() :

country = Country.objects.create(code="MA", code_alpha3="MAR")
country.create_translation("en", name="Morocco")
country.create_translation("fr", name="Maroc")
country.create_translation("ar", name="المغرب")

Lire une traduction

from django.utils.translation import activate

activate("fr")
country = Country.objects.language("fr").get(code="MA")
print(country.name)  # "Maroc"

# Avec fallback sécurisé
name = country.safe_translation_getter("name", any_language=True)

Requêtes filtrées par langue

# Toutes les devises dont le nom contient "dollar" en anglais
currencies = Currency.objects.language("en").filter(
    translations__name__icontains="dollar",
)

# Toutes les devises avec traductions pré-chargées
currencies = Currency.objects.prefetch_related("translations").all()

Sérialisation DRF

Pour exposer les traductions via l’API REST, django-parler fournit TranslatedModelSerializer :

from parler_rest.serializers import TranslatableModelSerializer, TranslatedFieldsField

class CountrySerializer(TranslatableModelSerializer):
    translations = TranslatedFieldsField(shared_model=Country)

    class Meta:
        model = Country
        fields = ["id", "code", "code_alpha3", "translations", "is_active"]

Cela produit un JSON de la forme :

{
  "id": "...",
  "code": "MA",
  "translations": {
    "en": {"name": "Morocco"},
    "fr": {"name": "Maroc"},
    "ar": {"name": "المغرب"}
  }
}

Architecture des tables

Chaque modèle traduisible crée une table *Translation qui contient :

  • id : clé primaire auto-incrémentée

  • language_code : code de langue (en, fr, ar)

  • master_id : FK vers le modèle parent

  • Les champs traduits (name, display_name, description, etc.)

  • Contrainte unique : (language_code, master_id)

ref_countries               ref_countries_translation
┌──────────┐                ┌──────────────────────┐
│ id (PK)  │◄──────────────│ master_id (FK)       │
│ code     │                │ language_code        │
│ ...      │                │ name                 │
└──────────┘                └──────────────────────┘

Cette architecture permet d’ajouter de nouvelles langues sans modifier la table principale.