Switching locales with Jetpack Compose
Tags: kotlin, android, localization, localazy
Published: 2020-11-12 12:15:34

I've already published an article on [how it's important to allow users to switch languages](https://localazy.com/blog/why-allow-users-to-switch-languages). There's plenty of reasons to do so. 

Now, let's see how simple it can be with Jetpack Compose to implement custom language switching. 

## Prepare your app

Be sure that you have all strings in `res/values/strings.xml` and whenever you need them, get them using Android's standard mechanism `getString()`. 

It's best to use English as the base language as it works best with translation tools, and it's usually easier to find collaborators and volunteers. 

And that's all. You don't need to care about XML files in other languages. We use a localization platform for that. 

## Manage translations

Once you have your `strings.xml` file ready, [sign up for Localazy](https://localazy.com) and follow Android integration instruction. It's simple as it only means a few lines to be added to your root's and app's`build.gradle` — no need to change source code or resources. 

All changes you will need to do will be as simple as:

**add Localazy to the root's build.gradle file:**

```groovy
buildscript {

    repositories {
        // ...
        maven { url "https://maven.localazy.com/repository/release/" }
    }

    dependencies {
        // ...
        classpath "com.localazy:gradle:1.5.2"
    }

}
```

**Add Localazy to the app's build.gradle file:**

```groovy
apply plugin: 'com.localazy.gradle'

localazy {
    readKey "your-read-key"
    writeKey "your-write-key"
}
```

And that's it! Now, you can upload strings using the `uploadStrings` task using Gradle. It's available on the command line:

```shell
./gradlew uploadStrings
```

And also in the **Gradle** view in Android Studio.

![|Gradle View in Android Studio](https://content.localazy.com/jetpack_locale_switching/android-studio.png)

From that moment on, you can manage translations easily using Localazy, and there are also shared translations to translate a huge portion of your app to up to 80 languages for free. 

## Jetpack: Hello World

To test how our locale switching works, let's create a simple Hello World app.

Here goes a code for a composable, a text, rendered in the center of the screen:

```kotlin
@Composable
fun WelcomeText(text: String) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .fillMaxHeight(),
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            text = text,
            modifier = Modifier
                .gravity(Alignment.CenterHorizontally)
        )
    }
}
```

To allow users to switch their language, we can use the floating action button. We can add it to our activity and give it an action - to open `SwitchActivity` that we will discuss later. 

```kotlin
class MainActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Scaffold(
                floatingActionButton = {
                    FloatingActionButton(
                        onClick = {
                            startActivity(
                                Intent(this@MainActivity, SwitchActivity::class.java)
                            )
                        },
                        elevation = 10.dp
                    ) {
                        Icon(Icons.Default.Translate)
                    }
                },
                bodyContent = {
                    WelcomeText(text = getString(R.string.welcome_message))
                }
            )
        }
    }

}
```

The resulting screen is expected:

![MainActivity screenshot](https://content.localazy.com/jetpack_locale_switching/mainactivity.png)

Now, let's implement `SwitchActivity`. 


## SwitchActivity

Localazy Android library has been automatically integrated with our app by the few lines in the build script mentioned above, so it's available. The whole [documentation for the Localazy Android library is available on the website](https://localazy.com/docs/android/localazy-android-library).

First, we wrap a simple `ViewModel` around it to make their data easily accessible to our newly created activity. 

```kotlin
class LocaleViewModel : ViewModel() {

    private val localazyListener = LocalazyWrappedListener {
        viewModelScope.launch {
            update()
        }
    }

    var locales by mutableStateOf(listOf<LocalazyLocale>())
        private set

    init {
        Localazy.setListener(localazyListener)
        update()
    }

    private fun update() {
        locales = Localazy.getLocales() ?: emptyList()
    }

}
```

We wrapped `LocalazyListener`, so we don't need to implement all the overrides to listen for a single event. Here goes the implementation:

```kotlin
/**
 * A simple class to wrap LocalazyListener, so we don't need to implement
 * all functions, and can use a lambda to monitor changes.
 */
class LocalazyWrappedListener(val body: () -> Unit) : LocalazyListener {

    override fun missingTextFound(p0: LocalazyId?, p1: Locale?, p2: String?) {}

    override fun missingKeyFound(p0: Locale?, p1: String?) {}

    override fun stringsUpdateStarted() {}

    /**
     * This function is called when updated data is downloaded.
     */
    override fun stringsUpdateFinished() {
        body()
    }

    override fun stringsUpdateFailed(p0: Int) {}

    override fun stringsUpdateNotNecessary() {}

    /**
     * This function is called when the strings are loaded.
     */
    override fun stringsLoaded(fromUpdate: Boolean, success: Boolean) {
        if (success) {
            body()
        }
    }

}
```

Great! That was pretty simple. Now, let's render the language selector using Jetpack's composables. Not only that we show the available languages, but we can also indicate that the given language is not yet fully translated (which is extremely handy as it can attract more contributors and volunteers to help with translating) and, of course, we also need to point users to our project on Localazy, so they can actually help us. 

```kotlin
@Composable
fun LocaleSwitcher(
    items: List<LocalazyLocale>,
    onChange: (LocalazyLocale) -> Unit,
    onHelp: () -> Unit
) {
    Column {
        LazyColumnFor(items = items, modifier = Modifier.padding(0.dp, 8.dp)) {
            TextButton(
                onClick = {
                    onChange(it)
                },
                modifier = Modifier.padding(16.dp, 4.dp, 4.dp, 4.dp).fillMaxWidth()
            ) {
                val name =
                    "${it.localizedName}${if (!it.isFullyTranslated) " (incomplete)" else ""}"
                Text(name)
            }
        }
        TextButton(
            onClick = {
                onHelp()
            },
            modifier = Modifier.padding(16.dp, 12.dp, 4.dp, 4.dp).fillMaxWidth()
        ) {
            Text("Help us translate the app!")
        }
    }
}
```

We have everything ready for `SwitchActivity`. Let's make the drum rolls. 

```kotlin
class SwitchActivity : AppCompatActivity() {

    private val localeViewModel by viewModels<LocaleViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            LocaleSwitcher(
                items = localeViewModel.locales,
                onChange = {

                    // Change the locale and persist the new choice.
                    Localazy.forceLocale(it.locale, true)

                    // Reopen MainActivity with clearing top.
                    startActivity(
                        Intent(
                            this@SwitchActivity,
                            MainActivity::class.java
                        ).apply {
                            flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
                        }
                    )

                },
                onHelp = {
                    // Open the project on Localazy to allow contributors to help us with translating.
                    startActivity(
                        Intent(Intent.ACTION_VIEW, Localazy.getProjectUri()).apply {
                            flags = Intent.FLAG_ACTIVITY_NEW_TASK
                        }
                    )
                }
            )
        }
    }

}
```

And the result:

![SwitchActivity screenshot](https://content.localazy.com/jetpack_locale_switching/switchactivity.png)

Which exactly matches my app on Localazy at the given time:

![Localazy](https://content.localazy.com/jetpack_locale_switching/localazy.png)

To translate my app to more languages, I don't need to touch XML files or source code. Everything can be managed through Localazy.

**Updated translations and new languages are delivered online to existing users without the need to re-submit the app to Play Store ;-). Awesome, well?**

## Source Code

You can find [the whole source code on Github](https://github.com/vaclavhodek/JetpackLocaleSwitching). 

---

This post was originally published on [Localazy](https://localazy.com/blog/switching-locales-with-jetpack-compose).