This article is the developer-facing companion to the post translation workflow. It documents the editor hooks, the AJAX handlers, the draft cloning path, and the meta/runtime layer that make the user-facing flow work. 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/translation-runtime.php | Resolves the active language for the current request. |
| includes/translation-hooks.php | Registers runtime filters (gettext, override_load_textdomain, etc.). |
| includes/admin/post-list-actions.php | Handles the Add Translation request and the draft cloning logic. |
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 editors fall back to 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 current language in this order:
- wpr_translate_return_current_language_admin() – admin selector state.
- wpr_translation_get_default_language_code() over wpestate_translation_get_active_languages() – site default.
- Legacy wpestate_default_language option.
- Hardcoded ‘en’ fallback.
It then calls wpr_translate_get_language( $code ), builds a flag URL from WPR_TRANSLATE_URL . ‘assets/img/flags/4×3/{code}.svg’, and hands the payload to the editor JS via:
wp_localize_script( 'wpr-translate-admin', 'wprTranslatePostLanguage', $payload );
Payload shape: label, code, flagUrl, flagAlt.
Resolving the Source Post
Auto-translate needs a source to translate from. wpr_translate_resolve_metabox_source_post_id( $post_id ) picks it in this order:
- Explicit post meta wpr_translated_original_post_id.
- Translation group row where original = 1 for the same trid (via wpr_translation_get_translation_group()).
- Group’s source_language if the original flag is not set.
If nothing resolves, the button renders disabled with the label Auto translation unavailable.
Auto Translate AJAX Endpoints
Two wp_ajax_* actions power the in-editor translation:
| 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 apply on publish. |
Both handlers:
- Validate the nonce wpr_translate_auto_translate_post.
- Enforce current_user_can( ‘edit_post’, $post_id ).
- Call do_action( ‘qm/cease’ ) to silence Query Monitor during heavy calls.
- Look up the engine from get_option( ‘wpr_translate_auto_translation’ ) – validated against openai, google_translate, microsoft_azure, deepl.
- Delegate to wpr_translate_auto_translate_post( $post_id, $language, $engine, $source_post_id ).
What wpr_translate_auto_translate_post Touches
Downstream, the translation payload is applied in:
- wpr_translate_apply_translated_post_data() – post_title, post_content, post_excerpt, post_name.
- wpr_translate_apply_translated_post_meta_fields() – per-meta-key based on the Custom Field Rules preferences.
- wpr_translate_apply_translated_taxonomies() – maps terms to translated equivalents via the translation group.
- wpr_translate_log_translated_elementor_data() and wpr_translate_log_translated_wpbakery_data() – structured builders.
Elementor and WPBakery fields are extracted before translation. For WPBakery, wpr_translate_extract_wpbakery_text_fields( $post_content ) walks shortcodes and pulls human-readable fields. A completed auto-translation is stamped with _wpr_translate_auto_translated_language meta on the target post to power the Redo the translation state.
Manual Translation – Draft Cloning
When an editor clicks the plus icon in the post list Language column, WordPress hits admin.php?action=wpr_add_translation&post_id=X&language=xx. That lands in wpr_translation_handle_add_translation_request(), which:
- Verifies the nonce produced by wpr_translation_get_translation_nonce_action( $post_id, $language ).
- Calls wpr_translation_create_translation_draft( $post_id, $language ).
- Redirects the user to get_edit_post_link( $new_post_id ).
wpr_translation_create_translation_draft() clones the source using wp_insert_post(), then:
- Copies the page template via wpr_translation_get_validated_page_template().
- Clones attachments via wpr_translation_clone_attachments().
- Clones taxonomies via wpr_translation_clone_taxonomies().
- Clones meta via wpr_translation_clone_meta_fields() in post-list-meta.php, which consults wpr_translation_get_cached_custom_field_preferences() for per-key rules.
- Writes the translation group row via wpr_translation_insert_translation_record().
Permissions & Nonces
- Add Translation action: capability edit_post on the source post; nonce wpr_add_translation_{post_id}_{language}.
- Auto Translate AJAX: capability edit_post; nonce wpr_translate_auto_translate_post.
- Admin menu pages all live behind manage_options.
Localized JS Settings
wpr_translate_enqueue_post_editor_assets() enqueues assets/js/admin.js + assets/css/admin.css and localizes:
wprTranslateAutoSettings = {
enabled: bool,
engine: 'openai' | 'google_translate' | 'microsoft_azure' | 'deepl',
ajax: { url, nonce },
strings: { error, success, processing },
};
Extension Points
- Action wpr_translation_created – fires from wpr_translation_insert_translation_record() after a new translation row is inserted. Arguments: $post_id, $element_type, $trid, $language_code, $source_language, $post_status, $original.
- Custom Field Rules – add a rule via the admin page to change whether a meta key is copied, translated, copied-once, or skipped. Rules are cached via wpr_translation_get_cached_custom_field_preferences().
- Auto-translate providers – implemented as self-contained files in includes/admin/auto-translate-*.php. To add a provider, replicate the pattern and register the engine key.
Gotchas
- The meta box only appears for posts where use_block_editor_for_post() returns true. In Classic editor sites, the button is rendered from post_submitbox_misc_actions.
- The publish-box button renders an Original post – no translation action message when the current post’s language equals the default. This is by design – you translate into a language.
- Heavy auto-translate requests call do_action( ‘qm/cease’ ) – if you rely on Query Monitor during translation debugging, disable Query Monitor or comment out that call in a child plugin.
- The asset handle wpr-translate-admin is registered lazily. Any code that piggy-backs via wp_localize_script() must hook at priority > 10.
Further Reading
- Post List Table Enhancements – the admin list columns, filters, and sortable language header.
- Translation Linking (trid system) – the shared identifier that binds all variants together.
- Meta Sync Across Language Variants – how wpr_translation_clone_meta_fields() applies per-key rules.
For the product-level overview of what this integration enables, read our guide to a multi-language real estate website.