Skip to content
New site — Virtual Media Folders now has its own home. Visit vmf.soderlind.no →

AI Translator for Polylang

Click Polylang’s Add a translation (+) button and get a linked draft that the WordPress AI Client translates in the background.

  • WordPress 7.0 or later (includes the WordPress AI Client, wp_ai_client_prompt())
  • PHP 8.3 or later
  • Polylang
  • An AI provider configured for the WordPress AI Client
  1. In a post list, click the + next to a language that has no translation yet.
  2. The plugin creates a draft in that language, linked to the source post. It copies what Polylang’s own “Add a translation” copies: menu order, comment and ping status, translated parent, sticky state, and the taxonomies and custom fields Polylang syncs.
  3. A WP-Cron job sends the title, excerpt and content to the AI and saves the translation into the draft.
  4. While the job runs, the post list shows Translating to {language}… and the confirmation notice updates automatically. The editor warns you not to edit or publish the draft while translation is running.
  5. When the job finishes, the post list shows AI translation complete and the status notice links to the draft. If it fails, the notice and post state show the error and a retry link.
  6. Review the draft and publish it.
  • The AI never sees the markup. The WordPress HTML API pulls out the text: text nodes, the alt, title, aria-label and placeholder attributes, and text values in block attributes such as a navigation link’s label. The AI gets them as an ordered JSON list and returns one translation per item. Each translation is written back into the original markup, so tags, URLs, classes, image IDs and block structure stay exactly as they were.
  • Text inside <code>, <kbd> and <samp> isn’t translated.
  • Content goes to the AI in chunks (6000 characters by default). Chunks only split between top-level blocks, or between paragraphs in classic content.
  • If the AI doesn’t return one translation per item, the plugin tries once more and then marks the job failed.
  • If someone edits the draft while it is being translated, the plugin throws away the AI result instead of overwriting their changes. It compares the title, excerpt and content as well as the modified time.
  • Only one worker translates a post at a time, using a lock in the options table. The worker refreshes the lock before every AI request. A lock with no refresh for 15 minutes can be taken over, and a worker that loses its lock stops without saving.
  • The job saves as the user who asked for the translation, so kses and capability checks work as they do in a normal edit.

A Retry translation row action shows up on translations that failed or have been stuck in processing for more than 15 minutes.

If WordPress finds no usable AI provider for a request (it reports “No models found…”, often a brief network or rate-limit hiccup), the plugin waits five seconds and tries once more. If that also fails, the job fails with “The AI provider could not be reached. Check Settings → Connectors and retry.”

Terminal window
# Create a Norwegian translation of post 42 and translate it now.
wp aitfp translate 42 --language=nb --user=admin
# Create it, but let WP-Cron translate it.
wp aitfp translate 42 --language=nb --user=admin --async
# Run (or re-run) the AI translation of translation post 43.
wp aitfp run 43

--user is required by translate so it can check capabilities.

FilterArgumentsDescription
aitfp_prompt_builderWP_AI_Client_Prompt_Builder $builder, string $field, array $languageChange the prompt builder before a request is sent, for example to choose a provider or model. $field is title, excerpt or content.
aitfp_content_chunk_sizeint $chunk_sizeMaximum characters of content per AI request. Default 6000, minimum 500.
aitfp_translatable_html_attributesstring[] $attributesHTML attributes whose values are translated. Default alt, title, aria-label, placeholder.
aitfp_translatable_block_attributesstring[] $keysBlock attribute (JSON) keys whose string values are translated. Default alt, caption, label, placeholder, title, buttonText, text, content, citation.
aitfp_request_timeoutint $timeoutAI request timeout in seconds. Default 180.
aitfp_translated_post_dataarray $data, WP_Post $post, array $languageChange the post data before it goes to wp_update_post().
aitfp_spawn_cronbool $spawn, int $post_idWhether to start WP-Cron right after queuing. Default true, even when DISABLE_WP_CRON is set.
ActionArgumentsDescription
aitfp_translatedint $post_id, array $language, int $source_idFires after a post has been translated.
aitfp_translation_failedint $post_id, WP_Error $error, array $languageFires when a translation fails.

$language is an array with code (Polylang slug), name and locale.

add_filter( 'aitfp_prompt_builder', function ( $builder ) {
return $builder->using_model_preference( 'gpt-5-mini' );
} );

When you delete the plugin, it removes its post meta (_aitfp_*) and any scheduled aitfp_translate_post events, on every site in a multisite network. The translated posts stay.

Terminal window
composer install
composer test

Unit tests run without WordPress: Brain Monkey mocks WordPress functions, and the HTML API classes are loaded from the johnpbloch/wordpress-core dev dependency.

To regenerate the translation template and compile existing translations, install WP-CLI and run:

Terminal window
npm run i18n

The template is saved in languages/. Add translated .po files there before rerunning the command. If translatable JavaScript is added, map its source files to their built paths in i18n-map.json and add --use-map=i18n-map.json to the i18n:make-json script.

To run the end-to-end test (WP-CLI, admin requests and real AI requests) against a running site:

Terminal window
AITFP_URL=https://example.test scripts/acceptance.sh

See docs/acceptance-test.md.

GPL v2 or later. Copyright Per Soderlind.