Um tradutor comum trata um manual técnico como uma fila de frases. Esse é o erro central. Em um documento de desenvolvimento, comandos de instalação, códigos de status, nomes de funções, caminhos de arquivo e blocos inteiros de código não são texto natural. São peças que precisam continuar exatamente iguais.

Nesta matéria, vamos construir um fluxo de tradução técnica com três propriedades: ele identifica o que pode ser traduzido, protege o que não pode ser alterado e valida o resultado antes de devolver o documento. O resultado final será um arquivo Markdown traduzido, com comandos, códigos de erro, links e marcação preservados.

A ideia vale para outros formatos, mas começaremos pelo caso mínimo. Primeiro fazemos o pipeline funcionar. Depois acrescentamos glossário, validação semântica e suporte a documentos maiores.

O documento precisa virar uma árvore de peças

Antes de chamar um modelo de linguagem, separe o documento em unidades com papéis diferentes. Um parágrafo comum pode ser traduzido. Um bloco cercado por três crases não deve ser enviado para tradução. Um link precisa manter seu endereço, mesmo que o texto visível mude.

Essa separação é mais importante que o prompt. Se o modelo recebe código misturado com explicação, ele pode corrigir uma sintaxe, traduzir um nome de variável ou trocar um código de erro por uma expressão "mais natural". A resposta parecerá boa para um leitor casual e estará errada para quem precisa executar o procedimento.

Para a primeira versão, usaremos quatro tipos de segmento:

  1. texto natural;
  2. bloco de código;
  3. linha de comando;
  4. marcador protegido, como URL, código de erro ou identificador técnico.

A estratégia é substituir cada trecho sensível por um marcador temporário, traduzir apenas o texto natural e restaurar os valores originais depois. O marcador funciona como uma etiqueta de bagagem: o modelo pode mover a frase ao redor, mas não deve abrir a mala.

A versão mínima começa com Markdown

Crie uma pasta com um arquivo chamado translate.mjs. O programa abaixo roda em Node 18 ou mais novo, sem instalar nada: usa o fetch nativo e faz chamadas HTTP diretas à API da HiNow, no mesmo formato do guia oficial em developers.hinow.ai. A tradução usa o hinow/himax, que aguenta documento longo e segue instrução de preservação com mais disciplina.

import { readFile, writeFile } from "node:fs/promises";

const API_URL = "https://api.hinow.ai/v1/chat/completions";
const MODEL = "hinow/himax";

const CODE_PATTERN = /```[\s\S]*?```/g;
const COMMAND_PATTERN = /^\s{0,3}\$ .+$/gm;
const URL_PATTERN = /https?:\/\/[^\s)]+/g;
const ERROR_PATTERN = /\b(?:HTTP|ERR|EAI|SQLSTATE)[-_ ]?[0-9A-Z_]+\b/g;

function protect(text) {
  const placeholders = new Map();
  const store = (fragment) => {
    const key = `__PROTECTED_${placeholders.size}__`;
    placeholders.set(key, fragment);
    return key;
  };
  let output = text.replace(CODE_PATTERN, store);
  output = output.replace(COMMAND_PATTERN, store);
  output = output.replace(URL_PATTERN, store);
  output = output.replace(ERROR_PATTERN, store);
  return { output, placeholders };
}

async function translate(text) {
  const response = await fetch(API_URL, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.HINOW_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: MODEL,
      messages: [
        {
          role: "system",
          content:
            "Traduza para português do Brasil. Preserve exatamente todos os " +
            "marcadores no formato __PROTECTED_N__. Não traduza nem altere " +
            "esses marcadores. Preserve a marcação Markdown. Responda APENAS " +
            "com o documento traduzido, sem comentário antes nem depois.",
        },
        { role: "user", content: text },
      ],
    }),
  });
  if (!response.ok) throw new Error(await response.text());
  const data = await response.json();
  return data.choices[0].message.content;
}

function restore(text, placeholders) {
  let output = text;
  for (const [key, fragment] of placeholders) {
    output = output.replaceAll(key, fragment);
  }
  return output;
}

function validate(result, placeholders) {
  const lost = [...placeholders.values()].filter(
    (fragment) => !result.includes(fragment),
  );
  if (lost.length > 0) {
    throw new Error(
      `Validation failed: ${lost.length} protected fragments missing from output.`,
    );
  }
  console.log(`Validation passed: ${placeholders.size} protected elements.`);
}

const [inputFile, outputFile] = process.argv.slice(2);
if (!inputFile || !outputFile) {
  console.error("Usage: node translate.mjs input.md output.md");
  process.exit(1);
}

