Field notes

August 4, 2026

In l10n, an LLM with context beats a human translator without it

If you are a small team shipping in more than one language, you can get native-quality localization out of AI models today. Actually good, the kind that survives a native speaker reading it closely. What decides the outcome is not which model you pick, but the context that reaches whoever is translating, whether that's a model or a human. The diligence of models can have an advantage over a professional you would hire otherwise.

One source string, Upgrade, translated into Spanish five times as more context is added: the first four render as actualizar or mejorar, struck through, and only the last, which has seen the screen it renders on, gives the correct pasar a.

We are a two-person founding team running a language-learning app in dozens of languages, so the copy is read by people who are unusually sensitive to it. I wanted to share our setup and learnings, and what we learned localizing the app and site into 15 languages over the course of a month.

To be clear about the claim: a good human translator is a better writer than a good model! That is not the comparison. The comparison is a well-briefed model against an under-briefed professional, because an under-briefed professional is what a small budget like ours actually buys.

Most translation failures are context failures

Take the word "Upgrade."

In most products that word covers two unrelated actions: installing a software or hardware upgrade, and moving up to a paid plan. English uses one word for both, so the string on its own (in isolation) tells you nothing about which one you are looking at.

Spanish uses two. Installing an update is actualizar. Moving to a paid plan is pasar a. Put actualizar on the billing screen and you have told a customer their software is about to update. French draws the same line: mettre à niveau is the install, améliorer is vague and faintly insulting about what they had before, and the phrase French products actually use for the paid plan is passer à.

source string

Upgrade

rendered into Spanish

the string alone

actualizar

+ a note on the key

actualizar

+ the glossary

mejorar

+ neighbouring strings

mejorar

+ the call site

pasar a

actualizar is the software-update verb. pasar a is what a Spanish product says on a billing screen. Nothing in the string tells you which one you are looking at.

The same string, translated five times. Only the last row has enough context to be right, and every row above it is a sentence that reads perfectly well.

So "Upgrade" sitting in a spreadsheet cell, or in a CAT tool like Weglot or Smartcat, is not enough information to translate the word "Upgrade." You cannot get it right without seeing the screen it appears on.

Those tools mostly do not put the translator inside your running product. In-context editing exists as a feature, but it is rarely where the work happens by default. In the best case there is a way to flag a question and wait for an answer, and translators are typically paid per word, so a flagged question is unpaid time. Expecting someone to spend it on your ambiguous button label is expecting them to work for free. Mostly, reasonably, they don't. They make a sensible guess instead.

Which is the part that should worry you: a fluent translator guessing fails worse than a clumsy one. You get a sentence that reads beautifully and says something untrue. Nothing looks broken, so nothing gets caught, and it ships. As a German native, I know first-hand that these bad translations are so common even the sites and apps of big companies, that we've grown used to it.

A model can just look. Hand it the string, a note on where it appears, the glossary, the neighbouring strings in the same file, and it can go back, grepping your codebase for that key to see what renders around it. Fractions of a cent, and it happens for every string rather than only the ones somebody thought to flag.

None of that is about intelligence. Retrieval is cheap for a model and expensive for a person, and localization turns out to be a lot of retrieval.

The setup that works for us (our l10n workflow)

Four things, in order. The order matters.

Seed

pin the glossary, researched against real search results

Lanes

100 to 150 keys, one coherent surface per call

Translate

model A transcreates

Review

model B edits as a native

Apply

merge, rebuild, run the checks

The two highlighted steps must run on different models. A model reviewing its own output agrees with itself.

The whole setup. The order matters more than any single step: seeding before translation is what stops each lane re-deciding your vocabulary.

Seed the glossary before you translate a single string

Pin your vocabulary first, then let translation start. Not after, when you are cleaning up inconsistencies.

Without this, every lane an agent translates independently re-decides what language you use, and you end up with three renderings of "streak" in one app. With it, the fan-out that translates parts of your site and app is born consistent.

