๐ค Localization
You declare your locale set and supply catalogs from an embed.FS. Each
request resolves one locale, and your actions look strings up through a
request-scoped function.
Declaring
opts := rastrillo.Options{
Locales: []string{"en", "fr"},
DefaultLocale: "en",
LocaleFS: localeFS, // embed.FS carrying locales/<code>.toml
}
Catalogs are flat TOML, nothing nested:
orders.title = "Your orders"
orders.greeting = "Hello, {name}"
How a locale gets picked
In this order: a URL path prefix, stripped before your mux sees it so
/fr/orders and /orders reach the same route; then the
rastrillo_locale cookie, when it names a declared locale; then
Accept-Language, q-ordered, so a browser sending fr-CA matches a
declared fr; then your default.
The cookie is written by the framework's own POST /_locale route,
which rastrillo.Serve mounts whenever you declare locales at all. The
locale-menu partial renders a switcher that posts to it:
{{template "locale-menu" dict "Items" .Locales "Return" .Path}}
where .Locales is rastrillo.LocaleItems(r) โ empty for a one-locale
app, so the partial renders nothing โ and .Path is the current path
and query to return to. Each item lands on the same path under the new
prefix, and the cookie makes the choice stick on unprefixed paths too.
Looking strings up
rastrillo.T(r, "orders.title")
rastrillo.Tf(r, "orders.greeting", "name", user.Name)
Tf interpolates {name}-style placeholders.
A missing translation stays visible on the page as orders.title โ
never blank, never a crash. Lookup falls back through five levels,
listed in the next section.
The framework base catalog
The framework ships its own strings โ rastrillo/ui's rastrillo.ui.*
keys โ in twelve locales: en, ga, zh-Hans, es, hi, pt, bn,
ru, ja, yue, vi, ar. Declare one of those and the built-in
components speak it with no catalog of your own. Matching is by the code
you declare, exactly: zh does not find zh-Hans.
Lookup falls back through five levels: the requested locale's app
catalog, the default locale's app catalog, the framework's catalog for
the requested locale (when it ships one), the framework's English, and
finally the key itself. A missing translation stays visible on the page
as orders.title โ never blank, never a crash.
Your own catalog entry for the same key wins, so you can reword a component's built-in string without ejecting it.
rastrillo.Dir(locale) gives the <html dir> value โ rtl for
Arabic, Persian, Hebrew and Urdu โ so a layout never guesses.
Inside a partial, a caller-supplied value beats the built-in default.
ui.FuncsWith rebinds T to a request-scoped lookup so those defaults
resolve in the request's locale rather than in hardcoded English:
tmpl.Funcs(ui.FuncsWith(func(key string, args ...any) string {
return rastrillo.Tf(r, key, args...)
}))
The pre-ship gate
rastrillo generate --check --default-locale en
Fails when a non-default catalog is missing keys the default has.
It also fails when a non-default catalog is for a locale the framework
does not ship and leaves any rastrillo.ui.* key untranslated โ those
components would silently render in English. The message lists the keys;
copy them from the module's locales/en.toml.
This runs under --check only. Plain rastrillo generate โ and so
rastrillo dev and rastrillo new โ never fails on an incomplete
catalog. That split is the design: silent fallback while you iterate,
loud failure before you ship.
--default-locale defaults to en and is not read from
Options.DefaultLocale. If your app sets a different default, pass the
matching value here, or the check compares every catalog against the
wrong one and passes while telling you nothing.
Two caveats
A locale code cannot also be a first path segment. An app declaring
locale en cannot serve an app route at /en/..., because the prefix is
stripped before the mux sees it. That is inherent to prefix routing.
A trailing-slash redirect under a locale prefix drops the locale. When
ServeMux issues its own trailing-slash redirect for a path under a
prefix, it emits the unprefixed path. Known limitation, affecting that
one redirect.
Read this page as markdown โ exact, unstyled, and cheap for an agent to fetch.