Vai al contenuto
OneKitly

Togliere gli accenti rompe la ricerca — finché non lo fai da entrambi i lati

Pubblicato il 09/07/2026 · 15 min di lettura · Strumenti testo e lingua

Daniel Okonkwo

Daniel OkonkwoSviluppatore front-end e redattore Tech presso OneKitly

Performance web · Formati di file

Verificato su 5 fonti

Vedi il profilo
In breve

Togliere i segni diacritici è un passaggio di normalizzazione, non una funzione di ricerca: aiuta solo se la funzione identica gira sull'indice e sulla query. Fai l'esperimento: sei query contro sei nomi. Senza normalizzazione, «cafe» trova Cafe Central e «café» trova Café Amélie: due risultati, ciascuno a metà giusto. Ripiega solo la query e «café» ora trova Cafe Central e manca Café Amélie, cioè proprio la corrispondenza esatta che prima otteneva, mentre «müller» scende da un risultato a zero. Ripiega solo l'indice e «café» non restituisce più nulla. Ripiega entrambi i lati e ogni grafia restituisce entrambi i caffè. Una normalizzazione unilaterale non ammorbidisce la corrispondenza; sposta soltanto il disallineamento altrove. Il ripiegamento in sé è NFD seguito dalla cancellazione dei segni combinanti: é è il punto di codice singolo U+00E9, diventa U+0065 U+0301 dopo la decomposizione, e cancellare U+0301 lascia e. Funziona per é, ö, å e ñ, e non fa assolutamente nulla per ø, ł, ß, œ e ı, che sono punti di codice singoli senza segno combinante e attraversano NFD intatti. Per quelli serve una tabella per lingua — ö tedesco verso oe, ß verso ss — oppure un Intl.Collator con sensibilità base, che avvicina Ørsted e Orsted senza cancellare nulla.

Ripiegare i segni diacritici è un passaggio di normalizzazione, e la normalizzazione funziona solo se la stessa funzione gira sull'indice e sulla query. NFC contro NFD con i punti di codice in chiaro, e le lettere — ø, ł, ß, œ, ı — che escono dal ripiegamento intatte.

La normalizzazione è una funzione, e deve girare da entrambi i lati

Ecco l'intero argomento in un esperimento. Prendi un indice di sei nomi — Café Amélie, Cafe Central, Zoë Müller, Zoe Miller, Łukasz Nowak, Ørsted Energi — e sei query: cafe, café, muller, müller, lukasz, orsted. Senza alcuna normalizzazione, cafe trova una voce e café ne trova un'altra, muller non trova nulla e müller trova Zoë Müller. Sei query, due risultati. È questo il problema che si vuole risolvere.

Ora applica il ripiegamento alla sola query, che è la modifica che si fa per prima perché la query è ciò che si controlla. Il risultato è peggiore, non migliore. La query café si ripiega in cafe e corrisponde a Cafe Central, mentre Café Amélie — la voce che un attimo prima trovava perfettamente — non corrisponde più affatto, perché l'indice conserva ancora la forma accentata. La query müller perde il suo unico risultato per la stessa ragione. Due risultati restano due risultati, ma non sono gli stessi due, e una delle perdite era una corrispondenza esatta.

Ripiega solo l'indice e accade l'immagine speculare: cafe trova ora entrambi i caffè, ma café non trova nulla, perché la query accentata non può più corrispondere all'indice ripiegato. Ripiega entrambi i lati e la tabella finalmente si comporta: le quattro grafie della query café restituiscono entrambe le voci, e le due grafie di Müller restituiscono l'unica voce. La regola che ne discende non è «togli gli accenti». È: qualunque funzione applichi, applica la stessa, in fase di indicizzazione e in fase di query, dallo stesso percorso di codice. Una normalizzazione applicata da un solo lato non è una versione più debole della cosa giusta: è una regola di corrispondenza diversa, e in genere peggiore.

NFC, NFD e che cos'è davvero il ripiegamento

Unicode consente di scrivere una lettera accentata in due modi. In forma composta, NFC, la lettera é è un unico punto di codice, U+00E9. In forma decomposta, NFD, sono due: U+0065, la e semplice, seguita da U+0301, l'accento acuto combinante. Entrambe appaiono identiche a schermo. Entrambe sono corrette. Non sono la stessa stringa: in JavaScript la é composta ha lunghezza 1 e la é decomposta lunghezza 2, e l'uguaglianza stretta fra le due è falsa. Una parola come café presa da una sorgente NFC e cercata in un documento NFD semplicemente non compare, e chi sviluppa vede una ricerca che fallisce su un testo che legge benissimo sulla pagina.

