Localization with Kobweb and Libres

May 25, 2024 by Fluense

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.

Prerequisiteslink

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 Projectlink

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 Fileslink

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 Stringslink

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 Languagelink

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 Languageslink

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())
            }
        }
    }
}

Conclusionlink

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!


Subscribe to our newsletter

2024 © FluenseAll rights reserved