Kolofon · 11 September 2026
0.13.0 — two languages, one deployment
when the English version stopped being a separate site

Text: Kolofon
For a month the English version of this site lived next to the Polish one like a neighbour through the wall: its own subdomain, its own repository, its own database, its own deployment. Every fix went out twice, and every new entry meant two pushes and two checks. 0.13.0 ends that arrangement. Both versions now run in one deployment, and the English one lives under `/en`.
What decides the language
Two things, and neither is a new config field. **The folder decides an entry's language**: `src/content/posts` is the default version, `src/content/en/posts` is English. **The address decides a page's language**: a version registered with the URL `https://…/en` gets the `/en` prefix, because that is what its `url` says. Every Polish address stayed exactly where it was.
locales: {
en: {
config: enConfig, // url: "https://…/en"
posts: import.meta.glob("./en/posts/*/*.json", { eager: true }),
authors: enAuthors,
},
},Language as request state
The harder half was not routing. It was that `cfg()` is called in hundreds of places and until now always returned the same thing. Now it has to know which version the request is in. A global variable was out straight away: one Worker isolate handles requests concurrently, and two renders interleave at every `await`. On the server the language rides in AsyncLocalStorage, in the browser it is the prefix in the address bar. The test: eighty concurrent requests in Polish and English, not one string from the wrong version.
The first idea for the prefix, the router's `basepath`, did not survive contact with TanStack Start: the server overwrites it on every request with the build-time value. `rewrite` survived — the router sees paths without the prefix, the browser sees them with it. Server functions go to a shared address with no prefix, so the browser attaches the language as a header.
The database
Things shared across languages — ratings, comments, votes — were already keyed by the entry key. What needed fixing was what belongs to a page: views now go to `page_views` with a `lang` column, events get `page_lang`, push subscriptions their own language. The old views table stays untouched. Rewriting the data is done by a migration recorded in a new `schema_migrations` register, and the old English database is carried over by a separate script that stops before its first write if it meets a table it does not know.
Along the way it turned out the event log of this site had not recorded anything since 14 August. Writes went to a `browser_id` column that nothing ever created, and the error vanished into a silent `catch`. The column is now created, and a failed write goes to the Worker log.
What this does not change
A single-language instance declares no `locales` and behaves exactly as in 0.12 — with one exception: its view counters move once to the new table under the default language. The old address of the English version still answers for now; the redirect to `/en` comes separately.
