Regex - 13 - Unicode et regex

Gerer les emojis, accents et caractères CJK dans les regex avec les flags u et v, les propriétés Unicode et les grapheme clusters.

13 - Unicode et regex

Ce que tu vas apprendre

  • Pourquoi [a-zA-Z] ne suffit pas dans un monde multilingue
  • Le flag u et son impact sur le comportement des regex
  • Les propriétés Unicode \p{Letter}, \p{Number}, \p{Emoji}
  • Le flag v et les opérations ensemblistes
  • Ce qu'est un grapheme cluster et pourquoi ca compte

Prerequisites


J'ai passe des annees a écrire [a-zA-Z] pour valider des noms. Et puis un jour, un utilisateur s'appelait "Renee" (avec un accent). Un autre "Muller" (avec un u trema). Un troisieme avait un nom en chinois. Ma regex rejetait tout le monde sauf les anglophones. Ce jour-la, j'ai compris que les regex et Unicode, c'est un sujet qu'on ne peut pas ignorer.

Le problème

JavaScript utilise UTF-16 en interne. Un caractère "simple" comme A occupe une unité de code (16 bits). Mais un emoji comme un drapeau en occupe quatre (deux paires de substitution). Sans precaution, les regex traitent chaque unité de code séparément.

javascript// Sans flag u : le chaos
const emoji = "salut";
console.log(/^.{5}$/.test(emoji));  // true, 5 caracteres

const withEmoji = "hey\u{1F600}!";
console.log(withEmoji.length);      // 6 (pas 5 !)
console.log(/^.{5}$/.test(withEmoji)); // false... ou true selon le contexte

Le problème ne se limite pas aux emojis :

  • Caractères accentues : "e" et "e avec accent aigu" peuvent etre representes de deux facons (caractère precompose ou caractère + diacritique combinant)
  • CJK (chinois, japonais, coreen) : des milliers de caractères hors du plan basique
  • Alphabets non latins : arabe, devanagari, cyrillique, grec...
  • Symboles mathematiques, musicaux, etc.

Le flag u : traiter les regex en mode Unicode

Le flag u change le comportement du moteur regex de JavaScript. Il indique que le pattern et la chaîne doivent etre traites comme des sequences de code points Unicode, pas comme des unités de code UTF-16.

javascript// Sans u : le point ne matche pas un emoji entier
/^.$/.test("\u{1F600}");   // false

// Avec u : le point matche un code point complet
/^.$/u.test("\u{1F600}");  // true

Ce que u change concrètement :

  • Le . matche un code point complet (y compris les caractères supplementaires)
  • \u{XXXXX} permet d'écrire des code points au-delà de \uFFFF
  • Les classes de caractères traitent les paires de substitution correctement
  • Les erreurs de syntaxe sont plus strictes (plus de tolerances silencieuses)
javascript// Syntaxe \u{} disponible avec le flag u
const regex = /\u{1F4A9}/u;
regex.test("pile of poo emoji ici");

// Sans u, \u{1F4A9} est interprete differemment

Mon conseil : mets systématiquement le flag u sur toutes tes regex. Il n'y a aucune bonne raison de traiter du texte en mode UTF-16 brut en 2026.

Propriétés Unicode : \p{} et \P{}

Avec le flag u, tu accedes aux propriétés Unicode via \p{Propriete} (matche) et \P{Propriete} (ne matche pas, negation).

Les categories generales

javascript// \p{Letter} : toute lettre, dans n'importe quel script
/^\p{Letter}+$/u.test("cafe");       // true
/^\p{Letter}+$/u.test("Muller");     // true (u trema inclus)
/^\p{Letter}+$/u.test("Tokyo");      // true (si ecrit en kanji)

// \p{Number} : tout chiffre, pas juste 0-9
/\p{Number}/u.test("3");     // true (chiffre arabe)

// \p{Emoji} : les emojis
/\p{Emoji}/u.test("hello");           // false

Les scripts

Tu peux cibler un alphabet spécifique :

javascript// Uniquement des caracteres latins
/^\p{Script=Latin}+$/u.test("Bonjour");  // true
/^\p{Script=Latin}+$/u.test("Privet");   // false (cyrillique)

// Uniquement du grec
/^\p{Script=Greek}+$/u.test("alpha");    // false (latin)

// Uniquement du han (caracteres chinois/kanji)
/^\p{Script=Han}+$/u.test("kanji");      // true (si en caracteres han)

Combinaisons courantes

