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; 113c636ad3SNetaliuse \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) { 49153e4498SNetali $controller->register_hook('ACTION_ACT_PREPROCESS','BEFORE', $this, 'preprocess'); 503832d0abSNetali $controller->register_hook('COMMON_PAGETPL_LOAD','AFTER', $this, 'autotrans_editor'); 513c636ad3SNetali $controller->register_hook('MENU_ITEMS_ASSEMBLY', 'AFTER', $this, 'add_menu_button'); 523c636ad3SNetali } 533c636ad3SNetali 540180404cSNetali public function add_menu_button(Doku_Event $event): void { 55153e4498SNetali global $ID; 56bbb1fba9SNetali global $ACT; 57bbb1fba9SNetali 58bbb1fba9SNetali if ($ACT != 'show') return; 59153e4498SNetali 603c636ad3SNetali if ($event->data['view'] != 'page') return; 613c636ad3SNetali 623c636ad3SNetali if (!$this->getConf('show_button')) return; 63153e4498SNetali 64153e4498SNetali $split_id = explode(':', $ID); 65153e4498SNetali $lang_ns = array_shift($split_id); 66153e4498SNetali // check if we are in a language namespace 67153e4498SNetali if (array_key_exists($lang_ns, $this->langs)) { 68ff327fe6SNetali if($this->getConf('default_lang_in_ns') and $lang_ns === $this->get_default_lang()) { 6999da9a08SNetali // if the default lang is in a namespace and we are in that namespace --> check for push translation 7099da9a08SNetali if (!$this->check_do_push_translate()) return; 7199da9a08SNetali } else { 72153e4498SNetali // in language namespace --> check if we should translate 733c636ad3SNetali if (!$this->check_do_translation(true)) return; 7499da9a08SNetali } 75153e4498SNetali } else { 7699da9a08SNetali // do not show the button if we are not in a language namespace and the default language is in a namespace 7799da9a08SNetali if($this->getConf('default_lang_in_ns')) return; 7899da9a08SNetali // not in language namespace and default language is npt in a namespace --> check if we should show the push translate button 79153e4498SNetali if (!$this->check_do_push_translate()) return; 80153e4498SNetali } 813c636ad3SNetali 823c636ad3SNetali array_splice($event->data['items'], -1, 0, [new MenuItem()]); 833832d0abSNetali } 843832d0abSNetali 85153e4498SNetali public function preprocess(Doku_Event $event, $param): void { 863832d0abSNetali global $ID; 873c636ad3SNetali 883c636ad3SNetali // check if action is show or translate 893c636ad3SNetali if ($event->data != 'show' and $event->data != 'translate') return; 903c636ad3SNetali 91153e4498SNetali $split_id = explode(':', $ID); 92153e4498SNetali $lang_ns = array_shift($split_id); 93153e4498SNetali // check if we are in a language namespace 94153e4498SNetali if (array_key_exists($lang_ns, $this->langs)) { 95ff327fe6SNetali if($this->getConf('default_lang_in_ns') and $lang_ns === $this->get_default_lang()) { 9699da9a08SNetali // if the default lang is in a namespace and we are in that namespace --> push translate 9799da9a08SNetali $this->push_translate($event); 9899da9a08SNetali } else { 9999da9a08SNetali // in language namespace --> autotrans direct 100153e4498SNetali $this->autotrans_direct($event); 10199da9a08SNetali } 102153e4498SNetali } else { 103153e4498SNetali // not in language namespace --> push translate 104153e4498SNetali $this->push_translate($event); 105153e4498SNetali } 106153e4498SNetali } 107153e4498SNetali 108153e4498SNetali private function autotrans_direct(Doku_Event $event): void { 109153e4498SNetali global $ID; 110153e4498SNetali 1113c636ad3SNetali // abort if action is translate and the translate button is disabled 1123c636ad3SNetali if ($event->data == 'translate' and !$this->getConf('show_button')) return; 1133c636ad3SNetali 1143c636ad3SNetali // do nothing on show action when mode is not direct 1153c636ad3SNetali if ($event->data == 'show' and $this->get_mode() != 'direct') return; 1163c636ad3SNetali 1173c636ad3SNetali // allow translation of existing pages is we are in the translate action 1183c636ad3SNetali $allow_existing = ($event->data == 'translate'); 1193c636ad3SNetali 1203c636ad3SNetali // reset action to show 1213c636ad3SNetali $event->data = 'show'; 1223c636ad3SNetali 123153e4498SNetali if (!$this->check_do_translation($allow_existing)) { 124153e4498SNetali return; 125153e4498SNetali } 1263832d0abSNetali 1270180404cSNetali $org_page_info = $this->get_org_page_info(); 1280180404cSNetali $translated_text = $this->deepl_translate($org_page_info["text"], $this->get_target_lang(), $org_page_info["ns"]); 1293832d0abSNetali 130153e4498SNetali if ($translated_text === '') { 131153e4498SNetali return; 132153e4498SNetali } 1333832d0abSNetali 1343832d0abSNetali saveWikiText($ID, $translated_text, 'Automatic translation'); 1353832d0abSNetali 136153e4498SNetali msg($this->getLang('msg_translation_success'), 1); 137153e4498SNetali 1383c636ad3SNetali // reload the page after translation 1393c636ad3SNetali send_redirect(wl($ID)); 1403832d0abSNetali } 1413832d0abSNetali 142153e4498SNetali public function autotrans_editor(Doku_Event $event, $param): void { 1433832d0abSNetali if ($this->get_mode() != 'editor') return; 1443832d0abSNetali 1453832d0abSNetali if (!$this->check_do_translation()) return; 1463832d0abSNetali 1470180404cSNetali $org_page_info = $this->get_org_page_info(); 1483832d0abSNetali 1490180404cSNetali $event->data['tpl'] = $this->deepl_translate($org_page_info["text"], $this->get_target_lang(), $org_page_info["ns"]); 1503832d0abSNetali } 1513832d0abSNetali 152153e4498SNetali private function push_translate(Doku_Event $event): void { 153153e4498SNetali global $ID; 154153e4498SNetali 155153e4498SNetali // check if action is translate 156153e4498SNetali if ($event->data != 'translate') return; 157153e4498SNetali 158153e4498SNetali // check if button is enabled 159153e4498SNetali if (!$this->getConf('show_button')) { 160153e4498SNetali send_redirect(wl($ID)); 161153e4498SNetali return; 162153e4498SNetali } 163153e4498SNetali 164153e4498SNetali if (!$this->check_do_push_translate()) { 165153e4498SNetali send_redirect(wl($ID)); 166153e4498SNetali return; 167153e4498SNetali } 168153e4498SNetali 169153e4498SNetali // push translate 170153e4498SNetali $push_langs = $this->get_push_langs(); 171153e4498SNetali $org_page_text = rawWiki($ID); 172153e4498SNetali foreach ($push_langs as $lang) { 173153e4498SNetali // skip invalid languages 174153e4498SNetali if (!array_key_exists($lang, $this->langs)) { 175153e4498SNetali msg($this->getLang('msg_translation_fail_invalid_lang') . $lang, -1); 176153e4498SNetali continue; 177153e4498SNetali } 178153e4498SNetali 17999da9a08SNetali if ($this->getConf('default_lang_in_ns')) { 18099da9a08SNetali // if default lang is in ns: replace language namespace in ID 18199da9a08SNetali $split_id = explode(':', $ID); 18299da9a08SNetali array_shift($split_id); 18399da9a08SNetali $lang_id = implode(':', $split_id); 18499da9a08SNetali $lang_id = $lang . ':' . $lang_id; 18599da9a08SNetali } else { 18699da9a08SNetali // if default lang is not in ns: add language namespace to ID 187153e4498SNetali $lang_id = $lang . ':' . $ID; 18899da9a08SNetali } 189153e4498SNetali 190153e4498SNetali // check permissions 191a3a51507SNetali $perm = auth_quickaclcheck($lang_id); 192153e4498SNetali $exists = page_exists($lang_id); 193153e4498SNetali if (($exists and $perm < AUTH_EDIT) or (!$exists and $perm < AUTH_CREATE)) { 194153e4498SNetali msg($this->getLang('msg_translation_fail_no_permissions') . $lang_id, -1); 195153e4498SNetali continue; 196153e4498SNetali } 197153e4498SNetali 1980180404cSNetali $translated_text = $this->deepl_translate($org_page_text, $lang, getNS($ID)); 199153e4498SNetali saveWikiText($lang_id, $translated_text, 'Automatic push translation'); 200153e4498SNetali } 201153e4498SNetali 202153e4498SNetali msg($this->getLang('msg_translation_success'), 1); 203153e4498SNetali 204153e4498SNetali // reload the page after translation to clear the action 205153e4498SNetali send_redirect(wl($ID)); 206153e4498SNetali } 207153e4498SNetali 2083832d0abSNetali private function get_mode(): string { 2093832d0abSNetali global $ID; 2103832d0abSNetali if ($this->getConf('editor_regex')) { 2113832d0abSNetali if (preg_match('/' . $this->getConf('editor_regex') . '/', $ID) === 1) return 'editor'; 2123832d0abSNetali } 2133832d0abSNetali if ($this->getConf('direct_regex')) { 2143832d0abSNetali if (preg_match('/' . $this->getConf('direct_regex') . '/', $ID) === 1) return 'direct'; 2153832d0abSNetali } 2163832d0abSNetali return $this->getConf('mode'); 2173832d0abSNetali } 2183832d0abSNetali 2193832d0abSNetali private function get_target_lang(): string { 2203832d0abSNetali global $ID; 2213832d0abSNetali $split_id = explode(':', $ID); 2223832d0abSNetali return array_shift($split_id); 2233832d0abSNetali } 2243832d0abSNetali 225ff327fe6SNetali private function get_default_lang(): string { 226ff327fe6SNetali global $conf; 227ff327fe6SNetali 228ff327fe6SNetali if (empty($conf['lang_before_translation'])) { 229ff327fe6SNetali $default_lang = $conf['lang']; 230ff327fe6SNetali } else { 231ff327fe6SNetali $default_lang = $conf['lang_before_translation']; 232ff327fe6SNetali } 233ff327fe6SNetali 234ff327fe6SNetali return $default_lang; 235ff327fe6SNetali } 236ff327fe6SNetali 2370180404cSNetali private function get_org_page_info(): array { 2383832d0abSNetali global $ID; 2393832d0abSNetali 2403832d0abSNetali $split_id = explode(':', $ID); 2413832d0abSNetali array_shift($split_id); 2423832d0abSNetali $org_id = implode(':', $split_id); 2433832d0abSNetali 24499da9a08SNetali // if default lang is in ns: add default ns in front of org id 24599da9a08SNetali if ($this->getConf('default_lang_in_ns')) { 246ff327fe6SNetali $org_id = $this->get_default_lang() . ':' . $org_id; 24799da9a08SNetali } 24899da9a08SNetali 2490180404cSNetali return array("ns" => getNS($org_id), "text" => rawWiki($org_id)); 2503832d0abSNetali } 2513832d0abSNetali 2523c636ad3SNetali private function check_do_translation($allow_existing = false): bool { 2533832d0abSNetali global $INFO; 2543832d0abSNetali global $ID; 2553832d0abSNetali 2563c636ad3SNetali // only translate if the current page does not exist 2573c636ad3SNetali if ($INFO['exists'] and !$allow_existing) return false; 2583c636ad3SNetali 2593c636ad3SNetali // permission check 2603c636ad3SNetali $perm = auth_quickaclcheck($ID); 2613c636ad3SNetali if (($INFO['exists'] and $perm < AUTH_EDIT) or (!$INFO['exists'] and $perm < AUTH_CREATE)) return false; 2623c636ad3SNetali 2633832d0abSNetali // skip blacklisted namespaces and pages 2643832d0abSNetali if ($this->getConf('blacklist_regex')) { 2653832d0abSNetali if (preg_match('/' . $this->getConf('blacklist_regex') . '/', $ID) === 1) return false; 2663832d0abSNetali } 2673832d0abSNetali 2683832d0abSNetali $split_id = explode(':', $ID); 2693832d0abSNetali $lang_ns = array_shift($split_id); 2703832d0abSNetali // only translate if the current page is in a language namespace 2713832d0abSNetali if (!array_key_exists($lang_ns, $this->langs)) return false; 2723832d0abSNetali 2733832d0abSNetali $org_id = implode(':', $split_id); 27499da9a08SNetali 27599da9a08SNetali // if default lang is in ns: add default ns in front of org id 27699da9a08SNetali if ($this->getConf('default_lang_in_ns')) { 277ff327fe6SNetali $org_id = $this->get_default_lang() . ':' . $org_id; 27899da9a08SNetali } 27999da9a08SNetali 2803832d0abSNetali // check if the original page exists 2813832d0abSNetali if (!page_exists($org_id)) return false; 2823832d0abSNetali 2833832d0abSNetali return true; 2843832d0abSNetali } 2853832d0abSNetali 286153e4498SNetali private function check_do_push_translate(): bool { 287153e4498SNetali global $ID; 28899da9a08SNetali global $INFO; 28999da9a08SNetali 29099da9a08SNetali if (!$INFO['exists']) return false; 29199da9a08SNetali 292a3a51507SNetali // only allow push translation if the user can edit this page 293a3a51507SNetali $perm = auth_quickaclcheck($ID); 294a3a51507SNetali if ($perm < AUTH_EDIT) return false; 295a3a51507SNetali 29699da9a08SNetali // if default language is in namespace: only allow push translation from that namespace 29799da9a08SNetali if($this->getConf('default_lang_in_ns')) { 29899da9a08SNetali $split_id = explode(':', $ID); 29999da9a08SNetali $lang_ns = array_shift($split_id); 30099da9a08SNetali 301ff327fe6SNetali if ($lang_ns !== $this->get_default_lang()) return false; 30299da9a08SNetali } 303153e4498SNetali 304153e4498SNetali $push_langs = $this->get_push_langs(); 305153e4498SNetali // push_langs empty --> push_translate disabled --> abort 306153e4498SNetali if (empty($push_langs)) return false; 307153e4498SNetali 308153e4498SNetali // skip blacklisted namespaces and pages 309153e4498SNetali if ($this->getConf('blacklist_regex')) { 310153e4498SNetali // blacklist regex match --> abort 311153e4498SNetali if (preg_match('/' . $this->getConf('blacklist_regex') . '/', $ID) === 1) return false; 312153e4498SNetali } 313153e4498SNetali 314153e4498SNetali return true; 315153e4498SNetali } 316153e4498SNetali 3170180404cSNetali private function deepl_translate($text, $target_lang, $org_ns): string { 318153e4498SNetali if (!trim($this->getConf('api_key'))) return ''; 3193832d0abSNetali 3200180404cSNetali $text = $this->patch_links($text, $target_lang, $org_ns); 3210180404cSNetali 3223832d0abSNetali $text = $this->insert_ignore_tags($text); 3233832d0abSNetali 3243832d0abSNetali $data = [ 3253832d0abSNetali 'auth_key' => $this->getConf('api_key'), 3260180404cSNetali 'target_lang' => $this->langs[$target_lang], 3273832d0abSNetali 'tag_handling' => 'xml', 3280180404cSNetali 'ignore_tags' => 'ignore', 3293832d0abSNetali 'text' => $text 3303832d0abSNetali ]; 3313832d0abSNetali 3323832d0abSNetali if ($this->getConf('api') == 'free') { 33381931e50SNetali $url = 'https://api-free.deepl.com/v2/translate'; 3343832d0abSNetali } else { 33581931e50SNetali $url = 'https://api.deepl.com/v2/translate'; 3363832d0abSNetali } 3373832d0abSNetali 33881931e50SNetali $http = new DokuHTTPClient(); 33981931e50SNetali $raw_response = $http->post($url, $data); 3403832d0abSNetali 3415f8ab21dSNetali if ($http->status >= 400) { 3425f8ab21dSNetali // add error messages 3435f8ab21dSNetali switch ($http->status) { 3445f8ab21dSNetali case 403: 3455f8ab21dSNetali msg($this->getLang('msg_translation_fail_bad_key'), -1); 3465f8ab21dSNetali break; 3475f8ab21dSNetali case 456: 3485f8ab21dSNetali msg($this->getLang('msg_translation_fail_quota_exceeded'), -1); 3495f8ab21dSNetali break; 3505f8ab21dSNetali default: 3515f8ab21dSNetali msg($this->getLang('msg_translation_fail'), -1); 3525f8ab21dSNetali break; 3535f8ab21dSNetali } 3545f8ab21dSNetali 3553832d0abSNetali // if any error occurred return an empty string 3565f8ab21dSNetali return ''; 3575f8ab21dSNetali } 3583832d0abSNetali 3593832d0abSNetali $json_response = json_decode($raw_response, true); 3603832d0abSNetali $translated_text = $json_response['translations'][0]['text']; 3613832d0abSNetali 3623832d0abSNetali $translated_text = $this->remove_ignore_tags($translated_text); 3633832d0abSNetali 3643832d0abSNetali return $translated_text; 3653832d0abSNetali } 3663832d0abSNetali 367153e4498SNetali private function get_push_langs(): array { 368153e4498SNetali $push_langs = trim($this->getConf('push_langs')); 369153e4498SNetali 370153e4498SNetali if ($push_langs === '') return array(); 371153e4498SNetali 372153e4498SNetali return explode(' ', $push_langs); 373153e4498SNetali } 374153e4498SNetali 3750180404cSNetali private function patch_links($text, $target_lang, $ns): string { 3760180404cSNetali /* 3770180404cSNetali * 1. Find links in [[ aa:bb ]] or [[ aa:bb | cc ]] 3780180404cSNetali * 2. Extract aa:bb 3790180404cSNetali * 3. Check if lang:aa:bb exists 3800180404cSNetali * 3.1. --> Yes --> replace 3810180404cSNetali * 3.2. --> No --> leave it as it is 3820180404cSNetali */ 3833832d0abSNetali 3840180404cSNetali 3850180404cSNetali /* 3860180404cSNetali * LINKS 3870180404cSNetali */ 3880180404cSNetali 3894b84d3cfSNetali preg_match_all('/\[\[([\s\S]*?)(#[\s\S]*?)?((\|)([\s\S]*?))?]]/', $text, $matches, PREG_SET_ORDER); 3900180404cSNetali 3910180404cSNetali foreach ($matches as $match) { 3920180404cSNetali 3930180404cSNetali // external link --> skip 394a3a51507SNetali if (strpos($match[1], '://') !== false) continue; 3950180404cSNetali 396*84cda41fSNetali // skip interwiki links 397*84cda41fSNetali if (strpos($match[1], '>') !== false) continue; 398*84cda41fSNetali 399*84cda41fSNetali // skip windows share links 400*84cda41fSNetali if (strpos($match[1], '\\\\') !== false) continue; 401*84cda41fSNetali 402*84cda41fSNetali $resolved_id = trim($match[1]); 4030180404cSNetali 4040180404cSNetali resolve_pageid($ns, $resolved_id, $exists); 4050180404cSNetali 40653f3766cSNetali $resolved_id_full = $resolved_id; 4070180404cSNetali 4086663bcb5SNetali // if the link already points to a target in a language namespace drop it and add the new language namespace 4096663bcb5SNetali $split_id = explode(':', $resolved_id); 4106663bcb5SNetali $lang_ns = array_shift($split_id); 4116663bcb5SNetali if (array_key_exists($lang_ns, $this->langs)) { 4126663bcb5SNetali $resolved_id = implode(':', $split_id); 4136663bcb5SNetali } 4146663bcb5SNetali 4150180404cSNetali $lang_id = $target_lang . ':' . $resolved_id; 4160180404cSNetali 4170180404cSNetali if (!page_exists($lang_id)) { 41853f3766cSNetali // Page in target lang does not exist --> replace with absolute ID in case it was a relative ID 41953f3766cSNetali $new_link = '[[' . $resolved_id_full . $match[2] . $match[3] . ']]'; 42053f3766cSNetali } else { 42153f3766cSNetali // Page in target lang exists --> replace link 4224b84d3cfSNetali $new_link = '[[' . $lang_id . $match[2] . $match[3] . ']]'; 42353f3766cSNetali } 4240180404cSNetali 4250180404cSNetali $text = str_replace($match[0], $new_link, $text); 4260180404cSNetali 4270180404cSNetali } 4280180404cSNetali 4290180404cSNetali /* 4300180404cSNetali * MEDIA 4310180404cSNetali */ 4320180404cSNetali 433*84cda41fSNetali preg_match_all('/\{\{(([\s\S]*?)(\?[\s\S]*?)?)(\|([\s\S]*?))?}}/', $text, $matches, PREG_SET_ORDER); 4340180404cSNetali 4350180404cSNetali foreach ($matches as $match) { 4360180404cSNetali 4370180404cSNetali // external image --> skip 438a3a51507SNetali if (strpos($match[1], '://') !== false) continue; 439a3a51507SNetali 440a3a51507SNetali // skip things like {{tag>...}} 441a3a51507SNetali if (strpos($match[1], '>') !== false) continue; 4420180404cSNetali 443*84cda41fSNetali // keep alignment 444*84cda41fSNetali $align_left = ""; 445*84cda41fSNetali $align_right = ""; 446*84cda41fSNetali 447*84cda41fSNetali // align left --> space in front of ID 448*84cda41fSNetali if (substr($match[1], 0, 1) == " ") $align_left = " "; 449*84cda41fSNetali // align right --> space behind id 450*84cda41fSNetali if (substr($match[1], -1) == " ") $align_right = " "; 451*84cda41fSNetali 452*84cda41fSNetali $resolved_id = trim($match[2]); 453*84cda41fSNetali $params = trim($match[3]); 4540180404cSNetali 4550180404cSNetali resolve_mediaid($ns, $resolved_id, $exists); 4560180404cSNetali 45753f3766cSNetali $resolved_id_full = $resolved_id; 4580180404cSNetali 4596663bcb5SNetali // if the link already points to a target in a language namespace drop it and add the new language namespace 4606663bcb5SNetali $split_id = explode(':', $resolved_id); 4616663bcb5SNetali $lang_ns = array_shift($split_id); 4626663bcb5SNetali if (array_key_exists($lang_ns, $this->langs)) { 4636663bcb5SNetali $resolved_id = implode(':', $split_id); 4646663bcb5SNetali } 4656663bcb5SNetali 4660180404cSNetali $lang_id = $target_lang . ':' . $resolved_id; 4670180404cSNetali 4680180404cSNetali $lang_id_fn = mediaFN($lang_id); 4690180404cSNetali 4700180404cSNetali if (!file_exists($lang_id_fn)) { 47153f3766cSNetali // media in target lang does not exist --> replace with absolute ID in case it was a relative ID 472*84cda41fSNetali $new_link = '{{' . $align_left . $resolved_id_full . $params . $align_right . $match[4] . '}}'; 47353f3766cSNetali } else { 47453f3766cSNetali // media in target lang exists --> replace it 475*84cda41fSNetali $new_link = '{{' . $align_left . $lang_id . $params . $align_right . $match[4] . '}}'; 47653f3766cSNetali } 4770180404cSNetali 4780180404cSNetali $text = str_replace($match[0], $new_link, $text); 4790180404cSNetali 4800180404cSNetali } 4810180404cSNetali 4820180404cSNetali return $text; 4830180404cSNetali } 4840180404cSNetali 4850180404cSNetali private function insert_ignore_tags($text): string { 4860180404cSNetali // ignore every other xml-like tags (the tags themselves, not their content), otherwise deepl would break the formatting 4870180404cSNetali $text = preg_replace('/<[\s\S]+?>/', '<ignore>${0}</ignore>', $text); 4880180404cSNetali 4891cd781c4SNetali // prevent deepl from breaking headings 4901cd781c4SNetali $text = preg_replace('/={1,6}/', '<ignore>${0}</ignore>', $text); 4911cd781c4SNetali 492a3a51507SNetali // fix for plugins like tag or template 493a3a51507SNetali $text = preg_replace('/\{\{[\s\w]+?>[\s\S]*?}}/', '<ignore>${0}</ignore>', $text); 4940180404cSNetali 4953b1ff295SNetali // ignore links in wikitext (outside of dokuwiki-links) 4963b1ff295SNetali $text = preg_replace('/\S+:\/\/\S+/', '<ignore>${0}</ignore>', $text); 4973b1ff295SNetali 4980180404cSNetali // ignore link/media ids but translate the text (if existing) 4994b84d3cfSNetali $text = preg_replace('/\[\[([\s\S]*?)(#[\s\S]*?)?((\|)([\s\S]*?))?]]/', '<ignore>[[${1}${2}${4}</ignore>${5}<ignore>]]</ignore>', $text); 5000180404cSNetali $text = preg_replace('/\{\{([\s\S]*?)(\?[\s\S]*?)?((\|)([\s\S]*?))?}}/', '<ignore>{{${1}${2}${4}</ignore>${5}<ignore>}}</ignore>', $text); 5010180404cSNetali 5023b1ff295SNetali // prevent deepl from messing with tables 5033b1ff295SNetali $text = str_replace("^", "<ignore>^</ignore>", $text); 5043b1ff295SNetali $text = str_replace("|", "<ignore>|</ignore>", $text); 5053b1ff295SNetali 5060180404cSNetali // prevent deepl from doing strange things with dokuwiki syntax 5070180404cSNetali $text = str_replace("''", "<ignore>''</ignore>", $text); 508f37d7ce7SNetali $text = str_replace("//", "<ignore>//</ignore>", $text); 509f37d7ce7SNetali $text = str_replace("**", "<ignore>**</ignore>", $text); 510f37d7ce7SNetali $text = str_replace("__", "<ignore>__</ignore>", $text); 5110180404cSNetali $text = str_replace("\\\\", "<ignore>\\\\</ignore>", $text); 5120180404cSNetali 51313221d46SNetali // prevent deepl from messing with smileys 51413221d46SNetali $smileys = array_keys(getSmileys()); 51513221d46SNetali foreach ($smileys as $smiley) { 51613221d46SNetali $text = str_replace($smiley, "<ignore>" . $smiley . "</ignore>", $text); 51713221d46SNetali } 51813221d46SNetali 5190180404cSNetali // ignore code tags 5200180404cSNetali $text = preg_replace('/(<php[\s\S]*?>[\s\S]*?<\/php>)/', '<ignore>${1}</ignore>', $text); 521057940f7SNetali $text = preg_replace('/(<file[\s\S]*?>[\s\S]*?<\/file>)/', '<ignore>${1}</ignore>', $text); 522057940f7SNetali $text = preg_replace('/(<code[\s\S]*?>[\s\S]*?<\/code>)/', '<ignore>${1}</ignore>', $text); 523057940f7SNetali 5240180404cSNetali // ignore the expressions from the ignore list 5253832d0abSNetali $ignored_expressions = explode(':', $this->getConf('ignored_expressions')); 5263832d0abSNetali 5273832d0abSNetali foreach ($ignored_expressions as $expression) { 5283832d0abSNetali $text = str_replace($expression, '<ignore>' . $expression . '</ignore>', $text); 5293832d0abSNetali } 5303832d0abSNetali 5313832d0abSNetali return $text; 5323832d0abSNetali } 5333832d0abSNetali 5343832d0abSNetali private function remove_ignore_tags($text): string { 5353832d0abSNetali $ignored_expressions = explode(':', $this->getConf('ignored_expressions')); 5363832d0abSNetali 5373832d0abSNetali foreach ($ignored_expressions as $expression) { 5383832d0abSNetali $text = str_replace('<ignore>' . $expression . '</ignore>', $expression, $text); 5393832d0abSNetali } 5403832d0abSNetali 5413b1ff295SNetali // prevent deepl from messing with tables 5423b1ff295SNetali $text = str_replace("<ignore>^</ignore>", "^", $text); 5433b1ff295SNetali $text = str_replace("<ignore>|</ignore>", "|", $text); 5443b1ff295SNetali 5450180404cSNetali $text = str_replace("<ignore>''</ignore>", "''", $text); 546f37d7ce7SNetali $text = str_replace("<ignore>//</ignore>", "//", $text); 547f37d7ce7SNetali $text = str_replace("<ignore>**</ignore>", "**", $text); 548f37d7ce7SNetali $text = str_replace("<ignore>__</ignore>", "__", $text); 5490180404cSNetali $text = str_replace("<ignore>\\\\</ignore>", "\\\\", $text); 5500180404cSNetali 5513b1ff295SNetali // ignore links in wikitext (outside of dokuwiki-links) 5523b1ff295SNetali $text = preg_replace('/<ignore>(\S+:\/\/\S+)<\/ignore>/', '${1}', $text); 5533b1ff295SNetali 5544b84d3cfSNetali $text = preg_replace('/<ignore>\[\[([\s\S]*?)(\|)?(<\/ignore>)([\s\S]*?)?<ignore>]]<\/ignore>/', '[[${1}${2}${4}]]', $text); 5554b84d3cfSNetali $text = preg_replace('/<ignore>\{\{([\s\S]*?)(\|)?(<\/ignore>)([\s\S]*?)?<ignore>}}<\/ignore>/', '{{${1}${2}${4}}}', $text); 5564b84d3cfSNetali 55713221d46SNetali // prevent deepl from messing with smileys 55813221d46SNetali $smileys = array_keys(getSmileys()); 55913221d46SNetali foreach ($smileys as $smiley) { 56013221d46SNetali $text = str_replace("<ignore>" . $smiley . "</ignore>", $smiley, $text); 56113221d46SNetali } 56213221d46SNetali 5630180404cSNetali $text = preg_replace('/<ignore>(<php[\s\S]*?>[\s\S]*?<\/php>)<\/ignore>/', '${1}', $text); 5640180404cSNetali $text = preg_replace('/<ignore>(<file[\s\S]*?>[\s\S]*?<\/file>)<\/ignore>/', '${1}', $text); 5650180404cSNetali $text = preg_replace('/<ignore>(<code[\s\S]*?>[\s\S]*?<\/code>)<\/ignore>/', '${1}', $text); 5660180404cSNetali 567a3a51507SNetali // fix for plugins like tag or template 568a3a51507SNetali $text = preg_replace('/<ignore>(\{\{[\s\w]+?>[\s\S]*?}})<\/ignore>/', '${1}', $text); 5690180404cSNetali 5701cd781c4SNetali // prevent deepl from breaking headings 5711cd781c4SNetali $text = preg_replace('/<ignore>(={1,6})<\/ignore>/','${1}', $text); 5721cd781c4SNetali 5731cd781c4SNetali // ignore every other xml-like tags (the tags themselves, not their content), otherwise deepl would break the formatting 5741cd781c4SNetali $text = preg_replace('/<ignore>(<[\s\S]+?>)<\/ignore>/', '${1}', $text); 5751cd781c4SNetali 5760180404cSNetali // restore < and > for example from arrows (-->) in wikitext 5770180404cSNetali $text = str_replace('>', '>', $text); 5780180404cSNetali $text = str_replace('<', '<', $text); 5790180404cSNetali 5803b1ff295SNetali // restore & in wikitext 5813b1ff295SNetali $text = str_replace('&', '&', $text); 5823b1ff295SNetali 5833832d0abSNetali return $text; 5843832d0abSNetali } 5853832d0abSNetali} 5863832d0abSNetali 587