androidkotlinlocaleandroid-contextcrowdin

changes in locale does work when add many modules


The Locale does work when have many modules.

Context:

but, when i add a new module in app the change locale does work. implementation project(":newmodule")

When is Single Module:

When is Multi Module:


Activity extended BaseActivity

class MainActivity : BaseActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        Crowdin.forceUpdate(context = this)
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}
open class BaseActivity : AppCompatActivity() {
    override fun attachBaseContext(newBase: Context) {
        super.attachBaseContext(Crowdin.wrapContext(Localization.wrap(context = newBase)))
    }
}
class Localization(base: Context) : ContextThemeWrapper(base, R.style.AppTheme) {
    companion object {

        fun wrap(context: Context, language: String = "es", country: String = "MX"): ContextThemeWrapper {
            var ctx = context
            val config = context.resources.configuration

            if (language != "") {
                val locale = Locale(language, country)
                Locale.setDefault(locale)
                config.setLocale(locale)
                // Used setLayoutDirection for RTL and LTR
                config.setLayoutDirection(locale)
                ctx = context.createConfigurationContext(config)

            }

            return Localization(ctx)
        }
    }
}

Solution

  • Explanation

    The problem is related to Appcompat. There are two different fixes depending on the version of AppCompat. Since you are using 1.2.0, you will have to implement that one.

    AppCompatDelegateImpl ends up removing the locale, because essentially a ContextThemeWrapper wraps your ContextWrapper. See the implementation @ AppCompatDelegateImpl.java line 368 . Also lines 388, and 480.

    try {
                    ContextThemeWrapperCompatApi17Impl.applyOverrideConfiguration(
                            (android.view.ContextThemeWrapper) baseContext, config);
                    return baseContext;
                } catch (IllegalStateException e) {
                    if (DEBUG) {
                        Log.d(TAG, "Failed to apply configuration to base context", e);
                    }
                }
    

    The work-around for this is to over-ride getDelegate inside your Base Activity like this:

    private var baseContextWrappingDelegate: AppCompatDelegate? = null
    
    override fun getDelegate() = baseContextWrappingDelegate ?: BaseContextWrappingDelegate(super.getDelegate()).apply {
        baseContextWrappingDelegate = this
    }
    

    And you also need the following class ( see : Change Locale not work after migrate to Androidx ).

    package androidx.appcompat.app
    
    import android.content.Context
    import android.content.res.Configuration
    import android.os.Bundle
    import android.util.AttributeSet
    import android.view.MenuInflater
    import android.view.View
    import android.view.ViewGroup
    import androidx.appcompat.view.ActionMode
    import androidx.appcompat.widget.Toolbar
    
    class BaseContextWrappingDelegate(private val superDelegate: AppCompatDelegate) : AppCompatDelegate() {
    
        override fun getSupportActionBar() = superDelegate.supportActionBar
    
        override fun setSupportActionBar(toolbar: Toolbar?) = superDelegate.setSupportActionBar(toolbar)
    
        override fun getMenuInflater(): MenuInflater? = superDelegate.menuInflater
    
        override fun onCreate(savedInstanceState: Bundle?) {
            superDelegate.onCreate(savedInstanceState)
            removeActivityDelegate(superDelegate)
            addActiveDelegate(this)
        }
    
        override fun onPostCreate(savedInstanceState: Bundle?) = superDelegate.onPostCreate(savedInstanceState)
    
        override fun onConfigurationChanged(newConfig: Configuration?) = superDelegate.onConfigurationChanged(newConfig)
    
        override fun onStart() = superDelegate.onStart()
    
        override fun onStop() = superDelegate.onStop()
    
        override fun onPostResume() = superDelegate.onPostResume()
    
        override fun setTheme(themeResId: Int) = superDelegate.setTheme(themeResId)
    
        override fun <T : View?> findViewById(id: Int) = superDelegate.findViewById<T>(id)
    
        override fun setContentView(v: View?) = superDelegate.setContentView(v)
    
        override fun setContentView(resId: Int) = superDelegate.setContentView(resId)
    
        override fun setContentView(v: View?, lp: ViewGroup.LayoutParams?) = superDelegate.setContentView(v, lp)
    
        override fun addContentView(v: View?, lp: ViewGroup.LayoutParams?) = superDelegate.addContentView(v, lp)
    
        override fun attachBaseContext2(context: Context) = wrap(superDelegate.attachBaseContext2(super.attachBaseContext2(context)))
    
        override fun setTitle(title: CharSequence?) = superDelegate.setTitle(title)
    
        override fun invalidateOptionsMenu() = superDelegate.invalidateOptionsMenu()
    
        override fun onDestroy() {
            superDelegate.onDestroy()
            removeActivityDelegate(this)
        }
    
        override fun getDrawerToggleDelegate() = superDelegate.drawerToggleDelegate
    
        override fun requestWindowFeature(featureId: Int) = superDelegate.requestWindowFeature(featureId)
    
        override fun hasWindowFeature(featureId: Int) = superDelegate.hasWindowFeature(featureId)
    
        override fun startSupportActionMode(callback: ActionMode.Callback) = superDelegate.startSupportActionMode(callback)
    
        override fun installViewFactory() = superDelegate.installViewFactory()
    
        override fun createView(parent: View?, name: String?, context: Context, attrs: AttributeSet): View? = superDelegate.createView(parent, name, context, attrs)
    
        override fun setHandleNativeActionModesEnabled(enabled: Boolean) {
            superDelegate.isHandleNativeActionModesEnabled = enabled
        }
    
        override fun isHandleNativeActionModesEnabled() = superDelegate.isHandleNativeActionModesEnabled
    
        override fun onSaveInstanceState(outState: Bundle?) = superDelegate.onSaveInstanceState(outState)
    
        override fun applyDayNight() = superDelegate.applyDayNight()
    
        override fun setLocalNightMode(mode: Int) {
            superDelegate.localNightMode = mode
        }
    
        override fun getLocalNightMode() = superDelegate.localNightMode
    
        private fun wrap(context: Context): Context {
            //Put wrapping implementation here
        }
    }
    

    References

    1. https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java#368

    2. Change Locale not work after migrate to Androidx

    3. https://android.googlesource.com/platform/frameworks/support/+/refs/heads/androidx-master-dev/appcompat/appcompat/src/main/java/androidx/appcompat/app/AppCompatDelegateImpl.java#368