Android Change Locale Dynamically

0

In this article, we will show you how to switch between different languages within an app using androids multi-language support. The list of languages supported will be shown in the settings, once a language is selected, the application will be switched to the new locale setting. This is useful when you want to keep your phone locale different from the application locale.

How to dynamically change the locale in Android Application?

You can dynamically switch your locale by changing the Configuration object’s “locale” field. You also need to make sure that you recreate the activities/fragments that support the change of locale and are already in the stack.

Here are the basic steps:

  1. Add list to languages to string.xml
  2. Add language setting that display list of languages supported
  3. Implement onPreferenceClick(). We need to figure out the locale based on the language selected and update the Configuration object’s locale field
  4. Restart the settings activity so that it is switched over to the new locale
  5. Let the calling Activity know that the language is changed by setting a response code so that the calling Activity can restart itself.

Add the languages to strings.xml

We will add languages as string-array to strings.xml. For the sake of example, we will have a label sign_in in both English and German.

values/strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">JavArticles</string>
    <string name="welcome">Language Change Example</string>
    <string name="pref_key_root" translatable="false">pref_root</string>
    <string name="pref_key_language" translatable="false">pref_language</string>
    <string name="language">Language</string>
    <string name="device">Device</string>
    <string name="cancel">Cancel</string>
    <string name="settings">Settings</string>
    <string name="sign_in">Sign in</string>

    <string-array name="languages" translatable="false">
        <item>en-US</item>
        <item>az</item>
        <item>de</item>
        <item>el</item>
        <item>es</item>
        <item>fr</item>
        <item>gd</item>
        <item>hi</item>
        <item>hu</item>
        <item>id</item>
        <item>it</item>
        <item>ja</item>
        <item>ko</item>
        <item>nb</item>
        <item>nl</item>
        <item>pl</item>
        <item>ru</item>
        <item>sv</item>
        <item>th</item>
        <item>uz</item>
        <item>zh-CN</item>
        <item>zh-TW</item>
        <item>zh-HK</item>
        <item>en-GB</item>
        <item>tr</item>
        <item>eu</item>
        <item>he</item>
        <item>pt-BR</item>
        <item>ar</item>
        <item>ro</item>
        <item>mk</item>
        <item>en-AU</item>
        <item>sr</item>
        <item>sk</item>
        <item>cy</item>
        <item>da</item>
    </string-array>

</resources>

In German,

values-de/strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>

	<string name="sign_in">Anmelden</string>
	<string name="settings">Einstellungen</string>

</resources>

Language Setting

We will add a new language settings.

Here is the settings XML which contains just one preference, on selection it should open the list of languages in a dialog.

settings.xml:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >

    <Preference
        android:key="@string/pref_key_language"
        android:title="@string/language" />

</PreferenceScreen>

We will add the settings to the overflow menu.

menu.xml:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >

    <item
        android:actionViewClass="com.javarticles.android.SettingsActivity"
        android:showAsAction="never"
        android:title="@string/settings"
        android:id="@+id/settings">
    </item>

</menu>

settings_activity.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical"
              android:id="@+id/fragment_container">

</LinearLayout>

SettingsActivity:

package com.javarticles.android;

import android.app.Activity;
import android.app.FragmentManager;
import android.os.Bundle;

public class SettingsActivity extends Activity {
	   
	    @Override
	    public void onCreate(Bundle savedInstanceState) {
	        super.onCreate(savedInstanceState);
	        setContentView(R.layout.settings_activity);

	        FragmentManager fragmentManager = getFragmentManager();
	        if (savedInstanceState == null) {	            
	        	SettingsFragment settingsFragment = new SettingsFragment();
	            fragmentManager.beginTransaction()
	                    .add(R.id.fragment_container, settingsFragment)
	                    .commit();
	        }
	    }
}

Once selected, we will show the list of languages in a dialog.

In the onCreate(), we add the preferences XML and the set an OnPreferenceClickListener object to the language preference.

