<?php
/**
- Polylang AI Auto-Translator
- This script automatically translates WordPress content using AI
- and updates the database in Polylang’s required format.
- Usage: php polylang-translator.php –source=en –target=zh-TW –type=post
*/
- Usage: php polylang-translator.php –source=en –target=zh-TW –type=post
require_once(DIR . ‘/wp-load.php’);
class PolylangAITranslator {
private $source_lang;
private $target_lang;
private $content_type;
private $api_key;
private $batch_size = 5;
public function __construct($source_lang, $target_lang, $content_type = 'post') {
$this->source_lang = $source_lang;
$this->target_lang = $target_lang;
$this->content_type = $content_type;
$this->api_key = getenv('ANTHROPIC_API_KEY'); // Set this in your environment
if (!function_exists('pll_get_post_language')) {
die("Error: Polylang is not installed or activated.\n");
}
}
/**
* Main execution method
*/
public function run() {
echo "Starting translation from {$this->source_lang} to {$this->target_lang}...\n";
$items = $this->get_untranslated_items();
echo "Found " . count($items) . " items to translate.\n";
$batches = array_chunk($items, $this->batch_size);
$total = count($items);
$processed = 0;
foreach ($batches as $batch) {
foreach ($batch as $item) {
$this->translate_item($item);
$processed++;
echo "Progress: {$processed}/{$total}\n";
}
// Avoid rate limiting
sleep(1);
}
echo "Translation completed!\n";
}
/**
* Get items that need translation
*/
private function get_untranslated_items() {
$args = array(
'post_type' => $this->content_type,
'posts_per_page' => -1,
'lang' => $this->source_lang,
'post_status' => 'publish'
);
$query = new WP_Query($args);
$items_to_translate = array();
foreach ($query->posts as $post) {
// Check if translation already exists
$translation_id = pll_get_post($post->ID, $this->target_lang);
if (!$translation_id) {
$items_to_translate[] = $post;
}
}
return $items_to_translate;
}
/**
* Translate a single item
*/
private function translate_item($post) {
echo "Translating: {$post->post_title} (ID: {$post->ID})\n";
// Prepare content for translation
$content_to_translate = array(
'title' => $post->post_title,
'content' => $post->post_content,
'excerpt' => $post->post_excerpt
);
// Translate using AI
$translated = $this->translate_with_ai($content_to_translate);
if (!$translated) {
echo "Error: Translation failed for post ID {$post->ID}\n";
return false;
}
// Create translated post
$new_post_id = $this->create_translated_post($post, $translated);
if ($new_post_id) {
// Link posts in Polylang
$this->link_translations($post->ID, $new_post_id);
// Translate post meta
$this->translate_post_meta($post->ID, $new_post_id);
// Translate taxonomies
$this->translate_taxonomies($post->ID, $new_post_id);
echo "Success: Created translation with ID {$new_post_id}\n";
return true;
}
return false;
}
/**
* Translate content using Claude AI
*/
private function translate_with_ai($content) {
$prompt = $this->build_translation_prompt($content);
$data = array(
'model' => 'claude-sonnet-4-20250514',
'max_tokens' => 4096,
'messages' => array(
array(
'role' => 'user',
'content' => $prompt
)
)
);
$ch = curl_init('https://api.anthropic.com/v1/messages');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'x-api-key: ' . $this->api_key,
'anthropic-version: 2023-06-01'
));
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($http_code !== 200) {
echo "API Error: HTTP {$http_code}\n";
return false;
}
$result = json_decode($response, true);
if (isset($result['content'][0]['text'])) {
return $this->parse_translation_response($result['content'][0]['text']);
}
return false;
}
/**
* Build translation prompt
*/
private function build_translation_prompt($content) {
$target_lang_name = $this->get_language_name($this->target_lang);
$prompt = "Translate the following WordPress post content to {$target_lang_name}. ";
$prompt .= "Maintain all HTML tags and formatting. Provide the translation in JSON format.\n\n";
$prompt .= "Original content:\n";
$prompt .= json_encode($content, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
$prompt .= "\n\nRespond with ONLY a JSON object with keys: title, content, excerpt";
return $prompt;
}
/**
* Parse AI translation response
*/
private function parse_translation_response($response) {
// Remove markdown code blocks if present
$response = preg_replace('/```json\s*|\s*```/', '', $response);
$response = trim($response);
$decoded = json_decode($response, true);
if (json_last_error() === JSON_ERROR_NONE) {
return $decoded;
}
echo "JSON Parse Error: " . json_last_error_msg() . "\n";
return false;
}
/**
* Create translated post in WordPress
*/
private function create_translated_post($original_post, $translated_content) {
$new_post = array(
'post_title' => $translated_content['title'],
'post_content' => $translated_content['content'],
'post_excerpt' => $translated_content['excerpt'],
'post_status' => 'publish',
'post_type' => $original_post->post_type,
'post_author' => $original_post->post_author,
'post_date' => $original_post->post_date,
'post_date_gmt' => $original_post->post_date_gmt,
'menu_order' => $original_post->menu_order,
'post_parent' => $original_post->post_parent
);
$new_post_id = wp_insert_post($new_post);
if ($new_post_id && !is_wp_error($new_post_id)) {
// Set language for new post
pll_set_post_language($new_post_id, $this->target_lang);
return $new_post_id;
}
return false;
}
/**
* Link original and translated posts in Polylang
*/
private function link_translations($original_id, $translation_id) {
$translations = pll_get_post_translations($original_id);
$translations[$this->target_lang] = $translation_id;
pll_save_post_translations($translations);
}
/**
* Translate post meta fields
*/
private function translate_post_meta($original_id, $translation_id) {
// Define meta fields that should be translated
$translatable_meta = array('_yoast_wpseo_metadesc', '_yoast_wpseo_title');
foreach ($translatable_meta as $meta_key) {
$meta_value = get_post_meta($original_id, $meta_key, true);
if (!empty($meta_value)) {
$translated_meta = $this->translate_with_ai(array('text' => $meta_value));
if ($translated_meta && isset($translated_meta['text'])) {
update_post_meta($translation_id, $meta_key, $translated_meta['text']);
}
}
}
// Copy non-translatable meta
$non_translatable_meta = array('_thumbnail_id');
foreach ($non_translatable_meta as $meta_key) {
$meta_value = get_post_meta($original_id, $meta_key, true);
if (!empty($meta_value)) {
update_post_meta($translation_id, $meta_key, $meta_value);
}
}
}
/**
* Translate and assign taxonomies
*/
private function translate_taxonomies($original_id, $translation_id) {
$taxonomies = get_object_taxonomies($this->content_type);
foreach ($taxonomies as $taxonomy) {
$terms = wp_get_post_terms($original_id, $taxonomy);
$translated_term_ids = array();
foreach ($terms as $term) {
$translated_term_id = pll_get_term($term->term_id, $this->target_lang);
if (!$translated_term_id) {
// Create translated term if it doesn't exist
$translated_term_id = $this->create_translated_term($term, $taxonomy);
}
if ($translated_term_id) {
$translated_term_ids[] = $translated_term_id;
}
}
if (!empty($translated_term_ids)) {
wp_set_post_terms($translation_id, $translated_term_ids, $taxonomy);
}
}
}
/**
* Create translated taxonomy term
*/
private function create_translated_term($original_term, $taxonomy) {
$translated = $this->translate_with_ai(array(
'name' => $original_term->name,
'description' => $original_term->description
));
if (!$translated) {
return false;
}
$new_term = wp_insert_term(
$translated['name'],
$taxonomy,
array('description' => $translated['description'])
);
if (!is_wp_error($new_term)) {
pll_set_term_language($new_term['term_id'], $this->target_lang);
// Link terms
$translations = pll_get_term_translations($original_term->term_id);
$translations[$this->target_lang] = $new_term['term_id'];
pll_save_term_translations($translations);
return $new_term['term_id'];
}
return false;
}
/**
* Get language name from code
*/
private function get_language_name($code) {
$languages = array(
'en' => 'English',
'zh-TW' => 'Traditional Chinese',
'zh-CN' => 'Simplified Chinese',
'ja' => 'Japanese',
'ko' => 'Korean',
'es' => 'Spanish',
'fr' => 'French',
'de' => 'German'
);
return isset($languages[$code]) ? $languages[$code] : $code;
}
}
// CLI execution
if (php_sapi_name() === ‘cli’) {
$options = getopt(”, array(‘source:’, ‘target:’, ‘type::’));
if (!isset($options['source']) || !isset($options['target'])) {
echo "Usage: php polylang-translator.php --source=en --target=zh-TW [--type=post]\n";
exit(1);
}
$source = $options['source'];
$target = $options['target'];
$type = isset($options['type']) ? $options['type'] : 'post';
$translator = new PolylangAITranslator($source, $target, $type);
$translator->run();
}
Polylang AI Auto-Translator Setup Guide
Prerequisites
- WordPress installation with Polylang plugin activated
- PHP 7.4 or higher with cURL extension
- Anthropic API key (Claude AI)
- Command line access to your WordPress installation
Installation Steps
1. Save the Main Script
Save polylang-translator.php to your WordPress root directory.
2. Set API Key
Set your Anthropic API key as an environment variable:
export ANTHROPIC_API_KEY='your-api-key-here'
Or add it to your .bashrc or .zshrc:
echo 'export ANTHROPIC_API_KEY="your-api-key-here"' >> ~/.bashrc
source ~/.bashrc
3. Create Configuration File (Optional)
Create translator-config.php in your WordPress root:
<?php
/**
* Polylang Translator Configuration
*/
return array(
// API Settings
'api_key' => getenv('ANTHROPIC_API_KEY'),
'api_model' => 'claude-sonnet-4-20250514',
'max_tokens' => 4096,
// Translation Settings
'batch_size' => 5,
'rate_limit_delay' => 1, // seconds
// Content Types to Translate
'post_types' => array('post', 'page', 'product'),
// Meta Fields to Translate
'translatable_meta' => array(
'_yoast_wpseo_title',
'_yoast_wpseo_metadesc',
'custom_field_name'
),
// Meta Fields to Copy (not translate)
'copy_meta' => array(
'_thumbnail_id',
'_product_image_gallery'
),
// Language Pairs
'languages' => array(
'en' => 'English',
'zh-TW' => 'Traditional Chinese',
'zh-CN' => 'Simplified Chinese',
'ja' => 'Japanese',
'ko' => 'Korean'
)
);
Usage
Basic Translation
Translate posts from English to Traditional Chinese:
php polylang-translator.php --source=en --target=zh-TW --type=post
Translate Pages
php polylang-translator.php --source=en --target=zh-TW --type=page
Translate Custom Post Types
php polylang-translator.php --source=en --target=ja --type=product
Advanced Usage
Batch Translation Script
Create batch-translate.sh for multiple language pairs:
#!/bin/bash
# Translate to multiple languages
languages=("zh-TW" "ja" "ko" "es")
for lang in "${languages[@]}"
do
echo "Translating to $lang..."
php polylang-translator.php --source=en --target=$lang --type=post
php polylang-translator.php --source=en --target=$lang --type=page
done
echo "All translations completed!"
Make it executable:
chmod +x batch-translate.sh
./batch-translate.sh
WordPress Cron Integration
Add to your theme’s functions.php:
// Schedule automatic translation
add_action('wp', 'schedule_auto_translation');
function schedule_auto_translation() {
if (!wp_next_scheduled('auto_translate_content')) {
wp_schedule_event(time(), 'daily', 'auto_translate_content');
}
}
add_action('auto_translate_content', 'run_auto_translation');
function run_auto_translation() {
$command = 'cd ' . ABSPATH . ' && php polylang-translator.php --source=en --target=zh-TW --type=post';
exec($command);
}
Monitoring and Logging
Create translator-log.php:
<?php
class TranslatorLogger {
private $log_file;
public function __construct() {
$this->log_file = __DIR__ . '/translation-log.txt';
}
public function log($message) {
$timestamp = date('Y-m-d H:i:s');
$entry = "[{$timestamp}] {$message}\n";
file_put_contents($this->log_file, $entry, FILE_APPEND);
}
public function get_stats() {
if (!file_exists($this->log_file)) {
return array();
}
$content = file_get_contents($this->log_file);
$success = substr_count($content, 'Success:');
$errors = substr_count($content, 'Error:');
return array(
'total_translations' => $success,
'errors' => $errors,
'success_rate' => $success > 0 ? ($success / ($success + $errors)) * 100 : 0
);
}
}
Troubleshooting
Common Issues
- “Polylang is not installed or activated”
- Ensure Polylang plugin is active
- Check if
pll_get_post_language()function exists
- API Rate Limits
- Increase
rate_limit_delayin config - Reduce
batch_size
- Increase
- Translation Quality Issues
- Adjust the translation prompt in
build_translation_prompt() - Consider using Claude Opus for better quality
- Adjust the translation prompt in
- Memory Issues
- Process in smaller batches
- Increase PHP memory limit:
php -d memory_limit=512M polylang-translator.php
Debug Mode
Add to the script for verbose output:
define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);
Best Practices
- Backup First: Always backup your database before running translations
- Test on Staging: Test the script on a staging environment first
- Review Translations: Manually review AI translations for accuracy
- Rate Limiting: Respect API rate limits to avoid throttling
- Language Context: Provide context in prompts for better translations
- SEO Meta: Always translate SEO meta fields for better search visibility
Cost Estimation
Claude Sonnet pricing (approximate):
- Input: $3 per million tokens
- Output: $15 per million tokens
Average post (1000 words):
- ~1500 input tokens
- ~2000 output tokens
- Cost: ~$0.03-0.05 per post
For 100 posts: ~$3-5
Security Considerations
- Store API keys in environment variables, never in code
- Restrict file permissions:
chmod 600 polylang-translator.php - Use HTTPS for API calls
- Validate and sanitize all inputs
- Limit execution to authorized users only
Support
For issues or improvements, check:
- Polylang documentation: https://polylang.pro/doc/
- Claude API docs: https://docs.anthropic.com/
- WordPress Codex: https://codex.wordpress.org/
WP开发教程 WordPress导师在线教程