Localization with Kobweb and Libres
- Prerequisites
- Adding Libres to Your Project
- Create String Resource Files
- Replace Hardcoded Strings with Localized Strings
- Use Browser's Preferred Language
- Manually Choose Different Languages
- Conclusion
Localization is crucial for making your web application accessible to a global audience. In this article, we will walk through the steps to add localization functionality to Kobweb using Libres, a library for managing resources in Kotlin Multiplatform projects.
Prerequisites
For this article, we will assume you already have a basic understanding of Kobweb. For Libres, you can refer to the documentation for plural rules and notes on how to change the locale. As an example we will use the default Kobweb app template, the full source code can be found here.
Adding Libres to Your Project
To use Libres, add the library to your project's dependencies.
Modify gradle/libs.versions.toml
Open the gradle/libs.versions.toml
file and add the following lines:
[versions]
libres = "1.2.2"
[libraries]
libres = { module = "io.github.skeptick.libres:libres-compose", version.ref = "libres" }
[plugins]
libres = { id = "io.github.skeptick.libres", version.ref = "libres" }
Modify site/build.gradle.kts
Next, open the site/build.gradle.kts
file and configure the Libres plugin:
plugins {
alias(libs.plugins.jetbrains.compose)
alias(libs.plugins.kobweb.application)
alias(libs.plugins.libres)
}
kotlin {
sourceSets {
commonMain.dependencies {
implementation(compose.runtime)
implementation(libs.libres)
}
}
}
libres {
// https://github.com/Skeptick/libres#setup
generatedClassName = "Res"
generateNamedArguments = true
baseLocaleLanguageCode = "en"
}
Create String Resource Files
Now, let's create the string resource files for each locale. We'll create XML files for English (strings_en.xml
) and German (strings_de.xml
) translations.
Create strings_en.xml
Create a new file at site/src/commonMain/libres/strings/strings_en.xml
with the following content:
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<string name="locale">en</string>
<string name="template_starting_point">Use this template as your starting point for</string>
<string name="about_part1">You can read the</string>
<string name="about_part2">page for more information.</string>
<string name="about">About</string>
<string name="cta">This could be your CTA</string>
</resources>
Create strings_de.xml
Similarly, create a new file at site/src/commonMain/libres/strings/strings_de.xml
with the following content:
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<string name="locale">de</string>
<string name="template_starting_point">Verwenden Sie diese Vorlage als Ausgangspunkt für</string>
<string name="about_part1">Lesen Sie die</string>
<string name="about_part2">Seite für mehr Informationen.</string>
<string name="about">Über uns</string>
<string name="cta">Dies könnte Ihr CTA sein</string>
</resources>
Replace Hardcoded Strings with Localized Strings
Replace the hardcoded strings in your Kotlin code with references to the localized strings from the resource files.
Update Index.kt
Modify site/src/jsMain/kotlin/pages/Index.kt
to use the localized strings:
@Composable
fun HomePage() {
Box(Modifier.fillMaxSize().padding(2.cssRem)) {
Column {
Div(HeadlineTextStyle.toAttrs()) {
SpanText(Res.string.template_starting_point)
}
Div(SubheadlineTextStyle.toAttrs()) {
SpanText(Res.string.about_part1)
Link("/about", Res.string.about)
SpanText(Res.string.about_part2)
}
val ctx = rememberPageContext()
Button(onClick = {
ctx.router.tryRoutingTo("/about")
}) {
Text(Res.string.cta)
}
}
}
}
Use Browser's Preferred Language
To enhance the user experience, we can set the language based on the browser's preferred language.
In order to do this, we use the language property
available in all browsers.
To allow users to manually select their language, we will first look for an entry in the local storage. If it's not present, we will check the browser's preferred language. If that's not available either, we will default to the first locale in the list.
First, add a helper property to keep track of the available locales in your app.
val Res.locales get() = listOf("en", "de")
Update AppEntry.kt
Modify site/src/jsMain/kotlin/AppEntry.kt
to set the language based on the browser's preferred language:
const val LOCALE_KEY = "locale"
@App
@Composable
fun AppEntry(content: @Composable () -> Unit) {
LibresSettings.languageCode =
localStorage.getItem(LOCALE_KEY)
?: Res.locales.find { it == window.navigator.language }
?: Res.locales.first()
SilkApp {
// Your existing code
}
}
Manually Choose Different Languages
Finally, let's create a dropdown to allow users to choose their preferred language manually. You can put this dropdown in your header or footer for easy access. We will update the language setting in the local storage and reload the page to apply the changes.
@Composable
fun LanguageDropdown() {
Select({
onChange {
localStorage.setItem(LOCALE_KEY, it.target.value)
window.location.reload()
}
}) {
Res.locales.forEach { locale ->
Option(locale, { if (locale == Res.string.locale) selected() }) {
SpanText(locale.uppercase())
}
}
}
}
Conclusion
Congratulations! You have successfully added localization functionality to your Kobweb site using Libres. For a live demonstration you can visit the Fluense web app where we applied this exact way of localization to improve accessibility and user experience.
It is important to note, that while this method is rather simple, it does not provide SEO-friendly localization. By using this method only the default locale will be picked up by crawlers. In the next part of this series, we will explore how to add SEO-friendly localization to your Kobweb app.
Happy coding!