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