Il ripiegamento sfrutta proprio questo. Si normalizza in NFD, che scompone ogni lettera canonicamente decomponibile in una lettera base più i suoi segni, poi si cancella tutto ciò che appartiene alla categoria generale Unicode M: i segni combinanti. é diventa U+0065 U+0301 e quindi e. ñ diventa U+006E U+0303 e quindi n. å diventa U+0061 U+030A e quindi a. È tutto qui il meccanismo, ed è per questo che l'operazione si chiama propriamente ripiegamento e non rimozione: non si toglie nulla all'alfabeto, si scarta una distinzione.

Una conseguenza pratica: prima di ripiegare qualsiasi cosa, normalizza tutto a un'unica forma. Se il tuo indice è in NFC e il testo in entrata è in NFD, ripiegarli entrambi sulle stesse lettere base copre la differenza per caso, ma ogni confronto fatto prima del ripiegamento, o su un campo che hai scelto di non ripiegare, resterà sbagliato. Normalizza in NFC all'ingresso, come regola di archiviazione, e tratta il ripiegamento come un indice separato costruito sopra.

Le lettere che non portano alcun segno

Il meccanismo ha un modo di fallimento evidente: se una lettera non ha decomposizione canonica, NFD la lascia stare e non c'è alcun segno combinante da cancellare, quindi il ripiegamento non fa nulla. Eseguilo e vedi esattamente quali sono quelle lettere. ß resta ß. ø resta ø. ł resta ł. œ resta œ. æ resta æ. ı resta ı. đ resta đ. In ognuno di questi casi il segno diacritico non è un segno applicato a una lettera base; il tratto della l, la sbarra della o, il legamento fra la o e la e fanno parte del disegno della lettera, e Unicode codifica ciascuna come un carattere a pieno titolo.

Ecco perché l'esperimento dei sei nomi continua a non restituire nulla per lukasz e orsted anche dopo aver ripiegato entrambi i lati. Łukasz Nowak si ripiega in Łukasz Nowak, immutato; Ørsted Energi si ripiega in Ørsted Energi, immutato. Le due voci che un utente ha meno probabilità di digitare correttamente sono proprio le due su cui il ripiegamento non aiuta. Una parola come Łódź è il caso più chiaro: ripiegala e ottieni Łodz, perché ó e ź si decompongono e ł no, quindi il risultato non è né l'originale né la grafia ASCII che qualcuno cercherebbe.

La correzione è una piccola tabella esplicita applicata prima del passaggio NFD: ł verso l, ø verso oe oppure o, œ verso oe, æ verso ae, đ verso d, ß verso ss. La decomposizione di compatibilità, NFKD, risolve i legamenti œ e æ ma non le lettere sbarrate, quindi non è nemmeno lei una risposta generale. Non c'è modo di aggirare la domanda su quale lingua si stia indicizzando, ed è proprio di questo che parlano le tre sezioni seguenti.

Tedesco: oe, non o — e ss, non s

Le dieresi tedesche si decompongono, quindi il ripiegamento ingenuo produce qualcosa. Produce la cosa sbagliata. Größe si ripiega in Große: la dieresi sparisce, la esse dura resta, e il risultato è una vera parola tedesca che significa altro. Müller si ripiega in Muller, Öl in Ol. La convenzione che i lettori tedeschi si aspettano davvero, codificata nella DIN 5007 variante 2 e usata negli elenchi telefonici, è lo scioglimento in due lettere: ä verso ae, ö verso oe, ü verso ue, ß verso ss. Größe diventa Groesse, Müller diventa Mueller, Straße diventa Strasse.

La esse dura merita un paragrafo a sé, perché si comporta come nessun'altra lettera di questo elenco. Non ha decomposizione canonica, quindi NFD la lascia; anche NFKC la lascia, così Straße resta Straße. Ma il suo passaggio a maiuscolo in JavaScript dà la stringa di due caratteri SS, il che significa che una pipeline ingenua di «maiuscolo e poi confronto» la ripiega già correttamente, mentre un ripiegamento ingenuo degli accenti no. La forma capitale U+1E9E esiste e torna a ß in minuscolo. Se il tuo indice tedesco è costruito con il maiuscolo e la tua query togliendo gli accenti, ß corrisponderà in una direzione e non nell'altra: ancora il problema unilaterale, con un altro travestimento.

