Acentuação quebrada

João, informação, Preço. Cole o texto e receba o original de volta — com o par de encoding identificado e o conserto na origem.

Roda no seu navegador · nada é enviado para servidor

Cole o texto quebrado

Por que o ç vira ç

Não é corrupção nem perda de dado. É tradução com o dicionário errado, e entender uma linha resolve o assunto para sempre.

Em UTF-8, os caracteres acima do inglês básico ocupam mais de um byte. O ç é gravado como dois bytes: C3 e A7.

Já o Latin-1 é uma tabela de um byte por caractere. Quando ele lê aqueles mesmos dois bytes, não vê um ç — vê dois caracteres separados: C3 é à e A7 é §. Daí ç virar ç.

É por isso que o lixo tem sempre a mesma cara. à e  aparecem em quase todo mojibake porque C3 e C2 são os primeiros bytes da maioria dos acentos latinos.

OriginalBytes em UTF-8Lido como Latin-1
çC3 A7ç
ãC3 A3ã
áC3 A1á
éC3 A9é
óC3 B3ó
espaço fixoC2 A0Â

A boa notícia e a má

A boa: nesse caso nada se perdeu. Os bytes continuam lá, só foram interpretados errado — dá para desfazer sem perder um acento sequer. É o que a ferramenta acima faz.

A má: existe um caso em que se perde, e ele tem cara própria. Se o texto tiver (losango com interrogação) ou ? no lugar dos acentos, os bytes originais foram substituídos e não há como recuperar — só voltando ao backup ou à fonte.

Por isso, ao consertar banco de dados: faça backup antes. Uma conversão errada transforma o caso recuperável no caso irrecuperável, e aí não tem ferramenta que resolva.

Onde consertar de verdade

Consertar o texto é remédio; consertar a origem é cura. São quatro lugares onde a tradução pode dar errado, e basta um estar fora do lugar:

1. A conexão com o banco — o suspeito número um

Este é o mais comum e o mais enganoso: a coluna está certa, o texto que chegou está certo, e mesmo assim sai torto — porque a conexão não declarou o charset e o servidor assumiu latin1.

-- confira o que a sua conexão está usando
SHOW VARIABLES LIKE 'character_set_client';
SHOW VARIABLES LIKE 'character_set_connection';
# PHP (PDO)
new PDO($dsn, $u, $p, [PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4"]);
# ou direto na DSN:  mysql:host=...;dbname=...;charset=utf8mb4

# Node (mysql2)
createPool({ ..., charset: 'utf8mb4' })

2. A coluna do banco

Use utf8mb4, nunca utf8. Apesar do nome, o utf8 do MySQL é incompleto: guarda no máximo três bytes por caractere e não aguenta emoji, que precisam de quatro. O sintoma é o emoji sumir ou virar ???? enquanto os acentos funcionam.

SELECT table_name, column_name, character_set_name
FROM information_schema.columns
WHERE table_schema = DATABASE() AND character_set_name IS NOT NULL;

3. A página

A declaração precisa estar nos primeiros bytes do HTML — se vier depois de muito conteúdo, o navegador já adivinhou errado:

<meta charset="utf-8">                          <!-- primeira coisa no <head> -->
Content-Type: text/html; charset=utf-8          <!-- e no cabeçalho HTTP -->

4. O arquivo

Descubra o que ele realmente é antes de converter — e nunca converta por cima do original:

file -I dados.csv                    # diz o charset detectado
iconv -f ISO-8859-1 -t UTF-8 dados.csv > dados-utf8.csv

O caso da dupla codificação

Quando você vê ç em vez de ç — lixo em cima de lixo — o texto passou pela conversão errada duas vezes. Costuma acontecer quando alguém “conserta” um banco rodando a conversão num conteúdo que já estava certo.

Tem solução, e a ferramenta acima desfaz as duas camadas. Mas é o melhor argumento para o backup: a segunda passada é o que transforma um problema de dez minutos num de dez horas.

O caso do Excel com CSV

Clássico brasileiro: você exporta CSV em UTF-8 correto, abre no Excel e os acentos quebram. O arquivo está certo — o Excel é que ignora UTF-8 sem marcação e assume a codificação regional do Windows.

A solução é gravar o BOM, três bytes invisíveis no começo que dizem ao Excel qual tabela usar:

// Node — o  é o BOM
fs.writeFileSync('saida.csv', '' + conteudo, 'utf8')
// PHP
fwrite($f, "\xEF\xBB\xBF");   // antes de qualquer linha

Outras ferramentas de diagnóstico: erro de CORS, erro de npm, JWT rejeitado.

Newsletter

Receba os artigos novos por e-mail

Sem spam. Só o aviso quando sai um tutorial ou artigo novo.