I18N: Introduce a user-specific language setting.

By enabling the user to select their preferred locale when editing the profile, we allow for greater personalization of the WordPress admin and therefore a better user experience.

The back end will be displayed in the user's individual locale while the locale used on the front end equals the one set for the whole site. If the user didn't specify a locale, the site's locale will be used as a fallback. The new `locale` property of the `WP_User` class can be used to retrieve the user's locale setting.

Props ocean90, ipm-frommen, swissspidy.
Fixes #29783.
Built from https://develop.svn.wordpress.org/trunk@38705


git-svn-id: http://core.svn.wordpress.org/trunk@38648 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
Pascal Birchler 2016-10-03 07:04:29 +00:00
parent bd03add624
commit 4a88d55054
20 changed files with 93 additions and 25 deletions

View File

@ -156,7 +156,7 @@ if ( $current_screen->taxonomy )
$admin_body_class .= ' branch-' . str_replace( array( '.', ',' ), '-', floatval( get_bloginfo( 'version' ) ) );
$admin_body_class .= ' version-' . str_replace( '.', '-', preg_replace( '/^([.0-9]+).*/', '$1', get_bloginfo( 'version' ) ) );
$admin_body_class .= ' admin-color-' . sanitize_html_class( get_user_option( 'admin_color' ), 'fresh' );
$admin_body_class .= ' locale-' . sanitize_html_class( strtolower( str_replace( '_', '-', get_locale() ) ) );
$admin_body_class .= ' locale-' . sanitize_html_class( strtolower( str_replace( '_', '-', get_user_locale() ) ) );
if ( wp_is_mobile() )
$admin_body_class .= ' mobile';

View File

@ -134,7 +134,7 @@ class WP_Plugin_Install_List_Table extends WP_List_Table {
'active_installs' => true
),
// Send the locale and installed plugin slugs to the API so it can provide context-sensitive results.
'locale' => get_locale(),
'locale' => get_user_locale(),
'installed_plugins' => $this->get_installed_plugin_slugs(),
);

View File

