Sanyanggae LogoSanyanggae

💻 Web & Front

Unicode et UTF-8 : quelle différence ? Texte coréen altéré, octets, longueur et normalisation

Comparez points de code, octets UTF-8 et longueur des chaînes JavaScript avec le coréen et les émojis. Distinguez mauvais décodage, polices, normalisation et conservation de l’original.

사냥개 (Sanyanggae)Publié Mis à jour 18 min de lecture

Cet article est une traduction de l’original coréen. Original coréen · Vérification de la traduction: Vérification par IA

Schéma explicatif à trois cases reliées : caractères et points de code, octets UTF-8, affichage et normalisation.
Schéma original distinguant numéros de caractères Unicode, octets UTF-8 stockés et règles d’affichage et de comparaison. Ce n’est ni une capture de panne réelle ni un résultat de mesure.

1. Le numéro d’un caractère n’est pas sa suite d’octets

Imaginez un document coréen exporté puis rouvert dont le titre devient une suite de signes étranges. La langue de son auteur n’a pas changé : les règles utilisées pour lire les nombres enregistrés peuvent être incohérentes. Unicode attribue des numéros, appelés points de code, aux caractères et symboles de nombreuses écritures. UTF-8 est un encodage qui représente les valeurs correspondantes par des suites d’octets. La syllabe coréenne ‘가’ possède par exemple le point de code U+AC00. La notation après U+ est hexadécimale ; elle ne désigne ni une position dans le fichier ni une adresse mémoire. Ce numéro ne fixe pas non plus la forme d’une police ou la position des pixels à l’écran. Un même point de code peut être dessiné différemment selon la police et la composition du texte. La prise en charge d’Unicode, l’enregistrement en UTF-8 et l’installation d’une police coréenne sont liés, mais se vérifient séparément. L’explication des UTF par Unicode aide à distinguer caractères et format de stockage.

En UTF-8, ‘가’ correspond aux trois octets hexadécimaux EA B0 80. La lettre latine A possède le point de code U+0041 et s’enregistre en un seul octet, 41. Un octet comprend huit bits : dans un même document, la longueur en octets peut donc varier d’un caractère à l’autre. L’extension .txt ou .csv ne permet pas non plus de connaître à elle seule l’encodage réel. UTF-8 représente chaque valeur scalaire Unicode valide par un à quatre octets. On ne peut donc pas calculer exactement la taille d’un fichier en multipliant seulement le nombre de caractères visibles. Cet exemple de syllabe coréenne ne se généralise pas à tous les signes coréens ni à toutes les langues. L’encodage transforme l’information textuelle en octets ; ce n’est ni une compression ni un chiffrement. Les informations propres au format, les sauts de ligne et la compression de transport peuvent modifier la taille totale d’une même phrase. Distinguer la conversion définie par le standard Encoding du WHATWG de la taille globale ou de la confidentialité aide à circonscrire le problème.

2. Octets, unités de code et caractères perçus

La propriété length d’une chaîne JavaScript compte des unités de code UTF-16. Elle ne donne ni le nombre d’octets d’un fichier UTF-8 ni nécessairement le nombre de caractères perçus par une personne. Le visage souriant ‘😀’ est un seul point de code, U+1F600, mais occupe deux unités UTF-16 : '😀'.length vaut 2. Son encodage UTF-8 F0 9F 98 80 occupe quatre octets. Le nombre décimal 128512 obtenu avec codePointAt(0) est le même numéro que U+1F600, pas une longueur ni une taille de fichier. ‘가’ représente, selon ces trois mesures, un point de code, une unité UTF-16 et trois octets UTF-8. Les résultats diffèrent parce que les unités diffèrent. La documentation MDN sur la longueur des chaînes précise l’unité de length. Cette valeur ne suffit pas davantage à déterminer le nombre réel d’octets utilisés en mémoire par un moteur JavaScript.

Compter les points de code un à un ne correspond pas toujours au nombre de caractères visibles. Un signe de base peut recevoir des marques combinatoires et plusieurs points de code peuvent former un seul émoji. Les règles Unicode de segmentation décrivent les limites des graphèmes étendus qui permettent de traiter ces ensembles. Même Array.from(text), qui parcourt une chaîne par points de code, ne compte pas automatiquement ces groupes comme une seule unité. Dans le navigateur, après vérification de la prise en charge, on peut envisager Intl.Segmenter avec granularity définie sur 'grapheme'. Un graphème étendu ne correspond toutefois pas forcément à un seul dessin de police, et les attentes d’édition varient selon la langue. Un champ de présentation doit afficher un comptage compréhensible, tandis que le stockage et les communications peuvent nécessiter une limite en octets distincte. Annoncer « 20 caractères » tout en tronquant seulement des unités UTF-16 risque de couper un émoji ou un caractère combiné au milieu.

