---
title: "Translating a help system without breaking it"
description: "A command-line tool that translates product help pages into several languages with OpenAI models and fails loudly when markup, numbers or identifiers change on the way."
date: 2026-11-05T12:30:00+01:00
lastmod: 2026-11-05T13:51:01+01:00
url: https://klimek.link/projects/help-translator/
author: "Marcin Klimek"
source: https://klimek.link/projects/help-translator/
---

# Translating a help system without breaking it

A software product I worked on for a client has a help system made of many small items, stored as XML with a light markup of its own: headers, bold, italic, links, tables, images and phrases that must never be translated. The client wanted it in many languages. I built a Python tool that does the translation with OpenAI models, and most of the work went into what happens after the model answers.

The tool was built and it works. The client's name, the product and the corpus stay with the client, and this page only describes the tool.

## A first version and a rebuild

The first version came out in 2025. In 2026 I rebuilt it, because an audit of the translations already delivered, set against the English originals, turned up things no check had seen: identifiers lost or invented, numbers changed, and internal placeholder tags that leaked into the text.

Now the output goes through a quality gate that fails the run on any of these:

- a node without a translation, or with an empty one;
- a placeholder that survived into the result;
- numbers that differ from the source, compared as a multiset so that word order and typography can change;
- bold, italic and header markers that don't match in count, in either direction;
- lost identifiers, stray characters and changed units.

Each of them has its own test, and a source defect such as a duplicated id is reported as a warning instead of being passed on quietly.

## What helps the model

The tool masks placeholders before sending text. It also takes a glossary built from the client's earlier translations, with per-language rules on how terms are declined, and feeds the relevant terms to the model together with the batch. Items are batched in file order with two items of overlap, which gave more consistent batches than the embedding and clustering stack I tried first, so I removed that stack. Before the first request the tool prints an estimate of the cost, and it stops when the credits run out.

The tool ships as a Docker image, and the client also gets a package with install and run scripts that update an older version in place.

About ten thousand lines of Python are covered by dozens of test files, with ruff, mypy and pre-commit hooks in CI. Along the way I collected places where the English source itself is hard to translate and wrote the client a proposal of corrections.

Source: https://klimek.link/projects/help-translator/