addPreferencesFromResource(R.xml.settings);
findPreference(getString(R.string.pref_key_language)).setOnPreferenceClickListener(this);

Once user selects a language, onPreferenceClick() will be called which in turn will call handleLanguagePreferenceClick() where we create the languages dialog and show it.

LanguagesDialog languagesDialog = new LanguagesDialog();
languagesDialog.show(getFragmentManager(), "LanguagesDialogFragment");

SettingsFragment:

package com.javarticles.android;

import android.os.Bundle;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceFragment;

public class SettingsFragment extends PreferenceFragment implements
		OnPreferenceClickListener {
	public static final String LANGUAGE_SETTING = "lang_setting";
	public static final int LANGUAGE_CHANGED = 1000;

	@Override
	public void onResume() {
		super.onResume();

		getActivity().setTitle(R.string.settings);
	}

	@Override
	public boolean onPreferenceClick(Preference preference) {
		String preferenceKey = preference != null ? preference.getKey() : "";

		if (preferenceKey.equals(getString(R.string.pref_key_language))) {
			return handleLanguagePreferenceClick();
		}

		return false;
	}

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		addPreferencesFromResource(R.xml.settings);
		findPreference(getString(R.string.pref_key_language))
				.setOnPreferenceClickListener(this);		
	}

	private boolean handleLanguagePreferenceClick() {
		LanguagesDialog languagesDialog = new LanguagesDialog();
		languagesDialog.show(getFragmentManager(), "LanguagesDialogFragment");
		return true;
	}

}

Next we will see how to create the languages dialog.

Languages Setting Dialog

Since we need to create a dialog here, we will extend DialogFragment so that the dialog’s lifecycle is automatically managed by the android framework.

Get the languages string array from the resources.

String[] availableLocales = getResources().getStringArray(
				R.array.languages);

Create the languages to be shown. Each language entry will be in the form ‘Display Language (Country Code)’. For example, ‘German (DE)’, ‘English (US)’.

final String[] values = new String[availableLocales.length + 1];

		for (int i = 0; i < availableLocales.length; ++i) {
			String localString = availableLocales[i];
			if (localString.contains("-")) {
				localString = localString.substring(0,
						localString.indexOf("-"));
			}
			Locale locale = new Locale(localString);
			values[i + 1]= locale.getDisplayLanguage() + " ("
					+ availableLocales[i]+ ")";
			localeMap.put(values[i + 1], availableLocales[i]);
		}
		values[0] = getActivity().getString(R.string.device) + " ("
				+ Locale.getDefault().getLanguage() + ")";
		localeMap.put(values[0], Locale.getDefault().getLanguage());		

We will sort the language list in alphabetical order.

Arrays.sort(values, 1, values.length);

Next, we create the dialog, create a list view based on the languages list ArrayAdapter.

AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(
				getActivity());
dialogBuilder.setTitle(getString(R.string.language));
ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(),
				android.R.layout.simple_list_item_1, values);
ListView listView = new ListView(getActivity());
listView.setAdapter(adapter);
dialogBuilder.setView(listView);
dialogBuilder.setNegativeButton(R.string.cancel,
				new DialogInterface.OnClickListener() {
					@Override
					public void onClick(DialogInterface dialog, int which) {
						dialog.dismiss();
					}
});

