Ir para o conteúdo
OneKitly

Retirar os acentos estraga a pesquisa — até o fazer dos dois lados

Publicado a 09/07/2026 · 16 min de leitura · Ferramentas de texto e idioma

Daniel Okonkwo

Daniel OkonkwoProgramador front-end e redator de Tecnologia na OneKitly

Desempenho web · Formatos de ficheiro

Verificado a partir de 5 fontes

Ver perfil
Em resumo

Retirar os diacríticos é um passo de normalização, não uma funcionalidade de pesquisa: só ajuda se a função idêntica correr sobre o índice e sobre a consulta. Faça a experiência: seis consultas contra seis nomes. Sem normalização, «cafe» encontra Cafe Central e «café» encontra Café Amélie: dois acertos, cada um a meio. Rebata só a consulta e «café» passa a encontrar Cafe Central e falha Café Amélie, a correspondência exata que antes obtinha, enquanto «müller» desce de um acerto para zero. Rebata só o índice e «café» não devolve nada. Rebata os dois lados e todas as grafias devolvem os dois cafés. Uma normalização unilateral não suaviza a correspondência; desloca o desencontro para outro sítio. O rebatimento em si é NFD seguido de apagar as marcas combinantes: é é o ponto de código único U+00E9, passa a U+0065 U+0301 depois da decomposição, e apagar U+0301 deixa e. Funciona para é, ö, å e ñ, e não faz absolutamente nada com ø, ł, ß, œ e ı, que são pontos de código únicos sem marca combinante e atravessam a NFD intactos. Esses exigem uma tabela por língua — ö alemão para oe, ß para ss — ou um Intl.Collator com sensibilidade base, que aproxima Ørsted de Orsted sem apagar nada.

Rebater os diacríticos é um passo de normalização, e a normalização só funciona quando a mesma função corre sobre o índice e sobre a consulta. NFC contra NFD com os pontos de código à vista, e as letras — ø, ł, ß, œ, ı — que saem do rebatimento intactas.

A normalização é uma função, e tem de correr dos dois lados

Aqui está todo o argumento numa experiência. Tome um índice de seis nomes — Café Amélie, Cafe Central, Zoë Müller, Zoe Miller, Łukasz Nowak, Ørsted Energi — e seis consultas: cafe, café, muller, müller, lukasz, orsted. Sem qualquer normalização, cafe encontra uma entrada e café encontra outra diferente, muller não encontra nada e müller encontra Zoë Müller. Seis consultas, dois acertos. É esse o problema que se pretende corrigir.

Aplique agora o rebatimento só à consulta, que é a mudança que se faz primeiro porque a consulta é o que se controla. O resultado é pior, não melhor. A consulta café rebate para cafe e corresponde a Cafe Central, enquanto Café Amélie — a entrada que encontrava perfeitamente há um instante — já não corresponde de todo, porque o índice continua a guardar a forma acentuada. A consulta müller perde o seu único acerto pela mesma razão. Dois acertos continuam a ser dois acertos, mas não são os mesmos dois, e uma das perdas era uma correspondência exata.

Rebata só o índice e acontece a imagem espelhada: cafe encontra agora os dois cafés, mas café não encontra nada, porque a consulta acentuada já não pode corresponder ao índice rebatido. Rebata os dois lados e a tabela finalmente comporta-se: as quatro grafias da consulta café devolvem as duas entradas, e as duas grafias de Müller devolvem a única entrada. A regra que isto estabelece não é «retire os acentos». É: seja qual for a função que aplicar, aplique a mesma, ao indexar e ao consultar, a partir do mesmo caminho de código. Uma normalização aplicada só de um lado não é uma versão suave do correto: é uma regra de correspondência diferente e em geral pior.

NFC, NFD e o que é realmente o rebatimento

O Unicode permite escrever uma letra acentuada de duas formas. Na forma composta, NFC, a letra é é um único ponto de código, U+00E9. Na forma decomposta, NFD, são dois: U+0065, o e simples, seguido de U+0301, o acento agudo combinante. Ambas se veem idênticas no ecrã. Ambas estão corretas. Não são a mesma cadeia: em JavaScript, o é composto tem comprimento 1 e o é decomposto comprimento 2, e a igualdade estrita entre ambos é falsa. Uma palavra como café tirada de uma fonte NFC e procurada num documento NFD simplesmente não aparece, e o programador vê uma pesquisa que falha sobre um texto que está a ler na página.

