This article documents the frontend language switcher widget shipped with wpresidence-translate. It covers registration, option storage, render pipeline, translation-target resolution, and the inline script that handles cookie persistence and navigation. The user-facing sibling explains the site-owner workflow; for the product overview see the multi-language real estate website guide.
Source Files
| File | Role |
|---|---|
| includes/widgets/language-switcher.php | Widget bootstrap – registers the sidebar widget and control via wp_register_sidebar_widget() / wp_register_widget_control(). |
| includes/widgets/language-switcher-options.php | Instance storage helpers (wpr_translate_language_switcher_get_options(), _save_options(), _next_number()). |
| includes/widgets/language-switcher-display.php | Frontend render – wpr_translate_language_switcher_widget_display() and wpr_translate_render_language_switcher_dropdown(). |
| includes/widgets/language-switcher-control.php | Admin form renderer at Appearance > Widgets – single Title field. |
Registration
The widget uses the legacy wp_register_sidebar_widget() API rather than WP_Widget because the plugin intentionally keeps multiple instances behind a single option. wpr_translate_register_language_switcher_widget() loops saved instances and registers each, plus a catch-all id for the widgets screen.
$widget_ops = array(
'classname' => 'wpr-language-switcher-widget',
'description' => __( '...', 'wpr-translate' ),
);
$control_ops = array( 'id_base' => 'wpr_translate_language_switcher' );
Instance numbers start at 1 and increment via wpr_translate_language_switcher_next_number(). Registered IDs follow the pattern wpr_translate_language_switcher-{N}.
Option Storage
All instances live under a single option:
| Option key | Shape |
|---|---|
| wpr_translate_language_switcher (constant WPR_TRANSLATE_LANGUAGE_SWITCHER_OPTION) | array( int $number => array( ‘title’ => string ) ) |
Helpers sort the array by numeric key, sanitize titles through sanitize_text_field(), and delete the option when no instances remain.
Render Pipeline
wpr_translate_language_switcher_widget_display( $args, $widget_args ) is the display callback. In order it:
- Loads saved options and resolves the instance title by $widget_args[‘number’].
- Calls wpr_translate_get_active_languages() and bails when fewer than 2 languages exist.
- Detects the current context – is_singular(), get_queried_object() for WP_Term, global $post fallback.
- Resolves the current language via wpr_translate_get_context_language( $current_post_id ); for term archives it overrides with wpr_translate_get_element_language( $term_id, ‘tax_{taxonomy}’ ).
- Builds a $translation_map – language code to target post/term ID – using wpr_translation_get_translation_group() for posts and wpr_translate_lookup_translated_term_id_strict() for terms.
- Computes a fallback post URL via wpr_translation_get_post_language_permalink(), falling back to get_permalink().
- Iterates languages, builds per-language payload (code, label, flag URL, target URL, router URL, fallback URL, cookie flag), and emits <li> items.
- Delegates to wpr_translate_render_language_switcher_dropdown() for the Bootstrap dropdown shell.
- Emits a small inline script that wires clicks, updates the button label/flag, stores the cookie preference, and navigates.
Output Shape
<div id="wpr-language-switcher-{widget_id}" class="dropdown wpr-language-switcher"
data-current-post-id="…" data-current-language-code="…"
data-flag-alt-template="%s flag">
<button class="btn dropdown-toggle wpr-language-switcher-toggle"
data-bs-toggle="dropdown" aria-expanded="false">…</button>
<ul class="dropdown-menu wpr-language-switcher-menu">
<li role="presentation">
<button class="dropdown-item wpr-language-switcher-item"
data-language-code="fr" data-translation-id="123"
data-target-url="…" data-router-url="…"
data-fallback-url="…" data-can-set-cookie="1"
data-flag-url="…" data-label="Français">…</button>
</li>
</ul>
</div>
When the widget is injected into a WPRentals header menu, the helper wpr_translate_is_wprentals_theme_active() adds sub-menu, menu-item, and menu-item-link classes plus Bootstrap-3 data-toggle=”dropdown” for compatibility.
Target URL Resolution
For each language the widget picks the best URL in this priority:
- Direct translation – wpr_translation_get_post_language_permalink() for posts or get_term_link() for terms.
- Front-page root – when the current post is the front page (or one of its translations), the language root URL from wpr_translate_language_router_get_switch_url().
- Fallback post/term URL – the default-language permalink of the current post or term, if any.
- Router URL – wpr_translate_language_router_get_switch_url( $language ) for archives, search, 404s.
The data-can-set-cookie attribute is 0 when the URL is a fallback (no real translation) so the cookie is not stuck on a language the visitor did not intentionally pick.
Cookie Persistence
On click, the inline script POSTs to admin-ajax.php with action=wpr_translate_set_language_cookie, the selected language code, a wp_create_nonce(‘wpr_translate_language_cookie’), and current_post_id. The transport preference order is navigator.sendBeacon, fetch with keepalive: true, and finally XMLHttpRequest so navigation is not delayed.
The server stores the choice under the cookie wpestate_translation_lang_pref, which wpr_translate_resolve_preferred_language() reads on subsequent requests.
Flags
Flag URLs are constructed with WPR_TRANSLATE_URL . ‘assets/img/flags/4×3/{code}.svg’. The widget uses the language’s explicit flag field first and falls back to the language code if no flag is set. Missing flags render an <img> with no src and the hidden-state class wpr-language-switcher-flag–hidden so the layout stays intact.
Menu Injection
$args[‘wpr_translate_menu_injected’] tells the display callback it is rendering inside a WordPress nav menu rather than a standalone widget area. Combined with wpr_translate_is_wprentals_theme_active(), this toggles WPRentals-compatible markup. Menu injection itself is handled by nav-menu-locations-frontend.php – outside the scope of this article.
Elementor Widget
There is no dedicated Elementor language switcher widget in either the plugin or residence-elementor at this time. For Elementor layouts, drop a standard WordPress widget area or use a shortcode/widget via the Sidebar widget. The legacy WPResidence theme widget WP_Estate_Translation continues to work for older sites, but new integrations should use the widget documented here.
Extension Tips
- Do not guard on $_SERVER[‘HTTP_ACCEPT_LANGUAGE’]. The widget never renders different markup per visitor – language detection happens via cookie on click, not at render time.
- Multiple instances: each instance is keyed by its number, and the wrapper ID is derived from $args[‘widget_id’]. Two instances on the same page each get their own handlers.
- Styling: hook your theme CSS on .wpr-language-switcher, .wpr-language-switcher-toggle, and .wpr-language-switcher-item. Avoid overriding the inline script.
- Translation-group helpers: if you are building a custom switcher, reuse wpr_translation_get_translation_group() and wpr_translation_get_post_language_permalink() rather than reading the trid tables directly.
Related Articles
- Managing Languages – upstream option the widget reads from.
- Language Detection & Redirects – how the cookie is consumed on subsequent requests.
- Admin Bar Language Switcher & Editor Header Selector – admin-side companion.
See the multi-language real estate website page for product context.