BookTranslator
BookTranslator

Como traduzir ficheiros Markdown sem quebrar código, links ou front matter

O que traduzir num ficheiro Markdown ou MDX, o que deixar intacto e como verificar o resultado. Com um ficheiro de exemplo, um verificador gratuito e um teste de 9 execuções com um LLM local.

BookTranslator

BookTranslator Team

15 min read

Para traduzir um ficheiro Markdown, traduza a prosa e deixe a maquinaria intacta: as chaves do front matter, o código, os URLs, as etiquetas dos links, as tags dos componentes e as etiquetas das notas de rodapé têm de voltar byte a byte. Depois, verifique três coisas que uma tradução cuidadosa ainda assim quebra: os links para cabeçalhos, os comentários no código e, em chinês, japonês ou coreano, o espaçamento à volta do texto a negrito e dos intervalos numéricos.

Traduzimos uma página MDX de 73 linhas para chinês simplificado nove vezes com um modelo local, usando três prompts diferentes, e verificámos cada resultado com um script. As partes que normalmente preocupam as pessoas sobreviveram nas nove execuções: o código, o código inline, os URLs, a tabela, o componente JSX e a lista de tarefas. Outras três coisas correram mal. O link para um cabeçalho quebrou nas nove execuções. Os comentários no código foram traduzidos nas seis execuções que enviaram o ficheiro completo, incluindo três execuções em que o prompt dizia para não mexer no código. E o prompt que escondia a sintaxe atrás de placeholders, que parece a opção mais segura, quebrou o front matter nas suas três execuções.

Resposta rápida: o que traduzir e o que deixar intacto

