WP Residence Help WP Residence Help

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

WP_Query Language Filtering — Developer Guide

42 views 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.

 

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.

 

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.

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

Help Categories

  • 18Agent, Agency & Developers
  • 5Blog Posts & Blog Lists
  • 38Elementor Shortcodes Built-In
  • 56FAQ
  • 15Footer
  • 5Getting Started
  • 37Header
  • 2IDX & MLSImport
  • 6Installation & Setup
  • 22Installation FAQ
  • 23Maps & Location Settings
  • 21Multi-Language Third Party Plugins
  • 6Other Third party Plugins
  • 19Pages
  • 4Payments & Monetization
  • 20Property Lists, Categories & Archive
  • 36Property Pages & Layouts
  • 31Search & Filtering
  • 163Technical how to | Custom Code Required
  • 8Technical: Actions and filters
  • 6Technical: Child Theme
  • 86Theme Options & Global Settings
  • 6Translations & Languages
  • 16WPBakery Shortcodes
  • 51WPResidence / WPEstate CRM
  • 50WPResidence 5.0 Documentation
  • 8WPResidence Elementor Studio
  • 50WPResidence Translate Plugin

Join Us On

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