Passed
Push — master ( 6b7e7f...8716af )
by Sergei
02:50
created

Translator::withDefaultCategory()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 1
dl 0
loc 9
ccs 6
cts 6
cp 1
crap 2
rs 10
c 0
b 0
f 0
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 59
    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 59
        $this->defaultMessageFormatter = $defaultMessageFormatter ?? new NullMessageFormatter();
46
    }
47
48 55
    public function addCategorySources(CategorySource ...$categories): void
49
    {
50 55
        foreach ($categories as $category) {
51 55
            if (isset($this->categorySources[$category->getName()])) {
52 14
                $this->categorySources[$category->getName()][] = $category;
53
            } else {
54 55
                $this->categorySources[$category->getName()] = [$category];
55
            }
56
        }
57
    }
58
59 3
    public function setLocale(string $locale): void
60
    {
61 3
        $this->locale = $locale;
62
    }
63
64 4
    public function getLocale(): string
65
    {
66 4
        return $this->locale;
67
    }
68
69 56
    public function translate(
70
        string|Stringable $id,
71
        array $parameters = [],
72
        string $category = null,
73
        string $locale = null
74
    ): string {
75 56
        $locale ??= $this->locale;
76
77 56
        $category ??= $this->defaultCategory;
78
79 56
        if (empty($this->categorySources[$category])) {
80 4
            $this->dispatchMissingTranslationCategoryEvent($category);
81 4
            return $this->defaultMessageFormatter->format((string) $id, $parameters, $locale);
82
        }
83
84 52
        return $this->translateUsingCategorySources((string) $id, $parameters, $category, $locale);
85
    }
86
87
    /**
88
     * @psalm-immutable
89
     */
90 2
    public function withDefaultCategory(string $category): self
91
    {
92 2
        if (!isset($this->categorySources[$category])) {
93 1
            throw new RuntimeException('Category with name "' . $category . '" does not exist.');
94
        }
95
96 1
        $new = clone $this;
97 1
        $new->defaultCategory = $category;
98 1
        return $new;
99
    }
100
101 1
    public function withLocale(string $locale): self
102
    {
103 1
        $new = clone $this;
104 1
        $new->setLocale($locale);
105 1
        return $new;
106
    }
107
108 52
    private function translateUsingCategorySources(
109
        string $id,
110
        array $parameters,
111
        string $category,
112
        string $locale
113
    ): string {
114 52
        $sourceCategory = end($this->categorySources[$category]);
115
        do {
116 52
            $message = $sourceCategory->getMessage($id, $locale, $parameters);
117
118 52
            if ($message !== null) {
119 37
                return $sourceCategory->format($message, $parameters, $locale, $this->defaultMessageFormatter);
120
            }
121
122 33
            if ($this->eventDispatcher !== null) {
123 12
                $this->eventDispatcher->dispatch(new MissingTranslationEvent($sourceCategory->getName(), $locale, $id));
124
            }
125 33
        } while (($sourceCategory = prev($this->categorySources[$category])) !== false);
126
127 28
        $localeObject = new Locale($locale);
128 28
        $fallback = $localeObject->fallbackLocale();
129
130 28
        if ($fallback->asString() !== $localeObject->asString()) {
131 14
            return $this->translateUsingCategorySources($id, $parameters, $category, $fallback->asString());
132
        }
133
134 21
        if (!empty($this->fallbackLocale)) {
135 10
            $fallbackLocaleObject = (new Locale($this->fallbackLocale))->fallbackLocale();
136 10
            if ($fallbackLocaleObject->asString() !== $localeObject->asString()) {
137 9
                return $this->translateUsingCategorySources($id, $parameters, $category, $fallbackLocaleObject->asString());
138
            }
139
        }
140
141 15
        return end($this->categorySources[$category])->format(
142
            $id,
143
            $parameters,
144
            $locale,
145 15
            $this->defaultMessageFormatter
146
        );
147
    }
148
149 4
    private function dispatchMissingTranslationCategoryEvent(string $category): void
150
    {
151
        if (
152 4
            $this->eventDispatcher !== null
153 4
            && !isset($this->dispatchedMissingTranslationCategoryEvents[$category])
154
        ) {
155 2
            $this->dispatchedMissingTranslationCategoryEvents[$category] = true;
156 2
            $this->eventDispatcher->dispatch(new MissingTranslationCategoryEvent($category));
157
        }
158
    }
159
}
160