This commit is contained in:
emmymayo
2025-02-05 23:15:46 +01:00
commit 7269c99357
16995 changed files with 3389680 additions and 0 deletions
@@ -0,0 +1,91 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
use MailPoetVendor\Symfony\Component\Translation\Exception\InvalidArgumentException;
use MailPoetVendor\Symfony\Contracts\Translation\LocaleAwareInterface;
use MailPoetVendor\Symfony\Contracts\Translation\TranslatorInterface;
class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface, WarmableInterface
{
public const MESSAGE_DEFINED = 0;
public const MESSAGE_MISSING = 1;
public const MESSAGE_EQUALS_FALLBACK = 2;
private $translator;
private $messages = [];
public function __construct(TranslatorInterface $translator)
{
if (!$translator instanceof TranslatorBagInterface || !$translator instanceof LocaleAwareInterface) {
throw new InvalidArgumentException(\sprintf('The Translator "%s" must implement TranslatorInterface, TranslatorBagInterface and LocaleAwareInterface.', \get_debug_type($translator)));
}
$this->translator = $translator;
}
public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null)
{
$trans = $this->translator->trans($id = (string) $id, $parameters, $domain, $locale);
$this->collectMessage($locale, $domain, $id, $trans, $parameters);
return $trans;
}
public function setLocale(string $locale)
{
$this->translator->setLocale($locale);
}
public function getLocale()
{
return $this->translator->getLocale();
}
public function getCatalogue(?string $locale = null)
{
return $this->translator->getCatalogue($locale);
}
public function getCatalogues() : array
{
return $this->translator->getCatalogues();
}
public function warmUp(string $cacheDir)
{
if ($this->translator instanceof WarmableInterface) {
return (array) $this->translator->warmUp($cacheDir);
}
return [];
}
public function getFallbackLocales()
{
if ($this->translator instanceof Translator || \method_exists($this->translator, 'getFallbackLocales')) {
return $this->translator->getFallbackLocales();
}
return [];
}
public function __call(string $method, array $args)
{
return $this->translator->{$method}(...$args);
}
public function getCollectedMessages()
{
return $this->messages;
}
private function collectMessage(?string $locale, ?string $domain, string $id, string $translation, ?array $parameters = [])
{
if (null === $domain) {
$domain = 'messages';
}
$catalogue = $this->translator->getCatalogue($locale);
$locale = $catalogue->getLocale();
$fallbackLocale = null;
if ($catalogue->defines($id, $domain)) {
$state = self::MESSAGE_DEFINED;
} elseif ($catalogue->has($id, $domain)) {
$state = self::MESSAGE_EQUALS_FALLBACK;
$fallbackCatalogue = $catalogue->getFallbackCatalogue();
while ($fallbackCatalogue) {
if ($fallbackCatalogue->defines($id, $domain)) {
$fallbackLocale = $fallbackCatalogue->getLocale();
break;
}
$fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue();
}
} else {
$state = self::MESSAGE_MISSING;
}
$this->messages[] = ['locale' => $locale, 'fallbackLocale' => $fallbackLocale, 'domain' => $domain, 'id' => $id, 'translation' => $translation, 'parameters' => $parameters, 'state' => $state, 'transChoiceNumber' => isset($parameters['%count%']) && \is_numeric($parameters['%count%']) ? $parameters['%count%'] : null];
}
}
@@ -0,0 +1,6 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation\Exception;
if (!defined('ABSPATH')) exit;
interface ExceptionInterface extends \Throwable
{
}
@@ -0,0 +1,13 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation\Exception;
if (!defined('ABSPATH')) exit;
class IncompleteDsnException extends InvalidArgumentException
{
public function __construct(string $message, ?string $dsn = null, ?\Throwable $previous = null)
{
if ($dsn) {
$message = \sprintf('Invalid "%s" provider DSN: ', $dsn) . $message;
}
parent::__construct($message, 0, $previous);
}
}
@@ -0,0 +1,6 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation\Exception;
if (!defined('ABSPATH')) exit;
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
@@ -0,0 +1,6 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation\Exception;
if (!defined('ABSPATH')) exit;
class InvalidResourceException extends \InvalidArgumentException implements ExceptionInterface
{
}
@@ -0,0 +1,6 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation\Exception;
if (!defined('ABSPATH')) exit;
class LogicException extends \LogicException implements ExceptionInterface
{
}
@@ -0,0 +1,11 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation\Exception;
if (!defined('ABSPATH')) exit;
class MissingRequiredOptionException extends IncompleteDsnException
{
public function __construct(string $option, ?string $dsn = null, ?\Throwable $previous = null)
{
$message = \sprintf('The option "%s" is required but missing.', $option);
parent::__construct($message, $dsn, $previous);
}
}
@@ -0,0 +1,6 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation\Exception;
if (!defined('ABSPATH')) exit;
class NotFoundResourceException extends \InvalidArgumentException implements ExceptionInterface
{
}
@@ -0,0 +1,23 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation\Exception;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Symfony\Contracts\HttpClient\ResponseInterface;
class ProviderException extends RuntimeException implements ProviderExceptionInterface
{
private $response;
private $debug;
public function __construct(string $message, ResponseInterface $response, int $code = 0, ?\Exception $previous = null)
{
$this->response = $response;
$this->debug = $response->getInfo('debug') ?? '';
parent::__construct($message, $code, $previous);
}
public function getResponse() : ResponseInterface
{
return $this->response;
}
public function getDebug() : string
{
return $this->debug;
}
}
@@ -0,0 +1,7 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation\Exception;
if (!defined('ABSPATH')) exit;
interface ProviderExceptionInterface extends ExceptionInterface
{
public function getDebug() : string;
}
@@ -0,0 +1,6 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation\Exception;
if (!defined('ABSPATH')) exit;
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
@@ -0,0 +1,26 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation\Exception;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Symfony\Component\Translation\Bridge;
use MailPoetVendor\Symfony\Component\Translation\Provider\Dsn;
class UnsupportedSchemeException extends LogicException
{
private const SCHEME_TO_PACKAGE_MAP = ['crowdin' => ['class' => Bridge\Crowdin\CrowdinProviderFactory::class, 'package' => 'symfony/crowdin-translation-provider'], 'loco' => ['class' => Bridge\Loco\LocoProviderFactory::class, 'package' => 'symfony/loco-translation-provider'], 'lokalise' => ['class' => Bridge\Lokalise\LokaliseProviderFactory::class, 'package' => 'symfony/lokalise-translation-provider']];
public function __construct(Dsn $dsn, ?string $name = null, array $supported = [])
{
$provider = $dsn->getScheme();
if (\false !== ($pos = \strpos($provider, '+'))) {
$provider = \substr($provider, 0, $pos);
}
$package = self::SCHEME_TO_PACKAGE_MAP[$provider] ?? null;
if ($package && !\class_exists($package['class'])) {
parent::__construct(\sprintf('Unable to synchronize translations via "%s" as the provider is not installed; try running "composer require %s".', $provider, $package['package']));
return;
}
$message = \sprintf('The "%s" scheme is not supported', $dsn->getScheme());
if ($name && $supported) {
$message .= \sprintf('; supported schemes for translation provider "%s" are: "%s"', $name, \implode('", "', $supported));
}
parent::__construct($message . '.');
}
}
@@ -0,0 +1,10 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Symfony\Contracts\Translation\LocaleAwareInterface;
use MailPoetVendor\Symfony\Contracts\Translation\TranslatorInterface;
use MailPoetVendor\Symfony\Contracts\Translation\TranslatorTrait;
class IdentityTranslator implements TranslatorInterface, LocaleAwareInterface
{
use TranslatorTrait;
}
@@ -0,0 +1,73 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Psr\Log\LoggerInterface;
use MailPoetVendor\Symfony\Component\Translation\Exception\InvalidArgumentException;
use MailPoetVendor\Symfony\Contracts\Translation\LocaleAwareInterface;
use MailPoetVendor\Symfony\Contracts\Translation\TranslatorInterface;
class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface
{
private $translator;
private $logger;
public function __construct(TranslatorInterface $translator, LoggerInterface $logger)
{
if (!$translator instanceof TranslatorBagInterface || !$translator instanceof LocaleAwareInterface) {
throw new InvalidArgumentException(\sprintf('The Translator "%s" must implement TranslatorInterface, TranslatorBagInterface and LocaleAwareInterface.', \get_debug_type($translator)));
}
$this->translator = $translator;
$this->logger = $logger;
}
public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null)
{
$trans = $this->translator->trans($id = (string) $id, $parameters, $domain, $locale);
$this->log($id, $domain, $locale);
return $trans;
}
public function setLocale(string $locale)
{
$prev = $this->translator->getLocale();
$this->translator->setLocale($locale);
if ($prev === $locale) {
return;
}
$this->logger->debug(\sprintf('The locale of the translator has changed from "%s" to "%s".', $prev, $locale));
}
public function getLocale()
{
return $this->translator->getLocale();
}
public function getCatalogue(?string $locale = null)
{
return $this->translator->getCatalogue($locale);
}
public function getCatalogues() : array
{
return $this->translator->getCatalogues();
}
public function getFallbackLocales()
{
if ($this->translator instanceof Translator || \method_exists($this->translator, 'getFallbackLocales')) {
return $this->translator->getFallbackLocales();
}
return [];
}
public function __call(string $method, array $args)
{
return $this->translator->{$method}(...$args);
}
private function log(string $id, ?string $domain, ?string $locale)
{
if (null === $domain) {
$domain = 'messages';
}
$catalogue = $this->translator->getCatalogue($locale);
if ($catalogue->defines($id, $domain)) {
return;
}
if ($catalogue->has($id, $domain)) {
$this->logger->debug('Translation use fallback catalogue.', ['id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()]);
} else {
$this->logger->warning('Translation not found.', ['id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()]);
}
}
}
@@ -0,0 +1,194 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Symfony\Component\Config\Resource\ResourceInterface;
use MailPoetVendor\Symfony\Component\Translation\Exception\LogicException;
class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterface
{
private $messages = [];
private $metadata = [];
private $resources = [];
private $locale;
private $fallbackCatalogue;
private $parent;
public function __construct(string $locale, array $messages = [])
{
$this->locale = $locale;
$this->messages = $messages;
}
public function getLocale()
{
return $this->locale;
}
public function getDomains()
{
$domains = [];
foreach ($this->messages as $domain => $messages) {
if (\str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) {
$domain = \substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX));
}
$domains[$domain] = $domain;
}
return \array_values($domains);
}
public function all(?string $domain = null)
{
if (null !== $domain) {
// skip messages merge if intl-icu requested explicitly
if (\str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) {
return $this->messages[$domain] ?? [];
}
return ($this->messages[$domain . self::INTL_DOMAIN_SUFFIX] ?? []) + ($this->messages[$domain] ?? []);
}
$allMessages = [];
foreach ($this->messages as $domain => $messages) {
if (\str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) {
$domain = \substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX));
$allMessages[$domain] = $messages + ($allMessages[$domain] ?? []);
} else {
$allMessages[$domain] = ($allMessages[$domain] ?? []) + $messages;
}
}
return $allMessages;
}
public function set(string $id, string $translation, string $domain = 'messages')
{
$this->add([$id => $translation], $domain);
}
public function has(string $id, string $domain = 'messages')
{
if (isset($this->messages[$domain][$id]) || isset($this->messages[$domain . self::INTL_DOMAIN_SUFFIX][$id])) {
return \true;
}
if (null !== $this->fallbackCatalogue) {
return $this->fallbackCatalogue->has($id, $domain);
}
return \false;
}
public function defines(string $id, string $domain = 'messages')
{
return isset($this->messages[$domain][$id]) || isset($this->messages[$domain . self::INTL_DOMAIN_SUFFIX][$id]);
}
public function get(string $id, string $domain = 'messages')
{
if (isset($this->messages[$domain . self::INTL_DOMAIN_SUFFIX][$id])) {
return $this->messages[$domain . self::INTL_DOMAIN_SUFFIX][$id];
}
if (isset($this->messages[$domain][$id])) {
return $this->messages[$domain][$id];
}
if (null !== $this->fallbackCatalogue) {
return $this->fallbackCatalogue->get($id, $domain);
}
return $id;
}
public function replace(array $messages, string $domain = 'messages')
{
unset($this->messages[$domain], $this->messages[$domain . self::INTL_DOMAIN_SUFFIX]);
$this->add($messages, $domain);
}
public function add(array $messages, string $domain = 'messages')
{
$altDomain = \str_ends_with($domain, self::INTL_DOMAIN_SUFFIX) ? \substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX)) : $domain . self::INTL_DOMAIN_SUFFIX;
foreach ($messages as $id => $message) {
unset($this->messages[$altDomain][$id]);
$this->messages[$domain][$id] = $message;
}
if ([] === ($this->messages[$altDomain] ?? null)) {
unset($this->messages[$altDomain]);
}
}
public function addCatalogue(MessageCatalogueInterface $catalogue)
{
if ($catalogue->getLocale() !== $this->locale) {
throw new LogicException(\sprintf('Cannot add a catalogue for locale "%s" as the current locale for this catalogue is "%s".', $catalogue->getLocale(), $this->locale));
}
foreach ($catalogue->all() as $domain => $messages) {
if ($intlMessages = $catalogue->all($domain . self::INTL_DOMAIN_SUFFIX)) {
$this->add($intlMessages, $domain . self::INTL_DOMAIN_SUFFIX);
$messages = \array_diff_key($messages, $intlMessages);
}
$this->add($messages, $domain);
}
foreach ($catalogue->getResources() as $resource) {
$this->addResource($resource);
}
if ($catalogue instanceof MetadataAwareInterface) {
$metadata = $catalogue->getMetadata('', '');
$this->addMetadata($metadata);
}
}
public function addFallbackCatalogue(MessageCatalogueInterface $catalogue)
{
// detect circular references
$c = $catalogue;
while ($c = $c->getFallbackCatalogue()) {
if ($c->getLocale() === $this->getLocale()) {
throw new LogicException(\sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale()));
}
}
$c = $this;
do {
if ($c->getLocale() === $catalogue->getLocale()) {
throw new LogicException(\sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale()));
}
foreach ($catalogue->getResources() as $resource) {
$c->addResource($resource);
}
} while ($c = $c->parent);
$catalogue->parent = $this;
$this->fallbackCatalogue = $catalogue;
foreach ($catalogue->getResources() as $resource) {
$this->addResource($resource);
}
}
public function getFallbackCatalogue()
{
return $this->fallbackCatalogue;
}
public function getResources()
{
return \array_values($this->resources);
}
public function addResource(ResourceInterface $resource)
{
$this->resources[$resource->__toString()] = $resource;
}
public function getMetadata(string $key = '', string $domain = 'messages')
{
if ('' == $domain) {
return $this->metadata;
}
if (isset($this->metadata[$domain])) {
if ('' == $key) {
return $this->metadata[$domain];
}
if (isset($this->metadata[$domain][$key])) {
return $this->metadata[$domain][$key];
}
}
return null;
}
public function setMetadata(string $key, $value, string $domain = 'messages')
{
$this->metadata[$domain][$key] = $value;
}
public function deleteMetadata(string $key = '', string $domain = 'messages')
{
if ('' == $domain) {
$this->metadata = [];
} elseif ('' == $key) {
unset($this->metadata[$domain]);
} else {
unset($this->metadata[$domain][$key]);
}
}
private function addMetadata(array $values)
{
foreach ($values as $domain => $keys) {
foreach ($keys as $key => $value) {
$this->setMetadata($key, $value, $domain);
}
}
}
}
@@ -0,0 +1,22 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Symfony\Component\Config\Resource\ResourceInterface;
interface MessageCatalogueInterface
{
public const INTL_DOMAIN_SUFFIX = '+intl-icu';
public function getLocale();
public function getDomains();
public function all(?string $domain = null);
public function set(string $id, string $translation, string $domain = 'messages');
public function has(string $id, string $domain = 'messages');
public function defines(string $id, string $domain = 'messages');
public function get(string $id, string $domain = 'messages');
public function replace(array $messages, string $domain = 'messages');
public function add(array $messages, string $domain = 'messages');
public function addCatalogue(self $catalogue);
public function addFallbackCatalogue(self $catalogue);
public function getFallbackCatalogue();
public function getResources();
public function addResource(ResourceInterface $resource);
}
@@ -0,0 +1,9 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation;
if (!defined('ABSPATH')) exit;
interface MetadataAwareInterface
{
public function getMetadata(string $key = '', string $domain = 'messages');
public function setMetadata(string $key, $value, string $domain = 'messages');
public function deleteMetadata(string $key = '', string $domain = 'messages');
}
@@ -0,0 +1,26 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation\Provider;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Symfony\Component\Translation\Exception\IncompleteDsnException;
abstract class AbstractProviderFactory implements ProviderFactoryInterface
{
public function supports(Dsn $dsn) : bool
{
return \in_array($dsn->getScheme(), $this->getSupportedSchemes(), \true);
}
protected abstract function getSupportedSchemes() : array;
protected function getUser(Dsn $dsn) : string
{
if (null === ($user = $dsn->getUser())) {
throw new IncompleteDsnException('User is not set.', $dsn->getScheme() . '://' . $dsn->getHost());
}
return $user;
}
protected function getPassword(Dsn $dsn) : string
{
if (null === ($password = $dsn->getPassword())) {
throw new IncompleteDsnException('Password is not set.', $dsn->getOriginalDsn());
}
return $password;
}
}
@@ -0,0 +1,79 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation\Provider;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Symfony\Component\Translation\Exception\InvalidArgumentException;
use MailPoetVendor\Symfony\Component\Translation\Exception\MissingRequiredOptionException;
final class Dsn
{
private $scheme;
private $host;
private $user;
private $password;
private $port;
private $path;
private $options;
private $originalDsn;
public function __construct(string $dsn)
{
$this->originalDsn = $dsn;
if (\false === ($params = \parse_url($dsn))) {
throw new InvalidArgumentException('The translation provider DSN is invalid.');
}
if (!isset($params['scheme'])) {
throw new InvalidArgumentException('The translation provider DSN must contain a scheme.');
}
$this->scheme = $params['scheme'];
if (!isset($params['host'])) {
throw new InvalidArgumentException('The translation provider DSN must contain a host (use "default" by default).');
}
$this->host = $params['host'];
$this->user = '' !== ($params['user'] ?? '') ? \rawurldecode($params['user']) : null;
$this->password = '' !== ($params['pass'] ?? '') ? \rawurldecode($params['pass']) : null;
$this->port = $params['port'] ?? null;
$this->path = $params['path'] ?? null;
\parse_str($params['query'] ?? '', $this->options);
}
public function getScheme() : string
{
return $this->scheme;
}
public function getHost() : string
{
return $this->host;
}
public function getUser() : ?string
{
return $this->user;
}
public function getPassword() : ?string
{
return $this->password;
}
public function getPort(?int $default = null) : ?int
{
return $this->port ?? $default;
}
public function getOption(string $key, $default = null)
{
return $this->options[$key] ?? $default;
}
public function getRequiredOption(string $key)
{
if (!\array_key_exists($key, $this->options) || '' === \trim($this->options[$key])) {
throw new MissingRequiredOptionException($key);
}
return $this->options[$key];
}
public function getOptions() : array
{
return $this->options;
}
public function getPath() : ?string
{
return $this->path;
}
public function getOriginalDsn() : string
{
return $this->originalDsn;
}
}
@@ -0,0 +1,39 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation\Provider;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Symfony\Component\Translation\TranslatorBag;
use MailPoetVendor\Symfony\Component\Translation\TranslatorBagInterface;
class FilteringProvider implements ProviderInterface
{
private $provider;
private $locales;
private $domains;
public function __construct(ProviderInterface $provider, array $locales, array $domains = [])
{
$this->provider = $provider;
$this->locales = $locales;
$this->domains = $domains;
}
public function __toString() : string
{
return (string) $this->provider;
}
public function write(TranslatorBagInterface $translatorBag) : void
{
$this->provider->write($translatorBag);
}
public function read(array $domains, array $locales) : TranslatorBag
{
$domains = !$this->domains ? $domains : \array_intersect($this->domains, $domains);
$locales = \array_intersect($this->locales, $locales);
return $this->provider->read($domains, $locales);
}
public function delete(TranslatorBagInterface $translatorBag) : void
{
$this->provider->delete($translatorBag);
}
public function getDomains() : array
{
return $this->domains;
}
}
@@ -0,0 +1,22 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation\Provider;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Symfony\Component\Translation\TranslatorBag;
use MailPoetVendor\Symfony\Component\Translation\TranslatorBagInterface;
class NullProvider implements ProviderInterface
{
public function __toString() : string
{
return 'null';
}
public function write(TranslatorBagInterface $translatorBag, bool $override = \false) : void
{
}
public function read(array $domains, array $locales) : TranslatorBag
{
return new TranslatorBag();
}
public function delete(TranslatorBagInterface $translatorBag) : void
{
}
}
@@ -0,0 +1,18 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation\Provider;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Symfony\Component\Translation\Exception\UnsupportedSchemeException;
final class NullProviderFactory extends AbstractProviderFactory
{
public function create(Dsn $dsn) : ProviderInterface
{
if ('null' === $dsn->getScheme()) {
return new NullProvider();
}
throw new UnsupportedSchemeException($dsn, 'null', $this->getSupportedSchemes());
}
protected function getSupportedSchemes() : array
{
return ['null'];
}
}
@@ -0,0 +1,10 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation\Provider;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Symfony\Component\Translation\Exception\IncompleteDsnException;
use MailPoetVendor\Symfony\Component\Translation\Exception\UnsupportedSchemeException;
interface ProviderFactoryInterface
{
public function create(Dsn $dsn) : ProviderInterface;
public function supports(Dsn $dsn) : bool;
}
@@ -0,0 +1,12 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation\Provider;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Symfony\Component\Translation\TranslatorBag;
use MailPoetVendor\Symfony\Component\Translation\TranslatorBagInterface;
interface ProviderInterface
{
public function __toString() : string;
public function write(TranslatorBagInterface $translatorBag) : void;
public function read(array $domains, array $locales) : TranslatorBag;
public function delete(TranslatorBagInterface $translatorBag) : void;
}
@@ -0,0 +1,31 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation\Provider;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Symfony\Component\Translation\Exception\InvalidArgumentException;
final class TranslationProviderCollection
{
private $providers;
public function __construct(iterable $providers)
{
$this->providers = \is_array($providers) ? $providers : \iterator_to_array($providers);
}
public function __toString() : string
{
return '[' . \implode(',', \array_keys($this->providers)) . ']';
}
public function has(string $name) : bool
{
return isset($this->providers[$name]);
}
public function get(string $name) : ProviderInterface
{
if (!$this->has($name)) {
throw new InvalidArgumentException(\sprintf('Provider "%s" not found. Available: "%s".', $name, (string) $this));
}
return $this->providers[$name];
}
public function keys() : array
{
return \array_keys($this->providers);
}
}
@@ -0,0 +1,31 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation\Provider;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Symfony\Component\Translation\Exception\UnsupportedSchemeException;
class TranslationProviderCollectionFactory
{
private $factories;
private $enabledLocales;
public function __construct(iterable $factories, array $enabledLocales)
{
$this->factories = $factories;
$this->enabledLocales = $enabledLocales;
}
public function fromConfig(array $config) : TranslationProviderCollection
{
$providers = [];
foreach ($config as $name => $currentConfig) {
$providers[$name] = $this->fromDsnObject(new Dsn($currentConfig['dsn']), !$currentConfig['locales'] ? $this->enabledLocales : $currentConfig['locales'], !$currentConfig['domains'] ? [] : $currentConfig['domains']);
}
return new TranslationProviderCollection($providers);
}
public function fromDsnObject(Dsn $dsn, array $locales, array $domains = []) : ProviderInterface
{
foreach ($this->factories as $factory) {
if ($factory->supports($dsn)) {
return new FilteringProvider($factory->create($dsn), $locales, $domains);
}
}
throw new UnsupportedSchemeException($dsn);
}
}
@@ -0,0 +1,160 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Symfony\Contracts\Translation\TranslatorInterface;
final class PseudoLocalizationTranslator implements TranslatorInterface
{
private const EXPANSION_CHARACTER = '~';
private $translator;
private $accents;
private $expansionFactor;
private $brackets;
private $parseHTML;
private $localizableHTMLAttributes;
public function __construct(TranslatorInterface $translator, array $options = [])
{
$this->translator = $translator;
$this->accents = $options['accents'] ?? \true;
if (1.0 > ($this->expansionFactor = $options['expansion_factor'] ?? 1.0)) {
throw new \InvalidArgumentException('The expansion factor must be greater than or equal to 1.');
}
$this->brackets = $options['brackets'] ?? \true;
$this->parseHTML = $options['parse_html'] ?? \false;
if ($this->parseHTML && !$this->accents && 1.0 === $this->expansionFactor) {
$this->parseHTML = \false;
}
$this->localizableHTMLAttributes = $options['localizable_html_attributes'] ?? [];
}
public function trans(string $id, array $parameters = [], ?string $domain = null, ?string $locale = null) : string
{
$trans = '';
$visibleText = '';
foreach ($this->getParts($this->translator->trans($id, $parameters, $domain, $locale)) as [$visible, $localizable, $text]) {
if ($visible) {
$visibleText .= $text;
}
if (!$localizable) {
$trans .= $text;
continue;
}
$this->addAccents($trans, $text);
}
$this->expand($trans, $visibleText);
$this->addBrackets($trans);
return $trans;
}
public function getLocale() : string
{
return $this->translator->getLocale();
}
private function getParts(string $originalTrans) : array
{
if (!$this->parseHTML) {
return [[\true, \true, $originalTrans]];
}
$html = \mb_encode_numericentity($originalTrans, [0x80, 0x10ffff, 0, 0x1fffff], \mb_detect_encoding($originalTrans, null, \true) ?: 'UTF-8');
$useInternalErrors = \libxml_use_internal_errors(\true);
$dom = new \DOMDocument();
$dom->loadHTML('<trans>' . $html . '</trans>');
\libxml_clear_errors();
\libxml_use_internal_errors($useInternalErrors);
return $this->parseNode($dom->childNodes->item(1)->childNodes->item(0)->childNodes->item(0));
}
private function parseNode(\DOMNode $node) : array
{
$parts = [];
foreach ($node->childNodes as $childNode) {
if (!$childNode instanceof \DOMElement) {
$parts[] = [\true, \true, $childNode->nodeValue];
continue;
}
$parts[] = [\false, \false, '<' . $childNode->tagName];
foreach ($childNode->attributes as $attribute) {
$parts[] = [\false, \false, ' ' . $attribute->nodeName . '="'];
$localizableAttribute = \in_array($attribute->nodeName, $this->localizableHTMLAttributes, \true);
foreach (\preg_split('/(&(?:amp|quot|#039|lt|gt);+)/', \htmlspecialchars($attribute->nodeValue, \ENT_QUOTES, 'UTF-8'), -1, \PREG_SPLIT_DELIM_CAPTURE) as $i => $match) {
if ('' === $match) {
continue;
}
$parts[] = [\false, $localizableAttribute && 0 === $i % 2, $match];
}
$parts[] = [\false, \false, '"'];
}
$parts[] = [\false, \false, '>'];
$parts = \array_merge($parts, $this->parseNode($childNode, $parts));
$parts[] = [\false, \false, '</' . $childNode->tagName . '>'];
}
return $parts;
}
private function addAccents(string &$trans, string $text) : void
{
$trans .= $this->accents ? \strtr($text, [' ' => '', '!' => '¡', '"' => '″', '#' => '♯', '$' => '€', '%' => '‰', '&' => '⅋', '\'' => '´', '(' => '{', ')' => '}', '*' => '', '+' => '⁺', ',' => '،', '-' => '', '.' => '·', '/' => '', '0' => '⓪', '1' => '①', '2' => '②', '3' => '③', '4' => '④', '5' => '⑤', '6' => '⑥', '7' => '⑦', '8' => '⑧', '9' => '⑨', ':' => '', ';' => '⁏', '<' => '≤', '=' => '≂', '>' => '≥', '?' => '¿', '@' => '՞', 'A' => 'Å', 'B' => 'Ɓ', 'C' => 'Ç', 'D' => 'Ð', 'E' => 'É', 'F' => 'Ƒ', 'G' => 'Ĝ', 'H' => 'Ĥ', 'I' => 'Î', 'J' => 'Ĵ', 'K' => 'Ķ', 'L' => 'Ļ', 'M' => 'Ṁ', 'N' => 'Ñ', 'O' => 'Ö', 'P' => 'Þ', 'Q' => 'Ǫ', 'R' => 'Ŕ', 'S' => 'Š', 'T' => 'Ţ', 'U' => 'Û', 'V' => 'Ṽ', 'W' => 'Ŵ', 'X' => 'Ẋ', 'Y' => 'Ý', 'Z' => 'Ž', '[' => '⁅', '\\' => '', ']' => '⁆', '^' => '˄', '_' => '‿', '`' => '', 'a' => 'å', 'b' => 'ƀ', 'c' => 'ç', 'd' => 'ð', 'e' => 'é', 'f' => 'ƒ', 'g' => 'ĝ', 'h' => 'ĥ', 'i' => 'î', 'j' => 'ĵ', 'k' => 'ķ', 'l' => 'ļ', 'm' => 'ɱ', 'n' => 'ñ', 'o' => 'ö', 'p' => 'þ', 'q' => 'ǫ', 'r' => 'ŕ', 's' => 'š', 't' => 'ţ', 'u' => 'û', 'v' => 'ṽ', 'w' => 'ŵ', 'x' => 'ẋ', 'y' => 'ý', 'z' => 'ž', '{' => '(', '|' => '¦', '}' => ')', '~' => '˞']) : $text;
}
private function expand(string &$trans, string $visibleText) : void
{
if (1.0 >= $this->expansionFactor) {
return;
}
$visibleLength = $this->strlen($visibleText);
$missingLength = (int) \ceil($visibleLength * $this->expansionFactor) - $visibleLength;
if ($this->brackets) {
$missingLength -= 2;
}
if (0 >= $missingLength) {
return;
}
$words = [];
$wordsCount = 0;
foreach (\preg_split('/ +/', $visibleText, -1, \PREG_SPLIT_NO_EMPTY) as $word) {
$wordLength = $this->strlen($word);
if ($wordLength >= $missingLength) {
continue;
}
if (!isset($words[$wordLength])) {
$words[$wordLength] = 0;
}
++$words[$wordLength];
++$wordsCount;
}
if (!$words) {
$trans .= 1 === $missingLength ? self::EXPANSION_CHARACTER : ' ' . \str_repeat(self::EXPANSION_CHARACTER, $missingLength - 1);
return;
}
\arsort($words, \SORT_NUMERIC);
$longestWordLength = \max(\array_keys($words));
while (\true) {
$r = \mt_rand(1, $wordsCount);
foreach ($words as $length => $count) {
$r -= $count;
if ($r <= 0) {
break;
}
}
$trans .= ' ' . \str_repeat(self::EXPANSION_CHARACTER, $length);
$missingLength -= $length + 1;
if (0 === $missingLength) {
return;
}
while ($longestWordLength >= $missingLength) {
$wordsCount -= $words[$longestWordLength];
unset($words[$longestWordLength]);
if (!$words) {
$trans .= 1 === $missingLength ? self::EXPANSION_CHARACTER : ' ' . \str_repeat(self::EXPANSION_CHARACTER, $missingLength - 1);
return;
}
$longestWordLength = \max(\array_keys($words));
}
}
}
private function addBrackets(string &$trans) : void
{
if (!$this->brackets) {
return;
}
$trans = '[' . $trans . ']';
}
private function strlen(string $s) : int
{
return \false === ($encoding = \mb_detect_encoding($s, null, \true)) ? \strlen($s) : \mb_strlen($s, $encoding);
}
}
@@ -0,0 +1,83 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation\Test;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\PHPUnit\Framework\TestCase;
use MailPoetVendor\Psr\Log\LoggerInterface;
use MailPoetVendor\Symfony\Component\HttpClient\MockHttpClient;
use MailPoetVendor\Symfony\Component\Translation\Dumper\XliffFileDumper;
use MailPoetVendor\Symfony\Component\Translation\Exception\IncompleteDsnException;
use MailPoetVendor\Symfony\Component\Translation\Exception\UnsupportedSchemeException;
use MailPoetVendor\Symfony\Component\Translation\Loader\LoaderInterface;
use MailPoetVendor\Symfony\Component\Translation\Provider\Dsn;
use MailPoetVendor\Symfony\Component\Translation\Provider\ProviderFactoryInterface;
use MailPoetVendor\Symfony\Contracts\HttpClient\HttpClientInterface;
abstract class ProviderFactoryTestCase extends TestCase
{
protected $client;
protected $logger;
protected $defaultLocale;
protected $loader;
protected $xliffFileDumper;
public abstract function createFactory() : ProviderFactoryInterface;
public static abstract function supportsProvider() : iterable;
public static abstract function createProvider() : iterable;
public static function unsupportedSchemeProvider() : iterable
{
return [];
}
public static function incompleteDsnProvider() : iterable
{
return [];
}
public function testSupports(bool $expected, string $dsn)
{
$factory = $this->createFactory();
$this->assertSame($expected, $factory->supports(new Dsn($dsn)));
}
public function testCreate(string $expected, string $dsn)
{
$factory = $this->createFactory();
$provider = $factory->create(new Dsn($dsn));
$this->assertSame($expected, (string) $provider);
}
public function testUnsupportedSchemeException(string $dsn, ?string $message = null)
{
$factory = $this->createFactory();
$dsn = new Dsn($dsn);
$this->expectException(UnsupportedSchemeException::class);
if (null !== $message) {
$this->expectExceptionMessage($message);
}
$factory->create($dsn);
}
public function testIncompleteDsnException(string $dsn, ?string $message = null)
{
$factory = $this->createFactory();
$dsn = new Dsn($dsn);
$this->expectException(IncompleteDsnException::class);
if (null !== $message) {
$this->expectExceptionMessage($message);
}
$factory->create($dsn);
}
protected function getClient() : HttpClientInterface
{
return $this->client ?? ($this->client = new MockHttpClient());
}
protected function getLogger() : LoggerInterface
{
return $this->logger ?? ($this->logger = $this->createMock(LoggerInterface::class));
}
protected function getDefaultLocale() : string
{
return $this->defaultLocale ?? ($this->defaultLocale = 'en');
}
protected function getLoader() : LoaderInterface
{
return $this->loader ?? ($this->loader = $this->createMock(LoaderInterface::class));
}
protected function getXliffFileDumper() : XliffFileDumper
{
return $this->xliffFileDumper ?? ($this->xliffFileDumper = $this->createMock(XliffFileDumper::class));
}
}
@@ -0,0 +1,44 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation\Test;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\PHPUnit\Framework\TestCase;
use MailPoetVendor\Psr\Log\LoggerInterface;
use MailPoetVendor\Symfony\Component\HttpClient\MockHttpClient;
use MailPoetVendor\Symfony\Component\Translation\Dumper\XliffFileDumper;
use MailPoetVendor\Symfony\Component\Translation\Loader\LoaderInterface;
use MailPoetVendor\Symfony\Component\Translation\Provider\ProviderInterface;
use MailPoetVendor\Symfony\Contracts\HttpClient\HttpClientInterface;
abstract class ProviderTestCase extends TestCase
{
protected $client;
protected $logger;
protected $defaultLocale;
protected $loader;
protected $xliffFileDumper;
public static abstract function createProvider(HttpClientInterface $client, LoaderInterface $loader, LoggerInterface $logger, string $defaultLocale, string $endpoint) : ProviderInterface;
public static abstract function toStringProvider() : iterable;
public function testToString(ProviderInterface $provider, string $expected)
{
$this->assertSame($expected, (string) $provider);
}
protected function getClient() : MockHttpClient
{
return $this->client ?? ($this->client = new MockHttpClient());
}
protected function getLoader() : LoaderInterface
{
return $this->loader ?? ($this->loader = $this->createMock(LoaderInterface::class));
}
protected function getLogger() : LoggerInterface
{
return $this->logger ?? ($this->logger = $this->createMock(LoggerInterface::class));
}
protected function getDefaultLocale() : string
{
return $this->defaultLocale ?? ($this->defaultLocale = 'en');
}
protected function getXliffFileDumper() : XliffFileDumper
{
return $this->xliffFileDumper ?? ($this->xliffFileDumper = $this->createMock(XliffFileDumper::class));
}
}
@@ -0,0 +1,39 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Symfony\Contracts\Translation\TranslatableInterface;
use MailPoetVendor\Symfony\Contracts\Translation\TranslatorInterface;
class TranslatableMessage implements TranslatableInterface
{
private $message;
private $parameters;
private $domain;
public function __construct(string $message, array $parameters = [], ?string $domain = null)
{
$this->message = $message;
$this->parameters = $parameters;
$this->domain = $domain;
}
public function __toString() : string
{
return $this->getMessage();
}
public function getMessage() : string
{
return $this->message;
}
public function getParameters() : array
{
return $this->parameters;
}
public function getDomain() : ?string
{
return $this->domain;
}
public function trans(TranslatorInterface $translator, ?string $locale = null) : string
{
return $translator->trans($this->getMessage(), \array_map(static function ($parameter) use($translator, $locale) {
return $parameter instanceof TranslatableInterface ? $parameter->trans($translator, $locale) : $parameter;
}, $this->getParameters()), $this->getDomain(), $locale);
}
}
@@ -0,0 +1,296 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Symfony\Component\Config\ConfigCacheFactory;
use MailPoetVendor\Symfony\Component\Config\ConfigCacheFactoryInterface;
use MailPoetVendor\Symfony\Component\Config\ConfigCacheInterface;
use MailPoetVendor\Symfony\Component\Translation\Exception\InvalidArgumentException;
use MailPoetVendor\Symfony\Component\Translation\Exception\NotFoundResourceException;
use MailPoetVendor\Symfony\Component\Translation\Exception\RuntimeException;
use MailPoetVendor\Symfony\Component\Translation\Formatter\IntlFormatterInterface;
use MailPoetVendor\Symfony\Component\Translation\Formatter\MessageFormatter;
use MailPoetVendor\Symfony\Component\Translation\Formatter\MessageFormatterInterface;
use MailPoetVendor\Symfony\Component\Translation\Loader\LoaderInterface;
use MailPoetVendor\Symfony\Contracts\Translation\LocaleAwareInterface;
use MailPoetVendor\Symfony\Contracts\Translation\TranslatorInterface;
// Help opcache.preload discover always-needed symbols
\class_exists(MessageCatalogue::class);
class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface
{
protected $catalogues = [];
private $locale;
private $fallbackLocales = [];
private $loaders = [];
private $resources = [];
private $formatter;
private $cacheDir;
private $debug;
private $cacheVary;
private $configCacheFactory;
private $parentLocales;
private $hasIntlFormatter;
public function __construct(string $locale, ?MessageFormatterInterface $formatter = null, ?string $cacheDir = null, bool $debug = \false, array $cacheVary = [])
{
$this->setLocale($locale);
if (null === $formatter) {
$formatter = new MessageFormatter();
}
$this->formatter = $formatter;
$this->cacheDir = $cacheDir;
$this->debug = $debug;
$this->cacheVary = $cacheVary;
$this->hasIntlFormatter = $formatter instanceof IntlFormatterInterface;
}
public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory)
{
$this->configCacheFactory = $configCacheFactory;
}
public function addLoader(string $format, LoaderInterface $loader)
{
$this->loaders[$format] = $loader;
}
public function addResource(string $format, $resource, string $locale, ?string $domain = null)
{
if (null === $domain) {
$domain = 'messages';
}
$this->assertValidLocale($locale);
$locale ?: ($locale = \class_exists(\Locale::class) ? \Locale::getDefault() : 'en');
$this->resources[$locale][] = [$format, $resource, $domain];
if (\in_array($locale, $this->fallbackLocales)) {
$this->catalogues = [];
} else {
unset($this->catalogues[$locale]);
}
}
public function setLocale(string $locale)
{
$this->assertValidLocale($locale);
$this->locale = $locale;
}
public function getLocale()
{
return $this->locale ?: (\class_exists(\Locale::class) ? \Locale::getDefault() : 'en');
}
public function setFallbackLocales(array $locales)
{
// needed as the fallback locales are linked to the already loaded catalogues
$this->catalogues = [];
foreach ($locales as $locale) {
$this->assertValidLocale($locale);
}
$this->fallbackLocales = $this->cacheVary['fallback_locales'] = $locales;
}
public function getFallbackLocales() : array
{
return $this->fallbackLocales;
}
public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null)
{
if (null === $id || '' === $id) {
return '';
}
if (null === $domain) {
$domain = 'messages';
}
$catalogue = $this->getCatalogue($locale);
$locale = $catalogue->getLocale();
while (!$catalogue->defines($id, $domain)) {
if ($cat = $catalogue->getFallbackCatalogue()) {
$catalogue = $cat;
$locale = $catalogue->getLocale();
} else {
break;
}
}
$len = \strlen(MessageCatalogue::INTL_DOMAIN_SUFFIX);
if ($this->hasIntlFormatter && ($catalogue->defines($id, $domain . MessageCatalogue::INTL_DOMAIN_SUFFIX) || \strlen($domain) > $len && 0 === \substr_compare($domain, MessageCatalogue::INTL_DOMAIN_SUFFIX, -$len, $len))) {
return $this->formatter->formatIntl($catalogue->get($id, $domain), $locale, $parameters);
}
return $this->formatter->format($catalogue->get($id, $domain), $locale, $parameters);
}
public function getCatalogue(?string $locale = null)
{
if (!$locale) {
$locale = $this->getLocale();
} else {
$this->assertValidLocale($locale);
}
if (!isset($this->catalogues[$locale])) {
$this->loadCatalogue($locale);
}
return $this->catalogues[$locale];
}
public function getCatalogues() : array
{
return \array_values($this->catalogues);
}
protected function getLoaders()
{
return $this->loaders;
}
protected function loadCatalogue(string $locale)
{
if (null === $this->cacheDir) {
$this->initializeCatalogue($locale);
} else {
$this->initializeCacheCatalogue($locale);
}
}
protected function initializeCatalogue(string $locale)
{
$this->assertValidLocale($locale);
try {
$this->doLoadCatalogue($locale);
} catch (NotFoundResourceException $e) {
if (!$this->computeFallbackLocales($locale)) {
throw $e;
}
}
$this->loadFallbackCatalogues($locale);
}
private function initializeCacheCatalogue(string $locale) : void
{
if (isset($this->catalogues[$locale])) {
return;
}
$this->assertValidLocale($locale);
$cache = $this->getConfigCacheFactory()->cache($this->getCatalogueCachePath($locale), function (ConfigCacheInterface $cache) use($locale) {
$this->dumpCatalogue($locale, $cache);
});
if (isset($this->catalogues[$locale])) {
return;
}
$this->catalogues[$locale] = (include $cache->getPath());
}
private function dumpCatalogue(string $locale, ConfigCacheInterface $cache) : void
{
$this->initializeCatalogue($locale);
$fallbackContent = $this->getFallbackContent($this->catalogues[$locale]);
$content = \sprintf(<<<EOF
<?php
use Symfony\\Component\\Translation\\MessageCatalogue;
\$catalogue = new MessageCatalogue('%s', %s);
%s
return \$catalogue;
EOF
, $locale, \var_export($this->getAllMessages($this->catalogues[$locale]), \true), $fallbackContent);
$cache->write($content, $this->catalogues[$locale]->getResources());
}
private function getFallbackContent(MessageCatalogue $catalogue) : string
{
$fallbackContent = '';
$current = '';
$replacementPattern = '/[^a-z0-9_]/i';
$fallbackCatalogue = $catalogue->getFallbackCatalogue();
while ($fallbackCatalogue) {
$fallback = $fallbackCatalogue->getLocale();
$fallbackSuffix = \ucfirst(\preg_replace($replacementPattern, '_', $fallback));
$currentSuffix = \ucfirst(\preg_replace($replacementPattern, '_', $current));
$fallbackContent .= \sprintf(<<<'EOF'
$catalogue%s = new MessageCatalogue('%s', %s);
$catalogue%s->addFallbackCatalogue($catalogue%s);
EOF
, $fallbackSuffix, $fallback, \var_export($this->getAllMessages($fallbackCatalogue), \true), $currentSuffix, $fallbackSuffix);
$current = $fallbackCatalogue->getLocale();
$fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue();
}
return $fallbackContent;
}
private function getCatalogueCachePath(string $locale) : string
{
return $this->cacheDir . '/catalogue.' . $locale . '.' . \strtr(\substr(\base64_encode(\hash('sha256', \serialize($this->cacheVary), \true)), 0, 7), '/', '_') . '.php';
}
protected function doLoadCatalogue(string $locale) : void
{
$this->catalogues[$locale] = new MessageCatalogue($locale);
if (isset($this->resources[$locale])) {
foreach ($this->resources[$locale] as $resource) {
if (!isset($this->loaders[$resource[0]])) {
if (\is_string($resource[1])) {
throw new RuntimeException(\sprintf('No loader is registered for the "%s" format when loading the "%s" resource.', $resource[0], $resource[1]));
}
throw new RuntimeException(\sprintf('No loader is registered for the "%s" format.', $resource[0]));
}
$this->catalogues[$locale]->addCatalogue($this->loaders[$resource[0]]->load($resource[1], $locale, $resource[2]));
}
}
}
private function loadFallbackCatalogues(string $locale) : void
{
$current = $this->catalogues[$locale];
foreach ($this->computeFallbackLocales($locale) as $fallback) {
if (!isset($this->catalogues[$fallback])) {
$this->initializeCatalogue($fallback);
}
$fallbackCatalogue = new MessageCatalogue($fallback, $this->getAllMessages($this->catalogues[$fallback]));
foreach ($this->catalogues[$fallback]->getResources() as $resource) {
$fallbackCatalogue->addResource($resource);
}
$current->addFallbackCatalogue($fallbackCatalogue);
$current = $fallbackCatalogue;
}
}
protected function computeFallbackLocales(string $locale)
{
if (null === $this->parentLocales) {
$this->parentLocales = \json_decode(\file_get_contents(__DIR__ . '/Resources/data/parents.json'), \true);
}
$originLocale = $locale;
$locales = [];
while ($locale) {
$parent = $this->parentLocales[$locale] ?? null;
if ($parent) {
$locale = 'root' !== $parent ? $parent : null;
} elseif (\function_exists('locale_parse')) {
$localeSubTags = \locale_parse($locale);
$locale = null;
if (1 < \count($localeSubTags)) {
\array_pop($localeSubTags);
$locale = \locale_compose($localeSubTags) ?: null;
}
} elseif ($i = \strrpos($locale, '_') ?: \strrpos($locale, '-')) {
$locale = \substr($locale, 0, $i);
} else {
$locale = null;
}
if (null !== $locale) {
$locales[] = $locale;
}
}
foreach ($this->fallbackLocales as $fallback) {
if ($fallback === $originLocale) {
continue;
}
$locales[] = $fallback;
}
return \array_unique($locales);
}
protected function assertValidLocale(string $locale)
{
if (!\preg_match('/^[a-z0-9@_\\.\\-]*$/i', $locale)) {
throw new InvalidArgumentException(\sprintf('Invalid "%s" locale.', $locale));
}
}
private function getConfigCacheFactory() : ConfigCacheFactoryInterface
{
if (!$this->configCacheFactory) {
$this->configCacheFactory = new ConfigCacheFactory($this->debug);
}
return $this->configCacheFactory;
}
private function getAllMessages(MessageCatalogueInterface $catalogue) : array
{
$allMessages = [];
foreach ($catalogue->all() as $domain => $messages) {
if ($intlMessages = $catalogue->all($domain . MessageCatalogue::INTL_DOMAIN_SUFFIX)) {
$allMessages[$domain . MessageCatalogue::INTL_DOMAIN_SUFFIX] = $intlMessages;
$messages = \array_diff_key($messages, $intlMessages);
}
if ($messages) {
$allMessages[$domain] = $messages;
}
}
return $allMessages;
}
}
@@ -0,0 +1,68 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Symfony\Component\Translation\Catalogue\AbstractOperation;
use MailPoetVendor\Symfony\Component\Translation\Catalogue\TargetOperation;
final class TranslatorBag implements TranslatorBagInterface
{
private $catalogues = [];
public function addCatalogue(MessageCatalogue $catalogue) : void
{
if (null !== ($existingCatalogue = $this->getCatalogue($catalogue->getLocale()))) {
$catalogue->addCatalogue($existingCatalogue);
}
$this->catalogues[$catalogue->getLocale()] = $catalogue;
}
public function addBag(TranslatorBagInterface $bag) : void
{
foreach ($bag->getCatalogues() as $catalogue) {
$this->addCatalogue($catalogue);
}
}
public function getCatalogue(?string $locale = null) : MessageCatalogueInterface
{
if (null === $locale || !isset($this->catalogues[$locale])) {
$this->catalogues[$locale] = new MessageCatalogue($locale);
}
return $this->catalogues[$locale];
}
public function getCatalogues() : array
{
return \array_values($this->catalogues);
}
public function diff(TranslatorBagInterface $diffBag) : self
{
$diff = new self();
foreach ($this->catalogues as $locale => $catalogue) {
if (null === ($diffCatalogue = $diffBag->getCatalogue($locale))) {
$diff->addCatalogue($catalogue);
continue;
}
$operation = new TargetOperation($diffCatalogue, $catalogue);
$operation->moveMessagesToIntlDomainsIfPossible(AbstractOperation::NEW_BATCH);
$newCatalogue = new MessageCatalogue($locale);
foreach ($catalogue->getDomains() as $domain) {
$newCatalogue->add($operation->getNewMessages($domain), $domain);
}
$diff->addCatalogue($newCatalogue);
}
return $diff;
}
public function intersect(TranslatorBagInterface $intersectBag) : self
{
$diff = new self();
foreach ($this->catalogues as $locale => $catalogue) {
if (null === ($intersectCatalogue = $intersectBag->getCatalogue($locale))) {
continue;
}
$operation = new TargetOperation($catalogue, $intersectCatalogue);
$operation->moveMessagesToIntlDomainsIfPossible(AbstractOperation::OBSOLETE_BATCH);
$obsoleteCatalogue = new MessageCatalogue($locale);
foreach ($operation->getDomains() as $domain) {
$obsoleteCatalogue->add(\array_diff($operation->getMessages($domain), $operation->getNewMessages($domain)), $domain);
}
$diff->addCatalogue($obsoleteCatalogue);
}
return $diff;
}
}
@@ -0,0 +1,8 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Symfony\Component\Translation\Exception\InvalidArgumentException;
interface TranslatorBagInterface
{
public function getCatalogue(?string $locale = null);
}
@@ -0,0 +1,73 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation\Util;
if (!defined('ABSPATH')) exit;
class ArrayConverter
{
public static function expandToTree(array $messages)
{
$tree = [];
foreach ($messages as $id => $value) {
$referenceToElement =& self::getElementByPath($tree, self::getKeyParts($id));
$referenceToElement = $value;
unset($referenceToElement);
}
return $tree;
}
private static function &getElementByPath(array &$tree, array $parts)
{
$elem =& $tree;
$parentOfElem = null;
foreach ($parts as $i => $part) {
if (isset($elem[$part]) && \is_string($elem[$part])) {
$elem =& $elem[\implode('.', \array_slice($parts, $i))];
break;
}
$parentOfElem =& $elem;
$elem =& $elem[$part];
}
if ($elem && \is_array($elem) && $parentOfElem) {
self::cancelExpand($parentOfElem, $part, $elem);
}
return $elem;
}
private static function cancelExpand(array &$tree, string $prefix, array $node)
{
$prefix .= '.';
foreach ($node as $id => $value) {
if (\is_string($value)) {
$tree[$prefix . $id] = $value;
} else {
self::cancelExpand($tree, $prefix . $id, $value);
}
}
}
private static function getKeyParts(string $key) : array
{
$parts = \explode('.', $key);
$partsCount = \count($parts);
$result = [];
$buffer = '';
foreach ($parts as $index => $part) {
if (0 === $index && '' === $part) {
$buffer = '.';
continue;
}
if ($index === $partsCount - 1 && '' === $part) {
$buffer .= '.';
$result[] = $buffer;
continue;
}
if (isset($parts[$index + 1]) && '' === $parts[$index + 1]) {
$buffer .= $part;
continue;
}
if ($buffer) {
$result[] = $buffer . $part;
$buffer = '';
continue;
}
$result[] = $part;
}
return $result;
}
}
@@ -0,0 +1,124 @@
<?php
namespace MailPoetVendor\Symfony\Component\Translation\Util;
if (!defined('ABSPATH')) exit;
use MailPoetVendor\Symfony\Component\Translation\Exception\InvalidArgumentException;
use MailPoetVendor\Symfony\Component\Translation\Exception\InvalidResourceException;
class XliffUtils
{
public static function getVersionNumber(\DOMDocument $dom) : string
{
foreach ($dom->getElementsByTagName('xliff') as $xliff) {
$version = $xliff->attributes->getNamedItem('version');
if ($version) {
return $version->nodeValue;
}
$namespace = $xliff->attributes->getNamedItem('xmlns');
if ($namespace) {
if (0 !== \substr_compare('urn:oasis:names:tc:xliff:document:', $namespace->nodeValue, 0, 34)) {
throw new InvalidArgumentException(\sprintf('Not a valid XLIFF namespace "%s".', $namespace));
}
return \substr($namespace, 34);
}
}
// Falls back to v1.2
return '1.2';
}
public static function validateSchema(\DOMDocument $dom) : array
{
$xliffVersion = static::getVersionNumber($dom);
$internalErrors = \libxml_use_internal_errors(\true);
if ($shouldEnable = self::shouldEnableEntityLoader()) {
$disableEntities = \libxml_disable_entity_loader(\false);
}
try {
$isValid = @$dom->schemaValidateSource(self::getSchema($xliffVersion));
if (!$isValid) {
return self::getXmlErrors($internalErrors);
}
} finally {
if ($shouldEnable) {
\libxml_disable_entity_loader($disableEntities);
}
}
$dom->normalizeDocument();
\libxml_clear_errors();
\libxml_use_internal_errors($internalErrors);
return [];
}
private static function shouldEnableEntityLoader() : bool
{
// Version prior to 8.0 can be enabled without deprecation
if (\PHP_VERSION_ID < 80000) {
return \true;
}
static $dom, $schema;
if (null === $dom) {
$dom = new \DOMDocument();
$dom->loadXML('<?xml version="1.0"?><test/>');
$tmpfile = \tempnam(\sys_get_temp_dir(), 'symfony');
\register_shutdown_function(static function () use($tmpfile) {
@\unlink($tmpfile);
});
$schema = '<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:include schemaLocation="file:///' . \str_replace('\\', '/', $tmpfile) . '" />
</xsd:schema>';
\file_put_contents($tmpfile, '<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="test" type="testType" />
<xsd:complexType name="testType"/>
</xsd:schema>');
}
return !@$dom->schemaValidateSource($schema);
}
public static function getErrorsAsString(array $xmlErrors) : string
{
$errorsAsString = '';
foreach ($xmlErrors as $error) {
$errorsAsString .= \sprintf("[%s %s] %s (in %s - line %d, column %d)\n", \LIBXML_ERR_WARNING === $error['level'] ? 'WARNING' : 'ERROR', $error['code'], $error['message'], $error['file'], $error['line'], $error['column']);
}
return $errorsAsString;
}
private static function getSchema(string $xliffVersion) : string
{
if ('1.2' === $xliffVersion) {
$schemaSource = \file_get_contents(__DIR__ . '/../Resources/schemas/xliff-core-1.2-strict.xsd');
$xmlUri = 'http://www.w3.org/2001/xml.xsd';
} elseif ('2.0' === $xliffVersion) {
$schemaSource = \file_get_contents(__DIR__ . '/../Resources/schemas/xliff-core-2.0.xsd');
$xmlUri = 'informativeCopiesOf3rdPartySchemas/w3c/xml.xsd';
} else {
throw new InvalidArgumentException(\sprintf('No support implemented for loading XLIFF version "%s".', $xliffVersion));
}
return self::fixXmlLocation($schemaSource, $xmlUri);
}
private static function fixXmlLocation(string $schemaSource, string $xmlUri) : string
{
$newPath = \str_replace('\\', '/', __DIR__) . '/../Resources/schemas/xml.xsd';
$parts = \explode('/', $newPath);
$locationstart = 'file:///';
if (0 === \stripos($newPath, 'phar://')) {
$tmpfile = \tempnam(\sys_get_temp_dir(), 'symfony');
if ($tmpfile) {
\copy($newPath, $tmpfile);
$parts = \explode('/', \str_replace('\\', '/', $tmpfile));
} else {
\array_shift($parts);
$locationstart = 'phar:///';
}
}
$drive = '\\' === \DIRECTORY_SEPARATOR ? \array_shift($parts) . '/' : '';
$newPath = $locationstart . $drive . \implode('/', \array_map('rawurlencode', $parts));
return \str_replace($xmlUri, $newPath, $schemaSource);
}
private static function getXmlErrors(bool $internalErrors) : array
{
$errors = [];
foreach (\libxml_get_errors() as $error) {
$errors[] = ['level' => \LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR', 'code' => $error->code, 'message' => \trim($error->message), 'file' => $error->file ?: 'n/a', 'line' => $error->line, 'column' => $error->column];
}
\libxml_clear_errors();
\libxml_use_internal_errors($internalErrors);
return $errors;
}
}