O rebatimento explora isto. Normaliza-se para NFD, o que separa toda a letra canonicamente decomponível numa letra base mais as suas marcas, e depois apaga-se tudo o que cai na categoria geral Unicode M: as marcas combinantes. é passa a U+0065 U+0301 e daí a e. ñ passa a U+006E U+0303 e daí a n. å passa a U+0061 U+030A e daí a a. É esse todo o mecanismo, e é por isso que a operação se chama com propriedade rebatimento e não remoção: não se tira nada do alfabeto, descarta-se uma distinção.

Uma consequência prática: antes de rebater seja o que for, normalize tudo para uma só forma. Se o seu índice está em NFC e o texto de entrada em NFD, rebater ambos para as mesmas letras base tapa a diferença por acidente, mas qualquer comparação feita antes do rebatimento, ou sobre um campo que decidiu não rebater, continuará errada. Normalize para NFC à entrada, como regra de armazenamento, e trate o rebatimento como um índice à parte construído por cima.

As letras que não levam marca nenhuma

O mecanismo tem um modo de falha evidente: se uma letra não tem decomposição canónica, a NFD deixa-a como está e não há marca combinante para apagar, por isso o rebatimento não faz nada. Execute-o e verá exatamente quais são essas letras. ß continua ß. ø continua ø. ł continua ł. œ continua œ. æ continua æ. ı continua ı. đ continua đ. Em cada um destes casos o diacrítico não é uma marca aplicada a uma letra base; o traço do l, a barra do o, a ligadura entre o o e o e fazem parte do desenho da letra, e o Unicode codifica cada uma como um caráter por direito próprio.

É por isso que a experiência dos seis nomes continua a não devolver nada para lukasz e orsted, mesmo depois de rebater os dois lados. Łukasz Nowak rebate para Łukasz Nowak, inalterado; Ørsted Energi rebate para Ørsted Energi, inalterado. As duas entradas que um utilizador tem menos probabilidade de escrever bem são precisamente as duas com que o rebatimento não ajuda. Uma palavra como Łódź é o caso mais claro: rebata-a e obtém Łodz, porque ó e ź se decompõem e ł não, pelo que o resultado não é nem o original nem a grafia ASCII que alguém procuraria.

A correção é um pequeno mapa explícito aplicado antes da passagem NFD: ł para l, ø para oe ou o, œ para oe, æ para ae, đ para d, ß para ss. A decomposição de compatibilidade, NFKD, resolve as ligaduras œ e æ mas não as letras com traço, por isso também não é uma resposta geral. Não há forma de contornar a pergunta sobre que língua se está a indexar, e é disso mesmo que tratam as três secções seguintes.

Alemão: oe, não o — e ss, não s

Os tremas alemães decompõem-se, por isso o rebatimento ingénuo produz alguma coisa. Produz a coisa errada. Größe rebate para Große: o trema desaparece, o s duro fica, e o resultado é uma palavra alemã real que significa outra coisa. Müller rebate para Muller, Öl para Ol. A convenção que os leitores alemães realmente esperam, codificada na DIN 5007 variante 2 e usada nas listas telefónicas, é o desdobramento em duas letras: ä para ae, ö para oe, ü para ue, ß para ss. Größe passa a Groesse, Müller a Mueller, Straße a Strasse.

O s duro merece o seu próprio parágrafo, porque se comporta como nenhuma outra letra desta lista. Não tem decomposição canónica, por isso a NFD deixa-o; a NFKC também o deixa, de modo que Straße continua Straße. Mas a sua passagem a maiúsculas em JavaScript dá a cadeia de dois caracteres SS, o que significa que um pipeline ingénuo de «maiúsculas e depois comparar» já o rebate bem, enquanto um rebatimento de acentos ingénuo não. A forma capital U+1E9E existe e volta a ß em minúsculas. Se o seu índice alemão for construído com maiúsculas e a sua consulta com remoção de acentos, ß corresponderá num sentido e não no outro: outra vez o problema unilateral, com outro disfarce.