We will also set an OnItemClickListener on the list view that does the job of changing the locale and restarting the settings activity.

		listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

			@Override
			public void onItemClick(AdapterView<?> parent, View view,
					int position, long id) {
//Find locale selected

//Update the Configuration locale

//Save the locale selected

//Refresh the Settings Activity

//Set the result as 'Language Changed' and Finish the current activity.
			}
		});

  1. Find locale selected.
    				Resources res = getResources();
    				DisplayMetrics dm = res.getDisplayMetrics();
    				Configuration conf = res.getConfiguration();
    				String localString = localeMap.get(values[position]);
    
  2. Create the locale and change configuration.locale field
    				if (localString.contains("-")) {
    					conf.locale = new Locale(localString.substring(0,
    							localString.indexOf("-")), localString.substring(
    							localString.indexOf("-") + 1, localString.length()));
    				} else {
    					conf.locale = new Locale(localString);
    				}
    
  3. Update the configuration
    				res.updateConfiguration(conf, dm);
    
  4. Save the language setting
    				settings.edit()
    						.putString(SettingsFragment.LANGUAGE_SETTING, localString).apply();
    
  5. Refresh the settings activity
    Intent refresh = new Intent(getActivity(), getActivity()
    						.getClass());
    				startActivity(refresh);
    
  6. Set the result before finishing the current activity
    				getActivity().setResult(SettingsFragment.LANGUAGE_CHANGED);
    				getActivity().finish();
    

LanguagesDialog:

package com.javarticles.android;


import java.util.Arrays;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.DisplayMetrics;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class LanguagesDialog extends DialogFragment {

	@Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {	
		final SharedPreferences settings = PreferenceManager
				.getDefaultSharedPreferences(getActivity());
		final Map<String, String> localeMap = new HashMap<String, String>();
		String[] availableLocales = getResources().getStringArray(
				R.array.languages);
		final String[] values = new String[availableLocales.length + 1];

		for (int i = 0; i < availableLocales.length; ++i) {
			String localString = availableLocales[i];
			if (localString.contains("-")) {
				localString = localString.substring(0,
						localString.indexOf("-"));
			}
			Locale locale = new Locale(localString);
			values[i + 1]= locale.getDisplayLanguage() + " ("
					+ availableLocales[i]+ ")";
			localeMap.put(values[i + 1], availableLocales[i]);
		}
		values[0] = getActivity().getString(R.string.device) + " ("
				+ Locale.getDefault().getLanguage() + ")";
		localeMap.put(values[0], Locale.getDefault().getLanguage());
		Arrays.sort(values, 1, values.length);
		
		AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(
				getActivity());
		dialogBuilder.setTitle(getString(R.string.language));
		ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(),
				android.R.layout.simple_list_item_1, values);
		ListView listView = new ListView(getActivity());
		listView.setAdapter(adapter);
		listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {

			@Override
			public void onItemClick(AdapterView<?> parent, View view,
					int position, long id) {
				Resources res = getResources();
				DisplayMetrics dm = res.getDisplayMetrics();
				Configuration conf = res.getConfiguration();
				String localString = localeMap.get(values[position]);
				if (localString.contains("-")) {
					conf.locale = new Locale(localString.substring(0,
							localString.indexOf("-")), localString.substring(
							localString.indexOf("-") + 1, localString.length()));
				} else {
					conf.locale = new Locale(localString);
				}
				res.updateConfiguration(conf, dm);
				settings.edit()
						.putString(SettingsFragment.LANGUAGE_SETTING, localString).apply();

				// Refresh the app
				Intent refresh = new Intent(getActivity(), getActivity()
						.getClass());
				startActivity(refresh);
				getActivity().setResult(SettingsFragment.LANGUAGE_CHANGED);
				getActivity().finish();

			}
		});

		dialogBuilder.setView(listView);
		dialogBuilder.setNegativeButton(R.string.cancel,
				new DialogInterface.OnClickListener() {
					@Override
					public void onClick(DialogInterface dialog, int which) {
						dialog.dismiss();
					}
				});
		
		return dialogBuilder.create();
	}
		
}

Main Activity Launcher

MainActivityLauncher will be our main activity. It is a ‘no display’ activity, with noHistory as true which means it won’t be cached in the back stack. All it does is, it applies the new locale, if the user’s chosen locale is different from the default locale. Once the locale is changed, it will restart itself, finish the current activity and then start the MainActivity which will be the actual main activity that will have a view.

MainActivityLauncher:

