ID remapping is the plugin feature that rewrites numeric references inside shortcodes and Elementor widgets so translated pages render the translated equivalents of properties, agents, developers, and taxonomy terms. This article is the implementation-level companion to the user-facing article. Product context is on our multi-language real estate website page.
File Map
| File | Role |
|---|---|
| includes/shortcode-compat.php | Intercepts shortcodes via pre_do_shortcode_tag and rewrites ID attributes. |
| includes/elementor-widget-compat.php | Front-end Elementor hooks — re-runs shortcode widgets and primes Search Form Builder term data. |
| includes/admin/elementor-translate-apply.php | Applies translated payloads back to _elementor_data, including post-ID and term-ID resolution. |
Shortcode Interception
Bootstrap via wpr_translate_bootstrap_shortcode_compat() attaches a single filter:
add_filter( 'pre_do_shortcode_tag', 'wpr_translate_filter_wpresidence_shortcodes', 10, 4 );
The callback short-circuits the normal shortcode pipeline, rewrites attributes, and invokes the registered callback manually with the translated attributes.
Shortcode Tag Allow-List
wpr_translate_is_wpresidence_shortcode_tag( $tag ) accepts:
- Tags prefixed with wpestate_, wpresidence_, or estate_.
- A fixed list: property_page, property_page_advanced, estate_property, estate_properties.
All other shortcodes pass through untouched (the filter returns null).
Attribute Classification
wpr_translate_identify_shortcode_attribute_type( $attribute ) classifies each attribute as single, list, or empty string (skip). Explicit name lists:
single: id, post_id, postid, property_id, propertyid,
agent_id, agentid, developer_id, developerid
list: ids, post_ids, postids, property_ids, propertyids,
agent_ids, agentids, developer_ids, developerids
A regex fallback matches trailing *id / *ids patterns for keys like estateid, listingids, itemid.
Single ID Conversion
wpr_translate_convert_shortcode_single_id( $value ) returns null for non-numeric input (so the attribute is left unchanged) and otherwise delegates to wpr_translate_resolve_post_for_current_language(). A translated ID is returned as a string only when it differs from the original.
List ID Conversion
wpr_translate_convert_shortcode_id_list( $value ) preserves the original delimiter. Detection order:
- | (pipe)
- ; (semicolon)
- (space)
- , (comma, default)
Arrays are also accepted; they are rejoined with a comma and returned as a string. Non-numeric entries are preserved in place.
Translation Resolution
wpr_translate_resolve_post_for_current_language( $post_id ) looks up the current language code via wpr_translate_get_current_language() and calls:
wpr_translate_resolve_post_for_language( $post_id, $post_type, $language )
The resolver requires two helpers to be available:
- wpr_translation_get_element_type( $post_type ) — maps a post type (estate_property, estate_agent, etc.) to an element type.
- wpr_translation_get_translation_group( $post_id, $element_type ) — returns the group payload with source_language and translations keyed by language code.
Fallback order in wpr_translate_resolve_post_for_language():
- Exact language match in the translations array.
- Source-language translation.
- First translation returned by wpr_translation_is_translation_post_available().
- Original post ID.
Recursion Guard
wpr_translate_filter_wpresidence_shortcodes() uses a static $processing array keyed by tag to prevent unbounded recursion when a shortcode callback re-invokes do_shortcode() internally. The flag is cleared after the outer call returns.
Elementor Shortcode Widget Bridge
Elementor’s built-in shortcode widget stores a literal shortcode string. If Elementor rendered the widget in preview mode it may not have executed the shortcode at all. wpr_translate_filter_elementor_shortcode_widget_content() detects this by comparing the rendered content with the raw shortcode string (after trim()). When they match, it calls do_shortcode() so the pre_do_shortcode_tag filter can run.
Elementor Widget ID Remapping
Widgets that carry ID arrays are handled by the apply pipeline in elementor-translate-apply.php. Post-type widgets (Agent Grids, Property lists) are walked via wpr_translate_apply_elementor_widget_translations(). Each target post ID is resolved via the same translation group helpers used for shortcodes.
Search Form Builder Term Priming
wpr_translate_prepare_search_form_builder_frontend_ids() runs on elementor/frontend/widget/before_render:
- Widget gate: $widget->get_name() === ‘WpResidence_Search_Form_Builder’.
- Reads the tabs_field setting. Falls back to scanning for a non-empty *_data setting if tabs_field is missing.
- Normalizes term IDs via wpr_translate_normalize_agent_grids_ids_for_log().
- Builds a map of term_id => sanitized name and writes it to a transient keyed by ‘wpestate_elementor_tax_’, language-scoped via wpestate_set_transient_name_multilang() when available.
- TTL: six hours.
Agent Grids Front-End Hook
wpr_translate_log_agent_grids_frontend_ids() targets widgets named Wpresidence_Agent_Grids. It reads grid_taxonomy, normalizes the ID list, and (under WP_DEBUG_LOG) records the resolved agent posts — used for debugging why a grid is empty in a given language.
Settings Gate
wpr_translate_is_elementor_compat_enabled() reads wpr_translate_settings[‘elementor_compatibility’]. When set to false, both the shortcode filter and the Elementor widget hooks become no-ops.
Extension Points
- Custom attribute types — extend wpr_translate_identify_shortcode_attribute_type() via a child theme override guarded by function_exists() if you need to recognize additional ID keys.
- Custom post types — make sure wpr_translation_get_element_type() handles your CPT; otherwise the resolver returns the original ID.
- Delimiter support — wpr_translate_convert_shortcode_id_list() handles comma, pipe, semicolon, and space; extend there for exotic delimiters.
Gotchas
- The pre_do_shortcode_tag filter returns null to let WordPress run the shortcode normally, and the rewritten output (or the original if unchanged) otherwise. Do not short-circuit with an empty string — it suppresses the shortcode.
- Non-numeric entries in ID lists are preserved verbatim. Do not assume every comma-separated item is a numeric ID.
- The Search Form Builder transient is a display hint, not the authoritative term map. Actual query filtering still goes through the translation taxonomy tables.
- Element IDs remain numeric strings in shortcode output to match WordPress’s native shortcode attribute shape — do not cast to int when writing back.
Further Reading
- Elementor Widget Translation — the widget-level translation pipeline that depends on this remapping layer.
- Translation Linking (trid system) — how translations are glued together.
- Taxonomy Translation — term-level translation lookups used by the resolvers.
Product-level overview lives on the multi-language real estate website page.