Passed
Push — master ( b292e6...c5f4fa )
by butschster
07:03
created

Translator::transChoice()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 29
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4.128

Importance

Changes 0
Metric Value
cc 4
eloc 14
nc 4
nop 5
dl 0
loc 29
ccs 8
cts 10
cp 0.8
crap 4.128
rs 9.7998
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Spiral\Translator;
6
7
use Psr\EventDispatcher\EventDispatcherInterface;
8
use Spiral\Core\Container\SingletonInterface;
9
use Spiral\Translator\Config\TranslatorConfig;
10
use Spiral\Translator\Event\LocaleUpdated;
11
use Spiral\Translator\Exception\LocaleException;
12
use Spiral\Translator\Exception\PluralizationException;
13
use Symfony\Component\Translation\IdentityTranslator;
14
15
/**
16
 * Implementation of Symfony\TranslatorInterface with memory caching, automatic message
17
 * registration and bundle/domain grouping.
18
 */
19
final class Translator implements TranslatorInterface, SingletonInterface
20
{
21
    private string $locale;
22
23 35
    public function __construct(
24
        private readonly TranslatorConfig $config,
25
        private readonly CatalogueManagerInterface $catalogueManager,
26
        /** @internal */
27
        private readonly IdentityTranslator $identityTranslator = new IdentityTranslator(),
28
        private readonly ?EventDispatcherInterface $dispatcher = null,
29
    ) {
30 35
        $this->locale = $this->config->getDefaultLocale();
31 35
        $this->catalogueManager->load($this->locale);
32
    }
33
34 4
    public function getDomain(string $bundle): string
35
    {
36 4
        return $this->config->resolveDomain($bundle);
37
    }
38
39
    /**
40
     * @throws LocaleException
41
     */
42 7
    public function setLocale(string $locale): void
43
    {
44 7
        if (!$this->catalogueManager->has($locale)) {
45 1
            throw new LocaleException($locale);
46
        }
47
48 6
        $this->locale = $locale;
49 6
        $this->catalogueManager->load($locale);
50
51 6
        $this->dispatcher?->dispatch(new LocaleUpdated($locale));
52
    }
53
54 7
    public function getLocale(): string
55
    {
56 7
        return $this->locale;
57
    }
58
59 22
    public function getCatalogueManager(): CatalogueManagerInterface
60
    {
61 22
        return $this->catalogueManager;
62
    }
63
64
    /**
65
     * Parameters will be embedded into string using { and } braces.
66
     *
67
     * @throws LocaleException
68
     */
69 8
    public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null): string
70
    {
71 8
        $domain = $domain ?? $this->config->getDefaultDomain();
72 8
        $locale = $locale ?? $this->locale;
73
74 8
        $message = $this->get($locale, $domain, $id);
75
76 8
        return self::interpolate($message, $parameters);
77
    }
78
79
    /**
80
     * Default symfony pluralizer to be used. Parameters will be embedded into string using { and }
81
     * braces. In addition you can use forced parameter {n} which contain formatted number value.
82
     *
83
     * @throws LocaleException
84
     * @throws PluralizationException
85
     */
86 4
    public function transChoice(
87
        string $id,
88
        string|int $number,
89
        array $parameters = [],
90
        string $domain = null,
91
        string $locale = null
92
    ): string {
93 4
        $domain = $domain ?? $this->config->getDefaultDomain();
94 4
        $locale = $locale ?? $this->locale;
95
96
        try {
97 4
            $message = $this->get($locale, $domain, $id);
98
99 4
            $pluralized = $this->identityTranslator->trans(
100
                $message,
101
                ['%count%' => $number],
102
                null,
103
                $locale
104
            );
105
        } catch (\InvalidArgumentException $e) {
106
            //Wrapping into more explanatory exception
107
            throw new PluralizationException($e->getMessage(), $e->getCode(), $e);
108
        }
109
110 4
        if (empty($parameters['n']) && is_numeric($number)) {
111 4
            $parameters['n'] = $number;
112
        }
113
114 4
        return self::interpolate($pluralized, $parameters);
115
    }
116
117
    /**
118
     * Interpolate string with given parameters, used by many spiral components.
119
     *
120
     * Input: Hello {name}! Good {time}! + ['name' => 'Member', 'time' => 'day']
121
     * Output: Hello Member! Good Day!
122
     *
123
     * @param array  $values Arguments (key => value). Will skip unknown names.
124
     * @param string $prefix Placeholder prefix, "{" by default.
125
     * @param string $postfix Placeholder postfix, "}" by default.
126
     */
127 13
    public static function interpolate(
128
        string $string,
129
        array $values,
130
        string $prefix = '{',
131
        string $postfix = '}'
132
    ): string {
133 13
        $replaces = [];
134 13
        foreach ($values as $key => $value) {
135 7
            $value = (\is_array($value) || $value instanceof \Closure) ? '' : $value;
136
137 7
            if (\is_object($value)) {
138 1
                if (\method_exists($value, '__toString')) {
139
                    $value = $value->__toString();
140
                } else {
141 1
                    $value = '';
142
                }
143
            }
144
145 7
            $replaces[$prefix . $key . $postfix] = $value;
146
        }
147
148 13
        return \strtr($string, $replaces);
149
    }
150
151
    /**
152
     * Check if string has translation braces [[ and ]].
153
     */
154 17
    public static function isMessage(string $string): bool
155
    {
156 17
        return \substr($string, 0, 2) == self::I18N_PREFIX
157 17
            && \substr($string, -2) == self::I18N_POSTFIX;
158
    }
159
160
    /**
161
     * Get translation message from the locale bundle or fallback to default locale.
162
     */
163 12
    protected function get(string &$locale, string $domain, string $string): string
164
    {
165 12
        if ($this->catalogueManager->get($locale)->has($domain, $string)) {
166 9
            return $this->catalogueManager->get($locale)->get($domain, $string);
167
        }
168
169 7
        $locale = $this->config->getFallbackLocale();
170
171 7
        if ($this->catalogueManager->get($locale)->has($domain, $string)) {
172 1
            return $this->catalogueManager->get($locale)->get($domain, $string);
173
        }
174
175
        // we can automatically register message
176 6
        if ($this->config->isAutoRegisterMessages()) {
177 6
            $this->catalogueManager->get($locale)->set($domain, $string, $string);
178 6
            $this->catalogueManager->save($locale);
179
        }
180
181
        // Unable to find translation
182 6
        return $string;
183
    }
184
}
185