TranslationsProvider::getBundleTranslations()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 1
b 0
f 0
nc 2
nop 3
dl 0
loc 8
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Locastic\SymfonyTranslationBundle\Provider;
6
7
use InvalidArgumentException;
8
use Locastic\SymfonyTranslationBundle\Utils\ArrayUtils;
9
use LogicException;
10
use Symfony\Component\HttpKernel\Config\FileLocator;
11
use Symfony\Component\Yaml\Yaml;
12
13
use function array_key_exists;
14
use function array_key_first;
15
use function array_replace_recursive;
16
use function in_array;
17
18
final class TranslationsProvider implements TranslationsProviderInterface
19
{
20
    private array $bundles;
21
22
    private TranslationDomainsProviderInterface $translationDomainsProvider;
23
24
    private LocalesProviderInterface $localesProvider;
25
26
    private FileLocator $fileLocator;
27
28
    private TranslationFileNameProviderInterface $translationFileNameProvider;
29
30
    private DefaultTranslationDirectoryProviderInterface $defaultTranslationDirectoryProvider;
31
32
    private ThemesProviderInterface $themesProvider;
33
34
    public function __construct(
35
        array $enabledBundles,
36
        TranslationDomainsProviderInterface $translationDomainsProvider,
37
        LocalesProviderInterface $localesProvider,
38
        FileLocator $fileLocator,
39
        TranslationFileNameProviderInterface $translationFileNameProvider,
40
        DefaultTranslationDirectoryProviderInterface $defaultTranslationDirectoryProvider,
41
        ThemesProviderInterface $themesProvider
42
    ) {
43
        $this->bundles = $enabledBundles;
44
        $this->translationDomainsProvider = $translationDomainsProvider;
45
        $this->localesProvider = $localesProvider;
46
        $this->fileLocator = $fileLocator;
47
        $this->translationFileNameProvider = $translationFileNameProvider;
48
        $this->defaultTranslationDirectoryProvider = $defaultTranslationDirectoryProvider;
49
        $this->themesProvider = $themesProvider;
50
    }
51
52
    public function getTranslations(string $defaultLocaleCode, array $locales): array
53
    {
54
        $bundleTranslations = [];
55
        foreach ($this->bundles as $bundleName => $bundle) {
56
            $bundleTranslations = array_replace_recursive(
57
                $bundleTranslations,
58
                $this->getBundleTranslations($bundleName, $defaultLocaleCode, $locales)
59
            );
60
        }
61
        $appTranslations = $this->getDirectoryTranslations(
62
            $this->defaultTranslationDirectoryProvider->getDefaultDirectory(),
63
            $defaultLocaleCode,
64
            $locales
65
        );
66
67
        $themes = $this->themesProvider->getAll();
68
        $translations = [];
69
        foreach ($themes as $theme) {
70
            if (!empty($theme->getPath())) {
71
                $translationDirectory = $theme->getPath() . '/translations/';
72
            } else {
73
                $translationDirectory = $this->defaultTranslationDirectoryProvider->getDefaultDirectory();
74
            }
75
            $themeTranslations = $this->getDirectoryTranslations($translationDirectory, $defaultLocaleCode, $locales);
76
            $themeTranslations = $this->removeEmptyKeys($themeTranslations);
77
78
            $mergedTranslations = array_replace_recursive($bundleTranslations, $appTranslations, $themeTranslations);
79
            $mergedTranslations = $this->fillMissingKeys($mergedTranslations, $locales);
80
            $translations[$theme->getName()] = $mergedTranslations;
81
        }
82
        $translations = $this->fillMissingKeys($translations, $locales);
83
84
        ArrayUtils::recursiveKsort($translations);
85
86
        return $translations;
87
    }
88
89
    public function getBundleTranslations(string $bundleName, string $localeCode, array $locales): array
90
    {
91
        if (!$this->doesBundleHaveTranslations($bundleName)) {
92
            return [];
93
        }
94
        $translationsDirectory = $this->getBundleTranslationsDirectory($bundleName);
95
96
        return $this->getDirectoryTranslations($translationsDirectory, $localeCode, $locales);
0 ignored issues
show
Bug introduced by
It seems like $translationsDirectory can also be of type null; however, parameter $directory of Locastic\SymfonyTranslat...DirectoryTranslations() 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

96
        return $this->getDirectoryTranslations(/** @scrutinizer ignore-type */ $translationsDirectory, $localeCode, $locales);
Loading history...
97
    }
98
99
    public function getDirectoryTranslations(string $directory, string $localeCode, array $locales): array
100
    {
101
        $domains = $this->translationDomainsProvider->toArray($directory);
102
        $defaultLocales = $this->localesProvider->getLocalesFromCode($localeCode);
103
104
        $directoryTranslations = [];
105
        foreach ($domains as $domain) {
106
            foreach ($defaultLocales as $defaultLocale) {
107
                $translations = $this->getYamlTranslations($directory, $domain, $defaultLocale);
108
                $translations = array_replace_recursive($translations, $this->getXmlTranslations($directory, $domain, $defaultLocale));
109
110
                if (!array_key_exists($domain, $directoryTranslations)) {
111
                    $directoryTranslations[$domain] = [];
112
                }
113
                $translations = ArrayUtils::arrayFlatten($translations);
114
                foreach ($translations as $key => $value) {
115
                    $translations[$key] = [$localeCode => $value];
116
                }
117
118
                $directoryTranslations[$domain] = array_replace_recursive($directoryTranslations[$domain], $translations);
119
            }
120
121
            foreach ($locales as $locale) {
122
                $availableLocales = $this->localesProvider->getLocalesFromCode($locale);
123
                foreach ($availableLocales as $availableLocale) {
124
                    $translations = $this->getYamlTranslations($directory, $domain, $availableLocale);
125
                    $translations = array_replace_recursive($translations, $this->getXmlTranslations($directory, $domain, $availableLocale));
126
127
                    if (!array_key_exists($domain, $directoryTranslations)) {
128
                        continue;
129
                    }
130
                    $translations = ArrayUtils::arrayFlatten($translations);
131
                    foreach ($translations as $key => $value) {
132
                        $directoryTranslations[$domain][$key][$locale] = $value;
133
                    }
134
                }
135
            }
136
        }
137
138
        return $directoryTranslations;
139
    }
140
141
    public function doesBundleHaveTranslations(string $bundleName): bool
142
    {
143
        try {
144
            $this->fileLocator->locate(sprintf('@%s/Resources/translations/', $bundleName));
145
146
            return true;
147
        } catch (InvalidArgumentException $exception) {
148
            return false;
149
        }
150
    }
151
152
    public function getBundleTranslationsDirectory(string $bundleName): ?string
153
    {
154
        if (!$this->doesBundleHaveTranslations($bundleName)) {
155
            return null;
156
        }
157
158
        return $this->fileLocator->locate(sprintf('@%s/Resources/translations/', $bundleName));
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->fileLocato...ations/', $bundleName)) could return the type string[] which is incompatible with the type-hinted return null|string. Consider adding an additional type-check to rule them out.
Loading history...
159
    }
160
161
    public function getYamlTranslations(string $directory, string $domain, string $locale): array
162
    {
163
        $translations = [];
164
165
        $formats = ['yml', 'yaml'];
166
        foreach ($formats as $format) {
167
            $fileName = $this->translationFileNameProvider->getFromValues($directory, $domain, $locale, $format);
168
            $translations = array_replace_recursive($translations, $this->getTranslationFileContent($fileName));
169
        }
170
171
        return $translations;
172
    }
173
174
    public function getTranslationFileContent(string $filePath, string $type = self::TYPE_YAML): array
175
    {
176
        if (!file_exists($filePath)) {
177
            return [];
178
        }
179
180
        switch ($type) {
181
            case self::TYPE_XML:
182
                throw new LogicException('This has not been implemented yet');
183
            case self::TYPE_YAML:
184
            default:
185
                return Yaml::parse(file_get_contents($filePath));
186
        }
187
    }
188
189
    public function getXmlTranslations(string $directory, string $domain, string $locale): array
190
    {
191
        $translations = [];
192
193
        $formats = ['xml'];
194
        foreach ($formats as $format) {
195
            $fileName = $this->translationFileNameProvider->getFromValues($directory, $domain, $locale, $format);
196
            $translations = array_replace_recursive($translations, $this->getTranslationFileContent($fileName, self::TYPE_XML));
197
        }
198
199
        return $translations;
200
    }
201
202
    private function fillMissingKeys(array $translations, array $locales): array
203
    {
204
        foreach ($translations as $key => $value) {
205
            if (in_array(array_key_first($value), $locales)) {
206
                foreach ($locales as $locale) {
207
                    if (!array_key_exists($locale, $value)) {
208
                        $translations[$key][$locale] = '';
209
                    }
210
                }
211
            } else {
212
                $translations[$key] = $this->fillMissingKeys($value, $locales);
213
            }
214
        }
215
216
        return $translations;
217
    }
218
219
    public function removeEmptyKeys(array $translations): array
220
    {
221
        foreach ($translations as $key => $value) {
222
            if (empty($value)) {
223
                unset($translations[$key]);
224
            } elseif (is_array($value)) {
225
                $translations[$key] = $this->removeEmptyKeys($value);
226
            }
227
        }
228
229
        return $translations;
230
    }
231
232
    public function defineAllKeys(array $translations, array $locales): array
233
    {
234
        $keys = [];
235
        foreach ($translations as $themeTranslations) {
236
            foreach ($themeTranslations as $domain => $domainTranslations) {
237
                if (!array_key_exists($domain, $keys)) {
238
                    $keys[$domain] = [];
239
                }
240
                foreach ($domainTranslations as $key => $keyTranslations) {
241
                    if (!in_array($key, $keys)) {
242
                        $keys[$domain][] = $key;
243
                    }
244
                }
245
            }
246
        }
247
248
        $translationTemplate = [];
249
        foreach ($locales as $locale) {
250
            $translationTemplate[$locale] = '';
251
        }
252
        foreach ($translations as $themeName => $themeTranslations) {
253
            foreach ($themeTranslations as $domain => $domainTranslations) {
254
                foreach ($keys[$domain] as $key) {
255
                    if (!array_key_exists($key, $domainTranslations)) {
256
                        $translations[$themeName][$domain][$key] = $translationTemplate;
257
                    }
258
                }
259
            }
260
        }
261
262
        return $translations;
263
    }
264
}
265