Two languages, one site

Part 2 of 2 of ./blog.sh v1.9

Somebody writes in Czech, and some of the people who would read it don't read Czech. The usual answer is a second blog — a second archive, a second set of tags, a second feed — and within a year the two have drifted apart: a post fixed in one and not the other, a tag renamed on one side, a reader who switches languages and lands somewhere that no longer matches. blog.sh 1.9 gives a different answer: one site, and the other language beside it.

One post, a text per language

A post keeps everything that is true of it once: its date, its tags, its series, whether it is pinned, whether it is published. What changes with the language is the words — the title and the text — and those sit side by side inside the same post:

./blog.sh translate a-slug-is-an-address-not-a-name --lang cs

The editor opens with the title and the body and nothing else, because that is all a translation is. The post is still one post: publishing it publishes it in every language, and a tag added to it is added in every language. There is no way for the Czech copy to be a draft while the English one is live, or to be filed under a different tag.

Nothing has to be migrated to start. A post with no translation is simply a post in the site's own language — which is every post in every archive until the first translate.

Where each language lives

The site's own language keeps the root, and every other language gets a root of its own:

/posts/2026/a-slug-is-an-address-not-a-name/
/cs/posts/2026/slug-je-adresa-ne-jmeno/

The root does not move when a second language arrives, so every link anyone has ever made to the site goes on working. The Czech copy gets a Czech address — made from the Czech title the first time it is written, then left alone, the same way the post's own address is. Pictures, the stylesheet and the writing app exist once and are shared; the sitemap names every language and says which addresses are the same page in another.

When a translation is missing

A post nobody has translated yet is not hidden from the other language. It stays in the Czech listing, in English, and its link leads to the one copy it has — so the same words never stand at two addresses, and a reader browsing in Czech still sees everything the site has written.

On a site with three languages, what stands in for a missing one is yours to decide. One line in the language's own file says that German, when it has nothing, borrows the Czech text rather than the English — and the words and the link always come from the same answer, never Czech text under a link to the English page.

Switching

The switcher is a small chip in the banner's corner, next to the light/dark button, and it works the same way: a click moves to the next language, and wraps around at the end. Which language you read in is a choice about this visit, like which mode you read in, so it sits where that choice already lives — not in the menu, which it would break in two. From a post it leads to that post in the other language; where the post has no copy there, it leads to that language's front page instead of to a page that doesn't exist.

Knowing what is written where

With two languages, the question an archive raises is no longer only "what is broken" but "what is missing", and it gets its own report:

$ ./blog.sh check --languages
                                               en  cs
a-badge-that-says-what-a-post-is               ✅   ✅
a-bicycle-nobody-had-to-draw                   ✅   ✅
…
how-big-it-can-get                             ✅   ·
the-lock-that-doesn-t-wait                     ✅   ·
the-parts-that-don-t-rebuild-themselves        ✅   ·

That is this site, today: every published post in both languages, and three drafts that haven't gone out yet still waiting for their Czech words. A plain check doesn't count those as problems, because a post that exists in one language isn't wrong. Publishing is where it matters, so publish stops and names the languages a post has no words in, and offers --allow-partial in the same sentence for the times a post is meant to go out in one language only.

The title over the page, the menu and the footer have words of their own too, and they are translated in a file beside the site's config — that is a post of its own. What this one is about is the part that decides whether two languages stay one site: a single archive that knows, post by post, which words it has.

Comments