Upgrade framework
This commit is contained in:
3
vendor/symfony/translation/.gitignore
vendored
3
vendor/symfony/translation/.gitignore
vendored
@@ -1,3 +0,0 @@
|
||||
vendor/
|
||||
composer.lock
|
||||
phpunit.xml
|
||||
97
vendor/symfony/translation/CHANGELOG.md
vendored
97
vendor/symfony/translation/CHANGELOG.md
vendored
@@ -1,6 +1,103 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
5.4
|
||||
---
|
||||
|
||||
* Add `github` format & autodetection to render errors as annotations when
|
||||
running the XLIFF linter command in a Github Actions environment.
|
||||
* Translation providers are not experimental anymore
|
||||
|
||||
5.3
|
||||
---
|
||||
|
||||
* Add `translation:pull` and `translation:push` commands to manage translations with third-party providers
|
||||
* Add `TranslatorBagInterface::getCatalogues` method
|
||||
* Add support to load XLIFF string in `XliffFileLoader`
|
||||
|
||||
5.2.0
|
||||
-----
|
||||
|
||||
* added support for calling `trans` with ICU formatted messages
|
||||
* added `PseudoLocalizationTranslator`
|
||||
* added `TranslatableMessage` objects that represent a message that can be translated
|
||||
* added the `t()` function to easily create `TranslatableMessage` objects
|
||||
* Added support for extracting messages from `TranslatableMessage` objects
|
||||
|
||||
5.1.0
|
||||
-----
|
||||
|
||||
* added support for `name` attribute on `unit` element from xliff2 to be used as a translation key instead of always the `source` element
|
||||
|
||||
5.0.0
|
||||
-----
|
||||
|
||||
* removed support for using `null` as the locale in `Translator`
|
||||
* removed `TranslatorInterface`
|
||||
* removed `MessageSelector`
|
||||
* removed `ChoiceMessageFormatterInterface`
|
||||
* removed `PluralizationRule`
|
||||
* removed `Interval`
|
||||
* removed `transChoice()` methods, use the trans() method instead with a %count% parameter
|
||||
* removed `FileDumper::setBackup()` and `TranslationWriter::disableBackup()`
|
||||
* removed `MessageFormatter::choiceFormat()`
|
||||
* added argument `$filename` to `PhpExtractor::parseTokens()`
|
||||
* removed support for implicit STDIN usage in the `lint:xliff` command, use `lint:xliff -` (append a dash) instead to make it explicit.
|
||||
|
||||
4.4.0
|
||||
-----
|
||||
|
||||
* deprecated support for using `null` as the locale in `Translator`
|
||||
* deprecated accepting STDIN implicitly when using the `lint:xliff` command, use `lint:xliff -` (append a dash) instead to make it explicit.
|
||||
* Marked the `TranslationDataCollector` class as `@final`.
|
||||
|
||||
4.3.0
|
||||
-----
|
||||
|
||||
* Improved Xliff 1.2 loader to load the original file's metadata
|
||||
* Added `TranslatorPathsPass`
|
||||
|
||||
4.2.0
|
||||
-----
|
||||
|
||||
* Started using ICU parent locales as fallback locales.
|
||||
* allow using the ICU message format using domains with the "+intl-icu" suffix
|
||||
* deprecated `Translator::transChoice()` in favor of using `Translator::trans()` with a `%count%` parameter
|
||||
* deprecated `TranslatorInterface` in favor of `Symfony\Contracts\Translation\TranslatorInterface`
|
||||
* deprecated `MessageSelector`, `Interval` and `PluralizationRules`; use `IdentityTranslator` instead
|
||||
* Added `IntlFormatter` and `IntlFormatterInterface`
|
||||
* added support for multiple files and directories in `XliffLintCommand`
|
||||
* Marked `Translator::getFallbackLocales()` and `TranslationDataCollector::getFallbackLocales()` as internal
|
||||
|
||||
4.1.0
|
||||
-----
|
||||
|
||||
* The `FileDumper::setBackup()` method is deprecated.
|
||||
* The `TranslationWriter::disableBackup()` method is deprecated.
|
||||
* The `XliffFileDumper` will write "name" on the "unit" node when dumping XLIFF 2.0.
|
||||
|
||||
4.0.0
|
||||
-----
|
||||
|
||||
* removed the backup feature of the `FileDumper` class
|
||||
* removed `TranslationWriter::writeTranslations()` method
|
||||
* removed support for passing `MessageSelector` instances to the constructor of the `Translator` class
|
||||
|
||||
3.4.0
|
||||
-----
|
||||
|
||||
* Added `TranslationDumperPass`
|
||||
* Added `TranslationExtractorPass`
|
||||
* Added `TranslatorPass`
|
||||
* Added `TranslationReader` and `TranslationReaderInterface`
|
||||
* Added `<notes>` section to the Xliff 2.0 dumper.
|
||||
* Improved Xliff 2.0 loader to load `<notes>` section.
|
||||
* Added `TranslationWriterInterface`
|
||||
* Deprecated `TranslationWriter::writeTranslations` in favor of `TranslationWriter::write`
|
||||
* added support for adding custom message formatter and decoupling the default one.
|
||||
* Added `PhpExtractor`
|
||||
* Added `PhpStringTokenParser`
|
||||
|
||||
3.2.0
|
||||
-----
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
|
||||
namespace Symfony\Component\Translation\Catalogue;
|
||||
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\MessageCatalogueInterface;
|
||||
use Symfony\Component\Translation\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Translation\Exception\LogicException;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\MessageCatalogueInterface;
|
||||
|
||||
/**
|
||||
* Base catalogues binary operation class.
|
||||
@@ -26,23 +26,16 @@ use Symfony\Component\Translation\Exception\LogicException;
|
||||
*/
|
||||
abstract class AbstractOperation implements OperationInterface
|
||||
{
|
||||
/**
|
||||
* @var MessageCatalogueInterface The source catalogue
|
||||
*/
|
||||
public const OBSOLETE_BATCH = 'obsolete';
|
||||
public const NEW_BATCH = 'new';
|
||||
public const ALL_BATCH = 'all';
|
||||
|
||||
protected $source;
|
||||
|
||||
/**
|
||||
* @var MessageCatalogueInterface The target catalogue
|
||||
*/
|
||||
protected $target;
|
||||
|
||||
/**
|
||||
* @var MessageCatalogue The result catalogue
|
||||
*/
|
||||
protected $result;
|
||||
|
||||
/**
|
||||
* @var null|array The domains affected by this operation
|
||||
* @var array|null The domains affected by this operation
|
||||
*/
|
||||
private $domains;
|
||||
|
||||
@@ -50,30 +43,26 @@ abstract class AbstractOperation implements OperationInterface
|
||||
* This array stores 'all', 'new' and 'obsolete' messages for all valid domains.
|
||||
*
|
||||
* The data structure of this array is as follows:
|
||||
* ```php
|
||||
* array(
|
||||
* 'domain 1' => array(
|
||||
* 'all' => array(...),
|
||||
* 'new' => array(...),
|
||||
* 'obsolete' => array(...)
|
||||
* ),
|
||||
* 'domain 2' => array(
|
||||
* 'all' => array(...),
|
||||
* 'new' => array(...),
|
||||
* 'obsolete' => array(...)
|
||||
* ),
|
||||
* ...
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* [
|
||||
* 'domain 1' => [
|
||||
* 'all' => [...],
|
||||
* 'new' => [...],
|
||||
* 'obsolete' => [...]
|
||||
* ],
|
||||
* 'domain 2' => [
|
||||
* 'all' => [...],
|
||||
* 'new' => [...],
|
||||
* 'obsolete' => [...]
|
||||
* ],
|
||||
* ...
|
||||
* ]
|
||||
*
|
||||
* @var array The array that stores 'all', 'new' and 'obsolete' messages
|
||||
*/
|
||||
protected $messages;
|
||||
|
||||
/**
|
||||
* @param MessageCatalogueInterface $source The source catalogue
|
||||
* @param MessageCatalogueInterface $target The target catalogue
|
||||
*
|
||||
* @throws LogicException
|
||||
*/
|
||||
public function __construct(MessageCatalogueInterface $source, MessageCatalogueInterface $target)
|
||||
@@ -85,16 +74,27 @@ abstract class AbstractOperation implements OperationInterface
|
||||
$this->source = $source;
|
||||
$this->target = $target;
|
||||
$this->result = new MessageCatalogue($source->getLocale());
|
||||
$this->messages = array();
|
||||
$this->messages = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDomains()
|
||||
public function getDomains(): array
|
||||
{
|
||||
if (null === $this->domains) {
|
||||
$this->domains = array_values(array_unique(array_merge($this->source->getDomains(), $this->target->getDomains())));
|
||||
$domains = [];
|
||||
foreach ([$this->source, $this->target] as $catalogue) {
|
||||
foreach ($catalogue->getDomains() as $domain) {
|
||||
$domains[$domain] = $domain;
|
||||
|
||||
if ($catalogue->all($domainIcu = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX)) {
|
||||
$domains[$domainIcu] = $domainIcu;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->domains = array_values($domains);
|
||||
}
|
||||
|
||||
return $this->domains;
|
||||
@@ -103,55 +103,55 @@ abstract class AbstractOperation implements OperationInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMessages($domain)
|
||||
public function getMessages(string $domain): array
|
||||
{
|
||||
if (!in_array($domain, $this->getDomains())) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid domain: %s.', $domain));
|
||||
if (!\in_array($domain, $this->getDomains())) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain));
|
||||
}
|
||||
|
||||
if (!isset($this->messages[$domain]['all'])) {
|
||||
if (!isset($this->messages[$domain][self::ALL_BATCH])) {
|
||||
$this->processDomain($domain);
|
||||
}
|
||||
|
||||
return $this->messages[$domain]['all'];
|
||||
return $this->messages[$domain][self::ALL_BATCH];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getNewMessages($domain)
|
||||
public function getNewMessages(string $domain): array
|
||||
{
|
||||
if (!in_array($domain, $this->getDomains())) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid domain: %s.', $domain));
|
||||
if (!\in_array($domain, $this->getDomains())) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain));
|
||||
}
|
||||
|
||||
if (!isset($this->messages[$domain]['new'])) {
|
||||
if (!isset($this->messages[$domain][self::NEW_BATCH])) {
|
||||
$this->processDomain($domain);
|
||||
}
|
||||
|
||||
return $this->messages[$domain]['new'];
|
||||
return $this->messages[$domain][self::NEW_BATCH];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getObsoleteMessages($domain)
|
||||
public function getObsoleteMessages(string $domain): array
|
||||
{
|
||||
if (!in_array($domain, $this->getDomains())) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid domain: %s.', $domain));
|
||||
if (!\in_array($domain, $this->getDomains())) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain));
|
||||
}
|
||||
|
||||
if (!isset($this->messages[$domain]['obsolete'])) {
|
||||
if (!isset($this->messages[$domain][self::OBSOLETE_BATCH])) {
|
||||
$this->processDomain($domain);
|
||||
}
|
||||
|
||||
return $this->messages[$domain]['obsolete'];
|
||||
return $this->messages[$domain][self::OBSOLETE_BATCH];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getResult()
|
||||
public function getResult(): MessageCatalogueInterface
|
||||
{
|
||||
foreach ($this->getDomains() as $domain) {
|
||||
if (!isset($this->messages[$domain])) {
|
||||
@@ -162,11 +162,42 @@ abstract class AbstractOperation implements OperationInterface
|
||||
return $this->result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param self::*_BATCH $batch
|
||||
*/
|
||||
public function moveMessagesToIntlDomainsIfPossible(string $batch = self::ALL_BATCH): void
|
||||
{
|
||||
// If MessageFormatter class does not exists, intl domains are not supported.
|
||||
if (!class_exists(\MessageFormatter::class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->getDomains() as $domain) {
|
||||
$intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX;
|
||||
switch ($batch) {
|
||||
case self::OBSOLETE_BATCH: $messages = $this->getObsoleteMessages($domain); break;
|
||||
case self::NEW_BATCH: $messages = $this->getNewMessages($domain); break;
|
||||
case self::ALL_BATCH: $messages = $this->getMessages($domain); break;
|
||||
default: throw new \InvalidArgumentException(sprintf('$batch argument must be one of ["%s", "%s", "%s"].', self::ALL_BATCH, self::NEW_BATCH, self::OBSOLETE_BATCH));
|
||||
}
|
||||
|
||||
if (!$messages || (!$this->source->all($intlDomain) && $this->source->all($domain))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result = $this->getResult();
|
||||
$allIntlMessages = $result->all($intlDomain);
|
||||
$currentMessages = array_diff_key($messages, $result->all($domain));
|
||||
$result->replace($currentMessages, $domain);
|
||||
$result->replace($allIntlMessages + $messages, $intlDomain);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs operation on source and target catalogues for the given domain and
|
||||
* stores the results.
|
||||
*
|
||||
* @param string $domain The domain which the operation will be performed for
|
||||
*/
|
||||
abstract protected function processDomain($domain);
|
||||
abstract protected function processDomain(string $domain);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
namespace Symfony\Component\Translation\Catalogue;
|
||||
|
||||
use Symfony\Component\Translation\MessageCatalogueInterface;
|
||||
|
||||
/**
|
||||
* Merge operation between two catalogues as follows:
|
||||
* all = source ∪ target = {x: x ∈ source ∨ x ∈ target}
|
||||
@@ -25,19 +27,21 @@ class MergeOperation extends AbstractOperation
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function processDomain($domain)
|
||||
protected function processDomain(string $domain)
|
||||
{
|
||||
$this->messages[$domain] = array(
|
||||
'all' => array(),
|
||||
'new' => array(),
|
||||
'obsolete' => array(),
|
||||
);
|
||||
$this->messages[$domain] = [
|
||||
'all' => [],
|
||||
'new' => [],
|
||||
'obsolete' => [],
|
||||
];
|
||||
$intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX;
|
||||
|
||||
foreach ($this->source->all($domain) as $id => $message) {
|
||||
$this->messages[$domain]['all'][$id] = $message;
|
||||
$this->result->add(array($id => $message), $domain);
|
||||
if (null !== $keyMetadata = $this->source->getMetadata($id, $domain)) {
|
||||
$this->result->setMetadata($id, $keyMetadata, $domain);
|
||||
$d = $this->source->defines($id, $intlDomain) ? $intlDomain : $domain;
|
||||
$this->result->add([$id => $message], $d);
|
||||
if (null !== $keyMetadata = $this->source->getMetadata($id, $d)) {
|
||||
$this->result->setMetadata($id, $keyMetadata, $d);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,9 +49,10 @@ class MergeOperation extends AbstractOperation
|
||||
if (!$this->source->has($id, $domain)) {
|
||||
$this->messages[$domain]['all'][$id] = $message;
|
||||
$this->messages[$domain]['new'][$id] = $message;
|
||||
$this->result->add(array($id => $message), $domain);
|
||||
if (null !== $keyMetadata = $this->target->getMetadata($id, $domain)) {
|
||||
$this->result->setMetadata($id, $keyMetadata, $domain);
|
||||
$d = $this->target->defines($id, $intlDomain) ? $intlDomain : $domain;
|
||||
$this->result->add([$id => $message], $d);
|
||||
if (null !== $keyMetadata = $this->target->getMetadata($id, $d)) {
|
||||
$this->result->setMetadata($id, $keyMetadata, $d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,42 +36,26 @@ interface OperationInterface
|
||||
{
|
||||
/**
|
||||
* Returns domains affected by operation.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDomains();
|
||||
public function getDomains(): array;
|
||||
|
||||
/**
|
||||
* Returns all valid messages ('all') after operation.
|
||||
*
|
||||
* @param string $domain
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getMessages($domain);
|
||||
public function getMessages(string $domain): array;
|
||||
|
||||
/**
|
||||
* Returns new messages ('new') after operation.
|
||||
*
|
||||
* @param string $domain
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getNewMessages($domain);
|
||||
public function getNewMessages(string $domain): array;
|
||||
|
||||
/**
|
||||
* Returns obsolete messages ('obsolete') after operation.
|
||||
*
|
||||
* @param string $domain
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getObsoleteMessages($domain);
|
||||
public function getObsoleteMessages(string $domain): array;
|
||||
|
||||
/**
|
||||
* Returns resulting catalogue ('result').
|
||||
*
|
||||
* @return MessageCatalogueInterface
|
||||
*/
|
||||
public function getResult();
|
||||
public function getResult(): MessageCatalogueInterface;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
|
||||
namespace Symfony\Component\Translation\Catalogue;
|
||||
|
||||
use Symfony\Component\Translation\MessageCatalogueInterface;
|
||||
|
||||
/**
|
||||
* Target operation between two catalogues:
|
||||
* intersection = source ∩ target = {x: x ∈ source ∧ x ∈ target}
|
||||
@@ -26,29 +28,31 @@ class TargetOperation extends AbstractOperation
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function processDomain($domain)
|
||||
protected function processDomain(string $domain)
|
||||
{
|
||||
$this->messages[$domain] = array(
|
||||
'all' => array(),
|
||||
'new' => array(),
|
||||
'obsolete' => array(),
|
||||
);
|
||||
$this->messages[$domain] = [
|
||||
'all' => [],
|
||||
'new' => [],
|
||||
'obsolete' => [],
|
||||
];
|
||||
$intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX;
|
||||
|
||||
// For 'all' messages, the code can't be simplified as ``$this->messages[$domain]['all'] = $target->all($domain);``,
|
||||
// because doing so will drop messages like {x: x ∈ source ∧ x ∉ target.all ∧ x ∈ target.fallback}
|
||||
//
|
||||
// For 'new' messages, the code can't be simplied as ``array_diff_assoc($this->target->all($domain), $this->source->all($domain));``
|
||||
// For 'new' messages, the code can't be simplified as ``array_diff_assoc($this->target->all($domain), $this->source->all($domain));``
|
||||
// because doing so will not exclude messages like {x: x ∈ target ∧ x ∉ source.all ∧ x ∈ source.fallback}
|
||||
//
|
||||
// For 'obsolete' messages, the code can't be simplifed as ``array_diff_assoc($this->source->all($domain), $this->target->all($domain))``
|
||||
// For 'obsolete' messages, the code can't be simplified as ``array_diff_assoc($this->source->all($domain), $this->target->all($domain))``
|
||||
// because doing so will not exclude messages like {x: x ∈ source ∧ x ∉ target.all ∧ x ∈ target.fallback}
|
||||
|
||||
foreach ($this->source->all($domain) as $id => $message) {
|
||||
if ($this->target->has($id, $domain)) {
|
||||
$this->messages[$domain]['all'][$id] = $message;
|
||||
$this->result->add(array($id => $message), $domain);
|
||||
if (null !== $keyMetadata = $this->source->getMetadata($id, $domain)) {
|
||||
$this->result->setMetadata($id, $keyMetadata, $domain);
|
||||
$d = $this->source->defines($id, $intlDomain) ? $intlDomain : $domain;
|
||||
$this->result->add([$id => $message], $d);
|
||||
if (null !== $keyMetadata = $this->source->getMetadata($id, $d)) {
|
||||
$this->result->setMetadata($id, $keyMetadata, $d);
|
||||
}
|
||||
} else {
|
||||
$this->messages[$domain]['obsolete'][$id] = $message;
|
||||
@@ -59,9 +63,10 @@ class TargetOperation extends AbstractOperation
|
||||
if (!$this->source->has($id, $domain)) {
|
||||
$this->messages[$domain]['all'][$id] = $message;
|
||||
$this->messages[$domain]['new'][$id] = $message;
|
||||
$this->result->add(array($id => $message), $domain);
|
||||
if (null !== $keyMetadata = $this->target->getMetadata($id, $domain)) {
|
||||
$this->result->setMetadata($id, $keyMetadata, $domain);
|
||||
$d = $this->target->defines($id, $intlDomain) ? $intlDomain : $domain;
|
||||
$this->result->add([$id => $message], $d);
|
||||
if (null !== $keyMetadata = $this->target->getMetadata($id, $d)) {
|
||||
$this->result->setMetadata($id, $keyMetadata, $d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
187
vendor/symfony/translation/Command/TranslationPullCommand.php
vendored
Normal file
187
vendor/symfony/translation/Command/TranslationPullCommand.php
vendored
Normal file
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Command;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Translation\Catalogue\TargetOperation;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\Provider\TranslationProviderCollection;
|
||||
use Symfony\Component\Translation\Reader\TranslationReaderInterface;
|
||||
use Symfony\Component\Translation\Writer\TranslationWriterInterface;
|
||||
|
||||
/**
|
||||
* @author Mathieu Santostefano <msantostefano@protonmail.com>
|
||||
*/
|
||||
#[AsCommand(name: 'translation:pull', description: 'Pull translations from a given provider.')]
|
||||
final class TranslationPullCommand extends Command
|
||||
{
|
||||
use TranslationTrait;
|
||||
|
||||
private $providerCollection;
|
||||
private $writer;
|
||||
private $reader;
|
||||
private string $defaultLocale;
|
||||
private array $transPaths;
|
||||
private array $enabledLocales;
|
||||
|
||||
public function __construct(TranslationProviderCollection $providerCollection, TranslationWriterInterface $writer, TranslationReaderInterface $reader, string $defaultLocale, array $transPaths = [], array $enabledLocales = [])
|
||||
{
|
||||
$this->providerCollection = $providerCollection;
|
||||
$this->writer = $writer;
|
||||
$this->reader = $reader;
|
||||
$this->defaultLocale = $defaultLocale;
|
||||
$this->transPaths = $transPaths;
|
||||
$this->enabledLocales = $enabledLocales;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
if ($input->mustSuggestArgumentValuesFor('provider')) {
|
||||
$suggestions->suggestValues($this->providerCollection->keys());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($input->mustSuggestOptionValuesFor('domains')) {
|
||||
$provider = $this->providerCollection->get($input->getArgument('provider'));
|
||||
|
||||
if ($provider && method_exists($provider, 'getDomains')) {
|
||||
$domains = $provider->getDomains();
|
||||
$suggestions->suggestValues($domains);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($input->mustSuggestOptionValuesFor('locales')) {
|
||||
$suggestions->suggestValues($this->enabledLocales);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($input->mustSuggestOptionValuesFor('format')) {
|
||||
$suggestions->suggestValues(['php', 'xlf', 'xlf12', 'xlf20', 'po', 'mo', 'yml', 'yaml', 'ts', 'csv', 'json', 'ini', 'res']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$keys = $this->providerCollection->keys();
|
||||
$defaultProvider = 1 === \count($keys) ? $keys[0] : null;
|
||||
|
||||
$this
|
||||
->setDefinition([
|
||||
new InputArgument('provider', null !== $defaultProvider ? InputArgument::OPTIONAL : InputArgument::REQUIRED, 'The provider to pull translations from.', $defaultProvider),
|
||||
new InputOption('force', null, InputOption::VALUE_NONE, 'Override existing translations with provider ones (it will delete not synchronized messages).'),
|
||||
new InputOption('intl-icu', null, InputOption::VALUE_NONE, 'Associated to --force option, it will write messages in "%domain%+intl-icu.%locale%.xlf" files.'),
|
||||
new InputOption('domains', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the domains to pull.'),
|
||||
new InputOption('locales', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the locales to pull.'),
|
||||
new InputOption('format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format.', 'xlf12'),
|
||||
])
|
||||
->setHelp(<<<'EOF'
|
||||
The <info>%command.name%</> command pulls translations from the given provider. Only
|
||||
new translations are pulled, existing ones are not overwritten.
|
||||
|
||||
You can overwrite existing translations (and remove the missing ones on local side) by using the <comment>--force</> flag:
|
||||
|
||||
<info>php %command.full_name% --force provider</>
|
||||
|
||||
Full example:
|
||||
|
||||
<info>php %command.full_name% provider --force --domains=messages --domains=validators --locales=en</>
|
||||
|
||||
This command pulls all translations associated with the <comment>messages</> and <comment>validators</> domains for the <comment>en</> locale.
|
||||
Local translations for the specified domains and locale are deleted if they're not present on the provider and overwritten if it's the case.
|
||||
Local translations for others domains and locales are ignored.
|
||||
EOF
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$provider = $this->providerCollection->get($input->getArgument('provider'));
|
||||
$force = $input->getOption('force');
|
||||
$intlIcu = $input->getOption('intl-icu');
|
||||
$locales = $input->getOption('locales') ?: $this->enabledLocales;
|
||||
$domains = $input->getOption('domains');
|
||||
$format = $input->getOption('format');
|
||||
$xliffVersion = '1.2';
|
||||
|
||||
if ($intlIcu && !$force) {
|
||||
$io->note('--intl-icu option only has an effect when used with --force. Here, it will be ignored.');
|
||||
}
|
||||
|
||||
switch ($format) {
|
||||
case 'xlf20': $xliffVersion = '2.0';
|
||||
// no break
|
||||
case 'xlf12': $format = 'xlf';
|
||||
}
|
||||
|
||||
$writeOptions = [
|
||||
'path' => end($this->transPaths),
|
||||
'xliff_version' => $xliffVersion,
|
||||
'default_locale' => $this->defaultLocale,
|
||||
];
|
||||
|
||||
if (!$domains) {
|
||||
$domains = $provider->getDomains();
|
||||
}
|
||||
|
||||
$providerTranslations = $provider->read($domains, $locales);
|
||||
|
||||
if ($force) {
|
||||
foreach ($providerTranslations->getCatalogues() as $catalogue) {
|
||||
$operation = new TargetOperation(new MessageCatalogue($catalogue->getLocale()), $catalogue);
|
||||
if ($intlIcu) {
|
||||
$operation->moveMessagesToIntlDomainsIfPossible();
|
||||
}
|
||||
$this->writer->write($operation->getResult(), $format, $writeOptions);
|
||||
}
|
||||
|
||||
$io->success(sprintf('Local translations has been updated from "%s" (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains)));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$localTranslations = $this->readLocalTranslations($locales, $domains, $this->transPaths);
|
||||
|
||||
// Append pulled translations to local ones.
|
||||
$localTranslations->addBag($providerTranslations->diff($localTranslations));
|
||||
|
||||
foreach ($localTranslations->getCatalogues() as $catalogue) {
|
||||
$this->writer->write($catalogue, $format, $writeOptions);
|
||||
}
|
||||
|
||||
$io->success(sprintf('New translations from "%s" has been written locally (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains)));
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
188
vendor/symfony/translation/Command/TranslationPushCommand.php
vendored
Normal file
188
vendor/symfony/translation/Command/TranslationPushCommand.php
vendored
Normal file
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Command;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Translation\Provider\FilteringProvider;
|
||||
use Symfony\Component\Translation\Provider\TranslationProviderCollection;
|
||||
use Symfony\Component\Translation\Reader\TranslationReaderInterface;
|
||||
use Symfony\Component\Translation\TranslatorBag;
|
||||
|
||||
/**
|
||||
* @author Mathieu Santostefano <msantostefano@protonmail.com>
|
||||
*/
|
||||
#[AsCommand(name: 'translation:push', description: 'Push translations to a given provider.')]
|
||||
final class TranslationPushCommand extends Command
|
||||
{
|
||||
use TranslationTrait;
|
||||
|
||||
private $providers;
|
||||
private $reader;
|
||||
private array $transPaths;
|
||||
private array $enabledLocales;
|
||||
|
||||
public function __construct(TranslationProviderCollection $providers, TranslationReaderInterface $reader, array $transPaths = [], array $enabledLocales = [])
|
||||
{
|
||||
$this->providers = $providers;
|
||||
$this->reader = $reader;
|
||||
$this->transPaths = $transPaths;
|
||||
$this->enabledLocales = $enabledLocales;
|
||||
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
if ($input->mustSuggestArgumentValuesFor('provider')) {
|
||||
$suggestions->suggestValues($this->providers->keys());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($input->mustSuggestOptionValuesFor('domains')) {
|
||||
$provider = $this->providers->get($input->getArgument('provider'));
|
||||
|
||||
if ($provider && method_exists($provider, 'getDomains')) {
|
||||
$domains = $provider->getDomains();
|
||||
$suggestions->suggestValues($domains);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($input->mustSuggestOptionValuesFor('locales')) {
|
||||
$suggestions->suggestValues($this->enabledLocales);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$keys = $this->providers->keys();
|
||||
$defaultProvider = 1 === \count($keys) ? $keys[0] : null;
|
||||
|
||||
$this
|
||||
->setDefinition([
|
||||
new InputArgument('provider', null !== $defaultProvider ? InputArgument::OPTIONAL : InputArgument::REQUIRED, 'The provider to push translations to.', $defaultProvider),
|
||||
new InputOption('force', null, InputOption::VALUE_NONE, 'Override existing translations with local ones (it will delete not synchronized messages).'),
|
||||
new InputOption('delete-missing', null, InputOption::VALUE_NONE, 'Delete translations available on provider but not locally.'),
|
||||
new InputOption('domains', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the domains to push.'),
|
||||
new InputOption('locales', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the locales to push.', $this->enabledLocales),
|
||||
])
|
||||
->setHelp(<<<'EOF'
|
||||
The <info>%command.name%</> command pushes translations to the given provider. Only new
|
||||
translations are pushed, existing ones are not overwritten.
|
||||
|
||||
You can overwrite existing translations by using the <comment>--force</> flag:
|
||||
|
||||
<info>php %command.full_name% --force provider</>
|
||||
|
||||
You can delete provider translations which are not present locally by using the <comment>--delete-missing</> flag:
|
||||
|
||||
<info>php %command.full_name% --delete-missing provider</>
|
||||
|
||||
Full example:
|
||||
|
||||
<info>php %command.full_name% provider --force --delete-missing --domains=messages --domains=validators --locales=en</>
|
||||
|
||||
This command pushes all translations associated with the <comment>messages</> and <comment>validators</> domains for the <comment>en</> locale.
|
||||
Provider translations for the specified domains and locale are deleted if they're not present locally and overwritten if it's the case.
|
||||
Provider translations for others domains and locales are ignored.
|
||||
EOF
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$provider = $this->providers->get($input->getArgument('provider'));
|
||||
|
||||
if (!$this->enabledLocales) {
|
||||
throw new InvalidArgumentException(sprintf('You must define "framework.translator.enabled_locales" or "framework.translator.providers.%s.locales" config key in order to work with translation providers.', parse_url($provider, \PHP_URL_SCHEME)));
|
||||
}
|
||||
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$domains = $input->getOption('domains');
|
||||
$locales = $input->getOption('locales');
|
||||
$force = $input->getOption('force');
|
||||
$deleteMissing = $input->getOption('delete-missing');
|
||||
|
||||
if (!$domains && $provider instanceof FilteringProvider) {
|
||||
$domains = $provider->getDomains();
|
||||
}
|
||||
|
||||
// Reading local translations must be done after retrieving the domains from the provider
|
||||
// in order to manage only translations from configured domains
|
||||
$localTranslations = $this->readLocalTranslations($locales, $domains, $this->transPaths);
|
||||
|
||||
if (!$domains) {
|
||||
$domains = $this->getDomainsFromTranslatorBag($localTranslations);
|
||||
}
|
||||
|
||||
if (!$deleteMissing && $force) {
|
||||
$provider->write($localTranslations);
|
||||
|
||||
$io->success(sprintf('All local translations has been sent to "%s" (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains)));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$providerTranslations = $provider->read($domains, $locales);
|
||||
|
||||
if ($deleteMissing) {
|
||||
$provider->delete($providerTranslations->diff($localTranslations));
|
||||
|
||||
$io->success(sprintf('Missing translations on "%s" has been deleted (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains)));
|
||||
|
||||
// Read provider translations again, after missing translations deletion,
|
||||
// to avoid push freshly deleted translations.
|
||||
$providerTranslations = $provider->read($domains, $locales);
|
||||
}
|
||||
|
||||
$translationsToWrite = $localTranslations->diff($providerTranslations);
|
||||
|
||||
if ($force) {
|
||||
$translationsToWrite->addBag($localTranslations->intersect($providerTranslations));
|
||||
}
|
||||
|
||||
$provider->write($translationsToWrite);
|
||||
|
||||
$io->success(sprintf('%s local translations has been sent to "%s" (for "%s" locale(s), and "%s" domain(s)).', $force ? 'All' : 'New', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains)));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function getDomainsFromTranslatorBag(TranslatorBag $translatorBag): array
|
||||
{
|
||||
$domains = [];
|
||||
|
||||
foreach ($translatorBag->getCatalogues() as $catalogue) {
|
||||
$domains += $catalogue->getDomains();
|
||||
}
|
||||
|
||||
return array_unique($domains);
|
||||
}
|
||||
}
|
||||
77
vendor/symfony/translation/Command/TranslationTrait.php
vendored
Normal file
77
vendor/symfony/translation/Command/TranslationTrait.php
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Command;
|
||||
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\MessageCatalogueInterface;
|
||||
use Symfony\Component\Translation\TranslatorBag;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
trait TranslationTrait
|
||||
{
|
||||
private function readLocalTranslations(array $locales, array $domains, array $transPaths): TranslatorBag
|
||||
{
|
||||
$bag = new TranslatorBag();
|
||||
|
||||
foreach ($locales as $locale) {
|
||||
$catalogue = new MessageCatalogue($locale);
|
||||
foreach ($transPaths as $path) {
|
||||
$this->reader->read($path, $catalogue);
|
||||
}
|
||||
|
||||
if ($domains) {
|
||||
foreach ($domains as $domain) {
|
||||
$bag->addCatalogue($this->filterCatalogue($catalogue, $domain));
|
||||
}
|
||||
} else {
|
||||
$bag->addCatalogue($catalogue);
|
||||
}
|
||||
}
|
||||
|
||||
return $bag;
|
||||
}
|
||||
|
||||
private function filterCatalogue(MessageCatalogue $catalogue, string $domain): MessageCatalogue
|
||||
{
|
||||
$filteredCatalogue = new MessageCatalogue($catalogue->getLocale());
|
||||
|
||||
// extract intl-icu messages only
|
||||
$intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX;
|
||||
if ($intlMessages = $catalogue->all($intlDomain)) {
|
||||
$filteredCatalogue->add($intlMessages, $intlDomain);
|
||||
}
|
||||
|
||||
// extract all messages and subtract intl-icu messages
|
||||
if ($messages = array_diff($catalogue->all($domain), $intlMessages)) {
|
||||
$filteredCatalogue->add($messages, $domain);
|
||||
}
|
||||
foreach ($catalogue->getResources() as $resource) {
|
||||
$filteredCatalogue->addResource($resource);
|
||||
}
|
||||
|
||||
if ($metadata = $catalogue->getMetadata('', $intlDomain)) {
|
||||
foreach ($metadata as $k => $v) {
|
||||
$filteredCatalogue->setMetadata($k, $v, $intlDomain);
|
||||
}
|
||||
}
|
||||
|
||||
if ($metadata = $catalogue->getMetadata('', $domain)) {
|
||||
foreach ($metadata as $k => $v) {
|
||||
$filteredCatalogue->setMetadata($k, $v, $domain);
|
||||
}
|
||||
}
|
||||
|
||||
return $filteredCatalogue;
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,19 @@
|
||||
|
||||
namespace Symfony\Component\Translation\Command;
|
||||
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\CI\GithubActionReporter;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Completion\CompletionInput;
|
||||
use Symfony\Component\Console\Completion\CompletionSuggestions;
|
||||
use Symfony\Component\Console\Exception\RuntimeException;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Translation\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Translation\Util\XliffUtils;
|
||||
|
||||
/**
|
||||
* Validates XLIFF files syntax and outputs encountered errors.
|
||||
@@ -24,19 +32,22 @@ use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
* @author Robin Chalas <robin.chalas@gmail.com>
|
||||
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
|
||||
*/
|
||||
#[AsCommand(name: 'lint:xliff', description: 'Lint an XLIFF file and outputs encountered errors')]
|
||||
class XliffLintCommand extends Command
|
||||
{
|
||||
private $format;
|
||||
private $displayCorrectFiles;
|
||||
private $directoryIteratorProvider;
|
||||
private $isReadableProvider;
|
||||
private string $format;
|
||||
private bool $displayCorrectFiles;
|
||||
private ?\Closure $directoryIteratorProvider;
|
||||
private ?\Closure $isReadableProvider;
|
||||
private bool $requireStrictFileNames;
|
||||
|
||||
public function __construct($name = null, $directoryIteratorProvider = null, $isReadableProvider = null)
|
||||
public function __construct(string $name = null, callable $directoryIteratorProvider = null, callable $isReadableProvider = null, bool $requireStrictFileNames = true)
|
||||
{
|
||||
parent::__construct($name);
|
||||
|
||||
$this->directoryIteratorProvider = $directoryIteratorProvider;
|
||||
$this->isReadableProvider = $isReadableProvider;
|
||||
$this->directoryIteratorProvider = null === $directoryIteratorProvider || $directoryIteratorProvider instanceof \Closure ? $directoryIteratorProvider : \Closure::fromCallable($directoryIteratorProvider);
|
||||
$this->isReadableProvider = null === $isReadableProvider || $isReadableProvider instanceof \Closure ? $isReadableProvider : \Closure::fromCallable($isReadableProvider);
|
||||
$this->requireStrictFileNames = $requireStrictFileNames;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,17 +56,15 @@ class XliffLintCommand extends Command
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('lint:xliff')
|
||||
->setDescription('Lints a XLIFF file and outputs encountered errors')
|
||||
->addArgument('filename', null, 'A file or a directory or STDIN')
|
||||
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt')
|
||||
->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN')
|
||||
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format')
|
||||
->setHelp(<<<EOF
|
||||
The <info>%command.name%</info> command lints a XLIFF file and outputs to STDOUT
|
||||
The <info>%command.name%</info> command lints an XLIFF file and outputs to STDOUT
|
||||
the first encountered syntax error.
|
||||
|
||||
You can validates XLIFF contents passed from STDIN:
|
||||
|
||||
<info>cat filename | php %command.full_name%</info>
|
||||
<info>cat filename | php %command.full_name% -</info>
|
||||
|
||||
You can also validate the syntax of a file:
|
||||
|
||||
@@ -71,60 +80,78 @@ EOF
|
||||
;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$filename = $input->getArgument('filename');
|
||||
$this->format = $input->getOption('format');
|
||||
$filenames = (array) $input->getArgument('filename');
|
||||
$this->format = $input->getOption('format') ?? (GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt');
|
||||
$this->displayCorrectFiles = $output->isVerbose();
|
||||
|
||||
if (!$filename) {
|
||||
if (!$stdin = $this->getStdin()) {
|
||||
throw new \RuntimeException('Please provide a filename or pipe file content to STDIN.');
|
||||
if (['-'] === $filenames) {
|
||||
return $this->display($io, [$this->validate(file_get_contents('php://stdin'))]);
|
||||
}
|
||||
|
||||
if (!$filenames) {
|
||||
throw new RuntimeException('Please provide a filename or pipe file content to STDIN.');
|
||||
}
|
||||
|
||||
$filesInfo = [];
|
||||
foreach ($filenames as $filename) {
|
||||
if (!$this->isReadable($filename)) {
|
||||
throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename));
|
||||
}
|
||||
|
||||
return $this->display($io, array($this->validate($stdin)));
|
||||
}
|
||||
|
||||
if (!$this->isReadable($filename)) {
|
||||
throw new \RuntimeException(sprintf('File or directory "%s" is not readable.', $filename));
|
||||
}
|
||||
|
||||
$filesInfo = array();
|
||||
foreach ($this->getFiles($filename) as $file) {
|
||||
$filesInfo[] = $this->validate(file_get_contents($file), $file);
|
||||
foreach ($this->getFiles($filename) as $file) {
|
||||
$filesInfo[] = $this->validate(file_get_contents($file), $file);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->display($io, $filesInfo);
|
||||
}
|
||||
|
||||
private function validate($content, $file = null)
|
||||
private function validate(string $content, string $file = null): array
|
||||
{
|
||||
$errors = [];
|
||||
|
||||
// Avoid: Warning DOMDocument::loadXML(): Empty string supplied as input
|
||||
if ('' === trim($content)) {
|
||||
return array('file' => $file, 'valid' => true);
|
||||
return ['file' => $file, 'valid' => true];
|
||||
}
|
||||
|
||||
libxml_use_internal_errors(true);
|
||||
$internal = libxml_use_internal_errors(true);
|
||||
|
||||
$document = new \DOMDocument();
|
||||
$document->loadXML($content);
|
||||
if ($document->schemaValidate(__DIR__.'/../Resources/schemas/xliff-core-1.2-strict.xsd')) {
|
||||
return array('file' => $file, 'valid' => true);
|
||||
|
||||
if (null !== $targetLanguage = $this->getTargetLanguageFromFile($document)) {
|
||||
$normalizedLocalePattern = sprintf('(%s|%s)', preg_quote($targetLanguage, '/'), preg_quote(str_replace('-', '_', $targetLanguage), '/'));
|
||||
// strict file names require translation files to be named '____.locale.xlf'
|
||||
// otherwise, both '____.locale.xlf' and 'locale.____.xlf' are allowed
|
||||
// also, the regexp matching must be case-insensitive, as defined for 'target-language' values
|
||||
// http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html#target-language
|
||||
$expectedFilenamePattern = $this->requireStrictFileNames ? sprintf('/^.*\.(?i:%s)\.(?:xlf|xliff)/', $normalizedLocalePattern) : sprintf('/^(?:.*\.(?i:%s)|(?i:%s)\..*)\.(?:xlf|xliff)/', $normalizedLocalePattern, $normalizedLocalePattern);
|
||||
|
||||
if (0 === preg_match($expectedFilenamePattern, basename($file))) {
|
||||
$errors[] = [
|
||||
'line' => -1,
|
||||
'column' => -1,
|
||||
'message' => sprintf('There is a mismatch between the language included in the file name ("%s") and the "%s" value used in the "target-language" attribute of the file.', basename($file), $targetLanguage),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$errorMessages = array_map(function ($error) {
|
||||
return array(
|
||||
'line' => $error->line,
|
||||
'column' => $error->column,
|
||||
'message' => trim($error->message),
|
||||
);
|
||||
}, libxml_get_errors());
|
||||
foreach (XliffUtils::validateSchema($document) as $xmlError) {
|
||||
$errors[] = [
|
||||
'line' => $xmlError['line'],
|
||||
'column' => $xmlError['column'],
|
||||
'message' => $xmlError['message'],
|
||||
];
|
||||
}
|
||||
|
||||
libxml_clear_errors();
|
||||
libxml_use_internal_errors(false);
|
||||
libxml_use_internal_errors($internal);
|
||||
|
||||
return array('file' => $file, 'valid' => false, 'messages' => $errorMessages);
|
||||
return ['file' => $file, 'valid' => 0 === \count($errors), 'messages' => $errors];
|
||||
}
|
||||
|
||||
private function display(SymfonyStyle $io, array $files)
|
||||
@@ -134,15 +161,18 @@ EOF
|
||||
return $this->displayTxt($io, $files);
|
||||
case 'json':
|
||||
return $this->displayJson($io, $files);
|
||||
case 'github':
|
||||
return $this->displayTxt($io, $files, true);
|
||||
default:
|
||||
throw new \InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format));
|
||||
throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format));
|
||||
}
|
||||
}
|
||||
|
||||
private function displayTxt(SymfonyStyle $io, array $filesInfo)
|
||||
private function displayTxt(SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false)
|
||||
{
|
||||
$countFiles = count($filesInfo);
|
||||
$countFiles = \count($filesInfo);
|
||||
$erroredFiles = 0;
|
||||
$githubReporter = $errorAsGithubAnnotations ? new GithubActionReporter($io) : null;
|
||||
|
||||
foreach ($filesInfo as $info) {
|
||||
if ($info['valid'] && $this->displayCorrectFiles) {
|
||||
@@ -150,9 +180,15 @@ EOF
|
||||
} elseif (!$info['valid']) {
|
||||
++$erroredFiles;
|
||||
$io->text('<error> ERROR </error>'.($info['file'] ? sprintf(' in %s', $info['file']) : ''));
|
||||
$io->listing(array_map(function ($error) {
|
||||
$io->listing(array_map(function ($error) use ($info, $githubReporter) {
|
||||
// general document errors have a '-1' line number
|
||||
return -1 === $error['line'] ? $error['message'] : sprintf('Line %d, Column %d: %s', $error['line'], $error['column'], $error['message']);
|
||||
$line = -1 === $error['line'] ? null : $error['line'];
|
||||
|
||||
if ($githubReporter) {
|
||||
$githubReporter->error($error['message'], $info['file'], $line, null !== $line ? $error['column'] : null);
|
||||
}
|
||||
|
||||
return null === $line ? $error['message'] : sprintf('Line %d, Column %d: %s', $line, $error['column'], $error['message']);
|
||||
}, $info['messages']));
|
||||
}
|
||||
}
|
||||
@@ -177,12 +213,12 @@ EOF
|
||||
}
|
||||
});
|
||||
|
||||
$io->writeln(json_encode($filesInfo, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
$io->writeln(json_encode($filesInfo, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES));
|
||||
|
||||
return min($errors, 1);
|
||||
}
|
||||
|
||||
private function getFiles($fileOrDirectory)
|
||||
private function getFiles(string $fileOrDirectory)
|
||||
{
|
||||
if (is_file($fileOrDirectory)) {
|
||||
yield new \SplFileInfo($fileOrDirectory);
|
||||
@@ -191,7 +227,7 @@ EOF
|
||||
}
|
||||
|
||||
foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) {
|
||||
if (!in_array($file->getExtension(), array('xlf', 'xliff'))) {
|
||||
if (!\in_array($file->getExtension(), ['xlf', 'xliff'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -199,21 +235,7 @@ EOF
|
||||
}
|
||||
}
|
||||
|
||||
private function getStdin()
|
||||
{
|
||||
if (0 !== ftell(STDIN)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$inputs = '';
|
||||
while (!feof(STDIN)) {
|
||||
$inputs .= fread(STDIN, 1024);
|
||||
}
|
||||
|
||||
return $inputs;
|
||||
}
|
||||
|
||||
private function getDirectoryIterator($directory)
|
||||
private function getDirectoryIterator(string $directory)
|
||||
{
|
||||
$default = function ($directory) {
|
||||
return new \RecursiveIteratorIterator(
|
||||
@@ -223,22 +245,40 @@ EOF
|
||||
};
|
||||
|
||||
if (null !== $this->directoryIteratorProvider) {
|
||||
return call_user_func($this->directoryIteratorProvider, $directory, $default);
|
||||
return ($this->directoryIteratorProvider)($directory, $default);
|
||||
}
|
||||
|
||||
return $default($directory);
|
||||
}
|
||||
|
||||
private function isReadable($fileOrDirectory)
|
||||
private function isReadable(string $fileOrDirectory)
|
||||
{
|
||||
$default = function ($fileOrDirectory) {
|
||||
return is_readable($fileOrDirectory);
|
||||
};
|
||||
|
||||
if (null !== $this->isReadableProvider) {
|
||||
return call_user_func($this->isReadableProvider, $fileOrDirectory, $default);
|
||||
return ($this->isReadableProvider)($fileOrDirectory, $default);
|
||||
}
|
||||
|
||||
return $default($fileOrDirectory);
|
||||
}
|
||||
|
||||
private function getTargetLanguageFromFile(\DOMDocument $xliffContents): ?string
|
||||
{
|
||||
foreach ($xliffContents->getElementsByTagName('file')[0]->attributes ?? [] as $attribute) {
|
||||
if ('target-language' === $attribute->nodeName) {
|
||||
return $attribute->nodeValue;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
|
||||
{
|
||||
if ($input->mustSuggestOptionValuesFor('format')) {
|
||||
$suggestions->suggestValues(['txt', 'json', 'github']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,20 +16,17 @@ use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
|
||||
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
|
||||
use Symfony\Component\Translation\DataCollectorTranslator;
|
||||
use Symfony\Component\VarDumper\Cloner\Data;
|
||||
|
||||
/**
|
||||
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
|
||||
*
|
||||
* @final
|
||||
*/
|
||||
class TranslationDataCollector extends DataCollector implements LateDataCollectorInterface
|
||||
{
|
||||
/**
|
||||
* @var DataCollectorTranslator
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* @param DataCollectorTranslator $translator
|
||||
*/
|
||||
public function __construct(DataCollectorTranslator $translator)
|
||||
{
|
||||
$this->translator = $translator;
|
||||
@@ -42,7 +39,7 @@ class TranslationDataCollector extends DataCollector implements LateDataCollecto
|
||||
{
|
||||
$messages = $this->sanitizeCollectedMessages($this->translator->getCollectedMessages());
|
||||
|
||||
$this->data = $this->computeCount($messages);
|
||||
$this->data += $this->computeCount($messages);
|
||||
$this->data['messages'] = $messages;
|
||||
|
||||
$this->data = $this->cloneVar($this->data);
|
||||
@@ -51,59 +48,70 @@ class TranslationDataCollector extends DataCollector implements LateDataCollecto
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function collect(Request $request, Response $response, \Exception $exception = null)
|
||||
public function collect(Request $request, Response $response, \Throwable $exception = null)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getMessages()
|
||||
{
|
||||
return isset($this->data['messages']) ? $this->data['messages'] : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCountMissings()
|
||||
{
|
||||
return isset($this->data[DataCollectorTranslator::MESSAGE_MISSING]) ? $this->data[DataCollectorTranslator::MESSAGE_MISSING] : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCountFallbacks()
|
||||
{
|
||||
return isset($this->data[DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK]) ? $this->data[DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK] : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCountDefines()
|
||||
{
|
||||
return isset($this->data[DataCollectorTranslator::MESSAGE_DEFINED]) ? $this->data[DataCollectorTranslator::MESSAGE_DEFINED] : 0;
|
||||
$this->data['locale'] = $this->translator->getLocale();
|
||||
$this->data['fallback_locales'] = $this->translator->getFallbackLocales();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName()
|
||||
public function reset()
|
||||
{
|
||||
$this->data = [];
|
||||
}
|
||||
|
||||
public function getMessages(): array|Data
|
||||
{
|
||||
return $this->data['messages'] ?? [];
|
||||
}
|
||||
|
||||
public function getCountMissings(): int
|
||||
{
|
||||
return $this->data[DataCollectorTranslator::MESSAGE_MISSING] ?? 0;
|
||||
}
|
||||
|
||||
public function getCountFallbacks(): int
|
||||
{
|
||||
return $this->data[DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK] ?? 0;
|
||||
}
|
||||
|
||||
public function getCountDefines(): int
|
||||
{
|
||||
return $this->data[DataCollectorTranslator::MESSAGE_DEFINED] ?? 0;
|
||||
}
|
||||
|
||||
public function getLocale()
|
||||
{
|
||||
return !empty($this->data['locale']) ? $this->data['locale'] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function getFallbackLocales()
|
||||
{
|
||||
return (isset($this->data['fallback_locales']) && \count($this->data['fallback_locales']) > 0) ? $this->data['fallback_locales'] : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return 'translation';
|
||||
}
|
||||
|
||||
private function sanitizeCollectedMessages($messages)
|
||||
private function sanitizeCollectedMessages(array $messages)
|
||||
{
|
||||
$result = array();
|
||||
$result = [];
|
||||
foreach ($messages as $key => $message) {
|
||||
$messageId = $message['locale'].$message['domain'].$message['id'];
|
||||
|
||||
if (!isset($result[$messageId])) {
|
||||
$message['count'] = 1;
|
||||
$message['parameters'] = !empty($message['parameters']) ? array($message['parameters']) : array();
|
||||
$message['parameters'] = !empty($message['parameters']) ? [$message['parameters']] : [];
|
||||
$messages[$key]['translation'] = $this->sanitizeString($message['translation']);
|
||||
$result[$messageId] = $message;
|
||||
} else {
|
||||
@@ -120,13 +128,13 @@ class TranslationDataCollector extends DataCollector implements LateDataCollecto
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function computeCount($messages)
|
||||
private function computeCount(array $messages)
|
||||
{
|
||||
$count = array(
|
||||
$count = [
|
||||
DataCollectorTranslator::MESSAGE_DEFINED => 0,
|
||||
DataCollectorTranslator::MESSAGE_MISSING => 0,
|
||||
DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK => 0,
|
||||
);
|
||||
];
|
||||
|
||||
foreach ($messages as $message) {
|
||||
++$count[$message['state']];
|
||||
@@ -135,7 +143,7 @@ class TranslationDataCollector extends DataCollector implements LateDataCollecto
|
||||
return $count;
|
||||
}
|
||||
|
||||
private function sanitizeString($string, $length = 80)
|
||||
private function sanitizeString(string $string, int $length = 80)
|
||||
{
|
||||
$string = trim(preg_replace('/\s+/', ' ', $string));
|
||||
|
||||
@@ -143,7 +151,7 @@ class TranslationDataCollector extends DataCollector implements LateDataCollecto
|
||||
if (mb_strlen($string, $encoding) > $length) {
|
||||
return mb_substr($string, 0, $length - 3, $encoding).'...';
|
||||
}
|
||||
} elseif (strlen($string) > $length) {
|
||||
} elseif (\strlen($string) > $length) {
|
||||
return substr($string, 0, $length - 3).'...';
|
||||
}
|
||||
|
||||
|
||||
@@ -11,34 +11,30 @@
|
||||
|
||||
namespace Symfony\Component\Translation;
|
||||
|
||||
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
|
||||
use Symfony\Component\Translation\Exception\InvalidArgumentException;
|
||||
use Symfony\Contracts\Translation\LocaleAwareInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
|
||||
*/
|
||||
class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInterface
|
||||
class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface, WarmableInterface
|
||||
{
|
||||
const MESSAGE_DEFINED = 0;
|
||||
const MESSAGE_MISSING = 1;
|
||||
const MESSAGE_EQUALS_FALLBACK = 2;
|
||||
public const MESSAGE_DEFINED = 0;
|
||||
public const MESSAGE_MISSING = 1;
|
||||
public const MESSAGE_EQUALS_FALLBACK = 2;
|
||||
|
||||
/**
|
||||
* @var TranslatorInterface|TranslatorBagInterface
|
||||
*/
|
||||
private $translator;
|
||||
private array $messages = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $messages = array();
|
||||
|
||||
/**
|
||||
* @param TranslatorInterface $translator The translator must implement TranslatorBagInterface
|
||||
* @param TranslatorInterface&TranslatorBagInterface&LocaleAwareInterface $translator
|
||||
*/
|
||||
public function __construct(TranslatorInterface $translator)
|
||||
{
|
||||
if (!$translator instanceof TranslatorBagInterface) {
|
||||
throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface and TranslatorBagInterface.', get_class($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;
|
||||
@@ -47,9 +43,9 @@ class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInter
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function trans($id, array $parameters = array(), $domain = null, $locale = null)
|
||||
public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null): string
|
||||
{
|
||||
$trans = $this->translator->trans($id, $parameters, $domain, $locale);
|
||||
$trans = $this->translator->trans($id = (string) $id, $parameters, $domain, $locale);
|
||||
$this->collectMessage($locale, $domain, $id, $trans, $parameters);
|
||||
|
||||
return $trans;
|
||||
@@ -58,18 +54,7 @@ class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInter
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null)
|
||||
{
|
||||
$trans = $this->translator->transChoice($id, $number, $parameters, $domain, $locale);
|
||||
$this->collectMessage($locale, $domain, $id, $trans, $parameters, $number);
|
||||
|
||||
return $trans;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setLocale($locale)
|
||||
public function setLocale(string $locale)
|
||||
{
|
||||
$this->translator->setLocale($locale);
|
||||
}
|
||||
@@ -77,7 +62,7 @@ class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInter
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLocale()
|
||||
public function getLocale(): string
|
||||
{
|
||||
return $this->translator->getLocale();
|
||||
}
|
||||
@@ -85,58 +70,67 @@ class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInter
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCatalogue($locale = null)
|
||||
public function getCatalogue(string $locale = null): MessageCatalogueInterface
|
||||
{
|
||||
return $this->translator->getCatalogue($locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the fallback locales.
|
||||
*
|
||||
* @return array $locales The fallback locales
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFallbackLocales()
|
||||
public function getCatalogues(): array
|
||||
{
|
||||
return $this->translator->getCatalogues();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function warmUp(string $cacheDir): array
|
||||
{
|
||||
if ($this->translator instanceof WarmableInterface) {
|
||||
return (array) $this->translator->warmUp($cacheDir);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the fallback locales.
|
||||
*/
|
||||
public function getFallbackLocales(): array
|
||||
{
|
||||
if ($this->translator instanceof Translator || method_exists($this->translator, 'getFallbackLocales')) {
|
||||
return $this->translator->getFallbackLocales();
|
||||
}
|
||||
|
||||
return array();
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes through all unknown calls onto the translator object.
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
public function __call(string $method, array $args)
|
||||
{
|
||||
return call_user_func_array(array($this->translator, $method), $args);
|
||||
return $this->translator->{$method}(...$args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getCollectedMessages()
|
||||
public function getCollectedMessages(): array
|
||||
{
|
||||
return $this->messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $locale
|
||||
* @param string|null $domain
|
||||
* @param string $id
|
||||
* @param string $translation
|
||||
* @param array|null $parameters
|
||||
* @param int|null $number
|
||||
*/
|
||||
private function collectMessage($locale, $domain, $id, $translation, $parameters = array(), $number = null)
|
||||
private function collectMessage(?string $locale, ?string $domain, string $id, string $translation, ?array $parameters = [])
|
||||
{
|
||||
if (null === $domain) {
|
||||
$domain = 'messages';
|
||||
}
|
||||
|
||||
$id = (string) $id;
|
||||
$catalogue = $this->translator->getCatalogue($locale);
|
||||
$locale = $catalogue->getLocale();
|
||||
$fallbackLocale = null;
|
||||
if ($catalogue->defines($id, $domain)) {
|
||||
$state = self::MESSAGE_DEFINED;
|
||||
} elseif ($catalogue->has($id, $domain)) {
|
||||
@@ -145,24 +139,24 @@ class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInter
|
||||
$fallbackCatalogue = $catalogue->getFallbackCatalogue();
|
||||
while ($fallbackCatalogue) {
|
||||
if ($fallbackCatalogue->defines($id, $domain)) {
|
||||
$locale = $fallbackCatalogue->getLocale();
|
||||
$fallbackLocale = $fallbackCatalogue->getLocale();
|
||||
break;
|
||||
}
|
||||
|
||||
$fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue();
|
||||
}
|
||||
} else {
|
||||
$state = self::MESSAGE_MISSING;
|
||||
}
|
||||
|
||||
$this->messages[] = array(
|
||||
$this->messages[] = [
|
||||
'locale' => $locale,
|
||||
'fallbackLocale' => $fallbackLocale,
|
||||
'domain' => $domain,
|
||||
'id' => $id,
|
||||
'translation' => $translation,
|
||||
'parameters' => $parameters,
|
||||
'transChoiceNumber' => $number,
|
||||
'state' => $state,
|
||||
);
|
||||
'transChoiceNumber' => isset($parameters['%count%']) && is_numeric($parameters['%count%']) ? $parameters['%count%'] : null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
35
vendor/symfony/translation/DependencyInjection/TranslationDumperPass.php
vendored
Normal file
35
vendor/symfony/translation/DependencyInjection/TranslationDumperPass.php
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* Adds tagged translation.formatter services to translation writer.
|
||||
*/
|
||||
class TranslationDumperPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if (!$container->hasDefinition('translation.writer')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$definition = $container->getDefinition('translation.writer');
|
||||
|
||||
foreach ($container->findTaggedServiceIds('translation.dumper', true) as $id => $attributes) {
|
||||
$definition->addMethodCall('addDumper', [$attributes[0]['alias'], new Reference($id)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
40
vendor/symfony/translation/DependencyInjection/TranslationExtractorPass.php
vendored
Normal file
40
vendor/symfony/translation/DependencyInjection/TranslationExtractorPass.php
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* Adds tagged translation.extractor services to translation extractor.
|
||||
*/
|
||||
class TranslationExtractorPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if (!$container->hasDefinition('translation.extractor')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$definition = $container->getDefinition('translation.extractor');
|
||||
|
||||
foreach ($container->findTaggedServiceIds('translation.extractor', true) as $id => $attributes) {
|
||||
if (!isset($attributes[0]['alias'])) {
|
||||
throw new RuntimeException(sprintf('The alias for the tag "translation.extractor" of service "%s" must be set.', $id));
|
||||
}
|
||||
|
||||
$definition->addMethodCall('addExtractor', [$attributes[0]['alias'], new Reference($id)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
74
vendor/symfony/translation/DependencyInjection/TranslatorPass.php
vendored
Normal file
74
vendor/symfony/translation/DependencyInjection/TranslatorPass.php
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
class TranslatorPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if (!$container->hasDefinition('translator.default')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$loaders = [];
|
||||
$loaderRefs = [];
|
||||
foreach ($container->findTaggedServiceIds('translation.loader', true) as $id => $attributes) {
|
||||
$loaderRefs[$id] = new Reference($id);
|
||||
$loaders[$id][] = $attributes[0]['alias'];
|
||||
if (isset($attributes[0]['legacy-alias'])) {
|
||||
$loaders[$id][] = $attributes[0]['legacy-alias'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($container->hasDefinition('translation.reader')) {
|
||||
$definition = $container->getDefinition('translation.reader');
|
||||
foreach ($loaders as $id => $formats) {
|
||||
foreach ($formats as $format) {
|
||||
$definition->addMethodCall('addLoader', [$format, $loaderRefs[$id]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$container
|
||||
->findDefinition('translator.default')
|
||||
->replaceArgument(0, ServiceLocatorTagPass::register($container, $loaderRefs))
|
||||
->replaceArgument(3, $loaders)
|
||||
;
|
||||
|
||||
if (!$container->hasParameter('twig.default_path')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$paths = array_keys($container->getDefinition('twig.template_iterator')->getArgument(1));
|
||||
if ($container->hasDefinition('console.command.translation_debug')) {
|
||||
$definition = $container->getDefinition('console.command.translation_debug');
|
||||
$definition->replaceArgument(4, $container->getParameter('twig.default_path'));
|
||||
|
||||
if (\count($definition->getArguments()) > 6) {
|
||||
$definition->replaceArgument(6, $paths);
|
||||
}
|
||||
}
|
||||
if ($container->hasDefinition('console.command.translation_extract')) {
|
||||
$definition = $container->getDefinition('console.command.translation_extract');
|
||||
$definition->replaceArgument(5, $container->getParameter('twig.default_path'));
|
||||
|
||||
if (\count($definition->getArguments()) > 7) {
|
||||
$definition->replaceArgument(7, $paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
147
vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php
vendored
Normal file
147
vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\AbstractRecursivePass;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\DependencyInjection\ServiceLocator;
|
||||
|
||||
/**
|
||||
* @author Yonel Ceruto <yonelceruto@gmail.com>
|
||||
*/
|
||||
class TranslatorPathsPass extends AbstractRecursivePass
|
||||
{
|
||||
private int $level = 0;
|
||||
|
||||
/**
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
private array $paths = [];
|
||||
|
||||
/**
|
||||
* @var array<int, Definition>
|
||||
*/
|
||||
private array $definitions = [];
|
||||
|
||||
/**
|
||||
* @var array<string, array<string, bool>>
|
||||
*/
|
||||
private array $controllers = [];
|
||||
|
||||
public function process(ContainerBuilder $container)
|
||||
{
|
||||
if (!$container->hasDefinition('translator')) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->findControllerArguments($container) as $controller => $argument) {
|
||||
$id = substr($controller, 0, strpos($controller, ':') ?: \strlen($controller));
|
||||
if ($container->hasDefinition($id)) {
|
||||
[$locatorRef] = $argument->getValues();
|
||||
$this->controllers[(string) $locatorRef][$container->getDefinition($id)->getClass()] = true;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
parent::process($container);
|
||||
|
||||
$paths = [];
|
||||
foreach ($this->paths as $class => $_) {
|
||||
if (($r = $container->getReflectionClass($class)) && !$r->isInterface()) {
|
||||
$paths[] = $r->getFileName();
|
||||
foreach ($r->getTraits() as $trait) {
|
||||
$paths[] = $trait->getFileName();
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($paths) {
|
||||
if ($container->hasDefinition('console.command.translation_debug')) {
|
||||
$definition = $container->getDefinition('console.command.translation_debug');
|
||||
$definition->replaceArgument(6, array_merge($definition->getArgument(6), $paths));
|
||||
}
|
||||
if ($container->hasDefinition('console.command.translation_extract')) {
|
||||
$definition = $container->getDefinition('console.command.translation_extract');
|
||||
$definition->replaceArgument(7, array_merge($definition->getArgument(7), $paths));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
$this->level = 0;
|
||||
$this->paths = [];
|
||||
$this->definitions = [];
|
||||
}
|
||||
}
|
||||
|
||||
protected function processValue(mixed $value, bool $isRoot = false): mixed
|
||||
{
|
||||
if ($value instanceof Reference) {
|
||||
if ('translator' === (string) $value) {
|
||||
for ($i = $this->level - 1; $i >= 0; --$i) {
|
||||
$class = $this->definitions[$i]->getClass();
|
||||
|
||||
if (ServiceLocator::class === $class) {
|
||||
if (!isset($this->controllers[$this->currentId])) {
|
||||
continue;
|
||||
}
|
||||
foreach ($this->controllers[$this->currentId] as $class => $_) {
|
||||
$this->paths[$class] = true;
|
||||
}
|
||||
} else {
|
||||
$this->paths[$class] = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ($value instanceof Definition) {
|
||||
$this->definitions[$this->level++] = $value;
|
||||
$value = parent::processValue($value, $isRoot);
|
||||
unset($this->definitions[--$this->level]);
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
return parent::processValue($value, $isRoot);
|
||||
}
|
||||
|
||||
private function findControllerArguments(ContainerBuilder $container): array
|
||||
{
|
||||
if ($container->hasDefinition('argument_resolver.service')) {
|
||||
$argument = $container->getDefinition('argument_resolver.service')->getArgument(0);
|
||||
if ($argument instanceof Reference) {
|
||||
$argument = $container->getDefinition($argument);
|
||||
}
|
||||
|
||||
return $argument->getArgument(0);
|
||||
}
|
||||
|
||||
if ($container->hasDefinition('debug.'.'argument_resolver.service')) {
|
||||
$argument = $container->getDefinition('debug.'.'argument_resolver.service')->getArgument(0);
|
||||
if ($argument instanceof Reference) {
|
||||
$argument = $container->getDefinition($argument);
|
||||
}
|
||||
$argument = $argument->getArgument(0);
|
||||
if ($argument instanceof Reference) {
|
||||
$argument = $container->getDefinition($argument);
|
||||
}
|
||||
|
||||
return $argument->getArgument(0);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -20,18 +20,18 @@ use Symfony\Component\Translation\MessageCatalogue;
|
||||
*/
|
||||
class CsvFileDumper extends FileDumper
|
||||
{
|
||||
private $delimiter = ';';
|
||||
private $enclosure = '"';
|
||||
private string $delimiter = ';';
|
||||
private string $enclosure = '"';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array())
|
||||
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
|
||||
{
|
||||
$handle = fopen('php://memory', 'rb+');
|
||||
$handle = fopen('php://memory', 'r+');
|
||||
|
||||
foreach ($messages->all($domain) as $source => $target) {
|
||||
fputcsv($handle, array($source, $target), $this->delimiter, $this->enclosure);
|
||||
fputcsv($handle, [$source, $target], $this->delimiter, $this->enclosure);
|
||||
}
|
||||
|
||||
rewind($handle);
|
||||
@@ -43,11 +43,8 @@ class CsvFileDumper extends FileDumper
|
||||
|
||||
/**
|
||||
* Sets the delimiter and escape character for CSV.
|
||||
*
|
||||
* @param string $delimiter delimiter character
|
||||
* @param string $enclosure enclosure character
|
||||
*/
|
||||
public function setCsvControl($delimiter = ';', $enclosure = '"')
|
||||
public function setCsvControl(string $delimiter = ';', string $enclosure = '"')
|
||||
{
|
||||
$this->delimiter = $delimiter;
|
||||
$this->enclosure = $enclosure;
|
||||
@@ -56,7 +53,7 @@ class CsvFileDumper extends FileDumper
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExtension()
|
||||
protected function getExtension(): string
|
||||
{
|
||||
return 'csv';
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@ interface DumperInterface
|
||||
/**
|
||||
* Dumps the message catalogue.
|
||||
*
|
||||
* @param MessageCatalogue $messages The message catalogue
|
||||
* @param array $options Options that are used by the dumper
|
||||
* @param array $options Options that are used by the dumper
|
||||
*/
|
||||
public function dump(MessageCatalogue $messages, $options = array());
|
||||
public function dump(MessageCatalogue $messages, array $options = []);
|
||||
}
|
||||
|
||||
80
vendor/symfony/translation/Dumper/FileDumper.php
vendored
80
vendor/symfony/translation/Dumper/FileDumper.php
vendored
@@ -11,13 +11,12 @@
|
||||
|
||||
namespace Symfony\Component\Translation\Dumper;
|
||||
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Translation\Exception\RuntimeException;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
|
||||
/**
|
||||
* FileDumper is an implementation of DumperInterface that dump a message catalogue to file(s).
|
||||
* Performs backup of already existing files.
|
||||
*
|
||||
* Options:
|
||||
* - path (mandatory): the directory where the files should be saved
|
||||
@@ -33,94 +32,77 @@ abstract class FileDumper implements DumperInterface
|
||||
*/
|
||||
protected $relativePathTemplate = '%domain%.%locale%.%extension%';
|
||||
|
||||
/**
|
||||
* Make file backup before the dump.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $backup = true;
|
||||
|
||||
/**
|
||||
* Sets the template for the relative paths to files.
|
||||
*
|
||||
* @param string $relativePathTemplate A template for the relative paths to files
|
||||
*/
|
||||
public function setRelativePathTemplate($relativePathTemplate)
|
||||
public function setRelativePathTemplate(string $relativePathTemplate)
|
||||
{
|
||||
$this->relativePathTemplate = $relativePathTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets backup flag.
|
||||
*
|
||||
* @param bool
|
||||
*/
|
||||
public function setBackup($backup)
|
||||
{
|
||||
$this->backup = $backup;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dump(MessageCatalogue $messages, $options = array())
|
||||
public function dump(MessageCatalogue $messages, array $options = [])
|
||||
{
|
||||
if (!array_key_exists('path', $options)) {
|
||||
if (!\array_key_exists('path', $options)) {
|
||||
throw new InvalidArgumentException('The file dumper needs a path option.');
|
||||
}
|
||||
|
||||
// save a file for each domain
|
||||
foreach ($messages->getDomains() as $domain) {
|
||||
// backup
|
||||
$fullpath = $options['path'].'/'.$this->getRelativePath($domain, $messages->getLocale());
|
||||
if (file_exists($fullpath)) {
|
||||
if ($this->backup) {
|
||||
@trigger_error('Creating a backup while dumping a message catalogue is deprecated since version 3.1 and will be removed in 4.0. Use TranslationWriter::disableBackup() to disable the backup.', E_USER_DEPRECATED);
|
||||
copy($fullpath, $fullpath.'~');
|
||||
}
|
||||
} else {
|
||||
$directory = dirname($fullpath);
|
||||
if (!file_exists($fullpath)) {
|
||||
$directory = \dirname($fullpath);
|
||||
if (!file_exists($directory) && !@mkdir($directory, 0777, true)) {
|
||||
throw new RuntimeException(sprintf('Unable to create directory "%s".', $directory));
|
||||
}
|
||||
}
|
||||
// save file
|
||||
|
||||
$intlDomain = $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX;
|
||||
$intlMessages = $messages->all($intlDomain);
|
||||
|
||||
if ($intlMessages) {
|
||||
$intlPath = $options['path'].'/'.$this->getRelativePath($intlDomain, $messages->getLocale());
|
||||
file_put_contents($intlPath, $this->formatCatalogue($messages, $intlDomain, $options));
|
||||
|
||||
$messages->replace([], $intlDomain);
|
||||
|
||||
try {
|
||||
if ($messages->all($domain)) {
|
||||
file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options));
|
||||
}
|
||||
continue;
|
||||
} finally {
|
||||
$messages->replace($intlMessages, $intlDomain);
|
||||
}
|
||||
}
|
||||
|
||||
file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a domain of a message catalogue to its string representation.
|
||||
*
|
||||
* @param MessageCatalogue $messages
|
||||
* @param string $domain
|
||||
* @param array $options
|
||||
*
|
||||
* @return string representation
|
||||
*/
|
||||
abstract public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array());
|
||||
abstract public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string;
|
||||
|
||||
/**
|
||||
* Gets the file extension of the dumper.
|
||||
*
|
||||
* @return string file extension
|
||||
*/
|
||||
abstract protected function getExtension();
|
||||
abstract protected function getExtension(): string;
|
||||
|
||||
/**
|
||||
* Gets the relative file path using the template.
|
||||
*
|
||||
* @param string $domain The domain
|
||||
* @param string $locale The locale
|
||||
*
|
||||
* @return string The relative file path
|
||||
*/
|
||||
private function getRelativePath($domain, $locale)
|
||||
private function getRelativePath(string $domain, string $locale): string
|
||||
{
|
||||
return strtr($this->relativePathTemplate, array(
|
||||
return strtr($this->relativePathTemplate, [
|
||||
'%domain%' => $domain,
|
||||
'%locale%' => $locale,
|
||||
'%extension%' => $this->getExtension(),
|
||||
));
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,12 +28,12 @@ class IcuResFileDumper extends FileDumper
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array())
|
||||
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
|
||||
{
|
||||
$data = $indexes = $resources = '';
|
||||
|
||||
foreach ($messages->all($domain) as $source => $target) {
|
||||
$indexes .= pack('v', strlen($data) + 28);
|
||||
$indexes .= pack('v', \strlen($data) + 28);
|
||||
$data .= $source."\0";
|
||||
}
|
||||
|
||||
@@ -44,19 +44,19 @@ class IcuResFileDumper extends FileDumper
|
||||
foreach ($messages->all($domain) as $source => $target) {
|
||||
$resources .= pack('V', $this->getPosition($data));
|
||||
|
||||
$data .= pack('V', strlen($target))
|
||||
$data .= pack('V', \strlen($target))
|
||||
.mb_convert_encoding($target."\0", 'UTF-16LE', 'UTF-8')
|
||||
.$this->writePadding($data)
|
||||
;
|
||||
;
|
||||
}
|
||||
|
||||
$resOffset = $this->getPosition($data);
|
||||
|
||||
$data .= pack('v', count($messages->all($domain)))
|
||||
$data .= pack('v', \count($messages->all($domain)))
|
||||
.$indexes
|
||||
.$this->writePadding($data)
|
||||
.$resources
|
||||
;
|
||||
;
|
||||
|
||||
$bundleTop = $this->getPosition($data);
|
||||
|
||||
@@ -66,7 +66,7 @@ class IcuResFileDumper extends FileDumper
|
||||
$keyTop, // Index keys top
|
||||
$bundleTop, // Index resources top
|
||||
$bundleTop, // Index bundle top
|
||||
count($messages->all($domain)), // Index max table length
|
||||
\count($messages->all($domain)), // Index max table length
|
||||
0 // Index attributes
|
||||
);
|
||||
|
||||
@@ -82,24 +82,22 @@ class IcuResFileDumper extends FileDumper
|
||||
return $header.$root.$data;
|
||||
}
|
||||
|
||||
private function writePadding($data)
|
||||
private function writePadding(string $data): ?string
|
||||
{
|
||||
$padding = strlen($data) % 4;
|
||||
$padding = \strlen($data) % 4;
|
||||
|
||||
if ($padding) {
|
||||
return str_repeat("\xAA", 4 - $padding);
|
||||
}
|
||||
return $padding ? str_repeat("\xAA", 4 - $padding) : null;
|
||||
}
|
||||
|
||||
private function getPosition($data)
|
||||
private function getPosition(string $data)
|
||||
{
|
||||
return (strlen($data) + 28) / 4;
|
||||
return (\strlen($data) + 28) / 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExtension()
|
||||
protected function getExtension(): string
|
||||
{
|
||||
return 'res';
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class IniFileDumper extends FileDumper
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array())
|
||||
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
|
||||
{
|
||||
$output = '';
|
||||
|
||||
@@ -38,7 +38,7 @@ class IniFileDumper extends FileDumper
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExtension()
|
||||
protected function getExtension(): string
|
||||
{
|
||||
return 'ini';
|
||||
}
|
||||
|
||||
@@ -23,13 +23,9 @@ class JsonFileDumper extends FileDumper
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array())
|
||||
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
|
||||
{
|
||||
if (isset($options['json_encoding'])) {
|
||||
$flags = $options['json_encoding'];
|
||||
} else {
|
||||
$flags = defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0;
|
||||
}
|
||||
$flags = $options['json_encoding'] ?? \JSON_PRETTY_PRINT;
|
||||
|
||||
return json_encode($messages->all($domain), $flags);
|
||||
}
|
||||
@@ -37,7 +33,7 @@ class JsonFileDumper extends FileDumper
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExtension()
|
||||
protected function getExtension(): string
|
||||
{
|
||||
return 'json';
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
|
||||
namespace Symfony\Component\Translation\Dumper;
|
||||
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\Loader\MoFileLoader;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
|
||||
/**
|
||||
* MoFileDumper generates a gettext formatted string representation of a message catalogue.
|
||||
@@ -24,20 +24,20 @@ class MoFileDumper extends FileDumper
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array())
|
||||
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
|
||||
{
|
||||
$sources = $targets = $sourceOffsets = $targetOffsets = '';
|
||||
$offsets = array();
|
||||
$offsets = [];
|
||||
$size = 0;
|
||||
|
||||
foreach ($messages->all($domain) as $source => $target) {
|
||||
$offsets[] = array_map('strlen', array($sources, $source, $targets, $target));
|
||||
$offsets[] = array_map('strlen', [$sources, $source, $targets, $target]);
|
||||
$sources .= "\0".$source;
|
||||
$targets .= "\0".$target;
|
||||
++$size;
|
||||
}
|
||||
|
||||
$header = array(
|
||||
$header = [
|
||||
'magicNumber' => MoFileLoader::MO_LITTLE_ENDIAN_MAGIC,
|
||||
'formatRevision' => 0,
|
||||
'count' => $size,
|
||||
@@ -45,9 +45,9 @@ class MoFileDumper extends FileDumper
|
||||
'offsetTranslated' => MoFileLoader::MO_HEADER_SIZE + (8 * $size),
|
||||
'sizeHashes' => 0,
|
||||
'offsetHashes' => MoFileLoader::MO_HEADER_SIZE + (16 * $size),
|
||||
);
|
||||
];
|
||||
|
||||
$sourcesSize = strlen($sources);
|
||||
$sourcesSize = \strlen($sources);
|
||||
$sourcesStart = $header['offsetHashes'] + 1;
|
||||
|
||||
foreach ($offsets as $offset) {
|
||||
@@ -57,12 +57,12 @@ class MoFileDumper extends FileDumper
|
||||
.$this->writeLong($offset[2] + $sourcesStart + $sourcesSize);
|
||||
}
|
||||
|
||||
$output = implode(array_map(array($this, 'writeLong'), $header))
|
||||
$output = implode('', array_map([$this, 'writeLong'], $header))
|
||||
.$sourceOffsets
|
||||
.$targetOffsets
|
||||
.$sources
|
||||
.$targets
|
||||
;
|
||||
;
|
||||
|
||||
return $output;
|
||||
}
|
||||
@@ -70,12 +70,12 @@ class MoFileDumper extends FileDumper
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExtension()
|
||||
protected function getExtension(): string
|
||||
{
|
||||
return 'mo';
|
||||
}
|
||||
|
||||
private function writeLong($str)
|
||||
private function writeLong(mixed $str): string
|
||||
{
|
||||
return pack('V*', $str);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class PhpFileDumper extends FileDumper
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array())
|
||||
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
|
||||
{
|
||||
return "<?php\n\nreturn ".var_export($messages->all($domain), true).";\n";
|
||||
}
|
||||
@@ -31,7 +31,7 @@ class PhpFileDumper extends FileDumper
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExtension()
|
||||
protected function getExtension(): string
|
||||
{
|
||||
return 'php';
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class PoFileDumper extends FileDumper
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array())
|
||||
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
|
||||
{
|
||||
$output = 'msgid ""'."\n";
|
||||
$output .= 'msgstr ""'."\n";
|
||||
@@ -39,23 +39,99 @@ class PoFileDumper extends FileDumper
|
||||
} else {
|
||||
$newLine = true;
|
||||
}
|
||||
$output .= sprintf('msgid "%s"'."\n", $this->escape($source));
|
||||
$output .= sprintf('msgstr "%s"', $this->escape($target));
|
||||
$metadata = $messages->getMetadata($source, $domain);
|
||||
|
||||
if (isset($metadata['comments'])) {
|
||||
$output .= $this->formatComments($metadata['comments']);
|
||||
}
|
||||
if (isset($metadata['flags'])) {
|
||||
$output .= $this->formatComments(implode(',', (array) $metadata['flags']), ',');
|
||||
}
|
||||
if (isset($metadata['sources'])) {
|
||||
$output .= $this->formatComments(implode(' ', (array) $metadata['sources']), ':');
|
||||
}
|
||||
|
||||
$sourceRules = $this->getStandardRules($source);
|
||||
$targetRules = $this->getStandardRules($target);
|
||||
if (2 == \count($sourceRules) && [] !== $targetRules) {
|
||||
$output .= sprintf('msgid "%s"'."\n", $this->escape($sourceRules[0]));
|
||||
$output .= sprintf('msgid_plural "%s"'."\n", $this->escape($sourceRules[1]));
|
||||
foreach ($targetRules as $i => $targetRule) {
|
||||
$output .= sprintf('msgstr[%d] "%s"'."\n", $i, $this->escape($targetRule));
|
||||
}
|
||||
} else {
|
||||
$output .= sprintf('msgid "%s"'."\n", $this->escape($source));
|
||||
$output .= sprintf('msgstr "%s"'."\n", $this->escape($target));
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
private function getStandardRules(string $id)
|
||||
{
|
||||
// Partly copied from TranslatorTrait::trans.
|
||||
$parts = [];
|
||||
if (preg_match('/^\|++$/', $id)) {
|
||||
$parts = explode('|', $id);
|
||||
} elseif (preg_match_all('/(?:\|\||[^\|])++/', $id, $matches)) {
|
||||
$parts = $matches[0];
|
||||
}
|
||||
|
||||
$intervalRegexp = <<<'EOF'
|
||||
/^(?P<interval>
|
||||
({\s*
|
||||
(\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*)
|
||||
\s*})
|
||||
|
||||
|
|
||||
|
||||
(?P<left_delimiter>[\[\]])
|
||||
\s*
|
||||
(?P<left>-Inf|\-?\d+(\.\d+)?)
|
||||
\s*,\s*
|
||||
(?P<right>\+?Inf|\-?\d+(\.\d+)?)
|
||||
\s*
|
||||
(?P<right_delimiter>[\[\]])
|
||||
)\s*(?P<message>.*?)$/xs
|
||||
EOF;
|
||||
|
||||
$standardRules = [];
|
||||
foreach ($parts as $part) {
|
||||
$part = trim(str_replace('||', '|', $part));
|
||||
|
||||
if (preg_match($intervalRegexp, $part)) {
|
||||
// Explicit rule is not a standard rule.
|
||||
return [];
|
||||
} else {
|
||||
$standardRules[] = $part;
|
||||
}
|
||||
}
|
||||
|
||||
return $standardRules;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExtension()
|
||||
protected function getExtension(): string
|
||||
{
|
||||
return 'po';
|
||||
}
|
||||
|
||||
private function escape($str)
|
||||
private function escape(string $str): string
|
||||
{
|
||||
return addcslashes($str, "\0..\37\42\134");
|
||||
}
|
||||
|
||||
private function formatComments(string|array $comments, string $prefix = ''): ?string
|
||||
{
|
||||
$output = null;
|
||||
|
||||
foreach ((array) $comments as $comment) {
|
||||
$output .= sprintf('#%s %s'."\n", $prefix, $comment);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class QtFileDumper extends FileDumper
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array())
|
||||
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
|
||||
{
|
||||
$dom = new \DOMDocument('1.0', 'utf-8');
|
||||
$dom->formatOutput = true;
|
||||
@@ -33,6 +33,17 @@ class QtFileDumper extends FileDumper
|
||||
|
||||
foreach ($messages->all($domain) as $source => $target) {
|
||||
$message = $context->appendChild($dom->createElement('message'));
|
||||
$metadata = $messages->getMetadata($source, $domain);
|
||||
if (isset($metadata['sources'])) {
|
||||
foreach ((array) $metadata['sources'] as $location) {
|
||||
$loc = explode(':', $location, 2);
|
||||
$location = $message->appendChild($dom->createElement('location'));
|
||||
$location->setAttribute('filename', $loc[0]);
|
||||
if (isset($loc[1])) {
|
||||
$location->setAttribute('line', $loc[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
$message->appendChild($dom->createElement('source', $source));
|
||||
$message->appendChild($dom->createElement('translation', $target));
|
||||
}
|
||||
@@ -43,7 +54,7 @@ class QtFileDumper extends FileDumper
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExtension()
|
||||
protected function getExtension(): string
|
||||
{
|
||||
return 'ts';
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
|
||||
namespace Symfony\Component\Translation\Dumper;
|
||||
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
|
||||
/**
|
||||
* XliffFileDumper generates xliff files from a message catalogue.
|
||||
@@ -24,14 +24,14 @@ class XliffFileDumper extends FileDumper
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array())
|
||||
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
|
||||
{
|
||||
$xliffVersion = '1.2';
|
||||
if (array_key_exists('xliff_version', $options)) {
|
||||
if (\array_key_exists('xliff_version', $options)) {
|
||||
$xliffVersion = $options['xliff_version'];
|
||||
}
|
||||
|
||||
if (array_key_exists('default_locale', $options)) {
|
||||
if (\array_key_exists('default_locale', $options)) {
|
||||
$defaultLocale = $options['default_locale'];
|
||||
} else {
|
||||
$defaultLocale = \Locale::getDefault();
|
||||
@@ -41,7 +41,7 @@ class XliffFileDumper extends FileDumper
|
||||
return $this->dumpXliff1($defaultLocale, $messages, $domain, $options);
|
||||
}
|
||||
if ('2.0' === $xliffVersion) {
|
||||
return $this->dumpXliff2($defaultLocale, $messages, $domain, $options);
|
||||
return $this->dumpXliff2($defaultLocale, $messages, $domain);
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException(sprintf('No support implemented for dumping XLIFF version "%s".', $xliffVersion));
|
||||
@@ -50,15 +50,15 @@ class XliffFileDumper extends FileDumper
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExtension()
|
||||
protected function getExtension(): string
|
||||
{
|
||||
return 'xlf';
|
||||
}
|
||||
|
||||
private function dumpXliff1($defaultLocale, MessageCatalogue $messages, $domain, array $options = array())
|
||||
private function dumpXliff1(string $defaultLocale, MessageCatalogue $messages, ?string $domain, array $options = [])
|
||||
{
|
||||
$toolInfo = array('tool-id' => 'symfony', 'tool-name' => 'Symfony');
|
||||
if (array_key_exists('tool_info', $options)) {
|
||||
$toolInfo = ['tool-id' => 'symfony', 'tool-name' => 'Symfony'];
|
||||
if (\array_key_exists('tool_info', $options)) {
|
||||
$toolInfo = array_merge($toolInfo, $options['tool_info']);
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ class XliffFileDumper extends FileDumper
|
||||
foreach ($messages->all($domain) as $source => $target) {
|
||||
$translation = $dom->createElement('trans-unit');
|
||||
|
||||
$translation->setAttribute('id', md5($source));
|
||||
$translation->setAttribute('id', strtr(substr(base64_encode(hash('sha256', $source, true)), 0, 7), '/+', '._'));
|
||||
$translation->setAttribute('resname', $source);
|
||||
|
||||
$s = $translation->appendChild($dom->createElement('source'));
|
||||
@@ -129,7 +129,7 @@ class XliffFileDumper extends FileDumper
|
||||
return $dom->saveXML();
|
||||
}
|
||||
|
||||
private function dumpXliff2($defaultLocale, MessageCatalogue $messages, $domain, array $options = array())
|
||||
private function dumpXliff2(string $defaultLocale, MessageCatalogue $messages, ?string $domain)
|
||||
{
|
||||
$dom = new \DOMDocument('1.0', 'utf-8');
|
||||
$dom->formatOutput = true;
|
||||
@@ -141,11 +141,37 @@ class XliffFileDumper extends FileDumper
|
||||
$xliff->setAttribute('trgLang', str_replace('_', '-', $messages->getLocale()));
|
||||
|
||||
$xliffFile = $xliff->appendChild($dom->createElement('file'));
|
||||
$xliffFile->setAttribute('id', $domain.'.'.$messages->getLocale());
|
||||
if (str_ends_with($domain, MessageCatalogue::INTL_DOMAIN_SUFFIX)) {
|
||||
$xliffFile->setAttribute('id', substr($domain, 0, -\strlen(MessageCatalogue::INTL_DOMAIN_SUFFIX)).'.'.$messages->getLocale());
|
||||
} else {
|
||||
$xliffFile->setAttribute('id', $domain.'.'.$messages->getLocale());
|
||||
}
|
||||
|
||||
foreach ($messages->all($domain) as $source => $target) {
|
||||
$translation = $dom->createElement('unit');
|
||||
$translation->setAttribute('id', md5($source));
|
||||
$translation->setAttribute('id', strtr(substr(base64_encode(hash('sha256', $source, true)), 0, 7), '/+', '._'));
|
||||
|
||||
if (\strlen($source) <= 80) {
|
||||
$translation->setAttribute('name', $source);
|
||||
}
|
||||
|
||||
$metadata = $messages->getMetadata($source, $domain);
|
||||
|
||||
// Add notes section
|
||||
if ($this->hasMetadataArrayInfo('notes', $metadata)) {
|
||||
$notesElement = $dom->createElement('notes');
|
||||
foreach ($metadata['notes'] as $note) {
|
||||
$n = $dom->createElement('note');
|
||||
$n->appendChild($dom->createTextNode($note['content'] ?? ''));
|
||||
unset($note['content']);
|
||||
|
||||
foreach ($note as $name => $value) {
|
||||
$n->setAttribute($name, $value);
|
||||
}
|
||||
$notesElement->appendChild($n);
|
||||
}
|
||||
$translation->appendChild($notesElement);
|
||||
}
|
||||
|
||||
$segment = $translation->appendChild($dom->createElement('segment'));
|
||||
|
||||
@@ -156,7 +182,6 @@ class XliffFileDumper extends FileDumper
|
||||
$text = 1 === preg_match('/[&<>]/', $target) ? $dom->createCDATASection($target) : $dom->createTextNode($target);
|
||||
|
||||
$targetElement = $dom->createElement('target');
|
||||
$metadata = $messages->getMetadata($source, $domain);
|
||||
if ($this->hasMetadataArrayInfo('target-attributes', $metadata)) {
|
||||
foreach ($metadata['target-attributes'] as $name => $value) {
|
||||
$targetElement->setAttribute($name, $value);
|
||||
@@ -171,14 +196,8 @@ class XliffFileDumper extends FileDumper
|
||||
return $dom->saveXML();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param array|null $metadata
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function hasMetadataArrayInfo($key, $metadata = null)
|
||||
private function hasMetadataArrayInfo(string $key, array $metadata = null): bool
|
||||
{
|
||||
return null !== $metadata && array_key_exists($key, $metadata) && ($metadata[$key] instanceof \Traversable || is_array($metadata[$key]));
|
||||
return is_iterable($metadata[$key] ?? null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
|
||||
namespace Symfony\Component\Translation\Dumper;
|
||||
|
||||
use Symfony\Component\Translation\Exception\LogicException;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\Util\ArrayConverter;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Symfony\Component\Translation\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* YamlFileDumper generates yaml files from a message catalogue.
|
||||
@@ -23,12 +23,19 @@ use Symfony\Component\Translation\Exception\LogicException;
|
||||
*/
|
||||
class YamlFileDumper extends FileDumper
|
||||
{
|
||||
private string $extension;
|
||||
|
||||
public function __construct(string $extension = 'yml')
|
||||
{
|
||||
$this->extension = $extension;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array())
|
||||
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
|
||||
{
|
||||
if (!class_exists('Symfony\Component\Yaml\Yaml')) {
|
||||
if (!class_exists(Yaml::class)) {
|
||||
throw new LogicException('Dumping translations in the YAML format requires the Symfony Yaml component.');
|
||||
}
|
||||
|
||||
@@ -48,8 +55,8 @@ class YamlFileDumper extends FileDumper
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function getExtension()
|
||||
protected function getExtension(): string
|
||||
{
|
||||
return 'yml';
|
||||
return $this->extension;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,6 @@ namespace Symfony\Component\Translation\Exception;
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface ExceptionInterface
|
||||
interface ExceptionInterface extends \Throwable
|
||||
{
|
||||
}
|
||||
|
||||
24
vendor/symfony/translation/Exception/IncompleteDsnException.php
vendored
Normal file
24
vendor/symfony/translation/Exception/IncompleteDsnException.php
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Exception;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
25
vendor/symfony/translation/Exception/MissingRequiredOptionException.php
vendored
Normal file
25
vendor/symfony/translation/Exception/MissingRequiredOptionException.php
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Exception;
|
||||
|
||||
/**
|
||||
* @author Oskar Stark <oskarstark@googlemail.com>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
41
vendor/symfony/translation/Exception/ProviderException.php
vendored
Normal file
41
vendor/symfony/translation/Exception/ProviderException.php
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Exception;
|
||||
|
||||
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class ProviderException extends RuntimeException implements ProviderExceptionInterface
|
||||
{
|
||||
private $response;
|
||||
private string $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;
|
||||
}
|
||||
}
|
||||
23
vendor/symfony/translation/Exception/ProviderExceptionInterface.php
vendored
Normal file
23
vendor/symfony/translation/Exception/ProviderExceptionInterface.php
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Exception;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
interface ProviderExceptionInterface extends ExceptionInterface
|
||||
{
|
||||
/*
|
||||
* Returns debug info coming from the Symfony\Contracts\HttpClient\ResponseInterface
|
||||
*/
|
||||
public function getDebug(): string;
|
||||
}
|
||||
54
vendor/symfony/translation/Exception/UnsupportedSchemeException.php
vendored
Normal file
54
vendor/symfony/translation/Exception/UnsupportedSchemeException.php
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Exception;
|
||||
|
||||
use Symfony\Component\Translation\Bridge;
|
||||
use 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.'.');
|
||||
}
|
||||
}
|
||||
@@ -20,22 +20,17 @@ use Symfony\Component\Translation\Exception\InvalidArgumentException;
|
||||
*/
|
||||
abstract class AbstractFileExtractor
|
||||
{
|
||||
/**
|
||||
* @param string|array $resource files, a file or a directory
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function extractFiles($resource)
|
||||
protected function extractFiles(string|iterable $resource): iterable
|
||||
{
|
||||
if (is_array($resource) || $resource instanceof \Traversable) {
|
||||
$files = array();
|
||||
if (is_iterable($resource)) {
|
||||
$files = [];
|
||||
foreach ($resource as $file) {
|
||||
if ($this->canBeExtracted($file)) {
|
||||
$files[] = $this->toSplFileInfo($file);
|
||||
}
|
||||
}
|
||||
} elseif (is_file($resource)) {
|
||||
$files = $this->canBeExtracted($resource) ? array($this->toSplFileInfo($resource)) : array();
|
||||
$files = $this->canBeExtracted($resource) ? [$this->toSplFileInfo($resource)] : [];
|
||||
} else {
|
||||
$files = $this->extractFromDirectory($resource);
|
||||
}
|
||||
@@ -43,24 +38,15 @@ abstract class AbstractFileExtractor
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
*
|
||||
* @return \SplFileInfo
|
||||
*/
|
||||
private function toSplFileInfo($file)
|
||||
private function toSplFileInfo(string $file): \SplFileInfo
|
||||
{
|
||||
return ($file instanceof \SplFileInfo) ? $file : new \SplFileInfo($file);
|
||||
return new \SplFileInfo($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
protected function isFile($file)
|
||||
protected function isFile(string $file): bool
|
||||
{
|
||||
if (!is_file($file)) {
|
||||
throw new InvalidArgumentException(sprintf('The "%s" file does not exist.', $file));
|
||||
@@ -70,16 +56,12 @@ abstract class AbstractFileExtractor
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $file
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
abstract protected function canBeExtracted($file);
|
||||
abstract protected function canBeExtracted(string $file);
|
||||
|
||||
/**
|
||||
* @param string|array $resource files, a file or a directory
|
||||
*
|
||||
* @return array files to be extracted
|
||||
* @return iterable
|
||||
*/
|
||||
abstract protected function extractFromDirectory($resource);
|
||||
abstract protected function extractFromDirectory(string|array $resource);
|
||||
}
|
||||
|
||||
@@ -25,15 +25,12 @@ class ChainExtractor implements ExtractorInterface
|
||||
*
|
||||
* @var ExtractorInterface[]
|
||||
*/
|
||||
private $extractors = array();
|
||||
private array $extractors = [];
|
||||
|
||||
/**
|
||||
* Adds a loader to the translation extractor.
|
||||
*
|
||||
* @param string $format The format of the loader
|
||||
* @param ExtractorInterface $extractor The loader
|
||||
*/
|
||||
public function addExtractor($format, ExtractorInterface $extractor)
|
||||
public function addExtractor(string $format, ExtractorInterface $extractor)
|
||||
{
|
||||
$this->extractors[$format] = $extractor;
|
||||
}
|
||||
@@ -41,7 +38,7 @@ class ChainExtractor implements ExtractorInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setPrefix($prefix)
|
||||
public function setPrefix(string $prefix)
|
||||
{
|
||||
foreach ($this->extractors as $extractor) {
|
||||
$extractor->setPrefix($prefix);
|
||||
@@ -51,7 +48,7 @@ class ChainExtractor implements ExtractorInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function extract($directory, MessageCatalogue $catalogue)
|
||||
public function extract(string|iterable $directory, MessageCatalogue $catalogue)
|
||||
{
|
||||
foreach ($this->extractors as $extractor) {
|
||||
$extractor->extract($directory, $catalogue);
|
||||
|
||||
@@ -24,15 +24,12 @@ interface ExtractorInterface
|
||||
/**
|
||||
* Extracts translation messages from files, a file or a directory to the catalogue.
|
||||
*
|
||||
* @param string|array $resource files, a file or a directory
|
||||
* @param MessageCatalogue $catalogue The catalogue
|
||||
* @param string|iterable<string> $resource Files, a file or a directory
|
||||
*/
|
||||
public function extract($resource, MessageCatalogue $catalogue);
|
||||
public function extract(string|iterable $resource, MessageCatalogue $catalogue);
|
||||
|
||||
/**
|
||||
* Sets the prefix that should be used for new found messages.
|
||||
*
|
||||
* @param string $prefix The prefix
|
||||
*/
|
||||
public function setPrefix($prefix);
|
||||
public function setPrefix(string $prefix);
|
||||
}
|
||||
|
||||
330
vendor/symfony/translation/Extractor/PhpExtractor.php
vendored
Normal file
330
vendor/symfony/translation/Extractor/PhpExtractor.php
vendored
Normal file
@@ -0,0 +1,330 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Extractor;
|
||||
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
|
||||
/**
|
||||
* PhpExtractor extracts translation messages from a PHP template.
|
||||
*
|
||||
* @author Michel Salib <michelsalib@hotmail.com>
|
||||
*/
|
||||
class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
|
||||
{
|
||||
public const MESSAGE_TOKEN = 300;
|
||||
public const METHOD_ARGUMENTS_TOKEN = 1000;
|
||||
public const DOMAIN_TOKEN = 1001;
|
||||
|
||||
/**
|
||||
* Prefix for new found message.
|
||||
*/
|
||||
private string $prefix = '';
|
||||
|
||||
/**
|
||||
* The sequence that captures translation messages.
|
||||
*/
|
||||
protected $sequences = [
|
||||
[
|
||||
'->',
|
||||
'trans',
|
||||
'(',
|
||||
self::MESSAGE_TOKEN,
|
||||
',',
|
||||
self::METHOD_ARGUMENTS_TOKEN,
|
||||
',',
|
||||
self::DOMAIN_TOKEN,
|
||||
],
|
||||
[
|
||||
'->',
|
||||
'trans',
|
||||
'(',
|
||||
self::MESSAGE_TOKEN,
|
||||
],
|
||||
[
|
||||
'new',
|
||||
'TranslatableMessage',
|
||||
'(',
|
||||
self::MESSAGE_TOKEN,
|
||||
',',
|
||||
self::METHOD_ARGUMENTS_TOKEN,
|
||||
',',
|
||||
self::DOMAIN_TOKEN,
|
||||
],
|
||||
[
|
||||
'new',
|
||||
'TranslatableMessage',
|
||||
'(',
|
||||
self::MESSAGE_TOKEN,
|
||||
],
|
||||
[
|
||||
'new',
|
||||
'\\',
|
||||
'Symfony',
|
||||
'\\',
|
||||
'Component',
|
||||
'\\',
|
||||
'Translation',
|
||||
'\\',
|
||||
'TranslatableMessage',
|
||||
'(',
|
||||
self::MESSAGE_TOKEN,
|
||||
',',
|
||||
self::METHOD_ARGUMENTS_TOKEN,
|
||||
',',
|
||||
self::DOMAIN_TOKEN,
|
||||
],
|
||||
[
|
||||
'new',
|
||||
'\Symfony\Component\Translation\TranslatableMessage',
|
||||
'(',
|
||||
self::MESSAGE_TOKEN,
|
||||
',',
|
||||
self::METHOD_ARGUMENTS_TOKEN,
|
||||
',',
|
||||
self::DOMAIN_TOKEN,
|
||||
],
|
||||
[
|
||||
'new',
|
||||
'\\',
|
||||
'Symfony',
|
||||
'\\',
|
||||
'Component',
|
||||
'\\',
|
||||
'Translation',
|
||||
'\\',
|
||||
'TranslatableMessage',
|
||||
'(',
|
||||
self::MESSAGE_TOKEN,
|
||||
],
|
||||
[
|
||||
'new',
|
||||
'\Symfony\Component\Translation\TranslatableMessage',
|
||||
'(',
|
||||
self::MESSAGE_TOKEN,
|
||||
],
|
||||
[
|
||||
't',
|
||||
'(',
|
||||
self::MESSAGE_TOKEN,
|
||||
',',
|
||||
self::METHOD_ARGUMENTS_TOKEN,
|
||||
',',
|
||||
self::DOMAIN_TOKEN,
|
||||
],
|
||||
[
|
||||
't',
|
||||
'(',
|
||||
self::MESSAGE_TOKEN,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function extract(string|iterable $resource, MessageCatalogue $catalog)
|
||||
{
|
||||
$files = $this->extractFiles($resource);
|
||||
foreach ($files as $file) {
|
||||
$this->parseTokens(token_get_all(file_get_contents($file)), $catalog, $file);
|
||||
|
||||
gc_mem_caches();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setPrefix(string $prefix)
|
||||
{
|
||||
$this->prefix = $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a token.
|
||||
*/
|
||||
protected function normalizeToken(mixed $token): ?string
|
||||
{
|
||||
if (isset($token[1]) && 'b"' !== $token) {
|
||||
return $token[1];
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeks to a non-whitespace token.
|
||||
*/
|
||||
private function seekToNextRelevantToken(\Iterator $tokenIterator)
|
||||
{
|
||||
for (; $tokenIterator->valid(); $tokenIterator->next()) {
|
||||
$t = $tokenIterator->current();
|
||||
if (\T_WHITESPACE !== $t[0]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function skipMethodArgument(\Iterator $tokenIterator)
|
||||
{
|
||||
$openBraces = 0;
|
||||
|
||||
for (; $tokenIterator->valid(); $tokenIterator->next()) {
|
||||
$t = $tokenIterator->current();
|
||||
|
||||
if ('[' === $t[0] || '(' === $t[0]) {
|
||||
++$openBraces;
|
||||
}
|
||||
|
||||
if (']' === $t[0] || ')' === $t[0]) {
|
||||
--$openBraces;
|
||||
}
|
||||
|
||||
if ((0 === $openBraces && ',' === $t[0]) || (-1 === $openBraces && ')' === $t[0])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the message from the iterator while the tokens
|
||||
* match allowed message tokens.
|
||||
*/
|
||||
private function getValue(\Iterator $tokenIterator)
|
||||
{
|
||||
$message = '';
|
||||
$docToken = '';
|
||||
$docPart = '';
|
||||
|
||||
for (; $tokenIterator->valid(); $tokenIterator->next()) {
|
||||
$t = $tokenIterator->current();
|
||||
if ('.' === $t) {
|
||||
// Concatenate with next token
|
||||
continue;
|
||||
}
|
||||
if (!isset($t[1])) {
|
||||
break;
|
||||
}
|
||||
|
||||
switch ($t[0]) {
|
||||
case \T_START_HEREDOC:
|
||||
$docToken = $t[1];
|
||||
break;
|
||||
case \T_ENCAPSED_AND_WHITESPACE:
|
||||
case \T_CONSTANT_ENCAPSED_STRING:
|
||||
if ('' === $docToken) {
|
||||
$message .= PhpStringTokenParser::parse($t[1]);
|
||||
} else {
|
||||
$docPart = $t[1];
|
||||
}
|
||||
break;
|
||||
case \T_END_HEREDOC:
|
||||
if ($indentation = strspn($t[1], ' ')) {
|
||||
$docPartWithLineBreaks = $docPart;
|
||||
$docPart = '';
|
||||
|
||||
foreach (preg_split('~(\r\n|\n|\r)~', $docPartWithLineBreaks, -1, \PREG_SPLIT_DELIM_CAPTURE) as $str) {
|
||||
if (\in_array($str, ["\r\n", "\n", "\r"], true)) {
|
||||
$docPart .= $str;
|
||||
} else {
|
||||
$docPart .= substr($str, $indentation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$message .= PhpStringTokenParser::parseDocString($docToken, $docPart);
|
||||
$docToken = '';
|
||||
$docPart = '';
|
||||
break;
|
||||
case \T_WHITESPACE:
|
||||
break;
|
||||
default:
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts trans message from PHP tokens.
|
||||
*/
|
||||
protected function parseTokens(array $tokens, MessageCatalogue $catalog, string $filename)
|
||||
{
|
||||
$tokenIterator = new \ArrayIterator($tokens);
|
||||
|
||||
for ($key = 0; $key < $tokenIterator->count(); ++$key) {
|
||||
foreach ($this->sequences as $sequence) {
|
||||
$message = '';
|
||||
$domain = 'messages';
|
||||
$tokenIterator->seek($key);
|
||||
|
||||
foreach ($sequence as $sequenceKey => $item) {
|
||||
$this->seekToNextRelevantToken($tokenIterator);
|
||||
|
||||
if ($this->normalizeToken($tokenIterator->current()) === $item) {
|
||||
$tokenIterator->next();
|
||||
continue;
|
||||
} elseif (self::MESSAGE_TOKEN === $item) {
|
||||
$message = $this->getValue($tokenIterator);
|
||||
|
||||
if (\count($sequence) === ($sequenceKey + 1)) {
|
||||
break;
|
||||
}
|
||||
} elseif (self::METHOD_ARGUMENTS_TOKEN === $item) {
|
||||
$this->skipMethodArgument($tokenIterator);
|
||||
} elseif (self::DOMAIN_TOKEN === $item) {
|
||||
$domainToken = $this->getValue($tokenIterator);
|
||||
if ('' !== $domainToken) {
|
||||
$domain = $domainToken;
|
||||
}
|
||||
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($message) {
|
||||
$catalog->set($message, $this->prefix.$message, $domain);
|
||||
$metadata = $catalog->getMetadata($message, $domain) ?? [];
|
||||
$normalizedFilename = preg_replace('{[\\\\/]+}', '/', $filename);
|
||||
$metadata['sources'][] = $normalizedFilename.':'.$tokens[$key][2];
|
||||
$catalog->setMetadata($message, $metadata, $domain);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
protected function canBeExtracted(string $file): bool
|
||||
{
|
||||
return $this->isFile($file) && 'php' === pathinfo($file, \PATHINFO_EXTENSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function extractFromDirectory(string|array $directory): iterable
|
||||
{
|
||||
if (!class_exists(Finder::class)) {
|
||||
throw new \LogicException(sprintf('You cannot use "%s" as the "symfony/finder" package is not installed. Try running "composer require symfony/finder".', static::class));
|
||||
}
|
||||
|
||||
$finder = new Finder();
|
||||
|
||||
return $finder->files()->name('*.php')->in($directory);
|
||||
}
|
||||
}
|
||||
136
vendor/symfony/translation/Extractor/PhpStringTokenParser.php
vendored
Normal file
136
vendor/symfony/translation/Extractor/PhpStringTokenParser.php
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Extractor;
|
||||
|
||||
/*
|
||||
* The following is derived from code at http://github.com/nikic/PHP-Parser
|
||||
*
|
||||
* Copyright (c) 2011 by Nikita Popov
|
||||
*
|
||||
* Some rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above
|
||||
* copyright notice, this list of conditions and the following
|
||||
* disclaimer in the documentation and/or other materials provided
|
||||
* with the distribution.
|
||||
*
|
||||
* * The names of the contributors may not be used to endorse or
|
||||
* promote products derived from this software without specific
|
||||
* prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
class PhpStringTokenParser
|
||||
{
|
||||
protected static $replacements = [
|
||||
'\\' => '\\',
|
||||
'$' => '$',
|
||||
'n' => "\n",
|
||||
'r' => "\r",
|
||||
't' => "\t",
|
||||
'f' => "\f",
|
||||
'v' => "\v",
|
||||
'e' => "\x1B",
|
||||
];
|
||||
|
||||
/**
|
||||
* Parses a string token.
|
||||
*
|
||||
* @param string $str String token content
|
||||
*/
|
||||
public static function parse(string $str): string
|
||||
{
|
||||
$bLength = 0;
|
||||
if ('b' === $str[0]) {
|
||||
$bLength = 1;
|
||||
}
|
||||
|
||||
if ('\'' === $str[$bLength]) {
|
||||
return str_replace(
|
||||
['\\\\', '\\\''],
|
||||
['\\', '\''],
|
||||
substr($str, $bLength + 1, -1)
|
||||
);
|
||||
} else {
|
||||
return self::parseEscapeSequences(substr($str, $bLength + 1, -1), '"');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses escape sequences in strings (all string types apart from single quoted).
|
||||
*
|
||||
* @param string $str String without quotes
|
||||
* @param string|null $quote Quote type
|
||||
*/
|
||||
public static function parseEscapeSequences(string $str, string $quote = null): string
|
||||
{
|
||||
if (null !== $quote) {
|
||||
$str = str_replace('\\'.$quote, $quote, $str);
|
||||
}
|
||||
|
||||
return preg_replace_callback(
|
||||
'~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3})~',
|
||||
[__CLASS__, 'parseCallback'],
|
||||
$str
|
||||
);
|
||||
}
|
||||
|
||||
private static function parseCallback(array $matches): string
|
||||
{
|
||||
$str = $matches[1];
|
||||
|
||||
if (isset(self::$replacements[$str])) {
|
||||
return self::$replacements[$str];
|
||||
} elseif ('x' === $str[0] || 'X' === $str[0]) {
|
||||
return \chr(hexdec($str));
|
||||
} else {
|
||||
return \chr(octdec($str));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a constant doc string.
|
||||
*
|
||||
* @param string $startToken Doc string start token content (<<<SMTHG)
|
||||
* @param string $str String token content
|
||||
*/
|
||||
public static function parseDocString(string $startToken, string $str): string
|
||||
{
|
||||
// strip last newline (thanks tokenizer for sticking it into the string!)
|
||||
$str = preg_replace('~(\r\n|\n|\r)$~', '', $str);
|
||||
|
||||
// nowdoc string
|
||||
if (str_contains($startToken, '\'')) {
|
||||
return $str;
|
||||
}
|
||||
|
||||
return self::parseEscapeSequences($str, null);
|
||||
}
|
||||
}
|
||||
60
vendor/symfony/translation/Formatter/IntlFormatter.php
vendored
Normal file
60
vendor/symfony/translation/Formatter/IntlFormatter.php
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Formatter;
|
||||
|
||||
use Symfony\Component\Translation\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Translation\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
|
||||
*/
|
||||
class IntlFormatter implements IntlFormatterInterface
|
||||
{
|
||||
private $hasMessageFormatter;
|
||||
private $cache = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formatIntl(string $message, string $locale, array $parameters = []): string
|
||||
{
|
||||
// MessageFormatter constructor throws an exception if the message is empty
|
||||
if ('' === $message) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!$formatter = $this->cache[$locale][$message] ?? null) {
|
||||
if (!($this->hasMessageFormatter ?? $this->hasMessageFormatter = class_exists(\MessageFormatter::class))) {
|
||||
throw new LogicException('Cannot parse message translation: please install the "intl" PHP extension or the "symfony/polyfill-intl-messageformatter" package.');
|
||||
}
|
||||
try {
|
||||
$this->cache[$locale][$message] = $formatter = new \MessageFormatter($locale, $message);
|
||||
} catch (\IntlException $e) {
|
||||
throw new InvalidArgumentException(sprintf('Invalid message format (error #%d): ', intl_get_error_code()).intl_get_error_message(), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($parameters as $key => $value) {
|
||||
if (\in_array($key[0] ?? null, ['%', '{'], true)) {
|
||||
unset($parameters[$key]);
|
||||
$parameters[trim($key, '%{ }')] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (false === $message = $formatter->format($parameters)) {
|
||||
throw new InvalidArgumentException(sprintf('Unable to format message (error #%s): ', $formatter->getErrorCode()).$formatter->getErrorMessage());
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
}
|
||||
27
vendor/symfony/translation/Formatter/IntlFormatterInterface.php
vendored
Normal file
27
vendor/symfony/translation/Formatter/IntlFormatterInterface.php
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Formatter;
|
||||
|
||||
/**
|
||||
* Formats ICU message patterns.
|
||||
*
|
||||
* @author Nicolas Grekas <p@tchwork.com>
|
||||
*/
|
||||
interface IntlFormatterInterface
|
||||
{
|
||||
/**
|
||||
* Formats a localized message using rules defined by ICU MessageFormat.
|
||||
*
|
||||
* @see http://icu-project.org/apiref/icu4c/classMessageFormat.html#details
|
||||
*/
|
||||
public function formatIntl(string $message, string $locale, array $parameters = []): string;
|
||||
}
|
||||
56
vendor/symfony/translation/Formatter/MessageFormatter.php
vendored
Normal file
56
vendor/symfony/translation/Formatter/MessageFormatter.php
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Formatter;
|
||||
|
||||
use Symfony\Component\Translation\IdentityTranslator;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
// Help opcache.preload discover always-needed symbols
|
||||
class_exists(IntlFormatter::class);
|
||||
|
||||
/**
|
||||
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
|
||||
*/
|
||||
class MessageFormatter implements MessageFormatterInterface, IntlFormatterInterface
|
||||
{
|
||||
private $translator;
|
||||
private $intlFormatter;
|
||||
|
||||
/**
|
||||
* @param TranslatorInterface|null $translator An identity translator to use as selector for pluralization
|
||||
*/
|
||||
public function __construct(TranslatorInterface $translator = null, IntlFormatterInterface $intlFormatter = null)
|
||||
{
|
||||
$this->translator = $translator ?? new IdentityTranslator();
|
||||
$this->intlFormatter = $intlFormatter ?? new IntlFormatter();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function format(string $message, string $locale, array $parameters = []): string
|
||||
{
|
||||
if ($this->translator instanceof TranslatorInterface) {
|
||||
return $this->translator->trans($message, $parameters, null, $locale);
|
||||
}
|
||||
|
||||
return strtr($message, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function formatIntl(string $message, string $locale, array $parameters = []): string
|
||||
{
|
||||
return $this->intlFormatter->formatIntl($message, $locale, $parameters);
|
||||
}
|
||||
}
|
||||
28
vendor/symfony/translation/Formatter/MessageFormatterInterface.php
vendored
Normal file
28
vendor/symfony/translation/Formatter/MessageFormatterInterface.php
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Formatter;
|
||||
|
||||
/**
|
||||
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
|
||||
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
|
||||
*/
|
||||
interface MessageFormatterInterface
|
||||
{
|
||||
/**
|
||||
* Formats a localized message pattern with given arguments.
|
||||
*
|
||||
* @param string $message The message (may also be an object that can be cast to string)
|
||||
* @param string $locale The message locale
|
||||
* @param array $parameters An array of parameters for the message
|
||||
*/
|
||||
public function format(string $message, string $locale, array $parameters = []): string;
|
||||
}
|
||||
@@ -11,55 +11,16 @@
|
||||
|
||||
namespace Symfony\Component\Translation;
|
||||
|
||||
use Symfony\Contracts\Translation\LocaleAwareInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorTrait;
|
||||
|
||||
/**
|
||||
* IdentityTranslator does not translate anything.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class IdentityTranslator implements TranslatorInterface
|
||||
class IdentityTranslator implements TranslatorInterface, LocaleAwareInterface
|
||||
{
|
||||
private $selector;
|
||||
private $locale;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param MessageSelector|null $selector The message selector for pluralization
|
||||
*/
|
||||
public function __construct(MessageSelector $selector = null)
|
||||
{
|
||||
$this->selector = $selector ?: new MessageSelector();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setLocale($locale)
|
||||
{
|
||||
$this->locale = $locale;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLocale()
|
||||
{
|
||||
return $this->locale ?: \Locale::getDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function trans($id, array $parameters = array(), $domain = null, $locale = null)
|
||||
{
|
||||
return strtr((string) $id, $parameters);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null)
|
||||
{
|
||||
return strtr($this->selector->choose((string) $id, (int) $number, $locale ?: $this->getLocale()), $parameters);
|
||||
}
|
||||
use TranslatorTrait;
|
||||
}
|
||||
|
||||
109
vendor/symfony/translation/Interval.php
vendored
109
vendor/symfony/translation/Interval.php
vendored
@@ -1,109 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation;
|
||||
|
||||
use Symfony\Component\Translation\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Tests if a given number belongs to a given math interval.
|
||||
*
|
||||
* An interval can represent a finite set of numbers:
|
||||
*
|
||||
* {1,2,3,4}
|
||||
*
|
||||
* An interval can represent numbers between two numbers:
|
||||
*
|
||||
* [1, +Inf]
|
||||
* ]-1,2[
|
||||
*
|
||||
* The left delimiter can be [ (inclusive) or ] (exclusive).
|
||||
* The right delimiter can be [ (exclusive) or ] (inclusive).
|
||||
* Beside numbers, you can use -Inf and +Inf for the infinite.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* @see http://en.wikipedia.org/wiki/Interval_%28mathematics%29#The_ISO_notation
|
||||
*/
|
||||
class Interval
|
||||
{
|
||||
/**
|
||||
* Tests if the given number is in the math interval.
|
||||
*
|
||||
* @param int $number A number
|
||||
* @param string $interval An interval
|
||||
*
|
||||
* @return bool
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public static function test($number, $interval)
|
||||
{
|
||||
$interval = trim($interval);
|
||||
|
||||
if (!preg_match('/^'.self::getIntervalRegexp().'$/x', $interval, $matches)) {
|
||||
throw new InvalidArgumentException(sprintf('"%s" is not a valid interval.', $interval));
|
||||
}
|
||||
|
||||
if ($matches[1]) {
|
||||
foreach (explode(',', $matches[2]) as $n) {
|
||||
if ($number == $n) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$leftNumber = self::convertNumber($matches['left']);
|
||||
$rightNumber = self::convertNumber($matches['right']);
|
||||
|
||||
return
|
||||
('[' === $matches['left_delimiter'] ? $number >= $leftNumber : $number > $leftNumber)
|
||||
&& (']' === $matches['right_delimiter'] ? $number <= $rightNumber : $number < $rightNumber)
|
||||
;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Regexp that matches valid intervals.
|
||||
*
|
||||
* @return string A Regexp (without the delimiters)
|
||||
*/
|
||||
public static function getIntervalRegexp()
|
||||
{
|
||||
return <<<EOF
|
||||
({\s*
|
||||
(\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*)
|
||||
\s*})
|
||||
|
||||
|
|
||||
|
||||
(?P<left_delimiter>[\[\]])
|
||||
\s*
|
||||
(?P<left>-Inf|\-?\d+(\.\d+)?)
|
||||
\s*,\s*
|
||||
(?P<right>\+?Inf|\-?\d+(\.\d+)?)
|
||||
\s*
|
||||
(?P<right_delimiter>[\[\]])
|
||||
EOF;
|
||||
}
|
||||
|
||||
private static function convertNumber($number)
|
||||
{
|
||||
if ('-Inf' === $number) {
|
||||
return log(0);
|
||||
} elseif ('+Inf' === $number || 'Inf' === $number) {
|
||||
return -log(0);
|
||||
}
|
||||
|
||||
return (float) $number;
|
||||
}
|
||||
}
|
||||
2
vendor/symfony/translation/LICENSE
vendored
2
vendor/symfony/translation/LICENSE
vendored
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2004-2017 Fabien Potencier
|
||||
Copyright (c) 2004-2022 Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -23,9 +23,9 @@ class ArrayLoader implements LoaderInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load($resource, $locale, $domain = 'messages')
|
||||
public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue
|
||||
{
|
||||
$this->flatten($resource);
|
||||
$resource = $this->flatten($resource);
|
||||
$catalogue = new MessageCatalogue($locale);
|
||||
$catalogue->add($resource, $domain);
|
||||
|
||||
@@ -36,31 +36,23 @@ class ArrayLoader implements LoaderInterface
|
||||
* Flattens an nested array of translations.
|
||||
*
|
||||
* The scheme used is:
|
||||
* 'key' => array('key2' => array('key3' => 'value'))
|
||||
* 'key' => ['key2' => ['key3' => 'value']]
|
||||
* Becomes:
|
||||
* 'key.key2.key3' => 'value'
|
||||
*
|
||||
* This function takes an array by reference and will modify it
|
||||
*
|
||||
* @param array &$messages The array that will be flattened
|
||||
* @param array $subnode Current subnode being parsed, used internally for recursive calls
|
||||
* @param string $path Current path being parsed, used internally for recursive calls
|
||||
*/
|
||||
private function flatten(array &$messages, array $subnode = null, $path = null)
|
||||
private function flatten(array $messages): array
|
||||
{
|
||||
if (null === $subnode) {
|
||||
$subnode = &$messages;
|
||||
}
|
||||
foreach ($subnode as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$nodePath = $path ? $path.'.'.$key : $key;
|
||||
$this->flatten($messages, $value, $nodePath);
|
||||
if (null === $path) {
|
||||
unset($messages[$key]);
|
||||
$result = [];
|
||||
foreach ($messages as $key => $value) {
|
||||
if (\is_array($value)) {
|
||||
foreach ($this->flatten($value) as $k => $v) {
|
||||
$result[$key.'.'.$k] = $v;
|
||||
}
|
||||
} elseif (null !== $path) {
|
||||
$messages[$path.'.'.$key] = $value;
|
||||
} else {
|
||||
$result[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,16 +20,16 @@ use Symfony\Component\Translation\Exception\NotFoundResourceException;
|
||||
*/
|
||||
class CsvFileLoader extends FileLoader
|
||||
{
|
||||
private $delimiter = ';';
|
||||
private $enclosure = '"';
|
||||
private $escape = '\\';
|
||||
private string $delimiter = ';';
|
||||
private string $enclosure = '"';
|
||||
private string $escape = '\\';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function loadResource($resource)
|
||||
protected function loadResource(string $resource): array
|
||||
{
|
||||
$messages = array();
|
||||
$messages = [];
|
||||
|
||||
try {
|
||||
$file = new \SplFileObject($resource, 'rb');
|
||||
@@ -41,7 +41,11 @@ class CsvFileLoader extends FileLoader
|
||||
$file->setCsvControl($this->delimiter, $this->enclosure, $this->escape);
|
||||
|
||||
foreach ($file as $data) {
|
||||
if ('#' !== substr($data[0], 0, 1) && isset($data[1]) && 2 === count($data)) {
|
||||
if (false === $data) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ('#' !== substr($data[0], 0, 1) && isset($data[1]) && 2 === \count($data)) {
|
||||
$messages[$data[0]] = $data[1];
|
||||
}
|
||||
}
|
||||
@@ -51,12 +55,8 @@ class CsvFileLoader extends FileLoader
|
||||
|
||||
/**
|
||||
* Sets the delimiter, enclosure, and escape character for CSV.
|
||||
*
|
||||
* @param string $delimiter delimiter character
|
||||
* @param string $enclosure enclosure character
|
||||
* @param string $escape escape character
|
||||
*/
|
||||
public function setCsvControl($delimiter = ';', $enclosure = '"', $escape = '\\')
|
||||
public function setCsvControl(string $delimiter = ';', string $enclosure = '"', string $escape = '\\')
|
||||
{
|
||||
$this->delimiter = $delimiter;
|
||||
$this->enclosure = $enclosure;
|
||||
|
||||
19
vendor/symfony/translation/Loader/FileLoader.php
vendored
19
vendor/symfony/translation/Loader/FileLoader.php
vendored
@@ -11,9 +11,10 @@
|
||||
|
||||
namespace Symfony\Component\Translation\Loader;
|
||||
|
||||
use Symfony\Component\Config\Resource\FileResource;
|
||||
use Symfony\Component\Translation\Exception\InvalidResourceException;
|
||||
use Symfony\Component\Translation\Exception\NotFoundResourceException;
|
||||
use Symfony\Component\Config\Resource\FileResource;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
|
||||
/**
|
||||
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
|
||||
@@ -23,7 +24,7 @@ abstract class FileLoader extends ArrayLoader
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load($resource, $locale, $domain = 'messages')
|
||||
public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue
|
||||
{
|
||||
if (!stream_is_local($resource)) {
|
||||
throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
|
||||
@@ -37,17 +38,17 @@ abstract class FileLoader extends ArrayLoader
|
||||
|
||||
// empty resource
|
||||
if (null === $messages) {
|
||||
$messages = array();
|
||||
$messages = [];
|
||||
}
|
||||
|
||||
// not an array
|
||||
if (!is_array($messages)) {
|
||||
if (!\is_array($messages)) {
|
||||
throw new InvalidResourceException(sprintf('Unable to load file "%s".', $resource));
|
||||
}
|
||||
|
||||
$catalogue = parent::load($messages, $locale, $domain);
|
||||
|
||||
if (class_exists('Symfony\Component\Config\Resource\FileResource')) {
|
||||
if (class_exists(FileResource::class)) {
|
||||
$catalogue->addResource(new FileResource($resource));
|
||||
}
|
||||
|
||||
@@ -55,11 +56,7 @@ abstract class FileLoader extends ArrayLoader
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $resource
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws InvalidResourceException If stream content has an invalid format.
|
||||
* @throws InvalidResourceException if stream content has an invalid format
|
||||
*/
|
||||
abstract protected function loadResource($resource);
|
||||
abstract protected function loadResource(string $resource): array;
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
|
||||
namespace Symfony\Component\Translation\Loader;
|
||||
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Config\Resource\FileResource;
|
||||
use Symfony\Component\Translation\Exception\InvalidResourceException;
|
||||
use Symfony\Component\Translation\Exception\NotFoundResourceException;
|
||||
use Symfony\Component\Config\Resource\FileResource;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
|
||||
/**
|
||||
* IcuResFileLoader loads translations from a resource bundle.
|
||||
@@ -26,7 +26,7 @@ class IcuDatFileLoader extends IcuResFileLoader
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load($resource, $locale, $domain = 'messages')
|
||||
public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue
|
||||
{
|
||||
if (!stream_is_local($resource.'.dat')) {
|
||||
throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
|
||||
@@ -39,12 +39,11 @@ class IcuDatFileLoader extends IcuResFileLoader
|
||||
try {
|
||||
$rb = new \ResourceBundle($locale, $resource);
|
||||
} catch (\Exception $e) {
|
||||
// HHVM compatibility: constructor throws on invalid resource
|
||||
$rb = null;
|
||||
}
|
||||
|
||||
if (!$rb) {
|
||||
throw new InvalidResourceException(sprintf('Cannot load resource "%s"', $resource));
|
||||
throw new InvalidResourceException(sprintf('Cannot load resource "%s".', $resource));
|
||||
} elseif (intl_is_failure($rb->getErrorCode())) {
|
||||
throw new InvalidResourceException($rb->getErrorMessage(), $rb->getErrorCode());
|
||||
}
|
||||
@@ -53,7 +52,7 @@ class IcuDatFileLoader extends IcuResFileLoader
|
||||
$catalogue = new MessageCatalogue($locale);
|
||||
$catalogue->add($messages, $domain);
|
||||
|
||||
if (class_exists('Symfony\Component\Config\Resource\FileResource')) {
|
||||
if (class_exists(FileResource::class)) {
|
||||
$catalogue->addResource(new FileResource($resource.'.dat'));
|
||||
}
|
||||
|
||||
|
||||
@@ -11,10 +11,10 @@
|
||||
|
||||
namespace Symfony\Component\Translation\Loader;
|
||||
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Config\Resource\DirectoryResource;
|
||||
use Symfony\Component\Translation\Exception\InvalidResourceException;
|
||||
use Symfony\Component\Translation\Exception\NotFoundResourceException;
|
||||
use Symfony\Component\Config\Resource\DirectoryResource;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
|
||||
/**
|
||||
* IcuResFileLoader loads translations from a resource bundle.
|
||||
@@ -26,7 +26,7 @@ class IcuResFileLoader implements LoaderInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load($resource, $locale, $domain = 'messages')
|
||||
public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue
|
||||
{
|
||||
if (!stream_is_local($resource)) {
|
||||
throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
|
||||
@@ -39,12 +39,11 @@ class IcuResFileLoader implements LoaderInterface
|
||||
try {
|
||||
$rb = new \ResourceBundle($locale, $resource);
|
||||
} catch (\Exception $e) {
|
||||
// HHVM compatibility: constructor throws on invalid resource
|
||||
$rb = null;
|
||||
}
|
||||
|
||||
if (!$rb) {
|
||||
throw new InvalidResourceException(sprintf('Cannot load resource "%s"', $resource));
|
||||
throw new InvalidResourceException(sprintf('Cannot load resource "%s".', $resource));
|
||||
} elseif (intl_is_failure($rb->getErrorCode())) {
|
||||
throw new InvalidResourceException($rb->getErrorMessage(), $rb->getErrorCode());
|
||||
}
|
||||
@@ -53,7 +52,7 @@ class IcuResFileLoader implements LoaderInterface
|
||||
$catalogue = new MessageCatalogue($locale);
|
||||
$catalogue->add($messages, $domain);
|
||||
|
||||
if (class_exists('Symfony\Component\Config\Resource\DirectoryResource')) {
|
||||
if (class_exists(DirectoryResource::class)) {
|
||||
$catalogue->addResource(new DirectoryResource($resource));
|
||||
}
|
||||
|
||||
@@ -70,13 +69,11 @@ class IcuResFileLoader implements LoaderInterface
|
||||
*
|
||||
* This function takes an array by reference and will modify it
|
||||
*
|
||||
* @param \ResourceBundle $rb the ResourceBundle that will be flattened
|
||||
* @param array $messages used internally for recursive calls
|
||||
* @param string $path current path being parsed, used internally for recursive calls
|
||||
*
|
||||
* @return array the flattened ResourceBundle
|
||||
* @param \ResourceBundle $rb The ResourceBundle that will be flattened
|
||||
* @param array $messages Used internally for recursive calls
|
||||
* @param string $path Current path being parsed, used internally for recursive calls
|
||||
*/
|
||||
protected function flatten(\ResourceBundle $rb, array &$messages = array(), $path = null)
|
||||
protected function flatten(\ResourceBundle $rb, array &$messages = [], string $path = null): array
|
||||
{
|
||||
foreach ($rb as $key => $value) {
|
||||
$nodePath = $path ? $path.'.'.$key : $key;
|
||||
|
||||
@@ -21,7 +21,7 @@ class IniFileLoader extends FileLoader
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function loadResource($resource)
|
||||
protected function loadResource(string $resource): array
|
||||
{
|
||||
return parse_ini_file($resource, true);
|
||||
}
|
||||
|
||||
@@ -23,14 +23,14 @@ class JsonFileLoader extends FileLoader
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function loadResource($resource)
|
||||
protected function loadResource(string $resource): array
|
||||
{
|
||||
$messages = array();
|
||||
$messages = [];
|
||||
if ($data = file_get_contents($resource)) {
|
||||
$messages = json_decode($data, true);
|
||||
|
||||
if (0 < $errorCode = json_last_error()) {
|
||||
throw new InvalidResourceException(sprintf('Error parsing JSON - %s', $this->getJSONErrorMessage($errorCode)));
|
||||
throw new InvalidResourceException('Error parsing JSON: '.$this->getJSONErrorMessage($errorCode));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,23 +39,19 @@ class JsonFileLoader extends FileLoader
|
||||
|
||||
/**
|
||||
* Translates JSON_ERROR_* constant into meaningful message.
|
||||
*
|
||||
* @param int $errorCode Error code returned by json_last_error() call
|
||||
*
|
||||
* @return string Message string
|
||||
*/
|
||||
private function getJSONErrorMessage($errorCode)
|
||||
private function getJSONErrorMessage(int $errorCode): string
|
||||
{
|
||||
switch ($errorCode) {
|
||||
case JSON_ERROR_DEPTH:
|
||||
case \JSON_ERROR_DEPTH:
|
||||
return 'Maximum stack depth exceeded';
|
||||
case JSON_ERROR_STATE_MISMATCH:
|
||||
case \JSON_ERROR_STATE_MISMATCH:
|
||||
return 'Underflow or the modes mismatch';
|
||||
case JSON_ERROR_CTRL_CHAR:
|
||||
case \JSON_ERROR_CTRL_CHAR:
|
||||
return 'Unexpected control character found';
|
||||
case JSON_ERROR_SYNTAX:
|
||||
case \JSON_ERROR_SYNTAX:
|
||||
return 'Syntax error, malformed JSON';
|
||||
case JSON_ERROR_UTF8:
|
||||
case \JSON_ERROR_UTF8:
|
||||
return 'Malformed UTF-8 characters, possibly incorrectly encoded';
|
||||
default:
|
||||
return 'Unknown error';
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
|
||||
namespace Symfony\Component\Translation\Loader;
|
||||
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\Exception\InvalidResourceException;
|
||||
use Symfony\Component\Translation\Exception\NotFoundResourceException;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
|
||||
/**
|
||||
* LoaderInterface is the interface implemented by all translation loaders.
|
||||
@@ -25,14 +25,8 @@ interface LoaderInterface
|
||||
/**
|
||||
* Loads a locale.
|
||||
*
|
||||
* @param mixed $resource A resource
|
||||
* @param string $locale A locale
|
||||
* @param string $domain The domain
|
||||
*
|
||||
* @return MessageCatalogue A MessageCatalogue instance
|
||||
*
|
||||
* @throws NotFoundResourceException when the resource cannot be found
|
||||
* @throws InvalidResourceException when the resource cannot be loaded
|
||||
*/
|
||||
public function load($resource, $locale, $domain = 'messages');
|
||||
public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue;
|
||||
}
|
||||
|
||||
@@ -19,27 +19,21 @@ use Symfony\Component\Translation\Exception\InvalidResourceException;
|
||||
class MoFileLoader extends FileLoader
|
||||
{
|
||||
/**
|
||||
* Magic used for validating the format of a MO file as well as
|
||||
* Magic used for validating the format of an MO file as well as
|
||||
* detecting if the machine used to create that file was little endian.
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
const MO_LITTLE_ENDIAN_MAGIC = 0x950412de;
|
||||
public const MO_LITTLE_ENDIAN_MAGIC = 0x950412DE;
|
||||
|
||||
/**
|
||||
* Magic used for validating the format of a MO file as well as
|
||||
* Magic used for validating the format of an MO file as well as
|
||||
* detecting if the machine used to create that file was big endian.
|
||||
*
|
||||
* @var float
|
||||
*/
|
||||
const MO_BIG_ENDIAN_MAGIC = 0xde120495;
|
||||
public const MO_BIG_ENDIAN_MAGIC = 0xDE120495;
|
||||
|
||||
/**
|
||||
* The size of the header of a MO file in bytes.
|
||||
*
|
||||
* @var int Number of bytes
|
||||
* The size of the header of an MO file in bytes.
|
||||
*/
|
||||
const MO_HEADER_SIZE = 28;
|
||||
public const MO_HEADER_SIZE = 28;
|
||||
|
||||
/**
|
||||
* Parses machine object (MO) format, independent of the machine's endian it
|
||||
@@ -47,7 +41,7 @@ class MoFileLoader extends FileLoader
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function loadResource($resource)
|
||||
protected function loadResource(string $resource): array
|
||||
{
|
||||
$stream = fopen($resource, 'r');
|
||||
|
||||
@@ -59,9 +53,9 @@ class MoFileLoader extends FileLoader
|
||||
$magic = unpack('V1', fread($stream, 4));
|
||||
$magic = hexdec(substr(dechex(current($magic)), -8));
|
||||
|
||||
if ($magic == self::MO_LITTLE_ENDIAN_MAGIC) {
|
||||
if (self::MO_LITTLE_ENDIAN_MAGIC == $magic) {
|
||||
$isBigEndian = false;
|
||||
} elseif ($magic == self::MO_BIG_ENDIAN_MAGIC) {
|
||||
} elseif (self::MO_BIG_ENDIAN_MAGIC == $magic) {
|
||||
$isBigEndian = true;
|
||||
} else {
|
||||
throw new InvalidResourceException('MO stream content has an invalid format.');
|
||||
@@ -77,7 +71,7 @@ class MoFileLoader extends FileLoader
|
||||
// offsetHashes
|
||||
$this->readLong($stream, $isBigEndian);
|
||||
|
||||
$messages = array();
|
||||
$messages = [];
|
||||
|
||||
for ($i = 0; $i < $count; ++$i) {
|
||||
$pluralId = null;
|
||||
@@ -95,8 +89,8 @@ class MoFileLoader extends FileLoader
|
||||
fseek($stream, $offset);
|
||||
$singularId = fread($stream, $length);
|
||||
|
||||
if (strpos($singularId, "\000") !== false) {
|
||||
list($singularId, $pluralId) = explode("\000", $singularId);
|
||||
if (str_contains($singularId, "\000")) {
|
||||
[$singularId, $pluralId] = explode("\000", $singularId);
|
||||
}
|
||||
|
||||
fseek($stream, $offsetTranslated + $i * 8);
|
||||
@@ -110,24 +104,19 @@ class MoFileLoader extends FileLoader
|
||||
fseek($stream, $offset);
|
||||
$translated = fread($stream, $length);
|
||||
|
||||
if (strpos($translated, "\000") !== false) {
|
||||
if (str_contains($translated, "\000")) {
|
||||
$translated = explode("\000", $translated);
|
||||
}
|
||||
|
||||
$ids = array('singular' => $singularId, 'plural' => $pluralId);
|
||||
$ids = ['singular' => $singularId, 'plural' => $pluralId];
|
||||
$item = compact('ids', 'translated');
|
||||
|
||||
if (is_array($item['translated'])) {
|
||||
$messages[$item['ids']['singular']] = stripcslashes($item['translated'][0]);
|
||||
if (!empty($item['ids']['singular'])) {
|
||||
$id = $item['ids']['singular'];
|
||||
if (isset($item['ids']['plural'])) {
|
||||
$plurals = array();
|
||||
foreach ($item['translated'] as $plural => $translated) {
|
||||
$plurals[] = sprintf('{%d} %s', $plural, $translated);
|
||||
}
|
||||
$messages[$item['ids']['plural']] = stripcslashes(implode('|', $plurals));
|
||||
$id .= '|'.$item['ids']['plural'];
|
||||
}
|
||||
} elseif (!empty($item['ids']['singular'])) {
|
||||
$messages[$item['ids']['singular']] = stripcslashes($item['translated']);
|
||||
$messages[$id] = stripcslashes(implode('|', (array) $item['translated']));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,11 +129,8 @@ class MoFileLoader extends FileLoader
|
||||
* Reads an unsigned long from stream respecting endianness.
|
||||
*
|
||||
* @param resource $stream
|
||||
* @param bool $isBigEndian
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function readLong($stream, $isBigEndian)
|
||||
private function readLong($stream, bool $isBigEndian): int
|
||||
{
|
||||
$result = unpack($isBigEndian ? 'N1' : 'V1', fread($stream, 4));
|
||||
$result = current($result);
|
||||
|
||||
@@ -18,11 +18,25 @@ namespace Symfony\Component\Translation\Loader;
|
||||
*/
|
||||
class PhpFileLoader extends FileLoader
|
||||
{
|
||||
private static ?array $cache = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function loadResource($resource)
|
||||
protected function loadResource(string $resource): array
|
||||
{
|
||||
return require $resource;
|
||||
if ([] === self::$cache && \function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) || filter_var(\ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOLEAN))) {
|
||||
self::$cache = null;
|
||||
}
|
||||
|
||||
if (null === self::$cache) {
|
||||
return require $resource;
|
||||
}
|
||||
|
||||
if (isset(self::$cache[$resource])) {
|
||||
return self::$cache[$resource];
|
||||
}
|
||||
|
||||
return self::$cache[$resource] = require $resource;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
namespace Symfony\Component\Translation\Loader;
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2010, Union of RAD http://union-of-rad.org (http://lithify.me/)
|
||||
* @copyright Copyright (c) 2010, Union of RAD https://github.com/UnionOfRAD/lithium
|
||||
* @copyright Copyright (c) 2012, Clemens Tolboom
|
||||
*/
|
||||
class PoFileLoader extends FileLoader
|
||||
@@ -20,7 +20,7 @@ class PoFileLoader extends FileLoader
|
||||
/**
|
||||
* Parses portable object (PO) format.
|
||||
*
|
||||
* From http://www.gnu.org/software/gettext/manual/gettext.html#PO-Files
|
||||
* From https://www.gnu.org/software/gettext/manual/gettext.html#PO-Files
|
||||
* we should be able to parse files having:
|
||||
*
|
||||
* white-space
|
||||
@@ -60,57 +60,57 @@ class PoFileLoader extends FileLoader
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function loadResource($resource)
|
||||
protected function loadResource(string $resource): array
|
||||
{
|
||||
$stream = fopen($resource, 'r');
|
||||
|
||||
$defaults = array(
|
||||
'ids' => array(),
|
||||
$defaults = [
|
||||
'ids' => [],
|
||||
'translated' => null,
|
||||
);
|
||||
];
|
||||
|
||||
$messages = array();
|
||||
$messages = [];
|
||||
$item = $defaults;
|
||||
$flags = array();
|
||||
$flags = [];
|
||||
|
||||
while ($line = fgets($stream)) {
|
||||
$line = trim($line);
|
||||
|
||||
if ($line === '') {
|
||||
if ('' === $line) {
|
||||
// Whitespace indicated current item is done
|
||||
if (!in_array('fuzzy', $flags)) {
|
||||
if (!\in_array('fuzzy', $flags)) {
|
||||
$this->addMessage($messages, $item);
|
||||
}
|
||||
$item = $defaults;
|
||||
$flags = array();
|
||||
} elseif (substr($line, 0, 2) === '#,') {
|
||||
$flags = [];
|
||||
} elseif ('#,' === substr($line, 0, 2)) {
|
||||
$flags = array_map('trim', explode(',', substr($line, 2)));
|
||||
} elseif (substr($line, 0, 7) === 'msgid "') {
|
||||
} elseif ('msgid "' === substr($line, 0, 7)) {
|
||||
// We start a new msg so save previous
|
||||
// TODO: this fails when comments or contexts are added
|
||||
$this->addMessage($messages, $item);
|
||||
$item = $defaults;
|
||||
$item['ids']['singular'] = substr($line, 7, -1);
|
||||
} elseif (substr($line, 0, 8) === 'msgstr "') {
|
||||
} elseif ('msgstr "' === substr($line, 0, 8)) {
|
||||
$item['translated'] = substr($line, 8, -1);
|
||||
} elseif ($line[0] === '"') {
|
||||
} elseif ('"' === $line[0]) {
|
||||
$continues = isset($item['translated']) ? 'translated' : 'ids';
|
||||
|
||||
if (is_array($item[$continues])) {
|
||||
if (\is_array($item[$continues])) {
|
||||
end($item[$continues]);
|
||||
$item[$continues][key($item[$continues])] .= substr($line, 1, -1);
|
||||
} else {
|
||||
$item[$continues] .= substr($line, 1, -1);
|
||||
}
|
||||
} elseif (substr($line, 0, 14) === 'msgid_plural "') {
|
||||
} elseif ('msgid_plural "' === substr($line, 0, 14)) {
|
||||
$item['ids']['plural'] = substr($line, 14, -1);
|
||||
} elseif (substr($line, 0, 7) === 'msgstr[') {
|
||||
} elseif ('msgstr[' === substr($line, 0, 7)) {
|
||||
$size = strpos($line, ']');
|
||||
$item['translated'][(int) substr($line, 7, 1)] = substr($line, $size + 3, -1);
|
||||
}
|
||||
}
|
||||
// save last item
|
||||
if (!in_array('fuzzy', $flags)) {
|
||||
if (!\in_array('fuzzy', $flags)) {
|
||||
$this->addMessage($messages, $item);
|
||||
}
|
||||
fclose($stream);
|
||||
@@ -123,29 +123,27 @@ class PoFileLoader extends FileLoader
|
||||
*
|
||||
* A .po file could contain by error missing plural indexes. We need to
|
||||
* fix these before saving them.
|
||||
*
|
||||
* @param array $messages
|
||||
* @param array $item
|
||||
*/
|
||||
private function addMessage(array &$messages, array $item)
|
||||
{
|
||||
if (is_array($item['translated'])) {
|
||||
$messages[stripcslashes($item['ids']['singular'])] = stripcslashes($item['translated'][0]);
|
||||
if (!empty($item['ids']['singular'])) {
|
||||
$id = stripcslashes($item['ids']['singular']);
|
||||
if (isset($item['ids']['plural'])) {
|
||||
$plurals = $item['translated'];
|
||||
// PO are by definition indexed so sort by index.
|
||||
ksort($plurals);
|
||||
// Make sure every index is filled.
|
||||
end($plurals);
|
||||
$count = key($plurals);
|
||||
// Fill missing spots with '-'.
|
||||
$empties = array_fill(0, $count + 1, '-');
|
||||
$plurals += $empties;
|
||||
ksort($plurals);
|
||||
$messages[stripcslashes($item['ids']['plural'])] = stripcslashes(implode('|', $plurals));
|
||||
$id .= '|'.stripcslashes($item['ids']['plural']);
|
||||
}
|
||||
} elseif (!empty($item['ids']['singular'])) {
|
||||
$messages[stripcslashes($item['ids']['singular'])] = stripcslashes($item['translated']);
|
||||
|
||||
$translated = (array) $item['translated'];
|
||||
// PO are by definition indexed so sort by index.
|
||||
ksort($translated);
|
||||
// Make sure every index is filled.
|
||||
end($translated);
|
||||
$count = key($translated);
|
||||
// Fill missing spots with '-'.
|
||||
$empties = array_fill(0, $count + 1, '-');
|
||||
$translated += $empties;
|
||||
ksort($translated);
|
||||
|
||||
$messages[$id] = stripcslashes(implode('|', $translated));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,12 @@
|
||||
|
||||
namespace Symfony\Component\Translation\Loader;
|
||||
|
||||
use Symfony\Component\Config\Resource\FileResource;
|
||||
use Symfony\Component\Config\Util\XmlUtils;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\Exception\InvalidResourceException;
|
||||
use Symfony\Component\Translation\Exception\NotFoundResourceException;
|
||||
use Symfony\Component\Config\Resource\FileResource;
|
||||
use Symfony\Component\Translation\Exception\RuntimeException;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
|
||||
/**
|
||||
* QtFileLoader loads translations from QT Translations XML files.
|
||||
@@ -27,8 +28,12 @@ class QtFileLoader implements LoaderInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load($resource, $locale, $domain = 'messages')
|
||||
public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue
|
||||
{
|
||||
if (!class_exists(XmlUtils::class)) {
|
||||
throw new RuntimeException('Loading translations from the QT format requires the Symfony Config component.');
|
||||
}
|
||||
|
||||
if (!stream_is_local($resource)) {
|
||||
throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
|
||||
}
|
||||
@@ -50,7 +55,7 @@ class QtFileLoader implements LoaderInterface
|
||||
$nodes = $xpath->evaluate('//TS/context/name[text()="'.$domain.'"]');
|
||||
|
||||
$catalogue = new MessageCatalogue($locale);
|
||||
if ($nodes->length == 1) {
|
||||
if (1 == $nodes->length) {
|
||||
$translations = $nodes->item(0)->nextSibling->parentNode->parentNode->getElementsByTagName('message');
|
||||
foreach ($translations as $translation) {
|
||||
$translationValue = (string) $translation->getElementsByTagName('translation')->item(0)->nodeValue;
|
||||
@@ -65,7 +70,7 @@ class QtFileLoader implements LoaderInterface
|
||||
$translation = $translation->nextSibling;
|
||||
}
|
||||
|
||||
if (class_exists('Symfony\Component\Config\Resource\FileResource')) {
|
||||
if (class_exists(FileResource::class)) {
|
||||
$catalogue->addResource(new FileResource($resource));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,15 @@
|
||||
|
||||
namespace Symfony\Component\Translation\Loader;
|
||||
|
||||
use Symfony\Component\Config\Resource\FileResource;
|
||||
use Symfony\Component\Config\Util\Exception\InvalidXmlException;
|
||||
use Symfony\Component\Config\Util\Exception\XmlParsingException;
|
||||
use Symfony\Component\Config\Util\XmlUtils;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\Exception\InvalidResourceException;
|
||||
use Symfony\Component\Translation\Exception\NotFoundResourceException;
|
||||
use Symfony\Component\Translation\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Config\Resource\FileResource;
|
||||
use Symfony\Component\Translation\Exception\RuntimeException;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\Util\XliffUtils;
|
||||
|
||||
/**
|
||||
* XliffFileLoader loads translations from XLIFF files.
|
||||
@@ -28,36 +31,53 @@ class XliffFileLoader implements LoaderInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load($resource, $locale, $domain = 'messages')
|
||||
public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue
|
||||
{
|
||||
if (!stream_is_local($resource)) {
|
||||
throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
|
||||
if (!class_exists(XmlUtils::class)) {
|
||||
throw new RuntimeException('Loading translations from the Xliff format requires the Symfony Config component.');
|
||||
}
|
||||
|
||||
if (!file_exists($resource)) {
|
||||
throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
|
||||
if (!$this->isXmlString($resource)) {
|
||||
if (!stream_is_local($resource)) {
|
||||
throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
|
||||
}
|
||||
|
||||
if (!file_exists($resource)) {
|
||||
throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
|
||||
}
|
||||
|
||||
if (!is_file($resource)) {
|
||||
throw new InvalidResourceException(sprintf('This is neither a file nor an XLIFF string "%s".', $resource));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if ($this->isXmlString($resource)) {
|
||||
$dom = XmlUtils::parse($resource);
|
||||
} else {
|
||||
$dom = XmlUtils::loadFile($resource);
|
||||
}
|
||||
} catch (\InvalidArgumentException|XmlParsingException|InvalidXmlException $e) {
|
||||
throw new InvalidResourceException(sprintf('Unable to load "%s": ', $resource).$e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
if ($errors = XliffUtils::validateSchema($dom)) {
|
||||
throw new InvalidResourceException(sprintf('Invalid resource provided: "%s"; Errors: ', $resource).XliffUtils::getErrorsAsString($errors));
|
||||
}
|
||||
|
||||
$catalogue = new MessageCatalogue($locale);
|
||||
$this->extract($resource, $catalogue, $domain);
|
||||
$this->extract($dom, $catalogue, $domain);
|
||||
|
||||
if (class_exists('Symfony\Component\Config\Resource\FileResource')) {
|
||||
if (is_file($resource) && class_exists(FileResource::class)) {
|
||||
$catalogue->addResource(new FileResource($resource));
|
||||
}
|
||||
|
||||
return $catalogue;
|
||||
}
|
||||
|
||||
private function extract($resource, MessageCatalogue $catalogue, $domain)
|
||||
private function extract(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain)
|
||||
{
|
||||
try {
|
||||
$dom = XmlUtils::loadFile($resource);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
throw new InvalidResourceException(sprintf('Unable to load "%s": %s', $resource, $e->getMessage()), $e->getCode(), $e);
|
||||
}
|
||||
|
||||
$xliffVersion = $this->getVersionNumber($dom);
|
||||
$this->validateSchema($xliffVersion, $dom, $this->getSchema($xliffVersion));
|
||||
$xliffVersion = XliffUtils::getVersionNumber($dom);
|
||||
|
||||
if ('1.2' === $xliffVersion) {
|
||||
$this->extractXliff1($dom, $catalogue, $domain);
|
||||
@@ -70,93 +90,107 @@ class XliffFileLoader implements LoaderInterface
|
||||
|
||||
/**
|
||||
* Extract messages and metadata from DOMDocument into a MessageCatalogue.
|
||||
*
|
||||
* @param \DOMDocument $dom Source to extract messages and metadata
|
||||
* @param MessageCatalogue $catalogue Catalogue where we'll collect messages and metadata
|
||||
* @param string $domain The domain
|
||||
*/
|
||||
private function extractXliff1(\DOMDocument $dom, MessageCatalogue $catalogue, $domain)
|
||||
private function extractXliff1(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain)
|
||||
{
|
||||
$xml = simplexml_import_dom($dom);
|
||||
$encoding = strtoupper($dom->encoding);
|
||||
$encoding = $dom->encoding ? strtoupper($dom->encoding) : null;
|
||||
|
||||
$xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:1.2');
|
||||
foreach ($xml->xpath('//xliff:trans-unit') as $translation) {
|
||||
$attributes = $translation->attributes();
|
||||
$namespace = 'urn:oasis:names:tc:xliff:document:1.2';
|
||||
$xml->registerXPathNamespace('xliff', $namespace);
|
||||
|
||||
if (!(isset($attributes['resname']) || isset($translation->source))) {
|
||||
continue;
|
||||
}
|
||||
foreach ($xml->xpath('//xliff:file') as $file) {
|
||||
$fileAttributes = $file->attributes();
|
||||
|
||||
$source = isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source;
|
||||
// If the xlf file has another encoding specified, try to convert it because
|
||||
// simple_xml will always return utf-8 encoded values
|
||||
$target = $this->utf8ToCharset((string) (isset($translation->target) ? $translation->target : $source), $encoding);
|
||||
$file->registerXPathNamespace('xliff', $namespace);
|
||||
|
||||
$catalogue->set((string) $source, $target, $domain);
|
||||
foreach ($file->xpath('.//xliff:trans-unit') as $translation) {
|
||||
$attributes = $translation->attributes();
|
||||
|
||||
$metadata = array();
|
||||
if ($notes = $this->parseNotesMetadata($translation->note, $encoding)) {
|
||||
$metadata['notes'] = $notes;
|
||||
}
|
||||
|
||||
if (isset($translation->target) && $translation->target->attributes()) {
|
||||
$metadata['target-attributes'] = array();
|
||||
foreach ($translation->target->attributes() as $key => $value) {
|
||||
$metadata['target-attributes'][$key] = (string) $value;
|
||||
if (!(isset($attributes['resname']) || isset($translation->source))) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($attributes['id'])) {
|
||||
$metadata['id'] = (string) $attributes['id'];
|
||||
}
|
||||
$source = isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source;
|
||||
// If the xlf file has another encoding specified, try to convert it because
|
||||
// simple_xml will always return utf-8 encoded values
|
||||
$target = $this->utf8ToCharset((string) ($translation->target ?? $translation->source), $encoding);
|
||||
|
||||
$catalogue->setMetadata((string) $source, $metadata, $domain);
|
||||
$catalogue->set((string) $source, $target, $domain);
|
||||
|
||||
$metadata = [
|
||||
'source' => (string) $translation->source,
|
||||
'file' => [
|
||||
'original' => (string) $fileAttributes['original'],
|
||||
],
|
||||
];
|
||||
if ($notes = $this->parseNotesMetadata($translation->note, $encoding)) {
|
||||
$metadata['notes'] = $notes;
|
||||
}
|
||||
|
||||
if (isset($translation->target) && $translation->target->attributes()) {
|
||||
$metadata['target-attributes'] = [];
|
||||
foreach ($translation->target->attributes() as $key => $value) {
|
||||
$metadata['target-attributes'][$key] = (string) $value;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($attributes['id'])) {
|
||||
$metadata['id'] = (string) $attributes['id'];
|
||||
}
|
||||
|
||||
$catalogue->setMetadata((string) $source, $metadata, $domain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \DOMDocument $dom
|
||||
* @param MessageCatalogue $catalogue
|
||||
* @param string $domain
|
||||
*/
|
||||
private function extractXliff2(\DOMDocument $dom, MessageCatalogue $catalogue, $domain)
|
||||
private function extractXliff2(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain)
|
||||
{
|
||||
$xml = simplexml_import_dom($dom);
|
||||
$encoding = strtoupper($dom->encoding);
|
||||
$encoding = $dom->encoding ? strtoupper($dom->encoding) : null;
|
||||
|
||||
$xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:2.0');
|
||||
|
||||
foreach ($xml->xpath('//xliff:unit/xliff:segment') as $segment) {
|
||||
$source = $segment->source;
|
||||
foreach ($xml->xpath('//xliff:unit') as $unit) {
|
||||
foreach ($unit->segment as $segment) {
|
||||
$attributes = $unit->attributes();
|
||||
$source = $attributes['name'] ?? $segment->source;
|
||||
|
||||
// If the xlf file has another encoding specified, try to convert it because
|
||||
// simple_xml will always return utf-8 encoded values
|
||||
$target = $this->utf8ToCharset((string) (isset($segment->target) ? $segment->target : $source), $encoding);
|
||||
// If the xlf file has another encoding specified, try to convert it because
|
||||
// simple_xml will always return utf-8 encoded values
|
||||
$target = $this->utf8ToCharset((string) ($segment->target ?? $segment->source), $encoding);
|
||||
|
||||
$catalogue->set((string) $source, $target, $domain);
|
||||
$catalogue->set((string) $source, $target, $domain);
|
||||
|
||||
$metadata = array();
|
||||
if (isset($segment->target) && $segment->target->attributes()) {
|
||||
$metadata['target-attributes'] = array();
|
||||
foreach ($segment->target->attributes() as $key => $value) {
|
||||
$metadata['target-attributes'][$key] = (string) $value;
|
||||
$metadata = [];
|
||||
if (isset($segment->target) && $segment->target->attributes()) {
|
||||
$metadata['target-attributes'] = [];
|
||||
foreach ($segment->target->attributes() as $key => $value) {
|
||||
$metadata['target-attributes'][$key] = (string) $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$catalogue->setMetadata((string) $source, $metadata, $domain);
|
||||
if (isset($unit->notes)) {
|
||||
$metadata['notes'] = [];
|
||||
foreach ($unit->notes->note as $noteNode) {
|
||||
$note = [];
|
||||
foreach ($noteNode->attributes() as $key => $value) {
|
||||
$note[$key] = (string) $value;
|
||||
}
|
||||
$note['content'] = (string) $noteNode;
|
||||
$metadata['notes'][] = $note;
|
||||
}
|
||||
}
|
||||
|
||||
$catalogue->setMetadata((string) $source, $metadata, $domain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a UTF8 string to the specified encoding.
|
||||
*
|
||||
* @param string $content String to decode
|
||||
* @param string $encoding Target encoding
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function utf8ToCharset($content, $encoding = null)
|
||||
private function utf8ToCharset(string $content, string $encoding = null): string
|
||||
{
|
||||
if ('UTF-8' !== $encoding && !empty($encoding)) {
|
||||
return mb_convert_encoding($content, $encoding, 'UTF-8');
|
||||
@@ -165,145 +199,9 @@ class XliffFileLoader implements LoaderInterface
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and parses the given file into a DOMDocument.
|
||||
*
|
||||
* @param string $file
|
||||
* @param \DOMDocument $dom
|
||||
* @param string $schema source of the schema
|
||||
*
|
||||
* @throws InvalidResourceException
|
||||
*/
|
||||
private function validateSchema($file, \DOMDocument $dom, $schema)
|
||||
private function parseNotesMetadata(\SimpleXMLElement $noteElement = null, string $encoding = null): array
|
||||
{
|
||||
$internalErrors = libxml_use_internal_errors(true);
|
||||
|
||||
$disableEntities = libxml_disable_entity_loader(false);
|
||||
|
||||
if (!@$dom->schemaValidateSource($schema)) {
|
||||
libxml_disable_entity_loader($disableEntities);
|
||||
|
||||
throw new InvalidResourceException(sprintf('Invalid resource provided: "%s"; Errors: %s', $file, implode("\n", $this->getXmlErrors($internalErrors))));
|
||||
}
|
||||
|
||||
libxml_disable_entity_loader($disableEntities);
|
||||
|
||||
$dom->normalizeDocument();
|
||||
|
||||
libxml_clear_errors();
|
||||
libxml_use_internal_errors($internalErrors);
|
||||
}
|
||||
|
||||
private function getSchema($xliffVersion)
|
||||
{
|
||||
if ('1.2' === $xliffVersion) {
|
||||
$schemaSource = file_get_contents(__DIR__.'/schema/dic/xliff-core/xliff-core-1.2-strict.xsd');
|
||||
$xmlUri = 'http://www.w3.org/2001/xml.xsd';
|
||||
} elseif ('2.0' === $xliffVersion) {
|
||||
$schemaSource = file_get_contents(__DIR__.'/schema/dic/xliff-core/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 $this->fixXmlLocation($schemaSource, $xmlUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internally changes the URI of a dependent xsd to be loaded locally.
|
||||
*
|
||||
* @param string $schemaSource Current content of schema file
|
||||
* @param string $xmlUri External URI of XML to convert to local
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function fixXmlLocation($schemaSource, $xmlUri)
|
||||
{
|
||||
$newPath = str_replace('\\', '/', __DIR__).'/schema/dic/xliff-core/xml.xsd';
|
||||
$parts = explode('/', $newPath);
|
||||
if (0 === stripos($newPath, 'phar://')) {
|
||||
$tmpfile = tempnam(sys_get_temp_dir(), 'sf2');
|
||||
if ($tmpfile) {
|
||||
copy($newPath, $tmpfile);
|
||||
$parts = explode('/', str_replace('\\', '/', $tmpfile));
|
||||
}
|
||||
}
|
||||
|
||||
$drive = '\\' === DIRECTORY_SEPARATOR ? array_shift($parts).'/' : '';
|
||||
$newPath = 'file:///'.$drive.implode('/', array_map('rawurlencode', $parts));
|
||||
|
||||
return str_replace($xmlUri, $newPath, $schemaSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the XML errors of the internal XML parser.
|
||||
*
|
||||
* @param bool $internalErrors
|
||||
*
|
||||
* @return array An array of errors
|
||||
*/
|
||||
private function getXmlErrors($internalErrors)
|
||||
{
|
||||
$errors = array();
|
||||
foreach (libxml_get_errors() as $error) {
|
||||
$errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
|
||||
LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
|
||||
$error->code,
|
||||
trim($error->message),
|
||||
$error->file ?: 'n/a',
|
||||
$error->line,
|
||||
$error->column
|
||||
);
|
||||
}
|
||||
|
||||
libxml_clear_errors();
|
||||
libxml_use_internal_errors($internalErrors);
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets xliff file version based on the root "version" attribute.
|
||||
* Defaults to 1.2 for backwards compatibility.
|
||||
*
|
||||
* @param \DOMDocument $dom
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getVersionNumber(\DOMDocument $dom)
|
||||
{
|
||||
/** @var \DOMNode $xliff */
|
||||
foreach ($dom->getElementsByTagName('xliff') as $xliff) {
|
||||
$version = $xliff->attributes->getNamedItem('version');
|
||||
if ($version) {
|
||||
return $version->nodeValue;
|
||||
}
|
||||
|
||||
$namespace = $xliff->attributes->getNamedItem('xmlns');
|
||||
if ($namespace) {
|
||||
if (substr_compare('urn:oasis:names:tc:xliff:document:', $namespace->nodeValue, 0, 34) !== 0) {
|
||||
throw new InvalidArgumentException(sprintf('Not a valid XLIFF namespace "%s"', $namespace));
|
||||
}
|
||||
|
||||
return substr($namespace, 34);
|
||||
}
|
||||
}
|
||||
|
||||
// Falls back to v1.2
|
||||
return '1.2';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \SimpleXMLElement|null $noteElement
|
||||
* @param string|null $encoding
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function parseNotesMetadata(\SimpleXMLElement $noteElement = null, $encoding = null)
|
||||
{
|
||||
$notes = array();
|
||||
$notes = [];
|
||||
|
||||
if (null === $noteElement) {
|
||||
return $notes;
|
||||
@@ -312,7 +210,7 @@ class XliffFileLoader implements LoaderInterface
|
||||
/** @var \SimpleXMLElement $xmlNote */
|
||||
foreach ($noteElement as $xmlNote) {
|
||||
$noteAttributes = $xmlNote->attributes();
|
||||
$note = array('content' => $this->utf8ToCharset((string) $xmlNote, $encoding));
|
||||
$note = ['content' => $this->utf8ToCharset((string) $xmlNote, $encoding)];
|
||||
if (isset($noteAttributes['priority'])) {
|
||||
$note['priority'] = (int) $noteAttributes['priority'];
|
||||
}
|
||||
@@ -326,4 +224,9 @@ class XliffFileLoader implements LoaderInterface
|
||||
|
||||
return $notes;
|
||||
}
|
||||
|
||||
private function isXmlString(string $resource): bool
|
||||
{
|
||||
return 0 === strpos($resource, '<?xml');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ namespace Symfony\Component\Translation\Loader;
|
||||
|
||||
use Symfony\Component\Translation\Exception\InvalidResourceException;
|
||||
use Symfony\Component\Translation\Exception\LogicException;
|
||||
use Symfony\Component\Yaml\Parser as YamlParser;
|
||||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
use Symfony\Component\Yaml\Parser as YamlParser;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
/**
|
||||
@@ -29,10 +29,10 @@ class YamlFileLoader extends FileLoader
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function loadResource($resource)
|
||||
protected function loadResource(string $resource): array
|
||||
{
|
||||
if (null === $this->yamlParser) {
|
||||
if (!class_exists('Symfony\Component\Yaml\Parser')) {
|
||||
if (!class_exists(\Symfony\Component\Yaml\Parser::class)) {
|
||||
throw new LogicException('Loading translations from the YAML format requires the Symfony Yaml component.');
|
||||
}
|
||||
|
||||
@@ -40,11 +40,15 @@ class YamlFileLoader extends FileLoader
|
||||
}
|
||||
|
||||
try {
|
||||
$messages = $this->yamlParser->parse(file_get_contents($resource), Yaml::PARSE_KEYS_AS_STRINGS);
|
||||
$messages = $this->yamlParser->parseFile($resource, Yaml::PARSE_CONSTANT);
|
||||
} catch (ParseException $e) {
|
||||
throw new InvalidResourceException(sprintf('Error parsing YAML, invalid file "%s"', $resource), 0, $e);
|
||||
throw new InvalidResourceException(sprintf('The file "%s" does not contain valid YAML: ', $resource).$e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
return $messages;
|
||||
if (null !== $messages && !\is_array($messages)) {
|
||||
throw new InvalidResourceException(sprintf('Unable to load file "%s".', $resource));
|
||||
}
|
||||
|
||||
return $messages ?: [];
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
76
vendor/symfony/translation/LoggingTranslator.php
vendored
76
vendor/symfony/translation/LoggingTranslator.php
vendored
@@ -13,30 +13,24 @@ namespace Symfony\Component\Translation;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Translation\Exception\InvalidArgumentException;
|
||||
use Symfony\Contracts\Translation\LocaleAwareInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
|
||||
*/
|
||||
class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface
|
||||
class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface
|
||||
{
|
||||
/**
|
||||
* @var TranslatorInterface|TranslatorBagInterface
|
||||
*/
|
||||
private $translator;
|
||||
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* @param TranslatorInterface $translator The translator must implement TranslatorBagInterface
|
||||
* @param LoggerInterface $logger
|
||||
* @param TranslatorInterface&TranslatorBagInterface&LocaleAwareInterface $translator The translator must implement TranslatorBagInterface
|
||||
*/
|
||||
public function __construct(TranslatorInterface $translator, LoggerInterface $logger)
|
||||
{
|
||||
if (!$translator instanceof TranslatorBagInterface) {
|
||||
throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface and TranslatorBagInterface.', get_class($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;
|
||||
@@ -46,9 +40,9 @@ class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function trans($id, array $parameters = array(), $domain = null, $locale = null)
|
||||
public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null): string
|
||||
{
|
||||
$trans = $this->translator->trans($id, $parameters, $domain, $locale);
|
||||
$trans = $this->translator->trans($id = (string) $id, $parameters, $domain, $locale);
|
||||
$this->log($id, $domain, $locale);
|
||||
|
||||
return $trans;
|
||||
@@ -57,26 +51,21 @@ class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null)
|
||||
{
|
||||
$trans = $this->translator->transChoice($id, $number, $parameters, $domain, $locale);
|
||||
$this->log($id, $domain, $locale);
|
||||
|
||||
return $trans;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setLocale($locale)
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLocale()
|
||||
public function getLocale(): string
|
||||
{
|
||||
return $this->translator->getLocale();
|
||||
}
|
||||
@@ -84,56 +73,57 @@ class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getCatalogue($locale = null)
|
||||
public function getCatalogue(string $locale = null): MessageCatalogueInterface
|
||||
{
|
||||
return $this->translator->getCatalogue($locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the fallback locales.
|
||||
*
|
||||
* @return array $locales The fallback locales
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFallbackLocales()
|
||||
public function getCatalogues(): array
|
||||
{
|
||||
return $this->translator->getCatalogues();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the fallback locales.
|
||||
*/
|
||||
public function getFallbackLocales(): array
|
||||
{
|
||||
if ($this->translator instanceof Translator || method_exists($this->translator, 'getFallbackLocales')) {
|
||||
return $this->translator->getFallbackLocales();
|
||||
}
|
||||
|
||||
return array();
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes through all unknown calls onto the translator object.
|
||||
*/
|
||||
public function __call($method, $args)
|
||||
public function __call(string $method, array $args)
|
||||
{
|
||||
return call_user_func_array(array($this->translator, $method), $args);
|
||||
return $this->translator->{$method}(...$args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs for missing translations.
|
||||
*
|
||||
* @param string $id
|
||||
* @param string|null $domain
|
||||
* @param string|null $locale
|
||||
*/
|
||||
private function log($id, $domain, $locale)
|
||||
private function log(string $id, ?string $domain, ?string $locale)
|
||||
{
|
||||
if (null === $domain) {
|
||||
$domain = 'messages';
|
||||
}
|
||||
|
||||
$id = (string) $id;
|
||||
$catalogue = $this->translator->getCatalogue($locale);
|
||||
if ($catalogue->defines($id, $domain)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($catalogue->has($id, $domain)) {
|
||||
$this->logger->debug('Translation use fallback catalogue.', array('id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()));
|
||||
$this->logger->debug('Translation use fallback catalogue.', ['id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()]);
|
||||
} else {
|
||||
$this->logger->warning('Translation not found.', array('id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()));
|
||||
$this->logger->warning('Translation not found.', ['id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
126
vendor/symfony/translation/MessageCatalogue.php
vendored
126
vendor/symfony/translation/MessageCatalogue.php
vendored
@@ -15,26 +15,21 @@ use Symfony\Component\Config\Resource\ResourceInterface;
|
||||
use Symfony\Component\Translation\Exception\LogicException;
|
||||
|
||||
/**
|
||||
* MessageCatalogue.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterface
|
||||
{
|
||||
private $messages = array();
|
||||
private $metadata = array();
|
||||
private $resources = array();
|
||||
private $locale;
|
||||
private $fallbackCatalogue;
|
||||
private $parent;
|
||||
private array $messages = [];
|
||||
private array $metadata = [];
|
||||
private array $resources = [];
|
||||
private string $locale;
|
||||
private $fallbackCatalogue = null;
|
||||
private ?self $parent = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $locale The locale
|
||||
* @param array $messages An array of messages classified by domain
|
||||
* @param array $messages An array of messages classified by domain
|
||||
*/
|
||||
public function __construct($locale, array $messages = array())
|
||||
public function __construct(string $locale, array $messages = [])
|
||||
{
|
||||
$this->locale = $locale;
|
||||
$this->messages = $messages;
|
||||
@@ -43,7 +38,7 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getLocale()
|
||||
public function getLocale(): string
|
||||
{
|
||||
return $this->locale;
|
||||
}
|
||||
@@ -51,37 +46,62 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getDomains()
|
||||
public function getDomains(): array
|
||||
{
|
||||
return array_keys($this->messages);
|
||||
}
|
||||
$domains = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function all($domain = null)
|
||||
{
|
||||
if (null === $domain) {
|
||||
return $this->messages;
|
||||
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 isset($this->messages[$domain]) ? $this->messages[$domain] : array();
|
||||
return array_values($domains);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function set($id, $translation, $domain = 'messages')
|
||||
public function all(string $domain = null): array
|
||||
{
|
||||
$this->add(array($id => $translation), $domain);
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function has($id, $domain = 'messages')
|
||||
public function set(string $id, string $translation, string $domain = 'messages')
|
||||
{
|
||||
if (isset($this->messages[$domain][$id])) {
|
||||
$this->add([$id => $translation], $domain);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function has(string $id, string $domain = 'messages'): bool
|
||||
{
|
||||
if (isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -95,16 +115,20 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function defines($id, $domain = 'messages')
|
||||
public function defines(string $id, string $domain = 'messages'): bool
|
||||
{
|
||||
return isset($this->messages[$domain][$id]);
|
||||
return isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function get($id, $domain = 'messages')
|
||||
public function get(string $id, string $domain = 'messages'): string
|
||||
{
|
||||
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];
|
||||
}
|
||||
@@ -119,9 +143,9 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function replace($messages, $domain = 'messages')
|
||||
public function replace(array $messages, string $domain = 'messages')
|
||||
{
|
||||
$this->messages[$domain] = array();
|
||||
unset($this->messages[$domain], $this->messages[$domain.self::INTL_DOMAIN_SUFFIX]);
|
||||
|
||||
$this->add($messages, $domain);
|
||||
}
|
||||
@@ -129,12 +153,16 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function add($messages, $domain = 'messages')
|
||||
public function add(array $messages, string $domain = 'messages')
|
||||
{
|
||||
if (!isset($this->messages[$domain])) {
|
||||
$this->messages[$domain] = $messages;
|
||||
} else {
|
||||
$this->messages[$domain] = array_replace($this->messages[$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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,10 +172,14 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
|
||||
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));
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -196,7 +228,7 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFallbackCatalogue()
|
||||
public function getFallbackCatalogue(): ?MessageCatalogueInterface
|
||||
{
|
||||
return $this->fallbackCatalogue;
|
||||
}
|
||||
@@ -204,7 +236,7 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getResources()
|
||||
public function getResources(): array
|
||||
{
|
||||
return array_values($this->resources);
|
||||
}
|
||||
@@ -220,7 +252,7 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getMetadata($key = '', $domain = 'messages')
|
||||
public function getMetadata(string $key = '', string $domain = 'messages'): mixed
|
||||
{
|
||||
if ('' == $domain) {
|
||||
return $this->metadata;
|
||||
@@ -235,12 +267,14 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
|
||||
return $this->metadata[$domain][$key];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setMetadata($key, $value, $domain = 'messages')
|
||||
public function setMetadata(string $key, mixed $value, string $domain = 'messages')
|
||||
{
|
||||
$this->metadata[$domain][$key] = $value;
|
||||
}
|
||||
@@ -248,10 +282,10 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function deleteMetadata($key = '', $domain = 'messages')
|
||||
public function deleteMetadata(string $key = '', string $domain = 'messages')
|
||||
{
|
||||
if ('' == $domain) {
|
||||
$this->metadata = array();
|
||||
$this->metadata = [];
|
||||
} elseif ('' == $key) {
|
||||
unset($this->metadata[$domain]);
|
||||
} else {
|
||||
|
||||
@@ -20,19 +20,17 @@ use Symfony\Component\Config\Resource\ResourceInterface;
|
||||
*/
|
||||
interface MessageCatalogueInterface
|
||||
{
|
||||
public const INTL_DOMAIN_SUFFIX = '+intl-icu';
|
||||
|
||||
/**
|
||||
* Gets the catalogue locale.
|
||||
*
|
||||
* @return string The locale
|
||||
*/
|
||||
public function getLocale();
|
||||
public function getLocale(): string;
|
||||
|
||||
/**
|
||||
* Gets the domains.
|
||||
*
|
||||
* @return array An array of domains
|
||||
*/
|
||||
public function getDomains();
|
||||
public function getDomains(): array;
|
||||
|
||||
/**
|
||||
* Gets the messages within a given domain.
|
||||
@@ -40,10 +38,8 @@ interface MessageCatalogueInterface
|
||||
* If $domain is null, it returns all messages.
|
||||
*
|
||||
* @param string $domain The domain name
|
||||
*
|
||||
* @return array An array of messages
|
||||
*/
|
||||
public function all($domain = null);
|
||||
public function all(string $domain = null): array;
|
||||
|
||||
/**
|
||||
* Sets a message translation.
|
||||
@@ -52,37 +48,31 @@ interface MessageCatalogueInterface
|
||||
* @param string $translation The messages translation
|
||||
* @param string $domain The domain name
|
||||
*/
|
||||
public function set($id, $translation, $domain = 'messages');
|
||||
public function set(string $id, string $translation, string $domain = 'messages');
|
||||
|
||||
/**
|
||||
* Checks if a message has a translation.
|
||||
*
|
||||
* @param string $id The message id
|
||||
* @param string $domain The domain name
|
||||
*
|
||||
* @return bool true if the message has a translation, false otherwise
|
||||
*/
|
||||
public function has($id, $domain = 'messages');
|
||||
public function has(string $id, string $domain = 'messages'): bool;
|
||||
|
||||
/**
|
||||
* Checks if a message has a translation (it does not take into account the fallback mechanism).
|
||||
*
|
||||
* @param string $id The message id
|
||||
* @param string $domain The domain name
|
||||
*
|
||||
* @return bool true if the message has a translation, false otherwise
|
||||
*/
|
||||
public function defines($id, $domain = 'messages');
|
||||
public function defines(string $id, string $domain = 'messages'): bool;
|
||||
|
||||
/**
|
||||
* Gets a message translation.
|
||||
*
|
||||
* @param string $id The message id
|
||||
* @param string $domain The domain name
|
||||
*
|
||||
* @return string The message translation
|
||||
*/
|
||||
public function get($id, $domain = 'messages');
|
||||
public function get(string $id, string $domain = 'messages'): string;
|
||||
|
||||
/**
|
||||
* Sets translations for a given domain.
|
||||
@@ -90,7 +80,7 @@ interface MessageCatalogueInterface
|
||||
* @param array $messages An array of translations
|
||||
* @param string $domain The domain name
|
||||
*/
|
||||
public function replace($messages, $domain = 'messages');
|
||||
public function replace(array $messages, string $domain = 'messages');
|
||||
|
||||
/**
|
||||
* Adds translations for a given domain.
|
||||
@@ -98,45 +88,37 @@ interface MessageCatalogueInterface
|
||||
* @param array $messages An array of translations
|
||||
* @param string $domain The domain name
|
||||
*/
|
||||
public function add($messages, $domain = 'messages');
|
||||
public function add(array $messages, string $domain = 'messages');
|
||||
|
||||
/**
|
||||
* Merges translations from the given Catalogue into the current one.
|
||||
*
|
||||
* The two catalogues must have the same locale.
|
||||
*
|
||||
* @param self $catalogue
|
||||
*/
|
||||
public function addCatalogue(MessageCatalogueInterface $catalogue);
|
||||
public function addCatalogue(self $catalogue);
|
||||
|
||||
/**
|
||||
* Merges translations from the given Catalogue into the current one
|
||||
* only when the translation does not exist.
|
||||
*
|
||||
* This is used to provide default translations when they do not exist for the current locale.
|
||||
*
|
||||
* @param self $catalogue
|
||||
*/
|
||||
public function addFallbackCatalogue(MessageCatalogueInterface $catalogue);
|
||||
public function addFallbackCatalogue(self $catalogue);
|
||||
|
||||
/**
|
||||
* Gets the fallback catalogue.
|
||||
*
|
||||
* @return self|null A MessageCatalogueInterface instance or null when no fallback has been set
|
||||
*/
|
||||
public function getFallbackCatalogue();
|
||||
public function getFallbackCatalogue(): ?self;
|
||||
|
||||
/**
|
||||
* Returns an array of resources loaded to build this collection.
|
||||
*
|
||||
* @return ResourceInterface[] An array of resources
|
||||
* @return ResourceInterface[]
|
||||
*/
|
||||
public function getResources();
|
||||
public function getResources(): array;
|
||||
|
||||
/**
|
||||
* Adds a resource for this collection.
|
||||
*
|
||||
* @param ResourceInterface $resource A resource instance
|
||||
*/
|
||||
public function addResource(ResourceInterface $resource);
|
||||
}
|
||||
|
||||
88
vendor/symfony/translation/MessageSelector.php
vendored
88
vendor/symfony/translation/MessageSelector.php
vendored
@@ -1,88 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation;
|
||||
|
||||
use Symfony\Component\Translation\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* MessageSelector.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Bernhard Schussek <bschussek@gmail.com>
|
||||
*/
|
||||
class MessageSelector
|
||||
{
|
||||
/**
|
||||
* Given a message with different plural translations separated by a
|
||||
* pipe (|), this method returns the correct portion of the message based
|
||||
* on the given number, locale and the pluralization rules in the message
|
||||
* itself.
|
||||
*
|
||||
* The message supports two different types of pluralization rules:
|
||||
*
|
||||
* interval: {0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples
|
||||
* indexed: There is one apple|There are %count% apples
|
||||
*
|
||||
* The indexed solution can also contain labels (e.g. one: There is one apple).
|
||||
* This is purely for making the translations more clear - it does not
|
||||
* affect the functionality.
|
||||
*
|
||||
* The two methods can also be mixed:
|
||||
* {0} There are no apples|one: There is one apple|more: There are %count% apples
|
||||
*
|
||||
* @param string $message The message being translated
|
||||
* @param int $number The number of items represented for the message
|
||||
* @param string $locale The locale to use for choosing
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function choose($message, $number, $locale)
|
||||
{
|
||||
preg_match_all('/(?:\|\||[^\|])++/', $message, $parts);
|
||||
$explicitRules = array();
|
||||
$standardRules = array();
|
||||
foreach ($parts[0] as $part) {
|
||||
$part = trim(str_replace('||', '|', $part));
|
||||
|
||||
if (preg_match('/^(?P<interval>'.Interval::getIntervalRegexp().')\s*(?P<message>.*?)$/xs', $part, $matches)) {
|
||||
$explicitRules[$matches['interval']] = $matches['message'];
|
||||
} elseif (preg_match('/^\w+\:\s*(.*?)$/', $part, $matches)) {
|
||||
$standardRules[] = $matches[1];
|
||||
} else {
|
||||
$standardRules[] = $part;
|
||||
}
|
||||
}
|
||||
|
||||
// try to match an explicit rule, then fallback to the standard ones
|
||||
foreach ($explicitRules as $interval => $m) {
|
||||
if (Interval::test($number, $interval)) {
|
||||
return $m;
|
||||
}
|
||||
}
|
||||
|
||||
$position = PluralizationRules::get($number, $locale);
|
||||
|
||||
if (!isset($standardRules[$position])) {
|
||||
// when there's exactly one rule given, and that rule is a standard
|
||||
// rule, use this rule
|
||||
if (1 === count($parts[0]) && isset($standardRules[0])) {
|
||||
return $standardRules[0];
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException(sprintf('Unable to choose a translation for "%s" with locale "%s" for value "%d". Double check that this translation has the correct plural options (e.g. "There is one apple|There are %%count%% apples").', $message, $locale, $number));
|
||||
}
|
||||
|
||||
return $standardRules[$position];
|
||||
}
|
||||
}
|
||||
@@ -25,30 +25,20 @@ interface MetadataAwareInterface
|
||||
* domain and then by key. Passing an empty key will return an array with all
|
||||
* metadata for the given domain.
|
||||
*
|
||||
* @param string $key The key
|
||||
* @param string $domain The domain name
|
||||
*
|
||||
* @return mixed The value that was set or an array with the domains/keys or null
|
||||
*/
|
||||
public function getMetadata($key = '', $domain = 'messages');
|
||||
public function getMetadata(string $key = '', string $domain = 'messages'): mixed;
|
||||
|
||||
/**
|
||||
* Adds metadata to a message domain.
|
||||
*
|
||||
* @param string $key The key
|
||||
* @param mixed $value The value
|
||||
* @param string $domain The domain name
|
||||
*/
|
||||
public function setMetadata($key, $value, $domain = 'messages');
|
||||
public function setMetadata(string $key, mixed $value, string $domain = 'messages');
|
||||
|
||||
/**
|
||||
* Deletes metadata for the given key and domain.
|
||||
*
|
||||
* Passing an empty domain will delete all metadata. Passing an empty key will
|
||||
* delete all metadata for the given domain.
|
||||
*
|
||||
* @param string $key The key
|
||||
* @param string $domain The domain name
|
||||
*/
|
||||
public function deleteMetadata($key = '', $domain = 'messages');
|
||||
public function deleteMetadata(string $key = '', string $domain = 'messages');
|
||||
}
|
||||
|
||||
209
vendor/symfony/translation/PluralizationRules.php
vendored
209
vendor/symfony/translation/PluralizationRules.php
vendored
@@ -1,209 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation;
|
||||
|
||||
/**
|
||||
* Returns the plural rules for a given locale.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class PluralizationRules
|
||||
{
|
||||
private static $rules = array();
|
||||
|
||||
/**
|
||||
* Returns the plural position to use for the given locale and number.
|
||||
*
|
||||
* @param int $number The number
|
||||
* @param string $locale The locale
|
||||
*
|
||||
* @return int The plural position
|
||||
*/
|
||||
public static function get($number, $locale)
|
||||
{
|
||||
if ('pt_BR' === $locale) {
|
||||
// temporary set a locale for brazilian
|
||||
$locale = 'xbr';
|
||||
}
|
||||
|
||||
if (strlen($locale) > 3) {
|
||||
$locale = substr($locale, 0, -strlen(strrchr($locale, '_')));
|
||||
}
|
||||
|
||||
if (isset(self::$rules[$locale])) {
|
||||
$return = call_user_func(self::$rules[$locale], $number);
|
||||
|
||||
if (!is_int($return) || $return < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/*
|
||||
* The plural rules are derived from code of the Zend Framework (2010-09-25),
|
||||
* which is subject to the new BSD license (http://framework.zend.com/license/new-bsd).
|
||||
* Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
|
||||
*/
|
||||
switch ($locale) {
|
||||
case 'az':
|
||||
case 'bo':
|
||||
case 'dz':
|
||||
case 'id':
|
||||
case 'ja':
|
||||
case 'jv':
|
||||
case 'ka':
|
||||
case 'km':
|
||||
case 'kn':
|
||||
case 'ko':
|
||||
case 'ms':
|
||||
case 'th':
|
||||
case 'tr':
|
||||
case 'vi':
|
||||
case 'zh':
|
||||
return 0;
|
||||
break;
|
||||
|
||||
case 'af':
|
||||
case 'bn':
|
||||
case 'bg':
|
||||
case 'ca':
|
||||
case 'da':
|
||||
case 'de':
|
||||
case 'el':
|
||||
case 'en':
|
||||
case 'eo':
|
||||
case 'es':
|
||||
case 'et':
|
||||
case 'eu':
|
||||
case 'fa':
|
||||
case 'fi':
|
||||
case 'fo':
|
||||
case 'fur':
|
||||
case 'fy':
|
||||
case 'gl':
|
||||
case 'gu':
|
||||
case 'ha':
|
||||
case 'he':
|
||||
case 'hu':
|
||||
case 'is':
|
||||
case 'it':
|
||||
case 'ku':
|
||||
case 'lb':
|
||||
case 'ml':
|
||||
case 'mn':
|
||||
case 'mr':
|
||||
case 'nah':
|
||||
case 'nb':
|
||||
case 'ne':
|
||||
case 'nl':
|
||||
case 'nn':
|
||||
case 'no':
|
||||
case 'om':
|
||||
case 'or':
|
||||
case 'pa':
|
||||
case 'pap':
|
||||
case 'ps':
|
||||
case 'pt':
|
||||
case 'so':
|
||||
case 'sq':
|
||||
case 'sv':
|
||||
case 'sw':
|
||||
case 'ta':
|
||||
case 'te':
|
||||
case 'tk':
|
||||
case 'ur':
|
||||
case 'zu':
|
||||
return ($number == 1) ? 0 : 1;
|
||||
|
||||
case 'am':
|
||||
case 'bh':
|
||||
case 'fil':
|
||||
case 'fr':
|
||||
case 'gun':
|
||||
case 'hi':
|
||||
case 'hy':
|
||||
case 'ln':
|
||||
case 'mg':
|
||||
case 'nso':
|
||||
case 'xbr':
|
||||
case 'ti':
|
||||
case 'wa':
|
||||
return (($number == 0) || ($number == 1)) ? 0 : 1;
|
||||
|
||||
case 'be':
|
||||
case 'bs':
|
||||
case 'hr':
|
||||
case 'ru':
|
||||
case 'sr':
|
||||
case 'uk':
|
||||
return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2);
|
||||
|
||||
case 'cs':
|
||||
case 'sk':
|
||||
return ($number == 1) ? 0 : ((($number >= 2) && ($number <= 4)) ? 1 : 2);
|
||||
|
||||
case 'ga':
|
||||
return ($number == 1) ? 0 : (($number == 2) ? 1 : 2);
|
||||
|
||||
case 'lt':
|
||||
return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2);
|
||||
|
||||
case 'sl':
|
||||
return ($number % 100 == 1) ? 0 : (($number % 100 == 2) ? 1 : ((($number % 100 == 3) || ($number % 100 == 4)) ? 2 : 3));
|
||||
|
||||
case 'mk':
|
||||
return ($number % 10 == 1) ? 0 : 1;
|
||||
|
||||
case 'mt':
|
||||
return ($number == 1) ? 0 : ((($number == 0) || (($number % 100 > 1) && ($number % 100 < 11))) ? 1 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 2 : 3));
|
||||
|
||||
case 'lv':
|
||||
return ($number == 0) ? 0 : ((($number % 10 == 1) && ($number % 100 != 11)) ? 1 : 2);
|
||||
|
||||
case 'pl':
|
||||
return ($number == 1) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 1 : 2);
|
||||
|
||||
case 'cy':
|
||||
return ($number == 1) ? 0 : (($number == 2) ? 1 : ((($number == 8) || ($number == 11)) ? 2 : 3));
|
||||
|
||||
case 'ro':
|
||||
return ($number == 1) ? 0 : ((($number == 0) || (($number % 100 > 0) && ($number % 100 < 20))) ? 1 : 2);
|
||||
|
||||
case 'ar':
|
||||
return ($number == 0) ? 0 : (($number == 1) ? 1 : (($number == 2) ? 2 : ((($number % 100 >= 3) && ($number % 100 <= 10)) ? 3 : ((($number % 100 >= 11) && ($number % 100 <= 99)) ? 4 : 5))));
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the default plural rule for a given locale.
|
||||
*
|
||||
* @param callable $rule A PHP callable
|
||||
* @param string $locale The locale
|
||||
*/
|
||||
public static function set(callable $rule, $locale)
|
||||
{
|
||||
if ('pt_BR' === $locale) {
|
||||
// temporary set a locale for brazilian
|
||||
$locale = 'xbr';
|
||||
}
|
||||
|
||||
if (strlen($locale) > 3) {
|
||||
$locale = substr($locale, 0, -strlen(strrchr($locale, '_')));
|
||||
}
|
||||
|
||||
self::$rules[$locale] = $rule;
|
||||
}
|
||||
}
|
||||
45
vendor/symfony/translation/Provider/AbstractProviderFactory.php
vendored
Normal file
45
vendor/symfony/translation/Provider/AbstractProviderFactory.php
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Provider;
|
||||
|
||||
use Symfony\Component\Translation\Exception\IncompleteDsnException;
|
||||
|
||||
abstract class AbstractProviderFactory implements ProviderFactoryInterface
|
||||
{
|
||||
public function supports(Dsn $dsn): bool
|
||||
{
|
||||
return \in_array($dsn->getScheme(), $this->getSupportedSchemes(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
abstract protected function getSupportedSchemes(): array;
|
||||
|
||||
protected function getUser(Dsn $dsn): string
|
||||
{
|
||||
if (null === $user = $dsn->getUser()) {
|
||||
throw new IncompleteDsnException('User is not set.', $dsn->getOriginalDsn());
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
protected function getPassword(Dsn $dsn): string
|
||||
{
|
||||
if (null === $password = $dsn->getPassword()) {
|
||||
throw new IncompleteDsnException('Password is not set.', $dsn->getOriginalDsn());
|
||||
}
|
||||
|
||||
return $password;
|
||||
}
|
||||
}
|
||||
110
vendor/symfony/translation/Provider/Dsn.php
vendored
Normal file
110
vendor/symfony/translation/Provider/Dsn.php
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Provider;
|
||||
|
||||
use Symfony\Component\Translation\Exception\InvalidArgumentException;
|
||||
use Symfony\Component\Translation\Exception\MissingRequiredOptionException;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
* @author Oskar Stark <oskarstark@googlemail.com>
|
||||
*/
|
||||
final class Dsn
|
||||
{
|
||||
private ?string $scheme;
|
||||
private ?string $host;
|
||||
private ?string $user;
|
||||
private ?string $password;
|
||||
private ?int $port;
|
||||
private ?string $path;
|
||||
private array $options = [];
|
||||
private string $originalDsn;
|
||||
|
||||
public function __construct(string $dsn)
|
||||
{
|
||||
$this->originalDsn = $dsn;
|
||||
|
||||
if (false === $parsedDsn = parse_url($dsn)) {
|
||||
throw new InvalidArgumentException(sprintf('The "%s" translation provider DSN is invalid.', $dsn));
|
||||
}
|
||||
|
||||
if (!isset($parsedDsn['scheme'])) {
|
||||
throw new InvalidArgumentException(sprintf('The "%s" translation provider DSN must contain a scheme.', $dsn));
|
||||
}
|
||||
$this->scheme = $parsedDsn['scheme'];
|
||||
|
||||
if (!isset($parsedDsn['host'])) {
|
||||
throw new InvalidArgumentException(sprintf('The "%s" translation provider DSN must contain a host (use "default" by default).', $dsn));
|
||||
}
|
||||
$this->host = $parsedDsn['host'];
|
||||
|
||||
$this->user = '' !== ($parsedDsn['user'] ?? '') ? urldecode($parsedDsn['user']) : null;
|
||||
$this->password = '' !== ($parsedDsn['pass'] ?? '') ? urldecode($parsedDsn['pass']) : null;
|
||||
$this->port = $parsedDsn['port'] ?? null;
|
||||
$this->path = $parsedDsn['path'] ?? null;
|
||||
parse_str($parsedDsn['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, mixed $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;
|
||||
}
|
||||
}
|
||||
65
vendor/symfony/translation/Provider/FilteringProvider.php
vendored
Normal file
65
vendor/symfony/translation/Provider/FilteringProvider.php
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Provider;
|
||||
|
||||
use Symfony\Component\Translation\TranslatorBag;
|
||||
use Symfony\Component\Translation\TranslatorBagInterface;
|
||||
|
||||
/**
|
||||
* Filters domains and locales between the Translator config values and those specific to each provider.
|
||||
*
|
||||
* @author Mathieu Santostefano <msantostefano@protonmail.com>
|
||||
*/
|
||||
class FilteringProvider implements ProviderInterface
|
||||
{
|
||||
private $provider;
|
||||
private array $locales;
|
||||
private array $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;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
39
vendor/symfony/translation/Provider/NullProvider.php
vendored
Normal file
39
vendor/symfony/translation/Provider/NullProvider.php
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Provider;
|
||||
|
||||
use Symfony\Component\Translation\TranslatorBag;
|
||||
use Symfony\Component\Translation\TranslatorBagInterface;
|
||||
|
||||
/**
|
||||
* @author Mathieu Santostefano <msantostefano@protonmail.com>
|
||||
*/
|
||||
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
|
||||
{
|
||||
}
|
||||
}
|
||||
34
vendor/symfony/translation/Provider/NullProviderFactory.php
vendored
Normal file
34
vendor/symfony/translation/Provider/NullProviderFactory.php
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Provider;
|
||||
|
||||
use Symfony\Component\Translation\Exception\UnsupportedSchemeException;
|
||||
|
||||
/**
|
||||
* @author Mathieu Santostefano <msantostefano@protonmail.com>
|
||||
*/
|
||||
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'];
|
||||
}
|
||||
}
|
||||
26
vendor/symfony/translation/Provider/ProviderFactoryInterface.php
vendored
Normal file
26
vendor/symfony/translation/Provider/ProviderFactoryInterface.php
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Provider;
|
||||
|
||||
use Symfony\Component\Translation\Exception\IncompleteDsnException;
|
||||
use Symfony\Component\Translation\Exception\UnsupportedSchemeException;
|
||||
|
||||
interface ProviderFactoryInterface
|
||||
{
|
||||
/**
|
||||
* @throws UnsupportedSchemeException
|
||||
* @throws IncompleteDsnException
|
||||
*/
|
||||
public function create(Dsn $dsn): ProviderInterface;
|
||||
|
||||
public function supports(Dsn $dsn): bool;
|
||||
}
|
||||
32
vendor/symfony/translation/Provider/ProviderInterface.php
vendored
Normal file
32
vendor/symfony/translation/Provider/ProviderInterface.php
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Provider;
|
||||
|
||||
use Symfony\Component\Translation\TranslatorBag;
|
||||
use Symfony\Component\Translation\TranslatorBagInterface;
|
||||
|
||||
interface ProviderInterface
|
||||
{
|
||||
public function __toString(): string;
|
||||
|
||||
/**
|
||||
* Translations available in the TranslatorBag only must be created.
|
||||
* Translations available in both the TranslatorBag and on the provider
|
||||
* must be overwritten.
|
||||
* Translations available on the provider only must be kept.
|
||||
*/
|
||||
public function write(TranslatorBagInterface $translatorBag): void;
|
||||
|
||||
public function read(array $domains, array $locales): TranslatorBag;
|
||||
|
||||
public function delete(TranslatorBagInterface $translatorBag): void;
|
||||
}
|
||||
57
vendor/symfony/translation/Provider/TranslationProviderCollection.php
vendored
Normal file
57
vendor/symfony/translation/Provider/TranslationProviderCollection.php
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Provider;
|
||||
|
||||
use Symfony\Component\Translation\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* @author Mathieu Santostefano <msantostefano@protonmail.com>
|
||||
*/
|
||||
final class TranslationProviderCollection
|
||||
{
|
||||
/**
|
||||
* @var array<string, ProviderInterface>
|
||||
*/
|
||||
private $providers;
|
||||
|
||||
/**
|
||||
* @param array<string, ProviderInterface> $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);
|
||||
}
|
||||
}
|
||||
57
vendor/symfony/translation/Provider/TranslationProviderCollectionFactory.php
vendored
Normal file
57
vendor/symfony/translation/Provider/TranslationProviderCollectionFactory.php
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Provider;
|
||||
|
||||
use Symfony\Component\Translation\Exception\UnsupportedSchemeException;
|
||||
|
||||
/**
|
||||
* @author Mathieu Santostefano <msantostefano@protonmail.com>
|
||||
*/
|
||||
class TranslationProviderCollectionFactory
|
||||
{
|
||||
private iterable $factories;
|
||||
private array $enabledLocales;
|
||||
|
||||
/**
|
||||
* @param iterable<mixed, ProviderFactoryInterface> $factories
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
368
vendor/symfony/translation/PseudoLocalizationTranslator.php
vendored
Normal file
368
vendor/symfony/translation/PseudoLocalizationTranslator.php
vendored
Normal file
@@ -0,0 +1,368 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation;
|
||||
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
/**
|
||||
* This translator should only be used in a development environment.
|
||||
*/
|
||||
final class PseudoLocalizationTranslator implements TranslatorInterface
|
||||
{
|
||||
private const EXPANSION_CHARACTER = '~';
|
||||
|
||||
private $translator;
|
||||
private bool $accents;
|
||||
private float $expansionFactor;
|
||||
private bool $brackets;
|
||||
private bool $parseHTML;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private array $localizableHTMLAttributes;
|
||||
|
||||
/**
|
||||
* Available options:
|
||||
* * accents:
|
||||
* type: boolean
|
||||
* default: true
|
||||
* description: replace ASCII characters of the translated string with accented versions or similar characters
|
||||
* example: if true, "foo" => "ƒöö".
|
||||
*
|
||||
* * expansion_factor:
|
||||
* type: float
|
||||
* default: 1
|
||||
* validation: it must be greater than or equal to 1
|
||||
* description: expand the translated string by the given factor with spaces and tildes
|
||||
* example: if 2, "foo" => "~foo ~"
|
||||
*
|
||||
* * brackets:
|
||||
* type: boolean
|
||||
* default: true
|
||||
* description: wrap the translated string with brackets
|
||||
* example: if true, "foo" => "[foo]"
|
||||
*
|
||||
* * parse_html:
|
||||
* type: boolean
|
||||
* default: false
|
||||
* description: parse the translated string as HTML - looking for HTML tags has a performance impact but allows to preserve them from alterations - it also allows to compute the visible translated string length which is useful to correctly expand ot when it contains HTML
|
||||
* warning: unclosed tags are unsupported, they will be fixed (closed) by the parser - eg, "foo <div>bar" => "foo <div>bar</div>"
|
||||
*
|
||||
* * localizable_html_attributes:
|
||||
* type: string[]
|
||||
* default: []
|
||||
* description: the list of HTML attributes whose values can be altered - it is only useful when the "parse_html" option is set to true
|
||||
* example: if ["title"], and with the "accents" option set to true, "<a href="#" title="Go to your profile">Profile</a>" => "<a href="#" title="Ĝö ţö ýöûŕ þŕöƒîļé">Þŕöƒîļé</a>" - if "title" was not in the "localizable_html_attributes" list, the title attribute data would be left unchanged.
|
||||
*/
|
||||
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'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
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, 0xFFFF, 0, 0xFFFF], 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];
|
||||
|
||||
/** @var \DOMAttr $attribute */
|
||||
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);
|
||||
}
|
||||
}
|
||||
45
vendor/symfony/translation/README.md
vendored
45
vendor/symfony/translation/README.md
vendored
@@ -3,11 +3,46 @@ Translation Component
|
||||
|
||||
The Translation component provides tools to internationalize your application.
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
```
|
||||
$ composer require symfony/translation
|
||||
```
|
||||
|
||||
```php
|
||||
use Symfony\Component\Translation\Translator;
|
||||
use Symfony\Component\Translation\Loader\ArrayLoader;
|
||||
|
||||
$translator = new Translator('fr_FR');
|
||||
$translator->addLoader('array', new ArrayLoader());
|
||||
$translator->addResource('array', [
|
||||
'Hello World!' => 'Bonjour !',
|
||||
], 'fr_FR');
|
||||
|
||||
echo $translator->trans('Hello World!'); // outputs « Bonjour ! »
|
||||
```
|
||||
|
||||
Sponsor
|
||||
-------
|
||||
|
||||
The Translation component for Symfony 5.4/6.0 is [backed][1] by:
|
||||
|
||||
* [Crowdin][2], a cloud-based localization management software helping teams to go global and stay agile.
|
||||
* [Lokalise][3], a continuous localization and translation management platform that integrates into your development workflow so you can ship localized products, faster.
|
||||
|
||||
Help Symfony by [sponsoring][4] its development!
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
* [Documentation](https://symfony.com/doc/current/components/translation/index.html)
|
||||
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
|
||||
* [Report issues](https://github.com/symfony/symfony/issues) and
|
||||
[send Pull Requests](https://github.com/symfony/symfony/pulls)
|
||||
in the [main Symfony repository](https://github.com/symfony/symfony)
|
||||
* [Documentation](https://symfony.com/doc/current/translation.html)
|
||||
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
|
||||
* [Report issues](https://github.com/symfony/symfony/issues) and
|
||||
[send Pull Requests](https://github.com/symfony/symfony/pulls)
|
||||
in the [main Symfony repository](https://github.com/symfony/symfony)
|
||||
|
||||
[1]: https://symfony.com/backers
|
||||
[2]: https://crowdin.com
|
||||
[3]: https://lokalise.com
|
||||
[4]: https://symfony.com/sponsor
|
||||
|
||||
62
vendor/symfony/translation/Reader/TranslationReader.php
vendored
Normal file
62
vendor/symfony/translation/Reader/TranslationReader.php
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Reader;
|
||||
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\Translation\Loader\LoaderInterface;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
|
||||
/**
|
||||
* TranslationReader reads translation messages from translation files.
|
||||
*
|
||||
* @author Michel Salib <michelsalib@hotmail.com>
|
||||
*/
|
||||
class TranslationReader implements TranslationReaderInterface
|
||||
{
|
||||
/**
|
||||
* Loaders used for import.
|
||||
*
|
||||
* @var array<string, LoaderInterface>
|
||||
*/
|
||||
private array $loaders = [];
|
||||
|
||||
/**
|
||||
* Adds a loader to the translation extractor.
|
||||
*
|
||||
* @param string $format The format of the loader
|
||||
*/
|
||||
public function addLoader(string $format, LoaderInterface $loader)
|
||||
{
|
||||
$this->loaders[$format] = $loader;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function read(string $directory, MessageCatalogue $catalogue)
|
||||
{
|
||||
if (!is_dir($directory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->loaders as $format => $loader) {
|
||||
// load any existing translation files
|
||||
$finder = new Finder();
|
||||
$extension = $catalogue->getLocale().'.'.$format;
|
||||
$files = $finder->files()->name('*.'.$extension)->in($directory);
|
||||
foreach ($files as $file) {
|
||||
$domain = substr($file->getFilename(), 0, -1 * \strlen($extension) - 1);
|
||||
$catalogue->addCatalogue($loader->load($file->getPathname(), $catalogue->getLocale(), $domain));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
27
vendor/symfony/translation/Reader/TranslationReaderInterface.php
vendored
Normal file
27
vendor/symfony/translation/Reader/TranslationReaderInterface.php
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Reader;
|
||||
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
|
||||
/**
|
||||
* TranslationReader reads translation messages from translation files.
|
||||
*
|
||||
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
|
||||
*/
|
||||
interface TranslationReaderInterface
|
||||
{
|
||||
/**
|
||||
* Reads translation messages from a directory to the catalogue.
|
||||
*/
|
||||
public function read(string $directory, MessageCatalogue $catalogue);
|
||||
}
|
||||
278
vendor/symfony/translation/Resources/bin/translation-status.php
vendored
Normal file
278
vendor/symfony/translation/Resources/bin/translation-status.php
vendored
Normal file
@@ -0,0 +1,278 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
if ('cli' !== \PHP_SAPI) {
|
||||
throw new Exception('This script must be run from the command line.');
|
||||
}
|
||||
|
||||
$usageInstructions = <<<END
|
||||
|
||||
Usage instructions
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
$ cd symfony-code-root-directory/
|
||||
|
||||
# show the translation status of all locales
|
||||
$ php translation-status.php
|
||||
|
||||
# only show the translation status of incomplete or erroneous locales
|
||||
$ php translation-status.php --incomplete
|
||||
|
||||
# show the translation status of all locales, all their missing translations and mismatches between trans-unit id and source
|
||||
$ php translation-status.php -v
|
||||
|
||||
# show the status of a single locale
|
||||
$ php translation-status.php fr
|
||||
|
||||
# show the status of a single locale, missing translations and mismatches between trans-unit id and source
|
||||
$ php translation-status.php fr -v
|
||||
|
||||
END;
|
||||
|
||||
$config = [
|
||||
// if TRUE, the full list of missing translations is displayed
|
||||
'verbose_output' => false,
|
||||
// NULL = analyze all locales
|
||||
'locale_to_analyze' => null,
|
||||
// append --incomplete to only show incomplete languages
|
||||
'include_completed_languages' => true,
|
||||
// the reference files all the other translations are compared to
|
||||
'original_files' => [
|
||||
'src/Symfony/Component/Form/Resources/translations/validators.en.xlf',
|
||||
'src/Symfony/Component/Security/Core/Resources/translations/security.en.xlf',
|
||||
'src/Symfony/Component/Validator/Resources/translations/validators.en.xlf',
|
||||
],
|
||||
];
|
||||
|
||||
$argc = $_SERVER['argc'];
|
||||
$argv = $_SERVER['argv'];
|
||||
|
||||
if ($argc > 4) {
|
||||
echo str_replace('translation-status.php', $argv[0], $usageInstructions);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
foreach (array_slice($argv, 1) as $argumentOrOption) {
|
||||
if ('--incomplete' === $argumentOrOption) {
|
||||
$config['include_completed_languages'] = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str_starts_with($argumentOrOption, '-')) {
|
||||
$config['verbose_output'] = true;
|
||||
} else {
|
||||
$config['locale_to_analyze'] = $argumentOrOption;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($config['original_files'] as $originalFilePath) {
|
||||
if (!file_exists($originalFilePath)) {
|
||||
echo sprintf('The following file does not exist. Make sure that you execute this command at the root dir of the Symfony code repository.%s %s', \PHP_EOL, $originalFilePath);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
$totalMissingTranslations = 0;
|
||||
$totalTranslationMismatches = 0;
|
||||
|
||||
foreach ($config['original_files'] as $originalFilePath) {
|
||||
$translationFilePaths = findTranslationFiles($originalFilePath, $config['locale_to_analyze']);
|
||||
$translationStatus = calculateTranslationStatus($originalFilePath, $translationFilePaths);
|
||||
|
||||
$totalMissingTranslations += array_sum(array_map(function ($translation) {
|
||||
return count($translation['missingKeys']);
|
||||
}, array_values($translationStatus)));
|
||||
$totalTranslationMismatches += array_sum(array_map(function ($translation) {
|
||||
return count($translation['mismatches']);
|
||||
}, array_values($translationStatus)));
|
||||
|
||||
printTranslationStatus($originalFilePath, $translationStatus, $config['verbose_output'], $config['include_completed_languages']);
|
||||
}
|
||||
|
||||
exit($totalTranslationMismatches > 0 ? 1 : 0);
|
||||
|
||||
function findTranslationFiles($originalFilePath, $localeToAnalyze)
|
||||
{
|
||||
$translations = [];
|
||||
|
||||
$translationsDir = dirname($originalFilePath);
|
||||
$originalFileName = basename($originalFilePath);
|
||||
$translationFileNamePattern = str_replace('.en.', '.*.', $originalFileName);
|
||||
|
||||
$translationFiles = glob($translationsDir.'/'.$translationFileNamePattern, \GLOB_NOSORT);
|
||||
sort($translationFiles);
|
||||
foreach ($translationFiles as $filePath) {
|
||||
$locale = extractLocaleFromFilePath($filePath);
|
||||
|
||||
if (null !== $localeToAnalyze && $locale !== $localeToAnalyze) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$translations[$locale] = $filePath;
|
||||
}
|
||||
|
||||
return $translations;
|
||||
}
|
||||
|
||||
function calculateTranslationStatus($originalFilePath, $translationFilePaths)
|
||||
{
|
||||
$translationStatus = [];
|
||||
$allTranslationKeys = extractTranslationKeys($originalFilePath);
|
||||
|
||||
foreach ($translationFilePaths as $locale => $translationPath) {
|
||||
$translatedKeys = extractTranslationKeys($translationPath);
|
||||
$missingKeys = array_diff_key($allTranslationKeys, $translatedKeys);
|
||||
$mismatches = findTransUnitMismatches($allTranslationKeys, $translatedKeys);
|
||||
|
||||
$translationStatus[$locale] = [
|
||||
'total' => count($allTranslationKeys),
|
||||
'translated' => count($translatedKeys),
|
||||
'missingKeys' => $missingKeys,
|
||||
'mismatches' => $mismatches,
|
||||
];
|
||||
$translationStatus[$locale]['is_completed'] = isTranslationCompleted($translationStatus[$locale]);
|
||||
}
|
||||
|
||||
return $translationStatus;
|
||||
}
|
||||
|
||||
function isTranslationCompleted(array $translationStatus): bool
|
||||
{
|
||||
return $translationStatus['total'] === $translationStatus['translated'] && 0 === count($translationStatus['mismatches']);
|
||||
}
|
||||
|
||||
function printTranslationStatus($originalFilePath, $translationStatus, $verboseOutput, $includeCompletedLanguages)
|
||||
{
|
||||
printTitle($originalFilePath);
|
||||
printTable($translationStatus, $verboseOutput, $includeCompletedLanguages);
|
||||
echo \PHP_EOL.\PHP_EOL;
|
||||
}
|
||||
|
||||
function extractLocaleFromFilePath($filePath)
|
||||
{
|
||||
$parts = explode('.', $filePath);
|
||||
|
||||
return $parts[count($parts) - 2];
|
||||
}
|
||||
|
||||
function extractTranslationKeys($filePath)
|
||||
{
|
||||
$translationKeys = [];
|
||||
$contents = new \SimpleXMLElement(file_get_contents($filePath));
|
||||
|
||||
foreach ($contents->file->body->{'trans-unit'} as $translationKey) {
|
||||
$translationId = (string) $translationKey['id'];
|
||||
$translationKey = (string) $translationKey->source;
|
||||
|
||||
$translationKeys[$translationId] = $translationKey;
|
||||
}
|
||||
|
||||
return $translationKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the trans-unit id and source match with the base translation.
|
||||
*/
|
||||
function findTransUnitMismatches(array $baseTranslationKeys, array $translatedKeys): array
|
||||
{
|
||||
$mismatches = [];
|
||||
|
||||
foreach ($baseTranslationKeys as $translationId => $translationKey) {
|
||||
if (!isset($translatedKeys[$translationId])) {
|
||||
continue;
|
||||
}
|
||||
if ($translatedKeys[$translationId] !== $translationKey) {
|
||||
$mismatches[$translationId] = [
|
||||
'found' => $translatedKeys[$translationId],
|
||||
'expected' => $translationKey,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $mismatches;
|
||||
}
|
||||
|
||||
function printTitle($title)
|
||||
{
|
||||
echo $title.\PHP_EOL;
|
||||
echo str_repeat('=', strlen($title)).\PHP_EOL.\PHP_EOL;
|
||||
}
|
||||
|
||||
function printTable($translations, $verboseOutput, bool $includeCompletedLanguages)
|
||||
{
|
||||
if (0 === count($translations)) {
|
||||
echo 'No translations found';
|
||||
|
||||
return;
|
||||
}
|
||||
$longestLocaleNameLength = max(array_map('strlen', array_keys($translations)));
|
||||
|
||||
foreach ($translations as $locale => $translation) {
|
||||
if (!$includeCompletedLanguages && $translation['is_completed']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($translation['translated'] > $translation['total']) {
|
||||
textColorRed();
|
||||
} elseif (count($translation['mismatches']) > 0) {
|
||||
textColorRed();
|
||||
} elseif ($translation['is_completed']) {
|
||||
textColorGreen();
|
||||
}
|
||||
|
||||
echo sprintf(
|
||||
'| Locale: %-'.$longestLocaleNameLength.'s | Translated: %2d/%2d | Mismatches: %d |',
|
||||
$locale,
|
||||
$translation['translated'],
|
||||
$translation['total'],
|
||||
count($translation['mismatches'])
|
||||
).\PHP_EOL;
|
||||
|
||||
textColorNormal();
|
||||
|
||||
$shouldBeClosed = false;
|
||||
if (true === $verboseOutput && count($translation['missingKeys']) > 0) {
|
||||
echo '| Missing Translations:'.\PHP_EOL;
|
||||
|
||||
foreach ($translation['missingKeys'] as $id => $content) {
|
||||
echo sprintf('| (id=%s) %s', $id, $content).\PHP_EOL;
|
||||
}
|
||||
$shouldBeClosed = true;
|
||||
}
|
||||
if (true === $verboseOutput && count($translation['mismatches']) > 0) {
|
||||
echo '| Mismatches between trans-unit id and source:'.\PHP_EOL;
|
||||
|
||||
foreach ($translation['mismatches'] as $id => $content) {
|
||||
echo sprintf('| (id=%s) Expected: %s', $id, $content['expected']).\PHP_EOL;
|
||||
echo sprintf('| Found: %s', $content['found']).\PHP_EOL;
|
||||
}
|
||||
$shouldBeClosed = true;
|
||||
}
|
||||
if ($shouldBeClosed) {
|
||||
echo str_repeat('-', 80).\PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function textColorGreen()
|
||||
{
|
||||
echo "\033[32m";
|
||||
}
|
||||
|
||||
function textColorRed()
|
||||
{
|
||||
echo "\033[31m";
|
||||
}
|
||||
|
||||
function textColorNormal()
|
||||
{
|
||||
echo "\033[0m";
|
||||
}
|
||||
141
vendor/symfony/translation/Resources/data/parents.json
vendored
Normal file
141
vendor/symfony/translation/Resources/data/parents.json
vendored
Normal file
@@ -0,0 +1,141 @@
|
||||
{
|
||||
"az_Cyrl": "root",
|
||||
"bs_Cyrl": "root",
|
||||
"en_150": "en_001",
|
||||
"en_AG": "en_001",
|
||||
"en_AI": "en_001",
|
||||
"en_AT": "en_150",
|
||||
"en_AU": "en_001",
|
||||
"en_BB": "en_001",
|
||||
"en_BE": "en_150",
|
||||
"en_BM": "en_001",
|
||||
"en_BS": "en_001",
|
||||
"en_BW": "en_001",
|
||||
"en_BZ": "en_001",
|
||||
"en_CC": "en_001",
|
||||
"en_CH": "en_150",
|
||||
"en_CK": "en_001",
|
||||
"en_CM": "en_001",
|
||||
"en_CX": "en_001",
|
||||
"en_CY": "en_001",
|
||||
"en_DE": "en_150",
|
||||
"en_DG": "en_001",
|
||||
"en_DK": "en_150",
|
||||
"en_DM": "en_001",
|
||||
"en_ER": "en_001",
|
||||
"en_FI": "en_150",
|
||||
"en_FJ": "en_001",
|
||||
"en_FK": "en_001",
|
||||
"en_FM": "en_001",
|
||||
"en_GB": "en_001",
|
||||
"en_GD": "en_001",
|
||||
"en_GG": "en_001",
|
||||
"en_GH": "en_001",
|
||||
"en_GI": "en_001",
|
||||
"en_GM": "en_001",
|
||||
"en_GY": "en_001",
|
||||
"en_HK": "en_001",
|
||||
"en_IE": "en_001",
|
||||
"en_IL": "en_001",
|
||||
"en_IM": "en_001",
|
||||
"en_IN": "en_001",
|
||||
"en_IO": "en_001",
|
||||
"en_JE": "en_001",
|
||||
"en_JM": "en_001",
|
||||
"en_KE": "en_001",
|
||||
"en_KI": "en_001",
|
||||
"en_KN": "en_001",
|
||||
"en_KY": "en_001",
|
||||
"en_LC": "en_001",
|
||||
"en_LR": "en_001",
|
||||
"en_LS": "en_001",
|
||||
"en_MG": "en_001",
|
||||
"en_MO": "en_001",
|
||||
"en_MS": "en_001",
|
||||
"en_MT": "en_001",
|
||||
"en_MU": "en_001",
|
||||
"en_MV": "en_001",
|
||||
"en_MW": "en_001",
|
||||
"en_MY": "en_001",
|
||||
"en_NA": "en_001",
|
||||
"en_NF": "en_001",
|
||||
"en_NG": "en_001",
|
||||
"en_NL": "en_150",
|
||||
"en_NR": "en_001",
|
||||
"en_NU": "en_001",
|
||||
"en_NZ": "en_001",
|
||||
"en_PG": "en_001",
|
||||
"en_PK": "en_001",
|
||||
"en_PN": "en_001",
|
||||
"en_PW": "en_001",
|
||||
"en_RW": "en_001",
|
||||
"en_SB": "en_001",
|
||||
"en_SC": "en_001",
|
||||
"en_SD": "en_001",
|
||||
"en_SE": "en_150",
|
||||
"en_SG": "en_001",
|
||||
"en_SH": "en_001",
|
||||
"en_SI": "en_150",
|
||||
"en_SL": "en_001",
|
||||
"en_SS": "en_001",
|
||||
"en_SX": "en_001",
|
||||
"en_SZ": "en_001",
|
||||
"en_TC": "en_001",
|
||||
"en_TK": "en_001",
|
||||
"en_TO": "en_001",
|
||||
"en_TT": "en_001",
|
||||
"en_TV": "en_001",
|
||||
"en_TZ": "en_001",
|
||||
"en_UG": "en_001",
|
||||
"en_VC": "en_001",
|
||||
"en_VG": "en_001",
|
||||
"en_VU": "en_001",
|
||||
"en_WS": "en_001",
|
||||
"en_ZA": "en_001",
|
||||
"en_ZM": "en_001",
|
||||
"en_ZW": "en_001",
|
||||
"es_AR": "es_419",
|
||||
"es_BO": "es_419",
|
||||
"es_BR": "es_419",
|
||||
"es_BZ": "es_419",
|
||||
"es_CL": "es_419",
|
||||
"es_CO": "es_419",
|
||||
"es_CR": "es_419",
|
||||
"es_CU": "es_419",
|
||||
"es_DO": "es_419",
|
||||
"es_EC": "es_419",
|
||||
"es_GT": "es_419",
|
||||
"es_HN": "es_419",
|
||||
"es_MX": "es_419",
|
||||
"es_NI": "es_419",
|
||||
"es_PA": "es_419",
|
||||
"es_PE": "es_419",
|
||||
"es_PR": "es_419",
|
||||
"es_PY": "es_419",
|
||||
"es_SV": "es_419",
|
||||
"es_US": "es_419",
|
||||
"es_UY": "es_419",
|
||||
"es_VE": "es_419",
|
||||
"ff_Adlm": "root",
|
||||
"hi_Latn": "en_IN",
|
||||
"ks_Deva": "root",
|
||||
"nb": "no",
|
||||
"nn": "no",
|
||||
"pa_Arab": "root",
|
||||
"pt_AO": "pt_PT",
|
||||
"pt_CH": "pt_PT",
|
||||
"pt_CV": "pt_PT",
|
||||
"pt_GQ": "pt_PT",
|
||||
"pt_GW": "pt_PT",
|
||||
"pt_LU": "pt_PT",
|
||||
"pt_MO": "pt_PT",
|
||||
"pt_MZ": "pt_PT",
|
||||
"pt_ST": "pt_PT",
|
||||
"pt_TL": "pt_PT",
|
||||
"sd_Deva": "root",
|
||||
"sr_Latn": "root",
|
||||
"uz_Arab": "root",
|
||||
"uz_Cyrl": "root",
|
||||
"zh_Hant": "root",
|
||||
"zh_Hant_MO": "zh_Hant_HK"
|
||||
}
|
||||
22
vendor/symfony/translation/Resources/functions.php
vendored
Normal file
22
vendor/symfony/translation/Resources/functions.php
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation;
|
||||
|
||||
if (!\function_exists(t::class)) {
|
||||
/**
|
||||
* @author Nate Wiebe <nate@northern.co>
|
||||
*/
|
||||
function t(string $message, array $parameters = [], string $domain = null): TranslatableMessage
|
||||
{
|
||||
return new TranslatableMessage($message, $parameters, $domain);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
147
vendor/symfony/translation/Test/ProviderFactoryTestCase.php
vendored
Normal file
147
vendor/symfony/translation/Test/ProviderFactoryTestCase.php
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Test;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpClient\MockHttpClient;
|
||||
use Symfony\Component\Translation\Dumper\XliffFileDumper;
|
||||
use Symfony\Component\Translation\Exception\IncompleteDsnException;
|
||||
use Symfony\Component\Translation\Exception\UnsupportedSchemeException;
|
||||
use Symfony\Component\Translation\Loader\LoaderInterface;
|
||||
use Symfony\Component\Translation\Provider\Dsn;
|
||||
use Symfony\Component\Translation\Provider\ProviderFactoryInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
/**
|
||||
* A test case to ease testing a translation provider factory.
|
||||
*
|
||||
* @author Mathieu Santostefano <msantostefano@protonmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract class ProviderFactoryTestCase extends TestCase
|
||||
{
|
||||
protected $client;
|
||||
protected $logger;
|
||||
protected string $defaultLocale;
|
||||
protected $loader;
|
||||
protected $xliffFileDumper;
|
||||
|
||||
abstract public function createFactory(): ProviderFactoryInterface;
|
||||
|
||||
/**
|
||||
* @return iterable<array{0: bool, 1: string}>
|
||||
*/
|
||||
abstract public function supportsProvider(): iterable;
|
||||
|
||||
/**
|
||||
* @return iterable<array{0: string, 1: string, 2: TransportInterface}>
|
||||
*/
|
||||
abstract public function createProvider(): iterable;
|
||||
|
||||
/**
|
||||
* @return iterable<array{0: string, 1: string|null}>
|
||||
*/
|
||||
public function unsupportedSchemeProvider(): iterable
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<array{0: string, 1: string|null}>
|
||||
*/
|
||||
public function incompleteDsnProvider(): iterable
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider supportsProvider
|
||||
*/
|
||||
public function testSupports(bool $expected, string $dsn)
|
||||
{
|
||||
$factory = $this->createFactory();
|
||||
|
||||
$this->assertSame($expected, $factory->supports(new Dsn($dsn)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider createProvider
|
||||
*/
|
||||
public function testCreate(string $expected, string $dsn)
|
||||
{
|
||||
$factory = $this->createFactory();
|
||||
$provider = $factory->create(new Dsn($dsn));
|
||||
|
||||
$this->assertSame($expected, (string) $provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider unsupportedSchemeProvider
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider incompleteDsnProvider
|
||||
*/
|
||||
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 ??= new MockHttpClient();
|
||||
}
|
||||
|
||||
protected function getLogger(): LoggerInterface
|
||||
{
|
||||
return $this->logger ??= $this->createMock(LoggerInterface::class);
|
||||
}
|
||||
|
||||
protected function getDefaultLocale(): string
|
||||
{
|
||||
return $this->defaultLocale ??= 'en';
|
||||
}
|
||||
|
||||
protected function getLoader(): LoaderInterface
|
||||
{
|
||||
return $this->loader ??= $this->createMock(LoaderInterface::class);
|
||||
}
|
||||
|
||||
protected function getXliffFileDumper(): XliffFileDumper
|
||||
{
|
||||
return $this->xliffFileDumper ??= $this->createMock(XliffFileDumper::class);
|
||||
}
|
||||
}
|
||||
76
vendor/symfony/translation/Test/ProviderTestCase.php
vendored
Normal file
76
vendor/symfony/translation/Test/ProviderTestCase.php
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Test;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\HttpClient\MockHttpClient;
|
||||
use Symfony\Component\Translation\Dumper\XliffFileDumper;
|
||||
use Symfony\Component\Translation\Loader\LoaderInterface;
|
||||
use Symfony\Component\Translation\Provider\ProviderInterface;
|
||||
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||
|
||||
/**
|
||||
* A test case to ease testing a translation provider.
|
||||
*
|
||||
* @author Mathieu Santostefano <msantostefano@protonmail.com>
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
abstract class ProviderTestCase extends TestCase
|
||||
{
|
||||
protected $client;
|
||||
protected $logger;
|
||||
protected string $defaultLocale;
|
||||
protected $loader;
|
||||
protected $xliffFileDumper;
|
||||
|
||||
abstract public function createProvider(HttpClientInterface $client, LoaderInterface $loader, LoggerInterface $logger, string $defaultLocale, string $endpoint): ProviderInterface;
|
||||
|
||||
/**
|
||||
* @return iterable<array{0: string, 1: ProviderInterface}>
|
||||
*/
|
||||
abstract public function toStringProvider(): iterable;
|
||||
|
||||
/**
|
||||
* @dataProvider toStringProvider
|
||||
*/
|
||||
public function testToString(ProviderInterface $provider, string $expected)
|
||||
{
|
||||
$this->assertSame($expected, (string) $provider);
|
||||
}
|
||||
|
||||
protected function getClient(): MockHttpClient
|
||||
{
|
||||
return $this->client ??= new MockHttpClient();
|
||||
}
|
||||
|
||||
protected function getLoader(): LoaderInterface
|
||||
{
|
||||
return $this->loader ??= $this->createMock(LoaderInterface::class);
|
||||
}
|
||||
|
||||
protected function getLogger(): LoggerInterface
|
||||
{
|
||||
return $this->logger ??= $this->createMock(LoggerInterface::class);
|
||||
}
|
||||
|
||||
protected function getDefaultLocale(): string
|
||||
{
|
||||
return $this->defaultLocale ??= 'en';
|
||||
}
|
||||
|
||||
protected function getXliffFileDumper(): XliffFileDumper
|
||||
{
|
||||
return $this->xliffFileDumper ??= $this->createMock(XliffFileDumper::class);
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Tests\Catalogue;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\MessageCatalogueInterface;
|
||||
|
||||
abstract class AbstractOperationTest extends TestCase
|
||||
{
|
||||
public function testGetEmptyDomains()
|
||||
{
|
||||
$this->assertEquals(
|
||||
array(),
|
||||
$this->createOperation(
|
||||
new MessageCatalogue('en'),
|
||||
new MessageCatalogue('en')
|
||||
)->getDomains()
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetMergedDomains()
|
||||
{
|
||||
$this->assertEquals(
|
||||
array('a', 'b', 'c'),
|
||||
$this->createOperation(
|
||||
new MessageCatalogue('en', array('a' => array(), 'b' => array())),
|
||||
new MessageCatalogue('en', array('b' => array(), 'c' => array()))
|
||||
)->getDomains()
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetMessagesFromUnknownDomain()
|
||||
{
|
||||
$this->{method_exists($this, $_ = 'expectException') ? $_ : 'setExpectedException'}('InvalidArgumentException');
|
||||
$this->createOperation(
|
||||
new MessageCatalogue('en'),
|
||||
new MessageCatalogue('en')
|
||||
)->getMessages('domain');
|
||||
}
|
||||
|
||||
public function testGetEmptyMessages()
|
||||
{
|
||||
$this->assertEquals(
|
||||
array(),
|
||||
$this->createOperation(
|
||||
new MessageCatalogue('en', array('a' => array())),
|
||||
new MessageCatalogue('en')
|
||||
)->getMessages('a')
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetEmptyResult()
|
||||
{
|
||||
$this->assertEquals(
|
||||
new MessageCatalogue('en'),
|
||||
$this->createOperation(
|
||||
new MessageCatalogue('en'),
|
||||
new MessageCatalogue('en')
|
||||
)->getResult()
|
||||
);
|
||||
}
|
||||
|
||||
abstract protected function createOperation(MessageCatalogueInterface $source, MessageCatalogueInterface $target);
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Tests\Catalogue;
|
||||
|
||||
use Symfony\Component\Translation\Catalogue\MergeOperation;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\MessageCatalogueInterface;
|
||||
|
||||
class MergeOperationTest extends AbstractOperationTest
|
||||
{
|
||||
public function testGetMessagesFromSingleDomain()
|
||||
{
|
||||
$operation = $this->createOperation(
|
||||
new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b'))),
|
||||
new MessageCatalogue('en', array('messages' => array('a' => 'new_a', 'c' => 'new_c')))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('a' => 'old_a', 'b' => 'old_b', 'c' => 'new_c'),
|
||||
$operation->getMessages('messages')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('c' => 'new_c'),
|
||||
$operation->getNewMessages('messages')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array(),
|
||||
$operation->getObsoleteMessages('messages')
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetResultFromSingleDomain()
|
||||
{
|
||||
$this->assertEquals(
|
||||
new MessageCatalogue('en', array(
|
||||
'messages' => array('a' => 'old_a', 'b' => 'old_b', 'c' => 'new_c'),
|
||||
)),
|
||||
$this->createOperation(
|
||||
new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b'))),
|
||||
new MessageCatalogue('en', array('messages' => array('a' => 'new_a', 'c' => 'new_c')))
|
||||
)->getResult()
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetResultWithMetadata()
|
||||
{
|
||||
$leftCatalogue = new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b')));
|
||||
$leftCatalogue->setMetadata('a', 'foo', 'messages');
|
||||
$leftCatalogue->setMetadata('b', 'bar', 'messages');
|
||||
$rightCatalogue = new MessageCatalogue('en', array('messages' => array('b' => 'new_b', 'c' => 'new_c')));
|
||||
$rightCatalogue->setMetadata('b', 'baz', 'messages');
|
||||
$rightCatalogue->setMetadata('c', 'qux', 'messages');
|
||||
|
||||
$mergedCatalogue = new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b', 'c' => 'new_c')));
|
||||
$mergedCatalogue->setMetadata('a', 'foo', 'messages');
|
||||
$mergedCatalogue->setMetadata('b', 'bar', 'messages');
|
||||
$mergedCatalogue->setMetadata('c', 'qux', 'messages');
|
||||
|
||||
$this->assertEquals(
|
||||
$mergedCatalogue,
|
||||
$this->createOperation(
|
||||
$leftCatalogue,
|
||||
$rightCatalogue
|
||||
)->getResult()
|
||||
);
|
||||
}
|
||||
|
||||
protected function createOperation(MessageCatalogueInterface $source, MessageCatalogueInterface $target)
|
||||
{
|
||||
return new MergeOperation($source, $target);
|
||||
}
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Tests\Catalogue;
|
||||
|
||||
use Symfony\Component\Translation\Catalogue\TargetOperation;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\MessageCatalogueInterface;
|
||||
|
||||
class TargetOperationTest extends AbstractOperationTest
|
||||
{
|
||||
public function testGetMessagesFromSingleDomain()
|
||||
{
|
||||
$operation = $this->createOperation(
|
||||
new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b'))),
|
||||
new MessageCatalogue('en', array('messages' => array('a' => 'new_a', 'c' => 'new_c')))
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('a' => 'old_a', 'c' => 'new_c'),
|
||||
$operation->getMessages('messages')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('c' => 'new_c'),
|
||||
$operation->getNewMessages('messages')
|
||||
);
|
||||
|
||||
$this->assertEquals(
|
||||
array('b' => 'old_b'),
|
||||
$operation->getObsoleteMessages('messages')
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetResultFromSingleDomain()
|
||||
{
|
||||
$this->assertEquals(
|
||||
new MessageCatalogue('en', array(
|
||||
'messages' => array('a' => 'old_a', 'c' => 'new_c'),
|
||||
)),
|
||||
$this->createOperation(
|
||||
new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b'))),
|
||||
new MessageCatalogue('en', array('messages' => array('a' => 'new_a', 'c' => 'new_c')))
|
||||
)->getResult()
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetResultWithMetadata()
|
||||
{
|
||||
$leftCatalogue = new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b')));
|
||||
$leftCatalogue->setMetadata('a', 'foo', 'messages');
|
||||
$leftCatalogue->setMetadata('b', 'bar', 'messages');
|
||||
$rightCatalogue = new MessageCatalogue('en', array('messages' => array('b' => 'new_b', 'c' => 'new_c')));
|
||||
$rightCatalogue->setMetadata('b', 'baz', 'messages');
|
||||
$rightCatalogue->setMetadata('c', 'qux', 'messages');
|
||||
|
||||
$diffCatalogue = new MessageCatalogue('en', array('messages' => array('b' => 'old_b', 'c' => 'new_c')));
|
||||
$diffCatalogue->setMetadata('b', 'bar', 'messages');
|
||||
$diffCatalogue->setMetadata('c', 'qux', 'messages');
|
||||
|
||||
$this->assertEquals(
|
||||
$diffCatalogue,
|
||||
$this->createOperation(
|
||||
$leftCatalogue,
|
||||
$rightCatalogue
|
||||
)->getResult()
|
||||
);
|
||||
}
|
||||
|
||||
protected function createOperation(MessageCatalogueInterface $source, MessageCatalogueInterface $target)
|
||||
{
|
||||
return new TargetOperation($source, $target);
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Tests\DataCollector;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Translation\DataCollectorTranslator;
|
||||
use Symfony\Component\Translation\DataCollector\TranslationDataCollector;
|
||||
|
||||
class TranslationDataCollectorTest extends TestCase
|
||||
{
|
||||
protected function setUp()
|
||||
{
|
||||
if (!class_exists('Symfony\Component\HttpKernel\DataCollector\DataCollector')) {
|
||||
$this->markTestSkipped('The "DataCollector" is not available');
|
||||
}
|
||||
}
|
||||
|
||||
public function testCollectEmptyMessages()
|
||||
{
|
||||
$translator = $this->getTranslator();
|
||||
$translator->expects($this->any())->method('getCollectedMessages')->will($this->returnValue(array()));
|
||||
|
||||
$dataCollector = new TranslationDataCollector($translator);
|
||||
$dataCollector->lateCollect();
|
||||
|
||||
$this->assertEquals(0, $dataCollector->getCountMissings());
|
||||
$this->assertEquals(0, $dataCollector->getCountFallbacks());
|
||||
$this->assertEquals(0, $dataCollector->getCountDefines());
|
||||
$this->assertEquals(array(), $dataCollector->getMessages()->getValue());
|
||||
}
|
||||
|
||||
public function testCollect()
|
||||
{
|
||||
$collectedMessages = array(
|
||||
array(
|
||||
'id' => 'foo',
|
||||
'translation' => 'foo (en)',
|
||||
'locale' => 'en',
|
||||
'domain' => 'messages',
|
||||
'state' => DataCollectorTranslator::MESSAGE_DEFINED,
|
||||
'parameters' => array(),
|
||||
'transChoiceNumber' => null,
|
||||
),
|
||||
array(
|
||||
'id' => 'bar',
|
||||
'translation' => 'bar (fr)',
|
||||
'locale' => 'fr',
|
||||
'domain' => 'messages',
|
||||
'state' => DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK,
|
||||
'parameters' => array(),
|
||||
'transChoiceNumber' => null,
|
||||
),
|
||||
array(
|
||||
'id' => 'choice',
|
||||
'translation' => 'choice',
|
||||
'locale' => 'en',
|
||||
'domain' => 'messages',
|
||||
'state' => DataCollectorTranslator::MESSAGE_MISSING,
|
||||
'parameters' => array('%count%' => 3),
|
||||
'transChoiceNumber' => 3,
|
||||
),
|
||||
array(
|
||||
'id' => 'choice',
|
||||
'translation' => 'choice',
|
||||
'locale' => 'en',
|
||||
'domain' => 'messages',
|
||||
'state' => DataCollectorTranslator::MESSAGE_MISSING,
|
||||
'parameters' => array('%count%' => 3),
|
||||
'transChoiceNumber' => 3,
|
||||
),
|
||||
array(
|
||||
'id' => 'choice',
|
||||
'translation' => 'choice',
|
||||
'locale' => 'en',
|
||||
'domain' => 'messages',
|
||||
'state' => DataCollectorTranslator::MESSAGE_MISSING,
|
||||
'parameters' => array('%count%' => 4, '%foo%' => 'bar'),
|
||||
'transChoiceNumber' => 4,
|
||||
),
|
||||
);
|
||||
$expectedMessages = array(
|
||||
array(
|
||||
'id' => 'foo',
|
||||
'translation' => 'foo (en)',
|
||||
'locale' => 'en',
|
||||
'domain' => 'messages',
|
||||
'state' => DataCollectorTranslator::MESSAGE_DEFINED,
|
||||
'count' => 1,
|
||||
'parameters' => array(),
|
||||
'transChoiceNumber' => null,
|
||||
),
|
||||
array(
|
||||
'id' => 'bar',
|
||||
'translation' => 'bar (fr)',
|
||||
'locale' => 'fr',
|
||||
'domain' => 'messages',
|
||||
'state' => DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK,
|
||||
'count' => 1,
|
||||
'parameters' => array(),
|
||||
'transChoiceNumber' => null,
|
||||
),
|
||||
array(
|
||||
'id' => 'choice',
|
||||
'translation' => 'choice',
|
||||
'locale' => 'en',
|
||||
'domain' => 'messages',
|
||||
'state' => DataCollectorTranslator::MESSAGE_MISSING,
|
||||
'count' => 3,
|
||||
'parameters' => array(
|
||||
array('%count%' => 3),
|
||||
array('%count%' => 3),
|
||||
array('%count%' => 4, '%foo%' => 'bar'),
|
||||
),
|
||||
'transChoiceNumber' => 3,
|
||||
),
|
||||
);
|
||||
|
||||
$translator = $this->getTranslator();
|
||||
$translator->expects($this->any())->method('getCollectedMessages')->will($this->returnValue($collectedMessages));
|
||||
|
||||
$dataCollector = new TranslationDataCollector($translator);
|
||||
$dataCollector->lateCollect();
|
||||
|
||||
$this->assertEquals(1, $dataCollector->getCountMissings());
|
||||
$this->assertEquals(1, $dataCollector->getCountFallbacks());
|
||||
$this->assertEquals(1, $dataCollector->getCountDefines());
|
||||
|
||||
$this->assertEquals($expectedMessages, array_values($dataCollector->getMessages()->getValue(true)));
|
||||
}
|
||||
|
||||
private function getTranslator()
|
||||
{
|
||||
$translator = $this
|
||||
->getMockBuilder('Symfony\Component\Translation\DataCollectorTranslator')
|
||||
->disableOriginalConstructor()
|
||||
->getMock()
|
||||
;
|
||||
|
||||
return $translator;
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Tests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Translation\Translator;
|
||||
use Symfony\Component\Translation\DataCollectorTranslator;
|
||||
use Symfony\Component\Translation\Loader\ArrayLoader;
|
||||
|
||||
class DataCollectorTranslatorTest extends TestCase
|
||||
{
|
||||
public function testCollectMessages()
|
||||
{
|
||||
$collector = $this->createCollector();
|
||||
$collector->setFallbackLocales(array('fr', 'ru'));
|
||||
|
||||
$collector->trans('foo');
|
||||
$collector->trans('bar');
|
||||
$collector->transChoice('choice', 0);
|
||||
$collector->trans('bar_ru');
|
||||
$collector->trans('bar_ru', array('foo' => 'bar'));
|
||||
|
||||
$expectedMessages = array();
|
||||
$expectedMessages[] = array(
|
||||
'id' => 'foo',
|
||||
'translation' => 'foo (en)',
|
||||
'locale' => 'en',
|
||||
'domain' => 'messages',
|
||||
'state' => DataCollectorTranslator::MESSAGE_DEFINED,
|
||||
'parameters' => array(),
|
||||
'transChoiceNumber' => null,
|
||||
);
|
||||
$expectedMessages[] = array(
|
||||
'id' => 'bar',
|
||||
'translation' => 'bar (fr)',
|
||||
'locale' => 'fr',
|
||||
'domain' => 'messages',
|
||||
'state' => DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK,
|
||||
'parameters' => array(),
|
||||
'transChoiceNumber' => null,
|
||||
);
|
||||
$expectedMessages[] = array(
|
||||
'id' => 'choice',
|
||||
'translation' => 'choice',
|
||||
'locale' => 'en',
|
||||
'domain' => 'messages',
|
||||
'state' => DataCollectorTranslator::MESSAGE_MISSING,
|
||||
'parameters' => array(),
|
||||
'transChoiceNumber' => 0,
|
||||
);
|
||||
$expectedMessages[] = array(
|
||||
'id' => 'bar_ru',
|
||||
'translation' => 'bar (ru)',
|
||||
'locale' => 'ru',
|
||||
'domain' => 'messages',
|
||||
'state' => DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK,
|
||||
'parameters' => array(),
|
||||
'transChoiceNumber' => null,
|
||||
);
|
||||
$expectedMessages[] = array(
|
||||
'id' => 'bar_ru',
|
||||
'translation' => 'bar (ru)',
|
||||
'locale' => 'ru',
|
||||
'domain' => 'messages',
|
||||
'state' => DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK,
|
||||
'parameters' => array('foo' => 'bar'),
|
||||
'transChoiceNumber' => null,
|
||||
);
|
||||
|
||||
$this->assertEquals($expectedMessages, $collector->getCollectedMessages());
|
||||
}
|
||||
|
||||
private function createCollector()
|
||||
{
|
||||
$translator = new Translator('en');
|
||||
$translator->addLoader('array', new ArrayLoader());
|
||||
$translator->addResource('array', array('foo' => 'foo (en)'), 'en');
|
||||
$translator->addResource('array', array('bar' => 'bar (fr)'), 'fr');
|
||||
$translator->addResource('array', array('bar_ru' => 'bar (ru)'), 'ru');
|
||||
|
||||
return new DataCollectorTranslator($translator);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Tests\Dumper;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\Dumper\CsvFileDumper;
|
||||
|
||||
class CsvFileDumperTest extends TestCase
|
||||
{
|
||||
public function testFormatCatalogue()
|
||||
{
|
||||
$catalogue = new MessageCatalogue('en');
|
||||
$catalogue->add(array('foo' => 'bar', 'bar' => 'foo
|
||||
foo', 'foo;foo' => 'bar'));
|
||||
|
||||
$dumper = new CsvFileDumper();
|
||||
|
||||
$this->assertStringEqualsFile(__DIR__.'/../fixtures/valid.csv', $dumper->formatCatalogue($catalogue, 'messages'));
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Tests\Dumper;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\Dumper\FileDumper;
|
||||
|
||||
class FileDumperTest extends TestCase
|
||||
{
|
||||
public function testDump()
|
||||
{
|
||||
$tempDir = sys_get_temp_dir();
|
||||
|
||||
$catalogue = new MessageCatalogue('en');
|
||||
$catalogue->add(array('foo' => 'bar'));
|
||||
|
||||
$dumper = new ConcreteFileDumper();
|
||||
$dumper->dump($catalogue, array('path' => $tempDir));
|
||||
|
||||
$this->assertFileExists($tempDir.'/messages.en.concrete');
|
||||
}
|
||||
|
||||
/**
|
||||
* @group legacy
|
||||
*/
|
||||
public function testDumpBackupsFileIfExisting()
|
||||
{
|
||||
$tempDir = sys_get_temp_dir();
|
||||
$file = $tempDir.'/messages.en.concrete';
|
||||
$backupFile = $file.'~';
|
||||
|
||||
@touch($file);
|
||||
|
||||
$catalogue = new MessageCatalogue('en');
|
||||
$catalogue->add(array('foo' => 'bar'));
|
||||
|
||||
$dumper = new ConcreteFileDumper();
|
||||
$dumper->dump($catalogue, array('path' => $tempDir));
|
||||
|
||||
$this->assertFileExists($backupFile);
|
||||
|
||||
@unlink($file);
|
||||
@unlink($backupFile);
|
||||
}
|
||||
|
||||
public function testDumpCreatesNestedDirectoriesAndFile()
|
||||
{
|
||||
$tempDir = sys_get_temp_dir();
|
||||
$translationsDir = $tempDir.'/test/translations';
|
||||
$file = $translationsDir.'/messages.en.concrete';
|
||||
|
||||
$catalogue = new MessageCatalogue('en');
|
||||
$catalogue->add(array('foo' => 'bar'));
|
||||
|
||||
$dumper = new ConcreteFileDumper();
|
||||
$dumper->setRelativePathTemplate('test/translations/%domain%.%locale%.%extension%');
|
||||
$dumper->dump($catalogue, array('path' => $tempDir));
|
||||
|
||||
$this->assertFileExists($file);
|
||||
|
||||
@unlink($file);
|
||||
@rmdir($translationsDir);
|
||||
}
|
||||
}
|
||||
|
||||
class ConcreteFileDumper extends FileDumper
|
||||
{
|
||||
public function formatCatalogue(MessageCatalogue $messages, $domain, array $options = array())
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
protected function getExtension()
|
||||
{
|
||||
return 'concrete';
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Tests\Dumper;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\Dumper\IcuResFileDumper;
|
||||
|
||||
class IcuResFileDumperTest extends TestCase
|
||||
{
|
||||
public function testFormatCatalogue()
|
||||
{
|
||||
$catalogue = new MessageCatalogue('en');
|
||||
$catalogue->add(array('foo' => 'bar'));
|
||||
|
||||
$dumper = new IcuResFileDumper();
|
||||
|
||||
$this->assertStringEqualsFile(__DIR__.'/../fixtures/resourcebundle/res/en.res', $dumper->formatCatalogue($catalogue, 'messages'));
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Tests\Dumper;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\Dumper\IniFileDumper;
|
||||
|
||||
class IniFileDumperTest extends TestCase
|
||||
{
|
||||
public function testFormatCatalogue()
|
||||
{
|
||||
$catalogue = new MessageCatalogue('en');
|
||||
$catalogue->add(array('foo' => 'bar'));
|
||||
|
||||
$dumper = new IniFileDumper();
|
||||
|
||||
$this->assertStringEqualsFile(__DIR__.'/../fixtures/resources.ini', $dumper->formatCatalogue($catalogue, 'messages'));
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Tests\Dumper;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\Dumper\JsonFileDumper;
|
||||
|
||||
class JsonFileDumperTest extends TestCase
|
||||
{
|
||||
public function testFormatCatalogue()
|
||||
{
|
||||
$catalogue = new MessageCatalogue('en');
|
||||
$catalogue->add(array('foo' => 'bar'));
|
||||
|
||||
$dumper = new JsonFileDumper();
|
||||
|
||||
$this->assertStringEqualsFile(__DIR__.'/../fixtures/resources.json', $dumper->formatCatalogue($catalogue, 'messages'));
|
||||
}
|
||||
|
||||
public function testDumpWithCustomEncoding()
|
||||
{
|
||||
$catalogue = new MessageCatalogue('en');
|
||||
$catalogue->add(array('foo' => '"bar"'));
|
||||
|
||||
$dumper = new JsonFileDumper();
|
||||
|
||||
$this->assertStringEqualsFile(__DIR__.'/../fixtures/resources.dump.json', $dumper->formatCatalogue($catalogue, 'messages', array('json_encoding' => JSON_HEX_QUOT)));
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Tests\Dumper;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\Dumper\MoFileDumper;
|
||||
|
||||
class MoFileDumperTest extends TestCase
|
||||
{
|
||||
public function testFormatCatalogue()
|
||||
{
|
||||
$catalogue = new MessageCatalogue('en');
|
||||
$catalogue->add(array('foo' => 'bar'));
|
||||
|
||||
$dumper = new MoFileDumper();
|
||||
|
||||
$this->assertStringEqualsFile(__DIR__.'/../fixtures/resources.mo', $dumper->formatCatalogue($catalogue, 'messages'));
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Component\Translation\Tests\Dumper;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Translation\MessageCatalogue;
|
||||
use Symfony\Component\Translation\Dumper\PhpFileDumper;
|
||||
|
||||
class PhpFileDumperTest extends TestCase
|
||||
{
|
||||
public function testFormatCatalogue()
|
||||
{
|
||||
$catalogue = new MessageCatalogue('en');
|
||||
$catalogue->add(array('foo' => 'bar'));
|
||||
|
||||
$dumper = new PhpFileDumper();
|
||||
|
||||
$this->assertStringEqualsFile(__DIR__.'/../fixtures/resources.php', $dumper->formatCatalogue($catalogue, 'messages'));
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user