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_Queryshould include the active language code (or slug) in the cache key. Use thewpestate_set_transient_name_multilang()helper, or derive the code viawpr_translate_get_current_language(). suppress_filtersbypasses language filtering. Callers that intentionally want cross-language results should set'suppress_filters' => trueor opt out via the plugin’s documented query vars. Never do this silently from inside a cached helper.- Avoid
meta_keyat 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 (seetheme-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_cachewith thewpr_translate_purge_cachenonce, or callwpr_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_postsinteracts 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.