Por que seu texto vira caracteres estranhos
Não existe texto puro no disco. Um arquivo contém bytes, e uma codificação é o acordo que diz qual byte ou grupo de bytes representa cada caractere. Um arquivo de texto simples não guarda registro desse acordo, então todo programa que o abre está fazendo uma suposição. Quando a suposição está errada aparecem os caracteres estranhos, e o formato exato da bagunça é uma pista confiável do que aconteceu.
A sujeira tem assinaturas que vale a pena reconhecer
A mais conhecida é a letra acentuada que vira duas. Em UTF-8 a letra é é guardada como os bytes C3 e A9, e um programa que os lê um a um com uma página de código da Europa ocidental imprime à e ©. Todo arquivo UTF-8 lido com uma codificação antiga de um byte transforma assim cada caractere não inglês em dois ou três, e é essa sequência que faz um texto coreano ou japonês parecer um muro de entulho latino.
Outras três marcas dizem coisas diferentes. Um arquivo que começa com  é UTF-8 com marca de ordem de bytes sendo lido como outra coisa. O losango preto com um ponto de interrogação é o caractere de substituição, que o decodificador escreve quando os bytes não poderiam ser válidos na codificação que lhe informaram. E um ponto de interrogação simples onde deveria haver uma letra normalmente significa que o texto foi convertido para uma codificação sem lugar para aquela letra.
Duas dessas assinaturas significam que o texto já se foi
é é recuperável, porque os bytes originais continuam no arquivo e só a interpretação está errada. Ler os mesmos bytes com a codificação certa traz o texto de volta exatamente.
O caractere de substituição e o ponto de interrogação sozinho são outra história: eles são o que o arquivo contém agora. Os bytes originais foram descartados no momento da conversão, e nenhum passo posterior consegue deduzir se aquele losango era é ou ü. Por isso a primeira regra ao consertar um problema de codificação é parar de salvar por cima do arquivo: cada gravação com a codificação errada transforma um problema reversível em um irrecuperável.
Codificação dupla, e por que ela se repete
Se um programa lê UTF-8 como se fosse de um byte e depois salva o resultado em UTF-8, o erro ganha cara de permanente: o arquivo agora contém de verdade os caracteres à e ©, cada um com seus próprios dois bytes. Faça isso duas vezes e uma letra vira quatro caracteres, depois oito.
Ainda assim dá para reverter, porque o processo é exatamente invertível se você souber quantas rodadas houve: recodifique para a página de código de um byte e decodifique como UTF-8, uma vez por rodada. Esse padrão aparece tanto porque costuma acontecer dentro de um encadeamento — um formulário, uma coluna de banco de dados, uma exportação — em que a mesma conversão roda sobre dados que já passaram por ela.
As codificações antigas que você ainda encontra
Texto ocidental costuma chegar em Windows-1252, que é o que a maioria quer dizer ao falar Latin-1. A diferença importa: o Windows-1252 preenche com aspas tipográficas, travessão e reticências a faixa que o Latin-1 deixa vazia, e é por isso que uma aspa curva aparece tantas vezes como “. Texto de sistemas asiáticos antigos chega em Shift_JIS, EUC-KR, Big5 ou GB18030, e o cirílico em Windows-1251 ou KOI8-R.
No escritório, a origem habitual é a planilha. Exportar um CSV no Windows ainda tende a gravar a página de código do sistema em vez de UTF-8, então um arquivo impecável na máquina que o criou quebra em todas as outras; e como reimportá-lo ali mostra tudo certo, quem o fez nunca vê o problema.
Detectar é adivinhar, e com texto curto é pior
UTF-8 se valida sozinho: suas sequências de vários bytes seguem um padrão estrito, então um arquivo que decodifica limpo como UTF-8 quase certamente é UTF-8. Essa é a única resposta segura que um detector consegue dar.
Já qualquer codificação de um byte aceita todos os bytes possíveis, de modo que nada elimina nenhuma delas. O detector escolhe olhando quais letras e pares de letras são comuns nos idiomas reais, o que funciona num parágrafo e falha num nome ou num cabeçalho de coluna. Uma ferramenta que responde Windows-1252 com baixa confiança para vinte bytes está sendo honesta, não inútil.
Onde um arquivo deveria se declarar
Formatos que carregam uma declaração são bem mais fáceis de acertar. HTML tem o meta charset, XML tem sua declaração, e qualquer coisa servida por HTTP pode informar o conjunto de caracteres no cabeçalho Content-Type — que prevalece sobre o que o documento diz de si mesmo, então uma página correta servida com cabeçalho errado quebra do mesmo jeito.
A marca de ordem de bytes no início de um arquivo UTF-8 é outra forma de declaração, e corta dos dois lados. Ela elimina o palpite nos editores que a entendem e estraga as ferramentas que não entendem: um analisador de JSON que reclama de um caractere inesperado na posição zero, um script cuja primeira linha deixou de ser um shebang, ou um CSV cujo primeiro nome de coluna vem com um caractere invisível colado.
Como consertar de fato um arquivo quebrado
Trabalhe a partir dos bytes, não da tela. Abra o arquivo em hexadecimal para ver se os caracteres acentuados ocupam um byte ou dois, porque só isso já separa um arquivo antigo de um UTF-8 sendo mal lido. Depois tente decodificar com uma codificação candidata e confira se voltam palavras reais: o teste é se o texto está certo, não se ele mudou.
Quando estiver legível, salve uma cópia em UTF-8 e mantenha o original intocado até ter certeza. E conserte na fronteira, não no meio: decodifique quando os dados entram, mantenha uma única codificação lá dentro e declare-a na saída. Os casos que se repetem quase sempre são encadeamentos que convertem em algum ponto do meio, que é exatamente como um único erro acaba aplicado duas vezes.