3. Séparer stockage, transmission et affichage du texte altéré

Le texte peut devenir illisible lorsque l’encodage choisi à l’écriture diffère du décodage utilisé à la lecture. Interpréter un fichier UTF-8 avec un autre encodage peut afficher de mauvais caractères sans que le fichier ait été modifié. En HTML, il faut comparer le format réellement enregistré, le charset du Content-Type de la réponse HTTP et la déclaration meta charset au début du document. L’attribut HTML lang='ko' indique la langue du contenu ; il n’a pas le même rôle que charset, qui concerne l’interprétation des octets. Le guide du W3C sur les déclarations d’encodage explique qu’il faut non seulement déclarer UTF-8, mais réellement enregistrer le fichier dans cet encodage et harmoniser les indications du serveur et du document. Les marques d’encodage peuvent aussi modifier l’ordre de priorité : une balise meta ne remplace pas toujours toutes les autres informations. Si le résultat diffère entre ouverture locale et réception depuis un serveur, la transmission fournit un indice important.

Des carrés à l’écran peuvent provenir d’une police dépourvue des glyphes nécessaires plutôt que d’un mauvais décodage. Si les données sont correctes mais qu’aucune forme appropriée n’est disponible, changer l’encodage de sauvegarde ne résout pas le problème. Inversement, une autre police ne restaure pas le caractère initial si les données ont déjà été interprétées comme un autre caractère. Conservez une copie du fichier problématique, puis comparez l’encodage choisi dans l’éditeur, les déclarations HTTP, les caractères interprétés par le navigateur et la police employée. Ne concluez pas d’après le seul aspect visuel : vérifiez aussi les points de code présents lorsque vous copiez le même passage. Notez si un seul navigateur est affecté ou si d’autres éditeurs lisent également mal le fichier. Enregistrer à répétition jusqu’à obtenir un résultat plausible peut faire perdre les octets initiaux. Si toutes les déclarations sont correctes mais que certains articles seulement sont touchés, cherchez aussi à quelle étape leur texte a changé avant son insertion dans la base de données.

4. Vérifier les mêmes valeurs avec TextEncoder et TextDecoder

Commencez une expérience simple avec une courte chaîne créée pour cela. TextEncoder transforme une chaîne JavaScript en tableau d’octets UTF-8. Le résultat de new TextEncoder().encode('가') est EA B0 80 en hexadécimal, et la length du tableau vaut 3. Un outil de développement peut afficher les mêmes valeurs en décimal, 234, 176, 128 : les données ne changent pas, seule la base de numération diffère. Comparez ce résultat à la longueur ordinaire 1 de la chaîne pour voir la différence d’unité. new TextDecoder('utf-8').decode(bytes) redonne ‘가’, bytes désignant le tableau obtenu précédemment. L’encodeur génère UTF-8 ; ce n’est pas un convertisseur universel permettant de choisir n’importe quel ancien encodage de sortie. Pour examiner un vrai fichier, utilisez les octets originaux préservés. Réencoder en UTF-8 un texte déjà mal lu à l’écran ne prouve pas quels octets contenait le premier fichier.

En passant 'utf-8' et { fatal: true } au constructeur TextDecoder, on peut traiter les données invalides comme une erreur au lieu de les remplacer silencieusement. L’octet isolé FF n’est par exemple pas une entrée UTF-8 valide : son décodage échoue dans ce mode. Le mode de remplacement ordinaire peut insérer le caractère de remplacement U+FFFD à l’endroit problématique. L’absence d’erreur ne signifie cependant pas que l’encodage initial a été identifié ou que le contenu a été validé. Certaines suites d’octets sont acceptables dans plusieurs encodages ; un texte déjà transformé peut aussi avoir été réenregistré en UTF-8 valide. Ce test vérifie si le décodeur choisi accepte l’entrée. Il ne certifie pas automatiquement la provenance du document, sa correspondance avec l’original ou l’absence de modification pendant son parcours.

