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()forWP_Term, global$postfallback. - Resolves the current language via
wpr_translate_get_context_language( $current_post_id ); for term archives it overrides withwpr_translate_get_element_language( $term_id, 'tax_{taxonomy}' ). - Builds a
$translation_map— language code to target post/term ID — usingwpr_translation_get_translation_group()for posts andwpr_translate_lookup_translated_term_id_strict()for terms. - Computes a fallback post URL via
wpr_translation_get_post_language_permalink(), falling back toget_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 orget_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/4x3/{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()andwpr_translation_get_post_language_permalink()rather than reading thetridtables 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.