Test Failed
Push — main ( a8a1f8...d105a1 )
by Rafael
11:47
created

Translator::getBaseLangFolder()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Alxarafe. Development of PHP applications in a flash!
4
 * Copyright (C) 2018-2020 Alxarafe <[email protected]>
5
 */
6
7
namespace Alxarafe\Core\Singletons;
8
9
use Symfony\Component\Finder\Finder;
10
use Symfony\Component\Translation\Loader\YamlFileLoader;
11
use Symfony\Component\Translation\Translator as SymfonyTranslator;
12
use Symfony\Component\Yaml\Exception\ParseException;
13
use Symfony\Component\Yaml\Yaml;
14
15
/**
16
 * Class Lang, give support to internationalization.
17
 *
18
 * @package Alxarafe\Core\Providers
19
 */
20
class Translator
21
{
22
    /**
23
     * Default language to use if language file not exists.
24
     */
25
    public const FALLBACK_LANG = 'en';
26
27
    /**
28
     * Base folder where languages files are stored.
29
     */
30
    public const LANG_FOLDER = DIRECTORY_SEPARATOR . 'Languages';
31
32
    /**
33
     * Default language to use.
34
     */
35
    public const LANG = 'es_ES';
36
37
    /**
38
     * Extension of language file.
39
     */
40
    public const EXT = '.yaml';
41
42
    /**
43
     * Format of language file.
44
     */
45
    public const FORMAT = 'yaml';
46
47
    /**
48
     * The Symfony translator.
49
     *
50
     * @var SymfonyTranslator
51
     */
52
    private static $translator;
53
54
    /**
55
     * List of used strings.
56
     *
57
     * @var array
58
     */
59
    private static $usedStrings;
60
61
    /**
62
     * List of strings without translation.
63
     *
64
     * @var array
65
     */
66
    private static $missingStrings;
67
68
    /**
69
     * Main language folder.
70
     *
71
     * @var string
72
     */
73
    private static $languageFolder;
74
75
    /**
76
     * Array of all language folders.
77
     *
78
     * @var array
79
     */
80
    private static $languageFolders;
81
82
    /**
83
     * Lang constructor.
84
     */
85
    public function __construct()
86
    {
87
        $config = Config::loadConfigurationFile()['translator']['main'] ?? 'en';
88
89
        self::$languageFolder = constant('BASE_FOLDER') . self::LANG_FOLDER;
90
        self::$translator = new SymfonyTranslator($config['language'] ?? self::FALLBACK_LANG);
91
        self::$translator->setFallbackLocales([self::FALLBACK_LANG]);
92
        self::$translator->addLoader(self::FORMAT, new YamlFileLoader());
93
        self::$usedStrings = [];
94
        self::$missingStrings = [];
95
        self::$languageFolders = [];
96
        $this->loadLangFiles();
97
    }
98
99
    /**
100
     * Load the translation files following the priorities.
101
     * In this case, the translator must be provided with the routes in reverse order.
102
     *
103
     * @return void
104
     */
105
    public static function loadLangFiles(): void
106
    {
107
        $langFolders = self::getLangFolders();
108
        if (count($langFolders) === 0 || empty($langFolders[0])) {
109
            die('No language folder found');
0 ignored issues
show
Best Practice introduced by
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...
110
        }
111
112
        $langFiles = Finder::create()
113
            ->files()
114
            ->name('*' . self::EXT)
115
            ->in($langFolders)
116
            ->sortByName();
117
118
        foreach ($langFiles as $langFile) {
119
            $langCode = str_replace(self::EXT, '', $langFile->getRelativePathName());
120
            try {
121
                Yaml::parseFile($langFile->getPathName());
122
                self::$translator->addResource(self::FORMAT, $langFile->getPathName(), $langCode);
123
            } catch (ParseException $e) {
124
                Logger::getInstance()::exceptionHandler($e);
0 ignored issues
show
Bug introduced by
The method getInstance() does not exist on Alxarafe\Core\Singletons\Logger. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

124
                Logger::/** @scrutinizer ignore-call */ 
125
                        getInstance()::exceptionHandler($e);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
125
                FlashMessages::getInstance()::setError($e->getFile() . ' ' . $e->getLine() . ' ' . $e->getMessage());
0 ignored issues
show
Bug introduced by
The method getInstance() does not exist on Alxarafe\Core\Singletons\FlashMessages. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

125
                FlashMessages::/** @scrutinizer ignore-call */ 
126
                               getInstance()::setError($e->getFile() . ' ' . $e->getLine() . ' ' . $e->getMessage());

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
126
            }
127
        }
128
    }
129
130
    /**
131
     * Return the lang folders.
132
     *
133
     * @return array
134
     */
135
    public static function getLangFolders(): array
136
    {
137
        return array_merge(
138
            [self::getBaseLangFolder()],
139
            self::$languageFolders
140
        );
141
    }
142
143
    /**
144
     * Returns the base lang folder.
145
     *
146
     * @return string
147
     */
148
    public static function getBaseLangFolder(): string
149
    {
150
        return self::$languageFolder;
151
    }
152
153
    /**
154
     * Return default values
155
     *
156
     * @return array
157
     */
158
    public function getDefaultValues(): array
159
    {
160
        return ['language' => self::FALLBACK_LANG];
161
    }
162
163
    /**
164
     * Add additional language folders.
165
     *
166
     * @param array $folders
167
     */
168
    public static function addDirs(array $folders = [])
169
    {
170
        foreach ($folders as $key => $folder) {
171
            $fullFolder = $folder . self::LANG_FOLDER;
172
            //            FileSystemUtils::mkdir($folders[$key], 0777, true);
173
            if (file_exists($fullFolder) && is_dir($fullFolder)) {
174
                $folders[$key] = $fullFolder;
175
                Debug::addMessage('messages', 'Added translation folder ' . $folders[$key]);
176
            }
177
        }
178
        self::$languageFolders = array_merge(self::$languageFolders, $folders);
179
        self::loadLangFiles();
180
    }
181
182
    /**
183
     * Sets the language code in use.
184
     *
185
     * @param string $lang
186
     *
187
     * @return void
188
     */
189
    public function setlocale(string $lang): void
190
    {
191
        self::$translator->setLocale($lang);
192
    }
193
194
    /**
195
     * Returns an array with the languages with available translations.
196
     *
197
     * @return array
198
     */
199
    public function getAvailableLanguages(): array
200
    {
201
        $languages = [];
202
        $dir = $this->getBaseLangFolder();
203
        //        FileSystemUtils::mkdir($dir, 0777, true);
204
        if (file_exists($dir) && is_dir($dir)) {
205
            $langFiles = Finder::create()
206
                ->files()
207
                ->name('*' . self::EXT)
208
                ->in($dir)
209
                ->sortByName();
210
211
            foreach ($langFiles as $langFile) {
212
                $langCode = str_replace(self::EXT, '', $langFile->getRelativePathName());
213
                $languages[$langCode] = $this->trans('language-' . $langCode);
214
            }
215
        }
216
        return $languages;
217
    }
218
219
    /**
220
     * Translate the text into the default language.
221
     *
222
     * @param string      $txt
223
     * @param array       $parameters
224
     * @param string|null $domain
225
     * @param string|null $locale
226
     *
227
     * @return string
228
     */
229
    public static function trans(string $txt, array $parameters = [], ?string $domain = null, ?string $locale = null): string
230
    {
231
        $translated = self::$translator->trans($txt, $parameters, $domain, $locale);
232
        self::verifyMissing($translated, $txt, $domain);
233
        return $translated;
234
    }
235
236
    /**
237
     * Stores if translation is used and if is missing.
238
     *
239
     * @param string      $translated
240
     * @param null|string $txt
241
     * @param null|string $domain
242
     */
243
    private static function verifyMissing(string $translated, $txt, $domain = null)
244
    {
245
        $domain = $domain ?? 'messages';
246
247
        $txt = (string) $txt;
248
        $catalogue = self::$translator->getCatalogue(self::getLocale());
249
250
        while (!$catalogue->defines($txt, $domain)) {
251
            if ($cat = $catalogue->getFallbackCatalogue()) {
252
                $catalogue = $cat;
253
                $locale = $catalogue->getLocale();
254
                if ($catalogue->has($txt, $domain) && $locale !== self::getLocale()) {
255
                    self::$missingStrings[$txt] = $translated;
256
                }
257
            } else {
258
                break;
259
            }
260
        }
261
        if (!$catalogue->has($txt, $domain)) {
262
            self::$missingStrings[$txt] = $translated;
263
        }
264
    }
265
266
    /**
267
     * Returns the language code in use.
268
     *
269
     * @return string
270
     */
271
    public static function getLocale(): string
272
    {
273
        return self::$translator->getLocale();
274
    }
275
276
    /**
277
     * Returns the missing strings.
278
     *
279
     * @return array
280
     */
281
    public function getMissingStrings(): array
282
    {
283
        return self::$missingStrings;
284
    }
285
286
    /**
287
     * Returns the strings used.
288
     *
289
     * @return array
290
     */
291
    public function getUsedStrings(): array
292
    {
293
        return self::$usedStrings;
294
    }
295
296
    /**
297
     * Returns the original translator.
298
     *
299
     * @return SymfonyTranslator
300
     */
301
    public function getTranslator(): SymfonyTranslator
302
    {
303
        return self::$translator;
304
    }
305
}
306