Search code examples
androidactivity-finishapplication-restart

How to apply changes after language change?


I use a custom settings activity for my preferences. One of them is a ListPreference for choosing app language. So far I could implement the locale preferences, but to apply changes, I restart app. The problem is, it does not work for all Android Versions. Here is what I did:

import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar
import androidx.fragment.app.FragmentActivity
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat

class SettingsActivity : FragmentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_settings)
        val toolbar: Toolbar = findViewById(R.id.toolbar_settings)
        toolbar.apply {
            setNavigationIcon(R.drawable.ic_arrow)
            setNavigationOnClickListener { finish() }
            title = getString(R.string.settings)
            setTitleTextColor(Color.WHITE)
        }
        supportFragmentManager.beginTransaction().replace(R.id.content, SettingsFragment())
            .commit()
    }

    class SettingsFragment : PreferenceFragmentCompat() {

        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
            addPreferencesFromResource(R.xml.preferences)

            val lang: ListPreference = findPreference("Language")!!
            val language = when (LanguageSettings().getLang()) {
                "en" -> getString(R.string.en_lang)
                "de" -> getString(R.string.de_lang)
                else -> getString(R.string.ru_lang)
            }

            lang.summary = "${getString(R.string.current_language)} $language"
            val temp = lang.value
            lang.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
                val dialogBuilder =
                    activity?.let { AlertDialog.Builder(it, R.style.AlertDialogLight) }
                dialogBuilder!!.setMessage(getString(R.string.restart_txt))
                    .setPositiveButton(getString(R.string.restart)) { _, _ ->
                        activity?.let { LanguageSettings().setLocale(it, newValue.toString()) }
                        restartApp()
                    }
                    .setNegativeButton(getString(R.string.cancel)) { dialog, _ ->
                        lang.value = temp
                        dialog.cancel()
                    }
                dialogBuilder.create().apply {
                    show()
                }
                true
            }
        }

        private fun restartApp() {
            val intent = activity!!.packageManager
                .getLaunchIntentForPackage(activity!!.packageName)
            intent!!.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            activity!!.finish()
            startActivity(intent)
        }
    }

    override fun attachBaseContext(newBase: Context) {
        super.attachBaseContext(LanguageSettings().onAttach(newBase))
    }
}

Below Lollipop, after restart the changes are applied, which means the app language is changed after restart. Also it works for android 10. But this does not work for Lollipop and Marshmallow (so far tested), but what only works, the language is changed only for SettingsActivity, other activities stay unchanged. May be there are also other versions between Marshmallow and Android X, for which it also works.

But nevertheless, I want to get it worked for all versions, my app supports SDK from 16 up to latest one.

My LanguageSettings.kt class:

import android.content.Context
import android.content.res.Configuration
import android.os.Build
import android.util.Log
import androidx.preference.PreferenceManager
import java.util.*

@Suppress("DEPRECATION")
class LanguageSettings {

    fun onAttach(context: Context): Context {
        return setLocale(context, getPersistedData(context, getLang()!!)!!)
    }

    fun onAttach(context: Context, defaultLanguage: String): Context {
        return setLocale(context, getPersistedData(context, defaultLanguage)!!)
    }

    fun getLang(): String? {
        return when (Locale.getDefault().displayLanguage) {
            "English" -> "en"
            "Deutsch" -> "de"
            "русский" -> "ru"
            else -> "en"
        }
    }

    fun setLocale(context: Context, language: String): Context {
        persist(context, language)
        val configuration: Configuration
        val resources = context.resources
        val locale =
            Locale(language)
        Locale.setDefault(locale)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            configuration = context.resources.configuration
            configuration.setLocale(locale)
            configuration.setLayoutDirection(locale)
            return context.createConfigurationContext(configuration)
        }
        configuration = resources.configuration
        configuration.locale = locale
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
            configuration.setLayoutDirection(locale)
        resources.updateConfiguration(configuration, resources.displayMetrics)
        return context
    }

    private fun getPersistedData(context: Context, defaultLanguage: String): String? {
        return PreferenceManager
            .getDefaultSharedPreferences(context)
            .getString("Language", defaultLanguage)
    }

    private fun persist(context: Context, language: String) {
        PreferenceManager
            .getDefaultSharedPreferences(context)
            .edit()
            .putString("Language", language)
            .apply()
    }
}

The problem: this works for Android 4.2-4.4 and above 7, but not for Android 5.0/5.1/6 (and may be for some versions also does not work, did not test for all versions so far, only for above mentioned versions).

Ans this my MyApp.kt class that extends Application is assigned in Manifest file (android:name=".MyApp"):

class MyApp : Application() {

    override fun attachBaseContext(base: Context) {
        super.attachBaseContext(LanguageSettings().onAttach(base, "en"))
    }
}

Solution

  • I found a solution after testing a lot of different cases:

    private fun setLocale(context: Context, language: String): Context {
            val configuration: Configuration
            val resources = context.resources
            val locale = when (language) {
                "de" -> Locale("de", "DE")
                "ru" -> Locale("ru", "RU")
                "en" -> Locale("en", "US")
                else -> Locale("en", "US")
            }
    
            Locale.setDefault(locale)
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                configuration = context.resources.configuration
                configuration.setLocale(locale)
                configuration.setLayoutDirection(locale)
                return context.createConfigurationContext(configuration)
            }
            configuration = resources.configuration
            configuration.locale = locale
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
                configuration.setLayoutDirection(locale)
            resources.updateConfiguration(configuration, resources.displayMetrics)
            return context
        } 
    

    This works for me properly in Kotlin.