Le WordPress d'aujourd'hui, décodé pour les développeurs

Multilingue

Emoji dans un texte source : quand il casse un générateur de slug traduit

Un script maison de génération de slug traduit s'effondre sur les émojis et les caractères combinants : diagnostic pas à pas.

Par WordPress Développement • 2 mai 2024 • 5 min de lecture • Aucun commentaire
Emoji dans un texte source : quand il casse un générateur de slug traduit

Pourquoi un titre d’article parfaitement lisible à l’écran produit-il, une fois traduit et transformé en URL, un slug qui se termine par un caractère invalide de type %EF%BF%BD ? C’est la question posée par un rédacteur qui venait de publier un article intitulé « Notre nouvelle gamme 🌱🌍 arrive » en anglais, avant de constater que la version traduite automatiquement générait un slug tronqué en plein milieu d’un émoji.

Le script de génération de slug maison — écrit avant que l’équipe ne songe sérieusement aux emoji dans les titres — fonctionnait très bien depuis des mois, jusqu’à ce jour où le titre traduit associait deux émojis composés côte à côte dans une position proche de la limite de troncature.

Symptôme : un slug tronqué au mauvais endroit

Le script réduisait chaque titre à une longueur maximale de soixante caractères avant de le passer à sanitize_title(), via une simple fonction PHP mb_substr(). Le problème : mb_substr() compte les caractères Unicode, mais certains émojis modernes ne sont pas un seul caractère — ce sont des séquences de plusieurs points de code assemblés par un caractère joker invisible (le zero width joiner).

function generate_translated_slug( $title, $max_length = 60 ) {
    $truncated = mb_substr( $title, 0, $max_length );
    return sanitize_title( $truncated );
}

Quand la troncature tombait pile au milieu d’une séquence d’émoji composé (par exemple un drapeau représenté par deux caractères régionaux assemblés), sanitize_title() recevait un fragment Unicode invalide en fin de chaîne. Sa gestion interne des caractères multi-octets, pensée pour du texte normal, transformait alors ce fragment en caractère de remplacement, visible dans l’URL finale sous forme d’un artefact d’encodage.

L'essentiel à retenir : sanitize_title() gère mal certains émojis multi-points de code ; Les caractères combinants cassent une troncature naïve par octet ; Une normalisation Unicode préalable règle le problème à la racine

Diagnostic : isoler la séquence fautive

Pour confirmer l’hypothèse, on a inspecté le titre problématique caractère par caractère avec mb_str_split(), en forçant l’encodage UTF-8 :

$chars = mb_str_split( $title, 1, 'UTF-8' );
foreach ( $chars as $i => $char ) {
    echo $i . ': ' . bin2hex( $char ) . "\n";
}

Cette inspection a révélé que l’émoji en position soixante (juste à la limite de troncature) faisait en réalité partie d’une séquence de trois points de code Unicode consécutifs, dont seul le premier était inclus dans la chaîne tronquée à soixante caractères — les deux octets suivants du deuxième point de code manquaient, produisant une séquence UTF-8 invalide.

Correctif : normaliser avant de tronquer, tronquer sur une frontière sûre

La solution consiste à ne jamais tronquer une chaîne contenant des émojis sur une longueur de caractères Unicode brute, mais à utiliser la classe Normalizer de l’extension intl (disponible nativement en PHP depuis longtemps) pour recomposer la chaîne, puis à s’appuyer sur preg_split() avec le flag u pour découper sur des frontières de graphèmes plutôt que de simples points de code :

function generate_translated_slug_safe( $title, $max_length = 60 ) {
    if ( class_exists( 'Normalizer' ) ) {
        $title = Normalizer::normalize( $title, Normalizer::FORM_C );
    }

    // Retire les émojis et caractères combinants avant génération du slug,
    // plutôt que de risquer une troncature au milieu d'une séquence.
    $title = preg_replace( '/[\x{1F300}-\x{1FAFF}\x{2600}-\x{27BF}]+/u', '', $title );

    $truncated = mb_substr( trim( $title ), 0, $max_length );
    return sanitize_title( $truncated );
}

Cette version retire simplement les émojis avant la génération du slug plutôt que de tenter une troncature « intelligente » au milieu d’une séquence composée — un choix pragmatique qui évite la complexité d’une détection de frontière de graphème parfaite, rarement nécessaire pour un usage d’URL.

Pourquoi sanitize_title() ne suffit pas seul

La fonction native sanitize_title() retire déjà beaucoup de caractères non alphanumériques, mais elle agit après la troncature dans ce script, pas avant. Or l’ordre des opérations compte énormément ici : retirer les émojis avant de tronquer élimine le risque à la racine, alors que les retirer après une troncature déjà invalide ne répare rien, la séquence est déjà cassée en amont.

  • Toujours normaliser la chaîne source avant toute opération de longueur
  • Retirer ou isoler les émojis avant la troncature, jamais après
  • Tester systématiquement avec un titre contenant des émojis composés multi-points de code, pas seulement des émojis simples

Prévention : un test unitaire dédié

Ce genre de bug, invisible tant qu’aucun titre ne tombe pile sur la limite de troncature, mérite un test unitaire explicite plutôt qu’une simple relecture visuelle :

public function test_slug_avec_emoji_compose() {
    $titre = str_repeat( 'a', 58 ) . '🏳️‍🌈 fin';
    $slug  = generate_translated_slug_safe( $titre );

    $this->assertTrue( mb_check_encoding( $slug, 'UTF-8' ) );
}

Un émoji n’est presque jamais un seul caractère : c’est une séquence, et toute fonction qui tronque du texte doit soit la connaître, soit s’en débarrasser avant de compter.

En résumé

Le bug tenait en une phrase : compter des caractères Unicode n’est pas la même chose que compter des graphèmes visuellement complets, et les émojis modernes en sont l’exemple le plus visible. Normaliser la chaîne, retirer les émojis avant toute troncature, et tester explicitement avec des séquences composées suffit à éviter ce genre de slug cassé, qui aurait pu passer inaperçu bien plus longtemps sur un site à faible volume de publication.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Partager :

À propos de l'auteur

WordPress Développement

Développeur WordPress, passionné par Elementor, le FSE et l’automatisation par IA.

Voir tous ses articles

Dans la même veine

À lire aussi