C'è un modo per farlo bene senza scrivere alcuna tabella. Un Intl.Collator per il tedesco con sensibilità base tratta già Grosse e Größe come uguali: è la collazione tedesca standard, in cui ß ha una differenza secondaria rispetto a ss. Chiedi la variante da elenco telefonico, la locale de-u-co-phonebk, e anche Groesse e Größe risultano uguali, perché quella collazione è proprio quella che tratta ö come oe. Il CLDR mantiene quelle tabelle; tu non devi.

Scandinavo e turco: lettere, non decorazioni

In danese, norvegese e svedese, æ, ø e å sono lettere dell'alfabeto, e vengono dopo la z. Un collator lo dimostra a schermo: ordina A, Aa, Å, Ø e Z con un collator danese e ottieni A, Z, Ø, Å, Aa; ordina le stesse cinque con un collator inglese e ottieni A, Å, Aa, Ø, Z. In danese, å non è stato discretamente archiviato accanto alla a: ha una posizione propria alla fine, e il digramma Aa si ordina insieme a lei. Ripiegare å in a non toglie quindi un accento, fonde due lettere diverse, e ripiegare ø in o ne fonde altre due.

Il turco offre il caso più netto, e riguarda le maiuscole più che gli accenti. Il turco distingue una ı senza punto, U+0131, da una i con il punto, e corrispondentemente una maiuscola con il punto İ, U+0130, dalla I normale. La minuscola di I è ı e la minuscola di İ è i, ma solo nella locale turca. Eseguilo in JavaScript e la trappola si vede: la stringa İSTANBUL messa in minuscolo con le regole predefinite è lunga nove caratteri, perché İ viene mappata su i seguita dal punto sovrascritto combinante U+0307. Messa in minuscolo con toLocaleLowerCase("tr") è lunga otto, il semplice istanbul che un utente digiterebbe. Un indice di ricerca costruito con il minuscolo predefinito non troverà mai quella query, e le due stringhe appaiono identiche a schermo.

Entrambi i casi puntano nella stessa direzione. Le lettere che rompono un ripiegamento ingenuo sono quelle che una lingua tratta come membri a pieno titolo del proprio alfabeto, e la trasformazione che una lingua si aspetta è una proprietà della lingua, non del carattere. È a questo che serve un argomento di locale, e passarlo non costa nulla.

Le parole che la tua stessa lingua non ti lascerà ripiegare

Il ripiegamento è distruttivo in un modo facile da dimostrare in qualunque lingua con segni diacritici. In italiano, è si ripiega in e: il verbo essere e la congiunzione diventano lo stesso token, e sono due delle parole più frequenti della lingua. Papà si ripiega in papa e però si ripiega in pero. Sono esattamente le collisioni che fanno sembrare rotta una lista di risultati a chi la legge.

Questo non è un argomento contro il ripiegamento. È un argomento a favore del conservare anche la forma non ripiegata. Lo schema che funziona sono due campi: conserva il testo originale esattamente come è stato scritto, normalizzato in NFC e nient'altro, e costruiscigli accanto un secondo campo ripiegato per la corrispondenza. Ordina le corrispondenze esatte sull'originale sopra quelle ripiegate, e il lettore che ha digitato l'accento ottiene per primo la voce che intendeva, mentre chi non l'ha digitato trova comunque qualcosa. Il ripiegamento come indice aggiuntivo è una funzionalità; il ripiegamento come sostituzione distruttiva dei tuoi dati è un bug che scoprirai più avanti.

Che cosa usare al posto di un ripiegamento scritto a mano

Per confrontare e ordinare, usa un collator invece di una trasformazione. Un Intl.Collator con sensibilità base dichiara cafe e café uguali, Muller e Müller uguali, Orsted e Ørsted uguali, e Lukasz e Łukasz uguali, comprese le quattro lettere che il ripiegamento NFD non riesce a toccare, perché le tabelle di collazione sanno che cosa sono quelle lettere. E lo fa senza produrre una stringa intermedia rovinata che poi va conservata da qualche parte.

