Reputation: 795
I'm using the following code to set specific language in my app. Language is saved into SharedPreferences
within the app. And it works perfectly up to API level 23. With Android N SharedPreferences
works well too, it returns the correct language code-string, but it does not change the locale (sets default language of the phone). What could be wrong?
Update 1: When I use Log.v("MyLog", config.locale.toString());
immediately after res.updateConfiguration(config, dm)
it returns correct locale, but language of the app does not changed.
Update 2: I also mentioned that if I change locale and then restart the activity (using new intent and finish on the old one), it changes the language properly, it even shows correct language after rotation. But when I close the app and open it again, I get default language. It's weird.
public class ActivityMain extends AppCompatActivity {
//...
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set locale
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(this);
String lang = pref.getString(ActivityMain.LANGUAGE_SAVED, "no_language");
if (!lang.equals("no_language")) {
Resources res = context.getResources();
Locale locale = new Locale(lang);
Locale.setDefault(locale);
DisplayMetrics dm = res.getDisplayMetrics();
Configuration config = res.getConfiguration();
if (Build.VERSION.SDK_INT >= 17) {
config.setLocale(locale);
} else {
config.locale = locale;
}
}
res.updateConfiguration(config, dm);
setContentView(R.layout.activity_main);
//...
}
//...
}
Update 3: THE ANSWER IS ALSO HERE: https://stackoverflow.com/a/40849142/3935063
Upvotes: 7
Views: 13560
Reputation: 799
This approach will work on all api level device, Make sure to recreate activity on changing language programatically.
1.Use Base Activity for attachBaseContext to set the locale language And extend this activity for all activities
open class BaseAppCompactActivity() : AppCompatActivity() {
override fun attachBaseContext(newBase: Context) {
super.attachBaseContext(LocaleHelper.onAttach(newBase))
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}
2.Use Application attachBaseContext and onConfigurationChanged to set the locale language
public class MyApplication extends Application {
private static MyApplication application;
@Override
public void onCreate() {
super.onCreate();
}
public static MyApplication getApplication () {
return application;
}
/**
* overide to change local sothat language can be chnaged from android device nogaut and above
*/
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(LocaleHelper.INSTANCE.onAttach(base));
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
/**
* also handle change language if device language changed
*/
super.onConfigurationChanged(newConfig);
}
}
3.Use Locale Helper for handling language changes , this approch work on all device
object LocaleHelper {
fun onAttach(context: Context, defaultLanguage: String): Context {
return setLocale(context, defaultLanguage)
}
fun setLocale(context: Context, language: String): Context {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
updateResources(context, language)
} else updateResourcesLegacy(context, language)
}
@TargetApi(Build.VERSION_CODES.N)
private fun updateResources(context: Context, language: String): Context {
val locale = Locale(language)
Locale.setDefault(locale)
val configuration = context.getResources().getConfiguration()
configuration.setLocale(locale)
configuration.setLayoutDirection(locale)
return context.createConfigurationContext(configuration)
}
private fun updateResourcesLegacy(context: Context, language: String): Context {
val locale = Locale(language)
Locale.setDefault(locale)
val resources = context.getResources()
val configuration = resources.getConfiguration()
configuration.locale = locale
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
configuration.setLayoutDirection(locale)
}
resources.updateConfiguration(configuration, resources.getDisplayMetrics())
return context
}
}
Upvotes: 1
Reputation: 87
I had the same problem but with Android 5, 6 and 7. After a lot of searches, the solution is in this post : Change Locale not work after migrate to Androidx (see the comment of Ehsan Shadi and not the "solution").
First, you have to update your library appcompat to 1.2.0 (fix for locale, see here : https://developer.android.com/jetpack/androidx/releases/appcompat#1.2.0) and you have to override the applyOverrideConfiguration function in your base activity.
I have tested this solution on all API (19 to 30 - Android 4.4 to 11) and it's worked perfectly :)
Upvotes: 1
Reputation: 166
I faced this issue when i started to target SDK 29. The LocaleHelper that i created worked on every version from my min SDK(21) to 29 but specifically on Android N it didn't use to work. So after searching many stack overflow answers and visiting https://issuetracker.google.com/issues/37123942 , i managed to find/modify a solution which is now working on all android versions.
So, first i took help of https://gunhansancar.com/change-language-programmatically-in-android/ to get a locale helper. Then i modified it to my needs like this:
object LocaleHelper {
const val TAG = "LocaleHelper"
fun updateLocale(base: Context): Context {
Log.e(TAG, "updateLocale: called");
val preferenceHelper = PreferenceHelper(base)
preferenceHelper.getStringPreference(PreferenceHelper.KEY_LANGUAGE).let {
return if (it.isNotEmpty()) {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
updateResources(base, it)
} else {
updateResourcesLegacy(base, it)
}
} else {
base
}
}
}
private fun updateResources(base: Context, language: String): Context{
val loc = Locale(language)
Locale.setDefault(loc)
val configuration = base.resources.configuration
configuration.setLocale(loc)
return base.createConfigurationContext(configuration)
}
@Suppress("DEPRECATION")
private fun updateResourcesLegacy(base: Context, language: String): Context{
val locale = Locale(language)
Locale.setDefault(locale)
val configuration = base.resources.configuration
configuration.locale = locale
configuration.setLayoutDirection(locale)
base.resources.updateConfiguration(configuration, base.resources.displayMetrics)
// Log.e(TAG, "updateLocale: returning for below N")
return base
}
}
Then, in the base activity, override the attachBaseContext method like:
/**
* While attaching the base context, make sure to attach it using locale helper.
* This helps in getting localized resources in every activity
*/
override fun attachBaseContext(newBase: Context?) {
var context = newBase
newBase?.let {
context = LocaleHelper.updateLocale(it)
}
super.attachBaseContext(context)
}
But i found that this used to work on Nougat and above and not in marshmallow and lollipop. So, I tried this:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Update the locale here before loading the layout to get localized strings.
LocaleHelper.updateLocale(this)
In the oncreate of base activity i called this method. And it started working. But there were instances where if we kill the app from recent tasks and then start it, the first screen wasn't localized. So to tackle that, i created a static temp variable in app class that held the value for initial app instance.
companion object {
val TAG = "App"
var isFirstLoad = true
}
And in my first screen of app, i did this in oncreate:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Need to call this so that when the application is opened for first time and we need to update the locale.
if(App.isFirstLoad) {
LocaleHelper.updateLocale(this)
App.isFirstLoad = false
}
setContentView(R.layout.activity_dash_board)
Now i tested on redmi 4a (Android 7) on which it wasn't working previously, emulators of sdk 21,23,27,29 and in redmi note 5 pro and pixel devices. In all these, the in app language selection works properly.
Sorry for the long post, but please do suggest for an optimized way! Thanks!
EDIT 1: So i faced this issue where it was not working on android 7.1 (sdk 25). I asked gunhan for help and then made this change.
override fun applyOverrideConfiguration(overrideConfiguration: Configuration?) {
super.applyOverrideConfiguration(LocaleHelper.applyOverrideConfiguration(baseContext, overrideConfiguration))
}
I added this function in my LocaleHelper:
fun applyOverrideConfiguration(base: Context, overrideConfiguration: Configuration?): Configuration? {
if (overrideConfiguration != null && Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
val uiMode = overrideConfiguration.uiMode
overrideConfiguration.setTo(base.resources.configuration)
overrideConfiguration.uiMode = uiMode
}
return overrideConfiguration
}
And now it's finally working. Thanks Gunhan!
Upvotes: 3
Reputation: 318
Create a new class extends ContextWrapper
public class MyContextWrapper extends ContextWrapper {
public MyContextWrapper(Context base) {
super(base);
}
@TargetApi(Build.VERSION_CODES.N)
public static ContextWrapper wrap(Context context, Locale newLocale) {
Resources res = context.getResources();
Configuration configuration = res.getConfiguration();
if (VersionUtils.isAfter24()) {
configuration.setLocale(newLocale);
LocaleList localeList = new LocaleList(newLocale);
LocaleList.setDefault(localeList);
configuration.setLocales(localeList);
context = context.createConfigurationContext(configuration);
} else if (VersionUtils.isAfter17()) {
configuration.setLocale(newLocale);
context = context.createConfigurationContext(configuration);
} else {
configuration.locale = newLocale;
res.updateConfiguration(configuration, res.getDisplayMetrics());
}
return new ContextWrapper(context);
}
}
override Activity's attachBaseContext method
@Override
protected void attachBaseContext(Context newBase) {
Locale languageType = LanguageUtil.getLanguageType(mContext);
super.attachBaseContext(MyContextWrapper.wrap(newBase, languageType));
}
finish the activity and start it again,new locale will become effective.
demo:https://github.com/fanturbo/MultiLanguageDemo
Upvotes: 13