diff --git a/briar-android/build.gradle b/briar-android/build.gradle
index 839eab3ef5c777f910da852d0c80314e17944cef..2a5d6b97bf62fb330b82c93f61be7a712ac23ebf 100644
--- a/briar-android/build.gradle
+++ b/briar-android/build.gradle
@@ -284,3 +284,34 @@ android {
 		warning 'ExtraTranslation'
 	}
 }
+
+task verifyTranslations {
+	doLast {
+		def file = "briar-android/src/main/res/values/arrays.xml"
+		def arrays = new XmlParser().parse(file)
+		def lc = arrays.children().find { it.@name == "pref_language_values" }
+		def translations = []
+		lc.children().each { value -> translations.add(value.text()) }
+
+		def folders = ["default", "en-US"]
+		new File("briar-android/src/main/res").eachDir { dir ->
+			if (dir.name.startsWith("values-")) {
+				folders.add(dir.name.substring(7).replace("-r", "-"))
+			}
+		}
+		folders.each { n ->
+			if (!translations.remove(n)) {
+				throw new GradleException("Translation " + n + " is missing in $file")
+			}
+		}
+		if (translations.size() != 0)
+			throw new GradleException("Translations\n" + translations.join("\n")
+					+ "\nhave no matching value folder")
+	}
+}
+
+project.afterEvaluate {
+	preBuild.dependsOn.add(verifyTranslations)
+}
+
+
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java
index 97e19062a941fde22b5260acec8b25345795c102..7b7e358460c2b883a4bf3d57a593c3730e7b425e 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java
@@ -2,6 +2,7 @@ package org.briarproject.briar.android;
 
 import android.app.Application;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.os.StrictMode;
 import android.os.StrictMode.ThreadPolicy;
 import android.os.StrictMode.VmPolicy;
@@ -75,7 +76,10 @@ public class BriarApplicationImpl extends Application
 
 	@Override
 	protected void attachBaseContext(Context base) {
-		super.attachBaseContext(base);
+		// Loading the language needs to be done here.
+		Localizer.initialize(base);
+		super.attachBaseContext(
+				Localizer.getInstance().setLocale(base));
 		ACRA.init(this);
 	}
 
@@ -108,6 +112,12 @@ public class BriarApplicationImpl extends Application
 		AndroidEagerSingletons.initEagerSingletons(applicationComponent);
 	}
 
