lowercase.fr
fonctionnalitéstarifsà proposnouveautésblog
se connecter

← blog

toLowerCase() et strings.ToLower ne sont pas d'accord

· l'équipe lowercase.fr

technique
unicode
javascript
go

mettre un texte en minuscules a l'air trivial. en javascript, c'est text.toLowerCase(). en go, strings.ToLower(text). même mot, même intention.

sauf que les deux ne renvoient pas toujours la même chose. et si votre front calcule quelque chose en javascript que votre back recalcule en go (une clé de cache, un identifiant normalisé, une signature, un dédoublonnage), cette différence devient un bug.

trois divergences

entrée toLowerCase() (javascript) strings.ToLower (go)
ΟΔΟΣ οδος (sigma final ς) οδοσ
İ i̇ (2 caractères : i + point combinant) i
Ᲊ (U+1C89) ᲊ sur un node récent Ᲊ, inchangé

1. le sigma final

le grec a deux minuscules pour sigma : σ au milieu d'un mot, ς à la fin. javascript applique la correspondance complète d'unicode, qui tient compte du contexte : un Σ en fin de mot devient ς. go applique la correspondance simple : un caractère donne toujours le même caractère, quel que soit son voisinage. Σ devient donc σ, partout.

2. le i turc avec point

İ (U+0130) n'a pas de minuscule en un seul caractère dans la correspondance complète : javascript renvoie i suivi de U+0307, le point combinant. la chaîne grandit d'un caractère. go, encore en correspondance simple, renvoie i. même longueur, pas de caractère combinant.

3. la version d'unicode

unicode ajoute régulièrement des lettres. unicode 16 et 17 ont introduit 55 nouvelles majuscules : l'alphabet garay (unicode 16), le béria erfé (unicode 17), quelques lettres latines étendues et une lettre cyrillique. toLowerCase() dépend des tables unicode de votre moteur javascript : node 24.15, qui embarque unicode 17, connaît ces lettres et les met en minuscules ; un node plus ancien en laisse tout ou partie telles quelles. go 1.26 embarque les tables d'unicode 15 et les laisse aussi.

conséquence peu connue : toLowerCase() peut renvoyer un résultat différent selon la version de node ou du navigateur. le même code, deux résultats.

comment nous l'avons mesuré

nous ne voulions pas une liste « de mémoire ». nous avons comparé les deux moteurs sur les 1 112 064 points de code unicode valides (tout l'espace unicode, moins les surrogates), chacun seul puis au milieu d'un mot, entre go 1.26.2 et node 24.15. résultat : en dehors des trois cas ci-dessus, zéro différence. nous raconterons la méthode en détail dans un prochain billet.

quand utiliser quoi

soyons honnêtes : dans la plupart des cas, toLowerCase() suffit. pour afficher un texte en minuscules, comparer sans tenir compte de la casse dans un seul langage, normaliser une saisie : utilisez la fonction native. une dépendance de plus n'est jamais gratuite.

la parité compte quand un même texte est mis en minuscules des deux côtés et que les résultats doivent être identiques au caractère près :

  • une clé ou un identifiant calculé côté client et vérifié par un backend go ;
  • un hash ou une signature sur du texte normalisé ;
  • des tests qui comparent la sortie d'un front javascript à celle d'une api go ;
  • un résultat qui ne doit pas changer d'une version de node à l'autre.

c'est exactement notre cas : l'api de lowercase.fr tourne en go, et nous voulions qu'un développeur javascript obtienne localement le même résultat que l'api.

notre solution : @lowercase-fr/tolower

@lowercase-fr/tolower fait strings.ToLower en javascript :

npm install @lowercase-fr/tolower
import { tolower } from '@lowercase-fr/tolower'

tolower('ΟΔΟΣ İSTANBUL') // 'οδοσ istanbul', comme go
'ΟΔΟΣ İSTANBUL'.toLowerCase() // 'οδος i̇stanbul'

le paquet ne fait rien de magique. il traite d'abord İ et Σ comme go, laisse intactes les 55 majuscules qu'unicode 15 ne connaît pas, puis délègue le reste à toLowerCase(). il pèse moins de 3 ko compressé (son code tient en moins de 2 ko), n'a aucune dépendance et aucun script d'installation. un test côté go échoue le jour où go met à jour ses tables unicode : c'est notre signal pour réduire la liste.

à noter : ni toLowerCase() ni strings.ToLower ne gèrent les règles propres à une langue, comme le turc où I devient ı. pour ça, c'est toLocaleLowerCase('tr') en javascript et unicode.TurkishCase en go. c'est un autre sujet.

en résumé

  • toLowerCase() et strings.ToLower divergent sur le sigma final, sur İ et sur les lettres récentes d'unicode ;
  • toLowerCase() dépend de la version d'unicode de votre moteur javascript ;
  • si vous n'avez besoin que de javascript, gardez la fonction native ;
  • si votre javascript doit être d'accord avec un backend go, @lowercase-fr/tolower est fait pour ça.