Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
66 / 66
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
TranslationServiceProvider
100.00% covered (success)
100.00%
66 / 66
100.00% covered (success)
100.00%
6 / 6
14
100.00% covered (success)
100.00%
1 / 1
 register
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
1 / 1
2
 boot
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
2
 setLocale
n/a
0 / 0
n/a
0 / 0
1
 getTranslator
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
3
 loadFile
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getFile
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getFileLoader
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Engelsystem\Helpers\Translation;
6
7use Engelsystem\Config\Config;
8use Engelsystem\Container\ServiceProvider;
9use Engelsystem\Http\Request;
10use Gettext\Loader\MoLoader;
11use Gettext\Loader\PoLoader;
12use Gettext\Translations;
13use Illuminate\Contracts\Container\BindingResolutionException;
14use Illuminate\Support\Str;
15use Symfony\Component\HttpFoundation\Session\Session;
16
17class TranslationServiceProvider extends ServiceProvider
18{
19    protected GettextTranslator|array $translators = [];
20
21    public function register(): void
22    {
23        /** @var Config $config */
24        $config = $this->app->get('config');
25        $configOptions = $config['config_options'];
26
27        $langPath = $this->app->get('path.lang');
28        $locales = array_diff(scandir($langPath), ['.', '..']);
29        $locales = array_values(array_filter($locales, fn($lang) => is_dir($langPath . '/' . $lang)));
30
31        $fallbackLocale = in_array('en_US', $locales) ? 'en_US' : $locales[0];
32
33        $namedLocales = array_flip($locales);
34        array_walk($namedLocales, function (&$value, $key): void {
35            $value = 'language.' . $key;
36        });
37        $configOptions['system']['config']['locales']['default'] = $locales;
38        $configOptions['system']['config']['locales']['data'] = $namedLocales;
39        $configOptions['system']['config']['default_locale']['default'] = $fallbackLocale;
40        $configOptions['system']['config']['default_locale']['data'] = $namedLocales;
41
42        $config['locales'] = $configOptions['locales'] ?? $locales;
43        $config['default_locale'] = $configOptions['default_locale'] ?? $fallbackLocale;
44
45        $config->set('config_options', $configOptions);
46
47        $translator = $this->app->make(
48            Translator::class,
49            [
50                'locale'                => $fallbackLocale,
51                'locales'               => $locales,
52                'fallbackLocale'        => $fallbackLocale,
53                'getTranslatorCallback' => [$this, 'getTranslator'],
54                'localeChangeCallback'  => [$this, 'setLocale'],
55            ],
56        );
57        $this->app->singleton(Translator::class, fn() => $translator);
58        $this->app->alias(Translator::class, 'translator');
59    }
60
61    public function boot(): void
62    {
63        /** @var Translator $translator */
64        $translator = $this->app->get(Translator::class);
65
66        /** @var Config $config */
67        $config = $this->app->get('config');
68        /** @var Session $session */
69        $session = $this->app->get('session');
70        /** @var Request $request */
71        $request = $this->app->get('request');
72
73        $locales = $config->get('locales', []);
74        $defaultLocale = $config->get('default_locale');
75        $locale = $request->getPreferredLanguage(array_merge([$defaultLocale], $locales));
76
77        $sessionLocale = $session->get('locale', $locale);
78        if (in_array($sessionLocale, $locales)) {
79            $locale = $sessionLocale;
80        }
81
82        $session->set('locale', $locale);
83        $translator->setLocale($locale);
84    }
85
86    /**
87     * @codeCoverageIgnore
88     */
89    public function setLocale(string $locale): void
90    {
91        $locale .= '.UTF-8';
92        // Set the users locale
93        putenv('LC_ALL=' . $locale);
94        setlocale(LC_ALL, $locale);
95
96        // Reset numeric formatting to allow output of floats
97        putenv('LC_NUMERIC=C');
98        setlocale(LC_NUMERIC, 'C');
99    }
100
101    public function getTranslator(string $locale): GettextTranslator
102    {
103        if (isset($this->translators[$locale])) {
104            return $this->translators[$locale];
105        }
106
107        $names = ['default', 'additional'];
108
109        /** @var Translations $translations */
110        $translations = $this->app->call([Translations::class, 'create']);
111        $path = $this->app->get('path.lang');
112        foreach ($names as $name) {
113            $file = $this->getFile($locale, $path, $name);
114            $translations = $this->loadFile($file, $translations);
115        }
116
117        $file = $this->getFile($locale, $this->app->get('path.config') . '/lang', 'custom');
118        $translations = $this->loadFile($file, $translations);
119
120        /** @var GettextTranslator $translator */
121        $translator = GettextTranslator::createFromTranslations($translations);
122        $this->translators[$locale] = $translator;
123
124        return $this->translators[$locale];
125    }
126
127    protected function loadFile(string $file, Translations $translations): Translations
128    {
129        if (!file_exists($file)) {
130            return $translations;
131        }
132
133        $loader = $this->getFileLoader($file);
134
135        return $loader->loadFile($file, $translations);
136    }
137
138    protected function getFile(string $locale, string $basePath, string $name = 'default'): string
139    {
140        $filepath = $basePath . '/' . $locale . '/' . $name;
141        $file = $filepath . '.mo';
142
143        if (!file_exists($file)) {
144            $file = $filepath . '.po';
145        }
146
147        return $file;
148    }
149
150    /**
151     * @throws BindingResolutionException
152     */
153    protected function getFileLoader(string $file): MoLoader|PoLoader
154    {
155        if (Str::endsWith($file, '.mo')) {
156            /** @var MoLoader $loader */
157            return $this->app->make(MoLoader::class);
158        } else {
159            /** @var PoLoader $loader */
160            return $this->app->make(PoLoader::class);
161        }
162    }
163}