+	@Override
+	public void onConfigurationChanged(Configuration newConfig) {
+		super.onConfigurationChanged(newConfig);
+		Localizer.getInstance().setLocale(this);
+	}
+
 	private void enableStrictMode() {
 		ThreadPolicy.Builder threadPolicy = new ThreadPolicy.Builder();
 		threadPolicy.detectAll();
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java b/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java
index f3cc72c5b7a7ddbb158739cf8c2eae465b246f2b..b6aaad4b930606ec4faf7735812ef3849e697b42 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java
@@ -168,6 +168,11 @@ public class BriarService extends Service {
 		registerReceiver(receiver, filter);
 	}
 
+	@Override
+	protected void attachBaseContext(Context base) {
+		super.attachBaseContext(Localizer.getInstance().setLocale(base));
+	}
+
 	private void showStartupFailureNotification(StartResult result) {
 		androidExecutor.runOnUiThread(() -> {
 			NotificationCompat.Builder b = new NotificationCompat.Builder(
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/Localizer.java b/briar-android/src/main/java/org/briarproject/briar/android/Localizer.java
new file mode 100644
index 0000000000000000000000000000000000000000..c734f13f0e9b78a2b1884cbe27e2da54f494272a
--- /dev/null
+++ b/briar-android/src/main/java/org/briarproject/briar/android/Localizer.java
@@ -0,0 +1,85 @@
+package org.briarproject.briar.android;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.preference.PreferenceManager;
+
+import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
+
+import java.util.Locale;
+
+import javax.annotation.Nullable;
+import javax.annotation.ParametersAreNonnullByDefault;
+
+import static android.os.Build.VERSION.SDK_INT;
+import static org.briarproject.briar.android.settings.SettingsFragment.LANGUAGE;
+
+@NotNullByDefault
+public class Localizer {
+
+	private static Localizer INSTANCE;
+	@Nullable
+	private final Locale locale;
+	private final SharedPreferences sharedPreferences;
+
+	private Localizer(Context context) {
+		sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+		locale = getLocaleFromTag(
+				sharedPreferences.getString(LANGUAGE, "default"));
+	}
+
+	public static synchronized void initialize(Context context) {
+		if (INSTANCE == null)
+			INSTANCE = new Localizer(context);
+	}
+
+	public static synchronized Localizer getInstance() {
+		if (INSTANCE == null)
+			throw new IllegalStateException("Localizer not initialized");
+		return INSTANCE;
+	}
+
+	public SharedPreferences getSharedPreferences() {
+		return sharedPreferences;
+	}
+
+	// Get Locale from BCP-47 tag
+	@Nullable
+	public static Locale getLocaleFromTag(String tag) {
+		if (tag.equals("default"))
+			return null;
+		if (SDK_INT >= 21) {
+			return Locale.forLanguageTag(tag);
+		}
+		if (tag.contains("-")) {
+			String[] langArray = tag.split("-");
+			return new Locale(langArray[0], langArray[1]);
+		} else
+			return new Locale(tag);
+	}
+
+	public Context setLocale(Context context) {
+		if (locale == null)
+			return context;
+		Resources res = context.getResources();
+		Configuration conf = res.getConfiguration();
+		Locale currentLocale;
+		if (SDK_INT >= 24) {
+			currentLocale = conf.getLocales().get(0);
+		} else
+			currentLocale = conf.locale;
+		if (locale.equals(currentLocale))
+			return context;
+		Locale.setDefault(locale);
+		if (SDK_INT >= 17) {
+			conf.setLocale(locale);
+			context.createConfigurationContext(conf);
+		} else
+			conf.locale = locale;
+		//noinspection deprecation
+		res.updateConfiguration(conf, res.getDisplayMetrics());
+		return context;
+	}
+}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java
index 7c8f5061ae0c8777f3bebd1679174dba8e545a39..c693ccbfc27443679d3582f8e7492ba7e2da69e1 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java
@@ -1,5 +1,6 @@
 package org.briarproject.briar.android.activity;
 
+import android.content.Context;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.support.annotation.LayoutRes;
@@ -18,6 +19,7 @@ import org.briarproject.briar.R;
 import org.briarproject.briar.android.AndroidComponent;
 import org.briarproject.briar.android.BriarApplication;
 import org.briarproject.briar.android.DestroyableContext;
+import org.briarproject.briar.android.Localizer;
 import org.briarproject.briar.android.controller.ActivityLifecycleController;
 import org.briarproject.briar.android.forum.ForumModule;
 import org.briarproject.briar.android.fragment.BaseFragment;
@@ -84,6 +86,12 @@ public abstract class BaseActivity extends AppCompatActivity
 		}
 	}
 
+	@Override
+	protected void attachBaseContext(Context base) {
+		super.attachBaseContext(
+				Localizer.getInstance().setLocale(base));
+	}
+
 	public ActivityComponent getActivityComponent() {
 		return activityComponent;
 	}
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java
index af09db700455912c9c9946100a02604937cb43cc..e7800f1db097f7b5904bf6e5e6623da908770969 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java
@@ -68,6 +68,7 @@ public class NavDrawerActivity extends BriarActivity implements
 	public static final String INTENT_GROUPS = "intent_groups";
 	public static final String INTENT_FORUMS = "intent_forums";
 	public static final String INTENT_BLOGS = "intent_blogs";
+	public static final String INTENT_SIGN_OUT = "intent_sign_out";
 
 	private static final Logger LOG =
 			Logger.getLogger(NavDrawerActivity.class.getName());
@@ -99,6 +100,8 @@ public class NavDrawerActivity extends BriarActivity implements
 					R.id.nav_btn_contacts);
 		} else if (intent.getBooleanExtra(INTENT_BLOGS, false)) {
 			startFragment(FeedFragment.newInstance(), R.id.nav_btn_blogs);
+		} else if (intent.getBooleanExtra(INTENT_SIGN_OUT, false)) {
+			signOut(false);
 		}
 		setIntent(null);
 	}
