Every check passed. A lot of it was broken.

aidocumentationi18n

A few days ago I finished translating the help documentation for Suparanku — a product of Supasaito, the company I co-founded — into eight more languages. Nine locales in all, forty pages each. Three hundred and sixty pages.

When I was done, every check passed. The build was green. The schema validator was green. The translation checker said every single label matched. I had scripts whose entire job was to catch exactly the kind of mistake you make when you translate forty pages into a language you can’t read, and all of them said the same thing: you’re fine.

A lot of it was broken.

Not broken in a way that crashed anything. Broken in a way where the page loads, the sentence reads beautifully, and it’s still wrong. And what I want to tell you about isn’t the bugs. It’s the thing underneath them, which took me embarrassingly long to see:

A green check is an answer to one very specific question. The trouble starts when you read it as an answer to yours.

Let me show you what I mean, because it sounds abstract until you see it happen.

The short version

  • Forty help pages, nine locales, three hundred and sixty pages. Three separate checks, all green, all correct, and the set was full of holes.
  • A placeholder written <your domain> was read as an HTML tag, and the browser rendered it as nothing. The sentence just stopped — in every language. Caught on staging, before a single reader saw it.
  • Bold stopped working in ten Japanese sentences out of sixteen, because the rules for what counts as a bold marker assume spaces between words. We had a check for it. It looked for **. The broken ones used single *.
  • I confirmed the translated pages were clean by searching them for an English word. Zero hits. Forty-eight of the things I was looking for were still sitting there — translated, like everything else on the page. Zero was the only number that search could ever have returned.
  • A script reported 384 labels found · 147 matched · 129 ambiguous · 108 unmatched. Four numbers that add up, one hallucinated row at the top of the list, and a real button missing from it.
  • Running the same job nine times in parallel turned a silent inconsistency in the product’s own wording into a visible disagreement between two translators. That is strictly better.
  • And the part no script reaches: nobody has read any of these pages natively. Not one language. I’m saying so out loud because an article that recommends something the author hasn’t done yet should.

The placeholder that wasn’t there

Our documentation quotes buttons and labels from the app. When it needs to show you where your own information goes, it writes a placeholder. Like this:

Click What we found on <your domain>

You’ve seen that convention a thousand times. Angle brackets mean “your thing goes here.”

Here’s what our readers actually saw:

Click What we found on

The placeholder was gone. Not misspelled — gone. Because in the format we write documentation in, angle brackets mean something else: they’re how you write HTML. So <your domain> wasn’t read as a placeholder at all. It was read as an instruction to create an element called “your domain”, and since no such element exists, the browser did what browsers do with things they don’t recognize.

It rendered nothing.

The sentence just… stopped. And it did that in every single language, because the placeholder in the body text had been faithfully translated into all of them — <Ihre Marke>, <tu marca>, <votre marque>, <自社ブランド>, <ваш бренд>. All nine.

We caught it on staging, before any of this reached a single reader. I’m not telling you a story about a customer-facing disaster — I’m telling you about the four weeks where I believed it was fine, and had the green checks to prove it.

Now here’s the part that actually taught me something.

Nothing failed. The page was valid. Genuinely, technically valid — an unknown element that renders as nothing is legal. The build had no reason to complain. The checker compares our text against the app’s text, and our text was correct. Every tool was doing its job perfectly.

And our own internal guide recommended writing it that way. With a little green checkmark next to it. I know, because I’m the one who’d have followed it.

So the better we followed our own rule, the more languages were wrong.

Valid isn’t the same as intended

That’s the whole lesson, really, and it took a second bug to make it land.

In Japanese, we mark app labels in bold. Same as everywhere. Except in a handful of Japanese sentences, the bold silently stopped working, and readers saw the raw asterisks — **like this** — sitting there in the middle of a paragraph like a typo.

The reason is genuinely obscure. The rules for what counts as “start of bold” and “end of bold” depend on what character sits next to the asterisks, and those rules were written with spaces between words in mind. Japanese doesn’t put spaces between words. So in certain combinations, the asterisks just… aren’t bold markers anymore. They’re asterisks.

Sixteen sentences were written that way. Ten of them broke. The six that worked happened, by pure luck, to be followed by a space or a period.

We had a check for this. It looked for **.

The broken ones used single *.

