Supporting multiple language in my app - android

I want make support of my application English, Hindi, Urdu.
For that I created two values strings folders like values-hi,values-ur then default strings.xml for English when I click on Urdu I need to load Urdu strings file for all labels and make UI RTL support.

I solved a similar issue using some sort of ContextWrapper to force the language in any activity.
The ContextWrapper code is:
public class ContextWrapper extends android.content.ContextWrapper {
public ContextWrapper(Context base) {
super(base);
}
#SuppressWarnings("deprecation")
public static ContextWrapper wrap(Context context, Locale newLocale) {
Resources res = context.getResources();
Configuration configuration = res.getConfiguration();
if (BuildUtils.isAtLeast24Api()) {
configuration.setLocale(newLocale);
LocaleList localeList = new LocaleList(newLocale);
LocaleList.setDefault(localeList);
configuration.setLocales(localeList);
context = context.createConfigurationContext(configuration);
} else if (BuildUtils.isAtLeast17Api()) {
configuration.setLocale(newLocale);
context = context.createConfigurationContext(configuration);
} else {
configuration.locale = newLocale;
res.updateConfiguration(configuration, res.getDisplayMetrics());
}
return new ContextWrapper(context);
}
}
You have to inherit attachBaseContext lifecycle callback in each activity you want to force the language, eg.
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(ContextWrapper.wrap(base, Locale.ENGLISH));
}
Obviously if you want to change language even in the current activity you have to reload it.

Related

Using specified strings.xml

I have two different strings.xml for multi-language app. Where can I set that I want to use English strings.xml file independent of phone locale language always but use Chinese language only in case when on splash screen we have chosen it?
To support language setting in your application you need to change current configuration and restart activity to reload all resources. You can change current configuration like this:
void changeLanguage(Context context, String language) {
final Locale locale = new Locale(language);
Locale.setDefault(locale);
final Resources res = context.getResources();
final Configuration config = new Configuration(res.getConfiguration());
config.setLocale(locale);
res.updateConfiguration(config, res.getDisplayMetrics());
}
And then don't forget to recreate your activity activity.recreate(); or reload your resources manually.
Also you need to set your locale on every process start. You can do it in application onCreate:
public void YourApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
changeLanguage(this, getLanguageSettingsFromPreferences());
}
}

Android loads values from default strings file (strings.xml) after changing locale for 2nd time

