1<?php 2/** 3 * Deepl Autotranslate Plugin 4 * 5 * @author Jennifer Graul <me@netali.de> 6 */ 7 8if(!defined('DOKU_INC')) die(); 9 10class action_plugin_deeplautotranslate extends DokuWiki_Action_Plugin { 11 12 // manual mapping of ISO-languages to DeepL-languages to fix inconsistent naming 13 private $langs = [ 14 'bg' => 'BG', 15 'cs' => 'CS', 16 'da' => 'DA', 17 'de' => 'DE', 18 'de-informal' => 'DE', 19 'el' => 'EL', 20 'en' => 'EN-GB', 21 'es' => 'ES', 22 'et' => 'ET', 23 'fi' => 'FI', 24 'fr' => 'FR', 25 'hu' => 'HU', 26 'hu-formal' => 'HU', 27 'it' => 'IT', 28 'ja' => 'JA', 29 'lt' => 'LT', 30 'lv' => 'LV', 31 'nl' => 'NL', 32 'pl' => 'PL', 33 'pt' => 'PT-PT', 34 'ro' => 'RO', 35 'ru' => 'RU', 36 'sk' => 'SK', 37 'sl' => 'SL', 38 'sv' => 'SV', 39 'zh' => 'ZH' 40 ]; 41 42 /** 43 * Register its handlers with the DokuWiki's event controller 44 */ 45 public function register(Doku_Event_Handler $controller) { 46 $controller->register_hook('ACTION_ACT_PREPROCESS','BEFORE', $this, 'autotrans_direct'); 47 $controller->register_hook('COMMON_PAGETPL_LOAD','AFTER', $this, 'autotrans_editor'); 48 } 49 50 public function autotrans_direct(Doku_Event $event, $param) { 51 if ($this->get_mode() != 'direct') return; 52 if ($event->data != 'show') return; 53 54 if (!$this->check_do_translation()) return; 55 56 global $ID; 57 global $INFO; 58 59 $org_page_text = $this->get_org_page_text(); 60 $translated_text = $this->deepl_translate($org_page_text, $this->langs[$this->get_target_lang()]); 61 62 if ($translated_text === '') { 63 return; 64 } 65 66 saveWikiText($ID, $translated_text, 'Automatic translation'); 67 68 $INFO = pageinfo(); 69 } 70 71 public function autotrans_editor(Doku_Event $event, $param) { 72 if ($this->get_mode() != 'editor') return; 73 74 if (!$this->check_do_translation()) return; 75 76 $org_page_text = $this->get_org_page_text(); 77 78 $event->data['tpl'] = $this->deepl_translate($org_page_text, $this->langs[$this->get_target_lang()]); 79 } 80 81 private function get_mode(): string { 82 global $ID; 83 if ($this->getConf('editor_regex')) { 84 if (preg_match('/' . $this->getConf('editor_regex') . '/', $ID) === 1) return 'editor'; 85 } 86 if ($this->getConf('direct_regex')) { 87 if (preg_match('/' . $this->getConf('direct_regex') . '/', $ID) === 1) return 'direct'; 88 } 89 return $this->getConf('mode'); 90 } 91 92 private function get_target_lang(): string { 93 global $ID; 94 $split_id = explode(':', $ID); 95 return array_shift($split_id); 96 } 97 98 private function get_org_page_text(): string { 99 global $ID; 100 101 $split_id = explode(':', $ID); 102 array_shift($split_id); 103 $org_id = implode(':', $split_id); 104 105 return rawWiki($org_id); 106 } 107 108 private function check_do_translation(): bool { 109 global $INFO; 110 // only translate if the current page does not exist 111 if ($INFO['exists']) return false; 112 113 global $ID; 114 115 // skip blacklisted namespaces and pages 116 if ($this->getConf('blacklist_regex')) { 117 if (preg_match('/' . $this->getConf('blacklist_regex') . '/', $ID) === 1) return false; 118 } 119 120 $split_id = explode(':', $ID); 121 $lang_ns = array_shift($split_id); 122 // only translate if the current page is in a language namespace 123 if (!array_key_exists($lang_ns, $this->langs)) return false; 124 125 $org_id = implode(':', $split_id); 126 // check if the original page exists 127 if (!page_exists($org_id)) return false; 128 129 return true; 130 } 131 132 private function deepl_translate($text, $target_lang): string { 133 if (!$this->getConf('api_key')) return ''; 134 $curl = curl_init(); 135 136 $text = $this->insert_ignore_tags($text); 137 138 $data = [ 139 'auth_key' => $this->getConf('api_key'), 140 'target_lang' => $target_lang, 141 'tag_handling' => 'xml', 142 'ignore_tags' => 'ignore', 143 'text' => $text 144 ]; 145 146 if ($this->getConf('api') == 'free') { 147 curl_setopt($curl, CURLOPT_URL, 'https://api-free.deepl.com/v2/translate'); 148 } else { 149 curl_setopt($curl, CURLOPT_URL, 'https://api.deepl.com/v2/translate'); 150 } 151 152 curl_setopt($curl,CURLOPT_POST, 1); 153 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 154 curl_setopt($curl, CURLOPT_POSTFIELDS, $data); 155 156 $raw_response = curl_exec($curl); 157 158 // if any error occurred return an empty string 159 if (curl_getinfo($curl, CURLINFO_RESPONSE_CODE) >= 400) { 160 curl_close($curl); 161 return ''; 162 } 163 164 $json_response = json_decode($raw_response, true); 165 $translated_text = $json_response['translations'][0]['text']; 166 167 $translated_text = $this->remove_ignore_tags($translated_text); 168 169 return $translated_text; 170 } 171 172 private function insert_ignore_tags($text): string { 173 $text = str_replace('[[', '<ignore>[[', $text); 174 $text = str_replace('{{', '<ignore>{{', $text); 175 $text = str_replace(']]', ']]</ignore>', $text); 176 $text = str_replace('}}', '}}</ignore>', $text); 177 178 $ignored_expressions = explode(':', $this->getConf('ignored_expressions')); 179 180 foreach ($ignored_expressions as $expression) { 181 $text = str_replace($expression, '<ignore>' . $expression . '</ignore>', $text); 182 } 183 184 return $text; 185 } 186 187 private function remove_ignore_tags($text): string { 188 $text = str_replace('<ignore>[[', '[[', $text); 189 $text = str_replace('<ignore>{{', '{{', $text); 190 $text = str_replace(']]</ignore>', ']]', $text); 191 $text = str_replace('}}</ignore>', '}}', $text); 192 193 $ignored_expressions = explode(':', $this->getConf('ignored_expressions')); 194 195 foreach ($ignored_expressions as $expression) { 196 $text = str_replace('<ignore>' . $expression . '</ignore>', $expression, $text); 197 } 198 199 return $text; 200 } 201} 202 203