I want to sit on that for a second, because it’s the most useful thing in this whole article. That check wasn’t wrong. It answered its question correctly every time it ran. Its question just wasn’t “is the emphasis working?” — it was “does the string ** appear?”. Those feel like the same question right up until the moment they aren’t.

I searched for an English word inside files that aren’t in English

This one still makes me laugh.

While writing the English pages, I’d left myself little notes. Each one said where a fact came from, so that six months later I could tell whether it was still true. They looked like this, sitting in the middle of the page:

(derived: the scoring file, scoreSrpsPlus())

They’re for me, not for readers. So the rule was simple: when a page gets translated, that line gets deleted.

To confirm the rule had been followed, I searched all the translated pages for the word the notes start with — derived — and got zero hits. Great. Documented it as done.

They were all still there. Forty-eight of them, across three languages.

Because the translators had done the obvious, reasonable thing: they’d translated the note along with everything else. The line still existed on every page. It just didn’t start with derived anymore. It started with 導出元 in Japanese, derivato in Italian, источник in Russian.

So I had built a search that could only ever come back empty. I was looking for an English word inside files whose entire defining property is that they are not in English. Zero wasn’t a result. Zero was the only number that search was capable of producing.

And the number went into our documentation as a verified fact.

That one isn’t a subtle rules-lawyer bug like the asterisks. That one is just me, being confident.

The number that was precise and invented

Here’s my favorite, and it’s the one I’d tattoo on my hand.

Early on, a script scanned all the pages and reported back:

384 labels found · 147 matched to a source · 129 ambiguous · 108 unmatched

Look at that. Four numbers, and they add up. It’s the kind of output you screenshot into a status update, which is more or less what I did.

Here’s what the script was doing. In our pages, anything that’s a button or a label from the app is wrapped in double asterisks — **Mark done** — so the script walks the text, finds an opening pair of asterisks, finds the next closing pair, and calls whatever sits between them a label.

That works fine until a sentence gets long enough to wrap onto a second line, and the label happens to straddle the break:

Check it yourself, then click **Mark
done**. If you change your mind, **Mark undone** puts it back.

The script read one line at a time. So on the first line it found an opening ** and never found its closer. On the second line it found what it thought was an opening — the leftover ** from done** — and paired it with the next one it met.

The label it came back with was . If you change your mind, .

That’s not a button. That’s the tail of a sentence, starting with a period.

And while it was busy inventing that one, it dropped Mark done — which is a button, a real one, in the product, and therefore one of the things the whole script existed to find.

So the report was precise, confident, internally consistent, and it contained a hallucinated row sitting right next to a missing real one. I’d have caught it in five seconds if I’d read the list instead of the summary — the fake entry sorted to the very top, because it started with a period.

There were two more like it that week. A dependency I declared “development-only” based on a list that had been cut off at three items out of sixty-nine. A “zero problems remaining” that I read off the second run of a command that had already fixed everything on the first.

Three mistakes, and here’s what they have in common: none of them was a mistake in reasoning. Every conclusion followed correctly from what was on the screen. What was on the screen was a slice. A regex too narrow, a list cut off, a command already spent.

You cannot catch that by thinking harder about your conclusion. I tried. The conclusion is fine. You catch it by looking at the whole output that produced it, which is boring, and which I now do.

Why “someone will notice” doesn’t work here

You might be thinking: fine, but surely a human reading the page would catch this stuff.

They wouldn’t, and this is the part I most want to convince you of.

Not one of the wrong translations was nonsense. Not one. Every single one was a fluent, natural, completely plausible sentence — that didn’t match what the app actually says.

The app says “Example only — illustrative, not your data.” Our page said “Illustrative example, not your data.” Which is a good sentence! It means the same thing! A native speaker proofreading that page would sail straight past it, and so would you, and so did I.

It’s wrong for a reason that has nothing to do with language: the reader is supposed to find that exact text on their screen. If they’re hunting for a sentence that doesn’t exist, the documentation has failed at the one job it had, in a way that reads perfectly.

In one case the app’s own text was a bit clumsy, and our page had quietly improved it. That’s worse, not better. Copy it clumsy. The reader needs to find it.

The upside of doing it nine times

I’ll end the bug parade with the one that surprised me pleasantly.