Per gli slug degli URL, dove hai davvero bisogno di un output ASCII limitato, tieni pure il ripiegamento, ma guidalo prima con una mappa di lingua esplicita e poi con NFD, e controlla il risultato. Un generatore di slug è uno dei pochi posti in cui un ripiegamento distruttivo e irreversibile è corretto, perché uno slug non è un dato: è un'etichetta rigenerabile, ed è autorizzata a perdere distinzioni che il testo originale portava. Assicurati soltanto di applicare la mappa prima di NFD, altrimenti ł e ø attraverseranno lo slug e ne usciranno come caratteri inutilizzabili.

E qualunque cosa decidi, scrivila una volta sola. La causa più frequente del bug nel titolo non è un ripiegamento sbagliato: è un buon ripiegamento implementato due volte, una nell'indicizzatore e una nella casella di ricerca, da due persone, a sei mesi di distanza. Una funzione, esportata da un solo modulo, chiamata da entrambi i lati.

Nove lettere attraverso NFD e un ripiegamento ingenuo dei segni combinanti, eseguito in Node 22. Le cinque la cui colonna NFD è un unico punto di codice escono immutate: non c'è alcun segno da cancellare.
LetteraPunti di codice NFCPunti di codice NFDIl ripiegamento ingenuo dàChe cosa richiede la lingua
éU+00E9U+0065 U+0301ee va bene per la ricerca, ma fonde parole che la lingua tiene distinte
öU+00F6U+006F U+0308ooe in tedesco; o è accettabile in svedese e finlandese
ßU+00DFU+00DF (nessuna decomposizione)ß, immutatoss
øU+00F8U+00F8 (nessuna decomposizione)ø, immutatooe; è una lettera a sé, non una o decorata
åU+00E5U+0061 U+030Aaaa in danese e norvegese; una lettera distinta, ordinata dopo la z
ıU+0131U+0131 (nessuna decomposizione)ı, immutatoi per un indice in alfabeto latino, ma mai da confondere con i all'interno del turco
İU+0130U+0049 U+0307Ii, ma solo toLocaleLowerCase("tr") lo produce in un solo punto di codice
łU+0142U+0142 (nessuna decomposizione)ł, immutatol
œU+0153U+0153 (nessuna decomposizione)œ, immutatooe; NFKD lo darebbe, NFD no
Rimuovi accentiRimuovi accenti e segni diacritici dal testo: é diventa e, ü diventa u.Prova lo strumento

Domande frequenti

Devo salvare il testo ripiegato o ripiegare al volo?
Salvalo, come campo aggiuntivo, e mai come sostituzione. Ripiegare al volo significa ripiegare l'intero indice a ogni query, il che è lento, e rende fin troppo facile che un percorso di codice ripieghi e un altro se ne dimentichi. Un campo ripiegato salvato costa poco, è costruito una volta dalla stessa funzione che chiama la casella di ricerca, e lascia l'originale intatto per la corrispondenza esatta e per la visualizzazione. L'unica cosa da non fare è ripiegare sul posto: una volta sparita la forma accentata dal tuo database non puoi più mostrare il nome correttamente, e nessuna astuzia successiva la riporta indietro.
NFKD è meglio di NFD qui, dato che decompone anche i legamenti?
Risolve un problema e ne crea diversi. NFKD trasforma davvero œ in oe e fi in fi, che è proprio ciò che vuoi in un indice di ricerca. Ma la decomposizione di compatibilità riscrive anche gli apici come cifre normali, le lettere latine a larghezza piena come ASCII, il segno dell'ohm come un omega e vari caratteri di spaziatura come spazi semplici. In un campo di visualizzazione è distruttivo in modi che non hai chiesto. In un campo di corrispondenza è di solito accettabile e spesso utile. Quindi: NFC per l'archiviazione, NFKD come input di un campo di corrispondenza ripiegato se vuoi il comportamento sui legamenti, e una mappa esplicita per ø, ł e ß, che nessuna delle due forme risolverà.
Lo fa il database per me se scelgo la collazione giusta?
In larga misura sì, ed è di solito una risposta migliore del ripiegare nel codice applicativo. Una collazione insensibile agli accenti implementa la stessa idea del collator, al livello in cui il confronto avviene davvero, così indice e query sono confrontati sotto un'unica regola per costruzione. Due avvertenze. Primo, la collazione vale per colonna o per confronto, quindi una query che confronta una colonna collazionata con un'espressione che hai ripiegato tu torna al problema unilaterale. Secondo, le collazioni insensibili agli accenti dipendono dalla lingua esattamente come descritto sopra, quindi scegli quella che corrisponde ai tuoi contenuti e non un valore predefinito generico.
Perché due stringhe che sembrano identiche falliscono un test di uguaglianza?
Perché una è composta e l'altra decomposta. L'uguaglianza stretta confronta unità di codice, e una é composta è un'unità mentre una é decomposta ne è due, quindi il test fallisce anche se la resa è identica al pixel. È la sorpresa Unicode più frequente in una funzione di ricerca, ed è anche la più facile da correggere: normalizza entrambi gli operandi in NFC prima di confrontare. Nota che un confronto sensibile alla locale dichiara già le due equivalenti, il che è una buona diagnosi: se localeCompare restituisce zero e l'uguaglianza stretta restituisce falso, hai trovato un disallineamento di normalizzazione e non un errore nei dati.
È mai giusto ripiegare il nome di una persona?
Per la corrispondenza, sì. Per la visualizzazione, no. Una persona che si chiama Zoë Müller ha diritto a vedere il proprio nome scritto correttamente sullo schermo, sulla fattura e nell'email, e un sistema che conserva solo la forma ripiegata non può garantirlo, qualunque cosa faccia dopo. Ripiega verso una chiave di ricerca accanto al record, mai sopra di esso, e assicurati che ogni percorso di uscita legga il campo originale. È anche la ragione pratica per cui lo schema a due campi vince: rende il percorso di visualizzazione e quello di corrispondenza strutturalmente diversi, così nessuno può stampare per sbaglio la chiave di ricerca.

