Index: src/wp-admin/includes/ms.php
===================================================================
--- src/wp-admin/includes/ms.php	(revision 38951)
+++ src/wp-admin/includes/ms.php	(working copy)
@@ -272,6 +272,8 @@
 	);
 	update_option( 'adminhash', $new_admin_email );
 
+	$switched_locale = switch_to_locale( get_user_locale() );
+
 	/* translators: Do not translate USERNAME, ADMIN_URL, EMAIL, SITENAME, SITEURL: those are placeholders. */
 	$email_text = __( 'Howdy ###USERNAME###,
 
@@ -315,6 +317,10 @@
 	$content = str_replace( '###SITEURL###', network_home_url(), $content );
 
 	wp_mail( $value, sprintf( __( '[%s] New Admin Email Address' ), wp_specialchars_decode( get_option( 'blogname' ) ) ), $content );
+
+	if ( $switched_locale ) {
+		restore_previous_locale();
+	}
 }
 
 /**
@@ -353,6 +359,8 @@
 		);
 		update_user_meta( $current_user->ID, '_new_email', $new_user_email );
 
+		$switched_locale = switch_to_locale( get_user_locale() );
+
 		/* translators: Do not translate USERNAME, ADMIN_URL, EMAIL, SITENAME, SITEURL: those are placeholders. */
 		$email_text = __( 'Howdy ###USERNAME###,
 
@@ -395,6 +403,10 @@
 
 		wp_mail( $_POST['email'], sprintf( __( '[%s] New Email Address' ), wp_specialchars_decode( get_option( 'blogname' ) ) ), $content );
 		$_POST['email'] = $current_user->user_email;
+
+		if ( $switched_locale ) {
+			restore_previous_locale();
+		}
 	}
 }
 
Index: src/wp-admin/includes/upgrade.php
===================================================================
--- src/wp-admin/includes/upgrade.php	(revision 38951)
+++ src/wp-admin/includes/upgrade.php	(working copy)
@@ -372,6 +372,9 @@
 	$email = $user->user_email;
 	$name = $user->user_login;
 	$login_url = wp_login_url();