javascript// Un nom qui accepte les lettres, espaces et tirets
const nomRegex = /^[\p{Letter}\s\-]+$/u;
nomRegex.test("Jean-Pierre");    // true
nomRegex.test("O'Brien");        // false (apostrophe non incluse)

// Texte "alphabetique" dans n'importe quelle langue
const texteRegex = /^[\p{Letter}\p{Mark}\p{Number}\s]+$/u;

\p{Mark} matche les diacritiques combinants (les accents qui se combinent avec la lettre précédente). C'est souvent nécessaire pour gerer correctement les caractères accentues sous leur forme décomposée.

Pourquoi [a-zA-Z] ne suffit pas

Regarde ce tableau et tu comprendras :

Pattern "cafe" "Muller" "résumé" "Renee"
[a-zA-Z]+ Non Non Non Non
[a-zA-Z\u00C0-\u024F]+ Oui Oui Oui Oui
\p{Letter}+ (avec u) Oui Oui Oui Oui

La deuxieme option est un hack : tu codes en dur une plage de code points qui couvre les caractères accentues latins courants. Ca marche pour le français et l'allemand, mais ca exclut le grec, l'arabe, le chinois...

\p{Letter} est la seule solution universelle. Et elle est lisible.

Le flag v : opérations ensemblistes

Le flag v est arrive avec ES2024. Il remplace et etend le flag u. Avec v, tu peux faire des opérations ensemblistes dans les classes de caractères.

javascript// Difference : lettres SAUF les voyelles
/[\p{Letter}--[aeiouAEIOU]]/v

// Intersection : emojis qui sont aussi des symboles
/[\p{Emoji}&&\p{Symbol}]/v

// Imbrication de classes
/[[\p{Script=Latin}]&&[\p{Letter}]]/v

La syntaxe :

  • [A--B] : A moins B (différence)
  • [A&&B] : A inter B (intersection)
javascript// Exemple concret : lettres latines sans les chiffres romains
const sansRomains = /[\p{Script=Latin}--\p{Number}]/v;

// Caracteres d'ecriture (lettres + marks) sauf les emojis
const textePur = /[[\p{Letter}\p{Mark}]--\p{Emoji}]/v;

Le flag v est supporte dans Chrome 112+, Firefox 116+, Safari 17+, et Node 20+. En 2026, tu peux l'utiliser sans trop de soucis, mais vérifié ton environnement cible.

Grapheme clusters : le piège ultime

Un grapheme cluster est ce qu'un humain percoit comme "un caractère". Mais en Unicode, ca peut etre compose de plusieurs code points.

Exemples :

  • Un emoji drapeau = 2 code points (indicateurs de region)
  • Un emoji famille = jusqu'a 7 code points lies par des ZWJ (Zero Width Joiner)
  • "e accent aigu" en forme décomposée = 2 code points (e + accent combinant)
javascript// L'emoji drapeau francais
const drapeau = "\u{1F1EB}\u{1F1F7}";
console.log(drapeau.length);          // 4 (unites UTF-16)
console.log([...drapeau].length);     // 2 (code points)
// Visuellement : 1 caractere

// Segmenter par graphemes
const segmenter = new Intl.Segmenter("fr", { granularity: "grapheme" });
const graphemes = [...segmenter.segment(drapeau)];
console.log(graphemes.length);        // 1 (grapheme cluster)

Les regex, meme avec le flag u, travaillent au niveau des code points, pas des grapheme clusters. Pour compter ou manipuler des "caractères visuels", Intl.Segmenter est souvent un meilleur choix que les regex.

Tu peux approfondir le sujet Unicode et internationalisation sur paltemps.fr.

Résumé

  • Le flag u est indispensable pour traiter correctement les caractères hors ASCII
  • \p{Letter} remplace avantageusement [a-zA-Z] dans un contexte multilingue
  • \p{Script=Latin}, \p{Emoji}, \p{Number} ciblent des categories spécifiques
  • \P{} (majuscule) est la negation de \p{}
  • Le flag v ajoute les opérations ensemblistes (--, &&) dans les classes
  • Les grapheme clusters sont ce qu'un humain voit, les code points sont ce que la regex traite
  • Intl.Segmenter est ton allie pour les opérations au niveau des graphemes

Article précédent : 12 - Les outils du quotidien Article suivant : 14 - Performance et sécurité

Sources

Réservez un audit gratuit de 30 minutes. Je vous montre concrètement ce qu'on peut automatiser.