Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
LocaleContext
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
6 / 6
11
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getLocale
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setLocale
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 isValidLocale
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getActiveLanguages
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
5
 resetCache
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Core\Translation;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Core\Api\ApiClientInterface;
12use App\Core\Translation\Presentation\Api\TranslationApiController;
13use Throwable;
14
15/**
16 * Locale Context Service.
17 *
18 * Manages active language locales, current request locale, and REST API lookup.
19 *
20 * @package App\Core\Translation
21 */
22final class LocaleContext implements LocaleContextInterface
23{
24    /** @var string Default fallback system language code. */
25    public const string DEFAULT_LOCALE = 'en';
26
27    /** @var array<string, array<string, mixed>> Minimal fallback language set if API is unavailable. */
28    private const array FALLBACK_LANGUAGES = [
29        'en' => [
30            'id'          => 1,
31            'code'        => 'en',
32            'name'        => 'English',
33            'native_name' => 'English',
34            'flag_icon'   => 'bi bi-globe',
35            'is_default'  => 1,
36            'sort_order'  => 10,
37        ],
38        'pl' => [
39            'id'          => 2,
40            'code'        => 'pl',
41            'name'        => 'Polish',
42            'native_name' => 'Polski',
43            'flag_icon'   => 'bi bi-flag',
44            'is_default'  => 0,
45            'sort_order'  => 20,
46        ],
47    ];
48
49    /** @var string Current active language code. */
50    private string $currentLocale = self::DEFAULT_LOCALE;
51
52    /** @var array<string, array<string, mixed>>|null Cached active languages map. */
53    private ?array $activeLanguages = null;
54
55    /**
56     * LocaleContext constructor.
57     *
58     * @param TranslationApiController|ApiClientInterface $apiSource API Controller or ApiClient instance.
59     */
60    public function __construct(
61        private readonly TranslationApiController|ApiClientInterface $apiSource
62    ) {
63    }
64
65    /**
66     * Gets current active locale code.
67     *
68     * @return string Current locale code (e.g. 'en', 'pl').
69     */
70    public function getLocale(): string
71    {
72        return $this->currentLocale;
73    }
74
75    /**
76     * Sets current active locale code if valid.
77     *
78     * @param string $locale Locale code to set.
79     */
80    public function setLocale(string $locale): void
81    {
82        $normalized = strtolower(trim($locale));
83        if ($this->isValidLocale($normalized)) {
84            $this->currentLocale = $normalized;
85        }
86    }
87
88    /**
89     * Checks if a locale code is valid and active in the system.
90     *
91     * @param string $locale Locale code to check.
92     * @return bool True if locale exists and is active.
93     */
94    public function isValidLocale(string $locale): bool
95    {
96        $languages = $this->getActiveLanguages();
97
98        return isset($languages[$locale]);
99    }
100
101    /**
102     * Returns list of active system languages indexed by code via REST API.
103     *
104     * @return array<string, array<string, mixed>> Active languages map.
105     */
106    public function getActiveLanguages(): array
107    {
108        if ($this->activeLanguages !== null) {
109            return $this->activeLanguages;
110        }
111
112        try {
113            if ($this->apiSource instanceof TranslationApiController) {
114                $response = $this->apiSource->activeLanguages();
115                $body = (string)$response->getBody();
116                /** @var array{data?: array<string, array<string, mixed>>} $payload */
117                $payload = (array)json_decode($body, true);
118                $languages = (array)($payload['data'] ?? []);
119            } else {
120                $payload = $this->apiSource->get('/api/v1/languages');
121                $languages = (array)($payload['data'] ?? []);
122            }
123
124            $this->activeLanguages = !empty($languages) ? $languages : self::FALLBACK_LANGUAGES;
125        } catch (Throwable) {
126            $this->activeLanguages = self::FALLBACK_LANGUAGES;
127        }
128
129        return $this->activeLanguages;
130    }
131
132    /**
133     * Resets internal languages cache.
134     */
135    public function resetCache(): void
136    {
137        $this->activeLanguages = null;
138    }
139}