This article documents the frontend language switcher shipped with WPEstate Translate 1.0.9: widget registration, instance storage, the render pipeline, target-URL resolution and the other places that reuse the same renderer. For the product overview see the multi-language real estate website guide.
Source Files
| File | Role |
|---|---|
| includes/widgets/class-wpr-translate-language-switcher-widget.php | WPR_Translate_Language_Switcher_Widget (extends WP_Widget) – widget type, single Title field, update() sanitizer. |
| includes/widgets/language-switcher.php | Registration, one-time legacy option migration, request-context capture, inline theme-option CSS. |
| includes/widgets/language-switcher-display.php | Frontend render – wpr_translate_language_switcher_widget_display() and wpr_translate_render_language_switcher_dropdown(). |
| includes/nav-menu-locations-frontend.php | Injects the same dropdown into nav menus (Settings > Menu Language Dropdown). |
Registration
The switcher is a standard WP_Widget with id_base wpr_translate_language_switcher, title WPResidence Language Switcher, classname wpr-language-switcher-widget and show_instance_in_rest => true. plugin-bootstrap.php hooks wpr_translate_register_widgets() on widgets_init, which calls wpr_translate_register_language_switcher_widget() and then register_widget().
Registered IDs keep the pattern wpr_translate_language_switcher-{N}, so sidebar placements from older versions still resolve.
Option Storage
| Option key | Shape |
|---|---|
| widget_wpr_translate_language_switcher (standard WP_Widget storage) | array( int $number => array( ‘title’ => string ) ) |
| wpr_translate_language_switcher (legacy, pre-WP_Widget) | Read once by wpr_translate_language_switcher_migrate_legacy_instances(), copied into the option above by instance number, then deleted. |
Titles are sanitized with sanitize_text_field().
Request Context
wpr_translate_language_switcher_capture_request_context() runs on wp (priority 1) and stores the main-query post ID, or term ID and taxonomy, in $GLOBALS['wpr_translate_language_switcher_request_context']. The renderer reads it through wpr_translate_language_switcher_get_request_context(), so secondary loops in the page body (property lists, sliders) cannot change which object the switcher links from.
Render Pipeline
wpr_translate_language_switcher_widget_display( $args, $widget_args ) is the display callback. In order it:
- Reads the title from
$widget_args['title'](menu and Elementor callers pass none). - Calls
wpr_translate_get_active_languages()and returns when fewer than 2 languages exist. - Loads the captured request context (post, or term + taxonomy).
- Resolves the current language via
wpr_translate_get_context_language( $current_post_id ); on term archives it useswpr_translate_get_element_language( $term_id, 'tax_{taxonomy}' ). - Builds
$translation_map(language code => target ID). Posts usewpr_translate_get_sibling_map()(postmeta first, trid table fallback) and skip members whose status is not viewable. Terms usewpr_translate_lookup_translated_term_id_strict()from the original term. - Builds a front-page map from
page_on_frontso translated front pages link to the language root. - Computes the fallback post URL (default-language member via
wpr_translation_get_post_language_permalink(), elseget_permalink()) and the fallback term URL. - Emits one
<li>per language and wraps them withwpr_translate_render_language_switcher_dropdown(). - Prints an inline script that updates the button label/flag on click 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"
data-selected-language-code="…">…</button>
<ul class="dropdown-menu wpr-language-switcher-menu">
<li role="presentation">
<button type="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-flag-url="…"
data-label="Français">…</button>
</li>
</ul>
</div>
Labels drop a trailing bracket such as “English (United States)” → “English”. When the switcher is injected into a menu and wpr_translate_is_wprentals_theme_active() is true, the wrapper gets wpr-language-switcher--wprentals, the list gets sub-menu, rows get menu-item classes, the toggle and items get menu-item-link, the toggle also gets Bootstrap 3 data-toggle="dropdown", and a second inline script handles open/close.
Target URL Resolution
For each language the item URL is chosen in this order:
- Direct translation –
wpr_translation_get_post_language_permalink()for posts; for termsget_term_link()passed throughwpr_translate_prefix_url_for_language(). - Front-page root – when the current post is the front page or one of its translations,
wpr_translate_language_router_get_switch_url( $language ). - Fallback post/term URL – the default-language URL of the current post or term.
- Router URL –
wpr_translate_language_router_get_switch_url( $language )for archives, search and 404s.
Terms of a taxonomy set to Not Translatable are shared by all languages, so every language maps to the same term and is listed under its own prefix.
Navigation and Language Persistence
On click the inline script navigates to data-target-url, then data-router-url, then data-fallback-url. Nothing is stored on the visitor’s side: the language of the next page is decided only by its URL prefix. The wpestate_translation_lang_pref cookie, the wpr_translate_set_language_cookie AJAX action and the data-can-set-cookie attribute were removed. If your page cache varies on that cookie, remove it from the vary list.
Flags
Flag URLs are built as WPR_TRANSLATE_URL . 'assets/img/flags/4x3/{code}.svg', using the language’s flag field first and its code as fallback. With no flag the <img> has no src and the wrapper gets wpr-language-switcher-flag--hidden.
Styling from Theme Options
wpr_translate_language_switcher_print_inline_styles() (on wp_head, priority 100) reads wp_estate_lang_switcher_font_size, wp_estate_lang_switcher_dropdown_font_size, wp_estate_lang_switcher_background_color and wp_estate_lang_switcher_font_color from the wpresidence_admin option and prints <style id="wpr-translate-language-switcher-inline"> only when at least one is set.
Other Callers of the Renderer
| Caller | How it calls the renderer |
|---|---|
Menu Language Dropdown (wpr_translate_inject_language_switcher_into_menu() on wp_nav_menu_items) |
Reads wpr_translate_settings['menu_language_switcher'][ $location ] (before / after); passes widget_id wpr-menu-language-switcher-{location} and wpr_translate_menu_injected => true. Menus rendered without theme_location (Studio / Elementor nav widgets) are matched to a location by menu term ID. |
Elementor Language Switcher widget (residence-elementor, Wpresidence_Site_Language_Changer, widgets/header-footer/site-language-changer.php; titled Language Dropdown in older residence-elementor builds) |
wpestate_render_language_dropdown() passes a unique widget_id wpr-language-switcher-elementor-{suffix} so header and footer copies do not collide. |
Extension Tips
- Do not branch on
$_SERVER['HTTP_ACCEPT_LANGUAGE']or cookies. Language is resolved from the URL prefix only. - Multiple instances: the wrapper ID comes from
$args['widget_id']; give every custom call a unique one. - Styling: target
.wpr-language-switcher,.wpr-language-switcher-toggleand.wpr-language-switcher-item. Avoid overriding the inline script. - Custom switchers: reuse
wpr_translate_get_sibling_map()andwpr_translation_get_post_language_permalink()rather than reading the trid table directly.
Related Articles
- The Language Switcher – placing the switcher, for site owners.
- Managing Languages – the language list the widget reads.
- Language Detection & Redirects – how the URL prefix decides the language.
- Admin Bar Language Switcher & Editor Header Selector – admin-side companion.
See the multi-language real estate website page for product context.