Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
91.49% covered (success)
91.49%
43 / 47
40.00% covered (danger)
40.00%
2 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
TranslationStatistics
91.49% covered (success)
91.49%
43 / 47
40.00% covered (danger)
40.00%
2 / 5
13.10
0.00% covered (danger)
0.00%
0 / 1
 getStatistics
93.10% covered (success)
93.10%
27 / 29
0.00% covered (danger)
0.00%
0 / 1
6.01
 getLanguageStatistics
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getMissingKeys
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 getTranslationKeys
85.71% covered (success)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
2.01
 extractLanguageCode
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
1<?php
2
3/**
4 * The Administration Translation class for managing translation statistics.
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public License,
7 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
8 * obtain one at https://mozilla.org/MPL/2.0/.
9 *
10 * @package   phpMyFAQ
11 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
12 * @copyright 2025-2026 phpMyFAQ Team
13 * @license   https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14 * @link      https://www.phpmyfaq.de
15 * @since     2025-01-23
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Administration;
21
22readonly class TranslationStatistics
23{
24    private const string REFERENCE_LANGUAGE = 'en';
25
26    /**
27     * Returns statistics about all available translations.
28     *
29     * @return array<string, array<string, mixed>>
30     */
31    public function getStatistics(): array
32    {
33        $statistics = [];
34        $translationsDir = PMF_TRANSLATION_DIR;
35        $referenceKeys = $this->getTranslationKeys(self::REFERENCE_LANGUAGE);
36        $totalReferenceKeys = count($referenceKeys);
37
38        $languageFiles = glob($translationsDir . '/language_*.php');
39
40        if ($languageFiles === false) {
41            return $statistics;
42        }
43
44        foreach ($languageFiles as $languageFile) {
45            $language = $this->extractLanguageCode($languageFile);
46
47            if ($language === null) {
48                continue;
49            }
50
51            $translationKeys = $this->getTranslationKeys($language);
52            $totalKeys = count($translationKeys);
53            $missingKeys = array_diff($referenceKeys, $translationKeys);
54            $missingCount = count($missingKeys);
55            $translatedCount = $totalKeys;
56
57            if ($language !== self::REFERENCE_LANGUAGE) {
58                $translatedCount = $totalReferenceKeys - $missingCount;
59            }
60
61            $statistics[$language] = [
62                'language_code' => $language,
63                'total_keys' => $totalKeys,
64                'translated_keys' => $translatedCount,
65                'missing_keys' => $missingCount,
66                'completion_percentage' => $totalReferenceKeys > 0
67                    ? round(($translatedCount / $totalReferenceKeys) * 100, precision: 2)
68                    : 0.0,
69            ];
70        }
71
72        uasort($statistics, static fn($a, $b): int => $b['completion_percentage'] <=> $a['completion_percentage']);
73
74        return $statistics;
75    }
76
77    /**
78     * Returns detailed statistics for a specific language.
79     *
80     * @return array<string, mixed>|null
81     */
82    public function getLanguageStatistics(string $language): ?array
83    {
84        $statistics = $this->getStatistics();
85
86        return $statistics[$language] ?? null;
87    }
88
89    /**
90     * Returns the list of missing translation keys for a specific language.
91     *
92     * @return array<int, string>
93     */
94    public function getMissingKeys(string $language): array
95    {
96        if ($language === self::REFERENCE_LANGUAGE) {
97            return [];
98        }
99
100        $referenceKeys = $this->getTranslationKeys(self::REFERENCE_LANGUAGE);
101        $languageKeys = $this->getTranslationKeys($language);
102
103        return array_values(array_diff($referenceKeys, $languageKeys));
104    }
105
106    /**
107     * Returns all translation keys for a specific language.
108     *
109     * @return array<int, string>
110     */
111    private function getTranslationKeys(string $language): array
112    {
113        $translationsDir = PMF_TRANSLATION_DIR;
114        $file = $translationsDir . '/language_' . $language . '.php';
115
116        if (!file_exists($file)) {
117            return [];
118        }
119
120        $PMF_LANG = [];
121        include $file;
122
123        return array_map(strval(...), array_keys($PMF_LANG));
124    }
125
126    /**
127     * Extracts the language code from a translation file path.
128     */
129    private function extractLanguageCode(string $filePath): ?string
130    {
131        $matches = [];
132        if (preg_match(pattern: '/language_([a-z_]+)\.php$/', subject: $filePath, matches: $matches)) {
133            return $matches[1];
134        }
135
136        return null;
137    }
138}