Multi-Language Support - Android Localization(In-App and System Settings Change Language)— Kotlin/Java/XML/Jetpack Compose

D I N E S H
6 min readNov 12, 2022

--

Hi, recently i worked a project to create a multi-language supported application and in that i struggled to get proper working version. Mostly what all i get is deprecated version :(. After lot of research i got Solution and in that i learned something new, and i feel if i share it will help someone.BTW its my First Blog….🤞

Photo by Caspar Camille Rubin on Unsplash

Its not enough to create application which supports only one language, most of our clients/customers or when we published the application, there may be the users who want to use it with different language like Tamil,Telugu,Bengali etc.

Lets learn how to add Multi-Language support to our app with a simple example. Here i will show you with jetpack compose also.

Before getting started, you have to make sure in gradle(app level)..

  1. need to make TargetApi as 33 and above.
  2. need to make Minimum API as 24
android {
....

defaultConfig {
...
minSdk 24
targetSdk 33
...
...

...
}
}

we all know, In Android 13 there is a lot new changes/migrations updates for Android Developers. Sometimes its gives trouble, and sometimes its makes our life easier. In Android 13 they introducing per-app language preferences Read More here.

If we implements the new per-app language preferences, then users can select the app’s language inside the system settings and also its provides APIs that make implementing an in-app language picker much easier .

They provided the new api AppCompatDelegate, LocaleManager, which is the class that contains to make in-app language switching, and its provide backward compatibility with older versions of Android (upto API 24). In order to make use it we have to use AppCompat version as library (1.6.0-beta01 or higher)

Steps :

The version i’m using “1.7.0-alpha01” only supports AppCompat theme. may be in future there is a change it will work for Material Theme also. Don’t worry i added the Tweak to create MaterialTheme UIs in XML also. For Jetpack Compose no need to worry, no need any tweak it will not affect any Material UI.

  1. Setting Supported Theme : AppCompat Theme

For XML Users, In Themes.xml check are you using AppCompat theme or not. We have to use AppCompatTheme.

for Jetpack Compose Developers, you can directly specify the theme in AndroidManifest.xml like below

<manifest>
...
<application
...
android:theme="@style/Theme.AppCompat.Light.NoActionBar">

<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

</application>
</manifest>

2. Add/Update the appCompat library version to latest.

implementation 'androidx.appcompat:appcompat:1.7.0-alpha01'

3. Add your multiple strings.xml file.

4. Now its time to create locales-config.xml in res/xml folder. Create a file called res/xml/locales_config.xml and specify your app’s languages, including your app's ultimate fallback locale, which is the locale specified in res/values/strings.xml.

<?xml version="1.0" encoding="utf-8"?>
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
<locale android:name="en"/>
<locale android:name="ta"/>
</locale-config>

5. In Manifest add android:localeConfig path like below. If your app not targerting SDK 33 or higher you will not get android:localeConfig.

<manifest>
...
<application
...
android:localeConfig="@xml/locales_config">
</application>
</manifest>

to support android 12 and lower, will add below service. make sure autoStoreLocales to true

<application
...
<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false"
android:exported="false">
<meta-data
android:name="autoStoreLocales"
android:value="true" />
</service>
...
</application>

6. Now add supported language in Gradle(app-level) file.

 android {
defaultConfig {
...
resConfigs("en", "ta")
}
}

Note : While Build/Generating application, Whatever you specify language resource inside resConfigs only includes in the APK.

Thats it🔥🔥 You finally achieved that now users select an app language in system settings.🥳

Its Cool know😎. Its the same process for Jetpack Compose also.

Now Lets see, In-App Language change.🤗

For XML :

  1. Create the In-App Language Picker. These things are your part. You can create how you want to show Language Picker like Dropdown or Whatever your UI needs to show to Users.

MainActivity.kt

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

val langs = resources.getStringArray(R.array.languages) // all language list.

//simple array adapter for dropdown purpose
val arrayAdapter = ArrayAdapter(this, android.R.layout.simple_dropdown_item_1line, langs)

//AutoCompleteView.
findViewById<AutoCompleteTextView>(R.id.autoCompleteTextView).apply {

//setting adapter into AutoCompleteView.
setAdapter(arrayAdapter)

//on Click Listener, to get selected dropdown item.
onItemClickListener =
AdapterView.OnItemClickListener { parent, view, position, id ->
changeLanguage(langs[position])
}
}
}

