Tutoriel RosaeNLG pour générer du français

This is the documentation for 3.2.0 version, which is not the latest version. Consider upgrading to 4.3.1.

Objectifs

Ce tutoriel donne un premier aperçu de RosaeNLG. Vous apprendrez à utiliser RosaeNLG pour générer des textes décrivant des téléphones OnePlus.

Il ne s’agit pas d’un tutoriel Pug - une connaissance basique de Pug est nécessaire en pré requis.

Les données des téléphones OnePlus proviennent de ce site.

Mise en place de l’environnement Node

Ce tutoriel fait du rendu côté serveur avec node.js. Mais vous pouvez aussi faire du rendu côté client, directement dans le navigateur.
L’éditeur intégré vous permettra d’exécuter aussi directement le tutoriel dans votre navigateur.

Vous pouvez passer cette partie si vous êtes familier avec l’environnement node.js.

  • installer node.js et npm

  • créer un répertoire tutoriel quelque part

  • npm init et accepter toutes les réponses proposées par défaut

  • npm install rosaenlg va télécharger RosaeNLG ; l’installation se terminera par + rosaenlg@x.x.x

  • créer un fichier tuto.js, et mettre console.log("hello NLG"); à l’intérieur

  • node tuto.js devrait générer hello NLG (ok ce n’est pas vraiment de la Natural Language Generation pour l’instant)

Données initiales

Les données initiales. Mettez-les dans le fichier tuto.js.

let phones = [
  {
    name: 'OnePlus 5T',
    colors: ['Black', 'Red', 'White'],
    displaySize: 6,
    screenRatio: 80.43,
    battery: 3300,
  },
  {
    name: 'OnePlus 5',
    colors: ['Gold', 'Gray'],
    displaySize: 5.5,
    screenRatio: 72.93,
    battery: 3300,
  },
  {
    name: 'OnePlus 3T',
    colors: ['Black', 'Gold', 'Gray'],
    displaySize: 5.5,
    screenRatio: 73.15,
    battery: 3400,
  },
];

Plomberie & premiers textes

Vous avez besoin de la librairie rosaenlg. Pour cela ajoutez la ligne suivante tout au début de votre fichier tuto.js :

const rosaenlgPug = require('rosaenlg');

Dans le même fichier, faites un rendu d’un template Pug (nous allons créer le template juste après) :

let res = rosaenlgPug.renderFile('tuto.pug', {
    language: 'fr_FR',
    phone: phones[0],
    cache: true
});
console.log( res );

Cela génèrera un rendu du template tuto.pug. Les paramètres sont les suivants :

  • choisir une langue (ici language: 'fr_FR') est obligatoire.

  • cache: true indique à Pug qu’il ne doit pas recompiler le template à chaque appel (en pratique l’exécution sera plus rapide).

  • vous pouvez organiser librement les autres propriétés ; ici nous avons simplement un propriété phone avec le premier téléphone de la liste.

Créez un fichier tuto.pug avec ce contenu:

p #{phone.name}

Ce 1er template est de la syntaxe Pug standard: nous affichons le nom du téléphone.

Quand vous faites un rendu du template (avec node tuto.js) vous devriez avoir:
OnePlus 5T

(ok, ce n’est toujours pas vraiment de la NLG pour l’instant)

Lister des éléments avec la structure eachz

Parlons des couleurs du téléphone: nous voulons générer _Les couleurs disponibles pour ce téléphone sont aaa, bbb and ccc.

Créez un mixin dédié au listage des couleurs (dans votre fichier tuto.pug):

mixin colors
  | les couleurs disponibles pour ce téléphone sont
  eachz color in phone.colors with { separator:',', last_separator:'et', end:'.' }
    | #{color}
  • eachz est une structure RosaeNLG, similaire à une boucle foreach avec des capacités de NLG.

  • { separator:',', last_separator:'and', end:'.' } indique à eachz que:

    • le séparateur standard est la virgule

    • et doit être utilisé entre les 2 dernières couleurs

    • il faut terminer avec un point