For the last few languages I ran several translators in parallel, each handling a batch of pages, all working from the same instructions. And in Portuguese, two of them disagreed: one wrote crawlers de IA where the other wrote rastreadores de IA, for the same thing.

My first reaction was that I’d made a mess. My second was to go look — and the app itself says it both ways. Seven strings each way. Both translators had reasoned correctly from different evidence, because the evidence disagreed.

If one person had done all forty pages, that inconsistency would have been a coin flip nobody ever noticed. Doing it nine times, in parallel, turned a silent inconsistency into a visible disagreement — which is strictly better, because you can fix what you can see.

That reframed the whole project for me. Translating the docs nine times wasn’t nine times the work of writing them once. It was an X-ray of the thing being described.

So what do I actually do differently

Three things, and none of them are clever.

I render it and look at it. Every single bug above was invisible to a check that reported success. Not one of them survived somebody actually looking at the finished page. I now have a script whose only job is to look at the output — the real, final page — and shout if something’s visibly off. It found two more bugs the day I wrote it.

The funny part: I’d put that off for weeks because I thought I needed a special tool to do it. I didn’t. The finished pages were already sitting there, already rendered. I’d been asking “how do I render this?” when the answer was “something already did.”

I ask what question a check is actually answering. Not “did it pass” — what did it look at. Three green checks and a broken page taught me they were answering three different questions, and none of them was mine. That sounds philosophical. It’s the most practical habit I picked up all year.

I don’t trust a check that has never caught anything. A new check that comes back clean tells you nothing yet. Feed it something you already know is broken. If it doesn’t scream, it’s not a check, it’s a decoration. Ten seconds, and it would have saved me an entire wrong number in an entire document.

The thing no script can do for you

Everything I’ve described is a machine catching a machine. And there’s a whole category of problem sitting outside what any of them can reach.

Let me say the uncomfortable version plainly, because the comfortable version would be a lie by framing: no native speaker has read any of these pages. Not one language. Not one page.

Every mechanical check passes on all nine. Every quoted label matches the app. By every measure I built, the set is clean. But “does this match the app” and “does this read like something a person would write” are different questions, and I only built tools for the first.

And I know roughly what a native reader would say, which is the part that stings. Go look at any two languages side by side and you’ll notice something: every one of them is a paragraph-for-paragraph mirror of the English. Same order. Same paragraph breaks. Same number of sentences. Nobody decided that — it’s just what happens when you translate rather than rewrite, and it’s why the pages read as translated in all nine languages at once.

That is exactly the thing no check I could ever write would flag. Every sentence is accurate. The structure is a fingerprint.

Portuguese is the one I’d hand over first, and not because it’s the worst — it came out cleanest on every measure I have. It’s because it carries a judgment call the others don’t: keeping the phrasing natural for readers in Brazil and in Portugal without leaning into either. There’s a written rule about it. No script has an opinion about a written rule like that, so it went unchecked, in the locale that scored best.

Here’s the conclusion I’d actually defend, and it’s the one thing in this article I’d tell you to do even if you ignore the rest:

Run the checks, then have a native speaker read it anyway. Not as a formality at the end. As the other half of the job.

The two fail in opposite directions, which is exactly why you want both. My checks catch what a human sails straight past — a sentence that reads beautifully and quotes a button that doesn’t exist under that name. A native reader catches what no check can even represent: that it’s all technically correct and still reads like a translation. Neither one covers the other, and running only the first — which is what I did — gets you nine languages that are verifiably accurate and possibly nobody’s idea of good writing.

If you care about the translation, and I do, because this is documentation someone reads at the exact moment they’re stuck and frustrated — then the check tells you it’s right, and only a person can tell you it’s good. Those are two different jobs and I’d stopped after the first one.

Nine reads, then. Not one. It’s on the list, all nine of it, and I’m telling you because an article that recommends something the author hasn’t done yet should at least say so out loud.

The things this article is about

If you want to see what came out of all this, the documentation is here:

suparanku.com/en/help/ →

And there’s a companion to this one, from the other direction — the same failure mode, turned on myself. I published nine identifiers that do not exist across my own lessons: an attribute prefix, a CSS variable, an ARIA value. Every one of them plausible. Not one of them copied from the real project. That’s Nine wrong names shipped →.

If you take one thing from this: green means a specific question got a specific answer. Before you relax, find out which question it was.

And then go ask a human.

Have an awesome journey,

Francesco