diff --git a/wp-admin/includes/ms.php b/wp-admin/includes/ms.php index 7e08e6f4f2..48dbf55c74 100644 --- a/wp-admin/includes/ms.php +++ b/wp-admin/includes/ms.php @@ -272,6 +272,8 @@ function update_option_new_admin_email( $old_value, $value ) { ); 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 @@ All at ###SITENAME### $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 @@ function send_confirmation_on_profile_email() { ); 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 @@ All at ###SITENAME### 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(); + } } } diff --git a/wp-admin/ms-delete-site.php b/wp-admin/ms-delete-site.php index 9192509d94..6bc3cfcd89 100644 --- a/wp-admin/ms-delete-site.php +++ b/wp-admin/ms-delete-site.php @@ -42,6 +42,8 @@ if ( isset( $_POST['action'] ) && $_POST['action'] == 'deleteblog' && isset( $_P $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 @@ Webmaster $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(); + } ?>
diff --git a/wp-admin/user-new.php b/wp-admin/user-new.php index 38b78dd5af..4915960027 100644 --- a/wp-admin/user-new.php +++ b/wp-admin/user-new.php @@ -87,6 +87,8 @@ if ( isset($_REQUEST['action']) && 'adduser' == $_REQUEST['action'] ) { */ do_action( 'invite_user', $user_id, $role, $newuser_key ); + $switched_locale = switch_to_locale( get_user_locale( $user_details ) ); + /* translators: 1: Site name, 2: site URL, 3: role, 4: activation URL */ $message = __( 'Hi, @@ -96,6 +98,11 @@ You\'ve been invited to join \'%1$s\' at 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' ); } } diff --git a/wp-includes/class-wp-locale-switcher.php b/wp-includes/class-wp-locale-switcher.php new file mode 100644 index 0000000000..3d288c8c35 --- /dev/null +++ b/wp-includes/class-wp-locale-switcher.php @@ -0,0 +1,244 @@ +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 ); + } +} diff --git a/wp-includes/default-filters.php b/wp-includes/default-filters.php index 323e55e840..58aedf91f2 100644 --- a/wp-includes/default-filters.php +++ b/wp-includes/default-filters.php @@ -406,6 +406,7 @@ add_action( 'init', 'create_initial_post_types', 0 ); // highest priority 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 @@ add_filter( 'style_loader_src', 'wp_style_loader_src', 10, 2 ); // Taxonomy add_action( 'init', 'create_initial_taxonomies', 0 ); // highest priority +add_action( 'change_locale', 'create_initial_taxonomies' ); // Canonical add_action( 'template_redirect', 'redirect_canonical' ); diff --git a/wp-includes/l10n.php b/wp-includes/l10n.php index ba696ded93..ba856491af 100644 --- a/wp-includes/l10n.php +++ b/wp-includes/l10n.php @@ -1178,3 +1178,68 @@ function is_rtl() { } 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(); +} diff --git a/wp-includes/load.php b/wp-includes/load.php index 4a3e1c3e28..523960f023 100644 --- a/wp-includes/load.php +++ b/wp-includes/load.php @@ -841,13 +841,14 @@ function get_current_network_id() { * @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 @@ function wp_load_translations_early() { 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 @@ function wp_load_translations_early() { } $wp_locale = new WP_Locale(); + $wp_locale_switcher = new WP_Locale_Switcher(); + $wp_locale_switcher->init(); } /** diff --git a/wp-includes/ms-functions.php b/wp-includes/ms-functions.php index c65c9d5753..3eb7f671dc 100644 --- a/wp-includes/ms-functions.php +++ b/wp-includes/ms-functions.php @@ -800,6 +800,10 @@ function wpmu_signup_blog_notification( $domain, $path, $title, $user, $user_ema $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( get_user_locale( $user ) ); + $message = sprintf( /** * Filters the message content of the new blog notification email. @@ -849,6 +853,11 @@ function wpmu_signup_blog_notification( $domain, $path, $title, $user, $user_ema 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 @@ function wpmu_signup_user_notification( $user, $user_email, $key, $meta = array( 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( get_user_locale( $user ) ); + // Send email with activation link. $admin_email = get_site_option( 'admin_email' ); if ( $admin_email == '' ) @@ -934,6 +946,11 @@ function wpmu_signup_user_notification( $user, $user_email, $key, $meta = array( $user ); wp_mail( $user_email, wp_specialchars_decode( $subject ), $message, $message_headers ); + + if ( $switched_locale ) { + restore_previous_locale(); + } + return true; } @@ -1448,6 +1465,10 @@ function wpmu_welcome_notification( $blog_id, $user_id, $password, $title, $meta 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( get_user_locale( $user ) ); + $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 @@ We hope you enjoy your new site. Thanks! } $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 @@ We hope you enjoy your new site. Thanks! */ $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 @@ function wpmu_welcome_user_notification( $user_id, $password, $meta = array() ) $user = get_userdata( $user_id ); + $switched_locale = switch_to_locale( get_user_locale( $user ) ); + /** * Filters the content of the welcome email after user activation. * @@ -1590,6 +1617,11 @@ function wpmu_welcome_user_notification( $user_id, $password, $meta = array() ) */ $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; } diff --git a/wp-includes/pluggable.php b/wp-includes/pluggable.php index d7713b7dd2..d232426e3d 100644 --- a/wp-includes/pluggable.php +++ b/wp-includes/pluggable.php @@ -211,7 +211,7 @@ function wp_mail( $to, $subject, $message, $headers = '', $attachments = array() // (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 @@ function wp_notify_postauthor( $comment_id, $deprecated = null ) { $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 @@ function wp_notify_postauthor( $comment_id, $deprecated = null ) { @wp_mail( $email, wp_specialchars_decode( $subject ), $notify_message, $message_headers ); } + if ( $switched_locale ) { + restore_previous_locale(); + } + return true; } endif; @@ -1569,6 +1575,8 @@ function wp_notify_moderator($comment_id) { $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 @@ function wp_notify_moderator($comment_id) { @wp_mail( $email, wp_specialchars_decode( $subject ), $notify_message, $message_headers ); } + if ( $switched_locale ) { + restore_previous_locale(); + } + return true; } endif; @@ -1723,11 +1735,16 @@ function wp_new_user_notification( $user_id, $deprecated = null, $notify = '' ) $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 @@ function wp_new_user_notification( $user_id, $deprecated = null, $notify = '' ) $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( get_user_locale( $user ) ); + $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 @@ function wp_new_user_notification( $user_id, $deprecated = null, $notify = '' ) $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; diff --git a/wp-includes/pomo/mo.php b/wp-includes/pomo/mo.php index 6bc44d614d..47e9b6afe4 100644 --- a/wp-includes/pomo/mo.php +++ b/wp-includes/pomo/mo.php @@ -15,16 +15,37 @@ class MO extends Gettext_Translations { 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 @@ class MO extends Gettext_Translations { return $this->_nplurals; } } -endif; \ No newline at end of file +endif; diff --git a/wp-includes/user.php b/wp-includes/user.php index 734d26e0ff..d02b022272 100644 --- a/wp-includes/user.php +++ b/wp-includes/user.php @@ -1801,8 +1801,12 @@ function wp_update_user($userdata) { $blog_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); - if ( ! empty( $send_password_change_email ) ) { + $switched_locale = false; + if ( ! empty( $send_password_change_email ) || ! empty( $send_email_change_email ) ) { + $switched_locale = switch_to_locale( get_user_locale( $user_id ) ); + } + 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 @@ All at ###SITENAME### 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. diff --git a/wp-includes/version.php b/wp-includes/version.php index 2d7da86854..34001f2ed5 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -4,7 +4,7 @@ * * @global string $wp_version */ -$wp_version = '4.7-alpha-38960'; +$wp_version = '4.7-alpha-38961'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema. diff --git a/wp-settings.php b/wp-settings.php index bacb37d8f4..c592bab6e5 100644 --- a/wp-settings.php +++ b/wp-settings.php @@ -130,6 +130,7 @@ if ( SHORTINIT ) // 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 @@ unset( $locale_file ); */ $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' ) )