Articoli che potrebbero interessarti

Tutte le guide
SpiegazioneRilevare la lingua di un testo, e perché i testi brevi fallisconoMisurato, non affermato: 90 frasi brevi reali in sei lingue, nessuna rifiutata e 68 giuste — il 76%, che scende al 64% sotto le sedici lettere. Quattro delle risposte sbagliate sono tornate con il 100% di fiducia.SpiegazioneMaiuscolo di frase e maiuscolo di titolo: le regole cambiano con la linguaIl maiuscolo di titolo inglese ha tre soglie diverse a seconda del manuale di stile. Francese, spagnolo, portoghese e italiano non ne hanno nessuna. Il tedesco scrive maiuscolo ogni sostantivo. Lo strumento non sa nulla di tutto ciò: ecco esattamente cosa fa.SpiegazioneLe emoji sono più difficili di quanto sembri: perché «basta toglierle» non ha una risposta in una rigaUna emoji visibile può valere un punto di codice o quattordici unità UTF-16. Abbiamo lanciato tre espressioni regolari diffuse su una frase vera e ognuna si è rotta in modo diverso; una ha cancellato le cifre. Ecco perché, quale proprietà Unicode risponde a quale domanda, e la regola a gruppi di grafemi che funziona davvero.SpiegazioneChe cosa deve decidere un verificatore di palindromi prima di poter rispondereMaiuscole, spazi, punteggiatura e segni diacritici: quattro politiche, sei frasi reali, e la risposta cambia con ciascuna. Poi la metà più difficile — invertire una stringa non è di per sé definito, e l'inversione per unità di codice rompe le emoji e stacca gli accenti, dimostrato in Node.SpiegazioneGli hashtag sono un indice di ricerca, non un megafonoUn hashtag rende un post trovabile in una ricerca, che è un compito diverso dal farlo circolare. Modella la visibilità che un tag compra davvero e la risposta esce da sé: il valore di un tag è il suo numero di spettatori per post pubblicato, non il suo volume — quindi un tag molto popolare rende quasi nulla e uno specifico venticinque volte tanto.GuidaTag title, meta description, e che cosa ci fanno i motori di ricercaChe cosa dice la documentazione di Google sulla riscrittura dei titoli e sulla meta description, invece di quel che dice il folklore SEO. Poi la parte misurabile: i titoli vengono troncati per larghezza in pixel, quindi due titoli di esattamente sessanta caratteri possono renderizzarsi a 204,55 pixel di distanza e solo uno sopravvive.

Strumenti correlati

Fonti

Hai notato un errore in questo articolo?