@@ -225,12 +228,12 @@ public class NavDrawerActivity extends BriarActivity implements
 				finish();
 			} else if (fm.getBackStackEntryCount() == 0
 					&& fm.findFragmentByTag(ContactListFragment.TAG) == null) {
-			/*
-			 * This makes sure that the first fragment (ContactListFragment) the
-			 * user sees is the same as the last fragment the user sees before
-			 * exiting. This models the typical Google navigation behaviour such
-			 * as in Gmail/Inbox.
-			 */
+				/*
+				 * This makes sure that the first fragment (ContactListFragment) the
+				 * user sees is the same as the last fragment the user sees before
+				 * exiting. This models the typical Google navigation behaviour such
+				 * as in Gmail/Inbox.
+				 */
 				startFragment(ContactListFragment.newInstance(),
 						R.id.nav_btn_contacts);
 			} else {
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsActivity.java
index 37870a98318a627e94f4b72edd0d0217c9a3c2fb..48b4b7fa9023082623a678fbcbb7ba81c60a6ea5 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsActivity.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsActivity.java
@@ -36,5 +36,4 @@ public class SettingsActivity extends BriarActivity {
 		}
 		return false;
 	}
-
 }
diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java
index c69dd65deb44c9556650711472f70a72a02056b0..f75ba97e30ecd491c69abcb220086102adbbe87b 100644
--- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java
+++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java
@@ -1,8 +1,10 @@
 package org.briarproject.briar.android.settings;
 
 import android.annotation.TargetApi;
+import android.app.AlertDialog;
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.media.Ringtone;
 import android.media.RingtoneManager;
 import android.net.Uri;
@@ -30,8 +32,11 @@ import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent;
 import org.briarproject.bramble.api.system.AndroidExecutor;
 import org.briarproject.bramble.util.StringUtils;
 import org.briarproject.briar.R;
+import org.briarproject.briar.android.Localizer;
+import org.briarproject.briar.android.navdrawer.NavDrawerActivity;
 import org.briarproject.briar.android.util.UserFeedback;
 
+import java.util.Locale;
 import java.util.logging.Logger;
 
 import javax.inject.Inject;
@@ -58,6 +63,7 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK;
 import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_ALWAYS;
 import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
 import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_RINGTONE;
+import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.INTENT_SIGN_OUT;
 import static org.briarproject.briar.api.android.AndroidNotificationManager.BLOG_CHANNEL_ID;
 import static org.briarproject.briar.api.android.AndroidNotificationManager.CONTACT_CHANNEL_ID;
 import static org.briarproject.briar.api.android.AndroidNotificationManager.FORUM_CHANNEL_ID;
@@ -80,11 +86,13 @@ public class SettingsFragment extends PreferenceFragmentCompat
 	public static final String SETTINGS_NAMESPACE = "android-ui";
 	public static final String BT_NAMESPACE = BluetoothConstants.ID.getString();
 	public static final String TOR_NAMESPACE = TorConstants.ID.getString();
+	public static final String LANGUAGE = "pref_key_language";
 
 	private static final Logger LOG =
 			Logger.getLogger(SettingsFragment.class.getName());
 
 	private SettingsActivity listener;
+	private ListPreference language;
 	private ListPreference enableBluetooth;
 	private ListPreference torNetwork;
 	private CheckBoxPreference notifyPrivateMessages;