private fun changeLanguage(s: String) {
//code to change the app language.
}
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:theme="@style/Theme.MaterialComponents.Light.NoActionBar"
tools:context=".MainActivity">

<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="22sp"
android:textStyle="bold"
android:text="@string/welcome_message"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="32dp"
android:hint="@string/select_language"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView">

<AutoCompleteTextView
android:id="@+id/autoCompleteTextView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:inputType="none"
android:textColor="@color/black"
android:textSize="13sp"
tools:text="Happy" />
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

Strings.xml (default)

<resources>
<string name="app_name" translatable="false">MultiLanguage</string>
<string name="select_language" translatable="false">Select Language</string>
<string name="welcome_message">Welcome Macha....</string>

<string-array name="languages" translatable="false">
<item>en</item>
<item>ta</item>
</string-array>
</resources>

Strings.xml (ta — Tamil)

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="welcome_message">வணக்கம் டா மாப்ளே....</string>
</resources>

Final Ui : (Ofcourse it is a demo purpose.)

2. Now we have On Click on language we have to call AppCompatDelegate.setApplicationLocales with selected Language Code.

private fun changeLanguage(s: String) {
//code to change the app language.
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags(s))
} // this AppCompatDelegate should be in main Thread, because its automatically restarts the activity.

Yes this One Line code is enough 🔥 . See how cool and easy 😵.

It would be very Good if they give support for use MaterialTheme🤐. But yeah, we can still use MaterialTheme Views as i mentioned in my activity_main.xml file.😉

For Jetpack Compose:

Hmmm, here its pretty handy tweak,

Firsly, Create the UI, whatever you want to show and use same AppCompatDelegate.setApplicationLocales on click of selected Language. and then follow this below step,

  1. here MainActivity extends : ComponentActivity()

Which means, it support the default MaterialTheme and a new jetpack Compose project also coming with MaterialTheme. So, in order to work in jetpack compose, we have to change the ComponentActivity into AppCompatActivity.

class MainActivity : AppCompatActivity() { //CHANGESS

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
JetMultiLangTheme {
Surface(
modifier = Modifier
.fillMaxSize()
.clickable { // on Click call this line, it will recreate the app automatically,.
AppCompatDelegate.setApplicationLocales(LocaleListCompat.forLanguageTags("ta"))

},
color = MaterialTheme.colors.background,
) {
Greeting("Android")
}
}
}
}
}

Note : If you not getting AppCompatActivity check you added appcompat dependency in gradle app level.

implementation 'androidx.appcompat:appcompat:{latest_version}'

suppose you calling composable function which is in outside of class, you can use below snippet to get activity and run on main thread.

@Composable
fun SettingsScreen(){

val context = LocalContext.current //context

val onClickRefreshActivity = {
//context.findActivity() is kotlin extension function
context.findActivity()?.runOnUiThread {
val appLocale = LocaleListCompat.forLanguageTags("ta") //here ta is hardcoded for testing purpose,you can add users selected language code.
AppCompatDelegate.setApplicationLocales(appLocale)
}
}


Column(modifier = Modifier.fillMaxSize()) {
Button(onClick = {onClickRefreshActivity()}) //on click button changing language
{
Text(text = "Change Language")}
}
}
}

context.findActivity() is a extension function like,

fun Context.findActivity() : Activity? = when(this){
is Activity -> this
is ContextWrapper -> baseContext.findActivity()
else -> null
}

Thats It…!!

Note : Follow STEPS, then follow XML or Jetpack Compose . The Gradle Setup, Step No : 6 is very Important in order to work all these code.

Comments are welcomed, ping me any doubts.

Output :

--

--