TranslatePress cria uma linha para quase qualquer string entre HTML. Você tira o trecho do artigo original e a linha fica. O plugin tem ferramenta de duplicata e de limpeza. Ele não sabe, sozinho, se aquele texto ainda existe em algum post. O banco incha. O site multilíngue, que já era pesado, fica pior. Eu montei um workflow no n8n para atravessar a tabela de tradução, perguntar ao `wp_posts` se a string original ainda aparece, e só então apagar.
A cada 30 segundos o fluxo lê um cursor (o último ID conferido), pega a próxima string original com ID maior, busca esse texto em `post_title` e `post_content` com LIKE, e decide. Se achou, só avança o cursor. Se não achou, apaga a string original e as linhas equivalentes em todos os idiomas. Eu não consegui fazer dez linhas de uma vez: o Merge misturava vazio com achado e ia apagar o que não devia. Um por um. Backup restaurável antes de qualquer DELETE.
- Gatilho
- Intervalo de 30 segundos, um ID por execução
- Teste
- LIKE no wp_posts (título e conteúdo)
- Corte
- Apaga original + traduções; grava o cursor
Por que o plugin não apaga o que você já tirou do artigo
A unidade do TranslatePress é a string. Um título, um parágrafo, um alt, um botão. Cada um vira registro. Duplicata o plugin até encontra. Órfão — original que não está mais em nenhum post — ele não cruza com `wp_posts`. A tabela cresce com revisão, teste, bloco que você removeu, page builder que reescreveu o HTML.
Eu não quis apagar “tudo que é antigo”. Quis apagar o que não aparece mais no conteúdo publicado. A fonte da verdade, neste recorte, é o post. Custom field em outra tabela não entra nesse cruzamento. Eu avisei no vídeo: vou precisar de outro filtro para campo personalizado, senão o fluxo mata tradução de dado que não mora em `post_content`.
Espaço na string foi o primeiro filtro. Serve para não tratar URL e trecho sem espaço como candidato bonito demais para um LIKE amplo. Não é perfeito. É o freio que eu tinha naquele nó.
Cursor numa tabela de variáveis, porque o n8n cobrava Enterprise
O fluxo começa num intervalo de 30 segundos. Leva tempo para varrer um banco grande. Eu coloquei um código para escolher entre dois bancos, se um não tivesse a linha. O melhor é um banco só: o filtro fica mais rápido. No meu caso o inglês antigo era o que mais tinha linha, então a varredura começava por ali. A string original é a chave. Apagando o original, as línguas ligadas saem juntas.
n8n tem variável de fluxo. Variável persistente entre execuções, na época, eu associava ao plano Enterprise. Em vez de pagar isso, criei uma tabela `variaveis` no MySQL: nome e valor. Cada execução lê o valor mínimo (o último ID conferido), busca na tabela de tradução o próximo ID maior, em ordem crescente, e trabalha essa linha. No teste da gravação o cursor estava em 272 e a próxima string viva era 318. “15 melhores custos japonês” — um título, provavelmente de imagem, conteúdo minúsculo.
Servidor lento trava nesse SELECT. Eu tive que resetar. Se a conexão com o MySQL cai, o cursor não anda e o intervalo de 30 segundos vira fila de erro. Isso é hospedagem, não lógica do TranslatePress.
Merge com wp_posts: achou, avança; não achou, apaga todas as línguas
O segundo braço busca a string em `post_title` e `post_content` com LIKE e curinga. A string original está no meio do HTML, não sozinha numa coluna. Por isso o `%`. Se você guarda texto em `postmeta` ou em tabela de custom post, este nó não vê. Vai achar que o original morreu. Eu ia criar o filtro extra e não mostrei pronto no vídeo.
Os dois braços encontram no Merge. Se o post existe, o caminho verdadeiro atualiza a variável para o ID atual. A próxima execução começa depois desse ID. Nada é apagado. Se o post não existe, o caminho falso monta o DELETE: todas as tabelas de idioma em que a coluna original (ou o par original/traduzido) coincide, mais a tabela de strings originais. Eu ainda gravo, opcional, a string e o ID numa variável de log para conferir no banco se alguma execução saiu do script.
Quando eu tentava dez linhas por vez, as que o `wp_posts` não encontrava voltavam vazias, misturavam no Merge com as que encontravam, e o DELETE ficava cego. Tradução em lote, no plugin, aguenta bloco. Este cruzamento, não. Um ID, uma decisão, um cursor. Apaga em todos os idiomas de uma vez, mas decide em uma string de cada vez.
- Faça backup do MySQL e restaure numa cópia. Não comece em produção.
- Crie a tabela de cursor (id, nome, valor) se o seu n8n não persistir variável.
- Selecione `id > :cursor` na tabela de strings originais, `ORDER BY id ASC`, `LIMIT 1`.
- Busque o original em `wp_posts` (e, se existir, em `postmeta` e tabelas suas).
- Se existir, só atualize o cursor. Se não existir, DELETE original + dicionários, depois atualize o cursor.
- Leia o log da string apagada e busque no site de teste antes de soltar o intervalo 24 horas.
O que este fluxo não faz, e o que eu ia melhorar
Não é o limpador oficial do TranslatePress. Não substitui a tela de duplicatas. Não entende bloco do Gutenberg que serializa diferente, não entende texto só em widget, não entende opção de tema. Se a string vive fora de `wp_posts`, o fluxo a trata como morta. Esse é o risco central. Por isso o log. Por isso a cópia de teste. Por isso o filtro de espaço, ainda tosco.
Eu ia compartilhar o JSON do workflow para cada um colar no n8n e ajustar. Também ia melhorando e postando atualização. O vídeo é a mecânica, não o pacote eterno. Tabela muda de versão do plugin. Prefixo `wp_` muda. Nome de dicionário por par de idiomas muda. Abra o banco, veja as tabelas `trp_`, e só então escreva o DELETE.
Limpeza boa é chata. Trinta segundos vezes dezenas de milhares de strings é noite. É melhor do que um `DELETE FROM` sem WHERE. E é melhor do que viver com a MariaDB em 90% da RAM por lixo de tradução — o mesmo lixo que, meses depois, me empurrou para fora do WordPress.
Na sequência
- Restaure o backup numa cópia e rode o fluxo lá até confiar no log.
- Liste as tabelas `trp_` da sua versão antes de escrever o DELETE.
- Inclua `postmeta` e custom table se o site não vive só de `wp_posts`.
- Persista o cursor fora da memória do n8n, senão cada restart recomeça (ou pior: reapaga).
- Confira front e editor nos idiomas principais depois de um lote, não no fim da tabela.
Onde isso quebra
Apagar em produção na primeira execução porque “é só string órfã”.
Usar Merge em lote e deixar linha vazia contaminar o DELETE.
LIKE sem o filtro de custom field, matando tradução de dado que não está no post.
Confiar no limpador nativo do plugin para órfão — ele não cruza `wp_posts`.
Perguntas frequentes
Posso apagar as tabelas trp_ e deixar o plugin recriar?
Você perde tradução publicada. O fluxo existe exatamente para não fazer isso. Órfão sai; string que ainda está no post fica.
Por que 30 segundos e não um loop interno?
O n8n na minha VPS já era instável. Uma execução curta, um ID, um log. Loop longo num processo que restarta no meio é cursor perdido e DELETE pela metade.
n8n precisa ser Enterprise por causa da variável?
Eu contornei com tabela no MySQL. Se o seu plano persiste variável entre execuções, use. O cursor precisa sobreviver ao restart.
Isso acelera o front?
Reduz tabela de tradução e, no meu histórico, o peso que o plugin punha no banco. Não é cache. É menos linha para o MySQL carregar quando o plugin trabalha.