package com.javarticles.android;

import java.util.Locale;

import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
import android.preference.PreferenceManager;

public class MainActivityLauncher extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		updateLocaleIfNeeded();
		Intent intent = new Intent(this, MainActivity.class);
		intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK
				| Intent.FLAG_ACTIVITY_NEW_TASK);
		startActivity(intent);
	}

	private void updateLocaleIfNeeded() {
		SharedPreferences sharedPreferences = PreferenceManager
				.getDefaultSharedPreferences(this);

		if (sharedPreferences.contains(SettingsFragment.LANGUAGE_SETTING)) {
			String locale = sharedPreferences.getString(
					SettingsFragment.LANGUAGE_SETTING, "");			
			Locale localeSetting = new Locale(locale);

			if (!localeSetting.equals(Locale.getDefault())) {
				Resources resources = getResources();
				Configuration conf = resources.getConfiguration();
				conf.locale = localeSetting;
				resources.updateConfiguration(conf,
						resources.getDisplayMetrics());

				Intent refresh = new Intent(this, MainActivityLauncher.class);
				startActivity(refresh);	
				finish();
			}
		}
	}
}

MainActivity will display a simple label ‘Sign in’.

welcome.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.javarticles.android.MainActivity" >

    <TextView
        android:id="@+id/welcome"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="40dp"
        android:layout_marginTop="18dp"
        android:text="@string/sign_in"
        android:textColor="@color/welcome_text_color"
        android:textSize="20sp" />


</LinearLayout>

MainActivity will restart itself once it knows that the language is changed. We do this in onActivityResult. This is important else when the user presses back button to navigate back to the main screen, the label will still be shown in the old locale.

MainActivity:

package com.javarticles.android;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;

public class MainActivity extends Activity {
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);	
		setContentView(R.layout.welcome);		
	}
	
	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.menu, menu);		
		return super.onCreateOptionsMenu(menu);
	}

	@Override
    public boolean onOptionsItemSelected(MenuItem item) {
		int id = item.getItemId();
		switch (id) {
		case R.id.settings:
			startActivityForResult(new Intent(this, SettingsActivity.class), 1000);
		}
		return super.onOptionsItemSelected(item);
	}
	
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        Log.d("MainActivity.onActivityResult", "Request code is " + requestCode + ", result code is " + resultCode);
        switch (requestCode) {
            case 1000:
            	if (resultCode == SettingsFragment.LANGUAGE_CHANGED) {
                    startActivity(new Intent(this, MainActivity.class));
                    finish();
                }
                break;
        }
    }
}

Here is the manifest XML.

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.javarticles.android"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:maxSdkVersion="22"
        android:minSdkVersion="14"
        android:targetSdkVersion="22" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher_sandbox"
        android:label="@string/app_name" >
        <activity android:name="com.javarticles.android.MainActivityLauncher"
            android:noHistory="true"
            android:theme="@android:style/Theme.NoDisplay">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="com.javarticles.android.MainActivity"/>
        <activity
            android:name="com.javarticles.android.SettingsActivity"
            android:configChanges="locale" />
    </application>

</manifest>

Run the application

First screen shows the label in English.

AndroidLanguageChange_WelcomePage

First Page

Select Settings to open the list of preferences. We only have one – ‘Languages’.

AndroidLanguagChange_Settings

Click on Settings

Select Languages.

AndroidLanguageSettings_Language

Language Settings

Select German from the list.

AndroidLanguageChange_ShowLanguages

List of Languages

The ‘Settings’ label is changed to German.

AndroidLanguagChange_AfterSettingsChange

After Language is Changed

When we press ‘Back’ button, the Main screen’s ‘Sign in’ label is now changed and is shown in German.

AndroidLanguagChange_AfterSettingsChangeBack

Press Back from Settings

Download the source code

This was an example about applying locale dynamically in Android App.

You can download the source code here: appLocalization.zip
Share.

Comments are closed.