Translations
PlumeKit resolves a request's language once and makes a t() function available to handlers and views, so you don't pass a locale around. Strings are compiled into the app (no runtime file parsing), so the same translations work natively and on the edge.
Setup
Put one JSON file per language in Translations/, named by locale. Each is a flat map of key to string:
// Translations/en.json
{ "welcome.title": "Welcome back", "greeting": "Hello, {name}" }// Translations/pt.json
{ "welcome.title": "Bem-vindo de volta", "greeting": "Olá, {name}" }The build compiles them into a plumeKitTranslations value automatically. Set the fallback language, used when a request matches none, in plumekit.toml:
[i18n]
default = "en"Scaffolded apps already register the middleware in buildApp() (app.use(localization(plumeKitTranslations))), so once you add the files, t() works.
Using it
In a handler or a view, call t("key"). Placeholders in {braces} are filled from named arguments:
// handler
return .text(t("greeting", ["name": user.name]))<h1>{t("welcome.title")}</h1>
<p>{t("greeting", name: user.name)}</p>A missing key renders the key itself (so an untranslated string is visible, not blank), and a missing placeholder is left in place.
Choosing the language
The middleware picks the language in this order:
- a
?lang=query parameter, - a
localecookie, - the request's
Accept-Languageheader, - the
[i18n]default.
To honor a signed-in user's saved preference, override the active language for the rest of the request:
app.use { request, next in
if let user = try await currentUser(request), !user.language.isEmpty {
useLocale(user.language)
}
return try await next(request)
}currentLocale returns the active language if you need it.
In client scripts
Inside an @script block you can call t("key") too. In the compiled build the framework injects the active language's strings into the page, so the client t() returns the right translation:
<button @script { on click { window.alert(t("confirm.delete")) } }>Delete</button>This works in the compiled build only. Under the interpreter t() is not defined, so if you render templates that way, translate in the view instead and read the value from the DOM:
<button data-confirm="{t("confirm.delete")}"
@script { on click { window.alert(el.dataset.confirm) } }>Delete</button>The data-attribute form is translated server-side, so it works in every render mode.