This article is the developer-facing companion to the post translation workflow in WPEstate Translate 1.0.9. It documents the editor hooks, the AJAX handlers, the draft cloning path and the relationship layer that links a post to its translations. For product context see the user article and the multi-language real estate website landing page.
Files Involved
| File | Role |
|---|---|
| includes/admin/post-editor.php | Meta box, publish-box button, asset enqueue, auto-translate AJAX handlers. |
| includes/admin/editor-header-language.php | Localizes the block editor header language badge. |
| includes/admin/post-list-actions.php | Add Translation request and draft cloning. |
| includes/admin/post-list-meta.php | Meta cloning with Custom Field Rules. |
| includes/translation-relationships-posts.php | Post family read / link / resolve. |
| includes/translation-relationships-lifecycle.php | Delete and trash behaviour for linked posts. |
| includes/translation-runtime.php, includes/translation-hooks.php | Active language for the request and runtime filters. |
Editor Bootstrap
The post editor integration is wired up in two steps from post-editor.php:
function wpr_translate_bootstrap_post_editor_integration() {
add_action( 'load-post.php', 'wpr_translate_initialize_post_editor_hooks' );
add_action( 'load-post-new.php', 'wpr_translate_initialize_post_editor_hooks' );
add_action( 'wp_ajax_wpr_translate_auto_translate_post',
'wpr_translate_handle_auto_translate_post_ajax' );
add_action( 'wp_ajax_wpr_translate_auto_translate_post_publish',
'wpr_translate_handle_auto_translate_post_publish_ajax' );
}
On editor screens, wpr_translate_initialize_post_editor_hooks() then registers:
add_action( 'post_submitbox_misc_actions', 'wpr_translate_render_post_auto_translation_button' ); add_action( 'add_meta_boxes', 'wpr_translate_register_auto_translate_metabox', 10, 2 ); add_action( 'admin_enqueue_scripts', 'wpr_translate_enqueue_post_editor_assets' );
The meta box wpr-translate-auto-translate is only registered when use_block_editor_for_post() returns true. Classic editor screens use the publish-box section instead.
Editor Header Language Badge
The badge comes from wpr_translate_enqueue_post_editor_language_badge_assets(), hooked to admin_enqueue_scripts at priority 20 so it runs after wpr-translate-admin is registered. It resolves the language in this order:
wpr_translate_return_current_language_admin()– admin selector state.wpr_translation_get_default_language_code()overwpestate_translation_get_active_languages()– site default.- Legacy
wpestate_default_languageoption. - Hardcoded
'en'.
It then calls wpr_translate_get_language( $code ), builds a flag URL from WPR_TRANSLATE_URL . 'assets/img/flags/4x3/{code}.svg' and passes the payload to the editor JS:
wp_localize_script( 'wpr-translate-admin', 'wprTranslatePostLanguage', $payload );
Payload shape: label, code, flagUrl, flagAlt.
How Posts Are Linked
Post relationships are post meta, read through wpr_translate_relationship_get_post_family( $post_id ), which returns canonical_id, source_language and members (language code => post ID), or WP_Error for inconsistent meta. Reads are cached per request.
| Post meta | Stored on | Value |
|---|---|---|
| _wpr_language | Every member | Language code. |
| _wpr_translations | Source post | Map language code => post ID. |
| wpr_translated_original_post_id | Translated post | Source post ID. |
| wpr_translated_language | Translated post | Language code (must agree with _wpr_language). |
Posts without this meta fall back to wpr_translate_relationship_get_legacy_post_family(), which reads the wpestate_translation_translations table (content from older versions). Use wpr_translate_relationship_link_post( $source_id, $source_language, $member_id, $member_language ) to link and wpr_translate_relationship_resolve_post( $post_id, $language, $strict, $availability ) to find a translation.
Resolving the Source Post
wpr_translate_resolve_metabox_source_post_id( $post_id ) returns the family’s canonical_id when it differs from the current post, otherwise 0. If nothing resolves, the button renders disabled with the label Auto translation unavailable.
Auto Translate AJAX Endpoints
| Action | Handler | Purpose |
|---|---|---|
| wpr_translate_auto_translate_post | wpr_translate_handle_auto_translate_post_ajax() | Translate fields inside the editor (manual button). |
| wpr_translate_auto_translate_post_publish | wpr_translate_handle_auto_translate_post_publish_ajax() | Translate and publish. |
Both handlers:
- Validate the nonce
wpr_translate_auto_translate_post(fieldnonce). - Require
current_user_can( 'edit_post', $post_id )on the target and onsource_post_id; a missing source returns an error. - Call
do_action( 'qm/cease' )when Query Monitor is active. - Read the engine from
get_option( 'wpr_translate_auto_translation' )['engine'], validated againstopenai,google_translate,microsoft_azure,deepl(fallbackopenai). - Delegate to
wpr_translate_auto_translate_post( $post_id, $language, $engine, $source_post_id ).
What wpr_translate_auto_translate_post Touches
wpr_translate_apply_translated_post_data()–post_title,post_content,post_excerpt,post_name.wpr_translate_apply_translated_post_meta_fields()– per meta key, following Custom Field Rules.wpr_translate_apply_translated_taxonomies()– maps terms to their translations.wpr_translate_log_translated_elementor_data()andwpr_translate_log_translated_wpbakery_data()– page builder data.
WPBakery text is extracted with wpr_translate_extract_wpbakery_text_fields( $post_content ). A finished run stores _wpr_translate_auto_translated_language on the target post, which switches the button to Redo the translation.
Manual Translation – Draft Cloning
The plus icon in the post list Language column calls admin.php?action=wpr_add_translation&post_id=X&language=xx. wpr_translation_handle_add_translation_request():
- Checks the nonce from
wpr_translation_get_translation_nonce_action( $post_id, $language )(wpr_add_translation_{post_id}_{language}). - Calls
wpr_translation_create_translation_draft( $post_id, $language ). - Redirects to
get_edit_post_link( $new_post_id, 'raw' ).
wpr_translation_create_translation_draft() requires edit_post, reads the family and clones from the family source (or the default-language member):
| Situation | Result |
|---|---|
| An available translation already exists in that language | It is refreshed from the source (content, template, meta, attachments, terms) and returned. |
| The language slot is held by a trashed translation | WP_Error( 'wpr_translate_translation_trashed' ) – restore or permanently delete it first. |
| No translation yet | New draft via wp_insert_post(). |
Both paths then run wpr_translation_get_validated_page_template(), wpr_translation_clone_meta_fields(), wpr_translation_clone_attachments(), wpr_translation_clone_taxonomies() and link the pair with wpr_translate_relationship_link_post(). The legacy wpr_translation_insert_translation_record() is no longer called by this flow. See Meta Sync developer guide for which meta keys are skipped when cloning.
Deleting and Trashing
pre_delete_post(wpr_translate_relationship_before_delete_post()) protects a source post while it still has translations. During Empty Trash, when the source and all its translations are in the trash, the translations are deleted first and the source follows.- “Delete all <language> translations” also finds trashed members.
- Cloned attachments share the source file. On
delete_attachment,wpr_translate_keep_shared_attachment_file()keeps the file on disk while another attachment still uses it.
Permissions & Nonces
- Add Translation:
edit_poston the source (and on an existing translation it refreshes); noncewpr_add_translation_{post_id}_{language}. - Auto Translate AJAX:
edit_poston target and source; noncewpr_translate_auto_translate_post. - Admin menu pages require
manage_options.
Localized JS Settings
wpr_translate_enqueue_post_editor_assets() enqueues assets/js/admin.js and assets/css/admin.css and localizes:
wprTranslateAutoSettings = {
enabled: bool,
engine: 'openai' | 'google_translate' | 'microsoft_azure' | 'deepl',
ajax: { url, nonce },
strings: { ... },
};
Extension Points
- Relationship API – read with
wpr_translate_relationship_get_post_family(), link withwpr_translate_relationship_link_post(). Do not write_wpr_translationsby hand. - Action
wpr_translation_created– still fired bywpr_translation_insert_translation_record(), but that function is no longer called by the Add Translation flow. Do not rely on it for new translations. - Custom Field Rules – change whether a meta key is copied, translated, copied once or skipped.
- Auto-translate providers – self-contained files in includes/admin/auto-translate-*.php.
Gotchas
- The meta box only appears when
use_block_editor_for_post()is true; Classic editor sites get the button frompost_submitbox_misc_actions. - On the source post the publish box lists Add {language} translation links for missing languages; when none are missing it shows Original post -no translation action. Auto-translate runs from the translation, not the source.
- Inconsistent relationship meta makes the family read return
WP_Error; the editor then treats the post as having no source. - The asset handle
wpr-translate-adminis registered lazily. Code that useswp_localize_script()on it must hook at priority > 10.
Further Reading
- Post List Table Enhancements – admin list columns, filters and the language header.
- Translation Linking (trid system) – how variants are linked.
- Meta Sync Across Language Variants – how
wpr_translation_clone_meta_fields()applies per-key rules.
For the product-level overview, read our guide to a multi-language real estate website.