GoSite
Every module on a GoSite site has to exist in every language it publishes. I rebuilt the translation pipeline that makes that affordable.

Summary
GoSite is a site builder I own. Pages are assembled from content modules, and each one has to exist in every published language. I rebuilt the multilingual translation pipeline behind it: batching, chunk packing, caching and reinjection, in Next.js and TypeScript over Firestore.
The Challenge
The first version translated one module per model call. It worked, and it could not get cheaper: cost scaled with the number of modules, and the model saw a fragment instead of a page, so it lost the context that decides how a sentence reads.
The goal: batch blocks into single requests without ever putting translated text back into the wrong module.
The constraint
GoSite builds sites out of content modules, and every module has to exist in every language a site publishes. The first version translated module by module: one model call per block. It worked, and it had two problems that got worse as sites grew. Publishing was slow and expensive, and the model saw a fragment rather than a page, so it lost the context that decides how a sentence should read.
This is my own product, so the architecture decisions here are mine to make and mine to live with.
What I decided
Batch blocks into one request instead of paying per block.The per-block version was the cheaper thing to write and structurally could not get better: the cost scaled with the number of modules, forever. Batching had a ceiling worth chasing, and it bought back the page context the model was missing.
Accept two hard problems in exchange.Packing blocks up to a token budget, and putting each translated fragment back into the exact module it came from. Reinjection is the one that bites: a batch that returns slightly out of shape will quietly write the wrong text into the wrong block, and the page still renders.
Cache instead of re-requesting.Sites are republished far more often than their content changes, so paying to translate unchanged blocks on every publish is the largest avoidable cost in the system.
What I built
Next.js and TypeScript on the server side, with Firestore for storage. I owned this end to end: the frontend integration, the server-side TypeScript, the debugging, the tests and the production rollout.
- A chunk packer that fills each request up to a token budget rather than sending whatever happens to be next.
- A reinjection layer that maps every translated fragment back onto its source module.
- A cache layer, so republishing unchanged content does not re-translate it.
- An automated test suite over the packing and reinjection paths, which is where the production bugs actually were.
What went wrong, and what I changed
Reinjection bugs reached production before the tests did. Content came back attached to the wrong module, which is the failure mode of this design and the one I should have written tests against first. Packing and reassembly is exactly the kind of logic that is cheaper to test than to debug in a live system, and I did it in the wrong order.
The suite that exists now covers those paths, and it exists because of what they cost.
Where it stands
The pipeline runs in production at gosite.dev, with token usage per publish materially below the per-block version it replaced.