+
+	$switched_locale = switch_to_locale( $user->locale );
+
 	$message = sprintf( __( "Your new WordPress site has been successfully set up at:
 
 %1\$s
@@ -389,6 +392,10 @@
 "), $blog_url, $name, $password, $login_url );
 
 	@wp_mail($email, __('New WordPress Site'), $message);
+
+	if ( $switched_locale ) {
+		restore_previous_locale();
+	}
 }
 endif;
 
Index: src/wp-admin/ms-delete-site.php
===================================================================
--- src/wp-admin/ms-delete-site.php	(revision 38951)
+++ src/wp-admin/ms-delete-site.php	(working copy)
@@ -42,6 +42,8 @@
 
 	$url_delete = esc_url( admin_url( 'ms-delete-site.php?h=' . $hash ) );
 
+	$switched_locale = switch_to_locale( get_locale() );
+
 	/* translators: Do not translate USERNAME, URL_DELETE, SITE_NAME: those are placeholders. */
 	$content = __( "Howdy ###USERNAME###,
 
@@ -73,6 +75,10 @@
 	$content = str_replace( '###SITE_NAME###', get_network()->site_name, $content );
 
 	wp_mail( get_option( 'admin_email' ), "[ " . wp_specialchars_decode( get_option( 'blogname' ) ) . " ] ".__( 'Delete My Site' ), $content );
+
+	if ( $switched_locale ) {
+		restore_previous_locale();
+	}
 	?>
 
 	<p><?php _e( 'Thank you. Please check your email for a link to confirm your action. Your site will not be deleted until this link is clicked.' ) ?></p>
Index: src/wp-admin/user-new.php
===================================================================
--- src/wp-admin/user-new.php	(revision 38951)
+++ src/wp-admin/user-new.php	(working copy)
@@ -87,6 +87,8 @@
 			 */
 			do_action( 'invite_user', $user_id, $role, $newuser_key );
 
+			$switched_locale = switch_to_locale( $user_details->locale );
+
 			/* translators: 1: Site name, 2: site URL, 3: role, 4: activation URL */
 			$message = __( 'Hi,
 
@@ -96,6 +98,11 @@
 Please click the following link to confirm the invite:
 %4$s' );
 			wp_mail( $new_user_email, sprintf( __( '[%s] Joining confirmation' ), wp_specialchars_decode( get_option( 'blogname' ) ) ), sprintf( $message, get_option( 'blogname' ), home_url(), wp_specialchars_decode( translate_user_role( $role['name'] ) ), home_url( "/newbloguser/$newuser_key/" ) ) );
+
+			if ( $switched_locale ) {
+				restore_previous_locale();
+			}
+
 			$redirect = add_query_arg( array('update' => 'add'), 'user-new.php' );
 		}
 	}
Index: src/wp-includes/class-wp-locale-switcher.php
===================================================================
--- src/wp-includes/class-wp-locale-switcher.php	(revision 0)
+++ src/wp-includes/class-wp-locale-switcher.php	(working copy)
@@ -0,0 +1,244 @@
+<?php
+/**
+ * Locale API: WP_Locale_Switcher class
+ *
+ * @package WordPress
+ * @subpackage i18n
+ * @since 4.7.0
+ */
+
+/**
+ * Core class used for switching locales.
+ *
+ * @since 4.7.0
+ */
+class WP_Locale_Switcher {
+	/**
+	 * Locale stack.
+	 *
+	 * @since 4.7.0
+	 * @access private
+	 * @var string[]
+	 */
+	private $locales = array();
+
+	/**
+	 * Original locale.
+	 *
+	 * @since 4.7.0
+	 * @access private
+	 * @var string
+	 */
+	private $original_locale;
+
+	/**
+	 * Holds all available languages.
+	 *
+	 * @since 4.7.0
+	 * @access private
+	 * @var array An array of language codes (file names without the .mo extension).
+	 */
+	private $available_languages = array();
+
+	/**
+	 * Constructor.
+	 *
+	 * Stores the original locale as well as a list of all available languages.
+	 *
+	 * @since 4.7.0
+	 */
+	public function __construct() {
+		$this->original_locale     = is_admin() ? get_user_locale() : get_locale();
+		$this->available_languages = array_merge( array( 'en_US' ), get_available_languages() );
+	}
+
+	/**
+	 * Initializes the locale switcher.
+	 *
+	 * Hooks into the {@see 'locale'} filter to change the locale on the fly.
+	 */
+	public function init() {
+		add_filter( 'locale', array( $this, 'filter_locale' ) );
+	}
+
+	/**
+	 * Switches the translations according to the given locale.
+	 *
+	 * @since 4.7.0
+	 *
+	 * @param string $locale The locale to switch to.
+	 * @return bool True on success, false on failure.
+	 */
+	public function switch_to_locale( $locale ) {
+		$current_locale = is_admin() ? get_user_locale() : get_locale();
+		if ( $current_locale === $locale ) {
+			return false;
+		}
+
+		if ( ! in_array( $locale, $this->available_languages, true ) ) {
+			return false;
+		}
+
+		$this->locales[] = $locale;
+
+		$this->change_locale( $locale );
+
+		/**
+		 * Fires when the locale is switched.
+		 *
+		 * @since 4.7.0
+		 *
+		 * @param string $locale The new locale.
+		 */
+		do_action( 'switch_locale', $locale );
+
+		return true;
+	}
+
+	/**
+	 * Restores the translations according to the previous locale.
+	 *
+	 * @since 4.7.0
+	 *
+	 * @return string|false Locale on success, false on failure.
+	 */
+	public function restore_previous_locale() {
+		$previous_locale = array_pop( $this->locales );
+
+		if ( null === $previous_locale ) {
+			// The stack is empty, bail.
+			return false;
+		}
+
+		$locale = end( $this->locales );
+
+		if ( ! $locale ) {
+			// There's nothing left in the stack: go back to the original locale.
+			$locale = $this->original_locale;
+		}
+
+		$this->change_locale( $locale );
+
+		/**
+		 * Fires when the locale is restored to the previous one.
+		 *
+		 * @since 4.7.0
+		 *
+		 * @param string $locale          The new locale.
+		 * @param string $previous_locale The previous locale.
+		 */
+		do_action( 'restore_previous_locale', $locale, $previous_locale );
+
+		return $locale;
+	}
+
+	/**
+	 * Restores the translations according to the original locale.
+	 *
+	 * @since 4.7.0
+	 *
+	 * @return string|false Locale on success, false on failure.
+	 */
+	public function restore_current_locale() {
+		if ( empty( $this->locales ) ) {
+			return false;
+		}
+
+		$this->locales = array( $this->original_locale );
+
+		return $this->restore_previous_locale();
+	}
+
+	/**
+	 * Whether switch_to_locale() is in effect.
+	 *
+	 * @since 4.7.0
+	 *
+	 * @return bool True if the locale has been switched, false otherwise.
+	 */
+	public function is_switched() {
+		return ! empty( $this->locales );
+	}
+
+	/**
+	 * Filters the WordPress install's locale.
+	 *
+	 * @since 4.7.0
+	 *
+	 * @param string $locale The WordPress install's locale.
+	 * @return string The locale currently being switched to.
+	 */
+	public function filter_locale( $locale ) {
+		$switched_locale = end( $this->locales );
+
+		if ( $switched_locale ) {
+			return $switched_locale;
+		}
+
+		return $locale;
+	}
+
+	/**
+	 * Load translations for a given locale.
+	 *
+	 * When switching to a locale, translations for this locale must be loaded from scratch.
+	 *
+	 * @since 4.7.0
+	 * @access private
+	 *
+	 * @global Mo[] $l10n An array of all currently loaded text domains.
+	 *
+	 * @param string $locale The locale to load translations for.
+	 */
+	private function load_translations( $locale ) {
+		global $l10n;
+
+		$domains = $l10n ? array_keys( $l10n ) : array();
+
+		load_default_textdomain( $locale );
+
+		foreach ( $domains as $domain ) {
+			if ( 'default' === $domain ) {
+				continue;
+			}
+
+			$mofile = $l10n[ $domain ]->get_filename();
+
+			unload_textdomain( $domain );
+
+			if ( $mofile ) {
+				load_textdomain( $domain, $mofile );
+			}
+
+			get_translations_for_domain( $domain );
+		}
+	}
+
+	/**
+	 * Changes the site's locale to the given one.
+	 *
+	 * Loads the translations, changes the global `$wp_locale` object and updates
+	 * all post type labels.
+	 *
+	 * @since 4.7.0
+	 * @access private
+	 *
+	 * @global WP_Locale $wp_locale The WordPress date and time locale object.
+	 *
+	 * @param string $locale The locale to change to.
+	 */
+	private function change_locale( $locale ) {
+		$this->load_translations( $locale );
+
+		$GLOBALS['wp_locale'] = new WP_Locale();
+
+		/**
+		 * Fires when the locale is switched to or restored.
+		 *
+		 * @since 4.7.0
+		 *
+		 * @param string $locale The new locale.
+		 */
+		do_action( 'change_locale', $locale );
+	}
+}
Index: src/wp-includes/default-filters.php
===================================================================
--- src/wp-includes/default-filters.php	(revision 38951)
+++ src/wp-includes/default-filters.php	(working copy)
@@ -406,6 +406,7 @@
 add_action( 'admin_menu', '_add_post_type_submenus' );
 add_action( 'before_delete_post', '_reset_front_page_settings_for_post' );
 add_action( 'wp_trash_post',      '_reset_front_page_settings_for_post' );
+add_action( 'change_locale', 'create_initial_post_types' );
 
 // Post Formats
 add_filter( 'request', '_post_format_request' );
@@ -431,6 +432,7 @@
 
 // Taxonomy
 add_action( 'init', 'create_initial_taxonomies', 0 ); // highest priority
+add_action( 'change_locale', 'create_initial_taxonomies' );
 
 // Canonical
 add_action( 'template_redirect', 'redirect_canonical' );
Index: src/wp-includes/l10n.php
===================================================================
--- src/wp-includes/l10n.php	(revision 38951)
+++ src/wp-includes/l10n.php	(working copy)
@@ -1166,3 +1166,68 @@
 	}
 	return $wp_locale->is_rtl();
 }
