xref: /plugin/deeplautotranslate/action.php (revision 3832d0ab6c576668406240eabbafad4a5e131639)
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