comparison vendor/symfony/translation/Translator.php @ 0:c75dbcec494b

Initial commit from drush-created site
author Chris Cannam
date Thu, 05 Jul 2018 14:24:15 +0000
parents
children a9cd425dd02b
comparison
equal deleted inserted replaced
-1:000000000000 0:c75dbcec494b
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Symfony\Component\Translation;
13
14 use Symfony\Component\Translation\Loader\LoaderInterface;
15 use Symfony\Component\Translation\Exception\NotFoundResourceException;
16 use Symfony\Component\Translation\Exception\InvalidArgumentException;
17 use Symfony\Component\Translation\Exception\LogicException;
18 use Symfony\Component\Translation\Exception\RuntimeException;
19 use Symfony\Component\Config\ConfigCacheInterface;
20 use Symfony\Component\Config\ConfigCacheFactoryInterface;
21 use Symfony\Component\Config\ConfigCacheFactory;
22 use Symfony\Component\Translation\Formatter\MessageFormatterInterface;
23 use Symfony\Component\Translation\Formatter\ChoiceMessageFormatterInterface;
24 use Symfony\Component\Translation\Formatter\MessageFormatter;
25
26 /**
27 * @author Fabien Potencier <fabien@symfony.com>
28 */
29 class Translator implements TranslatorInterface, TranslatorBagInterface
30 {
31 /**
32 * @var MessageCatalogueInterface[]
33 */
34 protected $catalogues = array();
35
36 /**
37 * @var string
38 */
39 private $locale;
40
41 /**
42 * @var array
43 */
44 private $fallbackLocales = array();
45
46 /**
47 * @var LoaderInterface[]
48 */
49 private $loaders = array();
50
51 /**
52 * @var array
53 */
54 private $resources = array();
55
56 /**
57 * @var MessageFormatterInterface
58 */
59 private $formatter;
60
61 /**
62 * @var string
63 */
64 private $cacheDir;
65
66 /**
67 * @var bool
68 */
69 private $debug;
70
71 /**
72 * @var ConfigCacheFactoryInterface|null
73 */
74 private $configCacheFactory;
75
76 /**
77 * @param string $locale The locale
78 * @param MessageFormatterInterface|null $formatter The message formatter
79 * @param string|null $cacheDir The directory to use for the cache
80 * @param bool $debug Use cache in debug mode ?
81 *
82 * @throws InvalidArgumentException If a locale contains invalid characters
83 */
84 public function __construct($locale, $formatter = null, $cacheDir = null, $debug = false)
85 {
86 $this->setLocale($locale);
87
88 if ($formatter instanceof MessageSelector) {
89 $formatter = new MessageFormatter($formatter);
90 @trigger_error(sprintf('Passing a "%s" instance into the "%s" as a second argument is deprecated since Symfony 3.4 and will be removed in 4.0. Inject a "%s" implementation instead.', MessageSelector::class, __METHOD__, MessageFormatterInterface::class), E_USER_DEPRECATED);
91 } elseif (null === $formatter) {
92 $formatter = new MessageFormatter();
93 }
94
95 $this->formatter = $formatter;
96 $this->cacheDir = $cacheDir;
97 $this->debug = $debug;
98 }
99
100 public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory)
101 {
102 $this->configCacheFactory = $configCacheFactory;
103 }
104
105 /**
106 * Adds a Loader.
107 *
108 * @param string $format The name of the loader (@see addResource())
109 * @param LoaderInterface $loader A LoaderInterface instance
110 */
111 public function addLoader($format, LoaderInterface $loader)
112 {
113 $this->loaders[$format] = $loader;
114 }
115
116 /**
117 * Adds a Resource.
118 *
119 * @param string $format The name of the loader (@see addLoader())
120 * @param mixed $resource The resource name
121 * @param string $locale The locale
122 * @param string $domain The domain
123 *
124 * @throws InvalidArgumentException If the locale contains invalid characters
125 */
126 public function addResource($format, $resource, $locale, $domain = null)
127 {
128 if (null === $domain) {
129 $domain = 'messages';
130 }
131
132 $this->assertValidLocale($locale);
133
134 $this->resources[$locale][] = array($format, $resource, $domain);
135
136 if (in_array($locale, $this->fallbackLocales)) {
137 $this->catalogues = array();
138 } else {
139 unset($this->catalogues[$locale]);
140 }
141 }
142
143 /**
144 * {@inheritdoc}
145 */
146 public function setLocale($locale)
147 {
148 $this->assertValidLocale($locale);
149 $this->locale = $locale;
150 }
151
152 /**
153 * {@inheritdoc}
154 */
155 public function getLocale()
156 {
157 return $this->locale;
158 }
159
160 /**
161 * Sets the fallback locales.
162 *
163 * @param array $locales The fallback locales
164 *
165 * @throws InvalidArgumentException If a locale contains invalid characters
166 */
167 public function setFallbackLocales(array $locales)
168 {
169 // needed as the fallback locales are linked to the already loaded catalogues
170 $this->catalogues = array();
171
172 foreach ($locales as $locale) {
173 $this->assertValidLocale($locale);
174 }
175
176 $this->fallbackLocales = $locales;
177 }
178
179 /**
180 * Gets the fallback locales.
181 *
182 * @return array $locales The fallback locales
183 */
184 public function getFallbackLocales()
185 {
186 return $this->fallbackLocales;
187 }
188
189 /**
190 * {@inheritdoc}
191 */
192 public function trans($id, array $parameters = array(), $domain = null, $locale = null)
193 {
194 if (null === $domain) {
195 $domain = 'messages';
196 }
197
198 return $this->formatter->format($this->getCatalogue($locale)->get((string) $id, $domain), $locale, $parameters);
199 }
200
201 /**
202 * {@inheritdoc}
203 */
204 public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null)
205 {
206 if (!$this->formatter instanceof ChoiceMessageFormatterInterface) {
207 throw new LogicException(sprintf('The formatter "%s" does not support plural translations.', get_class($this->formatter)));
208 }
209
210 if (null === $domain) {
211 $domain = 'messages';
212 }
213
214 $id = (string) $id;
215 $catalogue = $this->getCatalogue($locale);
216 $locale = $catalogue->getLocale();
217 while (!$catalogue->defines($id, $domain)) {
218 if ($cat = $catalogue->getFallbackCatalogue()) {
219 $catalogue = $cat;
220 $locale = $catalogue->getLocale();
221 } else {
222 break;
223 }
224 }
225
226 return $this->formatter->choiceFormat($catalogue->get($id, $domain), $number, $locale, $parameters);
227 }
228
229 /**
230 * {@inheritdoc}
231 */
232 public function getCatalogue($locale = null)
233 {
234 if (null === $locale) {
235 $locale = $this->getLocale();
236 } else {
237 $this->assertValidLocale($locale);
238 }
239
240 if (!isset($this->catalogues[$locale])) {
241 $this->loadCatalogue($locale);
242 }
243
244 return $this->catalogues[$locale];
245 }
246
247 /**
248 * Gets the loaders.
249 *
250 * @return array LoaderInterface[]
251 */
252 protected function getLoaders()
253 {
254 return $this->loaders;
255 }
256
257 /**
258 * @param string $locale
259 */
260 protected function loadCatalogue($locale)
261 {
262 if (null === $this->cacheDir) {
263 $this->initializeCatalogue($locale);
264 } else {
265 $this->initializeCacheCatalogue($locale);
266 }
267 }
268
269 /**
270 * @param string $locale
271 */
272 protected function initializeCatalogue($locale)
273 {
274 $this->assertValidLocale($locale);
275
276 try {
277 $this->doLoadCatalogue($locale);
278 } catch (NotFoundResourceException $e) {
279 if (!$this->computeFallbackLocales($locale)) {
280 throw $e;
281 }
282 }
283 $this->loadFallbackCatalogues($locale);
284 }
285
286 /**
287 * @param string $locale
288 */
289 private function initializeCacheCatalogue($locale)
290 {
291 if (isset($this->catalogues[$locale])) {
292 /* Catalogue already initialized. */
293 return;
294 }
295
296 $this->assertValidLocale($locale);
297 $cache = $this->getConfigCacheFactory()->cache($this->getCatalogueCachePath($locale),
298 function (ConfigCacheInterface $cache) use ($locale) {
299 $this->dumpCatalogue($locale, $cache);
300 }
301 );
302
303 if (isset($this->catalogues[$locale])) {
304 /* Catalogue has been initialized as it was written out to cache. */
305 return;
306 }
307
308 /* Read catalogue from cache. */
309 $this->catalogues[$locale] = include $cache->getPath();
310 }
311
312 private function dumpCatalogue($locale, ConfigCacheInterface $cache)
313 {
314 $this->initializeCatalogue($locale);
315 $fallbackContent = $this->getFallbackContent($this->catalogues[$locale]);
316
317 $content = sprintf(<<<EOF
318 <?php
319
320 use Symfony\Component\Translation\MessageCatalogue;
321
322 \$catalogue = new MessageCatalogue('%s', %s);
323
324 %s
325 return \$catalogue;
326
327 EOF
328 ,
329 $locale,
330 var_export($this->catalogues[$locale]->all(), true),
331 $fallbackContent
332 );
333
334 $cache->write($content, $this->catalogues[$locale]->getResources());
335 }
336
337 private function getFallbackContent(MessageCatalogue $catalogue)
338 {
339 $fallbackContent = '';
340 $current = '';
341 $replacementPattern = '/[^a-z0-9_]/i';
342 $fallbackCatalogue = $catalogue->getFallbackCatalogue();
343 while ($fallbackCatalogue) {
344 $fallback = $fallbackCatalogue->getLocale();
345 $fallbackSuffix = ucfirst(preg_replace($replacementPattern, '_', $fallback));
346 $currentSuffix = ucfirst(preg_replace($replacementPattern, '_', $current));
347
348 $fallbackContent .= sprintf(<<<'EOF'
349 $catalogue%s = new MessageCatalogue('%s', %s);
350 $catalogue%s->addFallbackCatalogue($catalogue%s);
351
352 EOF
353 ,
354 $fallbackSuffix,
355 $fallback,
356 var_export($fallbackCatalogue->all(), true),
357 $currentSuffix,
358 $fallbackSuffix
359 );
360 $current = $fallbackCatalogue->getLocale();
361 $fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue();
362 }
363
364 return $fallbackContent;
365 }
366
367 private function getCatalogueCachePath($locale)
368 {
369 return $this->cacheDir.'/catalogue.'.$locale.'.'.strtr(substr(base64_encode(hash('sha256', serialize($this->fallbackLocales), true)), 0, 7), '/', '_').'.php';
370 }
371
372 private function doLoadCatalogue($locale)
373 {
374 $this->catalogues[$locale] = new MessageCatalogue($locale);
375
376 if (isset($this->resources[$locale])) {
377 foreach ($this->resources[$locale] as $resource) {
378 if (!isset($this->loaders[$resource[0]])) {
379 throw new RuntimeException(sprintf('The "%s" translation loader is not registered.', $resource[0]));
380 }
381 $this->catalogues[$locale]->addCatalogue($this->loaders[$resource[0]]->load($resource[1], $locale, $resource[2]));
382 }
383 }
384 }
385
386 private function loadFallbackCatalogues($locale)
387 {
388 $current = $this->catalogues[$locale];
389
390 foreach ($this->computeFallbackLocales($locale) as $fallback) {
391 if (!isset($this->catalogues[$fallback])) {
392 $this->initializeCatalogue($fallback);
393 }
394
395 $fallbackCatalogue = new MessageCatalogue($fallback, $this->catalogues[$fallback]->all());
396 foreach ($this->catalogues[$fallback]->getResources() as $resource) {
397 $fallbackCatalogue->addResource($resource);
398 }
399 $current->addFallbackCatalogue($fallbackCatalogue);
400 $current = $fallbackCatalogue;
401 }
402 }
403
404 protected function computeFallbackLocales($locale)
405 {
406 $locales = array();
407 foreach ($this->fallbackLocales as $fallback) {
408 if ($fallback === $locale) {
409 continue;
410 }
411
412 $locales[] = $fallback;
413 }
414
415 if (false !== strrchr($locale, '_')) {
416 array_unshift($locales, substr($locale, 0, -strlen(strrchr($locale, '_'))));
417 }
418
419 return array_unique($locales);
420 }
421
422 /**
423 * Asserts that the locale is valid, throws an Exception if not.
424 *
425 * @param string $locale Locale to tests
426 *
427 * @throws InvalidArgumentException If the locale contains invalid characters
428 */
429 protected function assertValidLocale($locale)
430 {
431 if (1 !== preg_match('/^[a-z0-9@_\\.\\-]*$/i', $locale)) {
432 throw new InvalidArgumentException(sprintf('Invalid "%s" locale.', $locale));
433 }
434 }
435
436 /**
437 * Provides the ConfigCache factory implementation, falling back to a
438 * default implementation if necessary.
439 *
440 * @return ConfigCacheFactoryInterface $configCacheFactory
441 */
442 private function getConfigCacheFactory()
443 {
444 if (!$this->configCacheFactory) {
445 $this->configCacheFactory = new ConfigCacheFactory($this->debug);
446 }
447
448 return $this->configCacheFactory;
449 }
450 }