+
+/**
+ * Switches the translations according to the given locale.
+ *
+ * @since 4.7.0
+ *
+ * @global WP_Locale_Switcher $wp_locale_switcher
+ *
+ * @param string $locale The locale.
+ * @return bool True on success, false on failure.
+ */
+function switch_to_locale( $locale ) {
+	/* @var WP_Locale_Switcher $wp_locale_switcher */
+	global $wp_locale_switcher;
+
+	return $wp_locale_switcher->switch_to_locale( $locale );
+}
+
+/**
+ * Restores the translations according to the previous locale.
+ *
+ * @since 4.7.0
+ *
+ * @global WP_Locale_Switcher $wp_locale_switcher
+ *
+ * @return string|false Locale on success, false on error.
+ */
+function restore_previous_locale() {
+	/* @var WP_Locale_Switcher $wp_locale_switcher */
+	global $wp_locale_switcher;
+
+	return $wp_locale_switcher->restore_previous_locale();
+}
+
+/**
+ * Restores the translations according to the original locale.
+ *
+ * @since 4.7.0
+ *
+ * @global WP_Locale_Switcher $wp_locale_switcher
+ *
+ * @return string|false Locale on success, false on error.
+ */
+function restore_current_locale() {
+	/* @var WP_Locale_Switcher $wp_locale_switcher */
+	global $wp_locale_switcher;
+
+	return $wp_locale_switcher->restore_current_locale();
+}
+
+/**
+ * Whether switch_to_locale() is in effect.
+ *
+ * @since 4.7.0
+ *
+ * @global WP_Locale_Switcher $wp_locale_switcher
+ *
+ * @return bool True if the locale has been switched, false otherwise.
+ */
+function is_locale_switched() {
+	/* @var WP_Locale_Switcher $wp_locale_switcher */
+	global $wp_locale_switcher;
+
+	return $wp_locale_switcher->is_switched();
+}
Index: src/wp-includes/load.php
===================================================================
--- src/wp-includes/load.php	(revision 38951)
+++ src/wp-includes/load.php	(working copy)
@@ -841,13 +841,14 @@
  * @since 3.4.0
  * @access private
  *
- * @global string    $text_direction
- * @global WP_Locale $wp_locale      The WordPress date and time locale object.
+ * @global string             $text_direction
+ * @global WP_Locale          $wp_locale      The WordPress date and time locale object.
+ * @global WP_Locale_Switcher $wp_locale_switcher WordPress locale switcher object.
  *
  * @staticvar bool $loaded
  */
 function wp_load_translations_early() {
-	global $text_direction, $wp_locale;
+	global $text_direction, $wp_locale, $wp_locale_switcher;
 
 	static $loaded = false;
 	if ( $loaded )
@@ -864,6 +865,7 @@
 	require_once ABSPATH . WPINC . '/pomo/mo.php';
 	require_once ABSPATH . WPINC . '/l10n.php';
 	require_once ABSPATH . WPINC . '/class-wp-locale.php';
+	require_once ABSPATH . WPINC . '/class-wp-locale-switcher.php';
 
 	// General libraries
 	require_once ABSPATH . WPINC . '/plugin.php';
@@ -915,6 +917,8 @@
 	}
 
 	$wp_locale = new WP_Locale();
