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