1<?php 2 3use dokuwiki\Extension\ActionPlugin; 4use dokuwiki\Extension\EventHandler; 5use dokuwiki\Extension\Event; 6 7/** 8 * Translation Plugin: Simple multilanguage plugin 9 * 10 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 11 * @author Andreas Gohr <andi@splitbrain.org> 12 * @author Guy Brand <gb@isis.u-strasbg.fr> 13 */ 14class action_plugin_translation extends ActionPlugin 15{ 16 /** 17 * For the helper plugin 18 * @var helper_plugin_translation 19 */ 20 protected $helper; 21 22 /** 23 * Constructor. Load helper plugin 24 */ 25 public function __construct() 26 { 27 $this->helper = plugin_load('helper', 'translation'); 28 } 29 30 /** 31 * Registers a callback function for a given event 32 * 33 * @param EventHandler $controller 34 */ 35 public function register(EventHandler $controller) 36 { 37 if ($this->getConf('translateui')) { 38 $controller->register_hook('INIT_LANG_LOAD', 'BEFORE', $this, 'translateUI'); 39 $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'translateJS'); 40 $controller->register_hook('JS_CACHE_USE', 'BEFORE', $this, 'translateJSCache'); 41 } else { 42 $controller->register_hook('TPL_CONTENT_DISPLAY', 'BEFORE', $this, 'addLanguageAttributes'); 43 } 44 45 if ($this->getConf('redirectstart')) { 46 $controller->register_hook('DOKUWIKI_STARTED', 'BEFORE', $this, 'redirectStartPage'); 47 } 48 49 $controller->register_hook('TPL_METAHEADER_OUTPUT', 'BEFORE', $this, 'addHrefLangAttributes'); 50 $controller->register_hook('COMMON_PAGETPL_LOAD', 'AFTER', $this, 'handlePageTemplates'); 51 $controller->register_hook('SEARCH_QUERY_PAGELOOKUP', 'AFTER', $this, 'sortSearchResults'); 52 } 53 54 /** 55 * Hook Callback. Set the language for the UI 56 * 57 * @param Event $event INIT_LANG_LOAD 58 */ 59 public function translateUI(Event $event) 60 { 61 global $conf; 62 global $ACT; 63 global $INPUT; 64 65 // $ID is not set yet, so we have to get it ourselves 66 $id = getID(); 67 68 // For ID based access, get the language from the ID, try request param or session otherwise 69 if ( 70 isset($ACT) && 71 in_array(act_clean($ACT), ['show', 'recent', 'diff', 'edit', 'preview', 'source', 'subscribe']) 72 ) { 73 $locale = $this->helper->getLangPart($id ?? ''); 74 $_SESSION[DOKU_COOKIE]['translationlc'] = $locale; // IDs always reset the language 75 } elseif ($INPUT->has('lang')) { 76 $locale = $INPUT->str('lang'); 77 } else { 78 $locale = $_SESSION[DOKU_COOKIE]['translationlc'] ?? ''; 79 } 80 81 // ensure a valid locale was given by using it as fake ID 82 $locale = $this->helper->getLangPart("$locale:foo"); 83 84 // if the language is not the default language, set the language 85 if ($locale && $locale !== $conf['lang']) { 86 $conf['lang_before_translation'] = $conf['lang']; //store for later access in syntax plugin 87 $event->data = $locale; 88 $conf['lang'] = $locale; 89 } 90 } 91 92 /** 93 * Hook Callback. Pass language code to JavaScript dispatcher 94 * 95 * @param Event $event TPL_METAHEADER_OUTPUT 96 */ 97 public function translateJS(Event $event) 98 { 99 global $conf; 100 101 $count = count($event->data['script']); 102 for ($i = 0; $i < $count; $i++) { 103 if ( 104 array_key_exists('src', $event->data['script'][$i]) && 105 strpos($event->data['script'][$i]['src'], '/lib/exe/js.php') !== false 106 ) { 107 $event->data['script'][$i]['src'] .= '&lang=' . hsc($conf['lang']); 108 } 109 } 110 } 111 112 /** 113 * Hook Callback. Cache JavaScript per language 114 * 115 * @param Event $event JS_CACHE_USE 116 */ 117 public function translateJSCache(Event $event) 118 { 119 global $conf; 120 121 // reuse the constructor to reinitialize the cache key 122 $event->data->__construct( 123 $event->data->key . $conf['lang'], 124 $event->data->ext 125 ); 126 } 127 128 /** 129 * Hook Callback. Add lang and dir attributes when UI isn't translated 130 * 131 * @param Event $event TPL_CONTENT_DISPLAY 132 */ 133 public function addLanguageAttributes(Event $event) 134 { 135 global $ID; 136 global $conf; 137 138 if (!$this->helper->istranslatable($ID)) return; 139 $locale = $this->helper->getLangPart($ID ?? ''); 140 141 if ($locale && $locale !== $conf['lang']) { 142 if (file_exists(DOKU_INC . 'inc/lang/' . $locale . '/lang.php')) { 143 $lang = []; 144 include(DOKU_INC . 'inc/lang/' . $locale . '/lang.php'); 145 $direction = $lang['direction'] ?? 'ltr'; 146 147 $event->data = '<div lang="' . hsc($locale) . '" dir="' . hsc($direction) . '">' . 148 $event->data . 149 '</div>'; 150 } 151 } 152 } 153 154 /** 155 * Hook Callback. Redirect to translated start page 156 * 157 * @param Event $event DOKUWIKI_STARTED 158 */ 159 public function redirectStartPage(Event $event) 160 { 161 global $ID; 162 global $ACT; 163 global $conf; 164 165 if ($ID == $conf['start'] && $ACT == 'show') { 166 $lc = $this->helper->getBrowserLang(); 167 168 [$translatedStartpage, ] = $this->helper->buildTransID($lc, $conf['start']); 169 if (cleanID($translatedStartpage) !== cleanID($ID)) { 170 send_redirect(wl(cleanID($translatedStartpage), '', true)); 171 } 172 } 173 } 174 175 /** 176 * Hook Callback. Add hreflang attributes to the page header 177 * 178 * @param Event $event TPL_METAHEADER_OUTPUT 179 */ 180 public function addHrefLangAttributes(Event $event) 181 { 182 global $ID; 183 global $conf; 184 185 if (!$this->helper->istranslatable($ID)) return; 186 187 $translations = $this->helper->getAvailableTranslations($ID); 188 if ($translations) { 189 foreach ($translations as $lc => $translation) { 190 $event->data['link'][] = [ 191 'rel' => 'alternate', 192 'hreflang' => $lc, 193 'href' => wl(cleanID($translation), '', true), 194 ]; 195 } 196 } 197 198 $default = $conf['lang_before_translation'] ?? $conf['lang']; 199 $defaultlink = $this->helper->buildTransID($default, ($this->helper->getTransParts($ID))[1])[0]; 200 $event->data['link'][] = [ 201 'rel' => 'alternate', 202 'hreflang' => 'x-default', 203 'href' => wl(cleanID($defaultlink), '', true), 204 ]; 205 } 206 207 /** 208 * Hook Callback. Make current language available as page template placeholder and handle 209 * original language copying 210 * 211 * @param Event $event COMMON_PAGETPL_LOAD 212 */ 213 public function handlePageTemplates(Event $event) 214 { 215 global $ID; 216 217 // load orginal content as template? 218 if ($this->getConf('copytrans') && $this->helper->istranslatable($ID, false)) { 219 // look for existing translations 220 $translations = $this->helper->getAvailableTranslations($ID); 221 if ($translations) { 222 // find original language (might've been provided via parameter or use first translation) 223 $orig = (string)$_REQUEST['fromlang']; 224 if (!$orig) $orig = array_key_first($translations); 225 226 // load file 227 $origfile = $translations[$orig]; 228 $event->data['tpl'] = io_readFile(wikiFN($origfile)); 229 230 // prefix with warning 231 $warn = io_readFile($this->localFN('totranslate')); 232 if ($warn) $warn .= "\n\n"; 233 $event->data['tpl'] = $warn . $event->data['tpl']; 234 235 // show user a choice of translations if any 236 if (count($translations) > 1) { 237 $links = []; 238 foreach (array_keys($translations) as $t) { 239 $links[] = '<a href="' . wl($ID, ['do' => 'edit', 'fromlang' => $t]) . '">' . 240 $this->helper->getLocalName($t) . 241 '</a>'; 242 } 243 244 msg( 245 sprintf( 246 $this->getLang('transloaded'), 247 $this->helper->getLocalName($orig), 248 implode(', ', $links) 249 ) 250 ); 251 } 252 } 253 } 254 255 // apply placeholders 256 $event->data['tpl'] = str_replace('@LANG@', $this->helper->realLC(''), $event->data['tpl']); 257 $event->data['tpl'] = str_replace('@TRANS@', $this->helper->getLangPart($ID), $event->data['tpl']); 258 } 259 260 /** 261 * Hook Callback. Resort page match results so that results are ordered by translation, having the 262 * default language first 263 * 264 * @param Event $event SEARCH_QUERY_PAGELOOKUP 265 */ 266 public function sortSearchResults(Event $event) 267 { 268 // sort into translation slots 269 $res = []; 270 foreach ($event->result as $r => $t) { 271 $tr = $this->helper->getLangPart($r); 272 if (!isset($res["x$tr"]) || !is_array($res["x$tr"])) { 273 $res["x$tr"] = []; 274 } 275 $res["x$tr"][] = [$r, $t]; 276 } 277 // sort by translations 278 ksort($res); 279 // combine 280 $event->result = []; 281 foreach ($res as $r) { 282 foreach ($r as $l) { 283 $event->result[$l[0]] = $l[1]; 284 } 285 } 286 } 287} 288