This article maps the cache layers inside the WPResidence Translate plugin (wpestate-translate, text domain wpr-translate) so you can reason about invalidation, extend it safely from a child theme or companion plugin, and keep a multi-language real estate website fast at scale.
Cache Layers Overview
| Layer | Scope | API | Where |
|---|---|---|---|
| Request-scoped static | Single PHP request | static $cache arrays in functions | includes/translation-runtime.php |
| Object cache | Persistent if drop-in installed | wp_cache_get() / wp_cache_set() in group wpr_translate | includes/custom-fields-sync.php |
| Transients (per language) | Cross-request, expiring | set_transient() / delete_transient(), names suffixed per language | Theme search lists; includes/elementor-widget-compat.php; includes/admin/theme-transients.php |
| Theme cache-purge | Whole-site | admin-post.php?action=wpestate_purge_cache | includes/admin/cache-purge.php |
Per-Request Static Caches
Runtime string lookups are hot-path code. They run on every gettext filter for every translated string on every page. To keep the database touch low, translation-runtime.php memoizes results inside each request:
// includes/translation-runtime.php function wpr_translate_domain_has_runtime_translations( $domain, $language_code ) { static $cache = array(); $cache_key = strtolower( $domain ) . '|' . strtolower( $language_code ); if ( array_key_exists( $cache_key, $cache ) ) { return (bool) $cache[ $cache_key ]; } // ... wpdb query against wp_wpestate_translation_strings ... $cache[ $cache_key ] = $found; return $found; }
function wpr_translate_lookup_runtime_translation( $domain, $original, $language_code ) {
static $cache = array();
$cache_key = strtolower( $domain ) . '|' . md5( $original ) . '|' . strtolower( $language_code );
if ( array_key_exists( $cache_key, $cache ) ) {
return $cache[ $cache_key ];
}
// ... wpdb query ...
$cache[ $cache_key ] = $value;
return $value;
}
Key design points:
- Keys are lowercased and the original string is hashed with md5() so UTF-8 strings are safe in a static array.
- Empty results are cached too. This matters: negative lookups dominate in the admin-only runtime override path.
- There is no cross-request persistence here on purpose. Runtime string translation only applies in admin editor contexts (see wpr_translate_should_use_runtime_translations()), and each admin page load is short-lived.
If you add new lookup functions, follow the same pattern. Do not reach for wp_cache_set() here. These are request-scoped by design, and a persistent cache would make staleness harder to reason about.
Object Cache – Custom Field Preferences
The Custom Field Rules are loaded from a JSON configuration file and cached under the wpr_translate object cache group:
// includes/custom-fields-sync.php $option_name = 'wpr_cf_preferences'; $cache_key = 'wpr_cf_preferences'; $cache_group = 'wpr_translate'; wp_cache_set( $cache_key, $preferences, $cache_group ); $cached = wp_cache_get( $cache_key, $cache_group );
The priming flow (wpr_translate_prime_custom_fields_preferences_cache()) compares an md5 hash of the config file contents against the cached payload. If the file changed, the plugin re-reads the JSON, re-writes the wpr_cf_preferences option, and overwrites the cache entry.
Extending: If you ship a child plugin that writes to the same rules config, either call wpr_translate_sync_custom_fields_preferences() after your write, or invalidate directly with wp_cache_delete( ‘wpr_cf_preferences’, ‘wpr_translate’ ).
Per-Language Transients – Theme Search Lists
The WPResidence theme caches its large taxonomy lookup lists as transients. On a multilingual site each language has its own set. The helper wpestate_set_transient_name_multilang() (theme side) appends the active language to the transient name so callers never juggle suffixes manually.
The canonical cached keys (base names before language suffix):
wpestate_get_action_select_list wpestate_get_category_select_list wpestate_get_city_select_list wpestate_get_area_select_list wpestate_get_county_state_select_list wpestate_get_status_select_list wpestate_get_features_select_list
Invalidation is centralized in includes/admin/theme-transients.php:
// includes/admin/theme-transients.php function wpr_translate_admin_clear_theme_search_transients( $context ) { // Only act for the theme admin-strings domain (or the legacy 'wpresidence_admin'). // Build the list of language IDs (slug + code) from wpr_translate_get_active_languages(). foreach ( $keys as $key ) { delete_transient( $key ); // base, no language foreach ( $language_ids as $language_id ) { delete_transient( $key . '_' . $language_id ); // per-language variants } } }
This runs whenever admin-facing theme strings change, so translated dropdown labels stay in sync without manual purges. The function also honors ICL_LANGUAGE_CODE to cover sites migrating from WPML compatibility shims.
Elementor taxonomy widgets prime their own transients via the same multilang helper. See includes/elementor-widget-compat.php:
// includes/elementor-widget-compat.php $primed_transient_key = 'wpestate_elementor_tax_'; if ( function_exists( 'wpestate_set_transient_name_multilang' ) ) { $primed_transient_key = (string) wpestate_set_transient_name_multilang( $primed_transient_key ); } set_transient( $primed_transient_key, $primed_terms, 60 * 60 * 6 );
TTL is 6 hours. Invalidation is lazy. On the next language switcher render after expiry, the cache refills from the terms table.
Theme-Wide Cache Purge Endpoint
includes/admin/cache-purge.php exposes an AJAX bridge that triggers the WPResidence theme’s own purge action:
// includes/admin/cache-purge.php function wpr_translate_purge_wpestate_cache() { $purge_url = admin_url( 'admin-post.php' ); $nonce = wp_create_nonce( 'wpestate_purge_cache' ); // ... copies cookies for auth, POSTs to admin-post.php ... wp_remote_post( $purge_url, array( 'body' => array( 'action' => 'wpestate_purge_cache', '_wpnonce' => $nonce, ), // ... ) ); } add_action( 'wp_ajax_wpr_translate_purge_cache', 'wpr_translate_ajax_purge_cache' );
Called by the bulk auto-translate screen when a batch finishes. Gated by manage_options and a dedicated nonce (wpr_translate_purge_cache). Re-use this endpoint from your own admin flows after operations that touch many posts.
Query Filter & Cache Interaction
includes/query-filter.php applies the active language to WP_Query via pre_get_posts and the_posts. A few caching considerations follow from that:
- Cached query results must be language-scoped. Anything you cache that wraps a WP_Query should include the active language code (or slug) in the cache key. Use the wpestate_set_transient_name_multilang() helper, or derive the code via wpr_translate_get_current_language().
- suppress_filters bypasses language filtering. Callers that intentionally want cross-language results should set ‘suppress_filters’ => true or opt out via the plugin’s documented query vars. Never do this silently from inside a cached helper.
- Avoid meta_key at WP_Query top level when not needed. It forces an INNER JOIN that silently drops posts without that key. Not a cache rule per se, but combined with caching it can freeze bad results.
Settings-Errors Transients
Admin screens use short-lived (30-second) settings_errors transients to carry notices across a redirect:
// includes/admin/menu.php, includes/admin/string-actions.php, includes/admin/reset-settings.php set_transient( 'settings_errors', get_settings_errors(), 30 );
These are unrelated to content caching. They are the standard WordPress pattern for post-submit notice survival. No invalidation needed. They self-expire.
Extension Points & Recipes
- Adding a cached helper that depends on active language – key with wpestate_set_transient_name_multilang( ‘your_base_key’ ) so the plugin’s existing invalidators (see theme-transients.php) cover you when you pick compatible base names.
- Bypassing cache for diagnostics – clear the static caches by forcing a fresh PHP process (new request); they have no public reset. For the object cache, use wp_cache_delete( ‘wpr_cf_preferences’, ‘wpr_translate’ ).
- Clearing transients after bulk operations – call wpr_translate_admin_clear_theme_search_transients( ‘wpresidence_admin’ ). Pass the theme admin strings context to match the guard clause.
- Flushing the whole theme cache – fire the AJAX endpoint wp_ajax_wpr_translate_purge_cache with the wpr_translate_purge_cache nonce, or call wpr_translate_purge_wpestate_cache() directly from PHP with an authenticated admin context.
Non-Latin Safety
All cache keys avoid sanitize_title() on user-supplied strings. Language codes go through sanitize_key(), translated values are hashed with md5() before being used as key fragments. Cyrillic, Arabic, and CJK language data caches identically to Latin data. There is no separate slow path.
Gotchas
- Static caches do not clear on option save. If you write admin code that updates a translation mid-request and then reads it back, expect stale results in the same request. Re-query directly or reset the static in tests via a dedicated hook.
- Transient TTLs are not adaptive. The 6-hour TTL on Elementor primed terms means a term renamed through WP admin might take up to 6 hours to reflect in some widgets, unless the plugin’s save hooks clear the specific key. Clear explicitly after bulk term updates.
- The cache-purge endpoint is whole-site. Use it for post-batch jobs, not per-edit. You will hurt front-end performance if you call it on every save.
- Reactivation does not clear transients. Activation runs flush_rewrite_rules() only. Stale caches survive deactivate/activate. Delete them explicitly if you need a clean slate.
Further Reading
- Database Schema – the tables the caches front.
- Cache Purge & Reset Tools – the admin-side counterparts to this reference.
- WP_Query Language Filtering – how pre_get_posts interacts with cached results.
- Automatic Translation (OpenAI, Google, DeepL, Azure) – the flows that trigger wpr_translate_purge_wpestate_cache().
For broader context on running a multi-language real estate website on WPResidence, see the product page.