WP Residence Help WP Residence Help

  • WpEstate
  • WPRESIDENCE
  • Video Tutorials
  • Client Support
  • API
Home / 30. WPResidence Translate Plugin / WP_Query Language Filtering — Developer Guide

WP_Query Language Filtering — Developer Guide

1 view 0

This article documents how the wpresidence-translate plugin (text domain wpr-translate) intercepts and rewrites WP_Query so every front-end query is language-aware. It covers the two main hooks (pre_get_posts and the_posts), the translations-table join, pin-query handling, taxonomy and meta adjustments, and the opt-out points. Broader context on the multilingual architecture is in the multi-language real estate website overview.

Files In Scope

File Role
includes/query-filter.php Bootstrap, pre_get_posts adjuster, the_posts rewriter, request language resolver.
includes/query-filter-helpers.php Helper functions for translating taxonomy term IDs/slugs and resolving translated post IDs.

Bootstrap

wpr_translate_bootstrap_query_filters() registers the two core hooks with an idempotent static flag:

add_action( 'pre_get_posts', 'wpr_translate_filter_frontend_query', 9 );
add_filter( 'the_posts',      'wpr_translate_filter_posts_results',   10, 2 );

Priority 9 on pre_get_posts ensures adjustments happen before theme and plugin code running at priority 10.

Request Language Resolution

wpr_translate_get_request_language_code() returns a normalized language code using this fallback chain:

  1. Explicit AJAX $_REQUEST['lang'] resolved via wpr_translate_get_language().
  2. $GLOBALS['wpr_translate_current_language'] set by the language router.
  3. wpestate_get_current_language() legacy theme helper.
  4. AJAX HTTP referrer first path segment.
  5. AJAX context post ID via wpr_translate_get_post_language().
  6. wpr_translate_resolve_preferred_language() (cookie / browser).
  7. Active-languages default via wpr_translation_get_default_language_code().

pre_get_posts Adjuster

wpr_translate_filter_frontend_query( $query ) short-circuits for:

  • is_admin() && ! wp_doing_ajax(),
  • wp_doing_cron(),
  • REST_REQUEST,
  • skipped post types: elementor_library, nav_menu_item,
  • queries with suppress_filters = true.

After the short-circuit gate it runs three transforms:

wpr_translate_adjust_query_post_arguments( $query, $language );
$meta_adjusted = wpr_translate_adjust_query_meta( $query, $language );
if ( ! $meta_adjusted ) {
    wpr_translate_adjust_query_taxonomies( $query, $language );
}

The meta branch runs first because swapping meta values shifts the query to source-language rows; taxonomy translation is then applied at the result phase via the_posts. When meta adjustment did not happen, taxonomy term IDs/slugs are translated up-front via wpr_translate_translate_taxonomy_terms().

Translations Table Join

For every public post type in the query, the plugin builds the set of element types via wpr_translation_get_element_type( $post_type ) and then queries {$wpdb->prefix}wpestate_translation_translations directly:

-- Non-default language: whitelist
SELECT element_id FROM {prefix}wpestate_translation_translations
WHERE element_type IN (%s, %s, ...) AND language_code = %s;

-- Default language: blacklist (exclude non-default records)
SELECT element_id FROM {prefix}wpestate_translation_translations
WHERE element_type IN (%s, %s, ...) AND language_code != %s;

The result is applied as post__in (non-default) or post__not_in (default) and intersected/merged with any existing theme filters. When the whitelist is empty but translations exist for these element types, the plugin forces an empty result set via post__in = [0] to avoid leaking untranslated content. When no translations exist at all for the element types, the filter stands down so the site keeps rendering during a gradual rollout.

Screenshot: Query Monitor panel showing the translations table SELECT and the resulting post__in clause injected into a property archive WP_Query

Singular Request Exemption

If p, page_id, pagename, or name resolves to a post whose language equals the request language (via wpr_translate_resolve_translated_post_id()), the filter sets wpr_translate_adjust_results = true and returns without restricting post__in. This ensures a direct hit on an untranslated URL still renders.

Taxonomy & Meta Adjustments

  • wpr_translate_translate_taxonomy_terms( $terms, $taxonomy, $field, $language ) — handles term_id and slug fields. Resolves canonical via wpr_translate_resolve_original_term_id(), strict-maps via wpr_translate_lookup_translated_term_id_strict(). Drops unresolved mappings in non-default languages.
  • Meta adjustments map post IDs stored inside meta values (e.g. relationship fields) to their default-language equivalents so the SQL matches source-language rows.

the_posts Rewriter

wpr_translate_filter_posts_results( $posts, $query ) runs on every query and:

  1. Detects pin queries: fields = ids AND post_type includes estate_property. These are processed even without the wpr_translate_adjust_results sentinel, because map pin requests often come from AJAX handlers that bypass the pre_get_posts filter.
  2. For ID-only result sets, every ID is mapped to its translated counterpart via wpr_translate_resolve_translated_post_id(), then filtered by stored post language using wpr_translation_resolve_post_language_code(). Unmapped posts survive only in the default language.
  3. For full WP_Post result sets, each post object is swapped for its translated counterpart when available. Duplicates are removed by post ID, and found_posts, post_count, and max_num_pages are realigned when all posts were loaded (posts_per_page = -1 or found ≤ requested).

Translated Post Resolver

wpr_translate_resolve_translated_post_id( $post_id, $language ) uses a per-request static cache keyed post_id:language:

  1. Loads the translation group for the post’s element type.
  2. Returns the exact-language translation if present and available (wpr_translation_is_translation_post_available()).
  3. Falls back through source language and then returns the original ID so the caller never gets zero.

