This project builds an ability that takes a post ID and a target language, generates a translated draft with the AI Client SDK, and links the new draft back to its original through post meta, so an editor reviewing translations can always trace one back to the other.
Course 5 for wp_ai_client_prompt(). A published post to translate, and clarity that
this project builds a standalone translation workflow, not an integration with a
specific existing i18n plugin.
Step 1: Register the ability
Input is a post ID and a target language. Output is the new draft’s ID, same as the Blog Post Generator project, this ability never publishes anything either.
// File: translation-workflow/translation-workflow.php
add_action( 'wp_abilities_api_init', function () {
wp_register_ability(
'translation-workflow/translate-post',
array(
'label' => __( 'Translate post', 'translation-workflow' ),
'description' => __( 'Generates a translated draft of a published post in a target language, linked to the original for review. Always creates a draft, never publishes.', 'translation-workflow' ),
'category' => 'content',
'input_schema' => array(
'type' => 'object',
'properties' => array(
'post_id' => array( 'type' => 'integer' ),
'target_language' => array( 'type' => 'string' ),
),
'required' => array( 'post_id', 'target_language' ),
),
'output_schema' => array(
'type' => 'object',
'properties' => array(
'translated_post_id' => array( 'type' => 'integer' ),
'edit_url' => array( 'type' => 'string' ),
),
),
'permission_callback' => function ( $input ) {
return current_user_can( 'edit_post', $input['post_id'] );
},
'execute_callback' => 'translation_workflow_translate',
'meta' => array(
'annotations' => array( 'destructive' => false ),
'mcp' => array( 'public' => true ),
),
)
);
} );
Step 2: Translate, then insert as a linked draft
// File: translation-workflow/translation-workflow.php
function translation_workflow_translate( $input ) {
$source = get_post( $input['post_id'] );
if ( ! $source ) {
return new WP_Error( 'not_found', 'Post not found.' );
}
$target_language = sanitize_text_field( $input['target_language'] );
$prompt = <<<PROMPT
Translate the following blog post into {$target_language}. Preserve the meaning,
tone, and paragraph structure. Do not summarize or shorten it. Return the translated
title on the first line prefixed with "Title: ", then the translated body after a
blank line.
Title: {$source->post_title}
Content: {$source->post_content}
PROMPT;
$response = wp_ai_client_prompt( $prompt )
->using_model_preference( 'anthropic/claude', 'openai/gpt', 'google/gemini' )
->generate_text();
if ( empty( $response ) ) {
return new WP_Error( 'empty_response', 'Translation failed, no draft was created.' );
}
list( $title, $body ) = translation_workflow_split_title_body( $response, $source->post_title );
$translated_id = wp_insert_post( array(
'post_title' => sanitize_text_field( $title ),
'post_content' => wp_kses_post( $body ),
'post_status' => 'draft', // Machine translation always needs human review before publishing.
'post_type' => $source->post_type,
), true );
if ( is_wp_error( $translated_id ) ) {
return $translated_id;
}
// Link both directions so either post can be traced to the other.
update_post_meta( $translated_id, '_translation_of', $source->ID );
update_post_meta( $translated_id, '_translation_language', $target_language );
update_post_meta( $source->ID, '_translation_' . sanitize_key( $target_language ), $translated_id );
return array(
'translated_post_id' => $translated_id,
'edit_url' => get_edit_post_link( $translated_id, 'raw' ),
);
}
function translation_workflow_split_title_body( $response, $fallback_title ) {
if ( preg_match( '/^Title:\s*(.+)$/mi', $response, $m ) ) {
return array( trim( $m[1] ), trim( str_replace( $m[0], '', $response ) ) );
}
return array( $fallback_title, $response );
}
_translation_of and _translation_language on the new post, plus a
language-specific key back on the source, means a query for “does post 42 already have
a French translation” is a single get_post_meta() call in either direction.
Step 3: Where this fits next to real i18n plugins
WordPress’s established multilingual ecosystem, plugins like WPML and Polylang, solve
problems this simple meta-based linking doesn’t: language switchers, translated
permalinks, taxonomy translation, and syncing menus or widgets across languages. If
your site already runs one of those, this ability’s job is narrower and complementary,
it generates the first-draft translation text, and the actual cross-language post
relationship your i18n plugin manages should be set up through that plugin’s own
mechanism rather than duplicated here. The _translation_of meta key in this project
is a stand-in, minimal enough to work standalone, but check your specific i18n
plugin’s documentation for its real linking mechanism before treating this meta key as
a substitute for it in a production multilingual site.
Test it
Translate a real post and confirm the link is set in both directions:
wp-env run cli wp eval '
$result = wp_get_ability( "translation-workflow/translate-post" )->execute( array(
"post_id" => 42,
"target_language" => "Spanish",
) );
print_r( $result );
'
wp-env run cli wp post meta get 42 _translation_spanish
Confirm the second command returns the same post ID as translated_post_id from the
first, and open the edit_url to read the translated draft for fluency and accuracy.
Even a strong model can mistranslate idioms, get formal/informal register wrong, or
mishandle terms that shouldn’t be translated at all (product names, code samples,
proper nouns). Because this ability always creates a draft, that review step is
structurally required before anything reaches a public, non-English (or non-source-
language) audience, don’t build a follow-up automation that auto-publishes these
drafts without a real review step in between.
Recap
This ability translates a post’s title and body with wp_ai_client_prompt(), inserts
the result as a linked draft using wp_insert_post(), and stores the relationship in
post meta in both directions. It’s a starting point for a translation review queue,
not a replacement for the taxonomy, routing, and UI features a dedicated i18n plugin
provides, use both together on a real multilingual site.
Resources & further reading
- PHP AI Client SDK repository, GitHub
- wp_insert_post() function reference, developer.wordpress.org
- WPML: WordPress Multilingual Plugin, wpml.org