Appelez le mixin:

p #{phone.name} . #[+colors]

Lancez l’exécution. Le résultat doit être : OnePlus 5T. Les couleurs disponibles pour ce téléphone sont Black, Red et White.

Les espacements entre les mots ainsi que la mise en majuscule a été gérée automatiquement par RosaeNLG. Le nom de cette opération est "réalisation de surface" en NLG.

À présent nous faisons vraiment de la Natural Language Generation 🚀

Il est préférable d’avoir les noms de couleur en français. Définissez les correspondances:

  eachz color in phone.colors with { separator:',', last_separator:'et', end:'.' }
    -
      const colorMapping = {
        'Black': 'Noir',
        'Red': 'Rouge',
        'White': 'Blanc',
        'Gold': 'Or',
        'Gray': 'Gris'
      }
    | #{colorMapping[color]}

Le résultat doit être : OnePlus 5T. Les couleurs disponibles pour ce téléphone sont Noir, Rouge et Blanc..

Boucler sur tous les téléphones

Générons du texte pour chaque téléphone. Dans le fichier tuto.js:

let res = rosaenlgPug.renderFile('tuto.pug', {
    language: '{rosaenlg_lang}',
    phones: phones, // tous les téléphones
    cache: true
});
console.log( res );

Dans tuto.pug :

- let phone;
each phoneElt in phones
  - phone = phoneElt;
  p #{phone.name} . #[+colors]
Ici nous avons mis directement la boucle dans le template Pug. Il est préférable, pour des cas réels, de boucler en-dehors (directement dans le JavaScript appelant), car cela permet de faire une remise à zéro de RosaeNLG et de Pug entre chaque texte, ce qui est très nettement meilleur pour les performances.

Vous devriez obtenir:
OnePlus 5T. Les couleurs disponibles pour ce téléphone sont Noir, Rouge et Blanc.
OnePlus 5. Les couleurs disponibles pour ce téléphone sont Or et Gris.
OnePlus 3T. Les couleurs disponibles pour ce téléphone sont Noir, Or et Gris.

Synonymes simples

Les lecteurs ont tendance à préférer des textes non répétitifs. Ajoutons quelques synonymes simples: teintes et finitions sont des synonymes de couleurs dans ce contexte.

Modifiez votre mixin colors:

mixin colors
  | les #[+syn('couleurs', 'teintes', 'finitions')] disponibles pour ce téléphone sont
  ...

Lancez l’exécution à plusieurs reprises et vous aurez des résultats différents.

Plus de synonymes

Le mixin syn est parfait pour des mots ou des fragments de phrases. Mais à présent, nous allons créer des textes d’introduction, et nous voulons une certaine diversité.

Mettons toutes ces différentes introductions dans un mixin dédié:

mixin intro
  synz
    syn
      | le #{phone.name} est vraiment un super téléphone.
    syn
      | j'adore le nouveau #{phone.name}.
    syn
      | le #{phone.name} : un super téléphone !

La structure synz > syn liste les alternatives synonymiques. Vous pouvez mettre ce que vous voulez dans chaque alternative (dans chaque syn) : des conditions, d’autres synonymes etc.

Appelons ce nouveau mixin :

mixin printPhone
  | #[+intro] .
  | #[+colors] .

- let phone;
each phoneElt in phones
  - phone = phoneElt;
  p #[+printPhone]

Vous devriez avoir :
L’OnePlus 5T : un super téléphone ! Les couleurs disponibles pour ce téléphone sont Noir, Rouge et Blanc.
J’adore le nouveau OnePlus 5. Les finitions disponibles pour ce téléphone sont Or et Gris.
L’OnePlus 3T : un super téléphone ! Les finitions disponibles pour ce téléphone sont Noir, Or et Gris.

