Passed
Pull Request — master (#86)
by Wilmer
02:48
created

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