← Back to articles
Web 2026-04-17 · rev. 2026-05-10 ProtoMedia

A site that speaks dozens of languages, without translators

Automatically translated from Italian · see original

How we built the translation system that powers these pages: AI on demand, always caching, zero perceived waiting time.

The problem with traditional i18n

For years, internationalizing a website has been a small workflow odyssey: .po files, human translators, forgotten keys, delayed deployments because "the German is still missing". Adding a language cost time and money; adding ten was out of the question for any small company.

With the latest generation of LLMs, the quality of machine translation has reached a point where — for a corporate website, not for literature — it is sufficient. The question is no longer "can we translate it automatically", but "how do we integrate it without breaking the user experience".

The architecture, in brief

The site is served statically in Italian. When a user chooses another language, a small engine comes into play on three MongoDB collections:

  • i18n_base: the source texts in the chosen language (today Italian), each with the declaration of its own language, a description of where and why it appears on the site (essential for translation quality), tone and character limit. This description becomes part of the prompt to the LLM, so it knows whether it is translating a button or a paragraph.
  • i18n_human: manually curated translations, when we want editorial quality for some pages instead of the LLM output. They take priority over the automatic cache and are automatically "unmarked" when the source text changes.
  • i18n_cache: already generated translations, indexed by (field, language). Each entry preserves the timestamp of the base text at the time of generation.
  • articoli_i18n: same logic applied to longer content, such as this article.

On the first access in a new language, the system asks the LLM only for the truly missing fields, saves the response in the cache and returns it to the browser. On subsequent visits, the same language arrives instantly, without any more AI calls.

Invalidation that works

A cache is an ordered lie: it says "my truth is this" until someone disproves it. The debunking mechanism here is simple: each cached translation stores the timestamp of the base text that generated it. When you modify the Italian text and bump its updated_at, all translations of that field in all languages automatically become stale and are regenerated as needed. Zero manual cascades, no cache to flush.

The user who doesn't wait

The most interesting part isn't the translation — it's how it's displayed. When changing languages, the browser faces an Italian page with small spinners next to each block of text to be updated. The temptation is to make a single call that translates everything together: simple, clean, and condemns the user to wait for the LLM to finish everything before seeing any changes.

We took the opposite direction. Texts are grouped by section (navbar, body, footer) and broken down into micro-batches of three fields each. The calls start sequentially: first the few fields of the navbar, which appear in seconds — the user sees that something is happening and starts reading. While reading, the first piece of the body arrives, then the second, finally the footer.

The lesson in brief: Perceived speed is not total speed. Translating a piece at a time, in visible order, counts more than finishing first.

Night work

A cache has only one true enemy: the first visit. The first German user pays the price of all future translations, and that's the experience that sticks with them. To eliminate (or almost) the problem, a background task wakes up every minute, chooses the most awaited language among those still incomplete, takes an untranslated text and passes it to the LLM. The next minute, the next one. In a few days, the most widespread languages are already warm.

The night task also does a second job: it automatically reviews low-quality translations, retrying them with a different AI model. When alignment between languages is important, a mediocre translation does not remain as such.

If you are the first German of the day and arrive while the system is still finalizing the cache, you still have progressive loading to keep you busy. If you are the second, you won't even notice it.

What you take home

A multilingual website without translators, without files to manage, without deployments to add a language. The user enters a code — ja, ca, eu, whatever they want — and the page translates. The second time it's instantaneous. By the third time, we've most likely already gotten there, translated at the right moment by a task that never sleeps.

This very page, if you are reading it in German, Spanish, or Korean, was not written by a translator at any time. It was translated once, by an LLM, and has been served from the cache ever since. The only thing written by hand is what you are reading now if it's in Italian.

Interested in bringing this architecture to your site? Let's talk about it.

Contact us

Have feedback? Write to us

This message is for our eyes only. We may publish your comment at the end of the article if it’s interesting, but only after reviewing it.

As you type, your browser is solving a small calculation – our way of keeping out automated submissions without using third-party services or asking you to identify traffic lights. Nothing is requested of you and no data leaves this site.