Les introductions sont choisies aléatoirement : il peut donc y avoir des répétitions d’un téléphone à un autre.

Néanmoins vous avez constaté que RosaeNLG a généré L’OnePlus, au lieu de Le OnePlus. RosaeNLG contient un mécanisme de contraction automatique (le arbre devient l’arbre etc.), mais qui se déclenche parfois de façon intempestive.

Protégez les noms de téléphone du mécanisme de contraction avec protect :

    syn
      | le
      protect
        | #{phone.name}
      | est vraiment un super téléphone.
    syn
      | j'adore le nouveau
      protect
        | #{phone.name}
      | .
    syn
      | le
      protect
        | #{phone.name}
      | : un super téléphone !

Vous devriez à présent bien avoir Le OnePlus 5T…​.

Lister des fragments de phrase

Parlons de l’écran : taille et % de couverture. Nous voulons générer il a un écran qui couvre 80.43 % de sa surface et fait 6 pouces.

Nous pourrions construire une grosse phrase avec plusieurs insertions de valeurs, mais le fait de structurer le code nous donnera plus de flexibilité.

Coupons notre grosse phrase en fragments, un pour chaque propriété :

mixin display
  itemz { separator:',', last_separator:'et' }
    item
      | couvre #[+value(phone.screenRatio)] % de sa surface
    item
      | fait #[+value(phone.displaySize)] pouces
  • value est un mixin qui va insérer la valeur dans le résultat, mais en respectant les conventions de formatage de la langue.

  • itemz > item ressemble à synz > syn, sauf qu’il ne va pas choisir une seule alternative, mais lister tous les éléments.

  • L’objet js après itemz indique à RosaeNLG comment assembler les éléments. Il est obligatoire. separator et last_separator fonctionnent de la même manière que dans la structure eachz.

N’oubliez pas d’appeler ce mixin :

mixin printPhone
  | #[+intro] .
  | #[+colors] .
  | #[+display] .

Le résultat est honorable, mais il manque le début du texte. Corrigeons cela :

mixin display
  itemz { begin_with_general: 'il a un écran qui', separator:',', last_separator:'et' }
    item
      | couvre #[+value(phone.screenRatio)] % de sa surface
    item
      | fait #[+value(phone.displaySize)] pouces

begin_with_general indique à RosaeNLG comment démarrer l’énumération. Il aurait été possible de le mettre en-dehors du mixing (juste au-dessus, avec | il a un écran qui), mais les définir à l’intérieur du mixin est une bonne pratique : par exemple, lorsque la liste est vide (s’il y a des conditions dans chaque item), RosaeNLG ne génèrera pas le contenu présent dans begin_with_general.

Les textes devraient être meilleurs :
Le OnePlus 5T est vraiment un super téléphone. Les finitions disponibles pour ce téléphone sont Noir, Rouge et Blanc. Il a un écran qui couvre 80,43 % de sa surface et fait 6 pouces.
Le OnePlus 5 est vraiment un super téléphone. Les teintes disponibles pour ce téléphone sont Or et Gris. Il a un écran qui couvre 72,93 % de sa surface et fait 5,5 pouces.
Le OnePlus 3T : un super téléphone ! Les finitions disponibles pour ce téléphone sont Noir, Or et Gris. Il a un écran qui couvre 73,15 % de sa surface et fait 5,5 pouces.

Ajoutons un peu de fantaisie en mélangeant les éléments avec le paramètre mix :

mixin display
  itemz { begin_with_general: 'il a un écran qui', separator:',', last_separator:'et', mix:true }
    item
      | couvre #[+value(phone.screenRatio)] % de sa surface
    item
      | fait #[+value(phone.displaySize)] pouces

Il a un écran qui couvre 80,43 % de sa surface et fait 6 pouces.
Il a un écran qui couvre 72,93 % de sa surface et fait 5,5 pouces.
Il a un écran qui fait 5,5 pouces et couvre 73,15 % de sa surface.

