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