astwerk

Locales & i18n

Locale subtrees are derived from the base tree, not written twice.

A multilingual site is the same site with different text. A locale's subtree is generated from the base tree; you list only the pages that differ.

Documentation

Locale

type Locale struct {
	Code     string
	Prefix   string
	Override map[string]func(ssg.Node) ssg.Node
}
Field Meaning
Code language code and mount directory (de/de/...)
Prefix URL prefix passed to pages; defaults to "/" + Code
Override patches for the nodes that differ, keyed by path relative to the locale root ("" is its home page)

BuildLocales

root = ssg.BuildLocales(root, []ssg.Locale{})

BuildLocales copies the base tree and mounts each copy under the locale's code. Everything not named in Override — every other page, including generated collection children — appears under /de/ on its own, rendered by the same components with Ctx.Prefix set to "/de".

One line mounts a whole mirror; the override swaps in the one page whose text genuinely differs:

root = ssg.BuildLocales(root, []ssg.Locale{{
	Code: "de",
	Override: map[string]func(ssg.Node) ssg.Node{
		"": func(n ssg.Node) ssg.Node { // the locale's own home page
			n.Title, n.Page = "Meine Seite", ssg.Templ(views_de.Home)
			return n
		},
	},
}})
What exactly is derived

BuildLocales copies the base tree and mounts the copy under the locale's code. The copy carries Page, Files, Generate and Children — and drops CopyFrom and CompileFrom (see below). Overrides are applied shallowest-first, and branches that end up with nothing to render are pruned. The deep mechanics are on How it works.

Overrides

An override is a function over the derived node, not a replacement for it: it receives the node BuildLocales would otherwise have derived, so changing one field is one line and nothing you don't touch is lost.

"about": func(n ssg.Node) ssg.Node {
	n.Title = "Über mich" // Page and Children survive untouched
	return n
},

A key with no counterpart in the base tree gets the zero Node, which is how a page that only exists in one language is written:

"impressum": func(n ssg.Node) ssg.Node {
	return ssg.Node{Title: "Impressum", Page: ssg.Templ(views_de.Impressum)}
},

Assets are not inherited

CopyFrom and CompileFrom are dropped from derived subtrees — a locale mirrors pages, not assets. A locale that genuinely needs its own asset sets the field back in an override.

Why assets aren't inherited

Stylesheets and scripts are shared across languages and served from the site root. Inheriting them would produce build/de/style/, duplicating every asset per language — and every new asset would silently grow every locale's output.

templ Nav(c ssg.Ctx) {
	<a href={ c.Link("projects/") }>Projects</a>
	<a href={ c.Link("about/") }>About</a>
}

Link applies the prefix for you: the same markup yields /projects/ in the base tree and /de/projects/ in the German subtree.

InLocale

templ LocaleSwitch(c ssg.Ctx) {
	<a href={ c.InLocale("") }>English</a>
	<a href={ c.InLocale("/de") }>Deutsch</a>
}

InLocale returns this page's URL in another language, which works because a locale subtree mirrors the base tree — the same page exists at both paths. Don't build it from Ctx.Path: Path already contains the current locale segment, so re-prefixing gives /de/de/about/. Ctx.Rel is the locale-independent form.

Why derived rather than declared

The obvious API is a Tree func() ssg.Node per locale. It was tried and thrown out: it forces duplication, because the only thing you can hand it is a whole subtree restating the structure you already wrote — every page added to the base tree then has to be added again per language, and the two drift. Deriving inverts that: the default is "the same site", and an override is an exception you opt into.

Example

root = ssg.BuildLocales(root, []ssg.Locale{{
	Code: "de",
	Override: map[string]func(ssg.Node) ssg.Node{
		"": func(n ssg.Node) ssg.Node {
			n.Title, n.Page = "Meine Seite", ssg.Templ(views_de.Home)
			return n
		},
	},
}})

One line mounts a German mirror of the whole site; the override swaps in the one page whose text genuinely differs.