Issues (238)

src/Traits/ManipuleFileLangTrait.php (11 issues)

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
It seems like $this->getTranslationByK...$enContent, $arrayKeys) can also be of type string; however, parameter $array of Translation\Traits\Manip...::calculateKeyPadding() does only seem to accept array, 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 ignore-type  annotation

112
                        $keyPad = $this->calculateKeyPadding(/** @scrutinizer ignore-type */ $this->getTranslationByKeys($enContent, $arrayKeys));
Loading history...
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 ignore-type  annotation

115
                    $formatted[$index] = $this->formatTranslationLine($key, /** @scrutinizer ignore-type */ $langVal, $indent, $keyPad);
Loading history...
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 ignore-call  annotation

245
        $inputPath = /** @scrutinizer ignore-call */ base_path('resources/lang') . "/{$lang}/{$fileName}.php";
Loading history...
The assignment to $inputPath is dead and can be removed.
Loading history...
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 ignore-call  annotation

261
        $inputPath = /** @scrutinizer ignore-call */ base_path('resources/lang') . "/{$lang}/{$fileName}.php";
Loading history...
The assignment to $inputPath is dead and can be removed.
Loading history...
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 ignore-call  annotation

280
        $inputPath = /** @scrutinizer ignore-call */ base_path('resources/lang') . "/{$lang}/{$fileName}.php";
Loading history...
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 ignore-call  annotation

296
        $inputPath = /** @scrutinizer ignore-call */ base_path('resources/lang') . "/en";
Loading history...
The assignment to $inputPath is dead and can be removed.
Loading history...
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
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
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
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
349
    }
350
}