+	$wp_locale_switcher = new WP_Locale_Switcher();
+	$wp_locale_switcher->init();
 }
 
 /**
Index: src/wp-includes/ms-functions.php
===================================================================
--- src/wp-includes/ms-functions.php	(revision 38951)
+++ src/wp-includes/ms-functions.php	(working copy)
@@ -800,6 +800,10 @@
 		$admin_email = 'support@' . $_SERVER['SERVER_NAME'];
 	$from_name = get_site_option( 'site_name' ) == '' ? 'WordPress' : esc_html( get_site_option( 'site_name' ) );
 	$message_headers = "From: \"{$from_name}\" <{$admin_email}>\n" . "Content-Type: text/plain; charset=\"" . get_option('blog_charset') . "\"\n";
+
+	$user = get_user_by( 'login', $user );
+	$switched_locale = switch_to_locale( $user && $user->locale ? $user->locale : get_locale() );
+
 	$message = sprintf(
 		/**
 		 * Filters the message content of the new blog notification email.
@@ -849,6 +853,11 @@
 		esc_url( 'http://' . $domain . $path )
 	);
 	wp_mail( $user_email, wp_specialchars_decode( $subject ), $message, $message_headers );
+
+	if ( $switched_locale ) {
+		restore_previous_locale();
+	}
+
 	return true;
 }
 
@@ -887,6 +896,9 @@
 	if ( ! apply_filters( 'wpmu_signup_user_notification', $user, $user_email, $key, $meta ) )
 		return false;
 
+	$user = get_user_by( 'login', $user );
+	$switched_locale = switch_to_locale( $user && $user->locale ? $user->locale : get_locale() );
+
 	// Send email with activation link.
 	$admin_email = get_site_option( 'admin_email' );
 	if ( $admin_email == '' )
@@ -934,6 +946,11 @@
 		$user
 	);
 	wp_mail( $user_email, wp_specialchars_decode( $subject ), $message, $message_headers );
+
+	if ( $switched_locale ) {
+		restore_previous_locale();
+	}
+
 	return true;
 }
 
@@ -1448,6 +1465,10 @@
 	if ( ! apply_filters( 'wpmu_welcome_notification', $blog_id, $user_id, $password, $title, $meta ) )
 		return false;
 
+	$user = get_userdata( $user_id );
+
+	$switched_locale = switch_to_locale( $user->locale );
+
 	$welcome_email = get_site_option( 'welcome_email' );
 	if ( $welcome_email == false ) {
 		/* translators: Do not translate USERNAME, SITE_NAME, BLOG_URL, PASSWORD: those are placeholders. */
@@ -1468,7 +1489,6 @@
 	}
 
 	$url = get_blogaddress_by_id($blog_id);
-	$user = get_userdata( $user_id );
 
 	$welcome_email = str_replace( 'SITE_NAME', $current_network->site_name, $welcome_email );
 	$welcome_email = str_replace( 'BLOG_TITLE', $title, $welcome_email );
@@ -1512,6 +1532,11 @@
 	 */
 	$subject = apply_filters( 'update_welcome_subject', sprintf( __( 'New %1$s Site: %2$s' ), $current_network->site_name, wp_unslash( $title ) ) );
 	wp_mail( $user->user_email, wp_specialchars_decode( $subject ), $message, $message_headers );
+
+	if ( $switched_locale ) {
+		restore_previous_locale();
+	}
+
 	return true;
 }
 
@@ -1551,6 +1576,8 @@
 
 	$user = get_userdata( $user_id );
 
+	$switched_locale = switch_to_locale( $user->locale );
+
 	/**
 	 * Filters the content of the welcome email after user activation.
 	 *
@@ -1590,6 +1617,11 @@
 	 */
 	$subject = apply_filters( 'update_welcome_user_subject', sprintf( __( 'New %1$s User: %2$s' ), $current_network->site_name, $user->user_login) );
 	wp_mail( $user->user_email, wp_specialchars_decode( $subject ), $message, $message_headers );
+
+	if ( $switched_locale ) {
+		restore_previous_locale();
+	}
+
 	return true;
 }
 
Index: src/wp-includes/pluggable.php
===================================================================
--- src/wp-includes/pluggable.php	(revision 38951)
+++ src/wp-includes/pluggable.php	(working copy)
@@ -211,7 +211,7 @@
 	// (Re)create it, if it's gone missing
 	if ( ! ( $phpmailer instanceof PHPMailer ) ) {
 		require_once ABSPATH . WPINC . '/class-phpmailer.php';
-		require_once ABSPATH . WPINC . '/class-smtp.php'; 
+		require_once ABSPATH . WPINC . '/class-smtp.php';
 		$phpmailer = new PHPMailer( true );
 	}
 
@@ -1418,6 +1418,8 @@
 		$emails = array_flip( $emails );
 	}
 
+	$switched_locale = switch_to_locale( get_locale() );
+
 	$comment_author_domain = @gethostbyaddr($comment->comment_author_IP);
 
 	// The blogname option is escaped with esc_html on the way into the database in sanitize_option
@@ -1522,6 +1524,10 @@
 		@wp_mail( $email, wp_specialchars_decode( $subject ), $notify_message, $message_headers );
 	}
 
+	if ( $switched_locale ) {
+		restore_previous_locale();
+	}
+
 	return true;
 }
 endif;
@@ -1569,6 +1575,8 @@
 			$emails[] = $user->user_email;
 	}
 
+	$switched_locale = switch_to_locale( get_locale() );
+
 	$comment_author_domain = @gethostbyaddr($comment->comment_author_IP);
 	$comments_waiting = $wpdb->get_var("SELECT count(comment_ID) FROM $wpdb->comments WHERE comment_approved = '0'");
 
@@ -1664,6 +1672,10 @@
 		@wp_mail( $email, wp_specialchars_decode( $subject ), $notify_message, $message_headers );
 	}
 
+	if ( $switched_locale ) {
+		restore_previous_locale();
+	}
+
 	return true;
 }
 endif;
