The site speaks the language of the page

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

Somebody translates their posts, opens the Czech half of the site — and the menu still says Writing, the footer still says Links, and every tag on every pill is the English word. The posts were only ever half of what a reader reads. The rest is what the site says about itself, and in 1.9 that has a language too.

One file per language

What the site says in Czech lives in config/site.cs.yml, beside the config. Start one from config/site.lang.yml.example — copy it, and its comments say the rest. It takes the same keys config/site.yml has for its words, written in the other language, and whatever it leaves out stands as site.yml has it. This site's own, trimmed:

# config/site.cs.yml
site:
  description: "Minimalistický blogovací engine, který ovládáš z terminálu …"
nav:
  - { label: "Vše", url: "/" }
  - { label: "Začni tady", url: "/posts/2026/blog-sh/" }
  - { label: "Proč", tag: "philosophy" }
about:
  heading: "O projektu"
footer:
  note_heading: "Licence"
  copyright: "Texty a obrázky © 2026 Daniel Šnor."
tags:
  philosophy: "filozofie"
  authoring: "psaní"

The title, the description, the banner's claim and its alt text, the about card, the footer and the menu: those are words, and they translate. The picture in the banner, the author, the address, the colours, the widgets' accounts are facts about the site, the same in every language, and there is no key for them here — translating a fact would make a second site.

Some headings translate themselves

"About" over the about card, "Links" and "Find me on" over the footer columns, and each widget's heading are the engine's own words when site.yml does not write them — and then every language says them in its own words, with nothing to translate. Write one in site.yml only to say something else, and translate that one. The wizards leave the engine's words out of site.yml for the same reason: frozen into the config, they would stay in one language.

A tag is an ID with a word per language

A post's tags are metadata, written once, like its date. So a tag keeps one ID in every language — one page per language at the same address, /cs/tag/philosophy/ — and only what a reader sees changes: the word on the pill, over the tag's page, in its feed, in the tag index and in what the search box finds. tags: gives each tag its word, keyed as the posts write it. A tag with no word shows as written, and a word for a tag no published post carries shows nowhere; check says so, and the build goes on.

The menu goes to the same places

The menu and the footer links are written out in full, the way site.yml has them — but every item goes where the same item goes in site.yml, in the same order. Only the label changes. A menu that drifts apart between languages is a second site, and a reader who switches language would lose the item they were reaching for. So the build refuses one that drifts, and says where:

❌ site.cs.yml: nav has to go to the same places as in config/site.yml, in the same order -- only the words change. Item 2: config/site.yml goes to tag:philosophy, site.cs.yml to tag:authoring.

The address in an item is still the one the site's own language writes. url: /posts/2026/blog-sh/ above leads to the Czech copy of that post in the Czech menu, whatever its Czech slug is — the item follows the piece, not the spelling. A tag: item leads to the tag's Czech listing, and the front page, the search and the archive to their Czech copies.

What stays in one language

The writing app is one copy for the whole site, and it speaks the site's own language, because that is the language its author writes in. And the rules are enforced rather than hoped for: a key the engine does not read is refused, so is a translation of something site.yml does not have — an about text only one language would show — and a language file that is not valid YAML stops the build, with check and doctor naming the line.

That is the whole of it: posts say their own words, and one file per language says the site's.

Comments