I am working on an android app where i change the locale (English to Arabic) on main screen with a single button. It works fine on main screen but the issue raises when i change the language more than once.
Following steps to regenerate:
On main (login) screen, Current Language is English and i change it to Arabic. (works fine)
Go to signup or forgot password page and Language is changed now. (Arabic)
Get back to main screen and change the locale back to English from Arabic. (Works on Login screen)
Go to signup page and now the direction is changed but string are loaded from Arabic. (Current language is English)
Here is my code for changing locale.
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.preference.PreferenceManager;
import android.support.v4.text.TextUtilsCompat;
import android.support.v4.view.ViewCompat;
import android.view.View;
import java.util.Locale;
public class LocaleSettings {
public static final String LANGUAGE_ENGLISH = "en";
public static final String LANGUAGE_ARABIC = "ar";
public static final String CURRENT_LANGUAGE = "currentLanguage";
/**
* Loads the current language of application
*
* #param context current context, pass "this" for current view context
*/
public static void loadLocal(Context context) {
setLocal(context, PreferenceManager.getDefaultSharedPreferences(context).getString(CURRENT_LANGUAGE, ""));
}
/**
* This fucntion sets the application language
*
* #param context - current context. pass "this" for current view context
* #param lang Language String, i.e. "en" or "ar"
*/
public static void setLocal(Context context, String lang) {
Locale locale = new Locale(lang);
Locale.setDefault(locale);
Configuration config = new Configuration();
config.setLocale(locale);
context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics());
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = pref.edit();
editor.putString(CURRENT_LANGUAGE, lang);
editor.apply();
editor.commit();
}
/**
* Use to change application language using current context
*
* #param context pass "this" for current view context
*/
public static void switchLanguage(Context context) {
if (getCurrentLanguage(context).equals(LANGUAGE_ENGLISH))
setLocal(context, LANGUAGE_ARABIC);
else
setLocal(context, LANGUAGE_ENGLISH);
}
/**
* Get application current active language
*
* #param context pass "this" for current view context
* #return String - language string i.e. en or ar
*/
public static String getCurrentLanguage(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getString(CURRENT_LANGUAGE, "");
}
public static boolean isRTL(String locale) {
return TextUtilsCompat.getLayoutDirectionFromLocale(new Locale(locale)) == ViewCompat.LAYOUT_DIRECTION_RTL ? true : false;
}
public static void enforceDirectionIfRTL(Context context){
if(isRTL(getCurrentLanguage(context))){
((Activity) context).getWindow().getDecorView().setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
}
}
}
Here is code for login activity
public class LoginActivity extends AppCompatActivity {
private Button loginButton = null;
private EditText account_no = null;
private EditText password = null;
final UserApi userApi = JoezdanServiceGenerator.createService(UserApi.class);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LocaleSettings.loadLocal(this);
setContentView(R.layout.activity_login);
configureLanaguageButton();
}
private void configureLanaguageButton() {
final ImageButton selectLocale = (ImageButton) findViewById(R.id.btnSelectLanguage);
selectLocale.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
LocaleSettings.switchLanguage(LoginActivity.this);
recreate();
}
});
}
... eliminating irrelevant code
}
This is my first android app so pardon me if there are mistakes. Thanks in advance.
From the Android documentation:
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.
Changing language internally is an exception.
First, please read this documentation and acknowledge the pitfalls of the design.
To summarize, here are 2 things that I want to mention:
updateConfiguration is deprecated, so we need another version to support backward compatible.
We need to override attachBaseContext to every activity to reflect changes.
Here is the implementation:
#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;
configuration.setLayoutDirection(locale);
resources.updateConfiguration(configuration, resources.getDisplayMetrics());
return context;
}
In order to support backward compatible, check version before changing language:
public static Context setLocale(Context context, String language) {
// You can save SharedPreference here
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return updateResources(context, language);
}
return updateResourcesLegacy(context, language);
}
In your LoginActivity, after changing the locale, you don't have to recreate the activity, you can get the resource then change every TextView manually.
Context context = LocaleUtils.setLocale(this, lang);
Resources resources = context.getResources();
yourFirstTextView.setText(resources.getString(R.string.your_first_text_res)
// ... yourSecondTextView....
In every activity, to reflect changes, add this function:
#Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(LocaleUtils.onAttach(newBase));
}
Btw, there is a bug, you can't change title language of Toolbar. In your onCreate(), call this function manually, setTitle("your Title")
I know these kind issues are ugly, and the solutions are a little bit hacky. But let give it a try. Let me know if that helps you out. :)
Full source code can be found here: https://github.com/gunhansancar/ChangeLanguageExample/blob/master/app/src/main/java/com/gunhansancar/changelanguageexample/helper/LocaleHelper.java
with greate article: https://gunhansancar.com/change-language-programmatically-in-android/
First thing you need to make sure that after the user change locals you close all the activities and restart app, something like this
Intent intent = new Intent(AcSettingsView.this, MainActivityView.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
finishAffinity();
Another thing to watch for is to make sure you set your locals everytime app starts
in your first screen like this
helper = new PreferenceHelper(this);
String lang = helper.getData(Constants.LANGUAGE_CODE);
if (eng) {
Utility.setLocale(getBaseContext(), Constants.ENGLISH_CODE);
} else {
Utility.setLocale(getBaseContext(), Constants.ARABIC_CODE);
}

Resources and layout direction rendered incorrectly only on Android 8.0 and above

I have a multilingual app with primary language English and secondary language Arabic.
I am calling setLocale() in the onCreate() of every Activity in my app:
public static void setLocale(Locale locale){
Locale.setDefault(locale);
Context context = MyApplication.getInstance();
final Resources resources = context.getResources();
final Configuration config = resources.getConfiguration();
config.setLocale(locale);
context.getResources().updateConfiguration(config,
resources.getDisplayMetrics());
}
where locale is one of the following:
The above method is called before super.onCreate(savedInstanceState) gets called.
As described in the documentation,
I have added android:supportsRtl="true" in the manifest.
I have changed all xml properties with left and right attributes to start and end respectively.
I have put Arabic-language strings in res\values-ar\strings folder and drawable resources in res\drawable-ar folder (and similarly for other resources).
The above setup works properly. After changing the Locale to ar-AE, Arabic text & resources are correctly displayed in my Activities.
However, there is a problem with both resources and layout direction for all Android devices with version 8.0 and above.
On a device with version less than 8.0, an RTL screen correctly looks like this:
And on all devices with 8.0+, the same screen turns up looking like this:
which is wrong.
It turns out that both the direction and the resources are getting
displayed incorrectly.
There are two problems here:
The correct Locale does not seem to be updated across the app configuration.
The direction of the text and drawables is opposite of what it should be.
With respect to direction, a curious method called setLayoutDirection() exists which I had not noticed before.
I would like to know what this problem is, why it happens in Oreo and what is the solution for it. Please help / comment on this.
EDIT:
According to the API Differences
report, the
updateConfiguration()
method was indeed deprecated in Android 7.1 (API level 25).
Also, found all the relevant posts on this. In order of importance:
1. Android N change language programmatically.
2. Android context.getResources.updateConfiguration() deprecated.
3. How to change Android O / Oreo / api 26 app language.
4. Android RTL issue in API 24 and higher on locale change
5. Change language programmatically (Android N 7.0 - API 24).
6. Android N - Change Locale in runtime.
7. RTL layout bug in android Oreo.
The updateConfiguration() method was deprecated
Now we need to use createConfigurationContext()
I have managed this way
create a new class ContextWrapper
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.os.LocaleList;
import java.util.Locale;
public class ContextWrapper extends android.content.ContextWrapper {
public ContextWrapper(Context base) {
super(base);
}
public static ContextWrapper wrap(Context context, Locale newLocale) {
Resources res = context.getResources();
Configuration configuration = res.getConfiguration();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
configuration.setLocale(newLocale);
LocaleList localeList = new LocaleList(newLocale);
LocaleList.setDefault(localeList);
configuration.setLocales(localeList);
context = context.createConfigurationContext(configuration);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
configuration.setLocale(newLocale);
context = context.createConfigurationContext(configuration);
} else {
configuration.locale = newLocale;
res.updateConfiguration(configuration, res.getDisplayMetrics());
}
return new ContextWrapper(context);
}}
create a new class of BaseActivity
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import java.util.Locale;
/**
* Created by nilesh on 20/3/18.
*/
public class BaseActivity extends AppCompatActivity {
#Override
protected void attachBaseContext(Context newBase) {
Locale newLocale;
String lang = new PrefManager(newBase).getLanguage();
if (lang.equals("zh_CN")) {
newLocale = new Locale("zh");
} else {
newLocale = new Locale(lang);
}
Context context = ContextWrapper.wrap(newBase, newLocale);
super.attachBaseContext(context);
}
}
Create a PrefManager class to store locale
import android.content.Context;
import android.content.SharedPreferences;
public class PrefManager {
private SharedPreferences.Editor editor;
private Context mContext;
private SharedPreferences prefs;
private final String LANGUAGE = "language";
private final String PREF = "user_data";
public PrefManager(Context mContext) {
this.mContext = mContext;
}
public String getLanguage() {
this.prefs = this.mContext.getSharedPreferences(PREF, 0);
return this.prefs.getString(LANGUAGE, "en_US");
}
public void setLanguage(String language) {
this.editor = this.mContext.getSharedPreferences(PREF, 0).edit();
this.editor.putString(LANGUAGE, language);
this.editor.apply();
}
}
Now you need to extends your BaseActivity in your all activity like
public class OrdersActivity extends BaseActivity
Now when your need to change Locale just update the value in PrefManager and restart your activity
PrefManager prefManager= new PrefManager(this);
prefManager.setLanguage("zh_CN");
// restart your activity
NOTE
You can download source code from github repo
The method Resources#updateConfiguration (Configuration config, DisplayMetrics metrics) is deprecated in API level 25.
The doc suggests to use Context#createConfigurationContext (Configuration overrideConfiguration)
You can simply make a base activity which is a common parent of all the activities as shown below.
public class BaseActivity
extends AppCompatActivity {
private static final String LANGUAGE_CODE_ENGLISH = "en";
private static final String LANGUAGE_CODE_ARABIC = "ar";
#Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(getLanguageAwareContext(newBase));
}
private static Context getLanguageAwareContext(Context context) {
Configuration configuration = context.getResources().getConfiguration();
configuration.setLocale(new Locale(getLanguageCode()));
return context.createConfigurationContext(configuration);
}
// Rewrite this method according to your needs
private static String getLanguageCode() {
return LANGUAGE_CODE_ARABIC;
}
}
Notes
getLanguageCode() should return language code. Typically the language code or any other data representing it is stored in preferences.
To change languages dynamically, recreate activity after setting appropriate language code into preferences.
Use activity context rather than application context to access any locale specific resources. In other words, use this or ActivityName.this from activities and getActivity() from fragments instead of getApplicationContext().
The complete solution to this problem consists of three steps:
STEP 1:
In the onCreate() of your BaseActivity (or all your Activitys), set the Locale as follows:
#Override
protected void onCreate(Bundle savedInstanceState) {
// set the Locale the very first thing
Utils.setLocale(Utils.getSavedLocale());
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
......
......
}
where getSavedLocale() is the Locale corresponding to the current region (this will be specific for your project ... ).
And the method Utils.setLocale(...) is defined as follows:
public static void setLocale(Locale locale){
Context context = MyApplication.getInstance();
Resources resources = context.getResources();
Configuration configuration = resources.getConfiguration();
Locale.setDefault(locale);
configuration.setLocale(locale);
configuration.setLayoutDirection(locale);
// updateConfiguration(...) is deprecated in N
if (Build.VERSION.SDK_INT >= 25) {
context = context.getApplicationContext().createConfigurationContext(configuration);
context = context.createConfigurationContext(configuration);
}
context.getResources().updateConfiguration(configuration,
resources.getDisplayMetrics());
}
This sets the correct Locale in every Activity. This is enough for apps supporting API level 25. For API level 26 & above, STEP 2 and STEP 3 are also required.
STEP 2:
Override the following method in your BaseActivity:
#Override
protected void attachBaseContext(Context newBase) {
newBase = Utils.getLanguageAwareContext(newBase);
super.attachBaseContext(newBase);
}
where the function getLanguageAwareContext(...) is defined as follows:
public static Context getLanguageAwareContext(Context context){
Configuration configuration = context.getResources().getConfiguration();
Locale locale = getIntendedLocale();
configuration.setLocale(locale);
configuration.setLayoutDirection(locale);
return context.createConfigurationContext(configuration);
}
This, along with STEP 1, sets the correct Locale in every Activity of your app for API level 26 and above.
One more step, however, is required for setting the language direction correctly ...
STEP 3:
In the onCreate() of your BaseActivity, add the following code:
#Override
protected void onCreate(Bundle savedInstanceState) {
....
....
// yup, it's a legit bug ... :)
if (Build.VERSION.SDK_INT >= 26) {
getWindow().getDecorView().setLayoutDirection(Utils.isRTL()
? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
}
....
....
}
where the isRTL() function is defined as follows:
public static boolean isRTL(){
return TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL;
}
The above steps should take care of all issues (at least regarding setting the Locale and text direction) on all extant versions of Android.
public void setLocale(final Context ctx, final String lang) {
AppSettings.getInstance(ctx).save(PrefKeys.language, lang);
final Locale loc = new Locale(lang);
Locale.setDefault(loc);
final Configuration cfg = new Configuration();
cfg.locale = loc;
ctx.getResources().updateConfiguration(cfg, null);
}
Change to English: setLocale(getActivity(), "en";
Change to Arabic : setLocale(getActivity(), "ar");
After this you need to restart the application to get the Language change effects.
Totally closed the app because i think it is making cache in background.
Use below code thats how I achieved it in my case, you also can give it a try:
Intent mStartActivity = new Intent(ctc, SplashActivity.class);
int mPendingIntentId = 123456;
PendingIntent mPendingIntent = PendingIntent.getActivity(ctc, mPendingIntentId, mStartActivity, PendingIntent.FLAG_CANCEL_CURRENT);
AlarmManager mgr = (AlarmManager)ctc.getSystemService(Context.ALARM_SERVICE);
mgr.set(AlarmManager.RTC, System.currentTimeMillis() + 100, mPendingIntent);
System.exit(0);
Resources.updateConfiguration is deprecated use this instead :
fun setLocale(old: Context, locale: Locale): Context {
val oldConfig = old.resources.configuration
oldConfig.setLocale(locale)
return old.createConfigurationContext(oldConfig)
}
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(newBase?.let { setLocale(it, Locale("ar")) })
}
In Java
private Context setLocale(Context old, Locale locale) {
Configuration oldConfig = old.getResources().getConfiguration();
oldConfig.setLocale(locale);
return old.createConfigurationContext(oldConfig);
}
#Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(setLocale(newBase, new Locale("ar")));
}

