TranslationsAnalyzer::loadedTranslations()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 10
c 1
b 0
f 0
dl 0
loc 14
rs 9.9332
cc 2
nc 2
nop 0
1
<?php
2
3
/**
4
 * webtrees-mod-translationtool: MyArtJaub Translation Tool Module for webtrees
5
 *
6
 * @package MyArtJaub\Webtrees\Module
7
 * @subpackage TranslationTool
8
 * @author Jonathan Jaubart <[email protected]>
9
 * @copyright Copyright (c) 2016-2023, Jonathan Jaubart
10
 * @license http://www.gnu.org/licenses/gpl.html GNU General Public License, version 3
11
 */
12
13
declare(strict_types=1);
14
15
namespace MyArtJaub\Webtrees\Module\TranslationTool\Model;
16
17
use Fisharebest\Webtrees\I18N;
18
use Gettext\Translation;
19
use Illuminate\Support\Collection;
20
use MyArtJaub\Webtrees\Module\TranslationTool\Services\SourceCodeService;
21
use ReflectionClass;
22
23
/**
24
 * Translations Analyzer
25
 * Extract translations from the code, and analize the translations list.
26
 */
27
class TranslationsAnalyzer
28
{
29
    /** @var SourceCodeService $sourcecode_service */
30
    private $sourcecode_service;
31
32
    /**
33
     * List of items to be translated found in the code.
34
     * @var ?Collection<string, array{headers: \Gettext\Headers, translation: Translation}> $strings_to_translate
35
     */
36
    private $strings_to_translate;
37
38
    /**
39
     * List of translations loaded through the standard I18N library.
40
     * @var ?Collection<string, string> $loaded_translations
41
     */
42
    private $loaded_translations;
43
44
    /**
45
     * List of translations loaded within the MyArtJaub modules
46
     * @var ?Collection<string, array<string>> $maj_translations
47
     */
48
    private $maj_translations;
49
50
    /**
51
     * List of paths for source code
52
     * @var Collection<string, array<string>> $source_code_paths
53
     */
54
    private $source_code_paths;
55
56
    /**
57
     * Reference language code
58
     * @var string $language
59
     */
60
    private $language;
61
62
    /**
63
     * Constructor for TranslationAnalyzer
64
     *
65
     * @param SourceCodeService $sourcecode_service
66
     * @param Collection<string, array<string>> $code_paths
67
     * @param string $language
68
     */
69
    public function __construct(SourceCodeService $sourcecode_service, Collection $code_paths, string $language = null)
70
    {
71
        $this->sourcecode_service = $sourcecode_service;
72
        $this->language = $language ?? I18N::locale()->languageTag();
73
        $this->source_code_paths = $code_paths;
74
    }
75
76
    /******************************
77
     *  Data retrieval functions  *
78
     ******************************/
79
80
    /**
81
     * Compute the key for a given GetText Translation entry, dealing with context \x04 and plural \x00 cases.
82
     *
83
     * @param Translation $translation
84
     * @return string
85
     */
86
    private function getTranslationKey(Translation $translation): string
87
    {
88
        $key = $translation->getOriginal();
89
        $translation_plural = $translation->getPlural();
90
        if ($translation_plural !== null && strlen($translation_plural) > 0) {
91
            $key .= I18N::PLURAL . $translation_plural;
92
        }
93
        $translation_context = $translation->getContext();
94
        if ($translation_context !== null && strlen($translation_context) > 0) {
95
            $key = $translation_context . I18N::CONTEXT . $key;
96
        }
97
        return $key;
98
    }
99
100
    /**
101
     * Returns the strings tagged for translation in the source code.
102
     * The returned structure is an associative Collection with :
103
     *      - key: MD5 hash of the Translation key
104
     *      - value: array [
105
     *              headers => GetText Translations Header (including domain)
106
     *              translation => GetTex Translation
107
     *          ]
108
     *
109
     * @return Collection<string, array{headers: \Gettext\Headers, translation: \Gettext\Translation}>
110
     */
111
    private function stringsToTranslate(): Collection
112
    {
113
        if ($this->strings_to_translate === null) {
114
            $strings_to_translate_list = $this->sourcecode_service->findStringsToTranslate($this->source_code_paths);
115
116
            $this->strings_to_translate = new Collection();
117
            foreach ($strings_to_translate_list as $translations) {
118
                foreach ($translations as $translation) {
119
                    $key = md5($this->getTranslationKey($translation));
120
                    $this->strings_to_translate->put($key, [
121
                        'headers' => $translations->getHeaders(),
122
                        'translation' => $translation
123
                    ]);
124
                }
125
            }
126
        }
127
        return $this->strings_to_translate;
128
    }
129
130
    /**
131
     * Returns the list of translations loaded through the standard I18N library.
132
     * The returned structure is an associative Collection with :
133
     *      - key: Original translation key
134
     *      - value: Translated string
135
     *
136
     * @return Collection<string, string>
137
     */
138
    private function loadedTranslations(): Collection
139
    {
140
        if ($this->loaded_translations === null) {
141
            $I18N_class = new ReflectionClass(I18N::class);
142
            $translator_property = $I18N_class->getProperty('translator');
143
            $translator_property->setAccessible(true);
144
            $wt_translator = (object) $translator_property->getValue();
145
146
            $translator_class = new ReflectionClass(get_class($wt_translator));
147
            $translations_property = $translator_class->getProperty('translations');
148
            $translations_property->setAccessible(true);
149
            $this->loaded_translations = collect($translations_property->getValue($wt_translator));
150
        }
151
        return $this->loaded_translations;
152
    }
153
154
    /**
155
     * Returns the list of translations loaded in MyArtJaub modules.
156
     * The returned structure is an associative Collection with :
157
     *      - key: MD5 hash of the translation key
158
     *      - value: array [
159
     *              0 => Module name
160
     *              1 => Translation key
161
     *          ]
162
     *
163
     * @return Collection<string, array<string>>
164
     */
165
    private function loadedMyArtJaubTranslations(): Collection
166
    {
167
        if ($this->maj_translations === null) {
168
            $maj_translations_list = $this->sourcecode_service->listMyArtJaubTranslations($this->language);
169
170
            $this->maj_translations = new Collection();
171
            foreach ($maj_translations_list as $module => $maj_mod_translations) {
172
                foreach (array_keys($maj_mod_translations) as $maj_mod_translation) {
173
                    $this->maj_translations->put(md5($maj_mod_translation), [$module, $maj_mod_translation]);
174
                }
175
            }
176
        }
177
        return $this->maj_translations;
178
    }
179
180
    /*************************
181
     *  Analyzer functions   *
182
     *************************/
183
184
185
    /**
186
     * Returns the translations missing through the standard I18N.
187
     * The returned array is composed of items with the structure:
188
     *      - array [
189
     *              headers => GetText Translations Header (including domain)
190
     *              translation => GetTex Translation
191
     *          ]
192
     *
193
     * @return array<array{headers: \Gettext\Headers, translation: \Gettext\Translation}>
194
     */
195
    public function missingTranslations(): array
196
    {
197
        $missing_translations = array();
198
        foreach ($this->stringsToTranslate() as $translation_info) {
199
            $translation = $translation_info['translation'];
200
            if (!$this->loadedTranslations()->has($this->getTranslationKey($translation))) {
201
                $missing_translations[] = $translation_info;
202
            }
203
        }
204
205
        return $missing_translations;
206
    }
207
208
    /**
209
     * Returns the translations defined in the MaJ modules, but not actually used in the code.
210
     * The returned array is composed of items with the structure:
211
     *      - array [
212
     *              0 => Module name
213
     *              1 => Translation key
214
     *          ]
215
     *
216
     * @return array<array<string>>
217
     */
218
    public function nonUsedMajTranslations(): array
219
    {
220
        $removed_translations = [];
221
        $strings_to_translate_list = $this->stringsToTranslate();
222
        foreach ($this->loadedMyArtJaubTranslations() as $msgid => $translation_info) {
223
            if (!$strings_to_translate_list->has($msgid)) {
224
                $removed_translations[] = $translation_info;
225
            }
226
        }
227
        return $removed_translations;
228
    }
229
230
    /**
231
     * Get some statistics about the translations data.
232
     * Returns an array with the statistics:
233
     *      nbTranslations : total number of translations
234
     *      nbTranslationsFound: total number of translations found in the code
235
     *      nbMajTranslations: total number of translations loaded in the MyArtJaub modules
236
     *
237
     * @return array<string, int>
238
     */
239
    public function translationsStatictics(): array
240
    {
241
        return [
242
            'nbTranslations' => $this->loadedTranslations()->count(),
243
            'nbTranslationsFound' => $this->stringsToTranslate()->count(),
244
            'nbMajTranslations' => $this->loadedMyArtJaubTranslations()->count()
245
        ];
246
    }
247
}
248