Song of LaritheaCute anime team-RPG

Home / DevDiary / Translation skill that almost killed me

Translation skill that almost killed me

07.09.2026

Who spent 10 days building a translation skill? Was it me? Yes! Was there screaming and swearing in CAPITAL LETTERS, now living forever in my Claude history? You betcha!

I just want to tell you about my process, in case you have the same level of crazy.

So to start with, I translated all the strings myself. Yes. I needed a baseline, and that was the only way to get it. I tried a couple of translation skills; nothing got me what I needed. This was the biggest chunk of work, because it is 3k strings. This was the longest part of the work, but not the complex one.

Complexity started after.

To translate correctly, AI needs enough context. But defining what "enough context" is falls on you as a human. And you have to do a lot of high-level thinking before you get to actually building the prompt. I will tell you about my process for my game.

All the strings in the game can be split into 3 huge groups. Narration (journals, character descriptions, spell descriptions, etc.), Dialogue (heroes talking to NPCs, tutorial, etc.), and Instructions (UI elements like buttons and actions — things that are not about the game world but about the game controls). This split is superficial: a spell card has a name — is that name UI or narration? I decided it is narration, so I can have a clean split.

This clean split needs to be propagated through the files (you don't have to re-organise the files, you can just have a layer that generates the files you want on the fly). Then, because the files end up different sizes, you need a chunking system.

This allows you to build a 4-level approach: generic game data → group data → file data → chunk. For example: things about the game → dialogues → dialogues in cutscenes → a dialogue.

Then you start building your context. Here is how mine is compiled.

Generic game prompt: information about the game (genre, setting — pretty small) and a list of language rules, e.g. how English translates into this language.

Then domain files. A domain adds its own rules (narration has different rules than dialogue), plus good domain examples and bad domain examples. Each domain has the relevant part of the glossary attached to it; the glossary explains the game concepts in that domain. For example, narration needs to know the magic system. Dialogues need to know the character and NPC voices.

The file level has its own context — what additional information or rules this file needs. For example, does it contain inserts like {{hero}}, or is it a set of names for the scenes? It is a chance to keep the domain smaller and to put in fixes that are only relevant to this file. It also has specific examples of good and bad translations.

The last part is the chunk instructions — how to take the file and chunk it for translation.

A diagram of how one translation prompt is assembled. On the left, three boxes feed into an Assemble step: GAME (generic, game rules, language rules), GROUP (dialogue, narration or instructions — role, rules, good examples, bad examples, glossary), and FILE (context, rules, good examples, bad examples). On the right, the game's own English file and the translation file both feed the file's module, which groups the lines into chunks and says which group the file is; that produces one chunk. Assemble takes the twelve boxes whole, in order, then the chunk, and outputs the prompt, kept one per chunk.
The deterministic half: the same files and the same English always produce the same prompt

This process is fully deterministic, and that is the point: it gives me a static, reliable, testable prompt.

With the deterministic part done, we move to the agentic part.

After the prompt is sent to the LLM and the answer comes back, we do a couple of easy checks — is the formatting correct (no tags lost, for example), and is the Russian correct (the right words, spelled properly). The next agentic part is different for each language.

In Russian, the next part is to compare the prompt-based translation with my manual one (remember, the 3k lines I translated), and then it is raised with me in chat. This was a very important step, because it is a chance to check whether the LLM is doing a good job. If not, I can tweak the file rules. Or the domain rules. Or even the game rules — generate a new prompt and try again.

Then the approval (usually a mix of my translations and the agentic ones) gets written into the document.

A flowchart of one translation run. The prompt goes to a translator subagent, which returns a temporary reply of one line per string. Step 1, CHECK, confirms the right lines were answered, placeholders kept and nothing missing — a problem refuses the run outright. A well-formed reply goes to step 2, GRAMMAR: a second prompt carrying who speaks, to whom, and their genders, answered by a grammar-checking subagent that marks each line OK or FIX; the fixes are applied and a gender gate checks again. Step 3, COMPARE, runs for Russian only, weighing each line against the Russian already in the translation file. Step 4 is me reading the English, my version and the new one, and ruling on every line. Step 5, WRITE, commits all lines or none, into the translation file, the approvals record, and the locale gate.
The agentic half — and step 4 is the one that will eventually become an LLM as a judge

So why so complex?

The reason for the complexity is forward-looking. I can evaluate the quality of a translation into Russian, but not into Spanish. And yes, I plan to save some money and hire a native speaker — but the higher the quality of the AI translations, the easier their job will be. So the module "Inga checks translations" will be replaced by an LLM as a judge at some point, so that when I can't say whether the translation is good, someone else will at least try.

I have only tested it on Russian, and the results were very good. After 10 days. Not on day 1.

So this week I am going to test it on Chinese.

Any volunteers to read 3000 Chinese lines of a fantasy game?

← Back to diary