WP Residence Help WP Residence Help

  • WPRESIDENCE
  • Video Tutorials
  • Client Support
  • API
Home / WPResidence Translate Plugin / Meta Sync Across Language Variants — Developer Guide

Meta Sync Across Language Variants — Developer Guide

44 views 0

This article covers the meta synchronization pipeline in the wpresidence-translate plugin (text domain wpr-translate): how rules are loaded, how they are normalized, which hooks drive the synchronization, and what safeguards prevent infinite loops. It is the companion to the user-facing Custom Field Rules article and feeds into the wider multi-language real estate website architecture.

Files Involved

File Role
includes/meta-sync.php Runtime synchronization on save_post and meta change hooks.
includes/custom-field-rules.php Rule loader, override storage, reload orchestration, state reporting.
includes/custom-field-rules-storage.php Normalization, grouping, and legacy rule conversion.
includes/custom-fields-sync.php JSON config path resolution, hash tracking, preferences cache.
includes/admin/views/settings-custom-fields.php Admin UI view rendering the Custom Field Rules page.

Behavior Keywords

Four keywords are accepted throughout the subsystem. They are normalized in wpr_translate_normalize_meta_field_behavior_value():

copy // propagate on every save_post of any sibling copy-once // propagate only when a translation is first created translate // never propagated; language-specific value expected ignore // never touched

The legacy alias copy_once is rewritten to copy-once. Unknown values are dropped.

Rule Source of Truth

Defaults come from a JSON file in the active theme:

get_stylesheet_directory() . '/wpr/custom-fields-config.json' // falls back to get_template_directory() . '/wpr/custom-fields-config.json'

The resolver is wpr_translate_get_custom_fields_config_path() in custom-fields-sync.php. The JSON is read by wpr_translate_read_custom_field_rules_file() in custom-field-rules.php, which accepts either a top-level array or an object with a custom_fields key. Each rule can carry:

{ "id": "property_price", "label": "Price", "description": "Property asking price", "action": "copy", "post": "estate_property" }

The newer value key is accepted as an alias for action.

Normalization & Grouping

wpr_translate_normalize_custom_field_rules() sanitizes every entry, generates a unique uid per post type via wpr_translate_build_unique_rule_id(), and defaults the action to copy when missing. wpr_translate_group_custom_field_rules_by_post() groups rules by post type and pushes estate_property to the top of the result array so the admin UI renders Property fields first.

Options Used

Option Contents
wpr_cf_rules_defaults Normalized default rules + file hash, path, and last-loaded timestamp.
wpr_cf_rule_overrides Admin-changed behaviors keyed by rule uid.
wpr_cf_file_state Status/error payload for the JSON loader surfaced on the admin page.
wpr_cf_preferences Secondary cache maintained by custom-fields-sync.php (hash + raw rules).

Overrides are merged on top of defaults in wpr_translate_get_custom_field_rules(). Each rule in the merged set carries a source of file or override, and the original file_action is preserved for UI display. Screenshot: The Custom Field Rules status panel showing rules file path, current hash, last-loaded hash, status and last reloaded timestamp

Runtime Hooks

Two callbacks drive synchronization, both in meta-sync.php:

Hook Callback Purpose
save_post wpr_translate_handle_save_post_meta_sync() Full-sweep sync of every copy meta key when any post in the translation set is saved.
updated_postmeta / added_postmeta / deleted_postmeta wpr_translate_handle_post_meta_change_sync() Single-key sync for code paths that write meta directly without calling wp_update_post().

Both callbacks short-circuit on autosaves, revisions, auto-draft posts, and non-existent post types. They also respect the opt-out filter:

apply_filters( 'wpr_translate_should_sync_post_meta', true, $post_id, $post_type, $post );

Sibling Resolution

Translation siblings are resolved by wpr_translate_resolve_meta_sync_translation_post_ids() using a two-tier strategy:

  1. Meta-first: reads wpr_translated_original_post_id from the current post to find the canonical anchor, then runs a get_posts() query with suppress_filters = true for every post pointing at the same anchor.
  2. TRID fallback: if meta linkage yielded only the current post, the resolver calls wpr_translation_get_translation_group() with the element type from wpr_translation_get_element_type( $post_type ).

The suppress_filters flag is critical. The sibling lookup must not be language-filtered by query-filter.php, or it would never see posts in other languages.

Full-Sweep Sync: wpr_sync_meta_fields()

Called from the save_post entry point. Pseudocode:

$behaviors = wpr_translate_get_meta_field_behaviors(); $copy_keys = keys where behavior === 'copy'; $siblings = wpr_translate_resolve_meta_sync_translation_post_ids( $post_id, $post_type );

foreach ( $copy_keys as $meta_key ) {
$source_values = get_post_meta( $source_post_id, $meta_key, false );
foreach ( $siblings as $target ) {
if ( serialized($source) !== serialized($target_current) ) {
wpr_translate_meta_sync_enter_internal_write();
delete_post_meta( $target, $meta_key );
foreach ( $source_values as $v ) add_post_meta( $target, $meta_key, $v );
wpr_translate_meta_sync_exit_internal_write();
}
}
}

Values are compared through maybe_serialize( maybe_unserialize( $v ) ) to avoid redundant rewrites when the shape changes but the semantic value does not.

Re-Entry Guards

  • Per-request static guard keyed by post_type:post_id inside wpr_sync_meta_fields(). Prevents a sibling save from recursing back to the source.
  • Internal-write depth counter via wpr_translate_meta_sync_enter_internal_write() / exit_internal_write(). The single-key handler checks wpr_translate_meta_sync_is_internal_write() and bails out when the change event was triggered by our own write.
  • Availability check via wpr_translation_is_translation_post_available() – trashed or unpublished translations are skipped.

Extension Points

  • apply_filters( ‘wpr_translate_should_sync_post_meta’, $should, $post_id, $post_type, $post ) – veto sync per post or post type.
  • apply_filters( ‘wpr_translate_meta_field_behaviors’, $map ) – final chance to mutate the behavior map (add/remove keys) before sync runs.
  • apply_filters( ‘wpr_translate_custom_field_rules_file’, $path ) – override the JSON defaults file path, for example for a plugin shipping its own defaults.

Admin Page Wiring

The view at includes/admin/views/settings-custom-fields.php receives $data[‘custom_field_rules’] (the merged map) and $data[‘custom_field_state’] (reload status). The reload button posts back with wpr_cf_action=reload and nonce action wpr_cf_reload; the handler calls wpr_translate_reload_custom_field_rules_from_file() which resets wpr_cf_rule_overrides, persists new defaults, and updates wpr_cf_file_state.

AJAX dropdown changes are persisted via wpr_translate_save_custom_field_overrides(), which sanitizes keys through sanitize_text_field() (UTF-8 safe) and validates actions against the copy|translate|copy-once|ignore allowlist.

Legacy Import

wpr_translate_convert_legacy_custom_fields_to_rules() in custom-field-rules-storage.php accepts the WPResidence wp_estate_custom_fields option format (indexed arrays with [0]=name, [1]=label, [2]=type, [4]=choices) and emits normalized rules. This is merged in on reload so older installations keep expected defaults for their theme-defined custom fields.

Related Reading

  • Translation Linking (trid system) – background on how sibling posts are glued together.
  • WP_Query Language Filtering – explains why sibling queries must use suppress_filters.
  • Database Schema – the translation table consulted by the TRID fallback path.

For product context, visit 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