Change language in android application resources

after studying about supporting multiple languages in android application, i have good the basic idea about how i can make resources for different languages within application, for example i want to add Spanish language in my application so in res direction within my application i have added values-en directory and in that direct i have added strings resource and within that resource i have added strings with values of Spanish text, in my application default language is English now i want to know how can i switch it to Spanish, i have the resources ready i just need to change my application language to Spanish
Use below code in your onCreate() after setContentView:
String languageToLoad = "es";
Locale locale = new Locale(languageToLoad);
Configuration config = new Configuration();
config.locale = locale;
getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics());
You also have to change your value folder to values-es. values-en is for English. Hope this helps.
To change App locale follow below code:
1) Create LocaleUtils class:
public class LocaleUtils {
private static Locale sLocale;
public static void setLocale(Locale locale) {
sLocale = locale;
if(sLocale != null) {
Locale.setDefault(sLocale);
}
}
public static void updateConfig(ContextThemeWrapper wrapper) {
if(sLocale != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
Configuration configuration = new Configuration();
configuration.setLocale(sLocale);
wrapper.applyOverrideConfiguration(configuration);
}
}
public static void updateConfig(Application app, Configuration configuration) {
if (sLocale != null && Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
//Wrapping the configuration to avoid Activity endless loop
Configuration config = new Configuration(configuration);
// We must use the now-deprecated config.locale and res.updateConfiguration here,
// because the replacements aren't available till API level 24 and 17 respectively.
config.locale = sLocale;
Resources res = app.getBaseContext().getResources();
res.updateConfiguration(config, res.getDisplayMetrics());
}
}
}
2) In Application Class:
public class YourAppName extends Application {
public void onCreate(){
super.onCreate();
LocaleUtils.setLocale(new Locale("es"));
LocaleUtils.updateConfig(this, getBaseContext().getResources().getConfiguration());
}
#Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
LocaleUtils.updateConfig(this, newConfig);
}
}
3) Remember, Your Application class name and <application android:name=".YourAppName"> should be same. Otherwise it won't work. Kudos to this answer.

