Aller au contenu

Qu'est-ce qui compte pour un caractère ?

Quatre systèmes différents comptent le même texte de quatre façons différentes. Pour un texte en anglais, ils sont d'accord — c'est pourquoi le problème reste invisible jusqu'au jour où ce n'est plus le cas.

Dernière relecture :

Un emoji, quatre réponses

Prenez un pouce levé avec une couleur de peau, ou l'emoji d'une famille. Comptez-le de quatre façons et vous obtenez quatre nombres, tous justes.

Un lecteur voit un caractère. Une seule chose, un seul appui sur la touche Retour arrière pour l'effacer.

Une expression régulière peut en voir sept. L'emoji est composé de plusieurs points de code reliés par des liants invisibles — un emoji de base, un modificateur, un liant, un autre emoji, et ainsi de suite.

JavaScript en annonce onze. Il stocke le texte en unités de 16 bits, et tout ce qui sort de la plage de base en occupe deux : chacun de ces points de code peut donc compter double.

Une colonne de base de données voit vingt-cinq octets. Encodé en UTF-8 pour le stockage, chaque point de code occupe jusqu'à quatre octets.

Pour un texte ordinaire en anglais, les quatre nombres sont identiques. C'est précisément pourquoi le problème reste invisible jusqu'à ce que quelqu'un saisisse un nom accentué, une phrase en japonais ou un simple emoji — et qu'un champ qui fonctionnait depuis trois ans se mette à tronquer des données.

Quel nombre votre limite mesure

La vraie question n'est jamais « quelle est la longueur de ce texte ». C'est « qu'est-ce qui impose la limite », et il y a quatre réponses courantes.

Une colonne de base de données déclarée en VARCHAR compte des octets dans la plupart des moteurs. C'est pourquoi un champ qui accepte sans peine 255 caractères anglais refuse bien moins de caractères japonais — trois octets chacun — et pourquoi un nom accentué ne rentre parfois pas alors que sa version sans accent passait.

Un SMS fait 160 caractères dans son propre alphabet à sept bits, et tombe à 70 par message dès qu'un seul caractère sort de cet alphabet. Un seul guillemet typographique collé depuis un traitement de texte, ou un seul emoji, peut transformer un message en une partie en message en trois parties, et tripler ce qu'on vous facture.

Un validateur de formulaire en JavaScript compte presque toujours en unités UTF-16 : c'est pourquoi un champ annoncé à 280 caractères peut refuser une chaîne qui paraît bien plus courte quand elle est pleine d'emoji.

Une personne compte ce qu'elle voit, et c'est la seule définition qui compte pour un titre, une balise title ou tout ce qui a un budget visuel.

Là où les comptes divergent

Savoir quelles saisies posent problème est plus utile que connaître la théorie, car ces saisies sont prévisibles.

Les emoji, surtout les emoji composés — couleurs de peau, familles, drapeaux, métiers. Un drapeau, ce sont deux lettres indicatrices régionales ; un métier, c'est généralement une personne, un liant et un objet.

Les caractères accentués et combinants. Un é peut s'écrire en un seul point de code ou comme un e suivi d'un accent aigu combinant. Les deux se ressemblent trait pour trait, mais ne se trient pas pareil, ne sont pas considérés comme égaux et ne se comptent pas pareil — et les deux formes se rencontrent dans les données réelles, souvent dans la même colonne.

Les écritures non latines. Les caractères chinois, japonais et coréens occupent trois octets chacun en UTF-8. L'hindi, le thaï, le tamoul et l'arabe forment des groupes qu'un lecteur perçoit comme une seule unité, mais qui s'étendent sur plusieurs points de code.

Les symboles mathématiques et musicaux, qui se trouvent hors de la plage de base et comptent donc double en UTF-16.

Que faire

Comptez ce que votre limite compte. Un compteur qui affiche les quatre nombres répond directement à la question, plus vite qu'un raisonnement. Dans le code, utilisez la fonction adaptée : Intl.Segmenter en JavaScript compte ce que voit un lecteur, len(s.encode("utf-8")) en Python compte les octets, et en PHP, mb_strlen plutôt que strlen.

Corrigez la colonne plutôt que le formulaire. Si la contrainte vient d'un champ de base de données, la solution durable se situe en amont : déclarez-le en utf8mb4 dans MySQL ou en text dans PostgreSQL. L'ancien « utf8 » de MySQL, c'est bien connu, se limite à trois octets par caractère et ne peut pas stocker le moindre emoji — un piège de longue date qui a tronqué sans bruit énormément de données réelles. Compter soigneusement au niveau du formulaire n'est qu'un contournement pour une colonne qui aurait dû être déclarée autrement.

Normalisez à l'entrée. Convertir le texte dans une forme normale unique au moment de la saisie fait que les deux écritures de é n'en font plus qu'une, et comparaisons, tris et comptages se mettent enfin d'accord. Tous les langages ont une fonction pour cela, et le faire une fois à l'entrée est bien plus simple que de gérer les deux formes partout.

Un cas voisin : le Base64

À mentionner, car il provoque le même genre de surprise. Encoder des données en Base64 prend trois octets à la fois et les écrit sous forme de quatre caractères : le résultat est donc environ un tiers plus volumineux — c'est de l'arithmétique, pas un manque d'efficacité. Une pièce jointe de 6 MB devient environ 8 MB de texte, ce qui explique qu'un fichier nettement sous une limite de taille puisse être refusé une fois encodé pour la transmission.

Le même calcul explique pourquoi des pièces jointes d'e-mail sont rejetées à des limites qu'elles semblent respecter. Si vous dimensionnez quelque chose par rapport à un plafond, mesurez la forme encodée.

Questions fréquentes

Pourquoi mon champ de 255 caractères refuse-t-il un texte plus court ?

Presque certainement parce qu'il compte des octets et non des caractères. Les lettres accentuées occupent deux octets en UTF-8 et les caractères CJK trois : 255 octets peuvent donc correspondre à 85 caractères japonais. Déclarer la colonne en utf8mb4 ou en text règle le problème proprement.

Un emoji peut-il vraiment me coûter trois SMS ?

Oui, c'est possible. Un message composé uniquement de caractères de l'alphabet GSM a droit à 160 caractères par partie ; un seul caractère hors de cet alphabet fait basculer tout le message dans un encodage à 70 caractères. Un message de 150 caractères avec un emoji fait donc trois parties au lieu d'une.

Quel compte utiliser pour une balise title ?

Aucun avec précision : les moteurs de recherche tronquent selon la largeur en pixels, pas selon le nombre de caractères, si bien qu'un titre en majuscules larges est coupé plus tôt qu'un titre en minuscules étroites. Environ 60 caractères est la règle empirique habituelle, et c'est un repère plutôt qu'une limite.

Pourquoi deux chaînes d'apparence identique ne correspondent-elles pas ?

Généralement parce qu'un caractère accentué est écrit de deux façons différentes — en un seul point de code dans l'une, et en une lettre de base suivie d'un signe combinant dans l'autre. Normaliser les deux dans la même forme avant de les comparer les fait correspondre, et mérite d'être fait au moment où les données entrent dans votre système.

Un nombre de mots est-il plus stable qu'un nombre de caractères ?

Pour l'anglais, en grande partie. Pas partout : le chinois et le japonais ne séparent pas les mots par des espaces, le thaï non plus, si bien que compter les mots dans ces écritures exige une segmentation, et que différents outils donnent des réponses différentes.