Localization in Compose Multiplatform

Mohab erabi
4 min readNov 1, 2024

In this article, we will explore how to implement localization in a Compose Multiplatform app. We’ll cover the necessary setup, resource sharing, and platform-specific implementations to ensure your app supports multiple languages effectively. Let’s walk through the steps to get your app ready for localization.

Before we start, make sure you have generated your Kotlin Multiplatform project using the KMP wizard at kmp.jetbrains.com. This step ensures you have a structured project set up for your Compose Multiplatform application.

Project Structure

We’ll utilize a shared structure for localization resources in the commonMain/composeresources folder. This allows you to share strings, fonts, images, videos, and files across both Android and iOS platforms.

Step 1: Update Info.plist for iOS

In your iOS app, you need to specify the supported localizations in the Info.plist file. Add the following XML snippet to include English and Arabic

<array>
<key>CFBundleLocalizations</key>
<array>
<string>en</string>
<string>ar</string>
</array>
</array>

Step 2: Configure Android Resources

In your composeapp/build.gradle.kts file, ensure the following configuration is included within the android block to enable resource generation for localization

androidResources {
generateLocaleConfig = true
}

Next, create a resources.properties file in the androidMain/res directory and add the following line:

unqualifiedResLocale=en-US

This setting tells Android to use the specified locale as the default. It helps the system understand which resources to load when no specific language is defined.

Step 3: Create Localized Strings in Common Main

Now, let’s create our shared string resources. In the commonMain folder, create a file named values/strings.xml and add the following content:

<resources>
<string name="hello">Hello, I love Kotlin</string>
<string name="en">English</string>
<string name="ar">العربية</string>
<string name="app_language">App Language: %1$s</string>
<string name="change_lang">Change Language</string>
</resources>

Additionally, create a folder named values-ar for Arabic localization. Inside, add the strings.xml file with the following content:

<resources>
<string name="hello">اهلا انا احب كوتلن</string>
<string name="en">English</string>
<string name="ar">العربية</string>
<string name="app_language">لغة التطبيق: %1$s</string>
<string name="change_lang">تغيير اللغة</string>
</resources>

in android stuido hit Build/Rebuild Project in order to genreate Res resources

now in commonMain/kotlin/ create a new .kt file AppLang.kt

import lokalization.composeapp.generated.resources.Res
import lokalization.composeapp.generated.resources.ar
import lokalization.composeapp.generated.resources.en
import org.jetbrains.compose.resources.StringResource

enum class AppLang(
val code: String,
val stringRes: StringResource
) {
English("en", Res.string.en),
Arabic("ar", Res.string.ar)
}

Step 4: Set Up the Android and iOS Locale Managers

Create a common interface for managing application locale in commonMain/AppLocaleManager.kt:

interface AppLocaleManager {
fun getLocale(): String
}

@Composable
expect fun rememberAppLocale(): AppLang

Android Implementation

In the androidMain directory, implement the AppLocaleManager interface:

class AndroidAppLocaleManager(
private val context: Context,
) : AppLocaleManager {

private val localManager = context.getSystemService<LocaleManager>()

override fun getLocale(): String {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val locales = localManager?.applicationLocales ?: return "en"
if (locales.isEmpty) "en" else
locales[0]?.toLanguageTag()?.split("-")?.firstOrNull() ?: "en"
} else {
AppCompatDelegate.getApplicationLocales()
.toLanguageTags().split("-")
.firstOrNull() ?: "en"
}
}
}

@Composable
actual fun rememberAppLocale(): AppLang {
val context = LocalContext.current
val locale = AndroidAppLocaleManager(context).getLocale()
return remember(locale) {
locale.toApLang()
}
}

iOS Implementation

In the iosMain directory, implement the locale manager:

class IosAppLocaleManager : AppLocaleManager {
override fun getLocale(): String {
val nsLocale = NSLocale.currentLocale.languageCode
return nsLocale
}
}

@Composable
actual fun rememberAppLocale(): AppLang {
val nsLocale = IosAppLocaleManager().getLocale()
return remember(nsLocale) {
when (nsLocale) {
"ar" -> AppLang.Arabic
else -> AppLang.English
}
}
}

Step 5: Create the Main Application Composable

Finally, in commonMain, create the main composable function for your app:

val LocalAppLocalization = compositionLocalOf {
AppLang.English
}
@Composable
fun App() {
val currentLanguage = rememberAppLocale()
val urlLauncher = rememberUrlLauncher()

CompositionLocalProvider(LocalAppLocalization provides currentLanguage) {
MaterialTheme {
Column(
modifier = Modifier.fillMaxSize().padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
) {
Text(stringResource(Res.string.app_language, stringResource(LocalAppLocalization.current.stringRes)))
Text(stringResource(Res.string.hello), style = TextStyle(fontSize = 20.sp, fontWeight = FontWeight.Bold))
Button(onClick = { urlLauncher.openAppSettings() }) {
Text(stringResource(Res.string.change_lang))
}
}
}
}
}

now lets run the app on ios and adnroid and test it

lets try to change the language now when Change Language clicked

Conclusion

In this article, we covered the steps to implement localization in a Kotlin Multiplatform application using Compose. By sharing resources in the commonMain/composeresources directory and setting up platform-specific locale managers, we ensured that our app can support multiple languages seamlessly.

Github Repository

https://github.com/mohaberabi/lokalization

--

--

No responses yet