Encore plus de variété

AJoutez du texte pour parler de la batterie :

  | ce téléphone a une batterie de #[+value(phone.battery)] mAh.

Nous avons à présent un volume correct de texte. Mais nous aimerions avoir plus de variété : nous parlons toujours des couleurs, de l’écran puis de la batterie, dans cet ordre, alors que nous pourrions en parler dans n’importe quel ordre. Mettez tous les fragments de texte dans une structure itemz > item, et ajoutez un paramètre mix :

mixin phone_chunks
  itemz {separator: '.', end:'.', mix:true}
    item
      | #[+colors]
    item
      | #[+display]
    item
      | ce téléphone a une batterie de #[+value(phone.battery)] mAh.

mixin printPhone
  | #[+intro] .
  | #[+phone_chunks]

Expressions référentielles

Il y a une structure cachée dans la façon dont nous parlons du téléphone :

  • La 1re fois que l’on en parle, nous utilisons le nom du téléphone.

  • Les fois suivantes, nous utilisons soit ce téléphone, soit il.

En NLG (et en linguistique) ce concept est appelé expressions référentielles. La 1re fois que nous parlons de quelque chose, nous utilisons son mode de représentation représentant et les fois suivantes nous utilisons sa réprésentation sous forme d'expression référentielle. Demandons à RosaeNLG de gérer cela automatiquement.

Créez 2 mixins, un pour chaque type de représentant :

mixin phone_ref(obj, params)
  | le
  protect
    | #{phone.name}

mixin phone_refexpr(obj, params)
  | #[+syn('ce téléphone', 'il')]
Le 1er paramètre, obj, correspond à l’objet phone. {obj.name} correspond à {phone.name}.

Pour que cela fonctionne il faut les référencer:

- let phone;
each phoneElt in phones
  - phone = phoneElt;
  
  p
    -
      phone.ref = phone_ref;
      phone.refexpr = phone_refexpr;
    | #[+printPhone]
    deleteSaid('BATTERY')

Nous pouvons les utiliser partout (ou presque):

| #[+value(phone)] est vraiment un super téléphone.

| #[+value(phone)] : un super téléphone !

| #[+value(phone)] a une batterie de #[+value(phone.battery)] mAh.

| les #[+syn('couleurs', 'teintes', 'finitions')] disponibles pour #[+value(phone)] sont

Nous devons changer la structure de il a un écran qui car nous ne pouvons pas mettre un value directement dans la structure begin_with_general. Cela doit être soit une string, soit une référence à un mixin:

mixin itHasADisplay
  | #[+value(phone)] a un écran qui
...
  itemz { begin_with_general: itHasADisplay, separator:',', last_separator:'et', mix:true }

Voici là ce que vous devriez obtenir :
Le OnePlus 5T est vraiment un super téléphone. Il a une batterie de 3 300 mAh. Il a un écran qui couvre 80,43 % de sa surface et fait 6 pouces. Les teintes disponibles pour il sont Noir, Rouge et Blanc.
Le OnePlus 5 est vraiment un super téléphone. Les finitions disponibles pour ce téléphone sont Or et Gris. Ce téléphone a un écran qui couvre 72,93 % de sa surface et fait 5,5 pouces. Ce téléphone a une batterie de 3 300 mAh.
J’adore le nouveau OnePlus 3T. Les finitions disponibles pour le OnePlus 3T sont Noir, Or et Gris. Il a une batterie de 3 400 mAh. Ce téléphone a un écran qui fait 5,5 pouces et couvre 73,15 % de sa surface.

Le résultat est correct, mais il y a 2 problèmes :

  1. Les teintes disponibles pour il sont Noir…​ n’est pas correct. il ne doit pas être déclenché dans ce contexte.

  2. Nous n’avons pas pu remplacer j’adore le nouveau #{phone.name} : en effet le déterminant est avant l’adjectif. Ce n’est pas très grave, mais on peut avoir le texte suivant J’adore le nouveau OnePlus 5. Les couleurs disponibles pour le OnePlus 5OnePlus 5 est répété.

