Kraft Integration
Switch language, and every subscribed component re-renders.
Register a controller
I18nController is a Stream<I18n>. Register it on the app and the whole
component tree can reach it.
val kraft = kraftApp {
semanticUI()
i18n(
I18nController.create(
locale = Locale("en"),
fallback = Locale("en"),
) {
install(AppI18nCatalog)
}
)
} create persists the chosen language in localStorage (key
kraft.i18n.lang by default). Boot precedence is: the stored value, else
initialLang, which itself defaults to the browser's language. Use
I18nController.inMemory(...) for tests and anywhere you do not want persistence.
Kraft's own form catalog is always installed first, at the lowest precedence, so validation messages translate even if an app forgets to provide anything. App catalogs install afterwards and win on shared keys.
Using translations in a component
class GreetingComponent(ctx: Ctx<Props>) : Component<Props>(ctx) {
private val t by Translations
override fun VDom.render() {
h1 { +t.checkout.greeting(name = props.name) }
}
} The by Translations delegate subscribes to the controller's translation stream, so the
component re-renders when the language changes and unsubscribes when it unmounts. There is a matching
by Formatting delegate for the formatting surface.
kraftApp { } always installs a default controller, so by Translations works
in an app that has not configured i18n — unresolved keys come back as the key itself. It is not
a fallback inside the delegate: looking up a missing attribute throws, and the default registration
is what stops that happening.
Switching language
i18nController.setLang(Locale("de")) The controller maps the chosen tag through Locale.parse onto a fresh I18n
snapshot and emits it. Every subscribed component re-renders in the new language, and the choice is
persisted.
Form validation
Kraft's form rules resolve their messages through the same surface, so validation errors follow the selected language with no extra wiring:
UiInputField(value, onChange = { value = it }) {
label("Email")
accepts(notBlank(), isEmail())
} Rules produce their message via the i18n surface rather than a hardcoded string, and an app overrides any of them by defining the same key in its own catalog — app catalogs install last and win.
Async rules
Validation rules may be suspending — a uniqueness check against the server, for example. The form controller exposes a status so the UI can disable submit while checks are in flight, and a guard so a form cannot be submitted while validation is still running.