const original = await readFile(inputFile, "utf8");
const { output: masked, placeholders } = protect(original);
const translated = await translate(masked);
const result = restore(translated, placeholders);
await writeFile(outputFile, result, "utf8");
validate(result, placeholders);

O programa protege blocos de código antes das linhas de comando porque um comando pode estar dentro de um bloco. O padrão usado aqui é simples e serve para a primeira execução, não para qualquer Markdown possível. Documentos com HTML embutido, tabelas complexas ou blocos aninhados exigem um analisador específico.

A função translate envia somente o texto com marcadores. A função restore recoloca o conteúdo original. Já validate interrompe o processo quando um trecho protegido não volta intacto, em vez de deixar o erro passar em silêncio. Repare que o código inteiro está em inglês, o padrão de qualquer base de código real, com uma exceção deliberada: a instrução enviada ao modelo é em português, porque o trabalho do programa é produzir português.

Faça um teste que possa falhar

Crie input.md com um trecho de manual em inglês. Para o teste ficar realista, use a documentação de uma API como conteúdo:

# Generating your first image

Send a POST request to https://api.hinow.ai/v1/images with your key:

$ node generate-image.mjs "a lighthouse at dawn"

If the request fails with HTTP 401, check your HINOW_API_KEY variable.

Execute:

export HINOW_API_KEY="hi_sua_chave"
node translate.mjs input.md output.md

O texto ao redor deve aparecer em português. O comando iniciado por $, a URL da API e o código HTTP 401 precisam permanecer idênticos. A saída esperada no terminal:

Validation passed: 3 protected elements.

Esse número não é uma característica fixa do programa. Ele depende do conteúdo do arquivo. O teste importante é outro: compare cada valor protegido do original com o arquivo final. Se um único comando mudou, o documento não está pronto para distribuição.

O que a primeira versão ainda não entende

Regex reconhece padrões, não significado. Ela pode proteger HTTP 401, mas não sabe que "Unauthorized" é a descrição associada ao erro e talvez deva permanecer em inglês em uma tabela de referência. Também não distingue automaticamente uma palavra técnica que deve ser traduzida de um identificador que precisa ficar intacto.

A próxima melhoria é um glossário explícito. Antes da tradução, substitua termos controlados por marcadores adicionais. Por exemplo, se worker, endpoint e rollback precisam seguir uma convenção interna, crie um mapa de termos e valide a presença das traduções aprovadas depois da resposta.

Outra melhoria é dividir documentos longos em blocos. Suponha que cada chamada leve 300 ms para preparar e que cada rodada de inferência leve 2 segundos. Oito rodadas custam cerca de 18,4 segundos, sem contar transferência e reprocessamento. Um manual de 80 páginas pode exigir muitas chamadas, então agrupar parágrafos preservando os marcadores reduz espera e custo. Essa conta é uma estimativa ilustrativa baseada nas premissas apresentadas, não uma medição universal.

Quando a HiNow entra no fluxo

A vantagem prática de usar a API da HiNow aqui é que o mesmo formato de chamada cobre as próximas etapas do projeto. Para um PDF escaneado, a rota é a mesma /v1/chat/completions: troque o modelo por hinow/hivision e envie a página como uma parte image_url na mensagem, junto com a instrução de texto. O fluxo inteiro, proteger, ler, traduzir e validar, fica em um único formato de requisição e uma única chave.

O ponto decisivo é não pedir ao modelo que "preserve tudo" como uma promessa vaga. Preserve você mesmo o que é estrutural, envie ao modelo apenas o que ele deve reescrever e verifique o retorno com regras mecânicas. A inteligência interpreta linguagem; o programa protege invariantes.

Como expandir sem perder controle

Adicione validação de contagem para títulos, links, blocos de código e marcadores. Compare também a sequência dos comandos, não apenas sua existência. Em seguida, crie uma revisão humana para avisos de segurança, unidades de medida, passos numerados e termos que dependem do domínio.

Para PDFs com colunas, tabelas e imagens, extraia uma representação estruturada antes de traduzir. O objetivo não é produzir texto novo e tentar remontar o desenho depois. É manter cada elemento associado à sua posição e traduzir somente o conteúdo permitido.

Esse desenho tende a tornar o projeto mais verificável, mas deve ser validado no contexto do manual. A tradução deixa de ser uma operação única e passa a ser um pipeline com fronteiras claras: detectar, proteger, traduzir, restaurar e validar. Essa ordem transforma um modelo de linguagem de grande porte em uma peça de um sistema verificável, em vez de colocá-lo no papel impossível de cuidar sozinho da integridade do manual.