Treat seeding as research for your agent. The tempting shortcut a model is likely to take is guessing based on its weights (the "embedded knowledge"), and it is wrong often enough to lead to a bad result: we picked a term of art for one market on the reasoning that similar languages keep the English word, and shipped a phrase real practitioners genuinely use but no actual customer has ever typed into Google, so they won't find us when looking for it. Look at how people search, per language, before you pin anything. And split by audience where it matters, because the same concept can want the industry term on a page for professionals and plain outcome language on a page for beginners.

The reason this is now practical is that the research agent can do it itself. Give it a search tool, a SERP API like serper.dev or similar, and it runs real queries in the target market: what ranks for each candidate term, what autocomplete suggests, which phrasing the local competitors use on their pages. That is the half hour a human translator probably won't spend unless paid for, per term, per language, and it is the difference between a glossary that is merely consistent and one that is actually right and feels native.

serp lookup

es-419 · "comprehensible input"

input comprensible

≈ nobody

videos para aprender inglés

what they type

Both are correct Spanish. One is the term the field uses, the other is the phrase a learner actually searches. Pin both, and split them by which audience the page serves.

Seeding is research, not lookup. Deriving a term from the language family gave us a phrase practitioners use and customers never search for.

Worth researching per language: what your competitors call the thing, whether a pun or metaphor in your English survives, and whether the loanword everyone assumes is universal actually reads as native. A word that rides along untranslated in Latin-script European languages ("Feed," "Reels," "Creator") reads as foreign debris dropped into Arabic, Japanese or Cyrillic, where essentially everything gets translated or transliterated. The freedom to borrow an English word is itself a pretty Latin-script affordance. Decide it per language, never globally.

Every language you add can add to the glossary as part of the review step (that last step described later in this article), so they next one you're tackling starts off with a larger glossary base that covers more ground.

Split the work into lanes, sized deliberately

One translation call should hold one coherent group of strings, roughly a page, a screen of an app or a feature, in the range of 100 to 150 keys.

Extremes are bad. Feed a model your entire catalog in one call and you get truncation and tail drift, quality decays toward the end of a long output. Feed it one string at a time and it cannot "see" that the button on the same screen says something related, so it might lack the context that's so important.

The grouping is language independent. Work it out once and reuse it for every language you add.

Separate translate from review, and use a different model for each

A model reviewing its own output tends to agree with itself. Two passes by the same model is one pass with extra steps.

So: one model transcreates, a second and different model reviews as an independent native-speaker editor, and its fixes merge before anything is written. The review pass is what makes a cheaper translate pass safe, which is also a cost lever if you need one.

Which model, per language, is worth an afternoon of testing. Our default is a Sonnet copywriter per language inside Claude Code, with an independent Opus editor reviewing. The per-language differences were bigger than we initially expected. Kimi and Qwen handled both Chinese scripts better than the general-purpose models we tried (not super surprising if you think about it, but we were led there by trial and error), including the part that is not really translation, like knowing which term a Taiwanese reader uses rather than a mainland one. Mistral's Saba was ahead on Arabic.

Run the same strings through a few options and have a native speaker (if you have the luxury of getting feedback from one) say which output is better. Work through this for languages you and the people around you speak, get it right, then extrapolate the learnings for the l10n into languages you don't speak and can't quality check yourself.

Between my co-founder Roman and I we speak a few languages, so we focussed on those first, even if they're not the highest yield in terms of learners we have on the platform. Our golden rule: never hand-edit a string. When we find an issue, we improve the prompt for the agent so it gets it right on another pass, so it'll (hopefully) get it right in the languages we and the people who were kind enough to weigh in don't speak.

Tell the agent to transcreate, not translate

This makes a big difference:

The English source is a brief describing what the line has to accomplish and not the shape to preseve. Tell the model so explicitly: you are not required to keep the sentence structure, the word order, the placeholder position, the length, or the metaphor. Write what a native speaker would write from scratch for other native speakers, as if this website and app were born in France, or Germany or Brazil. If the English pun does not survive, cut it. If your language needs two sentences where English used one, use two!

Without that permission a model defaults to faithful, and faithful is sadly what makes translated texts like something a native speaker can spot as a thoughtless translation from a mile away.

