1 <?php
2
3 4 5 6 7
8 class rex_i18n
9 {
10 11 12
13 private static $locales = [];
14 15 16
17 private static $directories = [];
18 19 20
21 private static $loaded = [];
22 23 24
25 private static $locale = null;
26 27 28
29 private static $msg = [];
30
31 32 33 34 35 36 37 38
39 public static function setLocale($locale, $phpSetLocale = true)
40 {
41 $saveLocale = self::$locale;
42 self::$locale = $locale;
43
44 if (empty(self::$loaded[$locale])) {
45 self::loadAll($locale);
46 }
47
48 if ($phpSetLocale) {
49 $locales = [];
50 foreach (explode(',', trim(self::msg('setlocale'))) as $locale) {
51 $locales[] = $locale . '.UTF-8';
52 $locales[] = $locale . '.UTF8';
53 $locales[] = $locale . '.utf-8';
54 $locales[] = $locale . '.utf8';
55 $locales[] = $locale;
56 }
57
58 setlocale(LC_ALL, $locales);
59 }
60
61 return $saveLocale;
62 }
63
64 65 66 67 68
69 public static function getLocale()
70 {
71 return self::$locale;
72 }
73
74 75 76 77 78
79 public static function getLanguage()
80 {
81 list($lang, $country) = explode('_', self::$locale, 2);
82 return $lang;
83 }
84
85 86 87 88 89
90 public static function addDirectory($dir)
91 {
92 $dir = rtrim($dir, DIRECTORY_SEPARATOR);
93
94 if (in_array($dir, self::$directories, true)) {
95 return;
96 }
97
98 self::$directories[] = $dir;
99
100 foreach (self::$loaded as $locale => $_) {
101 self::loadFile($dir, $locale);
102 }
103 }
104
105 106 107 108 109 110 111 112
113 public static function msg($key)
114 {
115 return self::getMsg($key, true, func_get_args());
116 }
117
118 119 120 121 122 123 124 125
126 public static function rawMsg($key)
127 {
128 return self::getMsg($key, false, func_get_args());
129 }
130
131 132 133 134 135 136 137 138 139
140 public static function msgInLocale($key, $locale)
141 {
142 $args = func_get_args();
143 $args[1] = $key;
144
145 array_shift($args);
146 return self::getMsg($key, true, $args, $locale);
147 }
148
149 150 151 152 153 154 155 156 157
158 public static function rawMsgInLocale($key, $locale)
159 {
160 $args = func_get_args();
161 $args[1] = $key;
162
163 array_shift($args);
164 return self::getMsg($key, false, $args, $locale);
165 }
166
167 168 169 170 171 172 173 174 175
176 private static function getMsgFallback($key, array $args, $locale)
177 {
178 $fallback = "[translate:$key]";
179
180 $msg = rex_extension::registerPoint(new rex_extension_point('I18N_MISSING_TRANSLATION', $fallback, [
181 'key' => $key,
182 'args' => $args,
183 ]));
184
185 if ($msg !== $fallback) {
186 return $msg;
187 }
188
189 foreach (rex::getProperty('lang_fallback', []) as $fallbackLocale) {
190 if ($locale === $fallbackLocale) {
191 continue;
192 }
193
194 if (empty(self::$loaded[$fallbackLocale])) {
195 self::loadAll($fallbackLocale);
196 }
197
198 if (isset(self::$msg[$fallbackLocale][$key])) {
199 return self::$msg[$fallbackLocale][$key];
200 }
201 }
202
203 return $fallback;
204 }
205
206 207 208 209 210 211 212
213 public static function hasMsg($key)
214 {
215 return isset(self::$msg[self::$locale][$key]);
216 }
217
218 219 220 221 222 223 224 225 226 227
228 private static function getMsg($key, $htmlspecialchars, array $args, $locale = null)
229 {
230 if (!self::$locale) {
231 self::$locale = rex::getProperty('lang');
232 }
233
234 if (!$locale) {
235 $locale = self::$locale;
236 }
237
238 if (empty(self::$loaded[$locale])) {
239 self::loadAll($locale);
240 }
241
242 if (isset(self::$msg[$locale][$key])) {
243 $msg = self::$msg[$locale][$key];
244 } else {
245 $msg = self::getMsgFallback($key, $args, $locale);
246 }
247
248 $patterns = [];
249 $replacements = [];
250 $argNum = count($args);
251 if ($argNum > 1) {
252 for ($i = 1; $i < $argNum; ++$i) {
253
254 $patterns[] = '/\{' . ($i - 1) . '\}/';
255 $replacements[] = $args[$i];
256 }
257 }
258
259 $msg = preg_replace($patterns, $replacements, $msg);
260
261 if ($htmlspecialchars) {
262 $msg = rex_escape($msg);
263 $msg = preg_replace('@<(/?(?:b|i|code|kbd|var)|br ?/?)>@i', '<$1>', $msg);
264 }
265
266 return $msg;
267 }
268
269 270 271 272 273 274 275
276 public static function hasMsgOrFallback($key)
277 {
278 if (isset(self::$msg[self::$locale][$key])) {
279 return true;
280 }
281
282 foreach (rex::getProperty('lang_fallback', []) as $locale) {
283 if (self::$locale === $locale) {
284 continue;
285 }
286
287 if (empty(self::$loaded[$locale])) {
288 self::loadAll($locale);
289 }
290
291 if (isset(self::$msg[$locale][$key])) {
292 return true;
293 }
294 }
295
296 return false;
297 }
298
299 300 301 302 303 304
305 public static function addMsg($key, $msg)
306 {
307 self::$msg[self::$locale][$key] = $msg;
308 }
309
310 311 312 313 314
315 public static function getLocales()
316 {
317 if (empty(self::$locales) && isset(self::$directories[0]) && is_readable(self::$directories[0])) {
318 self::$locales = [];
319
320 foreach (rex_finder::factory(self::$directories[0])->filesOnly() as $file) {
321 if (preg_match("/^(\w+)\.lang$/", $file->getFilename(), $matches)) {
322 self::$locales[] = $matches[1];
323 }
324 }
325 }
326
327 return self::$locales;
328 }
329
330 331 332 333 334 335 336 337 338 339 340
341 public static function translate($text, $use_htmlspecialchars = true, callable $i18nFunction = null)
342 {
343 if (!is_string($text)) {
344 throw new InvalidArgumentException('Expecting $text to be a String, "' . gettype($text) . '" given!');
345 }
346
347 $tranKey = 'translate:';
348 $transKeyLen = strlen($tranKey);
349 if (substr($text, 0, $transKeyLen) == $tranKey) {
350 if (!$i18nFunction) {
351 if ($use_htmlspecialchars) {
352 return self::msg(substr($text, $transKeyLen));
353 }
354 return self::rawMsg(substr($text, $transKeyLen));
355 }
356
357 return call_user_func($i18nFunction, substr($text, $transKeyLen));
358 }
359 if ($use_htmlspecialchars) {
360 return rex_escape($text);
361 }
362 return $text;
363 }
364
365 366 367 368 369 370 371 372 373 374 375
376 public static function translateArray($array, $use_htmlspecialchars = true, callable $i18nFunction = null)
377 {
378 if (is_array($array)) {
379 foreach ($array as $key => $value) {
380 if (is_string($value)) {
381 $array[$key] = self::translate($value, $use_htmlspecialchars, $i18nFunction);
382 } else {
383 $array[$key] = self::translateArray($value, $use_htmlspecialchars, $i18nFunction);
384 }
385 }
386 return $array;
387 }
388 if (is_string($array)) {
389 return self::translate($array, $use_htmlspecialchars, $i18nFunction);
390 }
391 if (null === $array || is_scalar($array)) {
392 return $array;
393 }
394 throw new InvalidArgumentException('Expecting $text to be a String or Array of Scalar, "' . gettype($array) . '" given!');
395 }
396
397 398 399 400 401 402
403 private static function loadFile($dir, $locale)
404 {
405 $file = $dir.DIRECTORY_SEPARATOR.$locale.'.lang';
406
407 if (
408 ($content = rex_file::get($file)) &&
409 preg_match_all('/^([^=\s]+)\h*=\h*(\S.*)(?<=\S)/m', $content, $matches, PREG_SET_ORDER)
410 ) {
411 foreach ($matches as $match) {
412 self::$msg[$locale][$match[1]] = $match[2];
413 }
414 }
415 }
416
417 418 419 420 421
422 private static function loadAll($locale)
423 {
424 foreach (self::$directories as $dir) {
425 self::loadFile($dir, $locale);
426 }
427
428 self::$loaded[$locale] = true;
429 }
430 }
431