PocketMine-MP 5.39.3 git-21ae710729750cd637333d673bbbbbc598fc659e
Loading...
Searching...
No Matches
Language.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
24namespace pocketmine\lang;
25
27use Symfony\Component\Filesystem\Path;
28use function array_filter;
29use function array_map;
30use function count;
31use function explode;
32use function file_exists;
33use function is_dir;
34use function max;
35use function ord;
36use function parse_ini_file;
37use function scandir;
38use function str_contains;
39use function str_ends_with;
40use function str_replace;
41use function str_starts_with;
42use function strlen;
43use function strpos;
44use function strtolower;
45use function substr;
46use const INI_SCANNER_RAW;
47use const SCANDIR_SORT_NONE;
48
50
51 public const FALLBACK_LANGUAGE = "eng";
52
59 public static function getLanguageList(string $path = "") : array{
60 if($path === ""){
61 $path = \pocketmine\LOCALE_DATA_PATH;
62 }
63
64 if(is_dir($path)){
65 $allFiles = scandir($path, SCANDIR_SORT_NONE);
66
67 if($allFiles !== false){
68 $files = array_filter($allFiles, function(string $filename) : bool{
69 return str_ends_with($filename, ".ini");
70 });
71
72 $result = [];
73
74 foreach($files as $file){
75 try{
76 $code = explode(".", $file, limit: 2)[0];
77 $strings = self::loadLang($path, $code);
78 if(isset($strings[KnownTranslationKeys::LANGUAGE_NAME])){
79 $result[$code] = $strings[KnownTranslationKeys::LANGUAGE_NAME];
80 }
81 }catch(LanguageNotFoundException $e){
82 // no-op
83 }
84 }
85
86 return $result;
87 }
88 }
89
90 throw new LanguageNotFoundException("Language directory $path does not exist or is not a directory");
91 }
92
93 protected string $langName;
98 protected array $lang = [];
103 protected array $fallbackLang = [];
104
108 public function __construct(string $lang, ?string $path = null, string $fallback = self::FALLBACK_LANGUAGE){
109 $this->langName = strtolower($lang);
110
111 if($path === null){
112 $path = \pocketmine\LOCALE_DATA_PATH;
113 }
114
115 $this->lang = self::loadLang($path, $this->langName);
116 $this->fallbackLang = self::loadLang($path, $fallback);
117 }
118
119 public function getName() : string{
120 return $this->get(KnownTranslationKeys::LANGUAGE_NAME);
121 }
122
123 public function getLang() : string{
124 return $this->langName;
125 }
126
131 protected static function loadLang(string $path, string $languageCode) : array{
132 $file = Path::join($path, $languageCode . ".ini");
133 if(file_exists($file)){
134 $strings = array_map('stripcslashes', Utils::assumeNotFalse(parse_ini_file($file, false, INI_SCANNER_RAW), "Missing or inaccessible required resource files"));
135 if(count($strings) > 0){
136 return $strings;
137 }
138 }
139
140 throw new LanguageNotFoundException("Language \"$languageCode\" not found");
141 }
142
143 private function getUsedParameterCount(string $rawString, int $given) : int{
144 $highestIndex = -1;
145 for($i = 0; $i < $given; $i++){
146 if(str_contains($rawString, "{%$i}")){
147 $highestIndex = $i;
148 }
149 }
150 return $highestIndex + 1;
151 }
152
156 public function translateString(string $str, array $params = [], ?string $onlyPrefix = null, int &$untranslatedParameterCount = 0) : string{
157 $baseText = $this->internalGet($str);
158 $parameterCount = count($params);
159 if($baseText !== null){
160 if($onlyPrefix !== null && !str_starts_with($str, $onlyPrefix)){ //client side translation
161 $untranslatedParameterCount = $this->getUsedParameterCount($baseText, $parameterCount);
162 return $str;
163 }
164 }else{ //key not found or embedded inside format string
165 $baseText = $this->parseTranslation($str, $onlyPrefix, $parameterCount);
166 $untranslatedParameterCount = $parameterCount;
167 }
168
169 foreach(Utils::promoteKeys($params) as $i => $p){
170 $replacement = $p instanceof Translatable ? $this->translate($p) : (string) $p;
171 $baseText = str_replace("{%$i}", $replacement, $baseText);
172 }
173
174 return $baseText;
175 }
176
177 public function translate(Translatable $c) : string{
178 $baseText = $this->internalGet($c->getText());
179 if($baseText === null){ //key not found or embedded inside format string
180 $baseText = $this->parseTranslation($c->getText());
181 }
182
183 foreach(Utils::promoteKeys($c->getParameters()) as $i => $p){
184 $replacement = $p instanceof Translatable ? $this->translate($p) : $p;
185 $baseText = str_replace("{%$i}", $replacement, $baseText);
186 }
187
188 return $baseText;
189 }
190
191 protected function internalGet(string $id) : ?string{
192 return $this->lang[$id] ?? $this->fallbackLang[$id] ?? null;
193 }
194
195 public function get(string $id) : string{
196 return $this->internalGet($id) ?? $id;
197 }
198
203 public function getAll() : array{
204 return $this->lang;
205 }
206
207 private function replaceTranslationKey(string $replaceString, ?string $onlyPrefix, int &$untranslatedParameterCount, int $givenParameterCount) : string{
208 if(($t = $this->internalGet(substr($replaceString, 1))) !== null){
209 if($onlyPrefix !== null && strpos($replaceString, $onlyPrefix) !== 1){ //client side translation
210 $newString = $replaceString;
211 $untranslatedParameterCount = max($untranslatedParameterCount, $this->getUsedParameterCount($t, $givenParameterCount));
212 }else{
213 $newString = $t;
214 }
215 }else{
216 $newString = $replaceString;
217 $untranslatedParameterCount = $givenParameterCount;
218 }
219 return $newString;
220 }
221
235 protected function parseTranslation(string $text, ?string $onlyPrefix = null, int &$parameterCount = 0) : string{
236 $givenParameterCount = $parameterCount;
237 $untranslatedParameterCount = 0;
238 $newString = "";
239
240 $replaceString = null;
241
242 $len = strlen($text);
243 for($i = 0; $i < $len; ++$i){
244 $c = $text[$i];
245 if($replaceString !== null){
246 $ord = ord($c);
247 if(
248 ($ord >= 0x30 && $ord <= 0x39) // 0-9
249 || ($ord >= 0x41 && $ord <= 0x5a) // A-Z
250 || ($ord >= 0x61 && $ord <= 0x7a) || // a-z
251 $c === "." || $c === "-"
252 ){
253 $replaceString .= $c;
254 }else{
255 $newString .= $this->replaceTranslationKey($replaceString, $onlyPrefix, $untranslatedParameterCount, $givenParameterCount);
256 $replaceString = null;
257
258 if($c === "%"){
259 $replaceString = $c;
260 }else{
261 $newString .= $c;
262 }
263 }
264 }elseif($c === "%"){
265 $replaceString = $c;
266 }else{
267 $newString .= $c;
268 }
269 }
270
271 if($replaceString !== null){
272 $newString .= $this->replaceTranslationKey($replaceString, $onlyPrefix, $untranslatedParameterCount, $givenParameterCount);
273 }
274
275 $parameterCount = $untranslatedParameterCount;
276 return $newString;
277 }
278}
translateString(string $str, array $params=[], ?string $onlyPrefix=null, int &$untranslatedParameterCount=0)
Definition Language.php:156
static loadLang(string $path, string $languageCode)
Definition Language.php:131
static getLanguageList(string $path="")
Definition Language.php:59
__construct(string $lang, ?string $path=null, string $fallback=self::FALLBACK_LANGUAGE)
Definition Language.php:108
parseTranslation(string $text, ?string $onlyPrefix=null, int &$parameterCount=0)
Definition Language.php:235