Translator::translateUsingCategorySources()   B
last analyzed

Complexity

Conditions 6
Paths 5

Size

Total Lines 36
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 20
c 1
b 0
f 0
dl 0
loc 36
ccs 20
cts 20
cp 1
rs 8.9777
cc 6
nc 5
nop 4
crap 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Translator;
6
7
use Psr\EventDispatcher\EventDispatcherInterface;
8
use RuntimeException;
9
use Stringable;
10
use Yiisoft\I18n\Locale;
11
use Yiisoft\Translator\Event\MissingTranslationCategoryEvent;
12
use Yiisoft\Translator\Event\MissingTranslationEvent;
13
14
/**
15
 * Translator translates a message into the specified language.
16
 */
17
final class Translator implements TranslatorInterface
18
{
19
    private MessageFormatterInterface $defaultMessageFormatter;
20
21
    /**
22
     * @var array Array of category message sources indexed by category names.
23
     * @psalm-var array<string,CategorySource[]>
24
     */
25
    private array $categorySources = [];
26
27
    /**
28
     * @psalm-var array<string,true>
29
     */
30
    private array $dispatchedMissingTranslationCategoryEvents = [];
31
32
    /**
33
     * @param string $locale Default locale to use if locale is not specified explicitly.
34
     * @param string|null $fallbackLocale Locale to use if message for the locale specified was not found. Null for
35
     * none.
36
     * @param EventDispatcherInterface|null $eventDispatcher Event dispatcher for translation events. Null for none.
37
     */
38 67
    public function __construct(
39
        private string $locale = 'en-US',
40
        private ?string $fallbackLocale = null,
41
        private string $defaultCategory = 'app',
42
        private ?EventDispatcherInterface $eventDispatcher = null,
43
        ?MessageFormatterInterface $defaultMessageFormatter = null,
44
    ) {
45 67
        $this->defaultMessageFormatter = $defaultMessageFormatter ?? new NullMessageFormatter();
46
    }
47
48 60
    public function addCategorySources(CategorySource ...$categories): static
49
    {
50 60
        foreach ($categories as $category) {
51 58
            if (isset($this->categorySources[$category->getName()])) {
52 14
                $this->categorySources[$category->getName()][] = $category;
53
            } else {
54 58
                $this->categorySources[$category->getName()] = [$category];
55
            }
56
        }
57
58 60
        return $this;
59
    }
60
61 5
    public function setLocale(string $locale): static
62
    {
63 5
        $this->locale = $locale;
64 5
        return $this;
65
    }
66
67 6
    public function getLocale(): string
68
    {
69 6
        return $this->locale;
70
    }
71
72 62
    public function translate(
73
        string|Stringable $id,
74
        array $parameters = [],
75
        string $category = null,
76
        string $locale = null
77
    ): string {
78 62
        $locale ??= $this->locale;
79
80 62
        $category ??= $this->defaultCategory;
81
82 62
        if (empty($this->categorySources[$category])) {
83 8
            $this->dispatchMissingTranslationCategoryEvent($category);
84 8
            return $this->defaultMessageFormatter->format((string) $id, $parameters, $this->fallbackLocale ?? $locale);
85
        }
86
87 54
        return $this->translateUsingCategorySources((string) $id, $parameters, $category, $locale);
88
    }
89
90 3
    public function withDefaultCategory(string $category): static
91
    {
92 3
        if (!isset($this->categorySources[$category])) {
93 1
            throw new RuntimeException('Category with name "' . $category . '" does not exist.');
94
        }
95
96 2
        $new = clone $this;
97 2
        $new->defaultCategory = $category;
98 2
        return $new;
99
    }
100
101 2
    public function withLocale(string $locale): static
102
    {
103 2
        $new = clone $this;
104 2
        $new->setLocale($locale);
105 2
        return $new;
106
    }
107
108 54
    private function translateUsingCategorySources(
109
        string $id,
110
        array $parameters,
111
        string $category,
112
        string $locale
113
    ): string {
114 54
        $sourceCategory = end($this->categorySources[$category]);
115
        do {
116 54
            $message = $sourceCategory->getMessage($id, $locale, $parameters);
117
118 54
            if ($message !== null) {
119 38
                return $sourceCategory->format($message, $parameters, $locale, $this->defaultMessageFormatter);
120
            }
121
122 34
            $this->eventDispatcher?->dispatch(new MissingTranslationEvent($sourceCategory->getName(), $locale, $id));
123 34
        } while (($sourceCategory = prev($this->categorySources[$category])) !== false);
124
125 29
        $localeObject = new Locale($locale);
126 29
        $fallback = $localeObject->fallbackLocale();
127
128 29
        if ($fallback->asString() !== $localeObject->asString()) {
129 15
            return $this->translateUsingCategorySources($id, $parameters, $category, $fallback->asString());
130
        }
131
132 22
        if (!empty($this->fallbackLocale)) {
133 10
            $fallbackLocaleObject = (new Locale($this->fallbackLocale))->fallbackLocale();
134 10
            if ($fallbackLocaleObject->asString() !== $localeObject->asString()) {
135 9
                return $this->translateUsingCategorySources($id, $parameters, $category, $fallbackLocaleObject->asString());
136
            }
137
        }
138
139 16
        return end($this->categorySources[$category])->format(
140 16
            $id,
141 16
            $parameters,
142 16
            $locale,
143 16
            $this->defaultMessageFormatter
144 16
        );
145
    }
146
147 8
    private function dispatchMissingTranslationCategoryEvent(string $category): void
148
    {
149
        if (
150 8
            $this->eventDispatcher !== null
151 8
            && !isset($this->dispatchedMissingTranslationCategoryEvents[$category])
152
        ) {
153 2
            $this->dispatchedMissingTranslationCategoryEvents[$category] = true;
154 2
            $this->eventDispatcher->dispatch(new MissingTranslationCategoryEvent($category));
155
        }
156
    }
157
}
158