@@ -1723,11 +1735,16 @@
 	$blogname = wp_specialchars_decode(get_option('blogname'), ENT_QUOTES);
 
 	if ( 'user' !== $notify ) {
+		$switched_locale = switch_to_locale( get_locale() );
 		$message  = sprintf( __( 'New user registration on your site %s:' ), $blogname ) . "\r\n\r\n";
 		$message .= sprintf( __( 'Username: %s' ), $user->user_login ) . "\r\n\r\n";
 		$message .= sprintf( __( 'Email: %s' ), $user->user_email ) . "\r\n";
 
 		@wp_mail( get_option( 'admin_email' ), sprintf( __( '[%s] New User Registration' ), $blogname ), $message );
+
+		if ( $switched_locale ) {
+			restore_previous_locale();
+		}
 	}
 
 	// `$deprecated was pre-4.3 `$plaintext_pass`. An empty `$plaintext_pass` didn't sent a user notifcation.
@@ -1748,6 +1765,8 @@
 	$hashed = time() . ':' . $wp_hasher->HashPassword( $key );
 	$wpdb->update( $wpdb->users, array( 'user_activation_key' => $hashed ), array( 'user_login' => $user->user_login ) );
 
+	$switched_locale = switch_to_locale( $user->locale );
+
 	$message = sprintf(__('Username: %s'), $user->user_login) . "\r\n\r\n";
 	$message .= __('To set your password, visit the following address:') . "\r\n\r\n";
 	$message .= '<' . network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user->user_login), 'login') . ">\r\n\r\n";
@@ -1755,6 +1774,10 @@
 	$message .= wp_login_url() . "\r\n";
 
 	wp_mail($user->user_email, sprintf(__('[%s] Your username and password info'), $blogname), $message);
+
+	if ( $switched_locale ) {
+		restore_previous_locale();
+	}
 }
 endif;
 
Index: src/wp-includes/pomo/mo.php
===================================================================
--- src/wp-includes/pomo/mo.php	(revision 38951)
+++ src/wp-includes/pomo/mo.php	(working copy)
@@ -16,15 +16,36 @@
 	var $_nplurals = 2;
 
 	/**
+	 * Loaded MO file.
+	 *
+	 * @var string
+	 */
+	private $filename = '';
+
+	/**
+	 * Returns the loaded MO file.
+	 *
+	 * @return string The loaded MO file.
+	 */
+	public function get_filename() {
+		return $this->filename;
+	}
+
+	/**
 	 * Fills up with the entries from MO file $filename
 	 *
 	 * @param string $filename MO file to load
 	 */
 	function import_from_file($filename) {
-		$reader = new POMO_FileReader($filename);
-		if (!$reader->is_resource())
+		$reader = new POMO_FileReader( $filename );
+
+		if ( ! $reader->is_resource() ) {
 			return false;
-		return $this->import_from_reader($reader);
+		}
+
+		$this->filename = (string) $filename;
+
+		return $this->import_from_reader( $reader );
 	}
 
 	/**
@@ -299,4 +320,4 @@
 		return $this->_nplurals;
 	}
 }
-endif;
\ No newline at end of file
+endif;
Index: src/wp-includes/user.php
===================================================================
--- src/wp-includes/user.php	(revision 38951)
+++ src/wp-includes/user.php	(working copy)
@@ -1801,8 +1801,12 @@
 
 		$blog_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
 
+		$switched_locale = false;
+		if ( ! empty( $send_password_change_email ) || ! empty( $send_email_change_email ) ) {
+			$switched_locale = switch_to_locale( isset( $user['locale'] ) ? $user['locale'] : get_locale() );
+		}
+
 		if ( ! empty( $send_password_change_email ) ) {
-
 			/* translators: Do not translate USERNAME, ADMIN_EMAIL, EMAIL, SITENAME, SITEURL: those are placeholders. */
 			$pass_change_text = __( 'Hi ###USERNAME###,
 
@@ -1910,6 +1914,10 @@
 
 			wp_mail( $email_change_email['to'], sprintf( $email_change_email['subject'], $blog_name ), $email_change_email['message'], $email_change_email['headers'] );
 		}
+
+		if ( $switched_locale ) {
+			restore_previous_locale();
+		}
 	}
 
 	// Update the cookies if the password changed.
Index: src/wp-settings.php
===================================================================
--- src/wp-settings.php	(revision 38951)
+++ src/wp-settings.php	(working copy)
@@ -130,6 +130,7 @@
 // Load the L10n library.
 require_once( ABSPATH . WPINC . '/l10n.php' );
 require_once( ABSPATH . WPINC . '/class-wp-locale.php' );
+require_once( ABSPATH . WPINC . '/class-wp-locale-switcher.php' );
 
 // Run the installer if WordPress is not installed.
 wp_not_installed();
@@ -400,6 +401,16 @@
  */
 $GLOBALS['wp_locale'] = new WP_Locale();
 
