1 | <?php |
||||
2 | |||||
3 | namespace Translation\Traits; |
||||
4 | |||||
5 | use Illuminate\Support\Facades\Config; |
||||
6 | use Illuminate\Support\Facades\Lang; |
||||
7 | use Illuminate\Support\Str; |
||||
8 | use Translation\Events\TranslationHasBeenSet; |
||||
9 | use Translation\Exceptions\AttributeIsNotTranslatable; |
||||
10 | use Translation\Repositories\ModelTranslationRepository; |
||||
11 | use Translation\Services\GoogleTranslate; |
||||
12 | |||||
13 | /** |
||||
14 | */ |
||||
15 | trait ManipuleFileLangTrait |
||||
16 | { |
||||
17 | |||||
18 | |||||
19 | /** |
||||
20 | * Format the contents of a single translation file in the given language. |
||||
21 | * @param string $lang |
||||
22 | * @param string $fileName |
||||
23 | * @return string |
||||
24 | */ |
||||
25 | public function formatFileContents(string $lang, string $fileName) : string |
||||
26 | { |
||||
27 | $enLines = $this->loadLangFileLines('en', $fileName); |
||||
28 | $langContent = $this->loadLang($lang, $fileName); |
||||
29 | $enContent = $this->loadLang('en', $fileName); |
||||
30 | |||||
31 | // Calculate the longest top-level key length |
||||
32 | $longestKeyLength = $this->calculateKeyPadding($enContent); |
||||
33 | |||||
34 | // Start formatted content |
||||
35 | $formatted = []; |
||||
36 | $mode = 'header'; |
||||
37 | $skipArray = false; |
||||
38 | $arrayKeys = []; |
||||
39 | |||||
40 | foreach ($enLines as $index => $line) { |
||||
41 | $trimLine = trim($line); |
||||
42 | if ($mode === 'header') { |
||||
43 | $formatted[$index] = $line; |
||||
44 | if (str_replace(' ', '', $trimLine) === 'return[') { |
||||
45 | $mode = 'body'; |
||||
46 | } |
||||
47 | } |
||||
48 | |||||
49 | if ($mode === 'body') { |
||||
50 | $matches = []; |
||||
51 | $arrayEndMatch = preg_match('/]\s*,\s*$/', $trimLine); |
||||
52 | |||||
53 | if ($skipArray) { |
||||
54 | if ($arrayEndMatch) { |
||||
55 | $skipArray = false; |
||||
56 | } |
||||
57 | continue; |
||||
58 | } |
||||
59 | |||||
60 | // Comment to ignore |
||||
61 | if (strpos($trimLine, '//!') === 0) { |
||||
62 | $formatted[$index] = ""; |
||||
63 | continue; |
||||
64 | } |
||||
65 | |||||
66 | // Comment |
||||
67 | if (strpos($trimLine, '//') === 0) { |
||||
68 | $formatted[$index] = "\t" . $trimLine; |
||||
69 | continue; |
||||
70 | } |
||||
71 | |||||
72 | // Arrays |
||||
73 | $arrayStartMatch = preg_match('/^\'(.*)\'\s+?=>\s+?\[(\],)?\s*?$/', $trimLine, $matches); |
||||
74 | |||||
75 | $indent = count($arrayKeys) + 1; |
||||
76 | if ($arrayStartMatch === 1) { |
||||
77 | if ($fileName === 'settings' && $matches[1] === 'language_select') { |
||||
78 | $skipArray = true; |
||||
79 | continue; |
||||
80 | } |
||||
81 | $arrayKeys[] = $matches[1]; |
||||
82 | $formatted[$index] = str_repeat(" ", $indent * 4) . str_pad("'{$matches[1]}'", $longestKeyLength) . "=> ["; |
||||
83 | if ($arrayEndMatch !== 1) { |
||||
84 | continue; |
||||
85 | } |
||||
86 | } |
||||
87 | if ($arrayEndMatch === 1) { |
||||
88 | $this->unsetArrayByKeys($langContent, $arrayKeys); |
||||
89 | array_pop($arrayKeys); |
||||
90 | if (isset($formatted[$index])) { |
||||
91 | $formatted[$index] .= '],'; |
||||
92 | } else { |
||||
93 | $formatted[$index] = str_repeat(" ", ($indent-1) * 4) . "],"; |
||||
94 | } |
||||
95 | continue; |
||||
96 | } |
||||
97 | |||||
98 | // Translation |
||||
99 | $translationMatch = preg_match('/^\'(.*)\'\s+?=>\s+?[\'"](.*)?[\'"].+?$/', $trimLine, $matches); |
||||
100 | if ($translationMatch === 1) { |
||||
101 | $key = $matches[1]; |
||||
102 | $keys = array_merge($arrayKeys, [$key]); |
||||
103 | $langVal = $this->getTranslationByKeys($langContent, $keys); |
||||
104 | if (empty($langVal)) { |
||||
105 | continue; |
||||
106 | } |
||||
107 | |||||
108 | $keyPad = $longestKeyLength; |
||||
109 | if (count($arrayKeys) === 0) { |
||||
110 | unset($langContent[$key]); |
||||
111 | } else { |
||||
112 | $keyPad = $this->calculateKeyPadding($this->getTranslationByKeys($enContent, $arrayKeys)); |
||||
0 ignored issues
–
show
Bug
introduced
by
![]() |
|||||
113 | } |
||||
114 | |||||
115 | $formatted[$index] = $this->formatTranslationLine($key, $langVal, $indent, $keyPad); |
||||
0 ignored issues
–
show
It seems like
$langVal can also be of type array ; however, parameter $value of Translation\Traits\Manip...formatTranslationLine() does only seem to accept string , maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
116 | continue; |
||||
117 | } |
||||
118 | } |
||||
119 | } |
||||
120 | |||||
121 | // Fill missing lines |
||||
122 | $arraySize = max(array_keys($formatted)); |
||||
123 | $formatted = array_replace(array_fill(0, $arraySize, ''), $formatted); |
||||
124 | |||||
125 | // Add remaining translations |
||||
126 | $langContent = array_filter($langContent, function ($item) { |
||||
127 | return !is_null($item) && !empty($item); |
||||
128 | }); |
||||
129 | if (count($langContent) > 0) { |
||||
130 | $formatted[] = ''; |
||||
131 | $formatted[] = "\t// Unmatched"; |
||||
132 | } |
||||
133 | foreach ($langContent as $key => $value) { |
||||
134 | if (is_array($value)) { |
||||
135 | $formatted[] = $this->formatTranslationArray($key, $value); |
||||
136 | } else { |
||||
137 | $formatted[] = $this->formatTranslationLine($key, $value); |
||||
138 | } |
||||
139 | } |
||||
140 | |||||
141 | // Add end line |
||||
142 | $formatted[] = '];'; |
||||
143 | return implode("\n", $formatted); |
||||
144 | } |
||||
145 | |||||
146 | /** |
||||
147 | * Format a translation line. |
||||
148 | * @param string $key |
||||
149 | * @param string $value |
||||
150 | * @param int $indent |
||||
151 | * @param int $keyPad |
||||
152 | * @return string |
||||
153 | */ |
||||
154 | public function formatTranslationLine(string $key, string $value, int $indent = 1, int $keyPad = 1) : string |
||||
155 | { |
||||
156 | $start = str_repeat(" ", $indent * 4) . str_pad("'{$key}'", $keyPad, ' '); |
||||
157 | if (strpos($value, "\n") !== false) { |
||||
158 | $escapedValue = '"' . str_replace("\n", '\n', $value) . '"'; |
||||
159 | $escapedValue = '"' . str_replace('"', '\"', $escapedValue) . '"'; |
||||
160 | } else { |
||||
161 | $escapedValue = "'" . str_replace("'", "\\'", $value) . "'"; |
||||
162 | } |
||||
163 | return "{$start} => {$escapedValue},"; |
||||
164 | } |
||||
165 | |||||
166 | /** |
||||
167 | * Find the longest key in the array and provide the length |
||||
168 | * for all keys to be used when printed. |
||||
169 | * @param array $array |
||||
170 | * @return int |
||||
171 | */ |
||||
172 | public function calculateKeyPadding(array $array) : int |
||||
173 | { |
||||
174 | $top = 0; |
||||
175 | foreach ($array as $key => $value) { |
||||
176 | $keyLen = strlen($key); |
||||
177 | $top = max($top, $keyLen); |
||||
178 | } |
||||
179 | return min(35, $top + 2); |
||||
180 | } |
||||
181 | |||||
182 | /** |
||||
183 | * Format an translation array with the given key. |
||||
184 | * Simply prints as an old-school php array. |
||||
185 | * Used as a last-resort backup to save unused translations. |
||||
186 | * @param string $key |
||||
187 | * @param array $array |
||||
188 | * @return string |
||||
189 | */ |
||||
190 | public function formatTranslationArray(string $key, array $array) : string |
||||
191 | { |
||||
192 | $arrayPHP = var_export($array, true); |
||||
193 | return " '{$key}' => {$arrayPHP},"; |
||||
194 | } |
||||
195 | |||||
196 | /** |
||||
197 | * Find a string translation value within a multi-dimensional array |
||||
198 | * by traversing the given array of keys. |
||||
199 | * @param array $translations |
||||
200 | * @param array $keys |
||||
201 | * @return string|array |
||||
202 | */ |
||||
203 | public function getTranslationByKeys(array $translations, array $keys) |
||||
204 | { |
||||
205 | $val = $translations; |
||||
206 | foreach ($keys as $key) { |
||||
207 | $val = $val[$key] ?? ''; |
||||
208 | if ($val === '') { |
||||
209 | return ''; |
||||
210 | } |
||||
211 | } |
||||
212 | return $val; |
||||
213 | } |
||||
214 | |||||
215 | /** |
||||
216 | * Unset an inner item of a multi-dimensional array by |
||||
217 | * traversing the given array of keys. |
||||
218 | * @param array $input |
||||
219 | * @param array $keys |
||||
220 | */ |
||||
221 | public function unsetArrayByKeys(array &$input, array $keys) |
||||
222 | { |
||||
223 | $val = &$input; |
||||
224 | $lastIndex = count($keys) - 1; |
||||
225 | foreach ($keys as $index => &$key) { |
||||
226 | if ($index === $lastIndex && is_array($val)) { |
||||
227 | unset($val[$key]); |
||||
228 | } |
||||
229 | if (!is_array($val)) { |
||||
230 | return; |
||||
231 | } |
||||
232 | $val = &$val[$key] ?? []; |
||||
233 | } |
||||
234 | } |
||||
235 | |||||
236 | /** |
||||
237 | * Write the given content to a translation file. |
||||
238 | * @param string $lang |
||||
239 | * @param string $fileName |
||||
240 | * @param string $content |
||||
241 | */ |
||||
242 | public function writeLangFile(string $lang, string $fileName, string $content) |
||||
243 | { |
||||
244 | $path = |
||||
245 | $inputPath = base_path('resources/lang') . "/{$lang}/{$fileName}.php"; |
||||
0 ignored issues
–
show
The function
base_path was not found. Maybe you did not declare it correctly or list all dependencies?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
246 | if (!file_exists($path)) { |
||||
247 | $this->errorOut("Expected translation file '{$path}' does not exist"); |
||||
248 | } |
||||
249 | file_put_contents($path, $content); |
||||
250 | } |
||||
251 | |||||
252 | /** |
||||
253 | * Load the contents of a language file as an array of text lines. |
||||
254 | * @param string $lang |
||||
255 | * @param string $fileName |
||||
256 | * @return array |
||||
257 | */ |
||||
258 | public function loadLangFileLines(string $lang, string $fileName) : array |
||||
259 | { |
||||
260 | $path = |
||||
261 | $inputPath = base_path('resources/lang') . "/{$lang}/{$fileName}.php"; |
||||
0 ignored issues
–
show
The function
base_path was not found. Maybe you did not declare it correctly or list all dependencies?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
262 | if (!file_exists($path)) { |
||||
263 | $this->errorOut("Expected translation file '{$path}' does not exist"); |
||||
264 | } |
||||
265 | $lines = explode("\n", file_get_contents($path)); |
||||
266 | return array_map(function ($line) { |
||||
267 | return trim($line, "\r"); |
||||
268 | }, $lines); |
||||
269 | } |
||||
270 | |||||
271 | /** |
||||
272 | * Load the contents of a language file |
||||
273 | * @param string $lang |
||||
274 | * @param string $fileName |
||||
275 | * @return array |
||||
276 | */ |
||||
277 | public function loadLang(string $lang, string $fileName) : array |
||||
278 | { |
||||
279 | $path = |
||||
280 | $inputPath = base_path('resources/lang') . "/{$lang}/{$fileName}.php"; |
||||
0 ignored issues
–
show
The function
base_path was not found. Maybe you did not declare it correctly or list all dependencies?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
281 | if (!file_exists($path)) { |
||||
282 | $this->errorOut("Expected translation file '{$path}' does not exist"); |
||||
283 | } |
||||
284 | |||||
285 | $fileData = include($path); |
||||
286 | return $fileData; |
||||
287 | } |
||||
288 | |||||
289 | /** |
||||
290 | * Fetch an array containing the names of all translation files without the extension. |
||||
291 | * @return array |
||||
292 | */ |
||||
293 | public function getTranslationFileNames() : array |
||||
294 | { |
||||
295 | $dir = |
||||
296 | $inputPath = base_path('resources/lang') . "/en"; |
||||
0 ignored issues
–
show
The function
base_path was not found. Maybe you did not declare it correctly or list all dependencies?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
297 | if (!file_exists($dir)) { |
||||
298 | $this->errorOut("Expected directory '{$dir}' does not exist"); |
||||
299 | } |
||||
300 | $files = scandir($dir); |
||||
301 | $fileNames = []; |
||||
302 | foreach ($files as $file) { |
||||
303 | if (substr($file, -4) === '.php') { |
||||
304 | $fileNames[] = substr($file, 0, strlen($file) - 4); |
||||
305 | } |
||||
306 | } |
||||
307 | return $fileNames; |
||||
308 | } |
||||
309 | |||||
310 | /** |
||||
311 | * Format a locale to follow the lowercase_UPERCASE standard |
||||
312 | * @param string $lang |
||||
313 | * @return string |
||||
314 | */ |
||||
315 | public function formatLocale(string $lang) : string |
||||
316 | { |
||||
317 | $langParts = explode('_', strtoupper($lang)); |
||||
318 | $langParts[0] = strtolower($langParts[0]); |
||||
319 | return implode('_', $langParts); |
||||
320 | } |
||||
321 | |||||
322 | /** |
||||
323 | * Dump a variable then die. |
||||
324 | * @param $content |
||||
325 | */ |
||||
326 | public function dd($content) |
||||
327 | { |
||||
328 | print_r($content); |
||||
329 | exit(1); |
||||
0 ignored issues
–
show
|
|||||
330 | } |
||||
331 | |||||
332 | /** |
||||
333 | * Log out some information text in blue |
||||
334 | * @param $text |
||||
335 | */ |
||||
336 | public function info($text) |
||||
337 | { |
||||
338 | echo "\e[34m" . $text . "\e[0m\n"; |
||||
339 | } |
||||
340 | |||||
341 | /** |
||||
342 | * Log out an error in red and exit. |
||||
343 | * @param $text |
||||
344 | */ |
||||
345 | public function errorOut($text) |
||||
346 | { |
||||
347 | echo "\e[31m" . $text . "\e[0m\n"; |
||||
348 | exit(1); |
||||
0 ignored issues
–
show
|
|||||
349 | } |
||||
350 | } |