Update:
Published locale-helper as a library which can be found at https://github.com/zeugma-solutions/locale-helper-android
While developing your awesome application, sometimes you are required to add a feature to change the language of your app on the fly. However, Android OS does not directly support this behaviour. And therefore, you need to solve this situation in some other ways.
Android by default uses the locale of the device to select the appropriate language dependent resources. And most of the time this behaviour is enough for common applications.
However, sometimes there is some business requirements that you need to implement. To do that I will outline the details of changing the language of your application programmatically on the fly.
Here is the appropriate way of changing the locale of the application:
There are some difficulties which you have to overcome to change the language programmatically.
- Your application will not remember your language change after it is closed or recreated during the configuration change.
- You should update currently visible UI properly according to the selected language.
Solution
“LocaleHelper” is the solution all you need. You just have to initialize locale on your application’s main class. After that all your language changes will persist.
After the recent changes in Android API Version 24(Nougat) we need to override attachBaseContext to reflect changes.
LocaleHelper.javA
if you call onAttach(Context context) constructor it will just set default locale of your device as the default locale of your application.
if you call onAttach(Context context, String defaultLanguage) constructor it will set the given language as the default language of your application for the first time that your application opens. After that you can change your locale by using the buttons or any other method that you provide to your users through your layout.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.gunhansancar.changelanguageexample.helper; | |
import android.annotation.TargetApi; | |
import android.content.Context; | |
import android.content.SharedPreferences; | |
import android.content.res.Configuration; | |
import android.content.res.Resources; | |
import android.os.Build; | |
import android.preference.PreferenceManager; | |
import java.util.Locale; | |
/** | |
* This class is used to change your application locale and persist this change for the next time | |
* that your app is going to be used. | |
* <p/> | |
* You can also change the locale of your application on the fly by using the setLocale method. | |
* <p/> | |
* Created by gunhansancar on 07/10/15. | |
*/ | |
public class LocaleHelper { | |
private static final String SELECTED_LANGUAGE = "Locale.Helper.Selected.Language"; | |
public static Context onAttach(Context context) { | |
String lang = getPersistedData(context, Locale.getDefault().getLanguage()); | |
return setLocale(context, lang); | |
} | |
public static Context onAttach(Context context, String defaultLanguage) { | |
String lang = getPersistedData(context, defaultLanguage); | |
return setLocale(context, lang); | |
} | |
public static String getLanguage(Context context) { | |
return getPersistedData(context, Locale.getDefault().getLanguage()); | |
} | |
public static Context setLocale(Context context, String language) { | |
persist(context, language); | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { | |
return updateResources(context, language); | |
} | |
return updateResourcesLegacy(context, language); | |
} | |
private static String getPersistedData(Context context, String defaultLanguage) { | |
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); | |
return preferences.getString(SELECTED_LANGUAGE, defaultLanguage); | |
} | |
private static void persist(Context context, String language) { | |
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context); | |
SharedPreferences.Editor editor = preferences.edit(); | |
editor.putString(SELECTED_LANGUAGE, language); | |
editor.apply(); | |
} | |
@TargetApi(Build.VERSION_CODES.N) | |
private static Context updateResources(Context context, String language) { | |
Locale locale = new Locale(language); | |
Locale.setDefault(locale); | |
Configuration configuration = context.getResources().getConfiguration(); | |
configuration.setLocale(locale); | |
configuration.setLayoutDirection(locale); | |
return context.createConfigurationContext(configuration); | |
} | |
@SuppressWarnings("deprecation") | |
private static Context updateResourcesLegacy(Context context, String language) { | |
Locale locale = new Locale(language); | |
Locale.setDefault(locale); | |
Resources resources = context.getResources(); | |
Configuration 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; | |
} | |
} |
MainApplication.java
You need to override attachBaseContext and call LocaleHelper.onAttach() to initialize the locale settings in your application.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.gunhansancar.changelanguageexample; | |
import android.app.Application; | |
import android.content.Context; | |
import com.gunhansancar.changelanguageexample.helper.LocaleHelper; | |
public class MainApplication extends Application { | |
@Override | |
protected void attachBaseContext(Context base) { | |
super.attachBaseContext(LocaleHelper.onAttach(base, "en")); | |
} | |
} |
MAINACTIVITY.java
Here you can find an example activity which has two buttons and three textviews on it to change the locale of the textviews on the fly.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.gunhansancar.changelanguageexample; | |
import android.content.Context; | |
import android.content.res.Resources; | |
import android.os.Bundle; | |
import android.support.v7.app.AppCompatActivity; | |
import android.widget.Button; | |
import android.widget.TextView; | |
import com.gunhansancar.changelanguageexample.helper.LocaleHelper; | |
import butterknife.BindView; | |
import butterknife.ButterKnife; | |
import butterknife.OnClick; | |
public class MainActivity extends AppCompatActivity { | |
@BindView(R.id.titleTextView) | |
TextView mTitleTextView; | |
@BindView(R.id.descTextView) | |
TextView mDescTextView; | |
@BindView(R.id.aboutTextView) | |
TextView mAboutTextView; | |
@BindView(R.id.toTRButton) | |
Button mToTRButton; | |
@BindView(R.id.toENButton) | |
Button mToENButton; | |
@Override | |
protected void onCreate(Bundle savedInstanceState) { | |
super.onCreate(savedInstanceState); | |
setContentView(R.layout.activity_main); | |
ButterKnife.bind(this); | |
setTitle(getString(R.string.main_activity_toolbar_title)); | |
} | |
@Override | |
protected void attachBaseContext(Context base) { | |
super.attachBaseContext(LocaleHelper.onAttach(base)); | |
} | |
@OnClick(R.id.toTRButton) | |
public void onChangeToTRClicked() { | |
updateViews("tr"); | |
} | |
@OnClick(R.id.toENButton) | |
public void onChangeToENClicked() { | |
updateViews("en"); | |
} | |
private void updateViews(String languageCode) { | |
Context context = LocaleHelper.setLocale(this, languageCode); | |
Resources resources = context.getResources(); | |
mTitleTextView.setText(resources.getString(R.string.main_activity_title)); | |
mDescTextView.setText(resources.getString(R.string.main_activity_desc)); | |
mAboutTextView.setText(resources.getString(R.string.main_activity_about)); | |
mToTRButton.setText(resources.getString(R.string.main_activity_to_tr_button)); | |
mToENButton.setText(resources.getString(R.string.main_activity_to_en_button)); | |
setTitle(resources.getString(R.string.main_activity_toolbar_title)); | |
} | |
} |
In your activity override attachBaseContext and call LocaleHelper.onAttach(). After that you just need to take care of the activity or fragment which includes the language change buttons. Other activities will not require any modifications at all.
You have two options to update currently visible layout:
- First, you can just update the text or any other language dependent resources one by one.
- Second, you can call activity.recreate() to restart currently loaded activity. Then the activity will reload the resources with the correct locale. However, if you select this option your users will notice the affect because it will close your application and you get black screen for a very small amount of time and then recreate your activity again in meanwhile.
Sample Project
For your reference, I added a sample project on github which can be found here. The sample project is targeting Nougat API 25(7.1). You can easily change the target for your needs.
If you have any question regarding to change locale of your android application, feel free to ask.
Update for new App Bundle Format
Google introduced a new App Bundle format to split apk files in smaller sizes when they’re being installed on the client devices. However, this means that we cannot have dynamic language changes in our applications.
To prevent that split we need to add extra lines in our build.gradle file inside the app folder like below.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
android { | |
//… | |
//… removed for brevity | |
bundle { | |
language { | |
enableSplit = false | |
} | |
} | |
} |