+/**
+ *  WordPress Locale Switcher object for switching locales.
+ *
+ * @since 4.7.0
+ *
+ * @global WP_Locale_Switcher $wp_locale_switcher WordPress locale switcher object.
+ */
+$GLOBALS['wp_locale_switcher'] = new WP_Locale_Switcher();
+$GLOBALS['wp_locale_switcher']->init();
+
 // Load the functions for the active theme, for both parent and child theme if applicable.
 if ( ! wp_installing() || 'wp-activate.php' === $pagenow ) {
 	if ( TEMPLATEPATH !== STYLESHEETPATH && file_exists( STYLESHEETPATH . '/functions.php' ) )
Index: tests/phpunit/tests/l10n/loadTextdomainJustInTime.php
===================================================================
--- tests/phpunit/tests/l10n/loadTextdomainJustInTime.php	(revision 38951)
+++ tests/phpunit/tests/l10n/loadTextdomainJustInTime.php	(working copy)
@@ -9,7 +9,7 @@
 	private $orig_theme_dir;
 	private $theme_root;
 
-	function setUp() {
+	public function setUp() {
 		parent::setUp();
 
 		$this->theme_root = DIR_TESTDATA . '/themedir1';
@@ -22,11 +22,11 @@
 		add_filter( 'template_root', array( $this, 'filter_theme_root' ) );
 		wp_clean_themes_cache();
 		unset( $GLOBALS['wp_themes'] );
-
+		unset( $GLOBALS['l10n'] );
 		unset( $GLOBALS['l10n_unloaded'] );
 	}
 
-	function tearDown() {
+	public function tearDown() {
 		$GLOBALS['wp_theme_directories'] = $this->orig_theme_dir;
 		remove_filter( 'theme_root', array( $this, 'filter_theme_root' ) );
 		remove_filter( 'stylesheet_root', array( $this, 'filter_theme_root' ) );
@@ -33,6 +33,8 @@
 		remove_filter( 'template_root', array( $this, 'filter_theme_root' ) );
 		wp_clean_themes_cache();
 		unset( $GLOBALS['wp_themes'] );
+		unset( $GLOBALS['l10n'] );
+		unset( $GLOBALS['l10n_unloaded'] );
 
 		parent::tearDown();
 	}
@@ -60,7 +62,6 @@
 		$expected_output             = i18n_plugin_test();
 		$is_textdomain_loaded_after  = is_textdomain_loaded( 'internationalized-plugin' );
 
-		unload_textdomain( 'internationalized-plugin' );
 		remove_filter( 'locale', array( $this, 'filter_set_locale_to_german' ) );
 
 		$this->assertFalse( $is_textdomain_loaded_before );
@@ -82,7 +83,6 @@
 		$expected_output             = i18n_theme_test();
 		$is_textdomain_loaded_after  = is_textdomain_loaded( 'internationalized-theme' );
 
-		unload_textdomain( 'internationalized-theme' );
 		remove_filter( 'locale', array( $this, 'filter_set_locale_to_german' ) );
 
 		$this->assertFalse( $is_textdomain_loaded_before );
@@ -141,4 +141,34 @@
 		$this->assertSame( 'Das ist ein Dummy Plugin', $expected_output_final );
 		$this->assertTrue( $is_textdomain_loaded_final );
 	}
+
+	/**
+	 * @ticket 26511
+	 */
+	public function test_plugin_translation_after_switching_locale() {
+		require_once DIR_TESTDATA . '/plugins/internationalized-plugin.php';
+
+		switch_to_locale( 'de_DE' );
+		$expected = i18n_plugin_test();
+		restore_previous_locale();
+
+		$this->assertSame( 'Das ist ein Dummy Plugin', $expected );
+	}
+
+	/**
+	 * @ticket 26511
+	 */
+	public function test_theme_translation_after_switching_locale() {
+		switch_theme( 'internationalized-theme' );
+
+		require_once get_stylesheet_directory() . '/functions.php';
+
+		switch_to_locale( 'de_DE' );
+		$expected = i18n_theme_test();
+		restore_previous_locale();
+
+		switch_theme( WP_DEFAULT_THEME );
+
+		$this->assertSame( 'Das ist ein Dummy Theme', $expected );
+	}
 }
Index: tests/phpunit/tests/l10n/localeSwitcher.php
===================================================================
--- tests/phpunit/tests/l10n/localeSwitcher.php	(revision 0)
+++ tests/phpunit/tests/l10n/localeSwitcher.php	(working copy)
@@ -0,0 +1,386 @@
+<?php
+
+/**
+ * @group l10n
+ * @group i18n
+ * @group 26511
+ */
+class Tests_Locale_Switcher extends WP_UnitTestCase {
+	/**
+	 * @var string
+	 */
+	protected $locale = '';
+
+	/**
+	 * @var string
+	 */
+	protected $previous_locale = '';
+
+	public function setUp() {
+		parent::setUp();
+
+		$this->locale = '';
+		$this->previous_locale = '';
+
+		unset( $GLOBALS['l10n'] );
+		unset( $GLOBALS['l10n_unloaded'] );
+	}
+
+	public function tearDown() {
+		unset( $GLOBALS['l10n'] );
+		unset( $GLOBALS['l10n_unloaded'] );
+
+		parent::tearDown();
+	}
+
+	public function test_switch_to_non_existent_locale_returns_false() {
+		$this->assertFalse( switch_to_locale( 'foo_BAR' ) );
+	}
+
+	public function test_switch_to_non_existent_locale_does_not_change_locale() {
+		switch_to_locale( 'foo_BAR' );
+
+		$this->assertSame( 'en_US', get_locale() );
+	}
+
+	public function test_switch_to_locale_returns_true() {
+		$expected = switch_to_locale( 'en_GB' );
+
+		// Cleanup.
+		restore_previous_locale();
+
+		$this->assertTrue( $expected );
+	}
+
+	public function test_switch_to_locale_changes_the_locale() {
+		switch_to_locale( 'en_GB' );
+
+		$locale = get_locale();
+
+		// Cleanup.
+		restore_previous_locale();
+
+		$this->assertSame( 'en_GB', $locale );
+	}
+
+	public function test_switch_to_locale_loads_translation() {
+		switch_to_locale( 'es_ES' );
+
+		$actual = __( 'Invalid parameter.' );
+
+		// Cleanup.
+		restore_previous_locale();
+
+		$this->assertSame( 'Parámetro no válido. ', $actual );
+	}
+
+	public function test_switch_to_locale_changes_wp_locale_global() {
+		global $wp_locale;
+
+		$expected = array(
+			'thousands_sep' => '.',
+			'decimal_point' => ',',
+		);
+
+		switch_to_locale( 'de_DE' );
+
+		$wp_locale_de_DE = clone $wp_locale;
+
+		// Cleanup.
+		restore_previous_locale();
+
+		$this->assertEqualSetsWithIndex( $expected, $wp_locale_de_DE->number_format );
+	}
+
+	public function test_switch_to_locale_en_US() {
+		switch_to_locale( 'en_GB' );
+		$locale_en_GB = get_locale();
+		switch_to_locale( 'en_US' );
+		$locale_en_US = get_locale();
+
+		// Cleanup.
+		restore_current_locale();
+
+		$this->assertSame( 'en_GB', $locale_en_GB );
+		$this->assertSame( 'en_US', $locale_en_US );
+	}
+
+	public function test_switch_to_locale_multiple_times() {
+		switch_to_locale( 'en_GB' );
+		switch_to_locale( 'es_ES' );
+		$locale = get_locale();
+
+		// Cleanup.
+		restore_previous_locale();
+		restore_previous_locale();
+
+		$this->assertSame( 'es_ES', $locale );
+	}
+
+	public function test_switch_to_locale_multiple_times_loads_translation() {
+		switch_to_locale( 'en_GB' );
+		switch_to_locale( 'de_DE' );
+		switch_to_locale( 'es_ES' );
+
+		$actual = __( 'Invalid parameter.' );
+
+		// Cleanup.
+		restore_previous_locale();
+		restore_previous_locale();
+		restore_previous_locale();
+
+		$this->assertSame( 'Parámetro no válido. ', $actual );
+	}
+
+	public function test_restore_previous_locale_without_switching() {
+		$this->assertFalse( restore_previous_locale() );
+	}
+
+	public function test_restore_previous_locale_changes_the_locale_back() {
+		switch_to_locale( 'en_GB' );
+
+		// Cleanup.
+		restore_previous_locale();
+
+		$this->assertSame( 'en_US', get_locale() );
+	}
+
+	public function test_restore_previous_locale_after_switching_multiple_times() {
+		switch_to_locale( 'en_GB' );
+		switch_to_locale( 'es_ES' );
+		restore_previous_locale();
+
+		$locale = get_locale();
+
+		// Cleanup.
+		restore_previous_locale();
+
+		$this->assertSame( 'en_GB', $locale );
+	}
+
+	public function test_restore_previous_locale_restores_translation() {
+		switch_to_locale( 'es_ES' );
+		restore_previous_locale();
+
+		$actual = __( 'Invalid parameter.' );
+
+		$this->assertSame( 'Invalid parameter.', $actual );
+	}
+
+	public function test_restore_previous_locale_action_passes_previous_locale() {
+		switch_to_locale( 'en_GB' );
+		switch_to_locale( 'es_ES' );
+
+		add_action( 'restore_previous_locale', array( $this, 'store_locale' ), 10, 2 );
+
+		restore_previous_locale();
+
+		$previous_locale = $this->previous_locale;
+
+		// Cleanup.
+		restore_previous_locale();
+
+		$this->assertSame( 'es_ES', $previous_locale );
+	}
+
+	public function test_restore_previous_locale_restores_wp_locale_global() {
+		global $wp_locale;
+
+		$expected = array(
+			'thousands_sep' => ',',
+			'decimal_point' => '.',
+		);
+
+		switch_to_locale( 'de_DE' );
+		restore_previous_locale();
+
+		$this->assertEqualSetsWithIndex( $expected, $wp_locale->number_format );
+	}
+
+	public function test_restore_current_locale_without_switching() {
+		$this->assertFalse( restore_current_locale() );
+	}
+
+	public function test_restore_current_locale_after_switching_multiple_times() {
+		switch_to_locale( 'en_GB' );
+		switch_to_locale( 'nl_NL' );
+		switch_to_locale( 'es_ES' );
+
+		restore_current_locale();
+
+		$this->assertSame( 'en_US', get_locale() );
+	}
+
+	public function store_locale( $locale, $previous_locale ) {
+		$this->locale = $locale;
+		$this->previous_locale = $previous_locale;
+	}
+
+	public function test_is_locale_switched_if_not_switched() {
+		$this->assertFalse( is_locale_switched() );
+	}
+
+	public function test_is_locale_switched_original_locale() {
+		$original_locale = get_locale();
+
+		switch_to_locale( 'en_GB' );
+		switch_to_locale( $original_locale );
+
+		$is_locale_switched = is_locale_switched();
+
+		restore_current_locale();
+
+		$this->assertTrue( $is_locale_switched );
+	}
+
+	public function test_is_locale_switched() {
+		switch_to_locale( 'en_GB' );
+		switch_to_locale( 'nl_NL' );
+
+		$is_locale_switched = is_locale_switched();
+
+		restore_current_locale();
+
+		$this->assertTrue( $is_locale_switched );
+	}
+
+	public function _test_switch_to_site_locale_if_user_locale_is_set() {
+		global $l10n, $wp_locale_switcher;
+
+		$site_locale = get_locale();
+
+		$user_id = $this->factory()->user->create( array(
+			'role'   => 'administrator',
+			'locale' => 'de_DE',
+		) );
+
+		wp_set_current_user( $user_id );
+		set_current_screen( 'dashboard' );
+
+		$locale_switcher = clone $wp_locale_switcher;
+
+		$wp_locale_switcher = new WP_Locale_Switcher();
+		$wp_locale_switcher->init();
+
+		$user_locale = get_user_locale();
+
+		$this->assertSame( 'de_DE', $user_locale );
+
+		load_default_textdomain( $user_locale );
+		$language_header_before_switch = $l10n['default']->headers['Language']; // de_DE
+
+		$locale_switched_user_locale = switch_to_locale( $user_locale ); // False.
+		$locale_switched_site_locale = switch_to_locale( $site_locale ); // True.
+		$site_locale_after_switch = get_locale();
+		$language_header_after_switch = isset( $l10n['default'] ); // en_US
+
+		restore_current_locale();
+
+		$language_header_after_restore = $l10n['default']->headers['Language']; // de_DE
+
+		$wp_locale_switcher = $locale_switcher;
+
+		set_current_screen( 'front' );
+
+		$this->assertFalse( $locale_switched_user_locale );
+		$this->assertTrue( $locale_switched_site_locale );
+		$this->assertSame( $site_locale, $site_locale_after_switch );
+		$this->assertSame( 'de_DE', $language_header_before_switch );
+		$this->assertFalse( $language_header_after_switch );
+		$this->assertSame( 'de_DE', $language_header_after_restore );
+	}
+
+	public function _test_switch_to_different_site_locale_if_user_locale_is_set() {
+		global $l10n, $wp_locale_switcher;
+
+		// Change site locale to es_ES.
+		add_filter( 'locale', array( $this, 'filter_locale' ) );
+
+		$site_locale = get_locale();
+
+		$user_id = $this->factory()->user->create( array(
+			'role'   => 'administrator',
+			'locale' => 'de_DE',
+		) );
+
+		wp_set_current_user( $user_id );
+		set_current_screen( 'dashboard' );
+
+		$locale_switcher = clone $wp_locale_switcher;
+
+		$wp_locale_switcher = new WP_Locale_Switcher();
+		$wp_locale_switcher->init();
+
+		$user_locale = get_user_locale();
+
+		$this->assertSame( 'de_DE', $user_locale );
+
+		load_default_textdomain( $user_locale );
+		$language_header_before_switch = $l10n['default']->headers['Language']; // de_DE
+
+		$locale_switched_user_locale = switch_to_locale( $user_locale ); // False.
+		$locale_switched_site_locale = switch_to_locale( $site_locale ); // True.
+		$site_locale_after_switch = get_locale();
+		$language_header_after_switch = $l10n['default']->headers['Language']; // es_ES
+
+		restore_current_locale();
+
+		$language_header_after_restore = $l10n['default']->headers['Language']; // de_DE
+
+		$wp_locale_switcher = $locale_switcher;
+
+		set_current_screen( 'front' );
+
+		remove_filter( 'locale', array( $this, 'filter_locale' ) );
+
+		$this->assertFalse( $locale_switched_user_locale );
+		$this->assertTrue( $locale_switched_site_locale );
+		$this->assertSame( $site_locale, $site_locale_after_switch );
+		$this->assertSame( 'de_DE', $language_header_before_switch );
+		$this->assertSame( 'es_ES', $language_header_after_switch );
+		$this->assertSame( 'de_DE', $language_header_after_restore );
+	}
+
+	public function _test_multiple_switches_to_site_locale_and_user_locale() {
+		global $wp_locale_switcher;
+
+		$site_locale = get_locale();
+
+		$user_id = $this->factory()->user->create( array(
+			'role'   => 'administrator',
+			'locale' => 'en_GB',
+		) );
+
+		wp_set_current_user( $user_id );
+		set_current_screen( 'dashboard' );
+
+		$locale_switcher = clone $wp_locale_switcher;
+
+		$wp_locale_switcher = new WP_Locale_Switcher();
+		$wp_locale_switcher->init();
+
+		$user_locale = get_user_locale();
+
+		load_default_textdomain( $user_locale );
+
+		require_once DIR_TESTDATA . '/plugins/internationalized-plugin.php';
+
+		switch_to_locale( 'de_DE' );
+		switch_to_locale( $site_locale );
+
+		$expected = i18n_plugin_test();
+
+		restore_current_locale();
+
+		$wp_locale_switcher = $locale_switcher;
+
+		set_current_screen( 'front' );
+
+		$this->assertSame( 'en_US', get_locale() );
+		$this->assertSame( 'This is a dummy plugin', $expected );
+	}
+
+	public function filter_locale() {
+		return 'es_ES';
+	}
+}
Index: tests/phpunit/tests/locale.php
===================================================================
--- tests/phpunit/tests/locale.php	(revision 38951)
+++ tests/phpunit/tests/locale.php	(working copy)
@@ -1,7 +1,7 @@
 <?php
 
 /**
- * @group locale
+ * @group l10n
  * @group i18n
  */
 class Tests_Locale extends WP_UnitTestCase {
