13832d0abSNetali<?php 23832d0abSNetali/** 33832d0abSNetali * Deepl Autotranslate Plugin 43832d0abSNetali * 53832d0abSNetali * @author Jennifer Graul <me@netali.de> 63832d0abSNetali */ 73832d0abSNetali 83832d0abSNetaliif(!defined('DOKU_INC')) die(); 93832d0abSNetali 1081931e50SNetaliuse \dokuwiki\HTTP\DokuHTTPClient; 11*3c636ad3SNetaliuse \dokuwiki\plugin\deeplautotranslate\MenuItem; 1281931e50SNetali 133832d0abSNetaliclass action_plugin_deeplautotranslate extends DokuWiki_Action_Plugin { 143832d0abSNetali 153832d0abSNetali // manual mapping of ISO-languages to DeepL-languages to fix inconsistent naming 163832d0abSNetali private $langs = [ 173832d0abSNetali 'bg' => 'BG', 183832d0abSNetali 'cs' => 'CS', 193832d0abSNetali 'da' => 'DA', 203832d0abSNetali 'de' => 'DE', 213832d0abSNetali 'de-informal' => 'DE', 223832d0abSNetali 'el' => 'EL', 233832d0abSNetali 'en' => 'EN-GB', 243832d0abSNetali 'es' => 'ES', 253832d0abSNetali 'et' => 'ET', 263832d0abSNetali 'fi' => 'FI', 273832d0abSNetali 'fr' => 'FR', 283832d0abSNetali 'hu' => 'HU', 293832d0abSNetali 'hu-formal' => 'HU', 303832d0abSNetali 'it' => 'IT', 313832d0abSNetali 'ja' => 'JA', 323832d0abSNetali 'lt' => 'LT', 333832d0abSNetali 'lv' => 'LV', 343832d0abSNetali 'nl' => 'NL', 353832d0abSNetali 'pl' => 'PL', 363832d0abSNetali 'pt' => 'PT-PT', 373832d0abSNetali 'ro' => 'RO', 383832d0abSNetali 'ru' => 'RU', 393832d0abSNetali 'sk' => 'SK', 403832d0abSNetali 'sl' => 'SL', 413832d0abSNetali 'sv' => 'SV', 423832d0abSNetali 'zh' => 'ZH' 433832d0abSNetali ]; 443832d0abSNetali 453832d0abSNetali /** 463832d0abSNetali * Register its handlers with the DokuWiki's event controller 473832d0abSNetali */ 483832d0abSNetali public function register(Doku_Event_Handler $controller) { 493832d0abSNetali $controller->register_hook('ACTION_ACT_PREPROCESS','BEFORE', $this, 'autotrans_direct'); 503832d0abSNetali $controller->register_hook('COMMON_PAGETPL_LOAD','AFTER', $this, 'autotrans_editor'); 51*3c636ad3SNetali $controller->register_hook('MENU_ITEMS_ASSEMBLY', 'AFTER', $this, 'add_menu_button'); 52*3c636ad3SNetali } 53*3c636ad3SNetali 54*3c636ad3SNetali public function add_menu_button(Doku_Event $event) { 55*3c636ad3SNetali if ($event->data['view'] != 'page') return; 56*3c636ad3SNetali 57*3c636ad3SNetali if (!$this->getConf('show_button')) return; 58*3c636ad3SNetali if (!$this->check_do_translation(true)) return; 59*3c636ad3SNetali 60*3c636ad3SNetali array_splice($event->data['items'], -1, 0, [new MenuItem()]); 613832d0abSNetali } 623832d0abSNetali 633832d0abSNetali public function autotrans_direct(Doku_Event $event, $param) { 643832d0abSNetali global $ID; 65*3c636ad3SNetali 66*3c636ad3SNetali // check if action is show or translate 67*3c636ad3SNetali if ($event->data != 'show' and $event->data != 'translate') return; 68*3c636ad3SNetali 69*3c636ad3SNetali // abort if action is translate and the translate button is disabled 70*3c636ad3SNetali if ($event->data == 'translate' and !$this->getConf('show_button')) return; 71*3c636ad3SNetali 72*3c636ad3SNetali // do nothing on show action when mode is not direct 73*3c636ad3SNetali if ($event->data == 'show' and $this->get_mode() != 'direct') return; 74*3c636ad3SNetali 75*3c636ad3SNetali // allow translation of existing pages is we are in the translate action 76*3c636ad3SNetali $allow_existing = ($event->data == 'translate'); 77*3c636ad3SNetali 78*3c636ad3SNetali // reset action to show 79*3c636ad3SNetali $event->data = 'show'; 80*3c636ad3SNetali 81*3c636ad3SNetali if (!$this->check_do_translation($allow_existing)) return; 823832d0abSNetali 833832d0abSNetali $org_page_text = $this->get_org_page_text(); 843832d0abSNetali $translated_text = $this->deepl_translate($org_page_text, $this->langs[$this->get_target_lang()]); 853832d0abSNetali 86e4700ea0SNetali if ($translated_text === '') return; 873832d0abSNetali 883832d0abSNetali saveWikiText($ID, $translated_text, 'Automatic translation'); 893832d0abSNetali 90*3c636ad3SNetali // reload the page after translation 91*3c636ad3SNetali send_redirect(wl($ID)); 923832d0abSNetali } 933832d0abSNetali 943832d0abSNetali public function autotrans_editor(Doku_Event $event, $param) { 953832d0abSNetali if ($this->get_mode() != 'editor') return; 963832d0abSNetali 973832d0abSNetali if (!$this->check_do_translation()) return; 983832d0abSNetali 993832d0abSNetali $org_page_text = $this->get_org_page_text(); 1003832d0abSNetali 1013832d0abSNetali $event->data['tpl'] = $this->deepl_translate($org_page_text, $this->langs[$this->get_target_lang()]); 1023832d0abSNetali } 1033832d0abSNetali 1043832d0abSNetali private function get_mode(): string { 1053832d0abSNetali global $ID; 1063832d0abSNetali if ($this->getConf('editor_regex')) { 1073832d0abSNetali if (preg_match('/' . $this->getConf('editor_regex') . '/', $ID) === 1) return 'editor'; 1083832d0abSNetali } 1093832d0abSNetali if ($this->getConf('direct_regex')) { 1103832d0abSNetali if (preg_match('/' . $this->getConf('direct_regex') . '/', $ID) === 1) return 'direct'; 1113832d0abSNetali } 1123832d0abSNetali return $this->getConf('mode'); 1133832d0abSNetali } 1143832d0abSNetali 1153832d0abSNetali private function get_target_lang(): string { 1163832d0abSNetali global $ID; 1173832d0abSNetali $split_id = explode(':', $ID); 1183832d0abSNetali return array_shift($split_id); 1193832d0abSNetali } 1203832d0abSNetali 1213832d0abSNetali private function get_org_page_text(): string { 1223832d0abSNetali global $ID; 1233832d0abSNetali 1243832d0abSNetali $split_id = explode(':', $ID); 1253832d0abSNetali array_shift($split_id); 1263832d0abSNetali $org_id = implode(':', $split_id); 1273832d0abSNetali 1283832d0abSNetali return rawWiki($org_id); 1293832d0abSNetali } 1303832d0abSNetali 131*3c636ad3SNetali private function check_do_translation($allow_existing = false): bool { 1323832d0abSNetali global $INFO; 1333832d0abSNetali global $ID; 1343832d0abSNetali 135*3c636ad3SNetali // only translate if the current page does not exist 136*3c636ad3SNetali if ($INFO['exists'] and !$allow_existing) return false; 137*3c636ad3SNetali 138*3c636ad3SNetali // permission check 139*3c636ad3SNetali $perm = auth_quickaclcheck($ID); 140*3c636ad3SNetali if (($INFO['exists'] and $perm < AUTH_EDIT) or (!$INFO['exists'] and $perm < AUTH_CREATE)) return false; 141*3c636ad3SNetali 1423832d0abSNetali // skip blacklisted namespaces and pages 1433832d0abSNetali if ($this->getConf('blacklist_regex')) { 1443832d0abSNetali if (preg_match('/' . $this->getConf('blacklist_regex') . '/', $ID) === 1) return false; 1453832d0abSNetali } 1463832d0abSNetali 1473832d0abSNetali $split_id = explode(':', $ID); 1483832d0abSNetali $lang_ns = array_shift($split_id); 1493832d0abSNetali // only translate if the current page is in a language namespace 1503832d0abSNetali if (!array_key_exists($lang_ns, $this->langs)) return false; 1513832d0abSNetali 1523832d0abSNetali $org_id = implode(':', $split_id); 1533832d0abSNetali // check if the original page exists 1543832d0abSNetali if (!page_exists($org_id)) return false; 1553832d0abSNetali 1563832d0abSNetali return true; 1573832d0abSNetali } 1583832d0abSNetali 1593832d0abSNetali private function deepl_translate($text, $target_lang): string { 1603832d0abSNetali if (!$this->getConf('api_key')) return ''; 1613832d0abSNetali 1623832d0abSNetali $text = $this->insert_ignore_tags($text); 1633832d0abSNetali 1643832d0abSNetali $data = [ 1653832d0abSNetali 'auth_key' => $this->getConf('api_key'), 1663832d0abSNetali 'target_lang' => $target_lang, 1673832d0abSNetali 'tag_handling' => 'xml', 168e4700ea0SNetali 'ignore_tags' => 'ignore,code,file,php', 1693832d0abSNetali 'text' => $text 1703832d0abSNetali ]; 1713832d0abSNetali 1723832d0abSNetali if ($this->getConf('api') == 'free') { 17381931e50SNetali $url = 'https://api-free.deepl.com/v2/translate'; 1743832d0abSNetali } else { 17581931e50SNetali $url = 'https://api.deepl.com/v2/translate'; 1763832d0abSNetali } 1773832d0abSNetali 17881931e50SNetali $http = new DokuHTTPClient(); 17981931e50SNetali $raw_response = $http->post($url, $data); 1803832d0abSNetali 1813832d0abSNetali // if any error occurred return an empty string 18281931e50SNetali if ($http->status >= 400) return ''; 1833832d0abSNetali 1843832d0abSNetali $json_response = json_decode($raw_response, true); 1853832d0abSNetali $translated_text = $json_response['translations'][0]['text']; 1863832d0abSNetali 1873832d0abSNetali $translated_text = $this->remove_ignore_tags($translated_text); 1883832d0abSNetali 1893832d0abSNetali return $translated_text; 1903832d0abSNetali } 1913832d0abSNetali 1923832d0abSNetali private function insert_ignore_tags($text): string { 1933832d0abSNetali $text = str_replace('[[', '<ignore>[[', $text); 1943832d0abSNetali $text = str_replace('{{', '<ignore>{{', $text); 1953832d0abSNetali $text = str_replace(']]', ']]</ignore>', $text); 1963832d0abSNetali $text = str_replace('}}', '}}</ignore>', $text); 1973832d0abSNetali 1983832d0abSNetali $ignored_expressions = explode(':', $this->getConf('ignored_expressions')); 1993832d0abSNetali 2003832d0abSNetali foreach ($ignored_expressions as $expression) { 2013832d0abSNetali $text = str_replace($expression, '<ignore>' . $expression . '</ignore>', $text); 2023832d0abSNetali } 2033832d0abSNetali 2043832d0abSNetali return $text; 2053832d0abSNetali } 2063832d0abSNetali 2073832d0abSNetali private function remove_ignore_tags($text): string { 2083832d0abSNetali $text = str_replace('<ignore>[[', '[[', $text); 2093832d0abSNetali $text = str_replace('<ignore>{{', '{{', $text); 2103832d0abSNetali $text = str_replace(']]</ignore>', ']]', $text); 2113832d0abSNetali $text = str_replace('}}</ignore>', '}}', $text); 2123832d0abSNetali 2133832d0abSNetali $ignored_expressions = explode(':', $this->getConf('ignored_expressions')); 2143832d0abSNetali 2153832d0abSNetali foreach ($ignored_expressions as $expression) { 2163832d0abSNetali $text = str_replace('<ignore>' . $expression . '</ignore>', $expression, $text); 2173832d0abSNetali } 2183832d0abSNetali 2193832d0abSNetali return $text; 2203832d0abSNetali } 2213832d0abSNetali} 2223832d0abSNetali 223