Há uma forma de acertar sem escrever tabela nenhuma. Um Intl.Collator alemão com sensibilidade base já trata Grosse e Größe como iguais: é a colação alemã padrão, onde ß tem uma diferença secundária em relação a ss. Peça a variante de lista telefónica, a configuração regional de-u-co-phonebk, e Groesse e Größe também comparam como iguais, porque essa colação é precisamente a que trata ö como oe. O CLDR mantém essas tabelas; não tem de o fazer.

Escandinavo e turco: letras, não decorações

Em dinamarquês, norueguês e sueco, æ, ø e å são letras do alfabeto, e vêm depois do z. Um colador prova-o no ecrã: ordene A, Aa, Å, Ø e Z com um colador dinamarquês e obtém A, Z, Ø, Å, Aa; ordene essas mesmas cinco com um colador inglês e obtém A, Å, Aa, Ø, Z. Em dinamarquês, å não foi discretamente arrumado ao lado do a: tem a sua própria posição no fim, e o dígrafo Aa ordena-se com ele. Rebater å para a não retira, portanto, um acento: funde duas letras diferentes, e rebater ø para o funde outras duas.

O turco oferece o caso mais nítido, e é sobre maiúsculas e minúsculas, não sobre acentos. O turco distingue um ı sem ponto, U+0131, de um i com ponto, e correspondentemente uma maiúscula com ponto İ, U+0130, do I normal. A minúscula de I é ı e a minúscula de İ é i, mas apenas na configuração regional turca. Execute-o em JavaScript e a armadilha aparece: a cadeia İSTANBUL passada a minúsculas com as regras por omissão mede nove caracteres, porque İ se converte em i seguido do ponto sobrescrito combinante U+0307. Passada a minúsculas com toLocaleLowerCase("tr") mede oito, o istanbul simples que um utilizador escreveria. Um índice de pesquisa construído com a minúscula por omissão nunca encontrará essa consulta, e as duas cadeias veem-se idênticas no ecrã.

Ambos os casos apontam para o mesmo. As letras que estragam um rebatimento ingénuo são as que uma língua trata como membros de pleno direito do seu alfabeto, e a transformação que uma língua espera é uma propriedade da língua, não do caráter. É para isso que existe um argumento de configuração regional, e passá-lo não custa nada.

As palavras que a sua própria língua não o deixará rebater

O rebatimento é destrutivo de um modo fácil de provar em qualquer língua com diacríticos. Em português, avô e avó rebatem ambos para avo: o avô e a avó tornam-se o mesmo token, e a distinção que a língua marca com o acento desaparece por completo. Pêlo e pelo seguem o mesmo caminho. É exatamente o tipo de colisão que faz uma lista de resultados parecer avariada a quem a lê.

Isto não é um argumento contra o rebatimento. É um argumento a favor de conservar também a forma não rebatida. O padrão que funciona são dois campos: guarde o texto original tal como foi escrito, normalizado para NFC e mais nada, e construa ao lado um segundo campo rebatido para a correspondência. Ordene as correspondências exatas sobre o original acima das rebatidas, e o leitor que escreveu o acento obtém primeiro a entrada que queria, enquanto quem não o escreveu encontra na mesma alguma coisa. O rebatimento como índice adicional é uma funcionalidade; o rebatimento como substituição destrutiva dos seus dados é um erro de que só saberá mais tarde.

O que usar em vez de um rebatimento escrito à mão

Para comparar e ordenar, use um colador em vez de uma transformação. Um Intl.Collator com sensibilidade base declara cafe e café iguais, Muller e Müller iguais, Orsted e Ørsted iguais, e Lukasz e Łukasz iguais, incluindo as quatro letras que o rebatimento NFD não consegue tocar, porque as tabelas de colação sabem o que são essas letras. E fá-lo sem produzir uma cadeia intermédia estragada que depois tenha de ser guardada algures.