One more thing to demand: an audit trail. Have the translator return its reasoning for every borderline call, the restructures, the idioms it swapped, the words it was unsure about. That is how we can sanity-check a language we don't speak. We cannot evaluate the Korean, but we can absolutely evaluate "I avoided the literal rendering here because it reads as a physical touch rather than looking a word up." Read the reasons if you can't read the output.

The part the setup cannot fix

Everything above assumes the strings are translatable in the first place. This is another pitfall, particular if English is your first (and/or only) language, and it's decided in your codebase before anyone (human OR machine) translates anything.

You might know the rule about never gluing sentences together from fragments. Keep the sentence in one string with the variable inside it. Follow it. It fixes word order, because you cannot get French chat noir out of two separate pieces.

But word order is the easy half. "One sentence, one placeholder" quietly assumes the value can drop into the sentence unchanged, and for a noun in most languages that is unfortunately false. The slot has to be in the right position and the right shape, and a placeholder only ever has one shape to give.

Your code resolves a variable to exactly one string, the dictionary form. Different sentences ask that word for wildly different things:

  • An article that fuses with the word. French wants l'anglais, not le anglais, and du français, not de le français. Neither is assemblable at the point of use, because what's necessary depends on the the word about to be inserted.
  • A case the position demands. Russian "videos in English" needs the prepositional, not the dictionary form. Fill it naively and you get the equivalent of "videos in to English."
  • A plural that is a table, not a rule. Polish takes one form for 1, another for 2 to 4, a third for 5 and up, looping on the last digit, so 22 and 25 behave differently. A real plural block handles it, but only if the string and your l10n tooling (we use inlang and Paraglide, it's a definite recommendation) was built to allow one.
  • A whole phrase collapsing into one word. Hungarian turns "in English" into the single adverb angolul. There is no sentence position where dropping the dictionary form produces it.

English inflects almost nothing, so an English-shaped string encodes English's grammatical "poverty" and exports it everywhere as though it were the default.

your resolver returns

English

one string, every time

French

an article fused to the word

l’anglaisle anglais

Russian

the case the position demands

английскоманглийский

Polish

a plural table, looping on the last digit

1 / 2 to 4 / 5+

Hungarian

the whole phrase as one word

angolul
A placeholder only ever has one shape to give. These are four different things real sentences ask of the same word.

Two fixes, both unglamorous. Give variable nouns a table of grammatical forms, so the sentence can ask for the case it needs. And when no shared frame can be made natural everywhere, stop trying: author one hand-written line per value and accept that you now have twelve strings where you wanted one. Extra strings are cheap with AI translations. It's how we ended up with just shy of 6,000 keyed strings across our app and website.

Fill those tables completely, including the entries where the platform default already looks right. A silent fall-through is how wrong words survive: the standard library's name for one language in another can be an endonym, or a nationality, and you will not notice for years.

The advantage of using coding agents for this is that they can both notice and fix these issues once they come across it in a language they're working on right now, and set up the next language for success.

The failure no tooling catches

One last thing, because it is the one that humbles every system described above.

A grammatically perfect translation can still be wrong. In German, attaching a language name as an adjective to a noun is correct German and reads as origin. "Spanish videos" becomes videos from Spain rather than videos spoken in Spanish, which for a catalog spanning twenty countries is not a style nit but a false statement. No morphology table catches that, because it was never a morphology problem. It needed a German speaker to look at a correct sentence and say "this means something else."

spanische Videos

Grammatically perfect. Means videos from Spain.

one country

Videos auf Spanisch

Videos spoken in Spanish, filmed anywhere.

every country that speaks it

No morphology table catches this, because it was never a morphology problem. It needs a German speaker to read a correct sentence and say "that means something else."

The failure no tooling catches: correct grammar that states something untrue.

So budget for that, and if you have the luxury of helpful supporters and customers, tap into their feedback as much as you can. It turns out we can't just do away with humans entirely just now. ;)

But for the most part, you can unlock new audiences by localizing with machines. Would we love to hire human translators instead? Sure. And we will once we've grown to a place where we can afford to!