
One of the strengths of building with Compose Multiplatform is the ability to reuse business and UI logic across Android, iOS, Desktop, and Web (experimental). But when building production-ready apps, localization is essential for delivering a personalized experience to global users.
In this blog post, We’ll walk through a simple approach to implement language switching in a Compose Multiplatform app using a shared changeLanguage function—tailored to each platform.
📁 Step 1: Organizing Language Resources in Compose Multiplatform
Unlike traditional Android apps that use res/values-<lang>
directories, Compose Multiplatform projects should place all localization resources under the composeResources
folder in commonMain
.
commonMain/
├── composeResources/
│ ├── values
│ ├────string.xml
│ ├── values-hi
│ ├────string.xml
│ ├── values-es
│ ├────string.xml
Each string.xml
contains localized string resources using the same keys.
Example:
<!-- values/string.xml --> <resources> <string name="email">Email</string> <string name="password">Password</string> </resources>
<!-- values-hi/string.xml --> <resources> <string name="email">ईमेल</string> <string name="password">पासवर्ड</string> </resources>
Accessing Resources:
The org.jetbrains.compose.resources Gradle plugin processes these files and generates a type-safe Res object (you might need to run a Gradle build like ./gradlew generateComposeResClass for it to appear). You access these resources in your Composables using functions like stringResource
💡 Example: Login Screen Using Localized Strings
import org.jetbrains.compose.resources.stringResource import your.project.package.generated.resources.Res // Import the generated Res object Text( text = stringResource(Res.string.email), style = MaterialTheme.typography.titleLarge ) OutlinedTextField( value = email, onValueChange = { email = it }, label = { Text(stringResource(Res.string.email)) }, modifier = Modifier.fillMaxWidth() ) OutlinedTextField( value = password, onValueChange = { password = it }, label = { Text(stringResource(Res.string.password)) }, modifier = Modifier.fillMaxWidth() ) Button(onClick = { /* login */ }) { Text(stringResource(Res.string.button_login)) }
✅ Avoid hardcoded strings like "Login"
or "Enter email"
—always use stringResource(Res.string.<id>)
for localization support.
🧩 Step 2: Platform-Agnostic Expect Function
We begin by declaring a platform-agnostic changeLanguage
function using Kotlin’s expect/actual
mechanism.
// commonMain/kotlin/com/yourpackage/app/LocalizationWrapper.kt expect fun changeLanguage(language: String)
🤖 Android Implementation
On Android, we update the default locale using Locale.setDefault()
.
// androidMain/kotlin/com/yourpackage/app/LocalizationWrapper.kt actual fun changeLanguage(language: String) { val locale = Locale(language) Locale.setDefault(locale) }
🍏 iOS Implementation
On iOS, we use NSUserDefaults
to update the AppleLanguages array. This is how iOS handles preferred languages.
// iosMain/kotlin/com/yourpackage/app/LocalizationWrapper.kt actual fun changeLanguage(language: String) { NSUserDefaults.standardUserDefaults .setObject(arrayListOf(language), "AppleLanguages") }
🖥️ Desktop (JVM) Implementation
For desktop, it’s as simple as updating the default locale just like Android.
// desktopMain/kotlin/com/yourpackage/app/LocalizationWrapper.kt actual fun changeLanguage(language: String) { val locale = Locale.of(language) Locale.setDefault(locale) }
🔍 What this does:
This functions updates the default locale at runtime for your Compose Multiplatform app.
🧠 Step 3: CompositionLocal for Propagating Language
Use CompositionLocalProvider
to manage the app’s current language across composables:
// commonMain/kotlin/com/yourpackage/app/App.kt val LocalLocalization = staticCompositionLocalOf { "en" } var languageCode by remember { mutableStateOf("en") }
CompositionLocalProvider(LocalLocalization provides languageCode) { Column { Button(onClick = { languageCode = "hi" changeLanguage("hi") }) { Text("Change Language") } App() // Your localized content } }
This approach enables you to reactively rerender the UI when language changes. Combine this with string resources using a wrapper or localization library to provide multi-language support.
Job Offers
🚀 Demo

📝 Final Thoughts
Localization is not just about translation — it’s about creating an inclusive and accessible user experience. As Compose Multiplatform continues to evolve, investing time in building a robust localization strategy will make your app stand out in international markets.
🔵 Reference:
This article was previously published on proandroiddev.com.