{
  "docs": {
    "gettingStarted": {
      "hero": {
        "badge": "Premiers pas",
        "title": "Documentation",
        "subtitle": "Tout ce dont vous avez besoin pour créer des applications web réactives avec No.JS"
      },
      "introduction": {
        "title": "Introduction",
        "text": "No.JS est un framework réactif HTML-first. Construisez des applications web dynamiques et orientées données en utilisant uniquement des attributs HTML — pas de build, pas de DOM virtuel, pas de JSX.",
        "callout": "Zéro dépendance · Fonctionne dans tous les navigateurs modernes · Aucune étape de build requise"
      },
      "installation": {
        "title": "Installation",
        "cdnSubtitle": "CDN (recommandé)",
        "selfHostedSubtitle": "Auto-hébergé",
        "selfHostedText": "Téléchargez dist/iife/no.js et incluez-le avec une balise <script>. C'est un fichier unique, sans dépendances.",
        "npmSubtitle": "npm / ESM",
        "npmText": "Avec npm, vous devez appeler NoJS.init() manuellement une fois le DOM prêt. Le script CDN le fait automatiquement."
      },
      "quickStart": {
        "title": "Démarrage rapide",
        "text": "Créez un fichier index.html : incluez le script, ajoutez quelques attributs, et c'est tout. Pas de app.mount(), pas de createApp(), pas de NgModule. Ça fonctionne, tout simplement."
      },
      "howItWorks": {
        "title": "Comment ça marche",
        "text": "Au DOMContentLoaded, No.JS parcourt le DOM à la recherche d'éléments possédant des attributs reconnus. Chaque attribut correspond à une directive exécutée par priorité.",
        "card1Title": "1. Analyse",
        "card1Desc": "Parcourt le DOM à la recherche d'éléments possédant des attributs reconnus.",
        "card2Title": "2. Résolution",
        "card2Desc": "Chaque attribut correspond à une directive exécutée par priorité (récupération de données d'abord, puis conditions, puis rendu).",
        "card3Title": "3. Réaction",
        "card3Desc": "Toutes les données résident dans des contextes réactifs (basés sur Proxy). Quand les données changent, chaque élément lié se met à jour automatiquement.",
        "card4Title": "4. Portée",
        "card4Desc": "Les contextes héritent des éléments parents, comme la portée lexicale. Un bind à l'intérieur d'une boucle each peut accéder à la fois à l'élément de la boucle et aux données des ancêtres."
      },
      "coreConcepts": {
        "title": "Concepts clés",
        "reactiveContextSubtitle": "Contexte réactif",
        "reactiveContextText": "Chaque élément peut avoir un contexte — un objet de données réactif. Les contextes sont créés par state, get, store, etc. Les éléments enfants héritent automatiquement du contexte de leur parent.",
        "directivePrioritySubtitle": "Priorité des directives",
        "tableCol1": "Priorité",
        "tableCol2": "Directives",
        "tableCol3": "Description",
        "tableRow1": "Initialiser l'état local/global",
        "tableRow4": "Structurel (ajouter/supprimer du DOM)",
        "tableRow5": "Rendu (mettre à jour le DOM existant)",
        "tableRow7": "Effets de bord",
        "expressionSubtitle": "Syntaxe des expressions",
        "expressionText": "La plupart des valeurs de directives acceptent des expressions JavaScript évaluées par rapport au contexte courant :",
        "tableRow1b": "Récupérer des données, error boundaries, namespace i18n",
        "tableRow2b": "Valeurs dérivées et watchers d'effets secondaires",
        "tableRow5b": "Références aux éléments",
        "tableRow15": "Configuration du drag and drop (Maintenant dans NoJS Elements)"
      }
    },
    "cheatsheet": {
      "hero": {
        "badge": "Référence API",
        "title": "Aide-mémoire des directives",
        "subtitle": "Référence complète de chaque directive No.JS"
      },
      "data": {
        "title": "Données",
        "col1": "Directive",
        "col2": "Exemple",
        "col3": "Description",
        "base": "Définir l'URL de base de l'API pour les descendants",
        "get": "Récupérer des données (GET)",
        "post": "Soumettre des données (POST)",
        "put": "Mettre à jour des données (PUT)",
        "patch": "Mise à jour partielle (PATCH)",
        "delete": "Supprimer des données (DELETE)",
        "as": "Nom des données récupérées dans le contexte",
        "body": "Corps de la requête",
        "headers": "En-têtes de la requête",
        "params": "Paramètres de requête",
        "cached": "Mettre en cache les réponses (memory/local/session)",
        "into": "Écrire la réponse dans un store global nommé",
        "debounce": "Debounce des refetchs d'URL réactives (ms)",
        "retry": "Nombre de tentatives par élément",
        "refresh": "Intervalle de polling en ms",
        "success": "ID du template à afficher en cas de succès",
        "then": "Expression à exécuter en cas de succès",
        "redirect": "Chemin de navigation après succès",
        "confirm": "Message de confirmation avant la requête",
        "skeleton": "Afficher/masquer un placeholder pendant le chargement",
        "retryDelay": "Délai entre les tentatives en ms"
      },
      "state": {
        "title": "État",
        "col1": "Directive",
        "col2": "Exemple",
        "col3": "Description",
        "state": "Créer un état réactif local",
        "store": "Définir/accéder à un store global",
        "computed": "Valeur dérivée réactive",
        "watch": "Réagir aux changements de valeur",
        "persist": "Attribut de la directive state — persiste l'état dans le stockage",
        "model": "Liaison bidirectionnelle pour les champs de saisie",
        "persistKey": "Clé de stockage pour la persistance",
        "persistFields": "Champs séparés par virgule à persister",
        "persistSchema": "Valider les clés restaurées par rapport à l'état initial"
      },
      "rendering": {
        "title": "Rendu",
        "col1": "Directive",
        "col2": "Exemple",
        "col3": "Description",
        "bind": "Définir le contenu texte",
        "bindHtml": "Définir le innerHTML (nettoyé)",
        "bindStar": "Lier n'importe quel attribut",
        "if": "Rendu conditionnel",
        "elseIf": "Condition chaînée",
        "then": "Template pour le cas vrai",
        "else": "Template pour le cas faux",
        "show": "Basculer la visibilité (CSS)",
        "hide": "Inverse de show",
        "switch": "Rendu switch/case",
        "case": "Correspondance de cas",
        "default": "Cas par défaut"
      },
      "loops": {
        "title": "Boucles",
        "col1": "Directive",
        "col2": "Exemple",
        "col3": "Description",
        "foreach": "Directive de boucle primaire",
        "each": "Alias de foreach",
        "for": "Alias de foreach",
        "from": "Tableau source (DEPRECATED — utilisez la syntaxe \"item in array\")",
        "template": "Template à cloner",
        "index": "Nom de la variable d'index",
        "key": "Clé unique pour le diffing",
        "filter": "Expression de filtrage",
        "sort": "Propriété de tri (nom seul, sans préfixe de variable d'élément)",
        "limit": "Nombre maximum d'éléments",
        "offset": "Éléments à ignorer"
      },
      "events": {
        "title": "Événements",
        "col1": "Directive",
        "col2": "Exemple",
        "col3": "Description",
        "onClick": "Gestionnaire de clic",
        "onSubmit": "Gestionnaire de soumission",
        "onInput": "Gestionnaire de saisie",
        "onKeydown": "Gestionnaire de touche",
        "onInit": "Se déclenche immédiatement lors de l'initialisation",
        "onMounted": "Cycle de vie : monté",
        "onUnmounted": "Cycle de vie : démonté",
        "throttle": "Limiter l'exécution du gestionnaire (ms)",
        "self": "Se déclenche uniquement si la cible de l'événement est l'élément lui-même",
        "backspace": "Modificateur de touche pour la touche Retour arrière",
        "onUpdated": "Lifecycle : mutation du DOM observée",
        "onError": "Lifecycle : erreur dans le sous-arbre"
      },
      "styling": {
        "title": "Styles",
        "col1": "Directive",
        "col2": "Exemple",
        "col3": "Description",
        "classStar": "Basculer une classe CSS",
        "classMap": "Classe depuis un objet",
        "styleStar": "Définir un style inline",
        "styleMap": "Style depuis un objet"
      },
      "forms": {
        "title": "Formulaires",
        "col1": "Directive",
        "col2": "Exemple",
        "col3": "Description",
        "validate": "Activer la validation du formulaire/champ (Maintenant dans NoJS Elements)",
        "error": "Template d'erreur pour le champ",
        "success": "Template de succès",
        "loading": "Template de chargement",
        "confirm": "Boîte de dialogue de confirmation",
        "redirect": "Redirection en cas de succès"
      },
      "routing": {
        "title": "Routage",
        "col1": "Directive",
        "col2": "Exemple",
        "col3": "Description",
        "route": "Définir une route ou un lien",
        "routeView": "Point de sortie de la route",
        "routeViewNamed": "Point de sortie de route nommé",
        "outlet": "Cibler un outlet nommé",
        "routeActive": "Classe de lien actif",
        "guard": "Condition de garde de route",
        "routeActiveExact": "Classe active en correspondance exacte pour les liens de route",
        "redirect": "Chemin de redirection en cas d'échec de la garde",
        "lazyPriority": "Charger le template distant avant tous les autres (Phase 0)",
        "lazyOnDemand": "Charger le template de route uniquement lors de la première visite (templates de route uniquement)",
        "routerForward": "Naviguer en avant dans l'historique",
        "routerOn": "S'abonner aux changements de route",
        "routeWildcard": "Route wildcard catch-all 404",
        "routerCurrent": "Objet de la route courante",
        "routeMatched": "<code>true</code> si une route explicite a correspondu, <code>false</code> pour wildcard/fallback",
        "i18nNs": "Dérivation automatique du namespace i18n à partir du nom de fichier de route",
        "routeViewSrc": "Point de sortie de routage basé sur les fichiers",
        "routeIndex": "Nom de fichier pour la racine <code>/</code> (défaut <code>\"index\"</code>)",
        "routeExt": "Extension de fichier (défaut <code>\".tpl\"</code>, repli <code>\".html\"</code>)",
        "transitionVT": "Preset View Transition API sur route-view (slide, fade, scale, none)"
      },
      "animation": {
        "title": "Animation",
        "col1": "Directive",
        "col2": "Exemple",
        "col3": "Description",
        "animate": "Animation d'entrée",
        "animateEnter": "Animation d'entrée",
        "animateLeave": "Animation de sortie",
        "animateDuration": "Durée en ms",
        "animateStagger": "Délai d'échelonnement",
        "transition": "Transition CSS (basée sur les classes, pour les éléments réguliers)",
        "transitionVT": "Preset View Transition API sur route-view (slide, fade, scale, none)"
      },
      "dnd": {
        "title": "Drag and Drop (Maintenant dans NoJS Elements)",
        "col1": "Directive",
        "col2": "Exemple",
        "col3": "Description",
        "drag": "Rendre un élément déplaçable",
        "dragType": "Identifiant de type de données",
        "dragEffect": "Effet autorisé (move/copy/link/all)",
        "dragHandle": "Restreindre le glissement au sélecteur de poignée",
        "dragDisabled": "Désactiver le glissement conditionnellement",
        "dragClass": "Classe ajoutée pendant le glissement",
        "dragGroup": "Limiter le glissement à un groupe nommé",
        "drop": "Définir une zone de dépôt",
        "dropAccept": "Type(s) de glissement accepté(s)",
        "dropEffect": "Effet de retour visuel",
        "dropClass": "Classe ajoutée au survol",
        "dropDisabled": "Désactiver le dépôt conditionnellement",
        "dropMax": "Nombre maximum d'éléments dans la zone",
        "dropSort": "Activer le tri positionnel",
        "dropPlaceholder": "Template placeholder pendant le survol",
        "dropSettleClass": "Classe CSS personnalisée pour l'animation de stabilisation",
        "dropEmptyClass": "Classe CSS personnalisée pour l'état vide sur drag-list",
        "dragList": "Liste triable liée à un tableau d'état",
        "dragListKey": "Clé unique pour chaque élément",
        "dragListItem": "Sélecteur de template d'élément",
        "dragListCopy": "Copier au lieu de déplacer lors du transfert",
        "dragListRemove": "Supprimer les éléments de la source lors du transfert",
        "dragMultiple": "Lasso / sélection multiple sur les enfants",
        "dragMultipleClass": "Classe ajoutée aux éléments sélectionnés",
        "dropRejectClass": "Classe ajoutée lors du rejet du survol"
      },
      "i18n": {
        "title": "i18n",
        "col1": "Directive",
        "col2": "Exemple",
        "col3": "Description",
        "t": "Traduire une clé",
        "tStar": "Paramètre de traduction",
        "tHtml": "Rendre la traduction en HTML sanitisé"
      },
      "misc": {
        "title": "Divers",
        "col1": "Directive",
        "col2": "Exemple",
        "col3": "Description",
        "ref": "Référence d'élément nommée",
        "call": "Déclencher un appel API",
        "trigger": "Émettre un événement personnalisé",
        "use": "Instancier un template",
        "src": "Template distant (voir aussi : lazy)",
        "loading": "Placeholder affiché pendant le chargement du template distant ; supprimé à l'arrivée",
        "include": "Cloner de manière synchrone un template inline à la position courante",
        "errorBoundary": "Limite d'erreur",
        "var": "Nom de variable du template"
      },
      "headManagement": {
        "title": "Head Management",
        "col1": "Directive",
        "col2": "Exemple",
        "col3": "Description",
        "pageTitle": "Définir document.title de manière réactive",
        "pageDescription": "Définir <meta name=\"description\">",
        "pageCanonical": "Définir <link rel=\"canonical\">",
        "pageJsonld": "Définir <script type=\"application/ld+json\">"
      }
    },
    "stateManagement": {
      "hero": {
        "badge": "Guides",
        "title": "Gestion de l'état",
        "subtitle": "État local, stores globaux, propriétés calculées et observateurs"
      },
      "state": {
        "title": "state — État local",
        "text": "Crée un contexte réactif limité à l'élément et à ses enfants.",
        "preview": "Aperçu",
        "helloLabel": "Bonjour,",
        "countLabel": "Compteur :",
        "resetBtn": "Réinitialiser"
      },
      "store": {
        "title": "store — Store global",
        "text": "Un store réactif global accessible depuis n'importe où. Idéal pour l'état d'authentification, le thème, les données partagées."
      },
      "configStores": {
        "title": "Pré-initialisation des Stores via config()",
        "text": "Vous pouvez pré-créer des stores avec des données initiales dans NoJS.config(). Chaque clé de l'objet stores devient un store global nommé, accessible immédiatement via $store.nom.",
        "callout": "Si un store existe déjà (ex. créé via un attribut store), config() ne l'écrasera pas — le store existant est préservé."
      },
      "into": {
        "title": "into — Écrire les résultats de Fetch dans un Store",
        "text": "L'attribut into sur n'importe quelle directive HTTP écrit la réponse directement dans un store global nommé.",
        "callout": "Le store n'a pas besoin d'être prédéfini — into le créera s'il n'existe pas."
      },
      "computed": {
        "title": "computed — État dérivé",
        "text": "Des valeurs automatiquement recalculées lorsque les dépendances changent.",
        "preview": "Aperçu",
        "priceLabel": "Prix :",
        "qtyLabel": "Qté :",
        "subtotalLabel": "Sous-total :",
        "totalLabel": "Total :"
      },
      "watch": {
        "title": "watch — Effets de bord",
        "text": "Exécute une action à chaque fois qu'une valeur change."
      },
      "persistence": {
        "title": "Persistance de l'état",
        "text": "Persistez l'état entre les rechargements de page en utilisant localStorage ou sessionStorage."
      },
      "notify": {
        "title": "NoJS.notify() — Mettre à jour le Store",
        "text": "Lorsque du JavaScript externe modifie un store via NoJS.store, appelez NoJS.notify() pour mettre à jour tous les bindings du DOM.",
        "callout": "Nécessaire uniquement lors de la modification de NoJS.store depuis du JavaScript pur — en dehors des expressions HTML. Les modifications dans on:click ou bind sont gérées automatiquement."
      },
      "persistSchema": {
        "title": "persist-schema — Validation de Schéma",
        "text": "Lors de la restauration de l'état persisté, persist-schema valide que les clés restaurées correspondent à la forme de l'état initial. Les clés absentes de l'état initial sont écartées. Cela empêche les données obsolètes ou corrompues de casser votre app après des changements de schéma."
      },
      "scoping": {
        "title": "Portée du Contexte & Shadowing",
        "text": "Les contextes héritent des éléments parents comme la portée lexicale. Un state enfant peut masquer une propriété du parent — l'enfant voit sa propre valeur, tandis que les frères voient celle du parent.",
        "callout": "Le shadowing est intentionnel et suit les règles de portée de JavaScript. Si vous avez besoin d'un état partagé entre frères, utilisez un store."
      }
    },
    "dataBinding": {
      "hero": {
        "badge": "Guides",
        "title": "Liaison de données",
        "subtitle": "Liaison de données unidirectionnelle et bidirectionnelle avec bind et model"
      },
      "bind": {
        "title": "bind — Contenu texte",
        "text": "Remplace le textContent de l'élément par l'expression évaluée."
      },
      "bindHtml": {
        "title": "bind-html — HTML interne",
        "text": "Affiche l'expression évaluée en HTML. Nettoyé par défaut.",
        "callout": "⚠️ Utilise un nettoyage structurel intégré basé sur DOMParser : supprime les balises <script>, bloque les gestionnaires d'événements on*, et retire les URI javascript:."
      },
      "bindAttr": {
        "title": "bind-* — Liaison d'attributs",
        "text": "Liez dynamiquement n'importe quel attribut HTML."
      },
      "model": {
        "title": "model — Liaison bidirectionnelle",
        "text": "Pour les champs de formulaire, model crée automatiquement une liaison de données bidirectionnelle.",
        "preview": "Aperçu",
        "nameLabel": "Nom",
        "placeholder": "Tapez votre nom...",
        "checkbox": "J'accepte",
        "agreedLabel": "Accepté :",
        "helloPrefix": "Bonjour,"
      },
      "radioSelect": {
        "title": "Radio Buttons & Multi-Select",
        "text": "model fonctionne avec tous les types d'input. Pour les radio buttons, liez le même nom de model à un groupe — la valeur se met à jour vers le value du radio sélectionné. Pour le multi-select, la valeur du model devient un array des options sélectionnées."
      },
      "commonMistakes": {
        "title": "Erreurs Courantes",
        "text": "Évitez ces pièges courants du data binding :",
        "mistake1": "Utiliser bind-html sans comprendre la sanitisation — tout contenu est sanitisé par défaut, mais soyez conscient de ce qui est supprimé.",
        "mistake2": "Oublier que bind remplace textContent — tous les éléments enfants dans une cible bind sont supprimés.",
        "mistake3": "Utiliser model sur des éléments non-formulaire — model ne fonctionne que sur input, textarea et select."
      }
    },
    "events": {
      "hero": {
        "badge": "Guides",
        "title": "Gestion des événements",
        "subtitle": "Liez les événements DOM directement en HTML avec la syntaxe on:event"
      },
      "handlers": {
        "title": "on:* — Gestionnaires d'événements",
        "text": "Liez n'importe quel événement DOM directement en HTML. Accédez à l'état et aux variables de contexte directement dans l'expression du gestionnaire.",
        "preview": "Aperçu",
        "inputPlaceholder": "Tapez quelque chose...",
        "youTyped": "Vous avez tapé :",
        "countLabel": "Compteur :"
      },
      "modifiers": {
        "title": "Modificateurs d'événements",
        "text": "Les modificateurs vous permettent de contrôler le comportement des événements directement dans l'attribut :"
      },
      "eventAndEl": {
        "title": "$event & $el",
        "text": "$event est l'événement DOM natif. $el fait référence à l'élément courant."
      },
      "lifecycle": {
        "title": "Hooks de cycle de vie",
        "col1": "Hook",
        "col2": "Quand",
        "onInit": "Directive traitée pour la première fois",
        "onMounted": "Élément inséré dans le DOM visible",
        "onUpdated": "Mutation du DOM observée (via MutationObserver)",
        "onUnmounted": "Élément retiré du DOM",
        "onError": "Erreur dans le sous-arbre de cet élément"
      },
      "keyModifiers": {
        "title": "Modificateurs de Touche",
        "text": "Utilisez les modificateurs de touche sur les événements clavier pour filtrer par touches spécifiques. Combinez avec Ctrl, Alt, Shift ou Meta pour les raccourcis.",
        "col1": "Modificateur",
        "col2": "Touche",
        "enter": "Entrée / Return",
        "escape": "Échap",
        "space": "Espace",
        "tab": "Tab",
        "backspace": "Retour arrière",
        "arrowUp": "Flèche Haut",
        "arrowDown": "Flèche Bas",
        "arrowLeft": "Flèche Gauche",
        "arrowRight": "Flèche Droite",
        "ctrl": "Ctrl (Contrôle)",
        "alt": "Alt (Option sur Mac)",
        "shift": "Shift (Maj)",
        "meta": "Meta (Cmd sur Mac, Win sur Windows)",
        "delete": "Supprimer"
      }
    },
    "conditionals": {
      "hero": {
        "badge": "Guides",
        "title": "Conditions",
        "subtitle": "Contrôlez le rendu avec if, show, hide et switch"
      },
      "ifThenElse": {
        "title": "if / then / else",
        "text": "Affichez des éléments ou des templates de manière conditionnelle selon des expressions.",
        "preview": "Aperçu",
        "checkbox": "Connecté",
        "welcome": "✅ Bon retour !",
        "login": "Veuillez vous connecter."
      },
      "elseIf": {
        "title": "else-if — Conditions chaînées"
      },
      "showHide": {
        "title": "show / hide",
        "text": "Bascule display: none sans ajouter/supprimer d'éléments DOM. Préférable pour les éléments fréquemment basculés.",
        "comparisonTitle": "if vs show",
        "colIf": "if",
        "colShow": "show",
        "mechanism": "Mécanisme",
        "mechanismIf": "Ajoute/supprime des éléments DOM",
        "mechanismShow": "Bascule l'affichage CSS",
        "bestFor": "Idéal pour",
        "bestForIf": "Contenu rarement basculé",
        "bestForShow": "Contenu fréquemment basculé",
        "preservesState": "Préserve l'état",
        "preservesIf": "Non (recrée)",
        "preservesShow": "Oui"
      },
      "switchCase": {
        "title": "switch / case",
        "text": "Affichez l'un parmi plusieurs templates selon une valeur.",
        "inlineSubtitle": "Contenu inline (sans templates)",
        "multiValueSubtitle": "Case à valeurs multiples"
      }
    },
    "loops": {
      "hero": {
        "badge": "Guides",
        "title": "Directives d'itération",
        "subtitle": "Itérez sur des tableaux avec foreach, each et for"
      },
      "foreach": {
        "title": "foreach — Itérer sur des tableaux",
        "text": "La directive d'itération principale. Utilisez foreach=\"item in array\" pour itérer sur des tableaux. each et for sont des alias avec un comportement identique.",
        "col1": "Attribut",
        "col2": "Description",
        "foreach": "Expression : item in array",
        "from": "Tableau source (DEPRECATED — utilisez la syntaxe item in array)",
        "index": "Nom de la variable d'index (par défaut : $index)",
        "key": "Expression de clé unique pour l'identification et le suivi des éléments",
        "else": "ID du template à afficher quand le tableau est vide",
        "filter": "Expression pour filtrer les éléments",
        "sort": "Chemin de propriété pour le tri (préfixe - pour l'ordre décroissant)",
        "limit": "Nombre maximum d'éléments à afficher",
        "offset": "Nombre d'éléments à ignorer",
        "preview": "Aperçu",
        "template": "ID de template externe (optionnel — les enfants deviennent le template si omis)"
      },
      "fullExample": {
        "title": "Exemple complet avec tous les attributs",
        "text": "Un exemple complet montrant foreach avec filtrage, tri, pagination et un template de secours pour l'état vide."
      },
      "aliases": {
        "title": "Alias : each et for",
        "text": "each et for sont des alias de foreach — ils partagent le même handler et supportent tous les mêmes attributs."
      },
      "inline": {
        "title": "Template enfants inline",
        "text": "Quand aucun attribut template n'est spécifié, les enfants de l'élément deviennent le template répété."
      },
      "deprecated": {
        "title": "Déprécié : attribut from",
        "text": "L'attribut from fonctionne encore mais est déprécié. Utilisez la syntaxe \"item in array\" à la place. L'utilisation de from émettra un avertissement dans la console."
      },
      "contextVars": {
        "title": "Variables de contexte de boucle",
        "col1": "Variable",
        "col2": "Description",
        "index": "Index courant (base 0)",
        "count": "Nombre total d'éléments",
        "first": "true si premier élément",
        "last": "true si dernier élément",
        "even": "true si l'index est pair",
        "odd": "true si l'index est impair"
      },
      "nested": {
        "title": "Boucles imbriquées",
        "text": "Les boucles enfants (foreach, each ou for) peuvent accéder aux variables de la portée parente."
      },
      "reactivity": {
        "title": "Réactivité",
        "text": "Les directives de boucle sont entièrement réactives. Quand le tableau source change (éléments ajoutés, supprimés ou réordonnés), le DOM se met à jour automatiquement. Utilisez l'attribut key pour un diffing efficace — sans lui, la liste entière est re-rendue à chaque changement."
      },
      "objectIteration": {
        "title": "Itération d'Objets",
        "text": "Pour itérer sur les entrées d'un objet, utilisez le filtre keys ou values pour le convertir en tableau d'abord.",
        "callout": "L'itération directe d'objets n'est pas supportée — convertissez toujours en tableau avec keys, values ou Object.entries() dans une expression computed."
      }
    },
    "templates": {
      "hero": {
        "badge": "Guides",
        "title": "Templates",
        "subtitle": "Fragments HTML réutilisables avec variables, slots et chargement distant"
      },
      "basic": {
        "title": "Template de base",
        "text": "Les templates sont des fragments HTML réutilisables qui ne sont jamais rendus directement. Ils sont clonés lorsqu'ils sont référencés par des directives comme then, else, template, loading, error, etc."
      },
      "var": {
        "title": "Variables de template (var)",
        "text": "Les templates peuvent déclarer quelle variable ils attendent du contexte appelant."
      },
      "slots": {
        "title": "Slots de template",
        "text": "Permettent aux templates d'accepter du contenu projeté."
      },
      "remote": {
        "title": "Templates distants (src)",
        "text": "Chargez des templates depuis des fichiers HTML externes."
      },
      "recursive": {
        "subtitle": "Chargement récursif",
        "text": "Les templates distants sont chargés de manière récursive — si un template distant contient lui-même des éléments <template src=\"...\">, ceux-ci sont automatiquement résolus aussi :"
      },
      "remoteRoutes": {
        "subtitle": "Templates distants dans les routes",
        "text": "Les templates distants à l'intérieur du contenu des routes sont également résolus automatiquement avant que la route ne s'affiche. Voir Routage pour les détails."
      },
      "lazy": {
        "title": "Chargement différé (lazy)",
        "text": "Contrôlez quand les templates distants sont récupérés en utilisant l'attribut lazy sur les éléments <template src=\"...\">. NoJS charge les templates par phases pour optimiser le premier rendu.",
        "col1": "Valeur",
        "col2": "Comportement",
        "absent": "(absent)",
        "absentDesc": "Priorisation automatique par défaut : les templates d'inclusion de contenu et le template de la route courante se chargent avant le premier rendu ; les autres templates de route sont préchargés en arrière-plan après le premier rendu.",
        "priorityDesc": "Forcer le chargement avant tout le reste — même avant les inclusions de contenu classiques. Utile pour les templates de mise en page critiques et partagés.",
        "ondemandDesc": "Valable uniquement sur les templates de route. Jamais préchargé — récupéré uniquement lors de la première navigation de l'utilisateur vers cette route. Idéal pour les pages lourdes ou rarement visitées."
      },
      "phases": {
        "subtitle": "Phases de chargement",
        "text": "Les templates sont résolus en quatre phases ordonnées : la Phase 0 récupère d'abord les templates lazy=\"priority\" ; la Phase 1 récupère tous les autres templates non-route plus le template de la route active (bloquant avant le premier rendu) ; la Phase 2 précharge les templates de route restants en arrière-plan après le premier rendu ; et le chargement à la demande récupère les templates de route lazy=\"ondemand\" uniquement lorsque l'utilisateur navigue pour la première fois vers ceux-ci."
      },
      "loading": {
        "title": "Placeholder de chargement (loading)",
        "text1": "Affichez un template de placeholder pendant qu'un template distant est en cours de chargement. Le placeholder est inséré de manière synchrone — avant toute requête réseau — et supprimé automatiquement une fois le contenu réel arrivé. Fonctionne pour les inclusions de contenu statiques et les templates imbriqués dans les pages de route.",
        "text2": "Les ID simples et la syntaxe #id sont acceptés. Le template de placeholder est cloné à chaque fois, il peut donc être réutilisé pour plusieurs templates distants :"
      },
      "include": {
        "title": "Inclusion de template inline (include)",
        "text1": "Clone un template inline à la position courante de manière synchrone, avant tout fetch. Utile pour injecter du balisage réutilisable (par ex. jeux d'icônes, fragments communs) sans effectuer de requête réseau.",
        "text2": "include et loading ont des objectifs différents : include clone du contenu inline de manière permanente ; loading insère un placeholder temporaire qui disparaît une fois le chargement du template distant terminé."
      }
    },
    "dataFetching": {
      "hero": {
        "badge": "Guides",
        "title": "Récupération de données",
        "subtitle": "Requêtes HTTP déclaratives — ajoutez simplement des attributs aux éléments HTML"
      },
      "baseUrl": {
        "title": "URL de base",
        "text1": "Définie une fois sur n'importe quel élément ancêtre. Tous les descendants get, post, etc. résolvent les URL relatives par rapport à celle-ci.",
        "text2": "Surcharge pour des sections spécifiques :",
        "text3": "Les URL absolues ignorent la résolution de base :"
      },
      "config": {
        "title": "Configuration programmatique"
      },
      "headers": {
        "title": "En-têtes par requête"
      },
      "get": {
        "title": "get — Récupérer et afficher des données",
        "attributesTitle": "Attributs",
        "col1": "Attribut",
        "col2": "Type",
        "col3": "Description",
        "get": "URL à récupérer (requête GET)",
        "as": "Nom à attribuer à la réponse dans le contexte. Par défaut : \"data\"",
        "loading": "ID du template à afficher pendant le chargement (ex. \"#skeleton\")",
        "error": "ID du template à afficher en cas d'erreur de fetch",
        "empty": "ID du template à afficher quand la réponse est un tableau vide/null",
        "refresh": "Intervalle de rafraîchissement automatique en ms (polling)",
        "cached": "Mettre en cache les réponses. cached = memory, cached=\"local\" = localStorage, cached=\"session\" = sessionStorage",
        "into": "Écrire la réponse dans un store global nommé",
        "debounce": "Debounce en ms (utile avec les URL réactives)",
        "headers": "Chaîne JSON d'en-têtes supplémentaires",
        "params": "Expression qui résout en objet de paramètres de requête"
      },
      "fullExample": {
        "title": "Exemple complet"
      },
      "reactiveUrls": {
        "title": "URL réactives",
        "text": "Les URL qui référencent des variables d'état se re-fetchent automatiquement lorsque ces valeurs changent."
      },
      "mutations": {
        "title": "post, put, patch, delete — Requêtes de mutation",
        "text": "Utilisées sur des formulaires ou déclenchées via call.",
        "formSubmissionTitle": "Soumission de formulaire",
        "putPatchDeleteTitle": "PUT / PATCH / DELETE"
      },
      "mutationAttrs": {
        "title": "Attributs de mutation",
        "col1": "Attribut",
        "col2": "Description",
        "method": "URL de la requête",
        "body": "Corps de la requête (chaîne JSON avec interpolation). Pour les formulaires, sérialise automatiquement les champs",
        "success": "ID du template à afficher en cas de succès. Reçoit la réponse comme var",
        "error": "ID du template à afficher en cas d'erreur. Reçoit l'erreur comme var",
        "loading": "ID du template à afficher pendant la requête",
        "confirm": "Afficher une boîte de dialogue confirm() du navigateur avant l'envoi",
        "redirect": "URL vers laquelle naviguer en cas de succès (route SPA)",
        "then": "Expression à exécuter en cas de succès (ex. \"users.push(result)\")",
        "into": "Écrire la réponse dans un store global nommé",
        "cached": "Mettre en cache les réponses (memory/local/session). Note : la mise en cache s'applique uniquement aux requêtes GET."
      },
      "lifecycle": {
        "title": "Cycle de vie de la requête"
      },
      "liveDemo": {
        "title": "Démo en direct — Fetch API",
        "label": "Résultat"
      }
    },
    "routing": {
      "hero": {
        "badge": "Guides",
        "title": "Routage",
        "subtitle": "Navigation SPA côté client complète sans rechargement de page"
      },
      "definition": {
        "title": "Définition des routes"
      },
      "params": {
        "title": "Paramètres de route & requête"
      },
      "context": {
        "title": "$route — Contexte de route",
        "col1": "Propriété",
        "col2": "Description",
        "path": "Chemin courant (ex. \"/users/42\")",
        "params": "Paramètres de route (ex. { id: \"42\" })",
        "query": "Paramètres de chaîne de requête (ex. { q: \"hello\" })",
        "hash": "Hash de l'URL (ex. \"#section\")",
        "matched": "Si une route explicite a correspondu (true) ou si un wildcard/fallback est en cours de rendu (false)"
      },
      "activeStyle": {
        "title": "Style de route active"
      },
      "guards": {
        "title": "Gardes de route"
      },
      "programmatic": {
        "title": "Navigation programmatique",
        "callout": "$router.push() et $router.replace() retournent des Promises — la navigation (y compris le chargement de templates distants) est entièrement asynchrone. Dans les gestionnaires on:click la valeur de retour est ignorée, mais dans les scripts vous pouvez les attendre avec await :"
      },
      "nested": {
        "title": "Routes imbriquées"
      },
      "remoteTemplates": {
        "title": "Templates distants dans les routes",
        "text1": "Les templates de route peuvent inclure <template src=\"...\"> pour charger du contenu depuis des fichiers externes. Ils sont automatiquement résolus avant que la route ne s'affiche :",
        "text2": "Les templates distants imbriqués (un template distant qui contient lui-même d'autres <template src>) sont chargés de manière récursive."
      },
      "fileBased": {
        "title": "Routage basé sur les fichiers",
        "text": "Au lieu de déclarer chaque template de route manuellement, pointez votre sortie <code>route-view</code> vers un dossier. No.JS résoudra automatiquement les chemins de route vers les fichiers templates dans ce dossier.",
        "howItWorks": "Comment ça fonctionne",
        "list1": "Ajoutez <code>route-view</code> à votre élément de sortie — le routage basé sur les fichiers est activé par défaut (config <code>router.templates: \"pages\"</code>). Remplacez par sortie avec <code>src=\"folder/\"</code>.",
        "list2": "Quand un utilisateur navigue vers <code>/analytics</code>, No.JS le résout en <code>pages/analytics.tpl</code>",
        "list3": "Le template est récupéré, mis en cache et rendu — automatiquement",
        "attributesTitle": "Attributs",
        "colAttr": "Attribut",
        "colDefault": "Par défaut",
        "colDesc": "Description",
        "srcDesc": "Dossier de base pour la résolution des templates (remplacement par sortie ; config : <code>router.templates</code>)",
        "routeIndexDesc": "Nom de fichier pour la route racine <code>/</code>",
        "extDesc": "Extension de fichier ajoutée aux segments de route (fallback : <code>\".html\"</code>)",
        "i18nNsDesc": "Lorsque présent, dérive automatiquement le namespace i18n du nom de fichier",
        "callout": "<strong>Config par défaut :</strong> La valeur par défaut de <code>router.templates</code> est <code>\"pages\"</code>, donc le routage basé sur les fichiers fonctionne immédiatement — ajoutez simplement <code>route-view</code> à votre sortie. Remplacez avec <code>NoJS.config({ router: { templates: 'views' } })</code> ou par sortie via <code>src=\"./custom/\"</code>.",
        "exampleTitle": "Exemple — Dashboard SaaS",
        "exampleText": "C'est tout — <strong>deux lignes</strong> pour une SPA complète avec six routes.",
        "mixingTitle": "Mélanger Routes Explicites et Basées sur les Fichiers",
        "mixingText": "Les déclarations explicites <code>&lt;template route=\"...\"&gt;</code> <strong>ont toujours la priorité</strong>. Cela vous permet de combiner les deux approches — utilisez le routage basé sur les fichiers pour les pages simples et les templates explicites pour les routes qui nécessitent des guards, des paramètres ou des sorties nommées :",
        "autoI18nTitle": "Namespace i18n Automatique",
        "autoI18nText": "Lorsque l'élément <code>route-view</code> a un attribut <code>i18n-ns</code> (même sans valeur), No.JS charge automatiquement le namespace i18n correspondant au nom du fichier :",
        "autoI18nText2": "Cela remplace le besoin d'ajouter <code>i18n-ns=\"...\"</code> sur chaque template de route individuellement."
      },
      "lazyLoading": {
        "title": "Chargement différé de templates",
        "text": "L'attribut lazy sur <template src=\"...\"> contrôle quand un template distant est récupéré par rapport au premier rendu. Utilisez-le pour prioriser les templates critiques et différer les pages lourdes ou rarement visitées.",
        "col1": "Valeur",
        "col2": "Phase",
        "col3": "Comportement",
        "absent": "(absent)",
        "absentPhase": "1 ou 2",
        "absentDesc": "Auto : les templates non-route et le template de la route active se chargent avant le premier rendu (Phase 1) ; les autres templates de route se préchargent en arrière-plan après le premier rendu (Phase 2).",
        "priorityPhase": "0",
        "priorityDesc": "Charger avant tout le reste — même avant les inclusions de contenu classiques. À utiliser pour les templates de mise en page critiques et partagés.",
        "ondemandPhase": "à la demande",
        "ondemandDesc": "Valable uniquement sur les templates de route. Jamais préchargé — récupéré lors de la première navigation de l'utilisateur vers cette route. Idéal pour les pages lourdes ou rarement visitées."
      },
      "anchor": {
        "title": "Liens d'ancrage",
        "text1": "En utilisant useHash: true, le hash de l'URL (#) est utilisé pour le routage (ex. #/docs). Cela entre normalement en conflit avec les liens d'ancrage standard comme <a href=\"#section\"> — mais No.JS le gère automatiquement dans les deux modes (hash et history).",
        "text2": "Les liens d'ancrage qui pointent vers un id d'élément sur la page sont interceptés par le routeur : l'élément cible est scrollé en vue de manière fluide, et le lien cliqué reçoit une classe active. La route elle-même n'est pas affectée.",
        "howItWorks": "Comment ça marche :",
        "list1": "Cliquer sur <a href=\"#introduction\"> fait défiler vers <div id=\"introduction\"> avec un comportement fluide",
        "list2": "La classe .active est basculée sur le lien cliqué (et retirée des éléments frères)",
        "list3": "Le chemin de la route courante est préservé — aucune navigation ne se produit",
        "list4": "Les liens avec un attribut route sont toujours traités comme une navigation de route, pas comme des ancres",
        "tip": "Astuce : Stylisez le lien d'ancrage actif avec .active dans votre CSS — le routeur gère la classe pour vous."
      },
      "namedOutlets": {
        "title": "Outlets nommés (route-view)",
        "text": "Plusieurs outlets route-view peuvent coexister sur la même page. Donnez un nom à chaque outlet via la valeur de l'attribut, et dirigez les templates de route vers des outlets spécifiques en utilisant l'attribut outlet.",
        "callout": "Les outlets sans template correspondant pour la route courante sont toujours vidés lors de la navigation."
      },
      "catchAll": {
        "title": "404 / Routes Catch-All",
        "text": "Utilisez <code>route=\"*\"</code> pour définir un template <strong>wildcard catch-all</strong> qui s'affiche lorsqu'aucune route explicite ne correspond au chemin courant. Le wildcard est toujours évalué en dernier, quel que soit l'ordre dans le DOM.",
        "text2": "Les routes explicites <strong>ont toujours la priorité</strong> — le wildcard ne se déclenche que lorsque <code>matchRoute()</code> ne retourne aucune correspondance.",
        "fallbackTitle": "Fallback 404 Automatique",
        "fallbackText": "Si vous ne définissez pas de template <code>route=\"*\"</code>, No.JS affiche automatiquement une page 404 minimale intégrée lorsqu'aucune route ne correspond. Cela garantit que les utilisateurs voient toujours quelque chose de significatif au lieu d'un outlet vide.",
        "fallbackTip": "Le fallback intégré est volontairement minimal et sans style. Définissez votre propre template <code>route=\"*\"</code> pour les applications en production.",
        "namedTitle": "Wildcards dans les Outlets Nommés",
        "namedText": "Chaque outlet nommé peut avoir son propre fallback wildcard. Lorsqu'aucune route ne correspond pour un outlet, le framework résout les fallbacks dans cet ordre :",
        "namedList1": "<strong>Wildcard local</strong> — <code>&lt;template route=\"*\" outlet=\"{name}\"&gt;</code> pour cet outlet spécifique",
        "namedList2": "<strong>Wildcard global</strong> — <code>&lt;template route=\"*\"&gt;</code> (le wildcard de l'outlet par défaut), utilisé uniquement pour les outlets non par défaut",
        "namedList3": "<strong>404 intégré</strong> — la page de fallback minimale du framework",
        "namedText2": "Si la barre latérale n'a pas de wildcard local, elle recourt au <code>route=\"*\"</code> global. Si aucun n'existe, le 404 intégré est utilisé.",
        "matchedTitle": "$route.matched",
        "matchedText": "Le booléen <code>$route.matched</code> vous indique si le chemin courant a trouvé une route explicite (<code>true</code>) ou un wildcard/fallback (<code>false</code>). Utilisez-le pour le rendu conditionnel dans vos templates :",
        "matchedText2": "<code>$route.matched</code> est défini <strong>avant</strong> le rendu du template, il est donc toujours disponible pendant le traitement.",
        "remoteTitle": "Template 404 Distant",
        "remoteText": "Les routes wildcard supportent tous les mêmes attributs que les templates de route classiques, y compris <code>src</code> pour le chargement distant :",
        "remoteText2": "Le template distant est récupéré, mis en cache et rendu comme tout autre template de route — et il a un accès complet à <code>$route.path</code>, <code>$route.matched</code>, et toutes les autres fonctionnalités du framework.",
        "fileBasedTitle": "404 dans le Routage Basé sur les Fichiers",
        "fileBasedText": "Lors de l'utilisation du routage basé sur les fichiers, naviguer vers un chemin dont le fichier <code>.tpl</code> n'existe pas sur le serveur (HTTP 404 ou autre erreur) déclenche automatiquement la chaîne de fallback wildcard.",
        "fileBasedText2": "La réponse HTTP en échec n'est <strong>pas</strong> mise en cache — les navigations ultérieures vers d'autres chemins ne sont pas affectées."
      },
      "headAttributes": {
        "title": "Attributs Head des Routes",
        "text": "Les templates de route peuvent déclarer <code>page-title</code> et <code>page-description</code> directement sur la balise <code>&lt;template&gt;</code>. Quand la route est activée, les balises <code>&lt;head&gt;</code> correspondantes sont mises à jour automatiquement.",
        "text2": "Les chaînes statiques et les expressions dynamiques sont prises en charge :",
        "colAttr": "Attribut",
        "colDesc": "Description",
        "pageTitleDesc": "Définit <code>document.title</code> quand la route est active",
        "pageDescriptionDesc": "Définit le contenu de <code>&lt;meta name=\"description\"&gt;</code> quand la route est active",
        "callout": "Pour une gestion complète du head (URLs canoniques, JSON-LD, Open Graph), consultez le guide <a href=\"/docs/head-management\">Gestion du Head</a>."
      },
      "focusBehavior": {
        "title": "Accessibilité — Gestion du Focus",
        "text": "Après chaque navigation, No.JS déplace le focus vers un élément prévisible à l'intérieur du contenu de la nouvelle route. Cela garantit que les lecteurs d'écran annoncent le changement de page et que les utilisateurs au clavier atterrissent à un endroit utile.",
        "text2": "Quand focusBehavior est 'auto' (par défaut), le framework parcourt une liste de priorité :",
        "priority1": "<code>[autofocus]</code> — un élément avec l'attribut <code>autofocus</code> à l'intérieur du template de route",
        "priority2": "<code>h1</code> — le premier <code>&lt;h1&gt;</code> à l'intérieur du contenu de la route",
        "priority3": "<code>[tabindex=\"-1\"]</code> — un conteneur focalisable manuellement",
        "priority4": "L'élément route-view lui-même (dernier recours)",
        "defaultTitle": "Comportement par Défaut",
        "defaultText": "La gestion du focus est <strong>activée par défaut</strong> — vous n'avez rien à configurer. La stratégie <code>'auto'</code> fonctionne immédiatement.",
        "timingTitle": "Timing",
        "timingText": "Le focus est déplacé <strong>après</strong> que le template de route soit entièrement rendu et que tous les templates distants à l'intérieur soient résolus. Cela garantit que l'élément cible existe dans le DOM.",
        "sideEffectsTitle": "Effets Secondaires",
        "sideEffectsText": "L'élément focalisé reçoit <code>tabindex=\"-1\"</code> s'il n'a pas déjà un tabindex, et la page défile pour le rendre visible via <code>scrollIntoView({ block: 'nearest' })</code>.",
        "futureTitle": "Valeurs Futures",
        "futureText": "Actuellement seul <code>'auto'</code> est pris en charge. Les versions futures pourraient ajouter <code>'none'</code> (désactiver) et <code>'target'</code> (focaliser un sélecteur spécifique).",
        "ariaLiveTitle": "Région ARIA Live",
        "ariaLiveText": "Pour les lecteurs d'écran qui ne répondent pas aux changements de focus, ajoutez <code>aria-live=\"polite\"</code> à votre <code>route-view</code>. Le navigateur annoncera le nouveau contenu à son apparition :"
      },
      "viewTransitions": {
        "title": "View Transitions",
        "text": "L'attribut <code>transition</code> sur <code>route-view</code> utilise maintenant la <strong>View Transition API</strong> par défaut. Choisissez simplement un nom de preset et les changements de route sont animés nativement par le navigateur — sans CSS manuel.",
        "presetsTitle": "Presets Intégrés",
        "presetsText": "No.JS inclut quatre presets de transition intégrés :",
        "colPreset": "Preset",
        "colEffect": "Effet",
        "presetSlide": "Slide directionnel — le contenu glisse gauche/droite selon la direction de navigation (forward/backward)",
        "presetFade": "Crossfade — l'ancien contenu s'estompe tandis que le nouveau apparaît",
        "presetScale": "Scale zoom — l'ancien contenu se réduit tandis que le nouveau s'agrandit",
        "presetNone": "Changement instantané — sans animation, le contenu est remplacé immédiatement",
        "configTitle": "Configuration",
        "configText": "La View Transition API est <strong>activée par défaut</strong> via <code>router.viewTransition: true</code>. Réglez sur <code>false</code> pour revenir au système legacy de transitions basé sur les classes CSS.",
        "customCssTitle": "CSS Personnalisé",
        "customCssText": "Pour des animations personnalisées, ciblez les pseudo-éléments <code>::view-transition-old(route-content)</code> et <code>::view-transition-new(route-content)</code>. Le <code>view-transition-name: route-content</code> est défini automatiquement sur tout outlet ayant un attribut <code>transition</code>.",
        "howItWorksTitle": "Comment ça Fonctionne",
        "howItWorksText": "Lors d'un changement de route avec un preset <code>transition</code> :",
        "howStep1": "Le router détecte la direction de navigation (<strong>forward</strong> ou <strong>backward</strong>) depuis la pile d'historique",
        "howStep2": "<code>document.startViewTransition()</code> est appelé, capturant l'état actuel de l'outlet",
        "howStep3": "Le nouveau contenu de la route est rendu à l'intérieur de l'outlet",
        "howStep4": "Le navigateur anime entre les snapshots ancien et nouveau en utilisant les règles CSS du preset",
        "deprecationTitle": "Migration depuis les Transitions Basées sur les Classes",
        "deprecationText": "L'ancien système de transitions basé sur les classes (<code>*-enter</code>, <code>*-enter-active</code>, <code>*-leave</code>, etc.) sur <code>route-view</code> est <strong>deprecated</strong>. Il fonctionne toujours quand <code>router.viewTransition</code> est réglé sur <code>false</code>, mais la View Transition API est l'approche recommandée.",
        "deprecationCallout": "<strong>La migration est pratiquement automatique.</strong> Si vous utilisez déjà <code>transition=\"fade\"</code> sur un <code>route-view</code>, il utilise maintenant la View Transition API par défaut — aucun changement de code nécessaire. Votre CSS personnalisé <code>.fade-enter</code> / <code>.fade-leave</code> est simplement ignoré (sauf si vous définissez <code>viewTransition: false</code>)."
      }
    },
    "formsValidation": {
      "hero": {
        "badge": "Guides",
        "title": "Formulaires & Validation",
        "subtitle": "Soumission déclarative de formulaires avec règles de validation intégrées et personnalisées"
      },
      "submission": {
        "title": "Soumission déclarative de formulaire"
      },
      "rules": {
        "title": "Règles de validation"
      },
      "perRuleErrors": {
        "title": "Messages d'erreur par règle",
        "text": "Utilisez les attributs error-{règle} pour définir un message personnalisé pour une règle spécifique, ou error comme message générique."
      },
      "errorTemplates": {
        "title": "Modèles d'erreur",
        "text": "Pointez un attribut error vers un <template> en utilisant le préfixe # pour afficher une UI d'erreur enrichie. Dans le modèle, $error contient le message et $rule le nom de la règle échouée."
      },
      "errorClass": {
        "title": "Classe CSS d'erreur",
        "text": "Utilisez error-class sur le formulaire ou sur des champs individuels pour basculer une classe CSS quand un champ est invalide et a été touché."
      },
      "formContext": {
        "title": "$form — Contexte de formulaire",
        "text": "À l'intérieur de tout <form> avec l'attribut validate, $form fournit :",
        "col1": "Propriété",
        "col2": "Type",
        "col3": "Description",
        "valid": "true si tous les champs passent la validation",
        "dirty": "true si un champ a été modifié",
        "touched": "true si un champ a reçu puis perdu le focus",
        "submitting": "true pendant que la requête est en cours",
        "pending": "true pendant que les validateurs asynchrones s'exécutent",
        "errors": "Map des noms de champs → messages d'erreur",
        "values": "Valeurs courantes du formulaire",
        "firstError": "Message d'erreur du premier champ invalide (ordre du DOM)",
        "errorCount": "Nombre de champs actuellement en échec de validation",
        "fields": "Objet d'état par champ (valid, error, dirty, touched)",
        "reset": "Réinitialiser le formulaire aux valeurs initiales, effacer erreurs et classes"
      },
      "formFields": {
        "title": "$form.fields — État par champ",
        "text": "$form.fields expose l'état individuel de chaque champ indexé par nom.",
        "asTitle": "Alias de champ avec as",
        "asText": "Utilisez l'attribut as pour exposer l'état d'un champ sous un nom personnalisé dans le contexte."
      },
      "validateOn": {
        "title": "Déclencheurs de validation (validate-on)",
        "text": "Par défaut, la validation s'exécute sur input et focusout. Utilisez validate-on pour changer quand le feedback visuel apparaît.",
        "note": "En interne, les données de $form sont toujours maintenues à jour indépendamment de validate-on. Le déclencheur contrôle uniquement quand le feedback visuel est affiché."
      },
      "validateIf": {
        "title": "Validation conditionnelle (validate-if)",
        "text": "Ignorez la validation d'un champ en fonction d'une condition. Quand validate-if évalue à false, le champ est traité comme valide."
      },
      "autoDisable": {
        "title": "Désactiver automatiquement les boutons de soumission",
        "text": "Les boutons de soumission sont automatiquement désactivés quand le formulaire est invalide. Les boutons avec type=\"button\" ne sont pas affectés."
      },
      "customValidators": {
        "title": "Validateurs personnalisés"
      },
      "liveDemo": {
        "title": "Démo en direct — Formulaire d'inscription",
        "label": "Résultat",
        "usernameLabel": "Nom d'utilisateur",
        "usernamePlaceholder": "Choisissez un nom d'utilisateur",
        "emailLabel": "E-mail",
        "emailPlaceholder": "vous@exemple.com",
        "ageLabel": "Âge",
        "agePlaceholder": "Votre âge",
        "roleLabel": "Rôle",
        "roleDefault": "Sélectionnez un rôle",
        "roleDev": "Développeur",
        "roleDesign": "Designer",
        "roleMgr": "Manager",
        "termsLabel": "J'accepte les conditions",
        "registerButton": "S'inscrire",
        "successMessage": "Inscription terminée ! Bienvenue."
      }
    },
    "styling": {
      "hero": {
        "badge": "Guides",
        "title": "Styles dynamiques",
        "subtitle": "Basculez les classes CSS et les styles inline de manière réactive"
      },
      "classToggle": {
        "title": "class-* — Basculer les classes",
        "text": "Ajoutez ou supprimez des classes CSS de manière réactive en fonction d'expressions. Utilisez class-{name} pour des toggles individuels ou class-map pour plusieurs classes.",
        "multiObject": "Classes multiples depuis un objet",
        "multiObjectText": "Utilisez class-map avec une expression objet pour basculer plusieurs classes. Les clés sont des noms de classes, les valeurs des expressions booléennes.",
        "fromArray": "Depuis un tableau"
      },
      "inlineStyles": {
        "title": "style-* — Styles inline",
        "text": "Définissez des propriétés CSS inline de manière réactive avec des attributs style-{property}. Les noms de propriétés utilisent le kebab-case (ex : style-font-size, style-background-color).",
        "fromObject": "Depuis un objet",
        "fromObjectText": "Utilisez style-map avec une expression objet pour définir plusieurs styles inline en une seule fois."
      },
      "liveDemo": {
        "title": "Démo en direct — Styles dynamiques",
        "label": "Résultat",
        "toggleButton": "Basculer Actif"
      },
      "classStatic": {
        "title": "Interaction avec class Statique",
        "text": "Les directives class-* fonctionnent avec les attributs class statiques. No.JS ne bascule que les classes gérées par la directive — les classes statiques ne sont jamais supprimées.",
        "callout": "Vous pouvez combiner class=\"card\" avec class-active=\"isActive\" en toute sécurité. La classe card est toujours présente ; active est basculée."
      },
      "cssCustomProperties": {
        "title": "CSS Custom Properties",
        "text": "Utilisez style-* avec les CSS custom properties (variables) pour un style réactif avec thèmes. Les noms de propriétés suivent la même convention kebab-case.",
        "callout": "Les CSS custom properties définies via style-* sont scopées à l'élément et héritées par ses enfants — comme les CSS custom properties classiques."
      }
    },
    "animations": {
      "hero": {
        "badge": "Guides",
        "title": "Animations & Transitions",
        "subtitle": "Animations d'entrée/sortie déclaratives et transitions CSS"
      },
      "enterLeave": {
        "title": "animate — Animations d'entrée/sortie",
        "attrsTitle": "Attributs d'animation",
        "col1": "Attribut",
        "col2": "Description",
        "row1": "Classe d'animation CSS ajoutée quand l'élément entre",
        "row2": "Classe d'animation CSS ajoutée quand l'élément sort",
        "row3": "Durée en millisecondes transmise à animationDuration et utilisée comme timeout de secours. Si omis, le secours se déclenche au prochain tick de la boucle d'événements (0 ms)",
        "row4": "Délai en millisecondes entre chaque élément dans une boucle (each / foreach)",
        "fallbackCallout": "Timeout de secours — No.JS écoute animationend / transitionend pour nettoyer les classes et déclencher les re-rendus après les animations de sortie. Si l'événement ne se déclenche jamais (ex. CSS absent, élément détaché) un setTimeout de sécurité garantit que le pipeline n'est jamais bloqué indéfiniment. Si animate-duration est omis, le timeout est de 0 ms — il se déclenche au prochain tick de la boucle d'événements sans attente artificielle. Passer un animate-duration=\"300\" explicite définit à la fois animation-duration sur l'élément cible et le timeout de sécurité à 300 ms."
      },
      "transition": {
        "title": "transition — Classes de transition CSS",
        "viewTransitionNote": "<strong>Note :</strong> Les transitions de route sur <code>route-view</code> utilisent maintenant la <a href=\"/docs/routing#view-transitions\">View Transition API</a> par défaut. L'attribut <code>transition</code> basé sur les classes décrit ci-dessous s'applique toujours aux <strong>éléments réguliers</strong> (ex : <code>if</code>, <code>show</code>).",
        "text1": "Suit une convention similaire au système de transition de Vue.",
        "text2": "No.JS ajoute/supprime des classes pendant la transition :",
        "col1": "Classe",
        "col2": "Quand",
        "row1": "État de départ de l'entrée",
        "row2": "État actif de l'entrée",
        "row3": "État final de l'entrée",
        "row4": "État de départ de la sortie",
        "row5": "État actif de la sortie",
        "row6": "État final de la sortie"
      },
      "loopAnimations": {
        "title": "Animations de boucle",
        "text": "Toutes les directives de boucle (foreach, each, for) supportent les animations d'entrée/sortie et l'échelonnement."
      },
      "builtIn": {
        "title": "Noms d'animations intégrés",
        "text": "No.JS inclut ces animations CSS :"
      },
      "liveDemo": {
        "title": "Démo en direct — Animation de bascule",
        "label": "Résultat",
        "toggleButton": "Basculer",
        "demoText": "Bonjour, Monde Animé ! ✨"
      },
      "a11y": {
        "title": "Accessibilité — Mouvement Réduit",
        "text": "No.JS respecte automatiquement la media query prefers-reduced-motion. Quand l'utilisateur préfère le mouvement réduit, toutes les animations CSS sont désactivées via une règle CSS intégrée qui définit animation-duration et transition-duration à 0.01ms.",
        "callout": "Cela s'applique globalement — aucune configuration par élément n'est nécessaire. Les utilisateurs qui préfèrent le mouvement réduit voient des changements d'état instantanés sans animation."
      }
    },
    "dnd": {
      "hero": {
        "badge": "Guides",
        "title": "Drag and Drop",
        "subtitle": "Glisser-déposer, listes triables et sélection multiple déclaratifs — zéro JavaScript"
      },
      "drag": {
        "title": "drag — Rendre un Élément Déplaçable",
        "preview": "Aperçu",
        "demoText": "Glissez les fruits dans le panier :",
        "fruitsLabel": "Fruits",
        "basketLabel": "Panier",
        "col1": "Attribut",
        "col2": "Type",
        "col3": "Défaut",
        "col4": "Description",
        "dragDesc": "La valeur glissée",
        "dragTypeDesc": "Type nommé — seules les zones <code>drop-accept</code> correspondantes répondent",
        "dragEffectDesc": "Correspond à <code>dataTransfer.effectAllowed</code>",
        "dragHandleDesc": "Restreint la zone de saisie à un élément enfant",
        "dragImageDesc": "Élément fantôme de glissement personnalisé",
        "dragImageOffsetDesc": "Décalage en pixels pour l'image de glissement personnalisée",
        "dragDisabledDesc": "Lorsque vrai, désactive le glissement",
        "dragClassDesc": "Classe ajoutée pendant le glissement",
        "dragGhostClassDesc": "Classe ajoutée à l'élément image de glissement",
        "dragGroupDesc": "Nom du groupe pour la sélection multiple"
      },
      "drop": {
        "title": "drop — Définir une Zone de Dépôt",
        "preview": "Aperçu",
        "demoText": "Glissez les éléments entre les zones :",
        "zoneALabel": "Zone A",
        "zoneBLabel": "Zone B",
        "col1": "Attribut",
        "col2": "Type",
        "col3": "Défaut",
        "col4": "Description",
        "dropDesc": "Expression exécutée au dépôt",
        "dropAcceptDesc": "Type(s) <code>drag-type</code> accepté(s). Utilisez <code>\"*\"</code> pour tout",
        "dropEffectDesc": "Correspond à <code>dataTransfer.dropEffect</code>",
        "dropClassDesc": "Classe ajoutée lorsqu'un élément valide survole",
        "dropRejectClassDesc": "Classe ajoutée lorsque l'élément est rejeté (mauvais type ou maximum dépassé)",
        "dropDisabledDesc": "Lorsque vrai, désactive le dépôt",
        "dropMaxDesc": "Nombre maximum d'éléments acceptés par la zone",
        "dropSortDesc": "Active le réordonnancement par position",
        "dropPlaceholderDesc": "Affiche un espace réservé au point d'insertion",
        "dropPlaceholderClassDesc": "Classe pour l'espace réservé",
        "dropSettleClassDesc": "Classe CSS personnalisée pour l'animation de stabilisation",
        "dropEmptyClassDesc": "Classe CSS personnalisée pour l'état vide de la zone de dépôt"
      },
      "dragList": {
        "title": "drag-list — Liste Triable",
        "text": "Une directive de haut niveau qui combine <code>drag</code> et <code>drop</code> en une liste triable liée à un tableau d'état.",
        "preview": "Aperçu",
        "todoLabel": "À Faire",
        "doneLabel": "Terminé",
        "col1": "Attribut",
        "col2": "Type",
        "col3": "Défaut",
        "col4": "Description",
        "dragListDesc": "Chemin vers le tableau dans l'état",
        "templateDesc": "Template pour chaque élément",
        "dragListKeyDesc": "Clé unique par élément pour une identité stable",
        "dragListItemDesc": "Nom de la variable de boucle dans le template",
        "dropSortDesc": "Direction du layout",
        "dropAcceptDesc": "Types acceptés (par défaut : même liste)",
        "dragListCopyDesc": "Copier les éléments au lieu de les déplacer",
        "dragListRemoveDesc": "Supprimer les éléments lorsqu'ils sont glissés",
        "dragDisabledDesc": "Désactive le glissement depuis cette liste",
        "dropDisabledDesc": "Désactive le dépôt dans cette liste",
        "dropMaxDesc": "Nombre maximum d'éléments autorisés",
        "dropSettleClassDesc": "Classe CSS personnalisée pour l'animation de stabilisation",
        "dropEmptyClassDesc": "Classe CSS personnalisée pour l'état vide de la drag-list",
        "dropPlaceholderDesc": "Affiche un espace réservé à l'endroit du dépôt"
      },
      "dragListEvents": {
        "title": "Événements de Drag-List",
        "col1": "Événement",
        "col3": "Description",
        "reorderDesc": "Élément réordonné dans la même liste",
        "receiveDesc": "Élément reçu d'une autre liste",
        "removeDesc": "Élément supprimé (glissé hors)",
        "preview": "Aperçu",
        "inboxLabel": "Boîte de réception",
        "archiveLabel": "Archives",
        "eventLogLabel": "Journal des événements :"
      },
      "dragMultiple": {
        "title": "drag-multiple — Sélection Multiple",
        "text": "Activez le clic pour sélectionner sur les éléments enfants, puis glissez tous les éléments sélectionnés en une fois.",
        "preview": "Aperçu",
        "demoText": "Cliquez pour sélectionner, <kbd>Ctrl</kbd>+clic pour multi-sélection, puis glissez vers la zone de dépôt :",
        "availableLabel": "Disponibles",
        "collectedLabel": "Collectés",
        "col1": "Attribut",
        "col2": "Défaut",
        "col3": "Description",
        "dragMultipleDesc": "Active le clic pour sélectionner",
        "dragMultipleClassDesc": "Classe ajoutée aux éléments sélectionnés",
        "dragGroupDesc": "Nom du groupe — tous les éléments sélectionnés se déplacent ensemble",
        "selectionTitle": "Comportement de sélection :",
        "selCol1": "Action",
        "selCol2": "Résultat",
        "selClick": "Clic",
        "selClickResult": "Sélectionne un seul élément (remplace le précédent)",
        "selCtrlClick": "Ctrl/Cmd + Clic",
        "selCtrlClickResult": "Ajoute à la sélection",
        "selEscape": "Échap",
        "selEscapeResult": "Efface toutes les sélections",
        "selDrag": "Glisser un élément sélectionné",
        "selDragResult": "<code>$drag</code> devient un tableau de tous les éléments sélectionnés"
      },
      "implicitVars": {
        "title": "Variables Implicites",
        "text": "Ces variables sont disponibles dans les expressions <code>drop</code> et les gestionnaires <code>on:drop</code> :",
        "col1": "Variable",
        "col2": "Type",
        "col3": "Description",
        "dragDesc": "La valeur glissée. Tableau si multi-sélection",
        "dragTypeDesc": "Le <code>drag-type</code> de l'élément",
        "dragEffectDesc": "Le <code>drag-effect</code>",
        "dropIndexDesc": "Indice d'insertion dans la zone de dépôt",
        "sourceDesc": "<code>{ list, index, el }</code> — info source",
        "targetDesc": "<code>{ list, index, el }</code> — info cible",
        "preview": "Aperçu",
        "demoText": "Chaque élément a un <code>drag-type</code> différent :",
        "dropHere": "Déposez ici pour inspecter"
      },
      "cssClasses": {
        "title": "Classes CSS",
        "text": "Injectées automatiquement par No.JS :",
        "col1": "Classe",
        "col2": "Quand appliquée",
        "draggingDesc": "Sur l'élément source pendant le glissement",
        "dragOverDesc": "Sur la zone de dépôt lorsqu'un élément valide survole",
        "dropRejectDesc": "Sur la zone de dépôt lorsque l'élément est rejeté (mauvais type ou maximum dépassé)",
        "dropPlaceholderDesc": "Sur l'espace réservé d'insertion",
        "selectedDesc": "Sur les éléments multi-sélectionnés",
        "dropSettleDesc": "Brève animation de stabilisation au dépôt",
        "dragListEmptyDesc": "Sur une <code>drag-list</code> lorsqu'elle est vide",
        "preview": "Aperçu",
        "demoText": "Glissez un élément et observez les classes s'appliquer :",
        "dropZoneHint": "Zone de dépôt (max 2) — observez <code>.nojs-drag-over</code> / <code>.nojs-drop-reject</code>"
      },
      "a11y": {
        "title": "Accessibilité",
        "text": "No.JS ajoute automatiquement les attributs ARIA et le support clavier :",
        "col1": "Fonctionnalité",
        "col2": "Détails",
        "draggableDesc": "Défini sur les sources de glissement",
        "ariaGrabbedDesc": "Reflète l'état de glissement (<code>true</code>/<code>false</code>)",
        "ariaDropeffectDesc": "Défini sur les zones de dépôt",
        "roleListboxDesc": "Sur les conteneurs <code>drag-list</code>",
        "roleOptionDesc": "Sur les éléments <code>drag-list</code>",
        "tabindexDesc": "Pour l'accès au clavier",
        "keyboardTitle": "Raccourcis clavier :",
        "keyCol1": "Touche",
        "keyCol2": "Action",
        "spaceDesc": "Saisir l'élément ciblé",
        "escapeDesc": "Annuler le glissement",
        "arrowDesc": "Naviguer entre les éléments pendant le glissement",
        "enterDesc": "Déposer à la position actuelle",
        "preview": "Aperçu",
        "demoText": "Utilisez <kbd>Tab</kbd> pour cibler, <kbd>Espace</kbd> pour saisir, <kbd>↑↓</kbd> pour déplacer, <kbd>Entrée</kbd> pour déposer :"
      }
    },
    "filters": {
      "hero": {
        "badge": "Guides",
        "title": "Filtres & Pipes",
        "subtitle": "Transformez les valeurs dans les expressions bind en utilisant la syntaxe pipe |"
      },
      "text": {
        "title": "Filtres de texte"
      },
      "number": {
        "title": "Filtres numériques"
      },
      "array": {
        "title": "Filtres de tableau"
      },
      "date": {
        "title": "Filtres de date"
      },
      "utility": {
        "title": "Filtres utilitaires"
      },
      "object": {
        "title": "Filtres d'objet"
      },
      "chaining": {
        "title": "Chaînage de filtres"
      },
      "custom": {
        "title": "Filtres personnalisés"
      },
      "liveDemo": {
        "title": "Démo en direct — Filtres",
        "label": "Résultat",
        "uppercaseLabel": "Majuscules :",
        "slugifyLabel": "Slugify :"
      },
      "referenceTable": {
        "title": "Référence Complète des Filtres",
        "text": "Les 32 filtres intégrés en un coup d'œil :",
        "col1": "Filtre",
        "col2": "Catégorie",
        "col3": "Description",
        "uppercase": "Convertit le texte en MAJUSCULES",
        "lowercase": "Convertit le texte en minuscules",
        "capitalize": "Met en majuscule la première lettre de chaque mot",
        "truncate": "Tronque le texte à N caractères avec des points de suspension",
        "slugify": "Convertit en slug compatible URL",
        "trim": "Supprime les espaces en début/fin",
        "encodeUri": "Encode un composant URI",
        "currency": "Formate un nombre en devise",
        "number": "Formate un nombre avec groupement par localité",
        "percent": "Formate un nombre en pourcentage",
        "ordinal": "Ajoute un suffixe ordinal (1er, 2e, 3e)",
        "filesize": "Formate les octets en taille lisible",
        "reverse": "Inverse un tableau ou une chaîne",
        "unique": "Supprime les valeurs en double",
        "pluck": "Extrait une propriété de chaque élément",
        "where": "Filtre un tableau par valeur de propriété",
        "sortBy": "Trie un tableau par propriété",
        "first": "Retourne le premier élément d'un tableau",
        "last": "Retourne le dernier élément d'un tableau",
        "date": "Formate un objet Date",
        "datetime": "Formate une Date avec l'heure",
        "relative": "Temps écoulé depuis la date (ex : 'il y a 5 minutes')",
        "fromNow": "Temps jusqu'à une date future (ex : 'dans 3 heures')",
        "json": "Sérialise une valeur en chaîne JSON",
        "default": "Retourne un fallback si la valeur est null/undefined",
        "keys": "Retourne les clés de l'objet en tableau",
        "values": "Retourne les valeurs de l'objet en tableau",
        "count": "Retourne la longueur d'un tableau",
        "join": "Joint les éléments d'un tableau avec un séparateur",
        "stripHtml": "Supprime les balises HTML d'une chaîne",
        "nl2br": "Convertit les sauts de ligne en balises <br>",
        "debug": "Affiche la valeur dans la console et la retourne"
      }
    },
    "i18n": {
      "hero": {
        "badge": "Guides",
        "title": "Internationalisation (i18n)",
        "subtitle": "Support multilingue avec traductions, pluralisation et formatage selon la locale"
      },
      "setup": {
        "title": "Configuration"
      },
      "externalFiles": {
        "title": "Fichiers de locale externes",
        "text": "Au lieu d'intégrer toutes les traductions dans le JavaScript, vous pouvez les charger depuis des fichiers JSON externes. C'est idéal pour les grandes applications avec de nombreuses locales ou quand les traductions sont gérées par un outil séparé.",
        "flatSubtitle": "Mode plat (un fichier par locale)",
        "flatText": "Structure :",
        "nsSubtitle": "Mode namespace (séparé par fonctionnalité)",
        "nsText": "Divisez les traductions par fonctionnalité pour le code-splitting et le chargement à la demande :",
        "nsRouteSubtitle": "Namespace par route",
        "nsRouteText": "Utilisez i18n-ns sur un template de route pour charger un namespace à la demande lorsque la route est visitée :",
        "nsElementSubtitle": "Namespace sur n'importe quel élément",
        "nsElementText": "Utilisez i18n-ns sur n'importe quel élément pour charger un namespace avant que ses enfants ne soient traités :",
        "cachingSubtitle": "Mise en cache",
        "cachingText": "Les fichiers JSON récupérés sont mis en cache en mémoire par défaut. Désactivez avec cache: false pendant le développement :"
      },
      "usage": {
        "title": "Utilisation"
      },
      "formatting": {
        "title": "Formatage des nombres & dates"
      },
      "liveDemo": {
        "title": "Démo en direct — Sélecteur de locale",
        "label": "Résultat",
        "localeLabel": "Locale :"
      },
      "fallback": {
        "title": "Comportement de Fallback",
        "text": "Quand une clé de traduction est absente de la localité actuelle, No.JS recourt à la fallbackLocale (par défaut : identique à defaultLocale). Si la clé est absente des deux, le chemin brut de la clé est affiché.",
        "callout": "Définissez fallbackLocale explicitement pour garantir que les utilisateurs voient toujours du texte significatif, même pour les localités partiellement traduites."
      },
      "detection": {
        "title": "Détection de la Localité du Navigateur",
        "text": "Activez la détection automatique de localité depuis navigator.language du navigateur avec detectBrowser: true. La localité détectée est comparée à vos localités disponibles.",
        "callout": "Combiné avec persist: true, la localité détectée est sauvegardée pour que les visites suivantes utilisent la même langue sans re-détection."
      }
    },
    "actionsRefs": {
      "hero": {
        "badge": "Référence API",
        "title": "Actions & Refs",
        "subtitle": "Déclenchez des appels API, émettez des événements personnalisés et référencez des éléments DOM"
      },
      "call": {
        "title": "call — Déclencher des requêtes API depuis n'importe quel élément"
      },
      "trigger": {
        "title": "trigger — Émettre des événements personnalisés"
      },
      "ref": {
        "title": "ref — Références nommées",
        "text": "Accédez aux éléments DOM sans querySelector :"
      },
      "refsMap": {
        "title": "$refs — Map de références",
        "text": "Tous les éléments avec ref sont accessibles via $refs dans la portée courante :"
      },
      "triggerAttrs": {
        "title": "Attributs du Trigger",
        "text": "Personnalisez le comportement du trigger avec des attributs supplémentaires :",
        "col1": "Attribut",
        "col2": "Description",
        "triggerData": "Payload de données attaché à la propriété detail du CustomEvent"
      }
    },
    "customDirectives": {
      "hero": {
        "badge": "Référence API",
        "title": "Directives personnalisées",
        "subtitle": "Étendez No.JS avec vos propres comportements pilotés par attributs"
      },
      "directive": {
        "title": "NoJS.directive()"
      },
      "usage": {
        "title": "Utilisation"
      },
      "webComponents": {
        "title": "Compatibilité Web Components",
        "text": "Les directives No.JS fonctionnent sur les éléments personnalisés :"
      },
      "componentPatterns": {
        "title": "Patterns de type composant avec les templates"
      },
      "priority": {
        "title": "Niveaux de Priorité",
        "text": "La priorité détermine quand votre directive s'exécute par rapport aux directives intégrées. Les nombres plus bas s'exécutent en premier.",
        "col1": "Plage",
        "col2": "Quand",
        "range0": "Avant tout (initialisation de l'état)",
        "range1": "Après l'état, avec la récupération de données",
        "range10": "Avec les directives structurelles (if, each)",
        "range20": "Avec les directives de rendu (bind, on:*)",
        "range30": "Après tout (validation, effets secondaires)"
      },
      "disposal": {
        "title": "Disposal & Cleanup",
        "text": "Les directives personnalisées doivent nettoyer après elles-mêmes. Utilisez le callback _onDispose pour supprimer les event listeners, nettoyer les timers et annuler les watchers.",
        "callout": "Les directives enregistrées après init (via plugins) ne sont pas gelées — mais ne peuvent pas remplacer les directives intégrées."
      }
    },
    "errorHandling": {
      "hero": {
        "badge": "Référence API",
        "title": "Gestion des erreurs",
        "subtitle": "Templates d'erreur par élément, logique de retry et gestionnaires d'erreurs globaux"
      },
      "perElement": {
        "title": "Gestion des erreurs par élément"
      },
      "globalHandler": {
        "title": "Gestionnaire d'erreurs global"
      },
      "errorBoundary": {
        "title": "error-boundary — Capturer les erreurs dans un sous-arbre"
      },
      "retry": {
        "title": "Comportement de Retry",
        "text": "Les requêtes HTTP échouées (erreurs 5xx et pannes réseau) sont automatiquement réessayées. Configurez le nombre de retries par élément avec l'attribut retry, et le délai entre les retries avec retry-delay.",
        "callout": "Les retries ne s'appliquent qu'aux erreurs serveur (5xx) et pannes réseau. Les erreurs client (4xx) ne sont pas réessayées."
      },
      "boundaryEvents": {
        "title": "Événements d'Error Boundary",
        "text": "Quand un error boundary capture une erreur, il émet un CustomEvent nojs:error sur l'élément boundary. Écoutez avec on:error pour enregistrer les erreurs ou afficher des notifications.",
        "callout": "L'objet $event.detail contient : message (string), source (element) et error (objet Error original)."
      },
      "expressionErrors": {
        "title": "Erreurs d'Expression",
        "text": "Quand une expression échoue à s'évaluer (ex : accéder à une propriété d'undefined), No.JS capture l'erreur, enregistre un avertissement via _warn() et retourne undefined. Une expression cassée ne fait jamais planter la page entière."
      }
    },
    "configuration": {
      "hero": {
        "badge": "Référence API",
        "title": "Configuration & Sécurité",
        "subtitle": "Paramètres globaux, intercepteurs de requêtes et bonnes pratiques de sécurité"
      },
      "globalSettings": {
        "title": "Paramètres globaux"
      },
      "configStores": {
        "title": "Pré-initialisation des Stores",
        "text": "Utilisez la clé stores dans NoJS.config() pour pré-créer des stores globaux nommés avec des données initiales. Chaque entrée devient un store réactif accessible via $store.nom partout dans votre HTML.",
        "callout": "Les stores créés via config() n'écraseront pas les stores existants — la première définition prévaut."
      },
      "configOptions": {
        "title": "Détails des options de configuration",
        "sanitizeTitle": "sanitize",
        "sanitizeType": "Type : boolean | Par défaut : true",
        "sanitizeText": "Contrôle si le contenu HTML rendu via bind-html est nettoyé par un sanitizer structurel basé sur DOMParser. Lorsqu'activé, toutes les balises et attributs potentiellement dangereux (ex. <script>, onerror) sont supprimés avant l'insertion dans le DOM.",
        "devtoolsTitle": "devtools",
        "devtoolsType": "Type : boolean | Par défaut : false",
        "devtoolsText": "Active le panneau devtools No.JS, accessible via window.__NOJS_DEVTOOLS__. Lorsqu'actif, il expose l'état réactif, les directives enregistrées, les routes actives et les arborescences de composants pour l'inspection dans la console du navigateur.",
        "templatesCacheTitle": "templates.cache",
        "templatesCacheType": "Type : boolean | Par défaut : true",
        "templatesCacheText1": "Contrôle si le contenu HTML des fichiers .tpl récupérés à distance est stocké dans un Map en mémoire après la première requête. Lors des navigations répétées vers la même route, le HTML en cache est utilisé directement et aucune requête HTTP n'est effectuée. Le cache persiste pendant la durée de la session de page (pas de TTL — les assets de template sont statiques).",
        "templatesCacheText2": "Définir à false pendant le développement local si vous souhaitez que les modifications des fichiers .tpl soient reflétées sans rechargement complet de la page.",
        "loadPathTitle": "i18n.loadPath",
        "loadPathType": "Type : string | null | Par défaut : null",
        "loadPathText": "Modèle d'URL pour charger les fichiers JSON de locale via fetch. Utilisez {locale} et optionnellement {ns} comme placeholders. Quand null, les traductions doivent être fournies en ligne via NoJS.i18n({ locales }).",
        "nsTitle": "i18n.ns",
        "nsType": "Type : string[] | Par défaut : []",
        "nsText": "Tableau d'identifiants de namespace à précharger au init(). Chaque namespace correspond à un fichier JSON séparé par locale. Des namespaces supplémentaires peuvent être chargés à la demande via la directive i18n-ns ou l'attribut de route.",
        "cacheTitle": "i18n.cache",
        "cacheType": "Type : boolean | Par défaut : true",
        "cacheText": "Contrôle si les fichiers JSON de locale récupérés sont stockés dans un Map en mémoire après la première requête. Définir à false pendant le développement pour le rechargement à chaud des fichiers de traduction."
      },
      "apiProperties": {
        "title": "Propriétés de l'API",
        "baseApiUrlTitle": "NoJS.baseApiUrl",
        "baseApiUrlText": "Getter/setter pour l'URL de base de l'API utilisée par toutes les directives de fetch et les appels NoJS.http. Peut être lue ou réassignée à l'exécution.",
        "versionTitle": "NoJS.version",
        "versionText": "Propriété en lecture seule qui retourne la chaîne de version actuelle du framework No.JS."
      },
      "interceptors": {
        "title": "Intercepteurs de requêtes"
      },
      "security": {
        "title": "Sécurité",
        "xssTitle": "Protection XSS",
        "xssList1": "bind définit toujours textContent, jamais innerHTML — sûr par défaut.",
        "xssList2": "bind-html nettoie le contenu en utilisant un sanitizer structurel intégré basé sur DOMParser (supprime les balises <script>, bloque les gestionnaires d'événements on*, retire les URI javascript:).",
        "xssList3": "Les expressions de template sont évaluées par un parser sandboxé personnalisé — aucun eval() ni Function() n'est utilisé, et les propriétés dangereuses comme __proto__ et constructor sont bloquées.",
        "csrfTitle": "Protection CSRF",
        "cspSecTitle": "Content Security Policy",
        "cspSecText1": "No.JS utilise un parser d'expressions personnalisé entièrement compatible CSP — aucun eval() ni constructeur Function() n'est utilisé. Aucune directive unsafe-eval n'est requise dans votre Content Security Policy."
      }
    },
    "plugins": {
      "hero": {
        "badge": "Référence API",
        "title": "Plugins",
        "subtitle": "Étendez No.JS avec des packages réutilisables — analytics, authentification, feature flags et plus"
      },
      "use": {
        "title": "NoJS.use()",
        "text": "Enregistrez un plugin avant ou après NoJS.init(). Si l'application est déjà initialisée, le hook init du plugin s'exécute immédiatement.",
        "objectFormTitle": "Forme Objet",
        "objectFormText": "La manière standard de définir un plugin. Fournissez un nom, une fonction install et des hooks de cycle de vie optionnels.",
        "functionTitle": "Raccourci Fonction",
        "functionText": "Pour les plugins simples, passez une fonction nommée. Le nom de la fonction devient le nom du plugin.",
        "functionCallout": "Les fonctions anonymes et les fonctions fléchées sont rejetées — le plugin doit avoir un nom.",
        "optionsTitle": "Options",
        "optionsText": "Le second argument de NoJS.use() est passé à la fonction install du plugin.",
        "optionsTrustedNote": "L'option trusted est spéciale — voir Intercepteurs de Confiance ci-dessous."
      },
      "interface": {
        "title": "Interface du Plugin",
        "thProperty": "Propriété",
        "thType": "Type",
        "thRequired": "Requis",
        "thDescription": "Description",
        "yes": "Oui",
        "no": "Non",
        "nameDesc": "Identifiant unique. Les noms dupliqués sont rejetés.",
        "versionDesc": "Chaîne semver pour le débogage.",
        "capabilitiesDesc": "Capacités déclarées (enregistrées en mode debug).",
        "installDesc": "Appelé de manière synchrone par NoJS.use().",
        "initDesc": "Appelé après que NoJS.init() se termine (le DOM est prêt).",
        "disposeDesc": "Appelé pendant NoJS.dispose() pour le nettoyage.",
        "lifecycleTitle": "Cycle de vie",
        "lifecycleInstall": "install s'exécute immédiatement et de manière synchrone. Utilisez-le pour enregistrer des intercepteurs, des globales, des directives, des écouteurs d'événements et des stores.",
        "lifecycleInit": "init s'exécute après le traitement du DOM et l'activation du routeur. Utilisez-le pour les tâches qui dépendent d'éléments rendus ou de l'état de la route.",
        "lifecycleDispose": "dispose s'exécute pendant la destruction de l'application. Utilisez-le pour fermer les connexions WebSocket, vider les intervalles, envoyer les analytics en attente, etc.",
        "duplicateTitle": "Détection des doublons",
        "duplicateText": "Un nom de plugin ne peut être enregistré qu'une seule fois. Si NoJS.use() est appelé à nouveau avec le même nom mais un objet différent, un avertissement est enregistré et l'appel est ignoré.",
        "freezingTitle": "Gel du registre des directives",
        "freezingText": "Les directives principales (state, bind, get, on:*, etc.) sont gelées après le chargement du framework. Les plugins peuvent enregistrer de nouvelles directives mais ne peuvent pas remplacer celles intégrées."
      },
      "globals": {
        "title": "NoJS.global()",
        "text": "Injecte une variable réactive accessible en tant que $name dans toute expression de template.",
        "namingTitle": "Conventions de nommage",
        "namingPrefix": "Les noms globaux sont accessibles avec un préfixe $ dans les templates : NoJS.global('foo', ...) devient $foo.",
        "namingNamespace": "Utilisez le nom de votre plugin comme namespace pour éviter les collisions : $analytics, $auth, $featureFlags.",
        "reservedTitle": "Noms réservés",
        "reservedText": "Les noms suivants ne peuvent pas être utilisés avec NoJS.global() :",
        "reservedProto": "Les vecteurs de pollution de prototype (__proto__, constructor, prototype) sont également bloqués.",
        "reactivityTitle": "Réactivité",
        "reactivityText": "Les valeurs objet passées à NoJS.global() sont automatiquement enveloppées dans un contexte réactif. Les mutations déclenchent des mises à jour du DOM comme les changements de store ou state.",
        "ownershipTitle": "Suivi de propriété",
        "ownershipText": "Quand un plugin enregistre une globale pendant sa phase install, il en devient le propriétaire. Si un autre plugin écrase cette globale, un avertissement est enregistré."
      },
      "sentinels": {
        "title": "Sentinelles d'intercepteur",
        "text": "Les intercepteurs de requête peuvent retourner des objets spéciaux avec des clés de Symbols sentinelle pour contrôler le pipeline de fetch.",
        "cancelTitle": "NoJS.CANCEL — Annuler la requête",
        "cancelText": "Retournez un objet avec [NoJS.CANCEL]: true pour empêcher l'envoi de la requête. Le fetch lance une AbortError.",
        "respondTitle": "NoJS.RESPOND — Servir une réponse en cache",
        "respondText": "Retournez un objet avec [NoJS.RESPOND]: data pour court-circuiter la requête et retourner data directement comme réponse. Aucune requête HTTP n'est effectuée.",
        "replaceTitle": "NoJS.REPLACE — Remplacer les données de réponse",
        "replaceText": "Retournez un objet avec [NoJS.REPLACE]: data depuis un intercepteur de réponse pour remplacer le corps de réponse analysé par des données personnalisées.",
        "summaryTitle": "Résumé",
        "thSentinel": "Sentinelle",
        "thUsedIn": "Utilisé dans",
        "thEffect": "Effet",
        "cancelUsedIn": "Intercepteur de requête",
        "respondUsedIn": "Intercepteur de requête",
        "replaceUsedIn": "Intercepteur de réponse",
        "cancelEffect": "Annule la requête (lance AbortError)",
        "respondEffect": "Retourne les données directement, ignore l'appel HTTP",
        "replaceEffect": "Remplace le corps de réponse analysé"
      },
      "trusted": {
        "title": "Intercepteurs de confiance",
        "text": "Par défaut, les intercepteurs reçoivent des copies expurgées des requêtes et réponses — les en-têtes sensibles et les paramètres URL sont supprimés ou remplacés par [REDACTED].",
        "fullAccess": "Les plugins d'authentification qui ont besoin d'accéder aux vrais en-têtes peuvent être installés avec { trusted: true }.",
        "callout": "Un avertissement en console est enregistré quand un plugin est installé avec un accès de confiance. N'accordez trusted qu'aux plugins que vous contrôlez ou avez audités.",
        "redactedTitle": "En-têtes expurgés",
        "requestLabel": "En-têtes de requête supprimés avant transmission aux intercepteurs non fiables :",
        "responseLabel": "En-têtes de réponse supprimés du proxy de réponse :"
      },
      "dispose": {
        "title": "NoJS.dispose()",
        "text": "Détruit toute l'application : dispose les plugins, vide les globales et supprime les intercepteurs.",
        "orderTitle": "Ordre de destruction",
        "orderStep1": "Les plugins sont détruits dans l'ordre inverse d'installation (le dernier installé est détruit en premier).",
        "orderStep2": "La fonction dispose de chaque plugin dispose d'un délai de 3 secondes. En cas de dépassement, une erreur est enregistrée et la destruction continue avec le plugin suivant.",
        "orderStep3": "Après la destruction de tous les plugins, les globales et les intercepteurs sont vidés.",
        "asyncTitle": "Dispose asynchrone",
        "asyncText": "Les fonctions dispose des plugins peuvent être asynchrones. Le framework attend chacune (sous réserve du délai de 3 secondes).",
        "callout": "Les plugins ne peuvent pas être installés pendant la destruction. Les appels à NoJS.use() pendant l'exécution de dispose() sont ignorés avec un avertissement."
      },
      "security": {
        "title": "Directives de sécurité",
        "text": "Lors de la création de plugins, suivez ces pratiques pour garder votre application sécurisée.",
        "namespaceTitle": "Namespacez tout",
        "namespaceText": "Préfixez les globales, stores et noms d'événements avec le nom de votre plugin pour éviter les collisions.",
        "evalTitle": "N'utilisez jamais eval ni Function",
        "evalText": "Les valeurs passées à NoJS.global() sont vérifiées pour les références dangereuses. eval et Function sont bloqués.",
        "cleanupTitle": "Nettoyez dans dispose",
        "cleanupText": "Fournissez toujours un hook dispose qui vide les intervalles, ferme les connexions et supprime les écouteurs d'événements.",
        "overwriteTitle": "Évitez de remplacer les globales des autres",
        "overwriteText": "Si votre plugin détecte qu'une globale appartient déjà à un autre plugin, envisagez d'utiliser un nom différent plutôt que de la remplacer silencieusement.",
        "validateTitle": "Validez les options",
        "validateText": "Vérifiez les options requises dans install et avertissez tôt."
      },
      "example": {
        "title": "Exemple complet",
        "text": "Un plugin d'analytics complet démontrant le cycle de vie du plugin, les globales, les intercepteurs et la destruction."
      }
    },
    "headManagement": {
      "hero": {
        "badge": "Guides",
        "title": "Gestion du Head",
        "subtitle": "Gérez le titre du document, la meta description, l'URL canonique et le JSON-LD depuis n'importe quel élément"
      },
      "intro": {
        "text": "No.JS vous permet de contrôler les métadonnées du <head> de manière déclarative depuis n'importe où dans votre markup. Utilisez des éléments cachés avec les attributs page-title, page-description, page-canonical et page-jsonld — ils sont évalués de manière réactive et mettent à jour le head du document automatiquement.",
        "routingTip": "Pour la gestion du head au niveau des routes, vous pouvez aussi utiliser <code>page-title</code> et <code>page-description</code> directement sur les balises <code>&lt;template route=\"...\"&gt;</code>. Voir <a href=\"/docs/routing#route-head-attributes\">Attributs Head des Routes</a>."
      },
      "placement": {
        "title": "Placement",
        "text": "Placez les éléments de gestion du head à l'intérieur de n'importe quel scope de state. Ils doivent avoir l'attribut hidden — ils ne sont jamais rendus visuellement.",
        "reactive": "Toutes les valeurs sont réactives — quand l'état sous-jacent change, le head du document est mis à jour immédiatement."
      },
      "pageTitle": {
        "title": "page-title",
        "text": "Définit document.title avec la valeur de l'expression évaluée.",
        "expression": "La valeur est une expression No.JS standard évaluée par rapport au contexte de l'élément."
      },
      "pageDescription": {
        "title": "page-description",
        "text": "Définit l'attribut content de la balise <meta name=\"description\">. Si la balise n'existe pas, elle est créée.",
        "duplicate": "Un seul page-description devrait être actif à la fois. Si plusieurs sont présents, le dernier traité l'emporte."
      },
      "pageCanonical": {
        "title": "page-canonical",
        "text": "Définit l'attribut href de la balise <link rel=\"canonical\">. Si la balise n'existe pas, elle est créée.",
        "existing": "Si un lien canonique existe déjà dans votre HTML, la valeur de l'attribut est mise à jour sur place."
      },
      "pageJsonld": {
        "title": "page-jsonld",
        "text": "Injecte un bloc <script type=\"application/ld+json\"> dans le head du document. Le texte interne de l'élément est utilisé comme payload JSON-LD.",
        "scriptNote": "Le contenu est inséré tel quel — assurez-vous que c'est du JSON valide. Utilisez l'interpolation <code>{variable}</code> pour injecter des valeurs dynamiques.",
        "marker": "La balise script générée est marquée avec <code>data-nojs-jsonld</code> pour pouvoir être identifiée et nettoyée au démontage.",
        "fullExampleTitle": "Exemple Complet de Page Produit"
      },
      "notes": {
        "title": "Notes et Cas Particuliers",
        "competingTitle": "Directives Multiples en Compétition",
        "competingText": "Si plusieurs éléments définissent le même attribut head (ex. deux éléments page-title), le dernier traité l'emporte. Quand un élément est démonté, sa contribution au head est rétablie à la valeur précédente.",
        "cleanupTitle": "Nettoyage au Démontage",
        "cleanupText": "Quand un élément avec une directive head est retiré du DOM (ex. à l'intérieur d'un bloc if ou lors d'un changement de route), sa contribution au head est automatiquement nettoyée.",
        "captureTitle": "Capture du Template JSON-LD",
        "captureText": "Le innerHTML de l'élément page-jsonld est capturé au moment du traitement. Si vous avez besoin de JSON-LD réactif, placez l'élément à l'intérieur d'un scope de state et utilisez l'interpolation {expression}."
      }
    }
  }
}
