Test Failed
Push — main ( b6e144...1a2341 )
by Rafael
05:34
created

Translator::load()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 10
nc 1
nop 0
dl 0
loc 14
rs 9.9332
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
abstract 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_DIR = 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 static function load()
86
    {
87
        $config = Config::getModuleVar('translator');
88
        $language = $config['main']['language'] ?? self::FALLBACK_LANG;
89
90
        self::$languageFolder = constant('BASE_DIR') . '/src' . self::LANG_DIR;
91
        self::$translator = new SymfonyTranslator($language);
92
        self::$translator->setFallbackLocales([self::FALLBACK_LANG]);
93
        self::$translator->addLoader(self::FORMAT, new YamlFileLoader());
94
        self::$usedStrings = [];
95
        self::$missingStrings = [];
96
        self::$languageFolders = [];
97
98
        self::loadLangFiles();
99
    }
100
101
    /**
102
     * Load the translation files following the priorities.
103
     * In this case, the translator must be provided with the routes in reverse order.
104
     *
105
     * @return void
106
     */
107
    public static function loadLangFiles(): void
108
    {
109
        $langFolders = self::getLangFolders();
110
        if (count($langFolders) === 0 || empty($langFolders[0])) {
111
            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...
112
        }
113
114
        $langFiles = Finder::create()
115
            ->files()
116
            ->name('*' . self::EXT)
117
            ->in($langFolders)
118
            ->sortByName();
119
120
        foreach ($langFiles as $langFile) {
121
            $langCode = str_replace(self::EXT, '', $langFile->getRelativePathName());
122
            try {
123
                Yaml::parseFile($langFile->getPathName());
124
                self::$translator->addResource(self::FORMAT, $langFile->getPathName(), $langCode);
125
            } catch (ParseException $e) {
126
                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

126
                Logger::/** @scrutinizer ignore-call */ 
127
                        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...
127
                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

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