Set Locale programmatically

My app supports 3 (soon 4) languages. Since several locales are quite similar I'd like to give the user the option to change locale in my application, for instance an Italian person might prefer Spanish over English.
Is there a way for the user to select among the locales that are available for the application and then change what locale is used? I don't see it as a problem to set locale for each Activity since it is a simple task to perform in a base class.
Hope this help(in onResume):
Locale locale = new Locale("ru");
Locale.setDefault(locale);
Configuration config = getBaseContext().getResources().getConfiguration();
config.locale = locale;
getBaseContext().getResources().updateConfiguration(config,
getBaseContext().getResources().getDisplayMetrics());
EDIT 21st OCTOBER 2022
Starting from Android 13 you can now set your locale from the AppCompatDelegate with the method setApplicationLocales(appLocale) and it has backwards compatibility.
For API 33 and above:
The process is quite simple and you can achieve it as follow:
val appLocale: LocaleListCompat =
LocaleListCompat.forLanguageTags("xx-YY")
// Call this on the main thread as it may require Activity.restart()
AppCompatDelegate.setApplicationLocales(appLocale)
While using this you should no longer need to call recreate() on the activity as the docs mention:
Note that calling setApplicationLocales() recreates your Activity, unless your app handles locale configuration changes by itself.
API lower than 33
If you are targetting an API lower than 33 you will need to also add a service to your manifest in order to handle locale storage:
<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false"
android:exported="false">
<meta-data
android:name="autoStoreLocales"
android:value="true" />
</service>
This is only available for AndroidX from androidx.appcompat:appcompat:1.6.0-alpha01
You can find the documentation here
-------------- Old Answers ------------
For people still looking for this answer, since configuration.locale was deprecated from API 24, you can now use:
configuration.setLocale(locale);
Take in consideration that the minSkdVersion for this method is API 17.
Full example code:
#SuppressWarnings("deprecation")
private void setLocale(Locale locale){
SharedPrefUtils.saveLocale(locale); // optional - Helper method to save the selected language to SharedPreferences in case you might need to attach to activity context (you will need to code this)
Resources resources = getResources();
Configuration configuration = resources.getConfiguration();
DisplayMetrics displayMetrics = resources.getDisplayMetrics();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1){
configuration.setLocale(locale);
} else{
configuration.locale=locale;
}
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N){
getApplicationContext().createConfigurationContext(configuration);
} else {
resources.updateConfiguration(configuration,displayMetrics);
}
}
Don't forget that, if you change the locale with a running Activity, you will need to restart it for the changes to take effect.
EDIT 11th MAY 2018
As from #CookieMonster's post, you might have problems keeping the locale change in higher API versions. If so, add the following code to your Base Activity (BaseActivity extends AppCompatActivity / other activities) so that you update the context locale on every Activity creation:
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(updateBaseContextLocale(base));
}
private Context updateBaseContextLocale(Context context) {
String language = SharedPrefUtils.getSavedLanguage(); // Helper method to get saved language from SharedPreferences
Locale locale = new Locale(language);
Locale.setDefault(locale);
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
return updateResourcesLocale(context, locale);
}
return updateResourcesLocaleLegacy(context, locale);
}
#TargetApi(Build.VERSION_CODES.N_MR1)
private Context updateResourcesLocale(Context context, Locale locale) {
Configuration configuration = new Configuration(context.getResources().getConfiguration());
configuration.setLocale(locale);
return context.createConfigurationContext(configuration);
}
#SuppressWarnings("deprecation")
private Context updateResourcesLocaleLegacy(Context context, Locale locale) {
Resources resources = context.getResources();
Configuration configuration = resources.getConfiguration();
configuration.locale = locale;
resources.updateConfiguration(configuration, resources.getDisplayMetrics());
return context;
}
If you use this, don't forget to save the language to SharedPreferences when you set the locale with setLocale(locale)
EDIT 7th APRIL 2020
You might be experiencing issues in Android 6 and 7, and this happens due to an issue in the androidx libraries while handling the night mode. For this you will also need to override applyOverrideConfiguration in your base activity and update the configuration's locale in case a fresh new locale one is created.
Sample code:
#Override
public void applyOverrideConfiguration(Configuration overrideConfiguration) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) {
// update overrideConfiguration with your locale
setLocale(overrideConfiguration) // you will need to implement this
}
super.applyOverrideConfiguration(overrideConfiguration);
}
I had a problem with setting locale programmatically with devices
that has Android OS N and higher.
For me the solution was writing this code in my base activity:
(if you don't have a base activity then you should make these changes in all of your activities)
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(updateBaseContextLocale(base));
}
private Context updateBaseContextLocale(Context context) {
String language = SharedPref.getInstance().getSavedLanguage();
Locale locale = new Locale(language);
Locale.setDefault(locale);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return updateResourcesLocale(context, locale);
}
return updateResourcesLocaleLegacy(context, locale);
}
#TargetApi(Build.VERSION_CODES.N)
private Context updateResourcesLocale(Context context, Locale locale) {
Configuration configuration = context.getResources().getConfiguration();
configuration.setLocale(locale);
return context.createConfigurationContext(configuration);
}
#SuppressWarnings("deprecation")
private Context updateResourcesLocaleLegacy(Context context, Locale locale) {
Resources resources = context.getResources();
Configuration configuration = resources.getConfiguration();
configuration.locale = locale;
resources.updateConfiguration(configuration, resources.getDisplayMetrics());
return context;
}
note that here it is not enough to call
createConfigurationContext(configuration)
you also need to get the context that this method returns and then to set this context in the attachBaseContext method.
As no answer is complete for the current way to solve this problem, I try to give instructions for a complete solution. Please comment if something is missing or could be done better.
General information
First, there exist some libraries that want to solve the problem but they all seem outdated or are missing some features:
https://github.com/delight-im/Android-Languages
Outdated (see issues)
When selecting a language in the UI, it always shows all languages (hardcoded in the library), not only those where translations exist
https://github.com/akexorcist/Android-LocalizationActivity
Seems quite sophisticated and might use a similar approach as shown below
Further I think writing a library might not be a good/easy way to solve this problem because there is not very much to do, and what has to be done is rather changing existing code than using something completely decoupled.
Therefore I composed the following instructions that should be complete.
My solution is mainly based on https://github.com/gunhansancar/ChangeLanguageExample (as already linked to by localhost). It is the best code I found to orientate at. Some remarks:
As necessary, it provides different implementations to change locale for Android N (and above) and below
It uses a method updateViews() in each Activity to manually update all strings after changing locale (using the usual getString(id)) which is not necessary in the approach shown below
It only supports languages and not complete locales (which also include region (country) and variant codes)
I changed it a bit, decoupling the part which persists the chosen locale (as one might want to do that separately, as suggested below).
Solution
The solution consists of the following two steps:
Permanently change the locale to be used by the app
Make the app use the custom locale set, without restarting
Step 1: Change the locale
Use the class LocaleHelper, based on gunhansancar's LocaleHelper:
Add a ListPreference in a PreferenceFragment with the available languages (has to be maintained when languages should be added later)
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;
import mypackage.SettingsFragment;
/**
* Manages setting of the app's locale.
*/
public class LocaleHelper {
public static Context onAttach(Context context) {
String locale = getPersistedLocale(context);
return setLocale(context, locale);
}
public static String getPersistedLocale(Context context) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
return preferences.getString(SettingsFragment.KEY_PREF_LANGUAGE, "");
}
/**
* Set the app's locale to the one specified by the given String.
*
* #param context
* #param localeSpec a locale specification as used for Android resources (NOTE: does not
* support country and variant codes so far); the special string "system" sets
* the locale to the locale specified in system settings
* #return
*/
public static Context setLocale(Context context, String localeSpec) {
Locale locale;
if (localeSpec.equals("system")) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
locale = Resources.getSystem().getConfiguration().getLocales().get(0);
} else {
//noinspection deprecation
locale = Resources.getSystem().getConfiguration().locale;
}
} else {
locale = new Locale(localeSpec);
}
Locale.setDefault(locale);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return updateResources(context, locale);
} else {
return updateResourcesLegacy(context, locale);
}
}
#TargetApi(Build.VERSION_CODES.N)
private static Context updateResources(Context context, Locale locale) {
Configuration configuration = context.getResources().getConfiguration();
configuration.setLocale(locale);
configuration.setLayoutDirection(locale);
return context.createConfigurationContext(configuration);
}
#SuppressWarnings("deprecation")
private static Context updateResourcesLegacy(Context context, Locale 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;
}
}
Create a SettingsFragment like the following:
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import mypackage.LocaleHelper;
import mypackage.R;
/**
* Fragment containing the app's main settings.
*/
public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
public static final String KEY_PREF_LANGUAGE = "pref_key_language";
public SettingsFragment() {
// Required empty public constructor
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_settings, container, false);
return view;
}
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
switch (key) {
case KEY_PREF_LANGUAGE:
LocaleHelper.setLocale(getContext(), PreferenceManager.getDefaultSharedPreferences(getContext()).getString(key, ""));
getActivity().recreate(); // necessary here because this Activity is currently running and thus a recreate() in onResume() would be too late
break;
}
}
#Override
public void onResume() {
super.onResume();
// documentation requires that a reference to the listener is kept as long as it may be called, which is the case as it can only be called from this Fragment
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}
#Override
public void onPause() {
super.onPause();
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
}
}
Create a resource locales.xml listing all locales with available translations in the following way (list of locale codes):
<!-- Lists available locales used for setting the locale manually.
For now only language codes (locale codes without country and variant) are supported.
Has to be in sync with "settings_language_values" in strings.xml (the entries must correspond).
-->
<resources>
<string name="system_locale" translatable="false">system</string>
<string name="default_locale" translatable="false"></string>
<string-array name="locales">
<item>#string/system_locale</item> <!-- system setting -->
<item>#string/default_locale</item> <!-- default locale -->
<item>de</item>
</string-array>
</resources>
In your PreferenceScreen you can use the following section to let the user select the available languages:
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="#string/preferences_category_general">
<ListPreference
android:key="pref_key_language"
android:title="#string/preferences_language"
android:dialogTitle="#string/preferences_language"
android:entries="#array/settings_language_values"
android:entryValues="#array/locales"
android:defaultValue="#string/system_locale"
android:summary="%s">
</ListPreference>
</PreferenceCategory>
</PreferenceScreen>
which uses the following strings from strings.xml:
<string name="preferences_category_general">General</string>
<string name="preferences_language">Language</string>
<!-- NOTE: Has to correspond to array "locales" in locales.xml (elements in same orderwith) -->
<string-array name="settings_language_values">
<item>Default (System setting)</item>
<item>English</item>
<item>German</item>
</string-array>
Step 2: Make the app use the custom locale
Now setup each Activity to use the custom locale set. The easiest way to accomplish this is to have a common base class for all activities with the following code (where the important code is in attachBaseContext(Context base) and onResume()):
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import mypackage.LocaleHelper;
import mypackage.R;
/**
* {#link AppCompatActivity} with main menu in the action bar. Automatically recreates
* the activity when the locale has changed.
*/
public class MenuAppCompatActivity extends AppCompatActivity {
private String initialLocale;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initialLocale = LocaleHelper.getPersistedLocale(this);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_settings:
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(LocaleHelper.onAttach(base));
}
#Override
protected void onResume() {
super.onResume();
if (initialLocale != null && !initialLocale.equals(LocaleHelper.getPersistedLocale(this))) {
recreate();
}
}
}
What it does is
Override attachBaseContext(Context base) to use the locale previously persisted with LocaleHelper
Detect a change of the locale and recreate the Activity to update its strings
Notes on this solution
Recreating an Activity does not update the title of the ActionBar (as already observed here: https://github.com/gunhansancar/ChangeLanguageExample/issues/1).
This can be achieved by simply having a setTitle(R.string.mytitle) in the onCreate() method of each activity.
It lets the user chose the system default locale, as well as the default locale of the app (which can be named, in this case "English").
Only language codes, no region (country) and variant codes (like fr-rCA) are supported so far. To support full locale specifications, a parser similar to that in the Android-Languages library can be used (which supports region but no variant codes).
If someone finds or has written a good parser, add a comment so I can include it in the solution.
#SuppressWarnings("deprecation")
public static void forceLocale(Context context, String localeCode) {
String localeCodeLowerCase = localeCode.toLowerCase();
Resources resources = context.getApplicationContext().getResources();
Configuration overrideConfiguration = resources.getConfiguration();
Locale overrideLocale = new Locale(localeCodeLowerCase);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
overrideConfiguration.setLocale(overrideLocale);
} else {
overrideConfiguration.locale = overrideLocale;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
context.getApplicationContext().createConfigurationContext(overrideConfiguration);
} else {
resources.updateConfiguration(overrideConfiguration, null);
}
}
Just use this helper method to force specific locale.
UDPATE 22 AUG 2017.
Better use this approach.
There is a super simple way.
in BaseActivity, Activity or Fragment override attachBaseContext
override fun attachBaseContext(context: Context) {
super.attachBaseContext(context.changeLocale("tr"))
}
extension
fun Context.changeLocale(language:String): Context {
val locale = Locale(language)
Locale.setDefault(locale)
val config = this.resources.configuration
config.setLocale(locale)
return createConfigurationContext(config)
}
Add a helper class with the following method:
public class LanguageHelper {
public static final void setAppLocale(String language, Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
Resources resources = activity.getResources();
Configuration configuration = resources.getConfiguration();
configuration.setLocale(new Locale(language));
activity.getApplicationContext().createConfigurationContext(configuration);
} else {
Locale locale = new Locale(language);
Locale.setDefault(locale);
Configuration config = activity.getResources().getConfiguration();
config.setLocale(locale);
activity.getResources().updateConfiguration(config,
activity.getResources().getDisplayMetrics());
}
}
}
And call it in your startup activity, like MainActivity.java:
public void onCreate(Bundle savedInstanceState) {
...
LanguageHelper.setAppLocale("fa", this);
...
}
Valid for API16 to API28
Just place this method some where:
Context newContext = context;
Locale locale = new Locale(languageCode);
Locale.setDefault(locale);
Resources resources = context.getResources();
Configuration config = new Configuration(resources.getConfiguration());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
config.setLocale(locale);
newContext = context.createConfigurationContext(config);
} else {
config.locale = locale;
resources.updateConfiguration(config, resources.getDisplayMetrics());
}
return newContext;
Insert this code in all your activitys using:
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(localeUpdateResources(base, "<-- language code -->"));
}
or call localeUpdateResources on fragments, adapters, etc. where you need the new context.
Credits: Yaroslav Berezanskyi
simple and easy
Locale locale = new Locale("en", "US");
Resources res = getResources();
DisplayMetrics dm = res.getDisplayMetrics();
Configuration conf = res.getConfiguration();
conf.locale = locale;
res.updateConfiguration(conf, dm);
where "en" is language code and "US" is country code.
I found the androidx.appcompat:appcompat:1.1.0 bug can also be fixed by simply calling getResources() in applyOverrideConfiguration()
#Override public void
applyOverrideConfiguration(Configuration cfgOverride)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
// add this to fix androidx.appcompat:appcompat 1.1.0 bug
// which happens on Android 6.x ~ 7.x
getResources();
}
super.applyOverrideConfiguration(cfgOverride);
}
Android 13 introduces Per-app language preferences which significantly simplifies handling in-app Locale changes.
Disclaimer: Android 13 is currently in "Developer Preview" at the time of writing and part of what mentioned here is in alpha (thus, subject to change).
As visible in the docs there are couple of new ways to handle such changes, for compatibility the best one seem to be through AppCompatDelegate:
val appLocale: LocaleListCompat = LocaleListCompat.forLanguageTags("xx-YY")
// Call this on the main thread as it may require Activity.restart()
AppCompatDelegate.setApplicationLocales(appLocale)
(Method is available from androidx.appcompat:appcompat:1.6.0-alpha01 )
This will take care of setting the locale to appLocale and restart any activities as needed.
Moreover, as mentioned on setApplicationLocales docs
On API level 33 and above, this API will handle storage automatically
Meaning that what needs to be done for a custom in-app locale to work is just to offer the user a language picker and call setApplicationLocales once when a choice is performed.
What about API < 33?
The docs on developer.android.com do mention that devs can require to store this value automatically on older API levels
..by adding a special metaData entry in their AndroidManifest, similar to :
but at the time of writing this, the actual metadata that is needed isn't mentioned (maybe something went wrong in the doc generation).
It was instead visible in the source code for AppCompatDelegate published here and it looks as follows
<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false"
android:exported="false">
<meta-data
android:name="autoStoreLocales"
android:value="true" />
</service>
Adding the above to your Manifest should make this solution work for all API levels
/**
* Requests the system to update the list of system locales.
* Note that the system looks halted for a while during the Locale migration,
* so the caller need to take care of it.
*/
public static void updateLocales(LocaleList locales) {
try {
final IActivityManager am = ActivityManager.getService();
final Configuration config = am.getConfiguration();
config.setLocales(locales);
config.userSetLocale = true;
am.updatePersistentConfiguration(config);
} catch (RemoteException e) {
// Intentionally left blank
}
}
There's a new way to let users select app's default language since Appcompat 1.6.0-alpha04 and later. Lets say you have a button that should change the app's language to Italian when the user clicks it:
binding.button.setOnClickListener {
val appLocale: LocaleListCompat = LocaleListCompat.forLanguageTags("it")
AppCompatDelegate.setApplicationLocales(appLocale)
}
Also, to add support for older devices (< API level 32) add the following service inside the <application> tag of your AndroidManifest.xml file:
<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false"
android:exported="false">
<meta-data
android:name="autoStoreLocales"
android:value="true" />
</service>
Make sure to have the latest AppCompat dependency:
implementation 'androidx.appcompat:appcompat:1.6.0-alpha05'
Reference: https://developer.android.com/about/versions/13/features/app-languages
As of 2020 language management become easy! All you have to do is:
Call to Activity.applyOverrideConfiguration
And call to Locale.setDefault
You must call those from the activity constructor since you can call to applyOverrideConfiguration only once, and the system calls it pretty early.
And watch out from app-bundles, Google will split your APK by language resources automatically when using app-bundles. Check out the new API and the workaround here.
I created a helper class to help you with it. In my implementation G.app is the application context. Also, I need to access resources from the app context so I use the Res class for it, this one is optional, but I provide its code as well.
Usage
public BaseActivity(){
LanguageUtility.init(this);
}
public void changeLanguage(Local local){
// you must recreat your activity after you call this
LanguageUtillity.setDefaultLanguage(local, this);
}
Source code
public class LanguageUtility {
private static Configuration configuration;
public static void setDefaultLanguage(Locale locale, Context context) {
Locale.setDefault(locale);
context.getSharedPreferences("LocaleSettings", Context.MODE_PRIVATE)
.edit()
.putString("language", locale.getLanguage())
.putString("country", locale.getCountry())
.putString("variant", locale.getVariant())
.apply();
configuration = createConfiguration(context);
Res.updateContext();
}
/**
* Used to update your app context in case you cache it.
*/
public static Context createConfigurationContext(Context context) {
return context.createConfigurationContext(getConfiguration(context));
}
public static void init(Activity activity) {
activity.applyOverrideConfiguration(LanguageUtility.getConfiguration(G.app));
// you can't access sharedPrefferences from activity constructor
// with activity context, so I used the app context.
Locale.setDefault(getLocale(G.app));
}
#NotNull
private static Configuration getConfiguration(Context context) {
if (configuration == null) {
configuration = createConfiguration(context);
}
return configuration;
}
#NotNull
private static Configuration createConfiguration(Context context) {
Locale locale = getLocale(context);
Configuration configuration = new Configuration();
configuration.setLocale(locale);
LanguageUtility.configuration = configuration;
return configuration;
}
#NotNull
private static Locale getLocale(Context context) {
Locale aDefault = Locale.getDefault();
SharedPreferences preferences =
context.getSharedPreferences("LocaleSettings", Context.MODE_PRIVATE);
String language = preferences.getString("language", aDefault.getLanguage());
String country = preferences.getString("country", aDefault.getCountry());
String variant = preferences.getString("variant", aDefault.getVariant());
return new Locale(language, country, variant);
}
}
An optional Res class.
public class Res {
#SuppressLint("StaticFieldLeak")
public static Context appLocalContext = LanguageUtility.createConfigurationContext(G.app);
public static void updateContext() {
appLocalContext = LanguageUtility.createConfigurationContext(G.app);
}
public static String getString(#StringRes int id, Object... formatArgs) {
return appLocalContext.getResources().getString(id, formatArgs);
}
public static int getColor(#ColorRes int id) {
return G.app.getColor(id);
}
}
Call this method on BaseActivity -> onCreate() and BaseFragment -> OnCreateView()
Tested on API 22, 23, 24, 25, 26, 27, 28, 29...uptodate version
fun Context.updateLang() {
val resources = resources
val config = Configuration(resources.configuration)
config.setLocale(PreferenceManager(this).getAppLanguage()) // language from preference
val dm = resources.displayMetrics
createConfigurationContext(config)
resources.updateConfiguration(config, dm)
}
For those who tried everything but not not working. Please check that if you set darkmode with AppCompatDelegate.setDefaultNightMode and the system is not dark, then Configuration.setLocale will not work above Andorid 7.0.
Add this code in your every activity to solve this issue:
override fun applyOverrideConfiguration(overrideConfiguration: Configuration?) {
if (overrideConfiguration != null) {
val uiMode = overrideConfiguration.uiMode
overrideConfiguration.setTo(baseContext.resources.configuration)
overrideConfiguration.uiMode = uiMode
}
super.applyOverrideConfiguration(overrideConfiguration)
}
Put this code in your activity
if (id==R.id.uz)
{
LocaleHelper.setLocale(MainActivity.this, mLanguageCode);
//It is required to recreate the activity to reflect the change in UI.
recreate();
return true;
}
if (id == R.id.ru) {
LocaleHelper.setLocale(MainActivity.this, mLanguageCode);
//It is required to recreate the activity to reflect the change in UI.
recreate();
}

Categories

Resources