Multilingual

A multilingual lazysite is a set of sibling domains - one per language - each a multi-site content root, declared as members of one language set (SM179). The engine knows the siblings, so it supplies every layout with ready-made switcher data, emits correct hreflang alternates in heads and sitemaps, and reports translation coverage. Translation itself stays in plain files, editable by tools and humans alike.

Configuration

Each member host declares its own lang (a BCP 47 tag) and a shared lang_group (the set name), settable in lazysite.conf, via domain-set, or on the Domains Add/Configure forms:

lang: en
lang_group: providers

alias_hosts: providers.example, de.providers.example
alias.providers.example.content_root: sites/providers
alias.providers.example.site_url: https://providers.example
alias.providers.example.lang: en
alias.providers.example.lang_group: providers

alias.de.providers.example.content_root: sites/providers-de
alias.de.providers.example.site_url: https://de.providers.example
alias.de.providers.example.lang: de
alias.de.providers.example.lang_group: providers

lang alone marks a monolingual site that knows its language: it lands in <html lang> and the Content-Language header (a page may override with front-matter lang:; both are sanitised to a bare tag first). A set needs at least two members; the base host, when it is a member, is the source language, otherwise the first member is.

The sibling-mirror convention

Sibling roots mirror the source root's file layout: compare.md in one language corresponds to compare.md in every other, and a missing file means "not translated yet". No manifest, no mapping table - the tree is the mapping. Layout, theme and assets are shared; front matter, body Markdown and data JSON are translated per root (values, never keys or structure).

The switcher and hreflang

For every rendered page the engine supplies [% languages %]: an ordered list of { lang, url, current, exists }, where url is the sibling's site_url plus the same path and exists stats the counterpart page. A layout renders a switcher from it in a few lines of TT; the default layout includes one, listing only languages whose counterpart exists. The same data drives <link rel="alternate" hreflang> pairs in the head (plus x-default on the source language), and per-domain sitemaps gain xhtml:link alternates for entries whose counterparts exist - so search engines see the set correctly with no hand-rolled markup.

Shared data and strings

  • json: page-variable sources resolve against the requesting domain's content root first, then the docroot - so tt_page_var: nav: json:/data/nav.json is byte-identical across roots, and a per-language data file transparently overrides a shared one; ::: include is likewise resolved within the content root
  • Layout chrome resolves through [% t %]: layouts/<layout>/strings/<lang>.json overlays strings/en.json, so a key missing from the site language falls back to English rather than vanishing
  • The engine's own emitted pages (the bare 404, auth reject pages) are localised from a built-in English table overlaid by lazysite/i18n/<lang>.json, fail-closed to English

Translation coverage

The lang-status action (control API and MCP, gated manage_content) compares each sibling root to the source root, file by file, and reports each page as current, stale or missing, with totals per root. Staleness is mtime-based by default; a translation tool may record translated_from: <content-hash> in front matter for exact staleness. An agent re-translates exactly the reported set - safe to re-run, no bookkeeping of its own.

Notes

  • Language keys ride the alias-override whitelist: operator configuration, never request-selectable beyond declared hosts
  • A configuration-only change invalidates the page cache, so a language or chrome edit is never served stale
  • There is no Accept-Language redirect and no silent fallback serving: a missing translation is omitted from the switcher, not papered over
  • whoami and the MCP discovery surface report the language set, its members and the source language, so a connecting agent learns the conventions without reading the docs