Farhan Farooqui
Farhan Farooqui

Reputation: 975

How to implement in app localization with Jetpack Compose

How to implement in app localization with Jetpack Compose? I mean I don't want the user to change their device language, instead, let them change only the app language. How to achieve that? All of the resources I found talks about changing the device language.

Upvotes: 5

Views: 5555

Answers (2)

nglauber
nglauber

Reputation: 23984

The recommended way to handle app internationalization is using the "Per-app language" feature.

You can find more info here: https://www.youtube.com/watch?v=DUKnNWwcNvo

[Previous answer]

This is what I did based on this answer here.

In your application class, do the following:

class MyApp : Application() {
    override fun attachBaseContext(base: Context) {
        super.attachBaseContext(LocaleHelper.setLocale(base, myLang))
    }

    companion object {
        var myLang = "en"
    }
}

I'm saving the language in myLang variable, but in practice you will save in Shared Preferences.

In onAttachBaseContext the setLocale is called (it's declared below).

Then, in your activity you will do the same:

class MainActivity : AppCompatActivity() {
    override fun attachBaseContext(newBase: Context) {
        super.attachBaseContext(
            LocaleHelper.setLocale(newBase, MyApp.myLang)
        )
    }
}

The object below will set the language in MyApp.myLang and update the Context object.

object LocaleHelper {
    fun setLocale(context: Context, language: String): Context? {
        MyApp.myLang = language
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            return updateResources(context, language);
        }
        return updateResourcesLegacy(context, language);
    }

    @TargetApi(Build.VERSION_CODES.N)
    private fun updateResources(context: Context, language: String): Context? {
        val locale = Locale(language)
        Locale.setDefault(locale)
        val configuration = context.resources.configuration
        configuration.setLocale(locale)
        configuration.setLayoutDirection(locale)
        return context.createConfigurationContext(configuration)
    }

    private fun updateResourcesLegacy(context: Context, language: String): Context {
        val locale = Locale(language)
        Locale.setDefault(locale)
        val resources = context.resources
        val configuration = resources.configuration
        configuration.locale = locale
        resources.updateConfiguration(configuration, resources.displayMetrics)
        return context
    }
}

Finally, in your composable, you can do the following:

@Composable
fun TestLanguage() {
    val context = LocalContext.current
    Text(text = stringResource(id = R.string.activity_java_text))
    Button(onClick = {
        LocaleHelper.setLocale(context, "pt")
        (context as? Activity)?.recreate()
    }) {
        Text(text = stringResource(id = R.string.btn_ok))
    }
}

Notice the recreate method is called to recreate the current activity and apply the language change.

Upvotes: 3

user3742248
user3742248

Reputation: 41

A new API was introduced in 2022 for adding per-app language preferences. Upgrading to androidx.appcompat:appcompat:1.6.0 latest version (>beta01) brings up two new methods: setApplicationLocales() and getApplicationLocales()

More details about setup and guidelines can be found here: https://developer.android.com/guide/topics/resources/app-languages

It is backwards compatible with older versions of Android, not just Android 13. It allows quick in app language setting. Beaware that it seems to restart the activity, at least in the current version.

Simplified sample code of a language picker which triggers a dialogue:

Row(
            horizontalArrangement = Arrangement.SpaceBetween,
            verticalAlignment = Alignment.CenterVertically,
            modifier = Modifier
                .fillMaxWidth()
                .clickable { openDialog.value = SettingsDialogueStates.PICK_LANGUAGE }
        ) {
            Row(
                horizontalArrangement = Arrangement.SpaceAround,
                verticalAlignment = Alignment.CenterVertically
            ) {
                Icon(
                    Icons.Default.Language,
                    contentDescription = "Language choice icon"
                )
                Text(
                    text = "Application language",
                    modifier = Modifier.padding(8.dp)
                )
            }

            Text(
                text = when (AppCompatDelegate.getApplicationLocales().toLanguageTags()) {
                    "he" -> "Hebrew"
                    "en-US", "en" -> "English"
                    else -> "System default"
                }
            )
        }

And just the dialogue:

    Dialog(
                onDismissRequest = {
                    openDialog.value = SettingsDialogueStates.HIDDEN
                }
            ) {
                Card(
                    shape = RoundedCornerShape(10.dp)
                ) {
                    Column(
                        horizontalAlignment = Alignment.CenterHorizontally,
                        verticalArrangement = Arrangement.Top
                    ) {
                        Text(
                            text = "Choose language",
                            style = MaterialTheme.typography.headlineLarge
                        )

                        OutlinedButton(
                            colors = ButtonDefaults.outlinedButtonColors(containerColor = Color.Transparent),
                            border = BorderStroke(0.dp, Color.Transparent),
                            modifier = Modifier.fillMaxWidth(),
                            onClick = {
                                AppCompatDelegate.setApplicationLocales(
                                    LocaleListCompat.create(
                                        Locale("en")
                                    )
                                )
                                openDialog.value = SettingsDialogueStates.HIDDEN
                            }
                        ) {
                            Text(text = "English")
                        }

                        OutlinedButton(
                            colors = ButtonDefaults.outlinedButtonColors(containerColor = Color.Transparent),
                            border = BorderStroke(0.dp, Color.Transparent),
                            modifier = Modifier.fillMaxWidth(),
                            onClick = {
                                AppCompatDelegate.setApplicationLocales(
                                    LocaleListCompat.create(
                                        Locale("he")
                                    )
                                )
                                openDialog.value = SettingsDialogueStates.HIDDEN
                            }
                        ) {
                            Text(text = "Hebrew")
                        }

                        OutlinedButton(
                            colors = ButtonDefaults.outlinedButtonColors(containerColor = Color.Transparent),
                            border = BorderStroke(0.dp, Color.Transparent),
                            modifier = Modifier.fillMaxWidth(),
                            onClick = {
                                AppCompatDelegate.setApplicationLocales(
                                    LocaleListCompat.getEmptyLocaleList()
                                )
                                openDialog.value = SettingsDialogueStates.HIDDEN
                            }
                        ) {
                            Text(text = "System default")
                        }
                    }
                }
            }

Upvotes: 4

Related Questions