Screenshot: Code view showing the post-id cache, group lookup, and fallback chain in wpr_translate_resolve_translated_post_id

Opting Queries Out

Three safe opt-outs:

  • suppress_filters => true on WP_Query / get_posts(). Makes the pre_get_posts adjuster return early. Use for backend maintenance jobs.
  • Skipped post types — adding a post type to the early bail-out list in wpr_translate_filter_frontend_query() (or registering it privately with a non-public status) removes it from filtering.
  • Context checks — REST, cron, and admin-non-AJAX are already exempt.

Avoid setting wpr_translate_adjust_results manually — it is a sentinel flag and its semantics are implementation-internal.

Performance Notes

  • The translations-table lookup uses prefixed prepared statements with an IN clause on element_type; indexes (element_id, element_type) and (language_code) cover the hot paths.
  • The helper cache in wpr_translate_resolve_translated_post_id() collapses repeated lookups during loops; combined with wp_cache_get-style results from the translation group loader, most pages hit only one or two SELECTs.
  • For very large property sets, consider priming the translation group cache via a warm-up function before a map or pin AJAX call.

Pin Query Details

Pin queries (map markers) use fields = ids and skip the standard wpr_translate_adjust_results sentinel path. The the_posts hook detects them separately and applies two transforms: ID translation and language filtering. Default-language requests keep unmapped IDs (so legacy listings still render); non-default requests drop any ID whose stored language does not match the target.

Gotchas

  • Do not mutate post__in after this hook without merging with the existing list. Code running later in pre_get_posts can accidentally undo language restrictions.
  • AJAX language detection is fragile — send lang explicitly in your AJAX payload for deterministic behavior.
  • REST is exempt by design. If you need a language-aware REST endpoint, call wpr_translate_resolve_translated_post_id() inside your endpoint handler.
  • Elementor templates and nav menu items are intentionally skipped to avoid breaking the template library and menu sync subsystem.
  • Queries with suppress_filters will see raw cross-language content. This is by design — use it for migrations, exports, and anything that must see every language at once.

Related Reading

  • Taxonomy Translation — get_terms filter that complements the post filter.
  • Translation Linking (trid system) — the TRID mapping consumed by the resolver.
  • Meta Sync Across Language Variants — why meta-based filters return consistent result sets.
  • URL Structure & Permalinks — where the request language code originates.

For the product picture, see the multi-language real estate website landing page.

30. WPResidence Translate Plugin

Related Articles

  • String Scanner — Developer Guide
  • The String Scanner
  • Gettext Pipeline & MO Files — Developer Guide
  • Gettext & MO Files — Making Translations Appear on the Front End

WP Residence Documentation

  • 01. Getting Started
    • How to Get Support
    • Get your buyer license code.
    • Use SSL / https
    • Server / Theme Requirements
  • 02. Installation & Setup
  • 03. Installation FAQ
  • 06. Search & Filtering
    • Advanced Search Display Settings
    • Advanced Search Form
    • Geolocation Search for Half Map
    • Save Search Theme Options
    • Advanced Search Colors
  • 09. Agent, Agency & Developers
  • 08. Property Pages & Layouts
  • 07. Property Lists, Categories & Archive
  • 13. WPResidence Elementor Studio
  • 10. Blog Posts & Blog List
  • 11. Shortcodes
    • Contact Form
    • Featured Agency/Developer
    • Membership Packages
    • Testimonials
    • Google Map with Property Marker
    • Listings per Agent, Agency or Developer
    • Display Categories
    • Agent List
    • Recent Items Slider
    • Recent items
    • List Properties or Articles by ID
    • Featured Agent
    • Featured Article
    • Featured Property
    • Login & Register Form
    • Icon Content Box Shortcode
  • 12. Widgets
  • 04. Theme Options & Global Settings
    • General Settings
    • User Types Settings
    • Appearance
    • Logos & Favicon
    • Header
    • Footer Style and Colors
    • Price & Currency
    • Property Custom Fields
    • Features & Amenities
    • Listing Labels
    • Theme Slider
    • Permalinks
    • Splash Page
    • Social & Contact
    • Map Settings
    • Pin Management
    • How read from file works
    • General Design Settings
    • Custom Colors Settings
    • Header Design & Colors
    • Mobile Menu Colors
    • User Dashboard Colors
    • Print PDF Design
    • Property, Agent, Blog Lists Design Settings
    • Sidebar Widget Design
    • Font management
    • How to add custom CSS
    • Custom Property Card Unit – Beta version
    • Email Management
    • Import & Export theme options
    • reCaptcha settings
    • YELP API Integration
    • iHomefinder Optima Express IDX
    • MEMBERSHIP & PAYMENT Settings
    • Property Submission Page
    • PayPal Setup
    • Stripe Setup
    • Wire Transfer Payment Method
  • 20. Translations & Languages
  • 26. FAQ
  • 10. Pages
  • 11. Header
  • 12. Footer
  • 05. Maps & Location Settings
  • 18. Payments & Monetization
  • Plugins
    • 19. Included Plugins
    • 22. Third Party Plugins – IDX Compatibility
    • 21. Third-Party Plugins – Multi-Language
    • 23. Third party Plugins – Other
  • Technical
    • 24. Technical how to | Custom Code Required
    • 25. Technical: Child Theme

Join Us On

Powered by WP Estate - All Rights Reserved
  • WpEstate
  • WPRESIDENCE
  • Video Tutorials
  • Client Support
  • API