This article covers the two admin-side language controls that WPEstate Translate wires into wp-admin: the admin-bar Content Language dropdown and the block editor header language badge. Both are thin layers on top of the language manager; the interesting parts are where they store state, how they gate access, and where they hook in. The sibling user article explains the UX. Product context lives on the multi-language real estate website page.
Source Files
| File | Role |
|---|---|
| includes/admin/admin-bar-language-switcher.php | Admin bar dropdown — node registration, switch handler, nonce-protected URL. |
| includes/admin/editor-header-language.php | Block editor language badge — localizes the payload into the admin JS bundle. |
| includes/admin/post-editor.php | Post editor auto-translate button; reads the same language resolution helpers. |
Admin Bar Switcher — Bootstrap
wpr_translate_bootstrap_admin_bar_language_switcher() is called from the plugin bootstrap. It registers two hooks:
add_action( 'admin_init', 'wpr_translate_handle_admin_language_switch' ); add_action( 'admin_bar_menu', 'wpr_translate_register_admin_bar_language_switcher', 90 );
Admin Bar Node Registration
wpr_translate_register_admin_bar_language_switcher( $wp_admin_bar ) runs at priority 90 on admin_bar_menu. It bails early unless:
- is_admin() is true.
- $wp_admin_bar instanceof WP_Admin_Bar.
- current_user_can( ‘edit_posts’ ).
- is_admin_bar_showing().
- count( wpr_translation_get_active_languages() ) >= 2.
The parent node is registered with ID wpr-translate-admin-language and title Content language: {Current}. Each child node is wpr-translate-admin-language-{code}, with the current entry receiving class wpr-translate-admin-language-current.
State — User Meta
The selection is stored per user under the meta key:
| Meta key | Type | Written by |
|---|---|---|
| wpr_translate_admin_language | string (ISO code) | wpr_translate_handle_admin_language_switch() via update_user_meta(). |
Reads go through wpr_translate_get_admin_selected_language_code(), which calls wpr_translate_get_current_admin_language() (includes/language-manager.php). That delegates to wpr_translate_language_context_get_admin_language(): it reads the user meta (logged-in users only), matches it against the language registry, and accepts any active language, including private ones. A missing or invalid value resolves to the default language, so the function returns the default code rather than an empty string. The result is cached for the rest of the request. An empty string is returned only when no language can be resolved at all.
Switch Handler Flow
wpr_translate_handle_admin_language_switch() runs on admin_init. Only triggers when $_GET[‘wpr_translate_admin_language’] is present. Steps:
- Check current_user_can( ‘edit_posts’ ).
- Verify $_GET[‘_wpnonce’] against the action wpr_translate_switch_admin_language.
- sanitize_key() the requested code and validate against active languages.
- If valid: update_user_meta( $user_id, ‘wpr_translate_admin_language’, $code ).
- If invalid or empty: delete_user_meta( $user_id, ‘wpr_translate_admin_language’ ).
- Build the redirect URL by stripping wpr_translate_admin_language and _wpnonce from REQUEST_URI, then wp_safe_redirect() and exit.
URLs are built by wpr_translate_get_admin_language_switch_url( $code ), which composes add_query_arg() onto the current REQUEST_URI and wraps with wp_nonce_url().
Fallback Language
When the user has no stored meta, the resolver above already returns the default language (the is_default entry from the Languages Manager). The node registration keeps a second fallback to wpr_translation_get_default_language_code( $languages ) for an empty result. This keeps the dropdown title informative on first visit.
Editor Header Badge — Bootstrap
wpr_translate_enqueue_post_editor_language_badge_assets( $hook ) is registered at priority 20:
add_action( 'admin_enqueue_scripts',
'wpr_translate_enqueue_post_editor_language_badge_assets', 20 );
It only acts when $hook is post.php or post-new.php. Priority 20 ensures the admin script bundle wpr-translate-admin is already registered and can receive the wp_localize_script() payload.
Language Resolution Order
- wpr_translate_return_current_language_admin() (includes/admin/taxonomy-metabox-filters.php). On post.php it returns the edited post’s own language via wpr_translation_resolve_post_language_code(). On post-new.php it returns the default language. Only on other screens does it use the admin bar selection (wpr_translate_get_current_admin_language()).
- wpr_translation_get_default_language_code( wpestate_translation_get_active_languages() ).
- get_option( ‘wpestate_default_language’, ” ) — legacy compatibility for sites upgraded from older WPResidence translation helpers. The plugin itself never writes this option.
- Hardcoded ‘en’.
JS Payload Shape
The badge data is exposed to the admin bundle under wprTranslatePostLanguage:
window.wprTranslatePostLanguage = {
label: 'Français ( France )',
code: 'fr',
flagUrl: 'https://example.com/wp-content/plugins/wpestate-translate/assets/img/flags/4x3/fr.svg',
flagAlt: 'Français ( France ) flag',
};
Flag URLs resolve via WPR_TRANSLATE_URL . ‘assets/img/flags/4×3/{flag}.svg’, falling back to the language code when no flag is set on the language record. The alt text template is __( ‘%s flag’, ‘wpr-translate’ ).
Post Editor Auto-Translate Button
The related file includes/admin/post-editor.php hooks post_submitbox_misc_actions via wpr_translate_render_post_auto_translation_button() (and registers an auto-translate metabox on add_meta_boxes), both only on load-post.php and load-post-new.php. It reuses the same language resolution chain — wpr_translate_get_post_language(), then wpr_translation_resolve_post_language_code(), then the default — so the publish-panel button, the editor header badge, and the admin bar switcher stay coherent.
When the target post’s language equals the default language, the button is replaced with the Original post -no translation action. notice. When no source post can be resolved via wpr_translate_resolve_metabox_source_post_id(), the button is rendered disabled with the notice Original source post was not found. Auto translation is disabled for this post.
The two AJAX handlers behind the button (wp_ajax_wpr_translate_auto_translate_post and wp_ajax_wpr_translate_auto_translate_post_publish) check edit_post on the target post and on the source post, so a user cannot translate content from a post they cannot edit.
Capability Map
| Surface | Capability |
|---|---|
| Admin bar node render | edit_posts |
| Admin bar switch handler | edit_posts + nonce wpr_translate_switch_admin_language |
| Editor badge payload | Any user who can load post.php/post-new.php |
| Auto-translate button | current_user_can( ‘edit_post’, $post->ID ) |
| Auto-translate AJAX | edit_post on the target post and on the source post |
| Languages admin page | manage_options |
Extension Notes
- Do not write to wpr_translate_admin_language directly from custom code — always go through update_user_meta() after validating against wpr_translation_get_active_languages().
- If you are adding your own language-aware list filter, read the user’s selection via wpr_translate_get_admin_selected_language_code(); it already falls back to the default language.
- The editor badge reads on every post editor page load — consumers should prefer wpr_translate_get_post_language( $post_id ) to follow the same source of truth.
- The switcher intentionally does not alter the admin UI locale. Changing WordPress admin language remains a user-profile concern; this switcher only filters content.
Related Articles
- Managing Languages — Developer Reference — the language manager API both surfaces read from.
- Translating Posts & Pages — the editor-side user workflow.
- Post List Table Enhancements — how the admin language filter interacts with list screens.
For background, see the multi-language real estate website page.