Para os slugs de URL, onde precisa mesmo de uma saída ASCII limitada, mantenha o rebatimento, mas conduza-o primeiro com um mapa de língua explícito e só depois com a NFD, e verifique o resultado. Um gerador de slugs é um dos poucos sítios onde um rebatimento destrutivo e irreversível é correto, porque um slug não é um dado: é uma etiqueta regenerável, e tem licença para perder distinções que o texto original carregava. Só garanta que o mapa é aplicado antes da NFD, ou ł e ø atravessarão o slug e sairão como caracteres inutilizáveis.

E seja qual for a sua decisão, escreva-a uma só vez. A causa mais frequente do erro do título não é um mau rebatimento: é um bom rebatimento implementado duas vezes, uma no indexador e outra na caixa de pesquisa, por duas pessoas, com seis meses de intervalo. Uma função, exportada de um só módulo, chamada dos dois lados.

Nove letras através da NFD e de um rebatimento ingénuo das marcas combinantes, executado no Node 22. As cinco cuja coluna NFD é um único ponto de código saem inalteradas: não há marca para apagar.
LetraPontos de código NFCPontos de código NFDO rebatimento ingénuo dáO que a língua exige
éU+00E9U+0065 U+0301ee serve para pesquisar, mas funde palavras que a língua distingue
öU+00F6U+006F U+0308ooe em alemão; o é aceitável em sueco e finlandês
ßU+00DFU+00DF (sem decomposição)ß, inalteradoss
øU+00F8U+00F8 (sem decomposição)ø, inalteradooe; é uma letra própria, não um o decorado
åU+00E5U+0061 U+030Aaaa em dinamarquês e norueguês; uma letra distinta, ordenada depois do z
ıU+0131U+0131 (sem decomposição)ı, inalteradoi para um índice em alfabeto latino, mas nunca confundi-la com i dentro do turco
İU+0130U+0049 U+0307Ii, mas só toLocaleLowerCase("tr") o produz num único ponto de código
łU+0142U+0142 (sem decomposição)ł, inalteradol
œU+0153U+0153 (sem decomposição)œ, inalteradooe; NFKD dá-lo-ia, NFD não
Remover acentosRemova acentos e diacríticos do texto: é passa a e e ü a u.Experimentar a ferramenta

Perguntas frequentes

Devo guardar o texto rebatido ou rebater na hora?
Guarde-o, como campo adicional, e nunca como substituição. Rebater na hora significa rebater todo o índice em cada consulta, o que é lento, e torna demasiado fácil que um caminho de código rebata e outro se esqueça. Um campo rebatido guardado é barato, é construído uma vez pela mesma função que a caixa de pesquisa chama, e deixa o original intacto para a correspondência exata e para exibição. A única coisa que não deve fazer é rebater no lugar: uma vez desaparecida a forma acentuada da sua base de dados, já não consegue mostrar o nome corretamente, e nenhuma esperteza posterior a traz de volta.
A NFKD é melhor do que a NFD aqui, já que também decompõe as ligaduras?
Resolve um problema e cria vários. A NFKD converte de facto œ em oe e fi em fi, que é exatamente o que se quer num índice de pesquisa. Mas a decomposição de compatibilidade também reescreve os expoentes como algarismos normais, as letras latinas de largura completa como ASCII, o sinal de ohm como um ómega e vários caracteres de espaçamento como espaços simples. Num campo de exibição isso é destrutivo de formas que não pediu. Num campo de correspondência costuma ser aceitável e muitas vezes útil. Portanto: NFC para armazenar, NFKD como uma entrada do campo rebatido de correspondência se quiser o comportamento com ligaduras, e um mapa explícito para ø, ł e ß, que nenhuma das formas vai corrigir.
A base de dados faz isto por mim se eu escolher a colação certa?
Em boa medida sim, e costuma ser melhor resposta do que rebater no código da aplicação. Uma colação insensível a acentos implementa a mesma ideia do colador, ao nível onde a comparação acontece de facto, por isso o índice e a consulta são comparados sob uma só regra por construção. Duas ressalvas. Primeira, a colação é por coluna ou por comparação, por isso uma consulta que compare uma coluna colacionada contra uma expressão que rebateu à mão volta ao problema unilateral. Segunda, as colações insensíveis a acentos dependem da língua exatamente como descrito acima, por isso escolha a que corresponde ao seu conteúdo e não um valor por omissão genérico.
Porque é que duas cadeias que parecem idênticas falham um teste de igualdade?
Porque uma está composta e a outra decomposta. A igualdade estrita compara unidades de código, e um é composto é uma unidade enquanto um é decomposto são duas, por isso o teste falha embora a renderização seja idêntica ao pixel. É a surpresa Unicode mais frequente numa funcionalidade de pesquisa, e também a mais fácil de corrigir: normalize os dois operandos para NFC antes de comparar. Note que uma comparação sensível à configuração regional já declara as duas equivalentes, o que é um bom diagnóstico: se localeCompare devolver zero e a igualdade estrita devolver falso, encontrou um desencontro de normalização e não um erro de dados.
Alguma vez é correto rebater o nome de uma pessoa?
Para a correspondência, sim. Para exibição, não. Alguém chamada Zoë Müller tem direito a ver o seu nome bem escrito no ecrã, na fatura e no email, e um sistema que só guarda a forma rebatida não o consegue garantir faça o que fizer depois. Rebata para uma chave de pesquisa ao lado do registo, nunca por cima, e garanta que todos os caminhos de saída leem o campo original. É também esta a razão prática pela qual o padrão de dois campos vence: torna o caminho de exibição e o de correspondência estruturalmente diferentes, para que ninguém possa imprimir a chave de pesquisa por acidente.

