Plugin Directory

Changeset 3129770


Ignore:
Timestamp:
08/01/2024 11:41:36 PM (20 months ago)
Author:
jamesdlow
Message:

1.0.3

  • Add Claude API
  • Add options to translate or clear out prior meta
Location:
ai-translate-for-polylang
Files:
8 added
3 edited

Legend:

Unmodified
Added
Removed
  • ai-translate-for-polylang/trunk/ai-translate-polylang.php

    r3129762 r3129770  
    44Plugin URI: https://wordpress.org/plugins/ai-translate-polylang/
    55Description: Add auto AI translation caperbility to Polylang
    6 Version: 1.0.2
     6Version: 1.0.3
    77Author: James Low
    88Author URI: http://jameslow.com
     
    1313Next Version:
    1414- 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)
    1916- Setting to only auto translate for certain categories/pages
    2017*/
     
    2320
    2421class AI_Translate_Polylang {
     22    //Constants
    2523    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;
    2630
    2731    /* Helper functions */
     
    4246        add_filter('default_title', array(static::class, 'default_title'), 10, 2);
    4347        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);
    4450    }
    4551    public static function init() {
    4652        self::require_settings();
    47         $settings = new \PageApp\Settings\SettingsLib(array(
     53        $settings = new \SettingsLib(array(
    4854            array('id'=>'ai_translate_new_post', 'type'=>'boolean', 'title'=>'Auto Translate New Translation Posts', 'default'=>'1'),
    4955            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'=>''),
    5061            array('id'=>'ai_translate_openai_key', 'type'=>'string', 'title'=>'OpenAI API Key', 'description'=>''),
    5162            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',
    5467                'gpt-4',
    55                 'gpt-4-turbo',
    56                 'gpt-4o'
     68                'gpt-3.5-turbo'
    5769            )),
     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'=>''),
    5881        ), 'AI Translate', 'mlang', false, 'manage_options', null, null, '', '_');
    5982    }
    6083    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')));
    6286    }
    6387    public static function default_content($content, $post) {
    6488        return self::translate_field($content, 'post_content');
    6589    }
    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) {
    67125        $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'])) {
    69127            if (!$original || $original != '') {
    70128                $code = sanitize_key($_GET['new_lang']);
    71129                $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                    }
    77137                }
    78138            }
    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);
    80160            if (!isset($result['error'])) {
    81161                $translation = $result['choices'][0]['message']['content'];
     
    83163                $translation = 'ERROR: '.$result['error']['message'];
    84164            }
    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    }
    97196    public static function openai() {
    98197        self::require_openai();
    99198        //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;
    101204    }
    102205    public static function openai_api($content, $role = 'You are a helpful assistant.', $tokens = 1000) {
    103206        //https://packagist.org/packages/orhanerday/open-ai
    104207        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),
    106209            'messages' => [
    107210                [
     
    120223         ]), true);
    121224    }
    122     public static function openai_translate($text, $to, $from = 'en') {
    123         return self::openai_api($text, self::prompt($to, $from));
    124     }
    125225}
    126226
  • ai-translate-for-polylang/trunk/inc/settingslib.php

    r3129762 r3129770  
    11<?php
    2 namespace PageApp\Settings;
    3 
    4 if (!class_exists('PageApp\Settings\SettingsLib')) {
     2if (!class_exists('SettingsLib')) {
    53class SettingsLib {
    64    var $settings;
     
    9694        } elseif ($setting->type == 'text') {
    9795            echo self::settings_text($setting);
     96        } elseif ($setting->type == 'title') {
     97            echo self::settings_row($setting);
    9898        } else {
    9999            echo self::settings_input($setting);
    100100        }
    101101    }
    102     public static function settings_row($setting, $html) {
     102    public static function settings_row($setting, $html = '') {
    103103        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>
    106106            <td>'.$html.'</td>
    107107        </tr>';
     
    135135        return isset($_REQUEST[$key]) && $_REQUEST[$key] != '' ? 1 : 0;
    136136    }
    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     }
    143137    public static function associative($array) {
    144138        return array_keys($array) !== range(0, count($array) - 1);
     
    151145            foreach ($this->settings as $setting) {
    152146                $setting = (object) $setting;
     147                $key = $setting->id;
     148                $value = isset($_REQUEST[$key]) ? stripslashes($_REQUEST[$key]) : '';
    153149                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));
    155153                } else {
    156                     self::process_option($setting->id);
     154                    update_option($key, sanitize_text_field($value));
    157155                }
    158156            }
  • ai-translate-for-polylang/trunk/readme.txt

    r3129762 r3129770  
    55Requires at least: 3.0
    66Tested up to: 6.5.4
    7 Stable tag: 1.0.2
     7Stable tag: 1.0.3
    88License: MIT
    99License URI: https://opensource.org/licenses/MIT
     
    3434== Changelog ==
    3535
     36= 1.0.3 =
     37* Add Claude API
     38* Add options to translate or clear out prior meta
     39
    3640= 1.0.2 =
    3741* Futher updates for Wordpress plugin standards
Note: See TracChangeset for help on using the changeset viewer.