@ -1312,7 +1312,7 @@ class WP_Press_This {
$admin_body_class .= ' branch-' . str_replace( array( '.', ',' ), '-', floatval( $wp_version ) );
$admin_body_class .= ' version-' . str_replace( '.', '-', preg_replace( '/^([.0-9]+).*/', '$1', $wp_version ) );
$admin_body_class .= ' admin-color-' . sanitize_html_class( get_user_option( 'admin_color' ), 'fresh' );
$admin_body_class .= ' locale-' . sanitize_html_class( strtolower( str_replace( '_', '-', get_locale() ) ) );
$admin_body_class .= ' locale-' . sanitize_html_class( strtolower( str_replace( '_', '-', get_user_locale() ) ) );
/** This filter is documented in wp-admin/admin-header.php */
$admin_body_classes = apply_filters( 'admin_body_class', '' );

View File

@ -16,7 +16,7 @@
*/
function wp_credits() {
$wp_version = get_bloginfo( 'version' );
$locale = get_locale();
$locale = get_user_locale();
$results = get_site_transient( 'wordpress_credits_' . $locale );

View File

@ -1353,7 +1353,7 @@ function wp_dashboard_browser_nag() {
$notice .= "<p class='browser-update-nag{$browser_nag_class}'>{$msg}</p>";
$browsehappy = 'http://browsehappy.com/';
$locale = get_locale();
$locale = get_user_locale();
if ( 'en_US' !== $locale )
$browsehappy = add_query_arg( 'locale', $locale, $browsehappy );

View File

@ -125,13 +125,13 @@ function wp_import_handle_upload() {
function wp_get_popular_importers() {
include( ABSPATH . WPINC . '/version.php' ); // include an unmodified $wp_version
$locale = get_locale();
$locale = get_user_locale();
$cache_key = 'popular_importers_' . md5( $locale . $wp_version );
$popular_importers = get_site_transient( $cache_key );
if ( ! $popular_importers ) {
$url = add_query_arg( array(
'locale' => get_locale(),
'locale' => get_user_locale(),
'version' => $wp_version,
), 'http://api.wordpress.org/core/importers/1.1/' );
$options = array( 'user-agent' => 'WordPress/' . $wp_version . '; ' . home_url() );

View File

@ -109,7 +109,7 @@ function plugins_api( $action, $args = array() ) {
}
if ( ! isset( $args->locale ) ) {
$args->locale = get_locale();
$args->locale = get_user_locale();
}
/**

View File

@ -1622,7 +1622,7 @@ do_action( "admin_head-$hook_suffix" );
/** This action is documented in wp-admin/admin-header.php */
do_action( 'admin_head' );
$admin_body_class .= ' locale-' . sanitize_html_class( strtolower( str_replace( '_', '-', get_locale() ) ) );
$admin_body_class .= ' locale-' . sanitize_html_class( strtolower( str_replace( '_', '-', get_user_locale() ) ) );
if ( is_rtl() )
$admin_body_class .= ' rtl';

View File

@ -412,7 +412,7 @@ function themes_api( $action, $args = array() ) {
}
if ( ! isset( $args->locale ) ) {
$args->locale = get_locale();
$args->locale = get_user_locale();
}
/**

View File

@ -94,6 +94,16 @@ function edit_user( $user_id = 0 ) {
$user->rich_editing = isset( $_POST['rich_editing'] ) && 'false' == $_POST['rich_editing'] ? 'false' : 'true';
$user->admin_color = isset( $_POST['admin_color'] ) ? sanitize_text_field( $_POST['admin_color'] ) : 'fresh';
$user->show_admin_bar_front = isset( $_POST['admin_bar_front'] ) ? 'true' : 'false';
$user->locale = '';
if ( isset( $_POST['locale'] ) ) {
$locale = sanitize_text_field( $_POST['locale'] );
if ( ! in_array( $locale, get_available_languages(), true ) ) {
$locale = '';
}
$user->locale = ( '' === $locale ) ? 'en_US' : $locale;
}
}
$user->comment_shortcuts = isset( $_POST['comment_shortcuts'] ) && 'true' == $_POST['comment_shortcuts'] ? 'true' : '';

View File

@ -209,8 +209,9 @@ if ( 'update' == $action ) {
$value = null;
if ( isset( $_POST[ $option ] ) ) {
$value = $_POST[ $option ];
if ( ! is_array( $value ) )
if ( ! is_array( $value ) ) {
$value = trim( $value );
}
$value = wp_unslash( $value );
}
update_option( $option, $value );
@ -218,12 +219,15 @@ if ( 'update' == $action ) {
// Switch translation in case WPLANG was changed.
$language = get_option( 'WPLANG' );
$user_language = get_user_locale();
if ( $language === $user_language ) {
if ( $language ) {
load_default_textdomain( $language );
} else {
unload_textdomain( 'default' );
}
}
}
/**
* Handle settings errors and return to options page

View File

@ -260,7 +260,7 @@ foreach ( $plugin_files as $plugin_file ) :
<input type="hidden" name="scrollto" id="scrollto" value="<?php echo $scrollto; ?>" />
</div>
<?php if ( !empty( $docs_select ) ) : ?>
<div id="documentation" class="hide-if-no-js"><label for="docs-list"><?php _e('Documentation:') ?></label> <?php echo $docs_select ?> <input type="button" class="button" value="<?php esc_attr_e( 'Look Up' ) ?> " onclick="if ( '' != jQuery('#docs-list').val() ) { window.open( 'https://api.wordpress.org/core/handbook/1.0/?function=' + escape( jQuery( '#docs-list' ).val() ) + '&amp;locale=<?php echo urlencode( get_locale() ) ?>&amp;version=<?php echo urlencode( get_bloginfo( 'version' ) ) ?>&amp;redirect=true'); }" /></div>
<div id="documentation" class="hide-if-no-js"><label for="docs-list"><?php _e('Documentation:') ?></label> <?php echo $docs_select ?> <input type="button" class="button" value="<?php esc_attr_e( 'Look Up' ) ?> " onclick="if ( '' != jQuery('#docs-list').val() ) { window.open( 'https://api.wordpress.org/core/handbook/1.0/?function=' + escape( jQuery( '#docs-list' ).val() ) + '&amp;locale=<?php echo urlencode( get_user_locale() ) ?>&amp;version=<?php echo urlencode( get_bloginfo( 'version' ) ) ?>&amp;redirect=true'); }" /></div>
<?php endif; ?>
<?php if ( is_writeable($real_file) ) : ?>
<?php if ( in_array( $file, (array) get_option( 'active_plugins', array() ) ) ) { ?>

View File

@ -263,7 +263,7 @@ else : ?>
<div id="documentation" class="hide-if-no-js">
<label for="docs-list"><?php _e('Documentation:') ?></label>
<?php echo $docs_select; ?>
<input type="button" class="button" value="<?php esc_attr_e( 'Look Up' ); ?>" onclick="if ( '' != jQuery('#docs-list').val() ) { window.open( 'https://api.wordpress.org/core/handbook/1.0/?function=' + escape( jQuery( '#docs-list' ).val() ) + '&amp;locale=<?php echo urlencode( get_locale() ) ?>&amp;version=<?php echo urlencode( get_bloginfo( 'version' ) ) ?>&amp;redirect=true'); }" />
<input type="button" class="button" value="<?php esc_attr_e( 'Look Up' ); ?>" onclick="if ( '' != jQuery('#docs-list').val() ) { window.open( 'https://api.wordpress.org/core/handbook/1.0/?function=' + escape( jQuery( '#docs-list' ).val() ) + '&amp;locale=<?php echo urlencode( get_user_locale() ) ?>&amp;version=<?php echo urlencode( get_bloginfo( 'version' ) ) ?>&amp;redirect=true'); }" />
</div>
<?php endif; ?>

View File

@ -269,6 +269,38 @@ if ( !( IS_PROFILE_PAGE && !$user_can_edit ) ) : ?>
</fieldset>
</td>
</tr>
<?php
$languages = get_available_languages();
if ( $languages ) : ?>
<tr class="user-language-wrap">
<th scope="row">
<label for="site_language"><?php _e( 'Site Language' ); ?></label>
</th>
<td>
<?php
$user_locale = get_user_option( 'locale', $profileuser->ID );
if ( 'en_US' === $user_locale ) { // en_US
$user_locale = false;
} elseif ( ! in_array( $user_locale, $languages, true ) ) {
$user_locale = get_locale();
}
wp_dropdown_languages( array(
'name' => 'locale',
'id' => 'locale',
'selected' => $user_locale,
'languages' => $languages,
'show_available_translations' => false
) );
?>
</td>
</tr>
<?php
endif;
?>
<?php
/**
* Fires at the end of the 'Personal Options' settings table on the user editing screen.

View File

@ -351,7 +351,7 @@ final class _WP_Editors {
if ( empty( self::$first_init ) ) {
self::$baseurl = includes_url( 'js/tinymce' );
$mce_locale = get_locale();
$mce_locale = get_user_locale();
self::$mce_locale = $mce_locale = empty( $mce_locale ) ? 'en' : strtolower( substr( $mce_locale, 0, 2 ) ); // ISO 639-1
/** This filter is documented in wp-admin/includes/media.php */
@ -672,7 +672,7 @@ final class _WP_Editors {
}
}
$body_class .= ' locale-' . sanitize_html_class( strtolower( str_replace( '_', '-', get_locale() ) ) );
$body_class .= ' locale-' . sanitize_html_class( strtolower( str_replace( '_', '-', get_user_locale() ) ) );
if ( !empty($set['tinymce']['body_class']) ) {
$body_class .= ' ' . $set['tinymce']['body_class'];

View File

@ -1395,7 +1395,7 @@ final class WP_Theme implements ArrayAccess {
* @param array $themes Array of themes to sort, passed by reference.
*/
public static function sort_by_name( &$themes ) {
if ( 0 === strpos( get_locale(), 'en_' ) ) {
if ( 0 === strpos( get_user_locale(), 'en_' ) ) {
uasort( $themes, array( 'WP_Theme', '_name_sort' ) );
} else {
uasort( $themes, array( 'WP_Theme', '_name_sort_i18n' ) );

View File

@ -31,6 +31,7 @@
* @property string $display_name
* @property string $spam
* @property string $deleted
* @property string $locale
*/
class WP_User {
/**

View File

@ -75,6 +75,23 @@ function get_locale() {
return apply_filters( 'locale', $locale );
}
/**
* Retrieves the locale of the current user.
*
* If the user has a locale set to a non-empty string then it will be
* returned. Otherwise it returns the locale of get_locale().
*
* @since 4.7.0
*
* @return string The locale of the current user.
*/
function get_user_locale() {
$user = wp_get_current_user();
$locale = $user->locale;
return ( '' === $locale ) ? get_locale() : $locale;
}
/**
* Retrieve the translation of $text.
*
@ -633,7 +650,7 @@ function unload_textdomain( $domain ) {
*/
function load_default_textdomain( $locale = null ) {
if ( null === $locale ) {
$locale = get_locale();
$locale = is_admin() ? get_user_locale() : get_locale();
}
// Unload previously loaded strings so we can switch translations.

View File

@ -1358,6 +1358,7 @@ function validate_username( $username ) {
* @since 2.0.0
* @since 3.6.0 The `aim`, `jabber`, and `yim` fields were removed as default user contact
* methods for new installs. See wp_get_user_contact_methods().
* @since 4.7.0 The user's locale can be passed to `$userdata`.
*
* @global wpdb $wpdb WordPress database abstraction object.
*
@ -1392,6 +1393,7 @@ function validate_username( $username ) {
* @type string|bool $show_admin_bar_front Whether to display the Admin Bar for the user on the
* site's front end. Default true.
* @type string $role User's role.
* @type string $locale User's locale. Default empty.
* }
* @return int|WP_Error The newly created user's ID or a WP_Error object if the user could not
* be created.
@ -1606,6 +1608,8 @@ function wp_insert_user( $userdata ) {
$meta['show_admin_bar_front'] = empty( $userdata['show_admin_bar_front'] ) ? 'true' : $userdata['show_admin_bar_front'];
$meta['locale'] = isset( $userdata['locale'] ) ? $userdata['locale'] : '';
$user_nicename_check = $wpdb->get_var( $wpdb->prepare("SELECT ID FROM $wpdb->users WHERE user_nicename = %s AND user_login != %s LIMIT 1" , $user_nicename, $user_login));
if ( $user_nicename_check ) {
@ -1965,7 +1969,7 @@ function wp_create_user($username, $password, $email = '') {
* @return array List of user keys to be populated in wp_update_user().
*/
function _get_additional_user_keys( $user ) {
$keys = array( 'first_name', 'last_name', 'nickname', 'description', 'rich_editing', 'comment_shortcuts', 'admin_color', 'use_ssl', 'show_admin_bar_front' );
$keys = array( 'first_name', 'last_name', 'nickname', 'description', 'rich_editing', 'comment_shortcuts', 'admin_color', 'use_ssl', 'show_admin_bar_front', 'locale' );
return array_merge( $keys, array_keys( wp_get_user_contact_methods( $user ) ) );
}

View File

@ -4,7 +4,7 @@
*
* @global string $wp_version
*/
$wp_version = '4.7-alpha-38704';
$wp_version = '4.7-alpha-38705';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.