@@ -119,6 +127,8 @@ public class SettingsFragment extends PreferenceFragmentCompat
 	public void onCreatePreferences(Bundle bundle, String s) {
 		addPreferencesFromResource(R.xml.settings);
 
+		language = (ListPreference) findPreference(LANGUAGE);
+		setLanguageEntries();
 		enableBluetooth = (ListPreference) findPreference("pref_key_bluetooth");
 		torNetwork = (ListPreference) findPreference("pref_key_tor_network");
 		notifyPrivateMessages = (CheckBoxPreference) findPreference(
@@ -137,6 +147,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
 
 		setSettingsEnabled(false);
 
+		language.setOnPreferenceChangeListener(this);
 		enableBluetooth.setOnPreferenceChangeListener(this);
 		torNetwork.setOnPreferenceChangeListener(this);
 		if (SDK_INT >= 21) {
@@ -180,6 +191,33 @@ public class SettingsFragment extends PreferenceFragmentCompat
 		eventBus.removeListener(this);
 	}
 
+	private void setLanguageEntries() {
+		CharSequence[] tags = language.getEntryValues();
+		CharSequence[] nativeNames = new CharSequence[tags.length];
+		for (int i = 0; i < tags.length; i++) {
+			String tag = tags[i].toString();
+			if (tag.equals("default")) {
+				nativeNames[i] = getString(R.string.pref_language_default);
+				continue;
+			}
+			Locale locale = Localizer.getLocaleFromTag(tag);
+			if (locale == null)
+				throw new IllegalStateException();
+			String nativeName = locale.getDisplayLanguage(locale);
+			// Fallback to English if the name is unknown in both native and
+			// current locale.
+			if (nativeName.equals(tag)) {
+				String tmp = locale.getDisplayLanguage(Locale.ENGLISH);
+				if (!tmp.isEmpty() && !tmp.equals(nativeName))
+					nativeName = tmp;
+			}
+			nativeNames[i] =
+					nativeName.substring(0, 1).toUpperCase() +
+							nativeName.substring(1);
+		}
+		language.setEntries(nativeNames);
+	}
+
 	private void loadSettings() {
 		listener.runOnDbThread(() -> {
 			try {
@@ -312,41 +350,69 @@ public class SettingsFragment extends PreferenceFragmentCompat
 	}
 
 	@Override
-	public boolean onPreferenceChange(Preference preference, Object o) {
-		if (preference == enableBluetooth) {
-			boolean btSetting = Boolean.valueOf((String) o);
+	public boolean onPreferenceChange(Preference preference, Object newValue) {
+		if (preference == language) {
+			if (!language.getValue().equals(newValue))
+				languageChanged((String) newValue);
+			return false;
+		} else if (preference == enableBluetooth) {
+			boolean btSetting = Boolean.valueOf((String) newValue);
 			storeBluetoothSettings(btSetting);
 		} else if (preference == torNetwork) {
-			int torSetting = Integer.valueOf((String) o);
+			int torSetting = Integer.valueOf((String) newValue);
 			storeTorSettings(torSetting);
 		} else if (preference == notifyPrivateMessages) {
 			Settings s = new Settings();
-			s.putBoolean(PREF_NOTIFY_PRIVATE, (Boolean) o);
+			s.putBoolean(PREF_NOTIFY_PRIVATE, (Boolean) newValue);
 			storeSettings(s);
 		} else if (preference == notifyGroupMessages) {
 			Settings s = new Settings();
-			s.putBoolean(PREF_NOTIFY_GROUP, (Boolean) o);
+			s.putBoolean(PREF_NOTIFY_GROUP, (Boolean) newValue);
 			storeSettings(s);
 		} else if (preference == notifyForumPosts) {
 			Settings s = new Settings();
-			s.putBoolean(PREF_NOTIFY_FORUM, (Boolean) o);
+			s.putBoolean(PREF_NOTIFY_FORUM, (Boolean) newValue);
 			storeSettings(s);
 		} else if (preference == notifyBlogPosts) {
 			Settings s = new Settings();
-			s.putBoolean(PREF_NOTIFY_BLOG, (Boolean) o);
+			s.putBoolean(PREF_NOTIFY_BLOG, (Boolean) newValue);
 			storeSettings(s);
 		} else if (preference == notifyVibration) {
 			Settings s = new Settings();
-			s.putBoolean(PREF_NOTIFY_VIBRATION, (Boolean) o);
+			s.putBoolean(PREF_NOTIFY_VIBRATION, (Boolean) newValue);
 			storeSettings(s);
 		} else if (preference == notifyLockscreen) {
 			Settings s = new Settings();
-			s.putBoolean(PREF_NOTIFY_LOCK_SCREEN, (Boolean) o);
+			s.putBoolean(PREF_NOTIFY_LOCK_SCREEN, (Boolean) newValue);
 			storeSettings(s);
 		}
 		return true;
 	}
 
+	private void languageChanged(String newValue) {
+		AlertDialog.Builder builder =
+				new AlertDialog.Builder(getActivity());
+		builder.setTitle(R.string.pref_language_title);
+		builder.setMessage(R.string.pref_language_changed);
+		builder.setPositiveButton(R.string.sign_out_button,
+				(dialogInterface, i) -> {
+					language.setValue(newValue);
+					SharedPreferences prefs =
+							Localizer.getInstance().getSharedPreferences();
+					prefs.edit().putString(LANGUAGE, newValue)
+							.commit();
+					Intent intent = new Intent(getContext(),
+							NavDrawerActivity.class);
+					intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+					intent.putExtra(INTENT_SIGN_OUT, true);
+					getActivity().startActivity(intent);
+					getActivity().finish();
+				});
+		builder.setNegativeButton(R.string.cancel, null);
+		builder.setCancelable(false);
+		builder.show();
+	}
+
 	private void storeTorSettings(int torSetting) {
 		listener.runOnDbThread(() -> {
 			try {
diff --git a/briar-android/src/main/res/values/arrays.xml b/briar-android/src/main/res/values/arrays.xml
index 3eea71ecd758e23340424437cb570c15378a65a4..6ce3f24255fb11668a1b4934d3b74236b54e6904 100644
--- a/briar-android/src/main/res/values/arrays.xml
+++ b/briar-android/src/main/res/values/arrays.xml
@@ -18,4 +18,36 @@
 		<item>1</item>
 		<item>2</item>
 	</string-array>
-</resources>
\ No newline at end of file
+	<string-array name="pref_language_values">
+		<item>default</item>
+		<item>en-US</item>
+		<item>ast</item>
+		<item>bg</item>
+		<item>br</item>
+		<item>ca</item>
+		<item>cs</item>
+		<item>de</item>
+		<item>es</item>
+		<item>eu</item>
+		<item>fa</item>
+		<item>fi</item>
+		<item>fr</item>
+		<item>gl</item>
+		<item>he</item>
+		<item>hi</item>
+		<item>it</item>
+		<item>ja</item>
+		<item>ms</item>
+		<item>nb</item>
+		<item>nl</item>
+		<item>oc</item>
+		<item>pt-BR</item>
+		<item>ro</item>
+		<item>ru</item>
+		<item>sq</item>
+		<item>sr</item>
+		<item>sv</item>
+		<item>tr</item>
+		<item>zh-CN</item>
+	</string-array>
+</resources>
diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml
index 1306feae1d116a66efc8a03d6209570eace1d42d..a0087bedd7491c8dda6435863618cfdb94fece81 100644
--- a/briar-android/src/main/res/values/strings.xml
+++ b/briar-android/src/main/res/values/strings.xml
@@ -421,4 +421,10 @@
 	<string name="permission_camera_denied_toast">Camera permission was not granted</string>
 	<string name="qr_code">QR code</string>
 	<string name="show_qr_code_fullscreen">Show QR code fullscreen</string>
+
+	<!-- Languages -->
+	<string name="pref_language_title">Language</string>
+	<string name="pref_language_changed">This setting will take effect when you restart Briar. Please sign out and restart Briar.</string>
+	<string name="pref_language_default">System default</string>
+	<string name="display_settings_title">Display</string>
 </resources>
diff --git a/briar-android/src/main/res/xml/settings.xml b/briar-android/src/main/res/xml/settings.xml
index 452815014309f41f110e49c745f551e1aeb07d0d..7b3181fa05dcf2bf827b1506b309786fc7cd8956 100644
--- a/briar-android/src/main/res/xml/settings.xml
+++ b/briar-android/src/main/res/xml/settings.xml
@@ -1,6 +1,17 @@
 <?xml version="1.0" encoding="utf-8"?>
 <PreferenceScreen
 	xmlns:android="http://schemas.android.com/apk/res/android">
+	<PreferenceCategory
+		android:layout="@layout/preferences_category"
+		android:title="@string/display_settings_title">
+
+	<ListPreference
+		android:defaultValue="default"
+		android:entryValues="@array/pref_language_values"
+		android:key="pref_key_language"
+		android:summary="%s"
+		android:title="@string/pref_language_title"/>
+	</PreferenceCategory>
 
 	<PreferenceCategory
 		android:layout="@layout/preferences_category"
diff --git a/briar-android/src/test/java/org/briarproject/briar/android/TestBriarApplication.java b/briar-android/src/test/java/org/briarproject/briar/android/TestBriarApplication.java
index 6f232bef0a91ee669087f54aaec60428badc1be4..93433123b2992654b1bf96c62afc9fdfd0291d74 100644
--- a/briar-android/src/test/java/org/briarproject/briar/android/TestBriarApplication.java
+++ b/briar-android/src/test/java/org/briarproject/briar/android/TestBriarApplication.java
@@ -27,6 +27,7 @@ public class TestBriarApplication extends Application
 		super.onCreate();
 		LOG.info("Created");
 
+		Localizer.initialize(this);
 		applicationComponent = DaggerAndroidComponent.builder()
 				.appModule(new AppModule(this))
 				.build();