Bart Kessels
Bart Kessels
Passionate open source software engineer who loves to go backpacking

Support multiple languages in your app

Support multiple languages in your app
This image is generated using Dall-E
  • Prompt: Generate an image of a phone which displays multiple country flags in a minimalistic flat style
  • In our previous post we’ve updated the app to use Material Design. But the text we display is hard-coded in our app. What if our user base consists of English-speaking users and Dutch-speaking users, we’d probably want them to use our app in their native language.

    Let’s update our app, so we can support multiple languages. I’ve chosen English and Dutch for this blogpost because I’m a native Dutch-speaker and my English isn’t too bad either. You can obviously use different languages.

    Add the default language

    When creating a new application through the Android Studio wizard, you automatically get a res/values/strings.xml file. This res/values folder is where all the resources for the default language are stored. If we want to support another language, we simply add a new folder under res with the naming convention of values-b+<country_code> (Google, 2024). This can be done for any resource such as images as well, but for this blogpost we’ll only stick to the strings.

    Extract the strings

    Right now we have our strings inside our code, let’s remove them from the code and place them in our default language values/string.xml.

    1
    2
    3
    4
    5
    6
    
    <resources>
        <string name="app_name">My Application</string>
        <string name="uninitialized_text">Hello world!</string>
        <string name="updated_text">Hello again!</string>
        <string name="update_text_button">Click me</string>
    </resources>
    

    The important part of these strings is the name property. This needs to be the same between all languages because that is what we’ll be referencing inside the UI later.

    The app_name string is used for the text that’s displayed under your app icon on the Android home-screen.

    Support the Dutch language

    Now that we know what strings we need to translate, let’s create a new folder for the Dutch language res/values-b+nl and create a new strings.xml file with the following contents.

    1
    2
    3
    4
    5
    6
    
    <resources>
        <string name="app_name">Mijn Applicatie</string>
        <string name="uninitialized_text">Hallo wereld!</string>
        <string name="updated_text">Nogmaals hallo!</string>
        <string name="update_text_button">Klik mij</string>
    </resources>
    

    Right now we’ll leave the strings in the code as they are, because there are a few other changes we’ll be making before we’re going to update the UI.

    Update the view model

    Because we want to use the text that we’ve defined in the string.xml, we need to extract our Hello world! and Hello again! from our view model. And since we don’t want to rely on the Android libraries in our view model unit tests, we’re not going to use the string resource inside the view model.

    What we can do, however, is use states. Our application has two states an Uninitialized state and a Updated state. Where the Uninitialized state corresponds to the Hello world! text, and the Updated state to the Hello again! text.

    Let’s create a sealed interface called MainState and add the two Uninitialized and Updated states as implementations. The reason we use a sealed interface as a base for both is that when the implementations of a sealed class are known at compile-time. This allows us to use these states as if they were enum values (Kotlin Foundation, 2023).

    1
    2
    3
    4
    5
    6
    
    // ...
    sealed interface MainState {
        data object Uninitialized: MainState
        data object Updated: MainState
    }
    // ...
    

    Because neither state requires properties we can use them as data object’s. This allows us to use basic operations like equals and toString on the objects because of the data modifier (Kotlin Foundation). And the object type simply tells the compiler that our state is a singleton (Kotlin Foundation, 2023).

    Use the states

    Now that we’ve declared our states, let’s update the displayText MutableStateFlow to use the MainState instead of a string like we do now.

    1
    2
    3
    4
    5
    
    // ...
    
    private val _displayText = MutableStateFlow<MainState>(MainState.Uninitialized)
    
    // ...
    

    There is a slight difference as opposed to the string we’ve used before. Now we need to tell the MutableStateFlow what type to use. If we only initialize it with MainState.Uninitialized and don’t set a type, it thinks the type is the Uninitialized implementation of MainState. This will give us errors when we try to update the displayText property with another implementation of the MainState.

    Now update the updateText method to remove the Hello again! string and set it to MainState.Updated.

    1
    2
    3
    
    fun updateText() {
        _displayText.update { MainState.Updated }
    }
    

    Update the UI

    Now that we’ve broken our app, it won’t compile because in the UI it’s trying to pass a MainState argument into a string parameter.

    So, let’s first fix that issue by adding a switch statement to determine what string resource should be loaded for each state.

    1
    2
    3
    4
    
    val textToDisplay = when (viewModel.displayText.collectAsState().value) {
        MainState.Uninitialized -> R.string.uninitialized_text
        MainState.Updated -> R.string.updated_text
    }
    

    This will set the resource id of the expected string resource on the textToDisplay variable. To actually get the string from the resource id, we use the stringResource method inside our Text composable.

    1
    
    Text(stringResource(textToDisplay))
    

    And inside our button, we only have one string to display, so we can call the stringResource function with the update_button_text string resource id.

    1
    2
    3
    
    Button(onClick = viewModel::updateText) {
        Text(stringResource(R.string.update_text_button))
    }
    

    Changing our entire Main composable to look like this.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    // ...
    
    @Composable
    fun Main(
        viewModel: MainViewModel
    ) {
        val textToDisplay = when (viewModel.displayText.collectAsState().value) {
            MainState.Uninitialized -> R.string.uninitialized_text
            MainState.Updated -> R.string.updated_text
        }
    
        Column {
            Text(stringResource(textToDisplay))
            Button(onClick = viewModel::updateText) {
                Text(stringResource(R.string.update_text_button))
            }
        }
    }
    

    Run the app

    If you run the application, you probably won’t see a change. But if you change your language on the device to Dutch, you’ll see the following. By the way, I wouldn’t recommend changing your phone’s language to Dutch or any other language you don’t understand a little bit. To set your device back to a language you know can be hard when you can’t read the menu items in the settings.

    Supporting the Dutch language

    Categories

    Related articles

    Set up Jetpack Compose

    In Android development, the new standard for building UI's is Jetpack Compose. But how do you actually set ...

    Set up View Models with Jetpack Compose

    You've just set up Jetpack Compose, but how do you properly separate the UI from the business logic? This i...

    Set up Material Design with Jetpack Compose

    You've just set up Jetpack Compose, and now you want to make your app look like a native Android app. This ...

    Enable UI preview in Android Studio

    When developing an Android app, you're likely to iterate through multiple UIs. When going through this proc...

    Automatically testing the UI of your Android app

    During your development phase, you'd like everything that can be automated to be automated. This also goes ...