Nico Prat

Nico Prat

Gérer la typographie de la langue française

Éviter les sauts de lignes autour des ponctuations en français grâce aux regex


french-punctuation

En anglais, la majorité des ponctuations, comme le point d'interrogation ou d'exclamation, ne nécessitent pas d'espace avec le mot le précédent. D'ailleurs, je l'ai appris assez tard, bien après mon dernier cours d'anglais "scolaire". Si j'ai d'abord trouvé ça plutôt étonnant, ça a l'intérêt d'éviter qu'un "!" ne se retrouve sur la ligne suivante s'il manque de place. Plus facile pour les designers !

A mon avis, c'est un problème qui devrait être réglé au niveau du navigateur (comme les bandeaux d'acceptation des cookies ?), ou au moins au niveau de nos outils (aux dernières nouvelles, WordPress ne le gère pas nativement par exemple) ; mais dans la plupart des cas il va falloir régler le problème nous-même.

Regex à la rescousse#

En français, il existe une astuce mnémotechnique simple : les caractères de ponctuation constitués de deux "parties" nécessitent un espace avant ; Par exemple : ";", "!" ou "?". Voici une regex basique pour attraper ces caractères :

/\s+([:;»!?/])|([«])\s+/gu

Pour mieux comprendre comment elle fonctionne et tester quelques exemples : https://regex101.com/r/Op3iiK/1

Non breaking space#

Les plus anciens connaissent probablement encore certains caractères HTML par cœur, comme  , pour "non breaking space", ou "espace insécable" en français. Malheureusement la plupart des frameworks récents, comme Vue dans notre cas, n'interprêtent pas les caractères HTML lors du rendu pour des raisons de sécurité. Il existe cependant une alternative ASCII : \xA0. On peut donc utiliser notre regex en JavaScript pour remplacer les espaces "classiques" de cette façon :

`
Salut ! Ca va ?
« Comme un lundi » ; comme on dit...
`
.replace(
  /\s+([:;»!?/])|([«])\s+/gu,
  (match, left, right) => {
    if (left) {
      return `\xA0${left}`;
    }
    if (right) {
      return `${right}\xA0`;
    }
  },
);
/*
Salut ! Ca va ?
« Comme un lundi » ; comme on dit...
*/

Bonus 1 : automatiser#

Dans les cas les plus simples, on peut se permettre d'automatiser le remplacement du texte de manière un peu grossière via un MutationObserver : à chaque fois qu'un nœud de type texte est ajouté, on le passe à la moulinette de notre regex.

// Replace node content (nodeValue) only if of type text
const addNbspAroundPunctuation = (node: HTMLElement) => {
  if (node.nodeName !== '#text') {
    return;
  }
  node.nodeValue = node.nodeValue.replace(
    /\s+([:;»!?/])|([«])\s+/gu,
    (match, left, right) => {
      if (left) {
        return `\xA0${left}`;
      }
      if (right) {
        return `${right}\xA0`;
      }
    },
  );
};

// Apply the change for itself and its children
const recursiveAddNbspAroundPunctuation = (node: HTMLElement) => {
  addNbspAroundPunctuation(node);
  node.childNodes.forEach(recursiveAddNbspAroundPunctuation);
};

// Observer calls the function for each node added
const observer = new MutationObserver((mutations) => {
  mutations.forEach(({ addedNodes }) => {
    addedNodes.forEach(recursiveAddNbspAroundPunctuation);
  });
});

// Observe changes
observer.observe(document.body, { subtree: true, childList: true });

// Also run once first
recursiveAddNbspAroundPunctuation(document.body);

Les implications en terme de performances d'un tel mécanisme sont probablement négligeables dans la majorité des cas, mais dans des environnements modernes, on peut mieux faire.

Bonus 2 : exemple avec vue-i18n#

Dans notre cas, la quasi totalité des textes de notre application sont traduits et passent donc par la librairie vue-i18n : on peut donc régler le problème à priori, plutôt que de modifier le DOM après avoir été mis à jour grâce à la configuration postTranslation :

import { createI18n } from 'vue-i18n-composable';

const i18n = createI18n({
  postTranslation: (str) => addNbspAroundPunctuation(str),
});

On s'assure de cette façon que les textes seront toujours agréables à lire, sans forcer les traducteurs à gérer ces cas limites dans leurs propres outils.

Cette technique est appliquée sur ce blog donc vous pouvez la voir en action facilement !