Changeset 3129770
- Timestamp:
- 08/01/2024 11:41:36 PM (20 months ago)
- Location:
- ai-translate-for-polylang
- Files:
-
- 8 added
- 3 edited
-
tags/1.0.3 (added)
-
tags/1.0.3/ai-translate-polylang.php (added)
-
tags/1.0.3/inc (added)
-
tags/1.0.3/inc/open-ai (added)
-
tags/1.0.3/inc/open-ai/OpenAi.php (added)
-
tags/1.0.3/inc/open-ai/Url.php (added)
-
tags/1.0.3/inc/settingslib.php (added)
-
tags/1.0.3/readme.txt (added)
-
trunk/ai-translate-polylang.php (modified) (6 diffs)
-
trunk/inc/settingslib.php (modified) (4 diffs)
-
trunk/readme.txt (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
ai-translate-for-polylang/trunk/ai-translate-polylang.php
r3129762 r3129770 4 4 Plugin URI: https://wordpress.org/plugins/ai-translate-polylang/ 5 5 Description: Add auto AI translation caperbility to Polylang 6 Version: 1.0. 26 Version: 1.0.3 7 7 Author: James Low 8 8 Author URI: http://jameslow.com … … 13 13 Next Version: 14 14 - Clear out or translate Yoast SEO 15 - Manual translate button on post 16 - Claude translation API 17 - Setting for Claude/OpenAI 18 - Auto translate on publish post 15 - Auto translate on publish post (publish/save as draft) 19 16 - Setting to only auto translate for certain categories/pages 20 17 */ … … 23 20 24 21 class AI_Translate_Polylang { 22 //Constants 25 23 public static $PROMPT = 'Translate the content from {FROM_CODE} to {TO_CODE} preserving html, formatting and embedded media.'; 24 public static $OPENAI_MODEL = 'gpt-4o'; 25 public static $CLAUDE_MODEL = 'claude-3-5-sonnet-20240620'; 26 27 //Variables 28 public static $meta_translate; 29 public static $meta_clear; 26 30 27 31 /* Helper functions */ … … 42 46 add_filter('default_title', array(static::class, 'default_title'), 10, 2); 43 47 add_filter('default_content', array(static::class, 'default_content'), 10, 2); 48 //add_filter('pll_copy_post_metas', array(static::class, 'pll_copy_post_metas'), 11, 5); //This gets called, but doesn't clear out keys 49 add_filter('pll_translate_post_meta', array(static::class, 'pll_translate_post_meta'), 10, 5); 44 50 } 45 51 public static function init() { 46 52 self::require_settings(); 47 $settings = new \ PageApp\Settings\SettingsLib(array(53 $settings = new \SettingsLib(array( 48 54 array('id'=>'ai_translate_new_post', 'type'=>'boolean', 'title'=>'Auto Translate New Translation Posts', 'default'=>'1'), 49 55 array('id'=>'ai_translate_prompt', 'type'=>'string', 'title'=>'Custom Prompt', 'description'=>'{FROM_CODE} and {TO_CODE} will be replaced by from and to languages.', 'default'=>self::$PROMPT), 56 array('id'=>'ai_translate_llm', 'type'=>'select', 'title'=>'LLM Service', 'default'=>'OpenAI', 'values'=>array( 57 'OpenAI', 58 'Claude' 59 )), 60 array('id'=>'ai_translate_openai', 'type'=>'title', 'title'=>'OpenAI', 'description'=>''), 50 61 array('id'=>'ai_translate_openai_key', 'type'=>'string', 'title'=>'OpenAI API Key', 'description'=>''), 51 62 array('id'=>'ai_translate_openai_org', 'type'=>'string', 'title'=>'OpenAI Organization', 'description'=>'(Optional)'), 52 array('id'=>'ai_translate_openai_model', 'type'=>'select', 'title'=>'OpenAI Model', 'default'=>'gpt-3.5-turbo', 'values'=>array( 53 'gpt-3.5-turbo', 63 array('id'=>'ai_translate_openai_model', 'type'=>'select', 'title'=>'OpenAI Model', 'default'=>self::$OPENAI_MODEL, 'values'=>array( 64 'gpt-4o', 65 'gpt-4o-mini', 66 'gpt-4-turbo', 54 67 'gpt-4', 55 'gpt-4-turbo', 56 'gpt-4o' 68 'gpt-3.5-turbo' 57 69 )), 70 array('id'=>'ai_translate_claude', 'type'=>'title', 'title'=>'Claude', 'description'=>''), 71 array('id'=>'ai_translate_claude_key', 'type'=>'string', 'title'=>'Claude API Key', 'description'=>''), 72 array('id'=>'ai_translate_claude_model', 'type'=>'select', 'title'=>'OpenAI Model', 'default'=>self::$CLAUDE_MODEL, 'values'=>array( 73 'claude-3-5-sonnet-20240620', 74 'claude-3-opus-20240229', 75 'claude-3-sonnet-20240229', 76 'claude-3-haiku-20240307' 77 )), 78 array('id'=>'ai_translate_meta', 'type'=>'title', 'title'=>'Meta', 'description'=>''), 79 array('id'=>'ai_translate_meta_clear', 'type'=>'text', 'title'=>'Meta keys to clear', 'description'=>'', 'default'=>''), 80 array('id'=>'ai_translate_meta_translate', 'type'=>'text', 'title'=>'Meta keys to translate', 'description'=>'', 'default'=>''), 58 81 ), 'AI Translate', 'mlang', false, 'manage_options', null, null, '', '_'); 59 82 } 60 83 public static function default_title($title, $post) { 61 return wp_strip_all_tags(self::translate_field($title, 'post_title')); 84 $pattern = '/[^\p{L}\p{N}]+$/u'; //Remove trailing not alpha numeric characters 85 return preg_replace($pattern, '', wp_strip_all_tags(self::translate_field($title, 'post_title'))); 62 86 } 63 87 public static function default_content($content, $post) { 64 88 return self::translate_field($content, 'post_content'); 65 89 } 66 public static function translate_field($original, $field, $meta = false) { 90 public static function pll_copy_post_metas($keys, $sync, $from, $to, $lang) { 91 $keys = array_diff($keys, self::meta_clear()); 92 return $keys; 93 } 94 public static function pll_translate_post_meta($value, $key, $lang, $from, $to) { 95 if (in_array($key, self::meta_clear())) { 96 $value = ''; 97 } else if (in_array($key, self::meta_translate())) { 98 $value = self::translate_field($value); 99 } 100 return $value; 101 } 102 private static function meta_keys($option) { 103 $clear = get_option($option); 104 if ($clear) { 105 return preg_split('/\s+/', $clear); 106 } else { 107 return array(); 108 } 109 } 110 private static function meta_clear() { 111 if (!self::$meta_clear) { 112 self::$meta_clear = self::meta_keys('ai_translate_meta_clear'); 113 } 114 return self::$meta_clear; 115 } 116 private static function meta_translate(){ 117 if (!self::$meta_translate) { 118 self::$meta_translate = self::meta_keys('ai_translate_meta_translate'); 119 } 120 return self::$meta_translate; 121 } 122 123 /* Translation */ 124 public static function translate_field($original, $field = '', $meta = false) { 67 125 $translation = null; 68 if (get_option('ai_translate_new_post' ) == '1' && $_GET['new_lang'] && isset($_GET['from_post'])) {126 if (get_option('ai_translate_new_post', '0') == '1' && $_GET['new_lang'] && isset($_GET['from_post'])) { 69 127 if (!$original || $original != '') { 70 128 $code = sanitize_key($_GET['new_lang']); 71 129 $post_id = sanitize_key($_GET['from_post']); 72 if ($meta) { 73 $original = get_post_meta($post_id, $field, true); 74 } else { 75 $post = get_post($post_id); 76 $original = $post->$field; 130 if ($field) { 131 if ($meta) { 132 $original = get_post_meta($post_id, $field, true); 133 } else { 134 $post = get_post($post_id); 135 $original = $post->$field; 136 } 77 137 } 78 138 } 79 $result = self::openai_translate($original, $code, pll_get_post_language($post_id)); 139 $translation = self::translate($original, $code, pll_get_post_language($post_id)); 140 } else { 141 $translation = $original; 142 } 143 return $translation; 144 } 145 public static function prompt($to, $from = 'en') { 146 return str_replace('{TO_CODE}', $to, str_replace('{FROM_CODE}', $from, get_option('ai_translate_prompt', self::$PROMPT))); 147 } 148 public static function translate($text, $to, $from = 'en') { 149 $prompt = self::prompt($to, $from); 150 if (get_option('ai_translate_llm', 'OpenAI') == 'Claude') { 151 $result = self::claude_message($text, $prompt); 152 $body = json_decode($result['body'], true); 153 if ($result['response']['code'] == 200) { 154 return $body['content'][0]['text']; 155 } else { 156 return 'ERROR: '.$body['error']['message']; 157 } 158 } else { 159 $result = self::openai_api($text, $prompt); 80 160 if (!isset($result['error'])) { 81 161 $translation = $result['choices'][0]['message']['content']; … … 83 163 $translation = 'ERROR: '.$result['error']['message']; 84 164 } 85 } else { 86 $translation = $original; 87 } 88 return $translation; 89 } 90 public static function prompt($to, $from = 'en') { 91 $prompt = get_option('ai_translate_prompt'); 92 $prompt = $prompt && $prompt != '' ? $prompt : self::$PROMPT; 93 return str_replace('{TO_CODE}', $to, str_replace('{FROM_CODE}', $from, $prompt)); 94 } 95 96 /* Translation */ 165 return $translation; 166 } 167 } 168 169 /* APIs */ 170 public static function claude_message($content, $role = 'You are a helpful assistant.', $tokens = 1000, $temp = 0) { 171 $request = new \WP_Http(); 172 $headers = array( 173 'x-api-key' => get_option('ai_translate_claude_key', ''), 174 'anthropic-version' => '2023-06-01', 175 'Content-Type' => 'application/json', 176 ); 177 $message = array( 178 'model' => get_option('ai_translate_claude_model', self::$CLAUDE_MODEL), 179 'max_tokens' => 1000, 180 'temperature' => $temp, 181 'system' => $role, 182 'messages' => array( 183 array("role" => 'user', "content" => $content) 184 ) 185 ); 186 $args = array( 187 'method' => 'POST', 188 'headers' => $headers, 189 'body' => json_encode($message), 190 'timeout' => 60, 191 ); 192 return $request->request('https://api.anthropic.com/v1/messages', $args); 193 //https://www.datacamp.com/tutorial/getting-started-with-claude-3-and-the-claude-3-api 194 //return $request->request('https://api.anthropic.com/v1/complete', $args); 195 } 97 196 public static function openai() { 98 197 self::require_openai(); 99 198 //Create new open ai every time, otherwise it preserves conversation between calls and gets confused translating title/content 100 return new \Orhanerday\OpenAi\OpenAi(get_option('ai_translate_openai_key')); 199 $openai = new \Orhanerday\OpenAi\OpenAi(get_option('ai_translate_openai_key', '')); 200 if ($org = get_option('ai_translate_openai_org')) { 201 $openai->setORG($org); 202 } 203 return $openai; 101 204 } 102 205 public static function openai_api($content, $role = 'You are a helpful assistant.', $tokens = 1000) { 103 206 //https://packagist.org/packages/orhanerday/open-ai 104 207 return json_decode(self::openai()->chat([ 105 'model' => get_option('ai_translate_openai_model' ),208 'model' => get_option('ai_translate_openai_model', self::$OPENAI_MODEL), 106 209 'messages' => [ 107 210 [ … … 120 223 ]), true); 121 224 } 122 public static function openai_translate($text, $to, $from = 'en') {123 return self::openai_api($text, self::prompt($to, $from));124 }125 225 } 126 226 -
ai-translate-for-polylang/trunk/inc/settingslib.php
r3129762 r3129770 1 1 <?php 2 namespace PageApp\Settings; 3 4 if (!class_exists('PageApp\Settings\SettingsLib')) { 2 if (!class_exists('SettingsLib')) { 5 3 class SettingsLib { 6 4 var $settings; … … 96 94 } elseif ($setting->type == 'text') { 97 95 echo self::settings_text($setting); 96 } elseif ($setting->type == 'title') { 97 echo self::settings_row($setting); 98 98 } else { 99 99 echo self::settings_input($setting); 100 100 } 101 101 } 102 public static function settings_row($setting, $html ) {102 public static function settings_row($setting, $html = '') { 103 103 return ' 104 <tr valign="top" >105 <th scope="row">'.esc_html($setting->title).($setting->type=='text' ?'<div style="font-weight:normal;">'.esc_html($setting->description).'</div>':'').'</th>104 <tr valign="top" class="'.$setting->id.'"> 105 <th scope="row">'.esc_html($setting->title).($setting->type=='text'&&$setting->description?'<div style="font-weight:normal;">'.esc_html($setting->description).'</div>':'').'</th> 106 106 <td>'.$html.'</td> 107 107 </tr>'; … … 135 135 return isset($_REQUEST[$key]) && $_REQUEST[$key] != '' ? 1 : 0; 136 136 } 137 public static function process_checkbox($key) {138 update_option($key, self::checkbox_request($key));139 }140 public static function process_option($key) {141 update_option($key, sanitize_text_field($_REQUEST[$key]));142 }143 137 public static function associative($array) { 144 138 return array_keys($array) !== range(0, count($array) - 1); … … 151 145 foreach ($this->settings as $setting) { 152 146 $setting = (object) $setting; 147 $key = $setting->id; 148 $value = isset($_REQUEST[$key]) ? stripslashes($_REQUEST[$key]) : ''; 153 149 if ($setting->type == 'boolean') { 154 self::process_checkbox($setting->id); 150 update_option($key, self::checkbox_request($key)); 151 } else if ($setting->type == 'text') { 152 update_option($key, sanitize_textarea_field($value)); 155 153 } else { 156 self::process_option($setting->id);154 update_option($key, sanitize_text_field($value)); 157 155 } 158 156 } -
ai-translate-for-polylang/trunk/readme.txt
r3129762 r3129770 5 5 Requires at least: 3.0 6 6 Tested up to: 6.5.4 7 Stable tag: 1.0. 27 Stable tag: 1.0.3 8 8 License: MIT 9 9 License URI: https://opensource.org/licenses/MIT … … 34 34 == Changelog == 35 35 36 = 1.0.3 = 37 * Add Claude API 38 * Add options to translate or clear out prior meta 39 36 40 = 1.0.2 = 37 41 * Futher updates for Wordpress plugin standards
Note: See TracChangeset
for help on using the changeset viewer.