Artigos que podem interessar-lhe

Todos os guias
ExplicaçãoDetetar o idioma de um texto e porque os textos curtos falhamMedido, não afirmado: 90 frases curtas reais em seis idiomas, nenhuma recusada e 68 certas — 76%, caindo para 64% abaixo de dezasseis letras. Quatro das respostas erradas voltaram com 100% de confiança.ExplicaçãoMaiúscula de frase e maiúscula de título: as regras mudam com a línguaAs maiúsculas de título inglesas têm três cortes diferentes consoante o manual de estilo. O português, o francês, o espanhol e o italiano não têm nenhum. O alemão capitaliza todos os substantivos. A ferramenta não sabe nada disto — eis exatamente o que faz.ExplicaçãoOs emoji são mais difíceis do que parecem: porque «basta retirá-los» não tem resposta numa linhaUm emoji visível pode valer um ponto de código ou catorze unidades UTF-16. Corremos três expressões regulares populares sobre uma frase real e cada uma falhou de maneira diferente; uma apagou os algarismos. Eis porquê, que propriedade Unicode responde a que pergunta, e a regra de grupos de grafemas que funciona mesmo.ExplicaçãoO que um verificador de palíndromos tem de decidir antes de poder responderCaixa, espaços, pontuação e diacríticos: quatro políticas, seis frases reais, e a resposta muda com cada uma. Depois a metade mais difícil — inverter uma cadeia não está definido em si, e a inversão por unidade de código parte os emojis e desprende os acentos, demonstrado no Node.ExplicaçãoAs hashtags são um índice de pesquisa, não um megafoneUma hashtag torna uma publicação localizável numa consulta, o que é uma função diferente de a fazer espalhar-se. Modele a visibilidade que uma etiqueta compra realmente e a resposta cai sozinha: o valor de uma etiqueta é o seu número de espectadores por publicação publicada, não o seu volume — por isso uma etiqueta muito popular não devolve quase nada e uma específica devolve vinte e cinco vezes mais.GuiaEtiquetas title, meta descrições, e o que os motores fazem com elasO que a própria documentação da Google diz sobre reescrever títulos e sobre a meta descrição, em vez do que diz o folclore de SEO. Depois a parte mensurável: os títulos são truncados por largura em píxeis, pelo que dois títulos de exatamente sessenta caracteres podem ser desenhados com 204,55 píxeis de diferença e só um sobrevive.

Ferramentas relacionadas

Fontes

Detetaste um erro neste artigo?