Parte do ficheiroExemplo do nosso ficheiro de amostraTraduzir?
Chaves do front matterslug:, sidebar_position:, tags:Não
Valores do front mattertitle: "Export Your Library"Apenas os destinados ao leitor, como title e description
Imports e exports (MDX)import Callout from '@site/src/components/Callout';Não
Blocos de códigoshelfmark export --format epubNão. Decida em separado sobre os comentários e as strings destinadas ao utilizador.
Código inlinelibrary.json, --coversNão
Destinos de links e imagens(./export-options.md), (https://www.w3.org/TR/epub-33/ "EPUB 3.3")Não, exceto o título opcional entre aspas
Texto dos links e texto alternativo das imagens[glossary section], ![Folder layout after an export]Sim
Etiquetas e definições de referência[project glossary][glossary], [glossary]: ./glossary.md#termsMantenha a etiqueta [glossary] e o URL; traduza o texto visível
Cabeçalhos## Keep one glossary for the whole bookSim, mas primeiro atribua-lhes IDs fixos (ver abaixo)
Componentes JSX<Callout type="warning" title="Back up first">Mantenha os nomes das tags e dos atributos; traduza os valores dos atributos visíveis ao leitor, como title
Marcadores de admonição:::tip e :::Não
Etiquetas de notas de rodapé[^sync]Não
Comentários MDX{/* Translator note: ... */}Opcional. Os leitores nunca os veem.

Tudo o resto é prosa comum: parágrafos, itens de lista, células de tabela e citações em bloco.

O que testámos

Escrevemos o sample-source.mdx, uma página de documentação para uma aplicação de leitura fictícia chamada Shelfmark. Está escrita no estilo MDX usado pelo Docusaurus e por sites de documentação semelhantes, com um exemplo de cada construção da tabela acima, além de uma quebra de linha forçada com dois espaços, uma ligação a negrito, uma expressão a negrito terminada numa parêntese, dois intervalos numéricos escritos com travessões e uma ligação interna para o cabeçalho "Keep one glossary for the whole book."

De seguida, pedimos a um modelo local que o traduzisse para chinês simplificado de três formas:

  1. Ficheiro completo. Colar o ficheiro inteiro e pedir de volta o ficheiro traduzido.
  2. Com instruções. O mesmo, mais seis regras: manter as chaves do front matter, não alterar nada dentro do código, manter os URLs e as definições de referência, manter os imports, as tags e os comentários, manter as etiquetas de notas de rodapé e os marcadores de admonição, e traduzir os cabeçalhos, o texto das ligações, o texto alternativo e o atributo title.
  3. Protegido. Substituir o código, o código inline, os URLs, as tags, os comentários, as etiquetas de notas de rodapé, os marcadores de admonição e as linhas do front matter por marcadores numerados como ⟦12⟧, traduzir o resto e depois repor os originais. Os valores de title e description ficaram expostos para tradução e, como se veio a verificar, também as linhas --- do front matter e as aspas de fecho.

O modelo foi o Qwen 3.6 27B (Q4_K_M) no LM Studio, com o raciocínio desativado e temperatura 0,3, num Apple M4 Max com 64 GB de memória. Executámos cada condição três vezes, nove execuções no total, a 25 de outubro de 2026. O script é o run_llm_markdown_test.py, e os nove resultados estão publicados sem edição; o README lista todos os ficheiros. Se nunca configurou um modelo local, o nosso guia de tradução com LLM local explica as definições.

Foi um teste pequeno, com um modelo, um par de línguas e uma página. Mostra que problemas acontecem e como detetá-los. Não classifica modelos nem prompts.

Resultado: a sintaxe sobreviveu; as ligações e as decisões, não

Verificámos cada resultado com o check_markdown_translation.py e renderizámos cada um com o remark e o compilador MDX. O resultado completo do verificador está em check-results.txt, e o results.csv tem uma linha por execução.

VerificaçãoFicheiro completoInstruídoProtegido
Compila como MDX3 de 33 de 33 de 3
Código, código inline, URLs, tabela, etiquetas e lista de tarefas inalterados3 de 33 de 33 de 3
Negrito e itálico continuam a ser apresentados3 de 33 de 33 de 3
Front matter intacto3 de 33 de 30 de 3
A ligação na página continua a apontar para o respetivo cabeçalho0 de 30 de 30 de 3
Comentários de código mantidos como escritos0 de 30 de 33 de 3
title do callout traduzido3 de 33 de 30 de 3

As primeiras linhas são as boas notícias. Este modelo devolveu a sintaxe Markdown intacta quando colámos o ficheiro completo; não encontrámos nada para corrigir em código, URLs ou tabelas. As últimas quatro linhas são onde está o trabalho, e nenhuma delas aparece como erro quando compilas o site.

1. Os títulos traduzidos quebram as ligações para eles

O exemplo liga à sua secção de glossário com [glossary section](#keep-one-glossary-for-the-whole-book). Todas as execuções traduziram o título, como deviam, e todas as execuções mantiveram o destino da ligação, como deviam. O resultado é uma ligação para um ID que já não existe.

A maioria das ferramentas Markdown cria IDs de títulos a partir do texto do título. A documentação do Pandoc descreve o identificador como "baseado no texto do título", e o Docusaurus avisa que podes querer alterar ou traduzir um título "sem atualizar o ID existente para evitar quebrar ligações". Assim que o título passa a ler-se 为整本书保留一个术语表, o ID gerado é chinês, e #keep-one-glossary-for-the-whole-book não leva a lado nenhum. A página continua a compilar, por isso nada te avisa.

Também não podes corrigir isto editando a ligação após a tradução. Ao longo de nove execuções, o modelo escreveu o mesmo título de quatro formas diferentes, pelo que o ID gerado voltaria a mudar a cada retradução. A correção pertence ao momento anterior à tradução: atribui a cada título que é alvo de ligação um ID fixo.

  • Em MDX do Docusaurus, usa ## Keep one glossary for the whole book {/* #keep-one-glossary */}. O Docusaurus nota que a forma mais antiga {#id} "já não é sintaxe MDX válida", e o seu comando write-heading-ids adiciona IDs a todos os documentos por ti.
  • No Pandoc e em processadores Markdown que seguem o PHP Markdown Extra, usa {#keep-one-glossary} no fim do título.

O ID mantém-se então igual em todos os idiomas, e o verificador trata-o como o destino da ligação.

2. Os comentários de código são traduzidos, e a string visível não

O exemplo tem um comentário shell e um comentário YAML:

library:
  name: "My books"   # shown in the sidebar
  sort: title

Todas as seis execuções que enviaram o ficheiro completo traduziram ambos os comentários, incluindo as três em que o prompt dizia "Não alteres nada dentro de blocos de código delimitados". O código em si ficou intacto em todas as nove execuções. Entretanto, "My books", que o comentário diz ser mostrado na barra lateral, permaneceu em inglês em todas as nove execuções. O modelo fez o oposto do que uma página localizada precisa: traduziu a nota para programadores e deixou a etiqueta que os leitores vão ver.

Nenhuma das escolhas está errada em todos os casos. Para um tutorial cujos leitores vão copiar o código para o seu próprio projeto, os comentários traduzidos ajudam. Para documentação de referência cujo código tem de corresponder a um repositório, fazem a página discordar da fonte. Toma a decisão uma vez para o projeto, e revê manualmente os valores de string em exemplos de configuração, porque nenhuma ferramenta consegue distinguir uma etiqueta visível de um identificador. O verificador reporta alterações apenas em comentários como avisos por esta razão.

3. O método aparentemente seguro quebrou o front matter

A condição protegida cumpriu o que prometeu: todos os 32 placeholders voltaram em todas as três execuções, pelo que código, URLs e tags foram restaurados na perfeição. Mas o nosso script deixou duas pequenas coisas fora dos placeholders: a linha --- que abre o front matter, e as aspas de fecho depois do título e da descrição traduzidos. O modelo eliminou-as em todas as três execuções. O ficheiro começava assim:

title: "导出您的书库
description: "在更换设备之前,请备份您的笔记和书籍。
slug: export-library

Sem o --- de abertura, um parser Markdown deixa de ver front matter. Lê as seis linhas YAML mais o --- de fecho como um título, pelo que a página abriria com um título grande contendo o YAML. Ainda assim, compilou como MDX. Voltar a colocar o --- também não basta: o parser YAML rejeita as duas aspas não fechadas.

A lição não é evitar placeholders. É que tudo o que deixares exposto pode voltar alterado, e que o front matter não deve ir para o modelo como parte da página. Analisa-o, envia apenas os valores visíveis ao leitor (title, description) como strings separadas, e escreve as traduções de volta na estrutura original. São poucas linhas de código e eliminam toda esta classe de problemas.

Texto a negrito e intervalos em chinês, japonês e coreano

Mais dois problemas nunca apareceram nas nossas nove execuções, mas vale a pena verificá-los porque quebram páginas silenciosamente, e ambos já atingiram o próprio blog do BookTranslator. O blog é escrito em MDX e traduzido automaticamente para 27 idiomas, e numa execução de tradução agendada dois artigos em coreano falharam a nossa verificação de estrutura três vezes seguidas. As causas foram os dois padrões abaixo.

Negrito que termina em pontuação. O CommonMark decide se ** pode abrir ou fechar negrito observando os caracteres de ambos os lados. Um ** de fecho que vem imediatamente após pontuação tem de ser seguido por um espaço, pontuação ou o fim da linha; esta é a regra de flanco direito. O inglês tem sempre um espaço aí. O chinês, o japonês e o coreano frequentemente não têm. Assim, 打开**设置 → 导出(测试版)**并选择 mostra asteriscos literais, porque o negrito termina num parêntese de largura total e é seguido diretamente por 并. A mesma linha sem o parêntese é renderizada em negrito, e a linha com um espaço após o ** também. Um link em negrito tem o mesmo problema, porque um link termina em ): 点击**[导出](./export-options.md)**打开 falha, e o coreano **[API](./api.md)**는 também.

O nosso modelo colocou por acaso um espaço de ambos os lados de cada frase em negrito, pelo que a sua saída foi renderizada corretamente. Um editor que mais tarde remova esses espaços para seguir as convenções tipográficas chinesas traz o erro de volta. Pode experimentar as sete variantes em render-cases.md.

Intervalos numéricos com til. O texto chinês e japonês escreve frequentemente intervalos como 3~5. Os sites GitHub Flavored Markdown construídos sobre remark transformam tis únicos em riscado por predefinição: a opção singleTilde do remark-gfm é true a menos que a altere. Dois intervalos num mesmo parágrafo, como 需要 3~5 分钟,生成 10~20 MB 的文件夹, são renderizados com o texto entre eles riscado. A nossa amostra usou travessões, e oito execuções mantiveram-nos enquanto a nona escreveu 3 到 5. Peça travessões ou palavras nos intervalos, ou defina singleTilde: false na sua configuração do remark.

O verificador estrutural sinaliza ambos os padrões. Para confirmar o que é realmente renderizado, render_check.mjs conta negritos, itálicos, riscados e asteriscos restantes no original e na tradução, e tenta uma compilação MDX:

npm install @mdx-js/mdx@3 unified@11 remark-parse@11 remark-gfm@4
node render_check.mjs source.mdx translated.mdx

Menos nós de negrito ou mais asteriscos literais na tradução significa que alguma ênfase deixou de fechar.

Um Fluxo de Trabalho que Resiste

  1. Corrija primeiro os IDs dos cabeçalhos. Adicione IDs explícitos a cada cabeçalho para o qual algo aponta, na sintaxe suportada pelo gerador do seu site. Faça-o na língua de origem, uma vez, e faça commit antes de traduzir qualquer coisa.

  2. Retire o front matter da página. Traduza apenas os valores visíveis ao leitor, como title e description, como strings separadas, e escreva-os de volta no YAML original.

  3. Decida a política de código. Comentários traduzidos ou não; strings de apresentação em exemplos de configuração traduzidas ou não. Registe-a no seu glossário de tradução junto aos nomes de produtos que não devem mudar, como Shelfmark na nossa amostra.

  4. Traduza o corpo. Use uma ferramenta que faça parsing de Markdown, ou envie a página inteira com regras explícitas. No nosso teste, enviar a página inteira manteve a sintaxe intacta; se usar placeholders, proteja construções completas e não deixe nada meio exposto.

  5. Execute a verificação estrutural.

    python3 check_markdown_translation.py source.mdx translated.mdx --keep Shelfmark
    

    Compara chaves e valores do front matter, imports, blocos de código, código inline, destinos de links, definições de referência, níveis de cabeçalho, destinos de links na página, tags e atributos JSX, comentários MDX, marcadores de admonição, etiquetas de notas de rodapé, forma da tabela, caixas de listas de tarefas, quebras de linha forçadas, ênfase que não consegue fechar, intervalos com til e termos que pedir para manter. Não avalia a qualidade da tradução.

  6. Verifique a renderização para destinos CJK com render_check.mjs, e adicione um espaço após qualquer ** de fecho que siga pontuação.

  7. Compile o site e clique nos links. Abra a página em cada língua, clique em todos os links na página e leia os exemplos de configuração. Uma compilação bem-sucedida prova apenas que o ficheiro compila.

Para documentação onde a precisão importa, os passos de terminologia e revisão do nosso fluxo de trabalho de tradução técnica aplicam-se por cima disto.

Onde o BookTranslator se Encaixa

O tradutor de TXT do BookTranslator aceita ficheiros .txt, .md e .mdx. Segundo a página do produto, traduz-os mantendo parágrafos, quebras de linha, caracteres especiais e estrutura do texto, e divide ficheiros grandes em segmentos para tradução com contexto, pelo que pode carregar um manual inteiro ou um livro escrito em Markdown em vez de o colar em pedaços.

A página do produto não descreve tratamento especial para front matter, blocos de código ou componentes MDX, e não executámos esta amostra através do BookTranslator para este artigo. Seja qual for a ferramenta que usar, faça os passos 1 a 3 antes de traduzir e os passos 5 a 7 depois. Um cabeçalho sem ID fixo continua a perder os seus links, e um intervalo escrito com tis continua a transformar-se em riscado. Se está a publicar um livro em várias línguas, o nosso guia de autopublicação em várias línguas cobre o resto desse trabalho.

Obtenha os Ficheiros de Teste

Tudo deste teste é livre de reutilizar:

Publicações relacionadas