Il y a plusieurs façons plus ou moins sophistiquées de régler ces problèmes. Une approche directe consiste à utiliser des paramètres et des conditions.

Expressions référentielles conditionnées

Nous devons d’abord indiquer, à l’endroit où le il intempestif apparaît, qu’il n’est pas souhaité. Rajoutez un paramètre supplémentaire (un "flag") dans l’appel à value :

  | les #[+syn('couleurs', 'teintes', 'finitions')] disponibles pour
  | #[+value(phone, {'NOT_IL_ELLE':true})] sont

Puis :

  • utilisez ce paramètre dans le mixin de l’expression référentielle

  • utilisez une structure synz > syn au lieu de syn afin de pouvoir écrire la condition

mixin phone_refexpr(obj, params)
  synz
    syn
      | ce téléphone
    syn
      if !hasFlag(params, 'NOT_IL_ELLE')
        | il

Le il ne doit plus se déclencher de façon inopportune.

hasFlag est un raccourci syntaxique équivalent à params!=null && params['NOT_IL_ELLE']==true.
Lorsqu’un synonyme vide est déclenché (ce qui peut être le cas ici depuis que la condition a été ajoutée), RosaeNLG en choisira simplement un autre.

Sur j’adore le nouveau #{phone.name}, il s’agit d’éviter l’affichage du déterminant. Rajoutez encore un paramètre :

      | j'adore le nouveau #[+value(phone, {'NO_DET':true})].

et exploitez-le :

mixin phone_ref(obj, params)
  if !hasFlag(params,'NO_DET')
    | le
  protect
    | #{phone.name}
La ligne vide contenant simplement un pipe | sert à forcer l’ajout d’un espace. Si vous ne le mettez pas, vous aurez LeOnePlus. Il est difficile de prévoir à l’avance ces cas - rajoutez simplement un | dans une ligne vide lorsqu’ils arrivent.

Textes combinés et "has said"

Générons des textes plus sophistiqués en combinant, dans une même phrase, les informations sur l’écran et celles sur la batterie : Ce téléphone a un écran qui fait 6 pouces et couvre 80,43 % de sa surface, et dispose par ailleurs d’une batterie de 3 300 mAh.

C’est assez simple :

      | #[+display]
      | , et dispose par ailleurs d'une batterie de #[+value(phone.battery)] mAh

Le problème est que nous ne souhaitons pas parler deux fois de la batterie. Nous pourrions juste supprimer la phrase habituelle (Il a une batterie de 3 300 mAh), mais essayons plutôt de ne déclencher la phrase sur la batterie que si nous n’avons pas parlé de la batterie avant. Utilisons pour cela recordSaid et hasSaid.

    item
      | #[+display]
      
      if !hasSaid('BATTERY')
        | , et dispose par ailleurs d'une batterie de #[+value(phone.battery)] mAh
        recordSaid('BATTERY')
    item
      if !hasSaid('BATTERY')
        | #[+value(phone)] a une batterie de #[+value(phone.battery)] mAh
        recordSaid('BATTERY')

Le pattern hasSaid/recordSaid, utilisé 2 fois ici, est le suivant : si nous n’avons pas encore parlé de quelque chose :

  1. Nous en parlons

  2. Nous enregistrons le fait d’en avoir parlé

Vous devez utiliser ce mécanisme intégré et ne pas vous fier à des variables ou hashmaps spécifiques, car RosaeNLG va d’avant en arrière dans la génération du texte.
Il faut également faire un deleteSaid('BATTERY') dans la boucle principale, car on doit reparler de la batterie sur chacun des téléphones.

