1: <?php
2: /**
3: * This file contains the implementation of the interface ILanguageManager
4: * in the class LanguageManager for working with localization of texts.
5: *
6: * @package System
7: *
8: * @author Oleg Schildt
9: */
10:
11: namespace SmartFactory;
12:
13: use \SmartFactory\Interfaces\ILanguageManager;
14:
15: /**
16: * Class for working with localization of texts.
17: *
18: * @author Oleg Schildt
19: */
20: class LanguageManager implements ILanguageManager
21: {
22: /**
23: * Internal variable for storing the path with the localization files.
24: *
25: * @var string
26: *
27: * @author Oleg Schildt
28: */
29: protected string $localization_path = "";
30:
31: /**
32: * Internal variable for storing the paths of the additional localization files.
33: *
34: * @var string|array
35: *
36: * @author Oleg Schildt
37: */
38: protected string|array $additional_localization_files = [];
39:
40: /**
41: * Internal variable for storing the fallback language.
42: * If set and a translation is missing on a language, the translation on this language will be used.
43: *
44: * @var string
45: *
46: * @author Oleg Schildt
47: */
48: protected string $use_fallback_language = "";
49:
50: /**
51: * Internal variable for storing the current context.
52: *
53: * @var string
54: *
55: * @see LanguageManager::getContext()
56: *
57: * @author Oleg Schildt
58: */
59: static protected string $context = "default";
60:
61: /**
62: * Internal variable for storing the state whether the dictionary is loaded or not.
63: *
64: * @var bool
65: *
66: * @author Oleg Schildt
67: */
68: protected bool $dictionary_loaded = false;
69:
70: /**
71: * Internal variable for storing the state whether the APCU should be used.
72: *
73: * @var bool
74: *
75: * @author Oleg Schildt
76: */
77: protected bool $use_apcu = false;
78:
79: /**
80: * Internal variable for storing the state whether the last selected language can be stored to cookie.
81: *
82: * @var bool
83: *
84: * @author Oleg Schildt
85: */
86: protected bool $use_cookie = false;
87:
88: /**
89: * Internal variable for storing the cookie path.
90: *
91: * @var string
92: *
93: * @author Oleg Schildt
94: */
95: protected string $cookie_path = "/";
96:
97: /**
98: * Internal variable for storing the state whether the E_USER_NOTICE is triggered in the case of missing translations.
99: *
100: * @var bool
101: *
102: * @author Oleg Schildt
103: */
104: protected bool $warn_missing = true;
105:
106: /**
107: * Internal array for storing the list of supported languages.
108: *
109: * @var array
110: *
111: * @author Oleg Schildt
112: */
113: static protected array $supported_languages = [];
114:
115: /**
116: * Internal variable for storing the current language.
117: *
118: * @var array
119: *
120: * @author Oleg Schildt
121: */
122: static protected array $current_language = [];
123:
124: /**
125: * Internal array for storing the list of language name translations.
126: *
127: * @var array
128: *
129: * @author Oleg Schildt
130: */
131: static protected array $languages = [];
132:
133: /**
134: * Internal array for storing the list of country name translations.
135: *
136: * @var array
137: *
138: * @author Oleg Schildt
139: */
140: static protected array $countries = [];
141:
142: /**
143: * Internal array for storing the list of text translations.
144: *
145: * @var array
146: *
147: * @author Oleg Schildt
148: */
149: static protected array $texts = [];
150:
151: /**
152: * Initializes the language manager with parameters.
153: *
154: * @param array $parameters
155: * Settings for logging as an associative array in the form key => value:
156: *
157: * - $parameters["localization_path"] - the path where the localization files are stored.
158: *
159: * - $parameters["use_cookie"] - if set to true, the last selected language is stored to cookie.
160: *
161: * - $parameters["use_fallback_language"] - if set and a translation is missing on a language, the translation on this language will be used.
162: *
163: * - $parameters["cookie_path"] - Cookie path.
164: *
165: * - $parameters["use_apcu"] - if installed, apcu can be used to cache the translations in the memory.
166: *
167: * - $parameters["warn_missing"] - If it is set to true, the E_USER_NOTICE is triggered in the case of missing translations.
168: *
169: * @return void
170: *
171: * @throws \Exception
172: * It might throw an exception in the case of any errors.
173: *
174: * @author Oleg Schildt
175: */
176: public function init(array $parameters): void
177: {
178: if (!empty($parameters["localization_path"])) {
179: $this->localization_path = $parameters["localization_path"];
180: }
181:
182: if (!empty($parameters["use_apcu"])) {
183: $this->use_apcu = $parameters["use_apcu"];
184: }
185:
186: if (!empty($parameters["use_cookie"])) {
187: $this->use_cookie = $parameters["use_cookie"];
188: }
189:
190: if (!empty($parameters["cookie_path"])) {
191: $this->cookie_path = $parameters["cookie_path"];
192: }
193:
194: if (!empty($parameters["use_fallback_language"])) {
195: $this->use_fallback_language = $parameters["use_fallback_language"];
196: }
197:
198: if (!empty($parameters["warn_missing"])) {
199: $this->warn_missing = $parameters["warn_missing"];
200: }
201: }
202:
203: /**
204: * Adds additional localization files to the dictionary.
205: *
206: * @param string $localization_file
207: * The path of the additional localization file.
208: *
209: * @return void
210: *
211: * @throws \Exception
212: * It might throw an exception in the case of any errors.
213: *
214: * @author Oleg Schildt
215: */
216: public function addLocalizationFile(string $localization_file): void
217: {
218: $this->additional_localization_files[] = $localization_file;
219: }
220:
221: /**
222: * This is function for loading the translations from the source JSON file.
223: *
224: * @return void
225: *
226: * @throws \Exception
227: * It might throw the following exceptions in the case of any errors:
228: *
229: * - if the translation file is invalid.
230: *
231: * @author Oleg Schildt
232: */
233: public function loadDictionary(): void
234: {
235: if ($this->dictionary_loaded) {
236: return;
237: }
238:
239: if ($this->use_apcu) {
240: do {
241: if (!apcu_exists("dictionary_supported_languages")) {
242: break;
243: }
244:
245: self::$supported_languages = apcu_fetch("dictionary_supported_languages");
246: if (empty(self::$supported_languages)) {
247: break;
248: }
249:
250: if (!apcu_exists("dictionary_languages")) {
251: break;
252: }
253: self::$languages = apcu_fetch("dictionary_languages");
254: if (empty(self::$languages)) {
255: break;
256: }
257:
258: if (!apcu_exists("dictionary_countries")) {
259: break;
260: }
261: self::$countries = apcu_fetch("dictionary_countries");
262: if (empty(self::$countries)) {
263: break;
264: }
265:
266: if (!apcu_exists("dictionary_texts")) {
267: break;
268: }
269:
270: self::$texts = apcu_fetch("dictionary_texts");
271: if (empty(self::$texts)) {
272: break;
273: }
274:
275: return;
276: } while (false);
277: }
278:
279: $json_array = [];
280:
281: $json = file_get_contents($this->localization_path . "config.json");
282: if ($json === false) {
283: throw new \Exception("Translation file '" . $this->localization_path . "config.json" . "' cannot be loaded or does not exist!");
284: }
285:
286: try {
287: json_to_array($json, $json_array);
288: } catch (\Throwable $ex) {
289: throw new \Exception("Translation file '" . $this->localization_path . "config.json" . "' is invalid!" . "\n\n" . $ex->getMessage());
290: }
291:
292: $json = file_get_contents($this->localization_path . "languages.json");
293: if ($json === false) {
294: throw new \Exception("Translation file '" . $this->localization_path . "languages.json" . "' cannot be loaded or does not exist!");
295: }
296:
297: try {
298: $json_array["languages"] = [];
299: json_to_array($json, $json_array["languages"]);
300: } catch (\Throwable $ex) {
301: throw new \Exception("Translation file '" . $this->localization_path . "languages.json" . "' is invalid!" . "\n\n" . $ex->getMessage());
302: }
303:
304: $json = file_get_contents($this->localization_path . "countries.json");
305: if ($json === false) {
306: throw new \Exception("Translation file '" . $this->localization_path . "countries.json" . "' cannot be loaded or does not exist!");
307: }
308:
309: try {
310: $json_array["countries"] = [];
311: json_to_array($json, $json_array["countries"]);
312: } catch (\Throwable $ex) {
313: throw new \Exception("Translation file '" . $this->localization_path . "countries.json" . "' is invalid!" . "\n\n" . $ex->getMessage());
314: }
315:
316: $json = file_get_contents($this->localization_path . "texts.json");
317: if ($json === false) {
318: throw new \Exception("Translation file '" . $this->localization_path . "texts.json" . "' cannot be loaded or does not exist!");
319: }
320:
321: try {
322: $json_array["texts"] = [];
323: json_to_array($json, $json_array["texts"]);
324: } catch (\Throwable $ex) {
325: throw new \Exception("Translation file '" . $this->localization_path . "texts.json" . "' is invalid!" . "\n\n" . $ex->getMessage());
326: }
327:
328: if (!empty($this->additional_localization_files)) {
329: foreach ($this->additional_localization_files as $localization_file) {
330: $json = file_get_contents($localization_file);
331: if ($json === false) {
332: throw new \Exception("Translation file '" . $localization_file . "' cannot be loaded or does not exist!");
333: }
334:
335: try {
336: $translation_texts = [];
337: json_to_array($json, $translation_texts);
338:
339: $json_array["texts"] = array_merge($json_array["texts"], $translation_texts);
340: } catch (\Throwable $ex) {
341: throw new \Exception("Translation file '" . $localization_file . "' is invalid!" . "\n\n" . $ex->getMessage());
342: }
343: }
344: }
345:
346: if (!empty($json_array["interface_languages"])) {
347: foreach ($json_array["interface_languages"] as $lang_code) {
348: self::$supported_languages[$lang_code] = $lang_code;
349: }
350: }
351:
352: if (!empty($json_array["languages"])) {
353: foreach ($json_array["languages"] as $text_id =>&$translations) {
354: foreach ($translations as $lang_code => $translation) {
355: self::$languages[$lang_code][$text_id] = $translation;
356: }
357: }
358: }
359:
360: if (!empty($json_array["countries"])) {
361: foreach ($json_array["countries"] as $text_id => $translations) {
362: foreach ($translations as $lang_code => $translation) {
363: self::$countries[$lang_code][$text_id] = $translation;
364: }
365: }
366: }
367:
368: if (!empty($json_array["texts"])) {
369: foreach ($json_array["texts"] as $text_id => $translations) {
370: foreach ($translations as $lang_code => $translation) {
371: self::$texts[$lang_code][$text_id] = $translation;
372: }
373: }
374: }
375:
376: $this->dictionary_loaded = true;
377:
378: if ($this->use_apcu) {
379: apcu_store("dictionary_supported_languages", self::$supported_languages);
380: apcu_store("dictionary_languages", self::$languages);
381: apcu_store("dictionary_countries", self::$countries);
382: apcu_store("dictionary_texts", self::$texts);
383: }
384: } // loadDictionary
385:
386: /**
387: * Adds additional translations to the dictionary.
388: *
389: * @param array $dictionary
390: * The array with additional translations.
391: *
392: * @return void
393: *
394: * @throws \Exception
395: * It might throw an exception in the case of any errors.
396: *
397: * @author Oleg Schildt
398: */
399: public function extendDictionary(array $dictionary): void
400: {
401: $this->loadDictionary();
402:
403: foreach ($dictionary as $text_id => $translations) {
404: foreach ($translations as $lang_code => $translation) {
405: self::$texts[$lang_code][$text_id] = $translation;
406: }
407: }
408: }
409:
410: /**
411: * This function should detect the current language based on cookies, browser languages etc.
412: *
413: * Priority:
414: *
415: * 1. explicitly set by the request parameter language.
416: * 2. header 'Content-Language'.
417: * 3. last language in the cookie.
418: * 4. browser default language.
419: * 5. the first one from the supported list.
420: * 6. English.
421: *
422: * Some applications may consist of two parts - administration
423: * console and public site. A usual example is a CMS system.
424: *
425: * For example, you are using administration console in English
426: * and editing the public site for German and French.
427: * When you open the public site for preview in German or French,
428: * you want it to be open in the corresponding language, but
429: * the administration console should remain in English.
430: *
431: * With the help of $context, you are able to maintain different
432: * languages for different parts of your application.
433: * If you do not need the $context, just do not specify it.
434: *
435: * @return void
436: *
437: * @author Oleg Schildt
438: */
439: public function detectLanguage(): void
440: {
441: // Let's go
442:
443: // 6. English
444: $language = "en";
445:
446: // 5. the first one from the supported list
447: foreach (self::$supported_languages as $lng) {
448: $language = $lng;
449: break;
450: }
451:
452: // 4. browser default language
453: if (isset($_SERVER["HTTP_ACCEPT_LANGUAGE"]) && trim($_SERVER["HTTP_ACCEPT_LANGUAGE"]) != "") {
454: $accepted = explode(',', $_SERVER["HTTP_ACCEPT_LANGUAGE"]);
455:
456: foreach ($accepted as $name) {
457: $code = explode(';', $name);
458: // handle the cases like en-ca => en
459: $code = explode("-", $code[0]);
460:
461: if (!empty(self::$supported_languages[$code[0]])) {
462: $language = $code[0];
463: break;
464: }
465: }
466: }
467:
468: // 3. last language in the cookie
469: $tmp = get_cookie(self::$context . "_language");
470: if (!empty($tmp) && !empty(self::$supported_languages[$tmp])) {
471: $language = $tmp;
472: }
473:
474: // 2. header 'Content-Language'.
475: $header = get_header("Content-Language");
476: if (!empty($header) && !empty(self::$supported_languages[$header])) {
477: $language = $header;
478: }
479:
480: // 1. explicitly set by request parameter language
481: if (!empty($_REQUEST["language"]) && !empty(self::$supported_languages[$_REQUEST["language"]])) {
482: $language = $_REQUEST["language"];
483: }
484:
485: $this->setCurrentLanguage($language);
486: } // detectLanguage
487:
488: /**
489: * Sets the current context.
490: *
491: * Some applications may consist of two parts - administration
492: * console and public site. A usual example is a CMS system.
493: *
494: * For example, you are using administration console in English
495: * and editing the public site for German and French.
496: * When you open the public site for preview in German or French,
497: * you want it to be open in the corresponding language, but
498: * the administration console should remain in English.
499: *
500: * With the help of $context, you are able to maintain different
501: * languages for different parts of your application.
502: * If you do not need the $context, just do not specify it.
503: *
504: * @param string $context
505: * The name of the context.
506: *
507: * @return void
508: *
509: * @see LanguageManager::getContext()
510: *
511: * @author Oleg Schildt
512: */
513: public function setContext(string $context): void
514: {
515: self::$context = $context;
516: } // setContext
517:
518: /**
519: * Returns the current context.
520: *
521: * Some applications may consist of two parts - administration
522: * console and public site. A usual example is a CMS system.
523: *
524: * For example, you are using administration console in English
525: * and editing the public site for German and French.
526: * When you open the public site for preview in German or French,
527: * you want it to be open in the corresponding language, but
528: * the administration console should remain in English.
529: *
530: * With the help of $context, you are able to maintain different
531: * languages for different parts of your application.
532: * If you do not need the $context, just do not specify it.
533: *
534: * @see LanguageManager::setContext()
535: *
536: * @return string
537: * Returns the current context.
538: *
539: * @author Oleg Schildt
540: */
541: public function getContext(): string
542: {
543: return self::$context;
544: } // getContext
545:
546: /**
547: * Returns the list of supported languages.
548: *
549: * @return array
550: * Returns the list of supported languages.
551: *
552: * @author Oleg Schildt
553: */
554: public function getSupportedLanguages(): array
555: {
556: return self::$supported_languages;
557: } // getSupportedLanguages
558:
559: /**
560: * Sets the current language.
561: *
562: * @param string $language
563: * The language ISO code to be set.
564: *
565: * @return void
566: *
567: * @see LanguageManager::getCurrentLanguage()
568: *
569: * @author Oleg Schildt
570: */
571: public function setCurrentLanguage(string $language): void
572: {
573: if (empty(self::$supported_languages[$language])) {
574: return;
575: }
576:
577: self::$current_language[self::$context] = $language;
578:
579: if ($this->use_cookie) {
580: set_cookie(self::$context . "_language", $language, time() + 365 * 24 * 3600, ["samesite" => "strict", "path" => $this->cookie_path]);
581: }
582: } // setCurrentLanguage
583:
584: /**
585: * Returns the fallback language. If set and a translation
586: * is missing on a language, the translation on this language will be used.
587: *
588: * @return string
589: * Returns the fallback language.
590: *
591: * @author Oleg Schildt
592: */
593: public function getFallbackLanguage(): string
594: {
595: return $this->use_fallback_language;
596: } // getFallbackLanguage
597:
598: /**
599: * Returns the current language.
600: *
601: * @return string
602: * Returns the current language ISO code.
603: *
604: * @see LanguageManager::setCurrentLanguage()
605: *
606: * @author Oleg Schildt
607: */
608: public function getCurrentLanguage(): string
609: {
610: if (empty(self::$current_language[self::$context])) {
611: foreach (self::$supported_languages as $lng) {
612: self::$current_language[self::$context] = $lng;
613: break;
614: }
615: }
616:
617: return self::$current_language[self::$context];
618: } // getCurrentLanguage
619:
620: /**
621: * Provides the text translation for the text ID for the given language.
622: *
623: * @param string $text_id
624: * Text ID
625: *
626: * @param string $lng
627: * The language. If it is not specified,
628: * the default language is used.
629: *
630: * @param string $default_text
631: * The default text to be used if there is no translation.
632: *
633: * @return string
634: * Returns the translation text or the $default_text/$text_id if no translation
635: * is found.
636: *
637: * @throws \Exception
638: * It might throw exceptions in the case of any errors.
639: *
640: * @author Oleg Schildt
641: */
642: public function text(string $text_id, string $lng = "", string $default_text = ""): string
643: {
644: if (empty($lng)) {
645: $lng = $this->getCurrentLanguage();
646: }
647:
648: if (!$this->hasTranslation($text_id, $lng)) {
649: if ($this->warn_missing) {
650: trigger_error("No translation for the text '$text_id' in the language [$lng]!", E_USER_WARNING);
651: }
652:
653: if (empty($default_text)) {
654: if (!empty($this->use_fallback_language)) {
655: return $this->try_text($text_id, $this->use_fallback_language);
656: }
657:
658: return $text_id;
659: } else {
660: return $default_text;
661: }
662: }
663:
664: return self::$texts[$lng][$text_id];
665: } // text
666:
667: /**
668: * Provides the text translation for the text ID for the given language if the translation exists.
669: * Otherwise, it returns the text ID and emits no warning.
670: *
671: * @param string $text_id
672: * Text ID
673: *
674: * @param string $lng
675: * The language. If it is not specified,
676: * the default language is used.
677: *
678: * @return string
679: * Returns the translation text or the $default_text/$text_id if no translation
680: * is found.
681: *
682: * @throws \Exception
683: * It might throw exceptions in the case of any errors.
684: *
685: * @author Oleg Schildt
686: */
687: public function try_text(string $text_id, string $lng = ""): string
688: {
689: if (!$this->hasTranslation($text_id, $lng)) {
690: if (!empty($this->use_fallback_language) && $this->hasTranslation($text_id, $this->use_fallback_language)) {
691: return text($text_id, $this->use_fallback_language);
692: }
693:
694: return $text_id;
695: }
696:
697: return text($text_id, $lng);
698: } // try_text
699:
700: /**
701: * Checks whether the text translation for the text ID for the given language exists.
702: *
703: * @param string $text_id
704: * Text ID
705: *
706: * @param string $lng
707: * The language. If it is not specified,
708: * the default language is used.
709: *
710: * @return bool
711: * Returns true if the translation exists, otherwise false.
712: *
713: * @author Oleg Schildt
714: */
715: public function hasTranslation(string $text_id, string $lng = ""): bool
716: {
717: if (empty($lng)) {
718: $lng = $this->getCurrentLanguage();
719: }
720:
721: return !empty(self::$texts[$lng][$text_id]);
722: } // hasTranslation
723:
724: /**
725: * Provides the text translation for the language name by the code
726: * for the given language.
727: *
728: * @param string $code
729: * Language ISO code (lowercase, e.g. en, de, fr).
730: *
731: * @param string $lng
732: * The language. If it is not specified,
733: * the default language is used.
734: *
735: * @return string
736: * Returns the translation text for the language name or the $code if no translation
737: * is found.
738: *
739: * @see LanguageManager::getLanguageCode()
740: * @see LanguageManager::validateLanguageCode()
741: * @see LanguageManager::getLanguageList()
742: * @see LanguageManager::getCountryName()
743: *
744: * @author Oleg Schildt
745: */
746: public function getLanguageName(string $code, string $lng = ""): string
747: {
748: if (empty($lng)) {
749: $lng = $this->getCurrentLanguage();
750: }
751:
752: if (empty(self::$languages[$lng][$code])) {
753: if ($this->warn_missing) {
754: trigger_error("No translation for the language name [$code] in the language [$lng]!", E_USER_WARNING);
755: }
756: return $code;
757: }
758:
759: return self::$languages[$lng][$code];
760: } // getLanguageName
761:
762: /**
763: * Tries to find the language code by the given name.
764: *
765: * @param string $lang_name
766: * The name of the language in any supported language.
767: *
768: * @return string
769: * Returns the language code if it could be found, otherwise an empty string.
770: *
771: * @see LanguageManager::getLanguageName()
772: * @see LanguageManager::validateLanguageCode()
773: * @see LanguageManager::getLanguageList()
774: * @see LanguageManager::getCountryCode()
775: *
776: * @author Oleg Schildt
777: */
778: public function getLanguageCode(string $lang_name): string
779: {
780: foreach (self::$supported_languages as $lng) {
781: if (empty(self::$languages[$lng])) {
782: continue;
783: }
784:
785: foreach (self::$languages[$lng] as $code => $translation) {
786: if (strcasecmp($lang_name, $translation) == 0) {
787: return $code;
788: }
789: } // foreach
790: } // foreach
791:
792: return "";
793: } // getLanguageCode
794:
795: /**
796: * Checks whether the language code is valid (has translation).
797: *
798: * @param string $code
799: * Language ISO code (lowercase, e.g. en, de, fr).
800: *
801: * @param string $lng
802: * The language. If it is not specified,
803: * the default language is used.
804: *
805: * @return bool
806: * Returns true if the language code is valid (has translation), otherwise false.
807: *
808: * @see LanguageManager::getLanguageName()
809: * @see LanguageManager::getLanguageCode()
810: * @see LanguageManager::getLanguageList()
811: * @see LanguageManager::validateCountryCode()
812: *
813: * @author Oleg Schildt
814: */
815: public function validateLanguageCode(string $code, string $lng = ""): bool
816: {
817: if (empty($lng)) {
818: $lng = $this->getCurrentLanguage();
819: }
820:
821: return !empty(self::$languages[$lng][$code]);
822: } // validateLanguageCode
823:
824: /**
825: * Provides the list of languages for the given language in the form "code" => "translation".
826: *
827: * @param array &$language_list
828: * Target array where the language list should be loaded.
829: *
830: * @param string $lng
831: * The language. If it is not specified,
832: * the default language is used.
833: *
834: * @param array $display_first
835: * List of the language codes to be displayed first in the order, they appear in the list.
836: *
837: * @return bool
838: * Returns true if the language list is successfully retrieved, otherwise false.
839: *
840: * @see LanguageManager::getLanguageName()
841: * @see LanguageManager::getLanguageCode()
842: * @see LanguageManager::validateLanguageCode()
843: * @see LanguageManager::getCountryList()
844: *
845: * @author Oleg Schildt
846: */
847: public function getLanguageList(array &$language_list, string $lng = "", array $display_first = []): bool
848: {
849: if (empty($lng)) {
850: $lng = $this->getCurrentLanguage();
851: }
852:
853: if (empty(self::$languages[$lng])) {
854: return false;
855: }
856:
857: $language_list = array_flip($display_first);
858:
859: asort(self::$languages[$lng], SORT_LOCALE_STRING);
860:
861: foreach (self::$languages[$lng] as $code => $name) {
862: $language_list[$code] = $name;
863: }
864:
865: return true;
866: } // getLanguageList
867:
868: /**
869: * Provides the text translation for the country name by the code
870: * for the given language.
871: *
872: * @param string $code
873: * Country ISO code (uppercase, e.g. US, DE, FR).
874: *
875: * @param string $lng
876: * The language. If it is not specified,
877: * the default language is used.
878: *
879: * @return string
880: * Returns the translation text for the country name or the $code if no translation
881: * is found.
882: *
883: * @see LanguageManager::getCountryCode()
884: * @see LanguageManager::validateCountryCode()
885: * @see LanguageManager::getCountryList()
886: * @see LanguageManager::getLanguageName()
887: *
888: * @author Oleg Schildt
889: */
890: public function getCountryName(string $code, string $lng = ""): string
891: {
892: if (empty($lng)) {
893: $lng = $this->getCurrentLanguage();
894: }
895:
896: if (empty(self::$countries[$lng][$code])) {
897: if ($this->warn_missing) {
898: trigger_error("No translation for the country name [$code] in the language [$lng]!", E_USER_WARNING);
899: }
900: return $code;
901: }
902:
903: return self::$countries[$lng][$code];
904: } // getCountryName
905:
906: /**
907: * Tries to find the country code by the given name.
908: *
909: * @param string $country_name
910: * The name of the country in any supported language.
911: *
912: * @return string
913: * Returns the country code if it could be found, otherwise an empty string.
914: *
915: * @see LanguageManager::getCountryName()
916: * @see LanguageManager::validateCountryCode()
917: * @see LanguageManager::getCountryList()
918: * @see LanguageManager::getLanguageCode()
919: *
920: * @author Oleg Schildt
921: */
922: public function getCountryCode(string $country_name): string
923: {
924: foreach (self::$supported_languages as $lng) {
925: if (empty(self::$countries[$lng])) {
926: continue;
927: }
928:
929: foreach (self::$countries[$lng] as $code => $translation) {
930: if (strcasecmp($country_name, $translation) == 0) {
931: return $code;
932: }
933: } // foreach
934: } // foreach
935:
936: return "";
937: } // getCountryCode
938:
939: /**
940: * Checks whether the country code is valid (has translation).
941: *
942: * @param string $code
943: * Country ISO code (uppercase, e.g. US, DE, FR).
944: *
945: * @param string $lng
946: * The language. If it is not specified,
947: * the default language is used.
948: *
949: * @return bool
950: * Returns true if the country code is valid (has translation), otherwise false.
951: *
952: * @see LanguageManager::getCountryName()
953: * @see LanguageManager::getCountryCode()
954: * @see LanguageManager::getCountryList()
955: * @see LanguageManager::validateLanguageCode()
956: *
957: * @author Oleg Schildt
958: */
959: public function validateCountryCode(string $code, string $lng = ""): bool
960: {
961: if (empty($lng)) {
962: $lng = $this->getCurrentLanguage();
963: }
964:
965: return !empty(self::$countries[$lng][$code]);
966: } // validateCountryCode
967:
968: /**
969: * Provides the list of countries for the given language in the form "code" => "translation".
970: *
971: * @param array &$country_list
972: * Target array where the country list should be loaded.
973: *
974: * @param string $lng
975: * The language. If it is not specified,
976: * the default language is used.
977: *
978: * @param array $display_first
979: * List of the country codes to be displayed first in the order, they appear in the list.
980: *
981: * @return bool
982: * Returns true if the country list is successfully retrieved, otherwise false.
983: *
984: * @see LanguageManager::getCountryName()
985: * @see LanguageManager::getCountryCode()
986: * @see LanguageManager::validateCountryCode()
987: * @see LanguageManager::getLanguageList()
988: *
989: * @author Oleg Schildt
990: */
991: public function getCountryList(array &$country_list, string $lng = "", array $display_first = []): bool
992: {
993: if (empty($lng)) {
994: $lng = $this->getCurrentLanguage();
995: }
996:
997: if (empty(self::$countries[$lng])) {
998: return false;
999: }
1000:
1001: $country_list = array_flip($display_first);
1002:
1003: asort(self::$countries[$lng], SORT_LOCALE_STRING);
1004:
1005: foreach (self::$countries[$lng] as $code => $name) {
1006: $country_list[$code] = $name;
1007: }
1008:
1009: return true;
1010: } // getCountryList
1011: } // LanguageManager