Localization
The localization concern analyzes incoming requests for language preferences and allows the application logic to serve content based on those.
Basic Usage
By default, the concern will read the Accept-Language header
and provide the selected java.util.Locale as the current UI culture,
which can be read with CurrentCulture.uiCulture().
Webservices
import org.genhttp.engine.internal.Host
import org.genhttp.modules.i18n.*
import org.genhttp.modules.layouting.*
import org.genhttp.modules.practices.*
import org.genhttp.modules.webservices.*
suspend fun main() {
val localization = Localization.create()
val app = Layout.create()
.addService<LocalizedService>("service")
.add(localization)
Host.create()
.handler(app)
.defaults()
.development()
.run()
}
class LocalizedService {
@ResourceMethod
suspend fun getLocalized(): String = CurrentCulture.uiCulture().toLanguageTag()
}Functional
import org.genhttp.engine.internal.Host
import org.genhttp.modules.functional.*
import org.genhttp.modules.i18n.*
import org.genhttp.modules.practices.*
suspend fun main() {
val localization = Localization.create()
val app = Inline.create()
.get("/service/") { CurrentCulture.uiCulture().toLanguageTag() }
.add(localization)
Host.create()
.handler(app)
.defaults()
.development()
.run()
}Controllers
import org.genhttp.engine.internal.Host
import org.genhttp.modules.controllers.*
import org.genhttp.modules.i18n.*
import org.genhttp.modules.layouting.*
import org.genhttp.modules.practices.*
import org.genhttp.modules.reflection.*
suspend fun main() {
val localization = Localization.create()
val app = Layout.create()
.addController<LocalizedController>("service")
.add(localization)
Host.create()
.handler(app)
.defaults()
.development()
.run()
}
class LocalizedController {
@ControllerAction(Method.Get)
suspend fun index(): String = CurrentCulture.uiCulture().toLanguageTag()
}Running and accessing this example app via http://localhost:8080/service/ in your
browser will print the preferred locale of your client (such as de-DE). The selected
language is also announced to the client with the Content-Language header of the response.
GenHTTP sets CultureInfo.CurrentCulture and CultureInfo.CurrentUICulture of the current thread.
The JVM has no such per-thread culture, and a coroutine may continue on another thread after it
suspended, so genhttp-kt keeps the selected locale in the coroutine context of the content
(CultureContext): CurrentCulture.culture() and CurrentCulture.uiCulture() read it from
any suspending function called while the request is handled, and fall back to the default locale
of the JVM if none has been selected. The locale is also stored in the properties of the request
(CurrentCulture.CultureProperty and CurrentCulture.UiCultureProperty), so functions that do not
suspend can read it from there. The default locale of the JVM is never changed.
Language Negotiation
Typically your application will not support any language,
so the client and the server need to negotiate a language
that suits the client and the server can provide. Therefore,
the client will send a list of supported languages and their
ranking in the Accept-Language header, such as de,de-DE;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6,de-CH;q=0.5.
On the server side, you need to specify the cultures that are supported by your application, either statically or dynamically:
import org.genhttp.modules.i18n.*
import java.util.Locale
val localization = Localization.create()
// statically set supported cultures
localization.supports(Locale.GERMANY, Locale.FRANCE)
// dynamically evaluate support
localization.supports { culture -> culture.getDisplayName(Locale.ENGLISH).contains("ish") }A language requested without a region is resolved to its default region (de becomes de-DE,
en becomes en-US), so the locales you support should name a region as well.
The localization concern will then try to find the language requested by the client which is supported by the server and has the highest priority on the client-side. If there is no intersection between the two, the logic will fallback to the default locale that is used by the server environment. The default value can be overridden as needed:
import org.genhttp.modules.i18n.*
import java.util.Locale
val localization = Localization.create()
.default(Locale.forLanguageTag("pl-PL"))Parameter Source
As shown above, the server will analyze the Accept-Language header by
default to check the languages requested by the client. For API or web
applications you might want a different source instead:
val localization = Localization.create()
// read a query parameter, e.g. ?language=es
localization.fromQuery("language")
// read a cookie
localization.fromCookie("__my_app_language")
// read a custom header
localization.fromHeader("X-Custom-Language")
// read directly from the request, e.g. /de/...
localization.fromRequest { request -> request.header.target.current?.decode() }If needed, you can specify multiple sources at the same time.
A function passed to fromRequest may also return the locales directly (as an Iterable<Locale>)
instead of a language string. Kotlin chooses the variant by the return type of the lambda;
on the JVM, the variant returning locales is named fromRequestCultures.
Consuming the Language
As shown above, the server will provide the selected locale as the current UI culture. This behavior can be adjusted as needed:
import org.genhttp.api.content.authentication.User
import org.genhttp.modules.authentication.getUser
import org.genhttp.modules.i18n.*
import java.util.Locale
val localization = Localization.create()
// set current culture instead (read with CurrentCulture.culture())
localization.setter(currentCulture = true, currentUiCulture = false)
// use a custom logic
localization.setter { request, culture -> request.getUser<MyAppUser>()?.language = culture }
class MyAppUser(override val displayName: String) : User {
var language: Locale? = null
}Running in Docker
On the JVM, the locale data is part of the Java runtime, so there is nothing to install
in your Docker image when you use a regular JDK or JRE image. If you build a custom runtime
with jlink, add the jdk.localedata module (--add-modules jdk.localedata), which provides
the data of the locales other than US English.