Vous devriez obtenir ces jolies phrases :
J’adore le nouveau OnePlus 5T. Ce téléphone a une batterie de 3 300 mAh. Il a un écran qui fait 6 pouces et couvre 80,43 % de sa surface. Les couleurs disponibles pour ce téléphone sont Noir, Rouge et Blanc.
Le OnePlus 5 : un super téléphone ! Ce téléphone a un écran qui fait 5,5 pouces et couvre 72,93 % de sa surface, et dispose par ailleurs d’une batterie de 3 300 mAh. Les couleurs disponibles pour ce téléphone sont Or et Gris.
Le OnePlus 3T : un super téléphone ! Les teintes disponibles pour ce téléphone sont Noir, Or et Gris. Il a un écran qui fait 5,5 pouces et couvre 73,15 % de sa surface, et dispose par ailleurs d’une batterie de 3 400 mAh.

Encore plus d’expressions référentielles

Nous arrivons à générer ce téléphone ou il comme expression référentielle. Essayons d’ajouter ce téléphone, le téléphone, cet appareil, cette machine.

Il est très facile d’ajouter des synonymes dans la liste:

    syn
      | ce téléphone
    syn
      | le téléphone
    syn
      | cet appareil
    syn
      | cette machine

Hélas, machine est féminin. Vous aurez des textes comme Cette machine a une batterie de 3 300 mAh. Il a un écran…​ qui ne sont pas corrects. Il faut être capables de suivre le changement de genre de l’expression référentielle.

Genre explicite

Une première méthode consiste à l’indiquer explicitement avec setRefGender. setRefGender indique à RosaeNLG le genre courant de l’objet :

    syn
      | cette machine
      - setRefGender(phone, 'F');
    syn
      | ce téléphone
      - setRefGender(phone, 'M');

Nous pouvons ensuite interroger le genre courant de l’expression référentielle avec getRefGender :

    syn
      if !hasFlag(params, 'NOT_IL_ELLE')
        if getRefGender(phone)=='M'
          | il
        else
          | elle

Nous devrions également expliciter le genre du représentant :

mixin phone_ref(obj, params)
  if !hasFlag(params,'NO_DET')
    | le
  protect
    | #{phone.name}
  - setRefGender(phone, 'M')

À présent les genres sont respectés : Les couleurs disponibles pour cette machine sont Noir, Rouge et Blanc. Elle a un écran qui couvre 80,43 % de sa surface et fait 6 pouces.

La structure getRefGender déclenchant il ou elle suivant le genre est très classique et il existe un raccourci :

      if !hasFlag(params, 'NOT_IL_ELLE')
        | #{getMorF(['il', 'elle'], phone)}

Dictionnaire pour le genre

Il est courant de faire des erreurs lorsqu’on indique le genre. Il est préférable de faire appel au dictionnaire intégré de RosaeNLG (dérivé du lefff) pour trouver le genre :

    syn
      | cette machine
      - setRefGender(phone, 'machine');
    syn
      | ce téléphone
      - setRefGender(phone, 'téléphone');

Il existe un raccourci syntaxique permettant :

  1. d’afficher un mot

  2. de rechercher son genre dans le dictionnaire

  3. d’enregistrer son genre courant

    syn
      | cette #[+value('machine', {represents: phone})]
    syn
      | ce #[+value('téléphone', {represents: phone})]

Déterminant automatique

Générons automatiquement le déterminant. ce/cette sont des pronoms démonstratifs, le/la sont des articles définis :

    syn
      | #[+value('téléphone', {represents: phone, det: 'DEMONSTRATIVE'})]
    syn
      | #[+value('téléphone', {represents: phone, det: 'DEFINITE'})]
    syn
      | #[+value('machine', {represents: phone, det: 'DEMONSTRATIVE'})]
    syn
      | #[+value('appareil', {represents: phone, det: 'DEMONSTRATIVE'})]

