Jetpack Compose: Building Multi-language Apps

Developing applications that cater to a global audience requires robust internationalization (i18n) support. Jetpack Compose, Android’s modern UI toolkit, offers excellent capabilities for building multi-language applications. By properly implementing localization and text handling, you can create an app that seamlessly adapts to different languages and regions.

What is Internationalization (i18n) and Localization (l10n)?

Internationalization (i18n) is the design and development of an application that enables localization for various languages and regions without requiring engineering changes. Localization (l10n) involves adapting the internationalized application for a specific locale, including translating text and adjusting formatting.

Why Build Multi-Language Apps?

  • Reach a Wider Audience: Make your app accessible to users worldwide.
  • Improve User Experience: Provide a native-language experience for users.
  • Increase App Adoption: Cater to diverse linguistic preferences.

How to Build Multi-Language Apps in Jetpack Compose

Building multi-language apps involves handling text resources, formatting dates/times, and dealing with right-to-left (RTL) layouts.

Step 1: Set Up Resource Files

Android uses resource files to manage different locales. Place your localized strings in the res directory.

Create Default Strings File

Create a strings.xml file in the res/values directory:

<resources>
    <string name="app_name">My Application</string>
    <string name="greeting">Hello, world!</string>
</resources>
Create Localized Strings Files

For each language you want to support, create a values-xx directory (where xx is the language code) and a strings.xml file within it. For example, for Spanish, you’d create res/values-es/strings.xml:

<resources>
    <string name="app_name">Mi Aplicación</string>
    <string name="greeting">¡Hola, mundo!</string>
</resources>

Step 2: Accessing Strings in Compose

Use the stringResource() function to retrieve localized strings in your Compose UI:


import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.example.myapplication.R

@Composable
fun Greeting() {
    Text(text = stringResource(id = R.string.greeting))
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    Greeting()
}

Step 3: Configuring the Locale

To change the application’s locale, you can create a helper function to update the Configuration.


import android.content.Context
import android.content.res.Configuration
import java.util.Locale

fun updateLocale(context: Context, language: String) {
    val locale = Locale(language)
    Locale.setDefault(locale)

    val configuration = Configuration(context.resources.configuration)
    configuration.setLocale(locale)
    configuration.setLayoutDirection(locale)

    context.resources.updateConfiguration(configuration, context.resources.displayMetrics)
}

Usage Example:


import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.example.myapplication.R

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MyApp()
        }
    }
}

@Composable
fun MyApp() {
    val context = LocalContext.current
    Column {
        Text(text = stringResource(id = R.string.greeting))
        Button(onClick = {
            updateLocale(context, "es") // Switch to Spanish
            (context as? ComponentActivity)?.recreate() // Recreate activity to apply changes
        }) {
            Text("Switch to Spanish")
        }
        Button(onClick = {
            updateLocale(context, "en") // Switch to English
            (context as? ComponentActivity)?.recreate() // Recreate activity to apply changes
        }) {
            Text("Switch to English")
        }
    }
}

@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    MyApp()
}

In this example:

  • We define buttons that, when clicked, will switch the application’s locale.
  • The updateLocale function updates the locale configuration.
  • recreate() is called to refresh the Activity, ensuring the new locale is applied.

Step 4: Handling Plurals

Plurals are words that change form based on quantity (e.g., “item” vs. “items”). Android provides <plurals> resource for this.

Create Plurals Resource

Define a plurals.xml file (e.g., in res/values/plurals.xml):

<resources>
    <plurals name="numberOfItems">
        <item quantity="one">%d item</item>
        <item quantity="other">%d items</item>
    </plurals>
</resources>
Access Plurals in Compose

Use the pluralStringResource() function:


import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.foundation.layout.*
import androidx.compose.material.Surface
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.Alignment
import com.example.myapplication.R

@Composable
fun ItemCount(count: Int) {
    Text(text = pluralStringResource(id = R.plurals.numberOfItems, count = count, count),
         fontSize = 20.sp)
}

@Preview(showBackground = true)
@Composable
fun ItemCountPreview() {
    Column {
        ItemCount(count = 1)
        ItemCount(count = 5)
        ItemCount(count = 0)
    }
}

Step 5: Handling Date and Time Formats

Different locales have different conventions for formatting dates and times. Use java.text.DateFormat to format date/time according to the current locale.


import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import java.text.DateFormat
import java.util.Date

@Composable
fun FormattedDate() {
    val context = LocalContext.current
    val currentDate = Date()
    val dateFormat = DateFormat.getDateInstance(DateFormat.DEFAULT, context.resources.configuration.locale)
    val formattedDate = dateFormat.format(currentDate)
    Text(text = formattedDate)
}

@Preview(showBackground = true)
@Composable
fun FormattedDatePreview() {
    FormattedDate()
}

Step 6: Handling Right-to-Left (RTL) Layout

Certain languages, like Arabic and Hebrew, are read from right to left. Android provides automatic mirroring of layouts for RTL languages.

Enable RTL Support

Add android:supportsRtl="true" to your AndroidManifest.xml file within the <application> tag.

<application
    android:label="My Application"
    android:supportsRtl="true"
    ...>
    <!-- Activities -->
</application>
Use Logical Properties

Use logical properties like marginStart and marginEnd instead of marginLeft and marginRight in your layouts. Jetpack Compose automatically handles mirroring for RTL layouts.

Conclusion

Building multi-language apps in Jetpack Compose involves managing resource files, accessing localized strings, handling plurals, formatting dates/times, and supporting RTL layouts. By implementing these best practices, you can create a user-friendly app that caters to a global audience, enhancing user experience and adoption. Remember to test your app thoroughly with different locales to ensure everything works as expected. Embrace the power of internationalization and make your application accessible to users around the world.