Aller au contenu

ADR-027 : Extraction de l'i18n vers forge-mvc-i18n

Statut

Acceptée

Amende ADR-004 (périmètre du core minimal) et prolonge ADR-021 et ADR-022 : poursuite du dégraissage du core vers des opt-ins.


Contexte

core/i18n/ (translator par catalogues JSON, locale par défaut et fallback, cache, exceptions) échoue au test de légitimité d'ADR-004 : « si je retire cette brique du core, une application Forge basique tourne-t-elle encore ? » → oui.
La traduction est un besoin fréquent mais n'est pas une primitive générale du framework, et core/i18n/ est autonome (sa seule dépendance core est core.forge pour les clés de configuration i18n_default_locale / i18n_fallback_locale, disponible via forge-mvc).

Le module relève donc d'un opt-in, comme stats, workflow, pivot ou mail.

Particularité, le couplage au générateur CRUD.
Contrairement à mail ou pivot, trans() est tissé dans le générateur CRUD du cœur : forge make:crud et les pages publiques émettent des templates qui appellent {{ trans(...) }} (crud.edit, common.save, crud.confirm_delete…).
Aujourd'hui le renderer Jinja du cœur expose toujours trans comme global.
Une extraction « en dur » casserait au rendu tout CRUD généré tant que forge-mvc-i18n n'est pas installé.


Décision

Le translator runtime core/i18n/ est extrait vers l'opt-in forge-mvc-i18n, et le cœur conserve un repli trans no-op.

Avant Après
core/i18n/<module>.py forge_mvc_i18n.<module> (API réexportée par forge_mvc_i18n)
from core.i18n import … from forge_mvc_i18n import …
renderer : trans = vraie fonction (toujours) renderer : trans = repli no-op ; l'opt-in l'enrichit
  • Repli no-op du noyau.
    Le renderer Jinja (integrations/jinja2/renderer.py) expose toujours un global trans (_default_trans) qui retourne la clé telle quelle.
    Les templates générés rendent donc sans erreur, traduction ou non.
    Quand forge-mvc-i18n est installé, le renderer remplace ce repli par la vraie fonction trans() chargeant les catalogues.
    C'est exactement le pattern déjà utilisé pour can() (RBAC fournit un défaut lambda: False, l'opt-in l'enrichit), deux lignes plus haut dans le même renderer.
  • Clés de configuration conservées dans le noyau.
    i18n_default_locale et i18n_fallback_locale restent dans le registre _DEFAULTS de core.forge, comme les clés mail_* après ADR-022 : le registre de configuration reste centralisé, seul le code migre.
  • CLI de scaffolding conservée dans le noyau.
    forge i18n:init et forge i18n:check (cli/assets/i18n.py) sont autonomes : ils créent et vérifient translations/fr.json sans importer le translator runtime.
    Ils relèvent de l'outillage de projet du CLI cœur et restent disponibles sans installer forge-mvc-i18n.
    Seul le translator runtime est extrait.

Pré-1.0 (convention bêta) : extraction sans alias déprécié dans le core.
Les anciens chemins core.i18n sont supprimés.


Conséquences

  • Nouveau paquet packages/forge-mvc-i18n/ (distribution PyPI séparée, dépend de forge-mvc).
  • Garde-fou d'extraction I18N-EXTRACT-001 : le core ne contient plus i18n, le paquet expose l'API publique, le renderer garde un repli no-op.
  • Tests i18n protégés par pytest.importorskip("forge_mvc_i18n") (core autonome, garde-fou TESTS-OPTIN-IMPORTORSKIP-001).
  • forge_mvc_i18n ajouté aux listes opt-in transverses (importorskip, core-only-contract, classifiers PyPI, sweep imports docs) + release-policy.md.
  • DX : un CRUD généré sans forge-mvc-i18n affiche les clés brutes (crud.edit) au lieu du libellé traduit, dégradé mais lisible, jamais cassé.

Suite

  • Publication PyPI de forge-mvc-i18n (alignée sur la version du core) lors de la prochaine release des opt-ins.