Ce n’est pas indispensable, mais cela permet ensuite de mutualiser facilement des alternatives grâce à syn_fct. syn_fct est une fonction qui renvoie aléatoirement un élément d’un tableau :

    syn
      | #[+value('téléphone', {represents: phone, det: syn_fct(['DEFINITE', 'DEMONSTRATIVE'])})]
RosaeNLG gère automatiquement les h aspirés : cet hebdomadaire / ce hérisson.

Syntaxe simplifiée

Il est peut être laborieux de devoir découper les groupes nominaux en déterminant, adjectif et nom. Utilisez la syntaxe simplifiée avec <…​> :

    syn
      | #[+value('<ce appareil>', {represents: phone})]
    syn
      | #[+value('<cette machine>', {represents: phone})]
    syn
      | #[+value('<ce téléphone>', {represents: phone})]
    syn
      | #[+value('<le téléphone>', {represents: phone})]

RosaeNLG gèrera automatiquement :

  • le choix du bon article

  • les accords en genre et en nombre : <des bon gâteaux P>des bons gâteaux

  • les contractions : <le arbre>l’arbre

  • le h muet : <la vieux homme>le vieil homme

Inutile de faire les accords avec la syntaxe <…​>. <ce appareil>, <cet appareil>, <cette appareil> sont équivalents.
La syntaxe simplifiée ne marche pas dans un navigateur car elle nécessiterait trop de ressources linguistiques embarquées.

Changer le mode de synonyme

Nous avons parfois ce type de résultat :
Les finitions disponibles pour ce téléphone fabuleux sont Noir, Or et Gris. Le téléphone a un écran …​

téléphone est répété ce qui n’est pas parfait. Au lieu de choisir les synonymes aléatoirement, déclenchons-les en séquence, ce qui évitera les répétitions proches :

mixin phone_re(obj, params)
  synz {mode:'sequence'}
    syn
      ...

Nous aurons moins de répétitions :
J’adore le nouveau OnePlus 3T. Ce téléphone a une batterie de 3 400 mAh. Les couleurs disponibles pour cette machine exceptionnelle sont Noir, Or et Gris. Cet appareil a un écran qui couvre 73,15 % de sa surface et fait 5,5 pouces.

Accord de l’adjectif

Ajoutons un adjectif lorsqu’on parle des couleurs : les couleurs disponibles pour ce téléphone exceptionnel sont…​.

exceptionnel doit être accordé avec le réprésentant : ce téléphone exceptionnel / cette machine exceptionnelle. Nous pourrions utiliser getRefGender et une condition, mais RosaeNLG sait accorder les adjectifs :

  | les #[+syn('couleurs', 'teintes', 'finitions')] disponibles pour
  | #[+value(phone, {'NOT_IL_ELLE':true})]
  | #[+agreeAdj('exceptionnel', phone)]
  | sont

Ajoutons de la variété :

  | #[+agreeAdj(['exceptionnel','fabuleux','singulier'], phone)]

Cela génèrera cette machine fabuleuse, cette machine singulière, etc.

Il n’est pas nécessaire d’utiliser syn_fct ici: agreeAdj accepte directement un tableau en paramètre, et choisira automatiquement un adjectif.
La syntaxe simplifiée fonctionne avec les adjectifs: <ce machine fabuleux> génèrera <cette machine fabuleuse>. Vous pouvez essayer d’intégrer des adjectifs directement dans phone_refexpr.

Félicitations !

Vous avez terminé le tutoriel.

Toutes mes félicitations ! 🎆

Encore plus

Nous avons vu quelques aspects de NLG à travers ce tutoriel. Il y a d’autres fonctionnalités que vous pouvez explorer, comme par exemple :

  • la gestion des possessifs

  • l’accord des verbes

  • l’affichage et le formatage des dates et des nombres

  • etc.

Version finale du code

tuto.js