5. La normalisation harmonise des représentations équivalentes

Deux chaînes correctement décodées peuvent encore contenir des suites de points de code différentes. ‘가’ peut être représenté par U+AC00 seul ou par le jamo initial combinant U+1100 suivi de la voyelle U+1161. Ces deux dernières valeurs ne sont pas simplement les jamos de compatibilité usuels ‘ㄱ’ et ‘ㅏ’ juxtaposés : vérifiez les numéros. Les deux représentations se dessinent généralement comme la même syllabe, mais peuvent différer lors d’une comparaison brute de chaînes JavaScript. Appliquer NFC aux deux côtés conformément aux règles Unicode de normalisation permet de comparer sous une même forme cet exemple d’équivalence canonique. NFD utilise la décomposition canonique. Ici, la forme NFC occupe trois octets UTF-8 et les deux jamos de la forme NFD six : un aspect identique n’impose pas une même taille de stockage. Cette différence peut expliquer qu’un nom enregistré et une recherche apparemment identiques ne concordent pas. Elle est distincte d’un texte altéré par une mauvaise interprétation des octets.

Le choix de la forme de normalisation dépend lui aussi du but recherché. NFC traite l’équivalence canonique, tandis que NFKC inclut la décomposition de compatibilité et peut transformer plus largement des représentations comme les chiffres pleine chasse ou entourés d’un cercle. Cela peut servir à construire une valeur de comparaison pour la recherche, mais ces distinctions peuvent être importantes dans une notation mathématique ou un document original. Il est donc utile de conserver la source et de choisir explicitement la transformation appliquée à une copie de comparaison. La normalisation ne remplace ni la comparaison de casse, ni le tri propre à une langue, ni la recherche de mots de même prononciation. Transformer en masse des noms de fichiers ou identifiants de comptes existants peut faire converger des éléments distincts vers la même valeur ; examinez d’abord les règles de nommage et la gestion des collisions. Stocker tous les signes visuellement proches sous une même valeur n’est pas un objectif général de la normalisation.

6. Préserver l’original et documenter les transformations

Avant d’écraser le fichier initial, conservez ses octets et notez son parcours d’obtention. Essayez le décodage supposé sur une copie, puis vérifiez que le contenu lisible reste identique après un nouvel enregistrement. La marque initiale UTF-8 présentée dans l’explication du BOM par le W3C est EF BB BF et n’est pas obligatoire. UTF-8 ne nécessite pas de choisir un ordre des octets : cette marque n’a donc pas le même rôle que l’indication d’ordre dans UTF-16. Ajouter ou retirer un BOM se distingue aussi de la conversion des octets du texte vers un autre encodage. Si des valeurs illisibles ont déjà été remplacées par des caractères de remplacement ou des points d’interrogation puis sauvegardées, plusieurs valeurs initiales ont pu être confondues ; leur récupération automatique n’est pas garantie. On ne peut évaluer une éventuelle inversion qu’en connaissant l’encodage et l’historique des conversions. La normalisation ne restaure pas les octets perdus.

Pour une fonction multilingue, ne terminez pas les vérifications après une seule ligne anglaise. Constituez de courts exemples contenant syllabes coréennes, lettres latines avec signes combinatoires, émojis hors du plan de base et écritures de droite à gauche comme l’arabe ou l’hébreu ; comparez saisie, sauvegarde, relecture et exportation. À chaque étape, contrôlez l’aspect visuel, les points de code, la taille UTF-8 et l’unité de comptage définie par le produit. Le sens de lecture ne se règle pas en remplaçant UTF-8 par un autre encodage : les paramètres de langue et de direction de l’interface se vérifient séparément. Préservez aussi les textes existants lorsque vous modifiez une limite de saisie ou une comparaison de recherche, et expliquez le nouveau critère. Le compteur du navigateur et la limite d’acceptation du serveur doivent utiliser une unité cohérente ; si des limites distinctes sont nécessaires, affichez chacune clairement. Les nombres et exemples de code présentés ici illustrent de courtes chaînes et ne mesurent pas la panne d’un service particulier. Examiner successivement numéros, octets, unités des chaînes, affichage et comparaison permet de reproduire les changements à chaque frontière.

Sources et références

Cet article explique des informations issues de sources publiques. Citer un organisme n’implique ni sa relecture ni son approbation.

Éditeur et contact

Articles sur le même sujet