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