const rosaenlgPug = require('rosaenlg');
let phones = [
  {
    name: 'OnePlus 5T',
    colors: ['Black', 'Red', 'White'],
    displaySize: 6,
    screenRatio: 80.43,
    battery: 3300,
  },
  {
    name: 'OnePlus 5',
    colors: ['Gold', 'Gray'],
    displaySize: 5.5,
    screenRatio: 72.93,
    battery: 3300,
  },
  {
    name: 'OnePlus 3T',
    colors: ['Black', 'Gold', 'Gray'],
    displaySize: 5.5,
    screenRatio: 73.15,
    battery: 3400,
  },
];
const res = rosaenlgPug.renderFile('tuto.pug', {
  language: 'fr_FR',
  phones: phones,
  cache: true,
});
console.log(res);

tuto.pug

//- Copyright 2019 Ludan Stoecklé
//- SPDX-License-Identifier: Apache-2.0

//- tag::displayMixin[]
mixin display
  itemz { begin_with_general: itHasADisplay, separator:',', last_separator:'et', mix:true }
    item
      | couvre #[+value(phone.screenRatio)] % de sa surface
    item
      | fait #[+value(phone.displaySize)] pouces
//- end::displayMixin[]

//- tag::mixinItHasADisplay[]
mixin itHasADisplay
  | #[+value(phone)] a un écran qui
//- end::mixinItHasADisplay[]

//- tag::colorsMixin[]
mixin colors
  | les #[+syn('couleurs', 'teintes', 'finitions')] disponibles pour
  | #[+value(phone, {'NOT_IL_ELLE':true})]
  | #[+agreeAdj(['exceptionnel','fabuleux','singulier'], phone)]
  | sont

  eachz color in phone.colors with { separator:',', last_separator:'et', end:'.' }
    -
      var colorMapping = {
        'Black': 'Noir',
        'Red': 'Rouge',
        'White': 'Blanc',
        'Gold': 'Or',
        'Gray': 'Gris'
      }
    | #{colorMapping[color]}
//- end::colorsMixin[]

//- tag::introMixin[]
mixin intro
  synz
    syn
      | #[+value(phone)] est vraiment un super téléphone.
    syn
      | j'adore le nouveau #[+value(phone, {'NO_DET':true})].
    syn
      | #[+value(phone)] : un super téléphone !
//- end::introMixin[]

mixin phone_chunks
  itemz {separator: '.', end:'.', mix:true}
    item
      | #[+colors]
    //- tag::hasSaid[]
    item
      | #[+display]
      
      if !hasSaid('BATTERY')
        | , et dispose par ailleurs d'une batterie de #[+value(phone.battery)] mAh
        recordSaid('BATTERY')
    item
      if !hasSaid('BATTERY')
        | #[+value(phone)] a une batterie de #[+value(phone.battery)] mAh
        recordSaid('BATTERY')
    //- end::hasSaid[]

mixin phone_ref(obj, params)
  if !hasFlag(params,'NO_DET')
    | le
    protect
    | #{phone.name}
  - setRefGender(phone, 'M')

mixin phone_refexpr(obj, params)
  synz {mode:'sequence'}
    syn
      | #[+value('<ce appareil>', {represents: phone})]
    syn
      | #[+value('<cette machine>', {represents: phone})]
    syn
      if !hasFlag(params, 'NOT_IL_ELLE')
        | #{getMorF(['il', 'elle'], phone)}
    syn
      | #[+value('<ce téléphone>', {represents: phone})]
    syn
      | #[+value('<le téléphone>', {represents: phone})]

//- tag::phoneMixin[]
mixin printPhone
  | #[+intro] .
  | #[+phone_chunks]
//- end::phoneMixin[]

//- tag::main[]
- let phone;
each phoneElt in phones
  - phone = phoneElt;
  
  p
    -
      phone.ref = phone_ref;
      phone.refexpr = phone_refexpr;
    | #[+printPhone]
    deleteSaid('BATTERY')
//- end::main[]