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 12namespace Symfony\Component\Translation; 13 14use Symfony\Component\Config\Resource\ResourceInterface; 15use Symfony\Component\Translation\Exception\LogicException; 16 17/** 18 * @author Fabien Potencier <fabien@symfony.com> 19 */ 20class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterface 21{ 22 private $messages = []; 23 private $metadata = []; 24 private $resources = []; 25 private $locale; 26 private $fallbackCatalogue; 27 private $parent; 28 29 /** 30 * @param string $locale The locale 31 * @param array $messages An array of messages classified by domain 32 */ 33 public function __construct(?string $locale, array $messages = []) 34 { 35 if (null === $locale) { 36 @trigger_error(sprintf('Passing "null" to the first argument of the "%s" method has been deprecated since Symfony 4.4 and will throw an error in 5.0.', __METHOD__), \E_USER_DEPRECATED); 37 } 38 39 $this->locale = $locale; 40 $this->messages = $messages; 41 } 42 43 /** 44 * {@inheritdoc} 45 */ 46 public function getLocale() 47 { 48 return $this->locale; 49 } 50 51 /** 52 * {@inheritdoc} 53 */ 54 public function getDomains() 55 { 56 $domains = []; 57 $suffixLength = \strlen(self::INTL_DOMAIN_SUFFIX); 58 59 foreach ($this->messages as $domain => $messages) { 60 if (\strlen($domain) > $suffixLength && false !== $i = strpos($domain, self::INTL_DOMAIN_SUFFIX, -$suffixLength)) { 61 $domain = substr($domain, 0, $i); 62 } 63 $domains[$domain] = $domain; 64 } 65 66 return array_values($domains); 67 } 68 69 /** 70 * {@inheritdoc} 71 */ 72 public function all($domain = null) 73 { 74 if (null !== $domain) { 75 // skip messages merge if intl-icu requested explicitly 76 if (false !== strpos($domain, self::INTL_DOMAIN_SUFFIX)) { 77 return $this->messages[$domain] ?? []; 78 } 79 80 return ($this->messages[$domain.self::INTL_DOMAIN_SUFFIX] ?? []) + ($this->messages[$domain] ?? []); 81 } 82 83 $allMessages = []; 84 $suffixLength = \strlen(self::INTL_DOMAIN_SUFFIX); 85 86 foreach ($this->messages as $domain => $messages) { 87 if (\strlen($domain) > $suffixLength && false !== $i = strpos($domain, self::INTL_DOMAIN_SUFFIX, -$suffixLength)) { 88 $domain = substr($domain, 0, $i); 89 $allMessages[$domain] = $messages + ($allMessages[$domain] ?? []); 90 } else { 91 $allMessages[$domain] = ($allMessages[$domain] ?? []) + $messages; 92 } 93 } 94 95 return $allMessages; 96 } 97 98 /** 99 * {@inheritdoc} 100 */ 101 public function set($id, $translation, $domain = 'messages') 102 { 103 $this->add([$id => $translation], $domain); 104 } 105 106 /** 107 * {@inheritdoc} 108 */ 109 public function has($id, $domain = 'messages') 110 { 111 if (isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id])) { 112 return true; 113 } 114 115 if (null !== $this->fallbackCatalogue) { 116 return $this->fallbackCatalogue->has($id, $domain); 117 } 118 119 return false; 120 } 121 122 /** 123 * {@inheritdoc} 124 */ 125 public function defines($id, $domain = 'messages') 126 { 127 return isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id]); 128 } 129 130 /** 131 * {@inheritdoc} 132 */ 133 public function get($id, $domain = 'messages') 134 { 135 if (isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id])) { 136 return $this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id]; 137 } 138 139 if (isset($this->messages[$domain][$id])) { 140 return $this->messages[$domain][$id]; 141 } 142 143 if (null !== $this->fallbackCatalogue) { 144 return $this->fallbackCatalogue->get($id, $domain); 145 } 146 147 return $id; 148 } 149 150 /** 151 * {@inheritdoc} 152 */ 153 public function replace($messages, $domain = 'messages') 154 { 155 unset($this->messages[$domain], $this->messages[$domain.self::INTL_DOMAIN_SUFFIX]); 156 157 $this->add($messages, $domain); 158 } 159 160 /** 161 * {@inheritdoc} 162 */ 163 public function add($messages, $domain = 'messages') 164 { 165 if (!isset($this->messages[$domain])) { 166 $this->messages[$domain] = []; 167 } 168 $intlDomain = $domain; 169 $suffixLength = \strlen(self::INTL_DOMAIN_SUFFIX); 170 if (\strlen($domain) < $suffixLength || false === strpos($domain, self::INTL_DOMAIN_SUFFIX, -$suffixLength)) { 171 $intlDomain .= self::INTL_DOMAIN_SUFFIX; 172 } 173 foreach ($messages as $id => $message) { 174 if (isset($this->messages[$intlDomain]) && \array_key_exists($id, $this->messages[$intlDomain])) { 175 $this->messages[$intlDomain][$id] = $message; 176 } else { 177 $this->messages[$domain][$id] = $message; 178 } 179 } 180 } 181 182 /** 183 * {@inheritdoc} 184 */ 185 public function addCatalogue(MessageCatalogueInterface $catalogue) 186 { 187 if ($catalogue->getLocale() !== $this->locale) { 188 throw new LogicException(sprintf('Cannot add a catalogue for locale "%s" as the current locale for this catalogue is "%s".', $catalogue->getLocale(), $this->locale)); 189 } 190 191 foreach ($catalogue->all() as $domain => $messages) { 192 if ($intlMessages = $catalogue->all($domain.self::INTL_DOMAIN_SUFFIX)) { 193 $this->add($intlMessages, $domain.self::INTL_DOMAIN_SUFFIX); 194 $messages = array_diff_key($messages, $intlMessages); 195 } 196 $this->add($messages, $domain); 197 } 198 199 foreach ($catalogue->getResources() as $resource) { 200 $this->addResource($resource); 201 } 202 203 if ($catalogue instanceof MetadataAwareInterface) { 204 $metadata = $catalogue->getMetadata('', ''); 205 $this->addMetadata($metadata); 206 } 207 } 208 209 /** 210 * {@inheritdoc} 211 */ 212 public function addFallbackCatalogue(MessageCatalogueInterface $catalogue) 213 { 214 // detect circular references 215 $c = $catalogue; 216 while ($c = $c->getFallbackCatalogue()) { 217 if ($c->getLocale() === $this->getLocale()) { 218 throw new LogicException(sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale())); 219 } 220 } 221 222 $c = $this; 223 do { 224 if ($c->getLocale() === $catalogue->getLocale()) { 225 throw new LogicException(sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale())); 226 } 227 228 foreach ($catalogue->getResources() as $resource) { 229 $c->addResource($resource); 230 } 231 } while ($c = $c->parent); 232 233 $catalogue->parent = $this; 234 $this->fallbackCatalogue = $catalogue; 235 236 foreach ($catalogue->getResources() as $resource) { 237 $this->addResource($resource); 238 } 239 } 240 241 /** 242 * {@inheritdoc} 243 */ 244 public function getFallbackCatalogue() 245 { 246 return $this->fallbackCatalogue; 247 } 248 249 /** 250 * {@inheritdoc} 251 */ 252 public function getResources() 253 { 254 return array_values($this->resources); 255 } 256 257 /** 258 * {@inheritdoc} 259 */ 260 public function addResource(ResourceInterface $resource) 261 { 262 $this->resources[$resource->__toString()] = $resource; 263 } 264 265 /** 266 * {@inheritdoc} 267 */ 268 public function getMetadata($key = '', $domain = 'messages') 269 { 270 if ('' == $domain) { 271 return $this->metadata; 272 } 273 274 if (isset($this->metadata[$domain])) { 275 if ('' == $key) { 276 return $this->metadata[$domain]; 277 } 278 279 if (isset($this->metadata[$domain][$key])) { 280 return $this->metadata[$domain][$key]; 281 } 282 } 283 284 return null; 285 } 286 287 /** 288 * {@inheritdoc} 289 */ 290 public function setMetadata($key, $value, $domain = 'messages') 291 { 292 $this->metadata[$domain][$key] = $value; 293 } 294 295 /** 296 * {@inheritdoc} 297 */ 298 public function deleteMetadata($key = '', $domain = 'messages') 299 { 300 if ('' == $domain) { 301 $this->metadata = []; 302 } elseif ('' == $key) { 303 unset($this->metadata[$domain]); 304 } else { 305 unset($this->metadata[$domain][$key]); 306 } 307 } 308 309 /** 310 * Adds current values with the new values. 311 * 312 * @param array $values Values to add 313 */ 314 private function addMetadata(array $values) 315 { 316 foreach ($values as $domain => $keys) { 317 foreach ($keys as $key => $value) { 318 $this->setMetadata($key, $value, $domain); 319 } 320 } 321 } 322} 323