This article documents the provider-layer of the wpresidence-translate plugin: the admin view, the settings option, the four provider files, the OpenAI request shape, glossary/memory storage, and the AJAX endpoints that drive bulk runs. It complements the user article and the broader multi-language real estate website overview.
Files Involved
| File | Role |
|---|---|
| includes/admin/views/automatic-translation.php | Settings form + bulk-run UI (post-type selection, target language, start/stop). |
| includes/admin/views/glossary-memory.php | Glossary editor (source term, preferred translation, context). |
| includes/admin/auto-translate-openai.php | Implemented provider. Entry point wpr_translate_auto_translate_post_openai() and wpr_translate_openai_translate_json_payload(). |
| includes/admin/auto-translate-google.php | Stub. Returns WP_Error( ‘wpr_translate_google_not_implemented’ ). |
| includes/admin/auto-translate-deepl.php | Stub. Returns WP_Error( ‘wpr_translate_deepl_not_implemented’ ). |
| includes/admin/auto-translate-microsoft-azure.php | Stub. Returns WP_Error( ‘wpr_translate_azure_not_implemented’ ). |
Options & Storage
| Option / Table | Purpose |
|---|---|
| wpr_translate_auto_translation | Core settings. Keys: enabled, engine, openai_api_key, openai_system_prompt, openai_user_prompt, target_language. |
| wpr_translate_post_types | Per-post-type choice (yes/no) controlling bulk runs. |
| wpr_translate_glossary | Array of glossary entries: source, translation, context. |
| {prefix}wpestate_translation_glossary | Optional database-backed glossary used by the auto-translate layer. |
| {prefix}wpestate_translation_memory | Translation memory keyed by similarity_hash for reuse across jobs. |
Settings Form Defaults
The view applies these defaults via wp_parse_args():
'enabled' => false, 'engine' => 'openai', 'openai_api_key' => '', 'openai_system_prompt' => 'You are a translator that converts WordPress post data into the requested language.', 'openai_user_prompt' => 'Translate the following WordPress post payload into the %1$s language.',
The $engines whitelist currently contains only ‘openai’ => ‘OpenAI’. To expose a second provider as a selectable engine, extend this map in the view and wire up its provider file.
Provider Dispatch
Every provider function accepts the same signature, making dispatch trivial:
wpr_translate_auto_translate_post_openai( $post_id, $language, $payload = array(), $payload_json = '' ); wpr_translate_auto_translate_post_google( $post_id, $language, $payload = array(), $payload_json = '' ); wpr_translate_auto_translate_post_deepl( $post_id, $language, $payload = array(), $payload_json = '' ); wpr_translate_auto_translate_post_microsoft_azure( $post_id, $language, $payload = array(), $payload_json = '' );
Each returns true|WP_Error. Only the OpenAI implementation does real work today; the others are scaffolded and should be treated as placeholders.
OpenAI Request Shape
wpr_translate_openai_translate_json_payload( $language, $payload_json, $settings_context = ‘default’ ) builds:
- Endpoint: https://api.openai.com/v1/chat/completions
- Auth: Authorization: Bearer <openai_api_key>
- Body:
{ "model": "gpt-4o-mini", "response_format": { "type": "json_object" }, "messages": [ { "role": "system", "content": "<system_prompt> Keep the JSON structure identical. Respond with valid JSON only." }, { "role": "user", "content": "<user_prompt_template> Preserve the JSON structure and keys. Respond with JSON only.\n\n<payload_json>" } ] } - Timeout: 540 seconds.
- Retry: up to 3 attempts on WP_Error or non-2xx responses.
- User-Agent: WPR-Translate/<WPR_TRANSLATE_VERSION>; <home_url>
A non-OpenAI-key configuration returns WP_Error( ‘wpr_translate_missing_openai_key’ ). Malformed JSON bodies return wpr_translate_openai_invalid_json. Non-2xx responses surface body text via wpr_translate_openai_http_error.
Post Payload Preparation
wpr_translate_auto_translate_post_openai() builds a focused payload so only translatable fields are sent to the model:
array( 'post_id' => <int>, 'language' => <code>, 'post_type' => <sanitised key>, 'post' => array( 'post_title', 'post_excerpt', 'post_content' ), 'post_meta_fields' => array( ... translatable meta ... ), 'taxonomy' => array( ... terms prepared for LLM ... ), 'elementor_translate' => array( ... widget translation hints ... ), 'original_post_id' => <int>, );
Taxonomy terms are pre-processed via wpr_translate_prepare_taxonomy_payload_for_llm(). Elementor widget content (where present) is preserved in elementor_translate so widget-level text can be applied back after translation. Encoding uses JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES to keep non-Latin characters intact.
AJAX Endpoints
| Action | Callback | Purpose |
|---|---|---|
| wp_ajax_wpr_translate_auto_translate_strings | wpr_translate_admin_ajax_auto_translate_strings | Auto-translate untranslated rows on the String Translation page. |
| wp_ajax_wpr_translate_get_posts_for_auto_translation | wpr_translate_admin_ajax_get_posts_for_auto_translation | List posts awaiting bulk translation for the selected language + post types. |
| wp_ajax_wpr_translate_save_post_type_preferences | wpr_translate_admin_ajax_save_post_type_preferences | Persist the wpr_translate_post_types option from the UI radios. |
| wp_ajax_wpr_translate_auto_translate_post | wpr_translate_handle_auto_translate_post_ajax | Translate one post on demand from the editor header. |
| wp_ajax_wpr_translate_auto_translate_post_publish | wpr_translate_handle_auto_translate_post_publish_ajax | Auto-translate on publish. |
Glossary & Translation Memory
The glossary UI (views/glossary-memory.php) writes to the wpr_translate_glossary option as a list of { source, translation, context } entries. The option is registered through settings_fields( ‘wpr_translate_glossary’ ). The dedicated database tables {prefix}wpestate_translation_glossary and {prefix}wpestate_translation_memory (created at plugin activation) store larger glossary entries and memory hits indexed by similarity_hash.
Debug Logging
wpr_translate_openai_error_log() emits structured log entries for key lifecycle events:
- send_payload – payload JSON sent to the LLM.
- receive_payload_raw – raw response from the LLM.
- receive_payload_error – HTTP or decoding failure with error code and message.
Use these logs when a translation comes back malformed or a taxonomy mapping ends up under the wrong language code.
Extending With a New Provider
- Copy auto-translate-openai.php as auto-translate-myprovider.php.
- Replace the HTTP call with your provider’s endpoint and authentication scheme.
- Return the same shape (array|WP_Error) so the caller doesn’t need to change.
- Extend the $engines map in views/automatic-translation.php to expose the new engine.
- Add API-key / endpoint fields to the same view and include them in the sanitiser callback used with register_setting( ‘wpr_translate_auto_translation’, … ).
- Wire dispatch: where the plugin currently branches on $settings[‘engine’], add a case ‘myprovider’ mapping to your new function.
UTF-8 Safety
All payloads are JSON-encoded with JSON_UNESCAPED_UNICODE, and the response content is decoded without any sanitisation that would corrupt non-Latin text. Keep this invariant in custom providers – do not run translation output through sanitize_title() or sanitize_key().
Known Limitations
- Google/DeepL/Azure files are stubs – do not advertise them as working integrations.
- The model is hardcoded to gpt-4o-mini. If you need a different model, fork wpr_translate_openai_translate_json_payload() in a child-theme copy and filter the provider dispatch.
- OpenAI HTTP timeout is 540 seconds. Match your PHP max_execution_time accordingly for very large posts.
Further Reading
- Gettext Pipeline & MO Files – how translated strings reach the front end.
- Custom Field Rules – which meta keys reach the translation payload.
- Translation Linking (trid system) – how source and translated posts are paired.
For product positioning see the multi-language real estate website page.