package and depencies

This commit is contained in:
RafficMohammed
2023-01-08 02:57:24 +05:30
parent d5332eb421
commit 1d54b8bc7f
4309 changed files with 193331 additions and 172289 deletions

View File

@@ -1,6 +1,19 @@
CHANGELOG
=========
6.2
---
* Deprecate `PhpStringTokenParser`
* Deprecate `PhpExtractor` in favor of `PhpAstExtractor`
* Add `PhpAstExtractor` (requires [nikic/php-parser](https://github.com/nikic/php-parser) to be installed)
6.1
---
* Parameters implementing `TranslatableInterface` are processed
* Add the file extension to the `XliffFileDumper` constructor
5.4
---

View File

@@ -77,10 +77,7 @@ abstract class AbstractOperation implements OperationInterface
$this->messages = [];
}
/**
* {@inheritdoc}
*/
public function getDomains()
public function getDomains(): array
{
if (null === $this->domains) {
$domains = [];
@@ -100,10 +97,7 @@ abstract class AbstractOperation implements OperationInterface
return $this->domains;
}
/**
* {@inheritdoc}
*/
public function getMessages(string $domain)
public function getMessages(string $domain): array
{
if (!\in_array($domain, $this->getDomains())) {
throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain));
@@ -116,10 +110,7 @@ abstract class AbstractOperation implements OperationInterface
return $this->messages[$domain][self::ALL_BATCH];
}
/**
* {@inheritdoc}
*/
public function getNewMessages(string $domain)
public function getNewMessages(string $domain): array
{
if (!\in_array($domain, $this->getDomains())) {
throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain));
@@ -132,10 +123,7 @@ abstract class AbstractOperation implements OperationInterface
return $this->messages[$domain][self::NEW_BATCH];
}
/**
* {@inheritdoc}
*/
public function getObsoleteMessages(string $domain)
public function getObsoleteMessages(string $domain): array
{
if (!\in_array($domain, $this->getDomains())) {
throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain));
@@ -148,10 +136,7 @@ abstract class AbstractOperation implements OperationInterface
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])) {
@@ -174,12 +159,12 @@ abstract class AbstractOperation implements OperationInterface
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));
}
$messages = match ($batch) {
self::OBSOLETE_BATCH => $this->getObsoleteMessages($domain),
self::NEW_BATCH => $this->getNewMessages($domain),
self::ALL_BATCH => $this->getMessages($domain),
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;

View File

@@ -24,9 +24,6 @@ use Symfony\Component\Translation\MessageCatalogueInterface;
*/
class MergeOperation extends AbstractOperation
{
/**
* {@inheritdoc}
*/
protected function processDomain(string $domain)
{
$this->messages[$domain] = [
@@ -36,6 +33,18 @@ class MergeOperation extends AbstractOperation
];
$intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX;
foreach ($this->target->getCatalogueMetadata('', $domain) ?? [] as $key => $value) {
if (null === $this->result->getCatalogueMetadata($key, $domain)) {
$this->result->setCatalogueMetadata($key, $value, $domain);
}
}
foreach ($this->target->getCatalogueMetadata('', $intlDomain) ?? [] as $key => $value) {
if (null === $this->result->getCatalogueMetadata($key, $intlDomain)) {
$this->result->setCatalogueMetadata($key, $value, $intlDomain);
}
}
foreach ($this->source->all($domain) as $id => $message) {
$this->messages[$domain]['all'][$id] = $message;
$d = $this->source->defines($id, $intlDomain) ? $intlDomain : $domain;

View File

@@ -36,36 +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.
*
* @return array
*/
public function getMessages(string $domain);
public function getMessages(string $domain): array;
/**
* Returns new messages ('new') after operation.
*
* @return array
*/
public function getNewMessages(string $domain);
public function getNewMessages(string $domain): array;
/**
* Returns obsolete messages ('obsolete') after operation.
*
* @return array
*/
public function getObsoleteMessages(string $domain);
public function getObsoleteMessages(string $domain): array;
/**
* Returns resulting catalogue ('result').
*
* @return MessageCatalogueInterface
*/
public function getResult();
public function getResult(): MessageCatalogueInterface;
}

View File

@@ -25,9 +25,6 @@ use Symfony\Component\Translation\MessageCatalogueInterface;
*/
class TargetOperation extends AbstractOperation
{
/**
* {@inheritdoc}
*/
protected function processDomain(string $domain)
{
$this->messages[$domain] = [
@@ -37,6 +34,18 @@ class TargetOperation extends AbstractOperation
];
$intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX;
foreach ($this->target->getCatalogueMetadata('', $domain) ?? [] as $key => $value) {
if (null === $this->result->getCatalogueMetadata($key, $domain)) {
$this->result->setCatalogueMetadata($key, $value, $domain);
}
}
foreach ($this->target->getCatalogueMetadata('', $intlDomain) ?? [] as $key => $value) {
if (null === $this->result->getCatalogueMetadata($key, $intlDomain)) {
$this->result->setCatalogueMetadata($key, $value, $intlDomain);
}
}
// 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}
//

View File

@@ -0,0 +1,44 @@
<?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;
/**
* This interface is used to get, set, and delete metadata about the Catalogue.
*
* @author Hugo Alliaume <hugo@alliau.me>
*/
interface CatalogueMetadataAwareInterface
{
/**
* Gets catalogue metadata for the given domain and key.
*
* Passing an empty domain will return an array with all catalogue metadata indexed by
* domain and then by key. Passing an empty key will return an array with all
* catalogue metadata for the given domain.
*
* @return mixed The value that was set or an array with the domains/keys or null
*/
public function getCatalogueMetadata(string $key = '', string $domain = 'messages'): mixed;
/**
* Adds catalogue metadata to a message domain.
*/
public function setCatalogueMetadata(string $key, mixed $value, string $domain = 'messages');
/**
* Deletes catalogue metadata for the given key and domain.
*
* Passing an empty domain will delete all catalogue metadata. Passing an empty key will
* delete all metadata for the given domain.
*/
public function deleteCatalogueMetadata(string $key = '', string $domain = 'messages');
}

View File

@@ -11,6 +11,7 @@
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;
@@ -28,19 +29,17 @@ 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;
protected static $defaultName = 'translation:pull';
protected static $defaultDescription = 'Pull translations from a given provider.';
private $providerCollection;
private $writer;
private $reader;
private $defaultLocale;
private $transPaths;
private $enabledLocales;
private TranslationProviderCollection $providerCollection;
private TranslationWriterInterface $writer;
private TranslationReaderInterface $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 = [])
{
@@ -65,9 +64,8 @@ final class TranslationPullCommand extends Command
if ($input->mustSuggestOptionValuesFor('domains')) {
$provider = $this->providerCollection->get($input->getArgument('provider'));
if ($provider && method_exists($provider, 'getDomains')) {
$domains = $provider->getDomains();
$suggestions->suggestValues($domains);
if (method_exists($provider, 'getDomains')) {
$suggestions->suggestValues($provider->getDomains());
}
return;
@@ -84,9 +82,6 @@ final class TranslationPullCommand extends Command
}
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$keys = $this->providerCollection->keys();
@@ -121,9 +116,6 @@ EOF
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);

View File

@@ -11,6 +11,7 @@
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;
@@ -28,17 +29,15 @@ 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;
protected static $defaultName = 'translation:push';
protected static $defaultDescription = 'Push translations to a given provider.';
private $providers;
private $reader;
private $transPaths;
private $enabledLocales;
private TranslationProviderCollection $providers;
private TranslationReaderInterface $reader;
private array $transPaths;
private array $enabledLocales;
public function __construct(TranslationProviderCollection $providers, TranslationReaderInterface $reader, array $transPaths = [], array $enabledLocales = [])
{
@@ -74,9 +73,6 @@ final class TranslationPushCommand extends Command
}
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$keys = $this->providers->keys();
@@ -114,9 +110,6 @@ EOF
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$provider = $this->providers->get($input->getArgument('provider'));

View File

@@ -11,6 +11,7 @@
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;
@@ -31,33 +32,27 @@ use Symfony\Component\Translation\Util\XliffUtils;
* @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
{
protected static $defaultName = 'lint:xliff';
protected static $defaultDescription = 'Lint an XLIFF file and outputs encountered errors';
private $format;
private $displayCorrectFiles;
private $directoryIteratorProvider;
private $isReadableProvider;
private $requireStrictFileNames;
private string $format;
private bool $displayCorrectFiles;
private ?\Closure $directoryIteratorProvider;
private ?\Closure $isReadableProvider;
private bool $requireStrictFileNames;
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 ? null : $directoryIteratorProvider(...);
$this->isReadableProvider = null === $isReadableProvider ? null : $isReadableProvider(...);
$this->requireStrictFileNames = $requireStrictFileNames;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDescription(self::$defaultDescription)
->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
@@ -82,7 +77,7 @@ EOF
;
}
protected function execute(InputInterface $input, OutputInterface $output)
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$filenames = (array) $input->getArgument('filename');
@@ -158,16 +153,12 @@ EOF
private function display(SymfonyStyle $io, array $files)
{
switch ($this->format) {
case 'txt':
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));
}
return match ($this->format) {
'txt' => $this->displayTxt($io, $files),
'json' => $this->displayJson($io, $files),
'github' => $this->displayTxt($io, $files, true),
default => throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format)),
};
}
private function displayTxt(SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false)
@@ -186,9 +177,7 @@ EOF
// general document errors have a '-1' line number
$line = -1 === $error['line'] ? null : $error['line'];
if ($githubReporter) {
$githubReporter->error($error['message'], $info['file'], $line, null !== $line ? $error['column'] : null);
}
$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']));

View File

@@ -25,16 +25,13 @@ use Symfony\Component\VarDumper\Cloner\Data;
*/
class TranslationDataCollector extends DataCollector implements LateDataCollectorInterface
{
private $translator;
private DataCollectorTranslator $translator;
public function __construct(DataCollectorTranslator $translator)
{
$this->translator = $translator;
}
/**
* {@inheritdoc}
*/
public function lateCollect()
{
$messages = $this->sanitizeCollectedMessages($this->translator->getCollectedMessages());
@@ -45,27 +42,18 @@ class TranslationDataCollector extends DataCollector implements LateDataCollecto
$this->data = $this->cloneVar($this->data);
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Throwable $exception = null)
{
$this->data['locale'] = $this->translator->getLocale();
$this->data['fallback_locales'] = $this->translator->getFallbackLocales();
}
/**
* {@inheritdoc}
*/
public function reset()
{
$this->data = [];
}
/**
* @return array|Data
*/
public function getMessages()
public function getMessages(): array|Data
{
return $this->data['messages'] ?? [];
}
@@ -98,9 +86,6 @@ class TranslationDataCollector extends DataCollector implements LateDataCollecto
return (isset($this->data['fallback_locales']) && \count($this->data['fallback_locales']) > 0) ? $this->data['fallback_locales'] : [];
}
/**
* {@inheritdoc}
*/
public function getName(): string
{
return 'translation';

View File

@@ -25,8 +25,8 @@ class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInter
public const MESSAGE_MISSING = 1;
public const MESSAGE_EQUALS_FALLBACK = 2;
private $translator;
private $messages = [];
private TranslatorInterface $translator;
private array $messages = [];
/**
* @param TranslatorInterface&TranslatorBagInterface&LocaleAwareInterface $translator
@@ -40,10 +40,7 @@ class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInter
$this->translator = $translator;
}
/**
* {@inheritdoc}
*/
public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null)
public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null): string
{
$trans = $this->translator->trans($id = (string) $id, $parameters, $domain, $locale);
$this->collectMessage($locale, $domain, $id, $trans, $parameters);
@@ -51,44 +48,30 @@ class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInter
return $trans;
}
/**
* {@inheritdoc}
*/
public function setLocale(string $locale)
{
$this->translator->setLocale($locale);
}
/**
* {@inheritdoc}
*/
public function getLocale()
public function getLocale(): string
{
return $this->translator->getLocale();
}
/**
* {@inheritdoc}
*/
public function getCatalogue(string $locale = null)
public function getCatalogue(string $locale = null): MessageCatalogueInterface
{
return $this->translator->getCatalogue($locale);
}
/**
* {@inheritdoc}
*/
public function getCatalogues(): array
{
return $this->translator->getCatalogues();
}
/**
* {@inheritdoc}
*
* @return string[]
*/
public function warmUp(string $cacheDir)
public function warmUp(string $cacheDir): array
{
if ($this->translator instanceof WarmableInterface) {
return (array) $this->translator->warmUp($cacheDir);
@@ -99,10 +82,8 @@ class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInter
/**
* Gets the fallback locales.
*
* @return array
*/
public function getFallbackLocales()
public function getFallbackLocales(): array
{
if ($this->translator instanceof Translator || method_exists($this->translator, 'getFallbackLocales')) {
return $this->translator->getFallbackLocales();
@@ -119,19 +100,14 @@ class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInter
return $this->translator->{$method}(...$args);
}
/**
* @return array
*/
public function getCollectedMessages()
public function getCollectedMessages(): array
{
return $this->messages;
}
private function collectMessage(?string $locale, ?string $domain, string $id, string $translation, ?array $parameters = [])
{
if (null === $domain) {
$domain = 'messages';
}
$domain ??= 'messages';
$catalogue = $this->translator->getCatalogue($locale);
$locale = $catalogue->getLocale();

View File

@@ -20,28 +20,15 @@ use Symfony\Component\DependencyInjection\Reference;
*/
class TranslationDumperPass implements CompilerPassInterface
{
private $writerServiceId;
private $dumperTag;
public function __construct(string $writerServiceId = 'translation.writer', string $dumperTag = 'translation.dumper')
{
if (1 < \func_num_args()) {
trigger_deprecation('symfony/translation', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
}
$this->writerServiceId = $writerServiceId;
$this->dumperTag = $dumperTag;
}
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->writerServiceId)) {
if (!$container->hasDefinition('translation.writer')) {
return;
}
$definition = $container->getDefinition($this->writerServiceId);
$definition = $container->getDefinition('translation.writer');
foreach ($container->findTaggedServiceIds($this->dumperTag, true) as $id => $attributes) {
foreach ($container->findTaggedServiceIds('translation.dumper', true) as $id => $attributes) {
$definition->addMethodCall('addDumper', [$attributes[0]['alias'], new Reference($id)]);
}
}

View File

@@ -21,28 +21,15 @@ use Symfony\Component\DependencyInjection\Reference;
*/
class TranslationExtractorPass implements CompilerPassInterface
{
private $extractorServiceId;
private $extractorTag;
public function __construct(string $extractorServiceId = 'translation.extractor', string $extractorTag = 'translation.extractor')
{
if (0 < \func_num_args()) {
trigger_deprecation('symfony/translation', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
}
$this->extractorServiceId = $extractorServiceId;
$this->extractorTag = $extractorTag;
}
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->extractorServiceId)) {
if (!$container->hasDefinition('translation.extractor')) {
return;
}
$definition = $container->getDefinition($this->extractorServiceId);
$definition = $container->getDefinition('translation.extractor');
foreach ($container->findTaggedServiceIds($this->extractorTag, true) as $id => $attributes) {
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));
}

View File

@@ -18,34 +18,15 @@ use Symfony\Component\DependencyInjection\Reference;
class TranslatorPass implements CompilerPassInterface
{
private $translatorServiceId;
private $readerServiceId;
private $loaderTag;
private $debugCommandServiceId;
private $updateCommandServiceId;
public function __construct(string $translatorServiceId = 'translator.default', string $readerServiceId = 'translation.reader', string $loaderTag = 'translation.loader', string $debugCommandServiceId = 'console.command.translation_debug', string $updateCommandServiceId = 'console.command.translation_extract')
{
if (0 < \func_num_args()) {
trigger_deprecation('symfony/translation', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
}
$this->translatorServiceId = $translatorServiceId;
$this->readerServiceId = $readerServiceId;
$this->loaderTag = $loaderTag;
$this->debugCommandServiceId = $debugCommandServiceId;
$this->updateCommandServiceId = $updateCommandServiceId;
}
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->translatorServiceId)) {
if (!$container->hasDefinition('translator.default')) {
return;
}
$loaders = [];
$loaderRefs = [];
foreach ($container->findTaggedServiceIds($this->loaderTag, true) as $id => $attributes) {
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'])) {
@@ -53,8 +34,8 @@ class TranslatorPass implements CompilerPassInterface
}
}
if ($container->hasDefinition($this->readerServiceId)) {
$definition = $container->getDefinition($this->readerServiceId);
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]]);
@@ -63,26 +44,39 @@ class TranslatorPass implements CompilerPassInterface
}
$container
->findDefinition($this->translatorServiceId)
->findDefinition('translator.default')
->replaceArgument(0, ServiceLocatorTagPass::register($container, $loaderRefs))
->replaceArgument(3, $loaders)
;
if ($container->hasDefinition('validator') && $container->hasDefinition('translation.extractor.visitor.constraint')) {
$constraintVisitorDefinition = $container->getDefinition('translation.extractor.visitor.constraint');
$constraintClassNames = [];
foreach ($container->findTaggedServiceIds('validator.constraint_validator', true) as $id => $attributes) {
$serviceDefinition = $container->getDefinition($id);
// Extraction of the constraint class name from the Constraint Validator FQCN
$constraintClassNames[] = str_replace('Validator', '', substr(strrchr($serviceDefinition->getClass(), '\\'), 1));
}
$constraintVisitorDefinition->setArgument(0, $constraintClassNames);
}
if (!$container->hasParameter('twig.default_path')) {
return;
}
$paths = array_keys($container->getDefinition('twig.template_iterator')->getArgument(1));
if ($container->hasDefinition($this->debugCommandServiceId)) {
$definition = $container->getDefinition($this->debugCommandServiceId);
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($this->updateCommandServiceId)) {
$definition = $container->getDefinition($this->updateCommandServiceId);
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) {

View File

@@ -22,42 +22,26 @@ use Symfony\Component\DependencyInjection\ServiceLocator;
*/
class TranslatorPathsPass extends AbstractRecursivePass
{
private $translatorServiceId;
private $debugCommandServiceId;
private $updateCommandServiceId;
private $resolverServiceId;
private $level = 0;
private int $level = 0;
/**
* @var array<string, bool>
*/
private $paths = [];
private array $paths = [];
/**
* @var array<int, Definition>
*/
private $definitions = [];
private array $definitions = [];
/**
* @var array<string, array<string, bool>>
*/
private $controllers = [];
public function __construct(string $translatorServiceId = 'translator', string $debugCommandServiceId = 'console.command.translation_debug', string $updateCommandServiceId = 'console.command.translation_extract', string $resolverServiceId = 'argument_resolver.service')
{
if (0 < \func_num_args()) {
trigger_deprecation('symfony/translation', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
}
$this->translatorServiceId = $translatorServiceId;
$this->debugCommandServiceId = $debugCommandServiceId;
$this->updateCommandServiceId = $updateCommandServiceId;
$this->resolverServiceId = $resolverServiceId;
}
private array $controllers = [];
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->translatorServiceId)) {
if (!$container->hasDefinition('translator')) {
return;
}
@@ -82,12 +66,12 @@ class TranslatorPathsPass extends AbstractRecursivePass
}
}
if ($paths) {
if ($container->hasDefinition($this->debugCommandServiceId)) {
$definition = $container->getDefinition($this->debugCommandServiceId);
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($this->updateCommandServiceId)) {
$definition = $container->getDefinition($this->updateCommandServiceId);
if ($container->hasDefinition('console.command.translation_extract')) {
$definition = $container->getDefinition('console.command.translation_extract');
$definition->replaceArgument(7, array_merge($definition->getArgument(7), $paths));
}
}
@@ -98,10 +82,10 @@ class TranslatorPathsPass extends AbstractRecursivePass
}
}
protected function processValue($value, bool $isRoot = false)
protected function processValue(mixed $value, bool $isRoot = false): mixed
{
if ($value instanceof Reference) {
if ((string) $value === $this->translatorServiceId) {
if ('translator' === (string) $value) {
for ($i = $this->level - 1; $i >= 0; --$i) {
$class = $this->definitions[$i]->getClass();
@@ -136,8 +120,8 @@ class TranslatorPathsPass extends AbstractRecursivePass
private function findControllerArguments(ContainerBuilder $container): array
{
if ($container->hasDefinition($this->resolverServiceId)) {
$argument = $container->getDefinition($this->resolverServiceId)->getArgument(0);
if ($container->hasDefinition('argument_resolver.service')) {
$argument = $container->getDefinition('argument_resolver.service')->getArgument(0);
if ($argument instanceof Reference) {
$argument = $container->getDefinition($argument);
}
@@ -145,8 +129,8 @@ class TranslatorPathsPass extends AbstractRecursivePass
return $argument->getArgument(0);
}
if ($container->hasDefinition('debug.'.$this->resolverServiceId)) {
$argument = $container->getDefinition('debug.'.$this->resolverServiceId)->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);
}

View File

@@ -20,13 +20,10 @@ 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, string $domain, array $options = [])
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
{
$handle = fopen('php://memory', 'r+');
@@ -50,10 +47,7 @@ class CsvFileDumper extends FileDumper
$this->enclosure = $enclosure;
}
/**
* {@inheritdoc}
*/
protected function getExtension()
protected function getExtension(): string
{
return 'csv';
}

View File

@@ -42,9 +42,6 @@ abstract class FileDumper implements DumperInterface
$this->relativePathTemplate = $relativePathTemplate;
}
/**
* {@inheritdoc}
*/
public function dump(MessageCatalogue $messages, array $options = [])
{
if (!\array_key_exists('path', $options)) {
@@ -86,17 +83,13 @@ abstract class FileDumper implements DumperInterface
/**
* Transforms a domain of a message catalogue to its string representation.
*
* @return string
*/
abstract public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []);
abstract public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string;
/**
* Gets the file extension of the dumper.
*
* @return string
*/
abstract protected function getExtension();
abstract protected function getExtension(): string;
/**
* Gets the relative file path using the template.

View File

@@ -20,15 +20,9 @@ use Symfony\Component\Translation\MessageCatalogue;
*/
class IcuResFileDumper extends FileDumper
{
/**
* {@inheritdoc}
*/
protected $relativePathTemplate = '%domain%/%locale%.%extension%';
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = [])
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
{
$data = $indexes = $resources = '';
@@ -94,10 +88,7 @@ class IcuResFileDumper extends FileDumper
return (\strlen($data) + 28) / 4;
}
/**
* {@inheritdoc}
*/
protected function getExtension()
protected function getExtension(): string
{
return 'res';
}

View File

@@ -20,10 +20,7 @@ use Symfony\Component\Translation\MessageCatalogue;
*/
class IniFileDumper extends FileDumper
{
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = [])
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
{
$output = '';
@@ -35,10 +32,7 @@ class IniFileDumper extends FileDumper
return $output;
}
/**
* {@inheritdoc}
*/
protected function getExtension()
protected function getExtension(): string
{
return 'ini';
}

View File

@@ -20,20 +20,14 @@ use Symfony\Component\Translation\MessageCatalogue;
*/
class JsonFileDumper extends FileDumper
{
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = [])
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
{
$flags = $options['json_encoding'] ?? \JSON_PRETTY_PRINT;
return json_encode($messages->all($domain), $flags);
}
/**
* {@inheritdoc}
*/
protected function getExtension()
protected function getExtension(): string
{
return 'json';
}

View File

@@ -21,10 +21,7 @@ use Symfony\Component\Translation\MessageCatalogue;
*/
class MoFileDumper extends FileDumper
{
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = [])
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
{
$sources = $targets = $sourceOffsets = $targetOffsets = '';
$offsets = [];
@@ -57,7 +54,7 @@ class MoFileDumper extends FileDumper
.$this->writeLong($offset[2] + $sourcesStart + $sourcesSize);
}
$output = implode('', array_map([$this, 'writeLong'], $header))
$output = implode('', array_map($this->writeLong(...), $header))
.$sourceOffsets
.$targetOffsets
.$sources
@@ -67,15 +64,12 @@ class MoFileDumper extends FileDumper
return $output;
}
/**
* {@inheritdoc}
*/
protected function getExtension()
protected function getExtension(): string
{
return 'mo';
}
private function writeLong($str): string
private function writeLong(mixed $str): string
{
return pack('V*', $str);
}

View File

@@ -20,18 +20,12 @@ use Symfony\Component\Translation\MessageCatalogue;
*/
class PhpFileDumper extends FileDumper
{
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = [])
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
{
return "<?php\n\nreturn ".var_export($messages->all($domain), true).";\n";
}
/**
* {@inheritdoc}
*/
protected function getExtension()
protected function getExtension(): string
{
return 'php';
}

View File

@@ -20,10 +20,7 @@ use Symfony\Component\Translation\MessageCatalogue;
*/
class PoFileDumper extends FileDumper
{
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = [])
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
{
$output = 'msgid ""'."\n";
$output .= 'msgstr ""'."\n";
@@ -111,10 +108,7 @@ EOF;
return $standardRules;
}
/**
* {@inheritdoc}
*/
protected function getExtension()
protected function getExtension(): string
{
return 'po';
}
@@ -124,7 +118,7 @@ EOF;
return addcslashes($str, "\0..\37\42\134");
}
private function formatComments($comments, string $prefix = ''): ?string
private function formatComments(string|array $comments, string $prefix = ''): ?string
{
$output = null;

View File

@@ -20,10 +20,7 @@ use Symfony\Component\Translation\MessageCatalogue;
*/
class QtFileDumper extends FileDumper
{
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = [])
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
{
$dom = new \DOMDocument('1.0', 'utf-8');
$dom->formatOutput = true;
@@ -51,10 +48,7 @@ class QtFileDumper extends FileDumper
return $dom->saveXML();
}
/**
* {@inheritdoc}
*/
protected function getExtension()
protected function getExtension(): string
{
return 'ts';
}

View File

@@ -21,10 +21,12 @@ use Symfony\Component\Translation\MessageCatalogue;
*/
class XliffFileDumper extends FileDumper
{
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = [])
public function __construct(
private string $extension = 'xlf',
) {
}
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
{
$xliffVersion = '1.2';
if (\array_key_exists('xliff_version', $options)) {
@@ -47,12 +49,9 @@ class XliffFileDumper extends FileDumper
throw new InvalidArgumentException(sprintf('No support implemented for dumping XLIFF version "%s".', $xliffVersion));
}
/**
* {@inheritdoc}
*/
protected function getExtension()
protected function getExtension(): string
{
return 'xlf';
return $this->extension;
}
private function dumpXliff1(string $defaultLocale, MessageCatalogue $messages, ?string $domain, array $options = [])
@@ -81,6 +80,15 @@ class XliffFileDumper extends FileDumper
$xliffTool->setAttribute($id, $value);
}
if ($catalogueMetadata = $messages->getCatalogueMetadata('', $domain) ?? []) {
$xliffPropGroup = $xliffHead->appendChild($dom->createElement('prop-group'));
foreach ($catalogueMetadata as $key => $value) {
$xliffProp = $xliffPropGroup->appendChild($dom->createElement('prop'));
$xliffProp->setAttribute('prop-type', $key);
$xliffProp->appendChild($dom->createTextNode($value));
}
}
$xliffBody = $xliffFile->appendChild($dom->createElement('body'));
foreach ($messages->all($domain) as $source => $target) {
$translation = $dom->createElement('trans-unit');
@@ -147,6 +155,16 @@ class XliffFileDumper extends FileDumper
$xliffFile->setAttribute('id', $domain.'.'.$messages->getLocale());
}
if ($catalogueMetadata = $messages->getCatalogueMetadata('', $domain) ?? []) {
$xliff->setAttribute('xmlns:m', 'urn:oasis:names:tc:xliff:metadata:2.0');
$xliffMetadata = $xliffFile->appendChild($dom->createElement('m:metadata'));
foreach ($catalogueMetadata as $key => $value) {
$xliffMeta = $xliffMetadata->appendChild($dom->createElement('prop'));
$xliffMeta->setAttribute('type', $key);
$xliffMeta->appendChild($dom->createTextNode($value));
}
}
foreach ($messages->all($domain) as $source => $target) {
$translation = $dom->createElement('unit');
$translation->setAttribute('id', strtr(substr(base64_encode(hash('sha256', $source, true)), 0, 7), '/+', '._'));

View File

@@ -23,17 +23,14 @@ use Symfony\Component\Yaml\Yaml;
*/
class YamlFileDumper extends FileDumper
{
private $extension;
private string $extension;
public function __construct(string $extension = 'yml')
{
$this->extension = $extension;
}
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = [])
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
{
if (!class_exists(Yaml::class)) {
throw new LogicException('Dumping translations in the YAML format requires the Symfony Yaml component.');
@@ -52,10 +49,7 @@ class YamlFileDumper extends FileDumper
return Yaml::dump($data);
}
/**
* {@inheritdoc}
*/
protected function getExtension()
protected function getExtension(): string
{
return $this->extension;
}

View File

@@ -18,8 +18,8 @@ use Symfony\Contracts\HttpClient\ResponseInterface;
*/
class ProviderException extends RuntimeException implements ProviderExceptionInterface
{
private $response;
private $debug;
private ResponseInterface $response;
private string $debug;
public function __construct(string $message, ResponseInterface $response, int $code = 0, \Exception $previous = null)
{

View File

@@ -20,12 +20,7 @@ use Symfony\Component\Translation\Exception\InvalidArgumentException;
*/
abstract class AbstractFileExtractor
{
/**
* @param string|iterable $resource Files, a file or a directory
*
* @return iterable
*/
protected function extractFiles($resource)
protected function extractFiles(string|iterable $resource): iterable
{
if (is_iterable($resource)) {
$files = [];
@@ -49,11 +44,9 @@ abstract class AbstractFileExtractor
}
/**
* @return bool
*
* @throws InvalidArgumentException
*/
protected function isFile(string $file)
protected function isFile(string $file): bool
{
if (!is_file($file)) {
throw new InvalidArgumentException(sprintf('The "%s" file does not exist.', $file));
@@ -68,9 +61,7 @@ abstract class AbstractFileExtractor
abstract protected function canBeExtracted(string $file);
/**
* @param string|array $resource Files, a file or a directory
*
* @return iterable
*/
abstract protected function extractFromDirectory($resource);
abstract protected function extractFromDirectory(string|array $resource);
}

View File

@@ -25,7 +25,7 @@ class ChainExtractor implements ExtractorInterface
*
* @var ExtractorInterface[]
*/
private $extractors = [];
private array $extractors = [];
/**
* Adds a loader to the translation extractor.
@@ -35,9 +35,6 @@ class ChainExtractor implements ExtractorInterface
$this->extractors[$format] = $extractor;
}
/**
* {@inheritdoc}
*/
public function setPrefix(string $prefix)
{
foreach ($this->extractors as $extractor) {
@@ -45,10 +42,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);

View File

@@ -26,7 +26,7 @@ interface ExtractorInterface
*
* @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.

View File

@@ -0,0 +1,78 @@
<?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 PhpParser\NodeTraverser;
use PhpParser\NodeVisitor;
use PhpParser\Parser;
use PhpParser\ParserFactory;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Translation\Extractor\Visitor\AbstractVisitor;
use Symfony\Component\Translation\MessageCatalogue;
/**
* PhpAstExtractor extracts translation messages from a PHP AST.
*
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*/
final class PhpAstExtractor extends AbstractFileExtractor implements ExtractorInterface
{
private Parser $parser;
public function __construct(
/**
* @param iterable<AbstractVisitor&NodeVisitor> $visitors
*/
private readonly iterable $visitors,
private string $prefix = '',
) {
if (!class_exists(ParserFactory::class)) {
throw new \LogicException(sprintf('You cannot use "%s" as the "nikic/php-parser" package is not installed. Try running "composer require nikic/php-parser".', static::class));
}
$this->parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
}
public function extract(iterable|string $resource, MessageCatalogue $catalogue): void
{
foreach ($this->extractFiles($resource) as $file) {
$traverser = new NodeTraverser();
/** @var AbstractVisitor&NodeVisitor $visitor */
foreach ($this->visitors as $visitor) {
$visitor->initialize($catalogue, $file, $this->prefix);
$traverser->addVisitor($visitor);
}
$nodes = $this->parser->parse(file_get_contents($file));
$traverser->traverse($nodes);
}
}
public function setPrefix(string $prefix): void
{
$this->prefix = $prefix;
}
protected function canBeExtracted(string $file): bool
{
return 'php' === pathinfo($file, \PATHINFO_EXTENSION) && $this->isFile($file);
}
protected function extractFromDirectory(array|string $resource): iterable|Finder
{
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));
}
return (new Finder())->files()->name('*.php')->in($resource);
}
}

View File

@@ -11,6 +11,8 @@
namespace Symfony\Component\Translation\Extractor;
trigger_deprecation('symfony/translation', '6.2', '"%s" is deprecated, use "%s" instead.', PhpExtractor::class, PhpAstExtractor::class);
use Symfony\Component\Finder\Finder;
use Symfony\Component\Translation\MessageCatalogue;
@@ -18,6 +20,8 @@ use Symfony\Component\Translation\MessageCatalogue;
* PhpExtractor extracts translation messages from a PHP template.
*
* @author Michel Salib <michelsalib@hotmail.com>
*
* @deprecated since Symfony 6.2, use the PhpAstExtractor instead
*/
class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
{
@@ -28,7 +32,7 @@ class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
/**
* Prefix for new found message.
*/
private $prefix = '';
private string $prefix = '';
/**
* The sequence that captures translation messages.
@@ -128,10 +132,7 @@ class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
],
];
/**
* {@inheritdoc}
*/
public function extract($resource, MessageCatalogue $catalog)
public function extract(string|iterable $resource, MessageCatalogue $catalog)
{
$files = $this->extractFiles($resource);
foreach ($files as $file) {
@@ -141,9 +142,6 @@ class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
}
}
/**
* {@inheritdoc}
*/
public function setPrefix(string $prefix)
{
$this->prefix = $prefix;
@@ -151,12 +149,8 @@ class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
/**
* Normalizes a token.
*
* @param mixed $token
*
* @return string|null
*/
protected function normalizeToken($token)
protected function normalizeToken(mixed $token): ?string
{
if (isset($token[1]) && 'b"' !== $token) {
return $token[1];
@@ -311,19 +305,14 @@ class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
}
/**
* @return bool
*
* @throws \InvalidArgumentException
*/
protected function canBeExtracted(string $file)
protected function canBeExtracted(string $file): bool
{
return $this->isFile($file) && 'php' === pathinfo($file, \PATHINFO_EXTENSION);
}
/**
* {@inheritdoc}
*/
protected function extractFromDirectory($directory)
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));

View File

@@ -11,6 +11,8 @@
namespace Symfony\Component\Translation\Extractor;
trigger_deprecation('symfony/translation', '6.2', '"%s" is deprecated.', PhpStringTokenParser::class);
/*
* The following is derived from code at http://github.com/nikic/PHP-Parser
*
@@ -47,6 +49,9 @@ namespace Symfony\Component\Translation\Extractor;
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @deprecated since Symfony 6.2
*/
class PhpStringTokenParser
{
protected static $replacements = [
@@ -64,10 +69,8 @@ class PhpStringTokenParser
* Parses a string token.
*
* @param string $str String token content
*
* @return string
*/
public static function parse(string $str)
public static function parse(string $str): string
{
$bLength = 0;
if ('b' === $str[0]) {
@@ -90,10 +93,8 @@ class PhpStringTokenParser
*
* @param string $str String without quotes
* @param string|null $quote Quote type
*
* @return string
*/
public static function parseEscapeSequences(string $str, string $quote = null)
public static function parseEscapeSequences(string $str, string $quote = null): string
{
if (null !== $quote) {
$str = str_replace('\\'.$quote, $quote, $str);
@@ -124,10 +125,8 @@ class PhpStringTokenParser
*
* @param string $startToken Doc string start token content (<<<SMTHG)
* @param string $str String token content
*
* @return string
*/
public static function parseDocString(string $startToken, string $str)
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);

View File

@@ -0,0 +1,111 @@
<?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\Visitor;
use PhpParser\Node;
use Symfony\Component\Translation\MessageCatalogue;
/**
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*/
abstract class AbstractVisitor
{
private MessageCatalogue $catalogue;
private \SplFileInfo $file;
private string $messagePrefix;
public function initialize(MessageCatalogue $catalogue, \SplFileInfo $file, string $messagePrefix): void
{
$this->catalogue = $catalogue;
$this->file = $file;
$this->messagePrefix = $messagePrefix;
}
protected function addMessageToCatalogue(string $message, ?string $domain, int $line): void
{
$domain ??= 'messages';
$this->catalogue->set($message, $this->messagePrefix.$message, $domain);
$metadata = $this->catalogue->getMetadata($message, $domain) ?? [];
$normalizedFilename = preg_replace('{[\\\\/]+}', '/', $this->file);
$metadata['sources'][] = $normalizedFilename.':'.$line;
$this->catalogue->setMetadata($message, $metadata, $domain);
}
protected function getStringArguments(Node\Expr\CallLike|Node\Attribute|Node\Expr\New_ $node, int|string $index, bool $indexIsRegex = false): array
{
if (\is_string($index)) {
return $this->getStringNamedArguments($node, $index, $indexIsRegex);
}
$args = $node instanceof Node\Expr\CallLike ? $node->getRawArgs() : $node->args;
if (!($arg = $args[$index] ?? null) instanceof Node\Arg) {
return [];
}
return (array) $this->getStringValue($arg->value);
}
protected function hasNodeNamedArguments(Node\Expr\CallLike|Node\Attribute|Node\Expr\New_ $node): bool
{
$args = $node instanceof Node\Expr\CallLike ? $node->getRawArgs() : $node->args;
foreach ($args as $arg) {
if ($arg instanceof Node\Arg && null !== $arg->name) {
return true;
}
}
return false;
}
private function getStringNamedArguments(Node\Expr\CallLike|Node\Attribute $node, string $argumentName = null, bool $isArgumentNamePattern = false): array
{
$args = $node instanceof Node\Expr\CallLike ? $node->getArgs() : $node->args;
$argumentValues = [];
foreach ($args as $arg) {
if (!$isArgumentNamePattern && $arg->name?->toString() === $argumentName) {
$argumentValues[] = $this->getStringValue($arg->value);
} elseif ($isArgumentNamePattern && preg_match($argumentName, $arg->name?->toString() ?? '') > 0) {
$argumentValues[] = $this->getStringValue($arg->value);
}
}
return array_filter($argumentValues);
}
private function getStringValue(Node $node): ?string
{
if ($node instanceof Node\Scalar\String_) {
return $node->value;
}
if ($node instanceof Node\Expr\BinaryOp\Concat) {
if (null === $left = $this->getStringValue($node->left)) {
return null;
}
if (null === $right = $this->getStringValue($node->right)) {
return null;
}
return $left.$right;
}
if ($node instanceof Node\Expr\Assign && $node->expr instanceof Node\Scalar\String_) {
return $node->expr->value;
}
return null;
}
}

View File

@@ -0,0 +1,114 @@
<?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\Visitor;
use PhpParser\Node;
use PhpParser\NodeVisitor;
/**
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*
* Code mostly comes from https://github.com/php-translation/extractor/blob/master/src/Visitor/Php/Symfony/Constraint.php
*/
final class ConstraintVisitor extends AbstractVisitor implements NodeVisitor
{
private const CONSTRAINT_VALIDATION_MESSAGE_PATTERN = '/[a-zA-Z]*message/i';
public function __construct(
private readonly array $constraintClassNames = []
) {
}
public function beforeTraverse(array $nodes): ?Node
{
return null;
}
public function enterNode(Node $node): ?Node
{
if (!$node instanceof Node\Expr\New_ && !$node instanceof Node\Attribute) {
return null;
}
$className = $node instanceof Node\Attribute ? $node->name : $node->class;
if (!$className instanceof Node\Name) {
return null;
}
$parts = $className->parts;
$isConstraintClass = false;
foreach ($parts as $part) {
if (\in_array($part, $this->constraintClassNames, true)) {
$isConstraintClass = true;
break;
}
}
if (!$isConstraintClass) {
return null;
}
$arg = $node->args[0] ?? null;
if (!$arg instanceof Node\Arg) {
return null;
}
if ($this->hasNodeNamedArguments($node)) {
$messages = $this->getStringArguments($node, self::CONSTRAINT_VALIDATION_MESSAGE_PATTERN, true);
} else {
if (!$arg->value instanceof Node\Expr\Array_) {
// There is no way to guess which argument is a message to be translated.
return null;
}
$messages = [];
$options = $arg->value;
/** @var Node\Expr\ArrayItem $item */
foreach ($options->items as $item) {
if (!$item->key instanceof Node\Scalar\String_) {
continue;
}
if (!preg_match(self::CONSTRAINT_VALIDATION_MESSAGE_PATTERN, $item->key->value ?? '')) {
continue;
}
if (!$item->value instanceof Node\Scalar\String_) {
continue;
}
$messages[] = $item->value->value;
break;
}
}
foreach ($messages as $message) {
$this->addMessageToCatalogue($message, 'validators', $node->getStartLine());
}
return null;
}
public function leaveNode(Node $node): ?Node
{
return null;
}
public function afterTraverse(array $nodes): ?Node
{
return null;
}
}

View File

@@ -0,0 +1,64 @@
<?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\Visitor;
use PhpParser\Node;
use PhpParser\NodeVisitor;
/**
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*/
final class TransMethodVisitor extends AbstractVisitor implements NodeVisitor
{
public function beforeTraverse(array $nodes): ?Node
{
return null;
}
public function enterNode(Node $node): ?Node
{
if (!$node instanceof Node\Expr\MethodCall && !$node instanceof Node\Expr\FuncCall) {
return null;
}
if (!\is_string($node->name) && !$node->name instanceof Node\Identifier && !$node->name instanceof Node\Name) {
return null;
}
$name = (string) $node->name;
if ('trans' === $name || 't' === $name) {
$nodeHasNamedArguments = $this->hasNodeNamedArguments($node);
if (!$messages = $this->getStringArguments($node, $nodeHasNamedArguments ? 'message' : 0)) {
return null;
}
$domain = $this->getStringArguments($node, $nodeHasNamedArguments ? 'domain' : 2)[0] ?? null;
foreach ($messages as $message) {
$this->addMessageToCatalogue($message, $domain, $node->getStartLine());
}
}
return null;
}
public function leaveNode(Node $node): ?Node
{
return null;
}
public function afterTraverse(array $nodes): ?Node
{
return null;
}
}

View 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\Extractor\Visitor;
use PhpParser\Node;
use PhpParser\NodeVisitor;
/**
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*/
final class TranslatableMessageVisitor extends AbstractVisitor implements NodeVisitor
{
public function beforeTraverse(array $nodes): ?Node
{
return null;
}
public function enterNode(Node $node): ?Node
{
if (!$node instanceof Node\Expr\New_) {
return null;
}
if (!($className = $node->class) instanceof Node\Name) {
return null;
}
if (!\in_array('TranslatableMessage', $className->parts, true)) {
return null;
}
$nodeHasNamedArguments = $this->hasNodeNamedArguments($node);
if (!$messages = $this->getStringArguments($node, $nodeHasNamedArguments ? 'message' : 0)) {
return null;
}
$domain = $this->getStringArguments($node, $nodeHasNamedArguments ? 'domain' : 2)[0] ?? null;
foreach ($messages as $message) {
$this->addMessageToCatalogue($message, $domain, $node->getStartLine());
}
return null;
}
public function leaveNode(Node $node): ?Node
{
return null;
}
public function afterTraverse(array $nodes): ?Node
{
return null;
}
}

View File

@@ -23,9 +23,6 @@ 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
@@ -34,7 +31,7 @@ class IntlFormatter implements IntlFormatterInterface
}
if (!$formatter = $this->cache[$locale][$message] ?? null) {
if (!($this->hasMessageFormatter ?? $this->hasMessageFormatter = class_exists(\MessageFormatter::class))) {
if (!$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 {

View File

@@ -22,8 +22,8 @@ class_exists(IntlFormatter::class);
*/
class MessageFormatter implements MessageFormatterInterface, IntlFormatterInterface
{
private $translator;
private $intlFormatter;
private TranslatorInterface $translator;
private IntlFormatterInterface $intlFormatter;
/**
* @param TranslatorInterface|null $translator An identity translator to use as selector for pluralization
@@ -34,10 +34,7 @@ class MessageFormatter implements MessageFormatterInterface, IntlFormatterInterf
$this->intlFormatter = $intlFormatter ?? new IntlFormatter();
}
/**
* {@inheritdoc}
*/
public function format(string $message, string $locale, array $parameters = [])
public function format(string $message, string $locale, array $parameters = []): string
{
if ($this->translator instanceof TranslatorInterface) {
return $this->translator->trans($message, $parameters, null, $locale);
@@ -46,9 +43,6 @@ class MessageFormatter implements MessageFormatterInterface, IntlFormatterInterf
return strtr($message, $parameters);
}
/**
* {@inheritdoc}
*/
public function formatIntl(string $message, string $locale, array $parameters = []): string
{
return $this->intlFormatter->formatIntl($message, $locale, $parameters);

View File

@@ -23,8 +23,6 @@ interface MessageFormatterInterface
* @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
*
* @return string
*/
public function format(string $message, string $locale, array $parameters = []);
public function format(string $message, string $locale, array $parameters = []): string;
}

View File

@@ -20,10 +20,7 @@ use Symfony\Component\Translation\MessageCatalogue;
*/
class ArrayLoader implements LoaderInterface
{
/**
* {@inheritdoc}
*/
public function load($resource, string $locale, string $domain = 'messages')
public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue
{
$resource = $this->flatten($resource);
$catalogue = new MessageCatalogue($locale);

View File

@@ -20,14 +20,11 @@ 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(string $resource)
protected function loadResource(string $resource): array
{
$messages = [];
@@ -45,7 +42,7 @@ class CsvFileLoader extends FileLoader
continue;
}
if ('#' !== substr($data[0], 0, 1) && isset($data[1]) && 2 === \count($data)) {
if (!str_starts_with($data[0], '#') && isset($data[1]) && 2 === \count($data)) {
$messages[$data[0]] = $data[1];
}
}

View File

@@ -14,16 +14,14 @@ 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\Translation\MessageCatalogue;
/**
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*/
abstract class FileLoader extends ArrayLoader
{
/**
* {@inheritdoc}
*/
public function load($resource, string $locale, string $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));
@@ -36,9 +34,7 @@ abstract class FileLoader extends ArrayLoader
$messages = $this->loadResource($resource);
// empty resource
if (null === $messages) {
$messages = [];
}
$messages ??= [];
// not an array
if (!\is_array($messages)) {
@@ -55,9 +51,7 @@ abstract class FileLoader extends ArrayLoader
}
/**
* @return array
*
* @throws InvalidResourceException if stream content has an invalid format
*/
abstract protected function loadResource(string $resource);
abstract protected function loadResource(string $resource): array;
}

View File

@@ -23,10 +23,7 @@ use Symfony\Component\Translation\MessageCatalogue;
*/
class IcuDatFileLoader extends IcuResFileLoader
{
/**
* {@inheritdoc}
*/
public function load($resource, string $locale, string $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));
@@ -38,7 +35,7 @@ class IcuDatFileLoader extends IcuResFileLoader
try {
$rb = new \ResourceBundle($locale, $resource);
} catch (\Exception $e) {
} catch (\Exception) {
$rb = null;
}

View File

@@ -23,10 +23,7 @@ use Symfony\Component\Translation\MessageCatalogue;
*/
class IcuResFileLoader implements LoaderInterface
{
/**
* {@inheritdoc}
*/
public function load($resource, string $locale, string $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));
@@ -38,7 +35,7 @@ class IcuResFileLoader implements LoaderInterface
try {
$rb = new \ResourceBundle($locale, $resource);
} catch (\Exception $e) {
} catch (\Exception) {
$rb = null;
}
@@ -72,10 +69,8 @@ class IcuResFileLoader implements LoaderInterface
* @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
*/
protected function flatten(\ResourceBundle $rb, array &$messages = [], string $path = null)
protected function flatten(\ResourceBundle $rb, array &$messages = [], string $path = null): array
{
foreach ($rb as $key => $value) {
$nodePath = $path ? $path.'.'.$key : $key;

View File

@@ -18,10 +18,7 @@ namespace Symfony\Component\Translation\Loader;
*/
class IniFileLoader extends FileLoader
{
/**
* {@inheritdoc}
*/
protected function loadResource(string $resource)
protected function loadResource(string $resource): array
{
return parse_ini_file($resource, true);
}

View File

@@ -20,10 +20,7 @@ use Symfony\Component\Translation\Exception\InvalidResourceException;
*/
class JsonFileLoader extends FileLoader
{
/**
* {@inheritdoc}
*/
protected function loadResource(string $resource)
protected function loadResource(string $resource): array
{
$messages = [];
if ($data = file_get_contents($resource)) {
@@ -42,19 +39,13 @@ class JsonFileLoader extends FileLoader
*/
private function getJSONErrorMessage(int $errorCode): string
{
switch ($errorCode) {
case \JSON_ERROR_DEPTH:
return 'Maximum stack depth exceeded';
case \JSON_ERROR_STATE_MISMATCH:
return 'Underflow or the modes mismatch';
case \JSON_ERROR_CTRL_CHAR:
return 'Unexpected control character found';
case \JSON_ERROR_SYNTAX:
return 'Syntax error, malformed JSON';
case \JSON_ERROR_UTF8:
return 'Malformed UTF-8 characters, possibly incorrectly encoded';
default:
return 'Unknown error';
}
return match ($errorCode) {
\JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
\JSON_ERROR_STATE_MISMATCH => 'Underflow or the modes mismatch',
\JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
\JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
\JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded',
default => 'Unknown error',
};
}
}

View File

@@ -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
*
* @throws NotFoundResourceException when the resource cannot be found
* @throws InvalidResourceException when the resource cannot be loaded
*/
public function load($resource, string $locale, string $domain = 'messages');
public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue;
}

View File

@@ -38,10 +38,8 @@ class MoFileLoader extends FileLoader
/**
* Parses machine object (MO) format, independent of the machine's endian it
* was created on. Both 32bit and 64bit systems are supported.
*
* {@inheritdoc}
*/
protected function loadResource(string $resource)
protected function loadResource(string $resource): array
{
$stream = fopen($resource, 'r');

View File

@@ -18,14 +18,11 @@ namespace Symfony\Component\Translation\Loader;
*/
class PhpFileLoader extends FileLoader
{
private static $cache = [];
private static ?array $cache = [];
/**
* {@inheritdoc}
*/
protected function loadResource(string $resource)
protected function loadResource(string $resource): array
{
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))) {
if ([] === self::$cache && \function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOL) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) || filter_var(\ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOL))) {
self::$cache = null;
}
@@ -33,10 +30,6 @@ class PhpFileLoader extends FileLoader
return require $resource;
}
if (isset(self::$cache[$resource])) {
return self::$cache[$resource];
}
return self::$cache[$resource] = require $resource;
return self::$cache[$resource] ??= require $resource;
}
}

View File

@@ -57,10 +57,8 @@ class PoFileLoader extends FileLoader
* - Message IDs are allowed to have other encodings as just US-ASCII.
*
* Items with an empty id are ignored.
*
* {@inheritdoc}
*/
protected function loadResource(string $resource)
protected function loadResource(string $resource): array
{
$stream = fopen($resource, 'r');
@@ -83,15 +81,15 @@ class PoFileLoader extends FileLoader
}
$item = $defaults;
$flags = [];
} elseif ('#,' === substr($line, 0, 2)) {
} elseif (str_starts_with($line, '#,')) {
$flags = array_map('trim', explode(',', substr($line, 2)));
} elseif ('msgid "' === substr($line, 0, 7)) {
} elseif (str_starts_with($line, 'msgid "')) {
// 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 ('msgstr "' === substr($line, 0, 8)) {
} elseif (str_starts_with($line, 'msgstr "')) {
$item['translated'] = substr($line, 8, -1);
} elseif ('"' === $line[0]) {
$continues = isset($item['translated']) ? 'translated' : 'ids';
@@ -102,9 +100,9 @@ class PoFileLoader extends FileLoader
} else {
$item[$continues] .= substr($line, 1, -1);
}
} elseif ('msgid_plural "' === substr($line, 0, 14)) {
} elseif (str_starts_with($line, 'msgid_plural "')) {
$item['ids']['plural'] = substr($line, 14, -1);
} elseif ('msgstr[' === substr($line, 0, 7)) {
} elseif (str_starts_with($line, 'msgstr[')) {
$size = strpos($line, ']');
$item['translated'][(int) substr($line, 7, 1)] = substr($line, $size + 3, -1);
}

View File

@@ -25,10 +25,7 @@ use Symfony\Component\Translation\MessageCatalogue;
*/
class QtFileLoader implements LoaderInterface
{
/**
* {@inheritdoc}
*/
public function load($resource, string $locale, string $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.');

View File

@@ -28,10 +28,7 @@ use Symfony\Component\Translation\Util\XliffUtils;
*/
class XliffFileLoader implements LoaderInterface
{
/**
* {@inheritdoc}
*/
public function load($resource, string $locale, string $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 Xliff format requires the Symfony Config component.');
@@ -104,6 +101,10 @@ class XliffFileLoader implements LoaderInterface
$file->registerXPathNamespace('xliff', $namespace);
foreach ($file->xpath('.//xliff:prop') as $prop) {
$catalogue->setCatalogueMetadata($prop->attributes()['prop-type'], (string) $prop, $domain);
}
foreach ($file->xpath('.//xliff:trans-unit') as $translation) {
$attributes = $translation->attributes();
@@ -227,6 +228,6 @@ class XliffFileLoader implements LoaderInterface
private function isXmlString(string $resource): bool
{
return 0 === strpos($resource, '<?xml');
return str_starts_with($resource, '<?xml');
}
}

View File

@@ -26,10 +26,7 @@ class YamlFileLoader extends FileLoader
{
private $yamlParser;
/**
* {@inheritdoc}
*/
protected function loadResource(string $resource)
protected function loadResource(string $resource): array
{
if (null === $this->yamlParser) {
if (!class_exists(\Symfony\Component\Yaml\Parser::class)) {

View File

@@ -0,0 +1,75 @@
<?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\Routing\RequestContext;
use Symfony\Contracts\Translation\LocaleAwareInterface;
/**
* @author Kevin Bond <kevinbond@gmail.com>
*/
class LocaleSwitcher implements LocaleAwareInterface
{
private string $defaultLocale;
/**
* @param LocaleAwareInterface[] $localeAwareServices
*/
public function __construct(
private string $locale,
private iterable $localeAwareServices,
private ?RequestContext $requestContext = null,
) {
$this->defaultLocale = $locale;
}
public function setLocale(string $locale): void
{
\Locale::setDefault($this->locale = $locale);
$this->requestContext?->setParameter('_locale', $locale);
foreach ($this->localeAwareServices as $service) {
$service->setLocale($locale);
}
}
public function getLocale(): string
{
return $this->locale;
}
/**
* Switch to a new locale, execute a callback, then switch back to the original.
*
* @template T
*
* @param callable():T $callback
*
* @return T
*/
public function runWithLocale(string $locale, callable $callback): mixed
{
$original = $this->getLocale();
$this->setLocale($locale);
try {
return $callback();
} finally {
$this->setLocale($original);
}
}
public function reset(): void
{
$this->setLocale($this->defaultLocale);
}
}

View File

@@ -21,8 +21,8 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*/
class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface
{
private $translator;
private $logger;
private TranslatorInterface $translator;
private LoggerInterface $logger;
/**
* @param TranslatorInterface&TranslatorBagInterface&LocaleAwareInterface $translator The translator must implement TranslatorBagInterface
@@ -37,10 +37,7 @@ class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface,
$this->logger = $logger;
}
/**
* {@inheritdoc}
*/
public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null)
public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null): string
{
$trans = $this->translator->trans($id = (string) $id, $parameters, $domain, $locale);
$this->log($id, $domain, $locale);
@@ -48,9 +45,6 @@ class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface,
return $trans;
}
/**
* {@inheritdoc}
*/
public function setLocale(string $locale)
{
$prev = $this->translator->getLocale();
@@ -62,25 +56,16 @@ class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface,
$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();
}
/**
* {@inheritdoc}
*/
public function getCatalogue(string $locale = null)
public function getCatalogue(string $locale = null): MessageCatalogueInterface
{
return $this->translator->getCatalogue($locale);
}
/**
* {@inheritdoc}
*/
public function getCatalogues(): array
{
return $this->translator->getCatalogues();
@@ -88,10 +73,8 @@ class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface,
/**
* Gets the fallback locales.
*
* @return array
*/
public function getFallbackLocales()
public function getFallbackLocales(): array
{
if ($this->translator instanceof Translator || method_exists($this->translator, 'getFallbackLocales')) {
return $this->translator->getFallbackLocales();
@@ -113,9 +96,7 @@ class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface,
*/
private function log(string $id, ?string $domain, ?string $locale)
{
if (null === $domain) {
$domain = 'messages';
}
$domain ??= 'messages';
$catalogue = $this->translator->getCatalogue($locale);
if ($catalogue->defines($id, $domain)) {

View File

@@ -17,14 +17,15 @@ use Symfony\Component\Translation\Exception\LogicException;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterface
class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterface, CatalogueMetadataAwareInterface
{
private $messages = [];
private $metadata = [];
private $resources = [];
private $locale;
private $fallbackCatalogue;
private $parent;
private array $messages = [];
private array $metadata = [];
private array $catalogueMetadata = [];
private array $resources = [];
private string $locale;
private ?MessageCatalogueInterface $fallbackCatalogue = null;
private ?self $parent = null;
/**
* @param array $messages An array of messages classified by domain
@@ -35,18 +36,12 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
$this->messages = $messages;
}
/**
* {@inheritdoc}
*/
public function getLocale()
public function getLocale(): string
{
return $this->locale;
}
/**
* {@inheritdoc}
*/
public function getDomains()
public function getDomains(): array
{
$domains = [];
@@ -60,10 +55,7 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
return array_values($domains);
}
/**
* {@inheritdoc}
*/
public function all(string $domain = null)
public function all(string $domain = null): array
{
if (null !== $domain) {
// skip messages merge if intl-icu requested explicitly
@@ -88,18 +80,12 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
return $allMessages;
}
/**
* {@inheritdoc}
*/
public function set(string $id, string $translation, string $domain = 'messages')
{
$this->add([$id => $translation], $domain);
}
/**
* {@inheritdoc}
*/
public function has(string $id, string $domain = 'messages')
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;
@@ -112,18 +98,12 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
return false;
}
/**
* {@inheritdoc}
*/
public function defines(string $id, string $domain = 'messages')
public function defines(string $id, string $domain = 'messages'): bool
{
return isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id]);
}
/**
* {@inheritdoc}
*/
public function get(string $id, string $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];
@@ -140,9 +120,6 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
return $id;
}
/**
* {@inheritdoc}
*/
public function replace(array $messages, string $domain = 'messages')
{
unset($this->messages[$domain], $this->messages[$domain.self::INTL_DOMAIN_SUFFIX]);
@@ -150,9 +127,6 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
$this->add($messages, $domain);
}
/**
* {@inheritdoc}
*/
public function add(array $messages, string $domain = 'messages')
{
$altDomain = str_ends_with($domain, self::INTL_DOMAIN_SUFFIX) ? substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX)) : $domain.self::INTL_DOMAIN_SUFFIX;
@@ -166,9 +140,6 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
}
}
/**
* {@inheritdoc}
*/
public function addCatalogue(MessageCatalogueInterface $catalogue)
{
if ($catalogue->getLocale() !== $this->locale) {
@@ -191,11 +162,13 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
$metadata = $catalogue->getMetadata('', '');
$this->addMetadata($metadata);
}
if ($catalogue instanceof CatalogueMetadataAwareInterface) {
$catalogueMetadata = $catalogue->getCatalogueMetadata('', '');
$this->addCatalogueMetadata($catalogueMetadata);
}
}
/**
* {@inheritdoc}
*/
public function addFallbackCatalogue(MessageCatalogueInterface $catalogue)
{
// detect circular references
@@ -225,34 +198,22 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
}
}
/**
* {@inheritdoc}
*/
public function getFallbackCatalogue()
public function getFallbackCatalogue(): ?MessageCatalogueInterface
{
return $this->fallbackCatalogue;
}
/**
* {@inheritdoc}
*/
public function getResources()
public function getResources(): array
{
return array_values($this->resources);
}
/**
* {@inheritdoc}
*/
public function addResource(ResourceInterface $resource)
{
$this->resources[$resource->__toString()] = $resource;
}
/**
* {@inheritdoc}
*/
public function getMetadata(string $key = '', string $domain = 'messages')
public function getMetadata(string $key = '', string $domain = 'messages'): mixed
{
if ('' == $domain) {
return $this->metadata;
@@ -271,17 +232,11 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
return null;
}
/**
* {@inheritdoc}
*/
public function setMetadata(string $key, $value, string $domain = 'messages')
public function setMetadata(string $key, mixed $value, string $domain = 'messages')
{
$this->metadata[$domain][$key] = $value;
}
/**
* {@inheritdoc}
*/
public function deleteMetadata(string $key = '', string $domain = 'messages')
{
if ('' == $domain) {
@@ -293,6 +248,41 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
}
}
public function getCatalogueMetadata(string $key = '', string $domain = 'messages'): mixed
{
if (!$domain) {
return $this->catalogueMetadata;
}
if (isset($this->catalogueMetadata[$domain])) {
if (!$key) {
return $this->catalogueMetadata[$domain];
}
if (isset($this->catalogueMetadata[$domain][$key])) {
return $this->catalogueMetadata[$domain][$key];
}
}
return null;
}
public function setCatalogueMetadata(string $key, mixed $value, string $domain = 'messages')
{
$this->catalogueMetadata[$domain][$key] = $value;
}
public function deleteCatalogueMetadata(string $key = '', string $domain = 'messages')
{
if (!$domain) {
$this->catalogueMetadata = [];
} elseif (!$key) {
unset($this->catalogueMetadata[$domain]);
} else {
unset($this->catalogueMetadata[$domain][$key]);
}
}
/**
* Adds current values with the new values.
*
@@ -306,4 +296,13 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
}
}
}
private function addCatalogueMetadata(array $values)
{
foreach ($values as $domain => $keys) {
foreach ($keys as $key => $value) {
$this->setCatalogueMetadata($key, $value, $domain);
}
}
}
}

View File

@@ -24,17 +24,13 @@ interface MessageCatalogueInterface
/**
* Gets the catalogue locale.
*
* @return string
*/
public function getLocale();
public function getLocale(): string;
/**
* Gets the domains.
*
* @return array
*/
public function getDomains();
public function getDomains(): array;
/**
* Gets the messages within a given domain.
@@ -42,10 +38,8 @@ interface MessageCatalogueInterface
* If $domain is null, it returns all messages.
*
* @param string $domain The domain name
*
* @return array
*/
public function all(string $domain = null);
public function all(string $domain = null): array;
/**
* Sets a message translation.
@@ -61,30 +55,24 @@ interface MessageCatalogueInterface
*
* @param string $id The message id
* @param string $domain The domain name
*
* @return bool
*/
public function has(string $id, string $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
*/
public function defines(string $id, string $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
*/
public function get(string $id, string $domain = 'messages');
public function get(string $id, string $domain = 'messages'): string;
/**
* Sets translations for a given domain.
@@ -119,17 +107,15 @@ interface MessageCatalogueInterface
/**
* Gets the fallback catalogue.
*
* @return self|null
*/
public function getFallbackCatalogue();
public function getFallbackCatalogue(): ?self;
/**
* Returns an array of resources loaded to build this collection.
*
* @return ResourceInterface[]
*/
public function getResources();
public function getResources(): array;
/**
* Adds a resource for this collection.

View File

@@ -12,7 +12,7 @@
namespace Symfony\Component\Translation;
/**
* MetadataAwareInterface.
* This interface is used to get, set, and delete metadata about the translation messages.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
@@ -27,14 +27,12 @@ interface MetadataAwareInterface
*
* @return mixed The value that was set or an array with the domains/keys or null
*/
public function getMetadata(string $key = '', string $domain = 'messages');
public function getMetadata(string $key = '', string $domain = 'messages'): mixed;
/**
* Adds metadata to a message domain.
*
* @param mixed $value
*/
public function setMetadata(string $key, $value, string $domain = 'messages');
public function setMetadata(string $key, mixed $value, string $domain = 'messages');
/**
* Deletes metadata for the given key and domain.

View File

@@ -20,14 +20,14 @@ use Symfony\Component\Translation\Exception\MissingRequiredOptionException;
*/
final class Dsn
{
private $scheme;
private $host;
private $user;
private $password;
private $port;
private $path;
private $options;
private $originalDsn;
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)
{
@@ -79,7 +79,7 @@ final class Dsn
return $this->port ?? $default;
}
public function getOption(string $key, $default = null)
public function getOption(string $key, mixed $default = null)
{
return $this->options[$key] ?? $default;
}

View File

@@ -21,9 +21,9 @@ use Symfony\Component\Translation\TranslatorBagInterface;
*/
class FilteringProvider implements ProviderInterface
{
private $provider;
private $locales;
private $domains;
private ProviderInterface $provider;
private array $locales;
private array $domains;
public function __construct(ProviderInterface $provider, array $locales, array $domains = [])
{
@@ -37,9 +37,6 @@ class FilteringProvider implements ProviderInterface
return (string) $this->provider;
}
/**
* {@inheritdoc}
*/
public function write(TranslatorBagInterface $translatorBag): void
{
$this->provider->write($translatorBag);

View File

@@ -18,8 +18,8 @@ use Symfony\Component\Translation\Exception\UnsupportedSchemeException;
*/
class TranslationProviderCollectionFactory
{
private $factories;
private $enabledLocales;
private iterable $factories;
private array $enabledLocales;
/**
* @param iterable<mixed, ProviderFactoryInterface> $factories

View File

@@ -20,16 +20,16 @@ final class PseudoLocalizationTranslator implements TranslatorInterface
{
private const EXPANSION_CHARACTER = '~';
private $translator;
private $accents;
private $expansionFactor;
private $brackets;
private $parseHTML;
private TranslatorInterface $translator;
private bool $accents;
private float $expansionFactor;
private bool $brackets;
private bool $parseHTML;
/**
* @var string[]
*/
private $localizableHTMLAttributes;
private array $localizableHTMLAttributes;
/**
* Available options:
@@ -83,9 +83,6 @@ final class PseudoLocalizationTranslator implements TranslatorInterface
$this->localizableHTMLAttributes = $options['localizable_html_attributes'] ?? [];
}
/**
* {@inheritdoc}
*/
public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null): string
{
$trans = '';

View File

@@ -26,12 +26,11 @@ echo $translator->trans('Hello World!'); // outputs « Bonjour ! »
Sponsor
-------
The Translation component for Symfony 5.4/6.0 is [backed][1] by:
The Translation component for Symfony 6.1 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!
Help Symfony by [sponsoring][3] its development!
Resources
---------
@@ -44,5 +43,4 @@ Resources
[1]: https://symfony.com/backers
[2]: https://crowdin.com
[3]: https://lokalise.com
[4]: https://symfony.com/sponsor
[3]: https://symfony.com/sponsor

View File

@@ -27,7 +27,7 @@ class TranslationReader implements TranslationReaderInterface
*
* @var array<string, LoaderInterface>
*/
private $loaders = [];
private array $loaders = [];
/**
* Adds a loader to the translation extractor.
@@ -39,9 +39,6 @@ class TranslationReader implements TranslationReaderInterface
$this->loaders[$format] = $loader;
}
/**
* {@inheritdoc}
*/
public function read(string $directory, MessageCatalogue $catalogue)
{
if (!is_dir($directory)) {

View File

@@ -66,7 +66,7 @@ foreach (array_slice($argv, 1) as $argumentOrOption) {
continue;
}
if (0 === strpos($argumentOrOption, '-')) {
if (str_starts_with($argumentOrOption, '-')) {
$config['verbose_output'] = true;
} else {
$config['locale_to_analyze'] = $argumentOrOption;

View File

@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
May-19-2004:
@@ -1646,20 +1645,21 @@ Jan-10-2006
</xsd:group>
<xsd:attributeGroup name="AttrGroup_TextContent">
<xsd:attribute name="id" type="xsd:string" use="required"/>
<xsd:attribute name="ts" type="xsd:string" use="optional"/>
<xsd:attribute name="xid" type="xsd:string" use="optional"/>
<xsd:attribute name="equiv-text" type="xsd:string" use="optional"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
<xsd:anyAttribute namespace="##any" processContents="skip"/>
</xsd:attributeGroup>
<!-- XLIFF Structure -->
<xsd:element name="xliff">
<xsd:complexType>
<xsd:sequence maxOccurs="unbounded">
<xsd:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="strict"/>
<xsd:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="skip"/>
<xsd:element ref="xlf:file"/>
</xsd:sequence>
<xsd:attribute name="version" type="xlf:AttrType_Version" use="required"/>
<xsd:attribute ref="xml:lang" use="optional"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
<xsd:anyAttribute namespace="##any" processContents="skip"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="file">
@@ -1672,14 +1672,16 @@ Jan-10-2006
<xsd:attribute name="source-language" type="xsd:language" use="required"/>
<xsd:attribute name="datatype" type="xlf:AttrType_datatype" use="required"/>
<xsd:attribute name="tool-id" type="xsd:string" use="optional"/>
<xsd:attribute default="manual" name="tool" type="xsd:string" use="optional"/>
<xsd:attribute name="date" type="xsd:dateTime" use="optional"/>
<xsd:attribute ref="xml:space" use="optional"/>
<xsd:attribute name="ts" type="xsd:string" use="optional"/>
<xsd:attribute name="category" type="xsd:string" use="optional"/>
<xsd:attribute name="target-language" type="xsd:language" use="optional"/>
<xsd:attribute name="product-name" type="xsd:string" use="optional"/>
<xsd:attribute name="product-version" type="xsd:string" use="optional"/>
<xsd:attribute name="build-num" type="xsd:string" use="optional"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
<xsd:anyAttribute namespace="##any" processContents="skip"/>
</xsd:complexType>
<xsd:unique name="U_group_id">
<xsd:selector xpath=".//xlf:group"/>
@@ -1739,10 +1741,11 @@ Jan-10-2006
<xsd:element name="glossary" type="xlf:ElemType_ExternalReference"/>
<xsd:element name="reference" type="xlf:ElemType_ExternalReference"/>
<xsd:element ref="xlf:count-group"/>
<xsd:element ref="xlf:prop-group"/>
<xsd:element ref="xlf:note"/>
<xsd:element ref="xlf:tool"/>
</xsd:choice>
<xsd:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="strict"/>
<xsd:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="skip"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
@@ -1791,6 +1794,7 @@ Jan-10-2006
<xsd:attribute name="process-name" type="xsd:string" use="required"/>
<xsd:attribute name="company-name" type="xsd:string" use="optional"/>
<xsd:attribute name="tool-id" type="xsd:string" use="optional"/>
<xsd:attribute name="tool" type="xsd:string" use="optional"/>
<xsd:attribute name="date" type="xsd:dateTime" use="optional"/>
<xsd:attribute name="job-id" type="xsd:string" use="optional"/>
<xsd:attribute name="contact-name" type="xsd:string" use="optional"/>
@@ -1838,16 +1842,34 @@ Jan-10-2006
</xsd:simpleContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="prop-group">
<xsd:complexType>
<xsd:sequence maxOccurs="unbounded">
<xsd:element ref="xlf:prop"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="optional"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="prop">
<xsd:complexType>
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="prop-type" type="xsd:string" use="required"/>
<xsd:attribute ref="xml:lang" use="optional"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="tool">
<xsd:complexType mixed="true">
<xsd:sequence>
<xsd:any namespace="##any" processContents="strict" minOccurs="0" maxOccurs="unbounded"/>
<xsd:any namespace="##any" processContents="skip" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="tool-id" type="xsd:string" use="required"/>
<xsd:attribute name="tool-name" type="xsd:string" use="required"/>
<xsd:attribute name="tool-version" type="xsd:string" use="optional"/>
<xsd:attribute name="tool-company" type="xsd:string" use="optional"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
<xsd:anyAttribute namespace="##any" processContents="skip"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="body">
@@ -1865,8 +1887,9 @@ Jan-10-2006
<xsd:sequence>
<xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:context-group"/>
<xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:count-group"/>
<xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:prop-group"/>
<xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:note"/>
<xsd:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="strict"/>
<xsd:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="skip"/>
</xsd:sequence>
<xsd:choice maxOccurs="unbounded">
<xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:group"/>
@@ -1877,6 +1900,7 @@ Jan-10-2006
<xsd:attribute name="id" type="xsd:string" use="optional"/>
<xsd:attribute name="datatype" type="xlf:AttrType_datatype" use="optional"/>
<xsd:attribute default="default" ref="xml:space" use="optional"/>
<xsd:attribute name="ts" type="xsd:string" use="optional"/>
<xsd:attribute name="restype" type="xlf:AttrType_restype" use="optional"/>
<xsd:attribute name="resname" type="xsd:string" use="optional"/>
<xsd:attribute name="extradata" type="xsd:string" use="optional"/>
@@ -1901,7 +1925,7 @@ Jan-10-2006
<xsd:attribute name="minbytes" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="charclass" type="xsd:string" use="optional"/>
<xsd:attribute default="no" name="merged-trans" type="xlf:AttrType_YesNo" use="optional"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
<xsd:anyAttribute namespace="##any" processContents="skip"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="trans-unit">
@@ -1913,10 +1937,11 @@ Jan-10-2006
<xsd:choice maxOccurs="unbounded" minOccurs="0">
<xsd:element ref="xlf:context-group"/>
<xsd:element ref="xlf:count-group"/>
<xsd:element ref="xlf:prop-group"/>
<xsd:element ref="xlf:note"/>
<xsd:element ref="xlf:alt-trans"/>
</xsd:choice>
<xsd:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="strict"/>
<xsd:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="skip"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string" use="required"/>
<xsd:attribute name="approved" type="xlf:AttrType_YesNo" use="optional"/>
@@ -1924,6 +1949,7 @@ Jan-10-2006
<xsd:attribute default="yes" name="reformat" type="xlf:AttrType_reformat" use="optional"/>
<xsd:attribute default="default" ref="xml:space" use="optional"/>
<xsd:attribute name="datatype" type="xlf:AttrType_datatype" use="optional"/>
<xsd:attribute name="ts" type="xsd:string" use="optional"/>
<xsd:attribute name="phase-name" type="xsd:string" use="optional"/>
<xsd:attribute name="restype" type="xlf:AttrType_restype" use="optional"/>
<xsd:attribute name="resname" type="xsd:string" use="optional"/>
@@ -1947,7 +1973,7 @@ Jan-10-2006
<xsd:attribute name="minbytes" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="charclass" type="xsd:string" use="optional"/>
<xsd:attribute default="yes" name="merged-trans" type="xlf:AttrType_YesNo" use="optional"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
<xsd:anyAttribute namespace="##any" processContents="skip"/>
</xsd:complexType>
<xsd:unique name="U_tu_segsrc_mid">
<xsd:selector xpath="./xlf:seg-source/xlf:mrk"/>
@@ -1962,7 +1988,8 @@ Jan-10-2006
<xsd:complexType mixed="true">
<xsd:group maxOccurs="unbounded" minOccurs="0" ref="xlf:ElemGroup_TextContent"/>
<xsd:attribute ref="xml:lang" use="optional"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
<xsd:attribute name="ts" type="xsd:string" use="optional"/>
<xsd:anyAttribute namespace="##any" processContents="skip"/>
</xsd:complexType>
<xsd:unique name="U_source_bpt_rid">
<xsd:selector xpath=".//xlf:bpt"/>
@@ -1985,7 +2012,8 @@ Jan-10-2006
<xsd:complexType mixed="true">
<xsd:group maxOccurs="unbounded" minOccurs="0" ref="xlf:ElemGroup_TextContent"/>
<xsd:attribute ref="xml:lang" use="optional"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
<xsd:attribute name="ts" type="xsd:string" use="optional"/>
<xsd:anyAttribute namespace="##any" processContents="skip"/>
</xsd:complexType>
<xsd:unique name="U_segsrc_bpt_rid">
<xsd:selector xpath=".//xlf:bpt"/>
@@ -2011,6 +2039,8 @@ Jan-10-2006
<xsd:attribute name="state-qualifier" type="xlf:AttrType_state-qualifier" use="optional"/>
<xsd:attribute name="phase-name" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute ref="xml:lang" use="optional"/>
<xsd:attribute name="ts" type="xsd:string" use="optional"/>
<xsd:attribute name="restype" type="xlf:AttrType_restype" use="optional"/>
<xsd:attribute name="resname" type="xsd:string" use="optional"/>
<xsd:attribute name="coord" type="xlf:AttrType_Coordinates" use="optional"/>
<xsd:attribute name="font" type="xsd:string" use="optional"/>
@@ -2018,7 +2048,7 @@ Jan-10-2006
<xsd:attribute name="style" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="exstyle" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute default="yes" name="equiv-trans" type="xlf:AttrType_YesNo" use="optional"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
<xsd:anyAttribute namespace="##any" processContents="skip"/>
</xsd:complexType>
<xsd:unique name="U_target_bpt_rid">
<xsd:selector xpath=".//xlf:bpt"/>
@@ -2042,18 +2072,21 @@ Jan-10-2006
<xsd:sequence>
<xsd:element minOccurs="0" ref="xlf:source"/>
<xsd:element minOccurs="0" ref="xlf:seg-source"/>
<xsd:element maxOccurs="1" ref="xlf:target"/>
<xsd:element maxOccurs="unbounded" ref="xlf:target"/>
<xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:context-group"/>
<xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:prop-group"/>
<xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:note"/>
<xsd:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="strict"/>
<xsd:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="skip"/>
</xsd:sequence>
<xsd:attribute name="match-quality" type="xsd:string" use="optional"/>
<xsd:attribute name="tool-id" type="xsd:string" use="optional"/>
<xsd:attribute name="tool" type="xsd:string" use="optional"/>
<xsd:attribute name="crc" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute ref="xml:lang" use="optional"/>
<xsd:attribute name="origin" type="xsd:string" use="optional"/>
<xsd:attribute name="datatype" type="xlf:AttrType_datatype" use="optional"/>
<xsd:attribute default="default" ref="xml:space" use="optional"/>
<xsd:attribute name="ts" type="xsd:string" use="optional"/>
<xsd:attribute name="restype" type="xlf:AttrType_restype" use="optional"/>
<xsd:attribute name="resname" type="xsd:string" use="optional"/>
<xsd:attribute name="extradata" type="xsd:string" use="optional"/>
@@ -2070,7 +2103,7 @@ Jan-10-2006
<xsd:attribute name="exstyle" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="phase-name" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute default="proposal" name="alttranstype" type="xlf:AttrType_alttranstype" use="optional"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
<xsd:anyAttribute namespace="##any" processContents="skip"/>
</xsd:complexType>
<xsd:unique name="U_at_segsrc_mid">
<xsd:selector xpath="./xlf:seg-source/xlf:mrk"/>
@@ -2089,20 +2122,22 @@ Jan-10-2006
<xsd:choice maxOccurs="unbounded" minOccurs="0">
<xsd:element ref="xlf:context-group"/>
<xsd:element ref="xlf:count-group"/>
<xsd:element ref="xlf:prop-group"/>
<xsd:element ref="xlf:note"/>
<xsd:element ref="xlf:trans-unit"/>
</xsd:choice>
<xsd:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="strict"/>
<xsd:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="skip"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string" use="required"/>
<xsd:attribute name="mime-type" type="xlf:mime-typeValueList" use="required"/>
<xsd:attribute name="approved" type="xlf:AttrType_YesNo" use="optional"/>
<xsd:attribute default="yes" name="translate" type="xlf:AttrType_YesNo" use="optional"/>
<xsd:attribute default="yes" name="reformat" type="xlf:AttrType_reformat" use="optional"/>
<xsd:attribute name="ts" type="xsd:string" use="optional"/>
<xsd:attribute name="restype" type="xlf:AttrType_restype" use="optional"/>
<xsd:attribute name="resname" type="xsd:string" use="optional"/>
<xsd:attribute name="phase-name" type="xsd:string" use="optional"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
<xsd:anyAttribute namespace="##any" processContents="skip"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="bin-source">
@@ -2111,7 +2146,8 @@ Jan-10-2006
<xsd:element ref="xlf:internal-file"/>
<xsd:element ref="xlf:external-file"/>
</xsd:choice>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
<xsd:attribute name="ts" type="xsd:string" use="optional"/>
<xsd:anyAttribute namespace="##any" processContents="skip"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="bin-target">
@@ -2121,12 +2157,13 @@ Jan-10-2006
<xsd:element ref="xlf:external-file"/>
</xsd:choice>
<xsd:attribute name="mime-type" type="xlf:mime-typeValueList" use="optional"/>
<xsd:attribute name="ts" type="xsd:string" use="optional"/>
<xsd:attribute name="state" type="xlf:AttrType_state" use="optional"/>
<xsd:attribute name="state-qualifier" type="xlf:AttrType_state-qualifier" use="optional"/>
<xsd:attribute name="phase-name" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="restype" type="xlf:AttrType_restype" use="optional"/>
<xsd:attribute name="resname" type="xsd:string" use="optional"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
<xsd:anyAttribute namespace="##any" processContents="skip"/>
</xsd:complexType>
</xsd:element>
<!-- Element for inline codes -->
@@ -2217,7 +2254,8 @@ Jan-10-2006
<xsd:attribute name="mtype" type="xlf:AttrType_mtype" use="required"/>
<xsd:attribute name="mid" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="comment" type="xsd:string" use="optional"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
<xsd:attribute name="ts" type="xsd:string" use="optional"/>
<xsd:anyAttribute namespace="##any" processContents="skip"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>

View File

@@ -11,6 +11,7 @@
namespace Symfony\Component\Translation\Test;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpClient\MockHttpClient;
@@ -20,6 +21,7 @@ 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\Component\Translation\TranslatorBagInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
/**
@@ -31,11 +33,12 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
*/
abstract class ProviderFactoryTestCase extends TestCase
{
protected $client;
protected $logger;
protected $defaultLocale;
protected $loader;
protected $xliffFileDumper;
protected HttpClientInterface $client;
protected LoggerInterface|MockObject $logger;
protected string $defaultLocale;
protected LoaderInterface|MockObject $loader;
protected XliffFileDumper|MockObject $xliffFileDumper;
protected TranslatorBagInterface|MockObject $translatorBag;
abstract public function createFactory(): ProviderFactoryInterface;
@@ -122,26 +125,31 @@ abstract class ProviderFactoryTestCase extends TestCase
protected function getClient(): HttpClientInterface
{
return $this->client ?? $this->client = new MockHttpClient();
return $this->client ??= new MockHttpClient();
}
protected function getLogger(): LoggerInterface
{
return $this->logger ?? $this->logger = $this->createMock(LoggerInterface::class);
return $this->logger ??= $this->createMock(LoggerInterface::class);
}
protected function getDefaultLocale(): string
{
return $this->defaultLocale ?? $this->defaultLocale = 'en';
return $this->defaultLocale ??= 'en';
}
protected function getLoader(): LoaderInterface
{
return $this->loader ?? $this->loader = $this->createMock(LoaderInterface::class);
return $this->loader ??= $this->createMock(LoaderInterface::class);
}
protected function getXliffFileDumper(): XliffFileDumper
{
return $this->xliffFileDumper ?? $this->xliffFileDumper = $this->createMock(XliffFileDumper::class);
return $this->xliffFileDumper ??= $this->createMock(XliffFileDumper::class);
}
protected function getTranslatorBag(): TranslatorBagInterface
{
return $this->translatorBag ??= $this->createMock(TranslatorBagInterface::class);
}
}

View File

@@ -11,12 +11,14 @@
namespace Symfony\Component\Translation\Test;
use PHPUnit\Framework\MockObject\MockObject;
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\Component\Translation\TranslatorBagInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
/**
@@ -28,11 +30,12 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
*/
abstract class ProviderTestCase extends TestCase
{
protected $client;
protected $logger;
protected $defaultLocale;
protected $loader;
protected $xliffFileDumper;
protected HttpClientInterface $client;
protected LoggerInterface|MockObject $logger;
protected string $defaultLocale;
protected LoaderInterface|MockObject $loader;
protected XliffFileDumper|MockObject $xliffFileDumper;
protected TranslatorBagInterface|MockObject $translatorBag;
abstract public function createProvider(HttpClientInterface $client, LoaderInterface $loader, LoggerInterface $logger, string $defaultLocale, string $endpoint): ProviderInterface;
@@ -51,26 +54,31 @@ abstract class ProviderTestCase extends TestCase
protected function getClient(): MockHttpClient
{
return $this->client ?? $this->client = new MockHttpClient();
return $this->client ??= new MockHttpClient();
}
protected function getLoader(): LoaderInterface
{
return $this->loader ?? $this->loader = $this->createMock(LoaderInterface::class);
return $this->loader ??= $this->createMock(LoaderInterface::class);
}
protected function getLogger(): LoggerInterface
{
return $this->logger ?? $this->logger = $this->createMock(LoggerInterface::class);
return $this->logger ??= $this->createMock(LoggerInterface::class);
}
protected function getDefaultLocale(): string
{
return $this->defaultLocale ?? $this->defaultLocale = 'en';
return $this->defaultLocale ??= 'en';
}
protected function getXliffFileDumper(): XliffFileDumper
{
return $this->xliffFileDumper ?? $this->xliffFileDumper = $this->createMock(XliffFileDumper::class);
return $this->xliffFileDumper ??= $this->createMock(XliffFileDumper::class);
}
protected function getTranslatorBag(): TranslatorBagInterface
{
return $this->translatorBag ??= $this->createMock(TranslatorBagInterface::class);
}
}

View File

@@ -19,9 +19,9 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*/
class TranslatableMessage implements TranslatableInterface
{
private $message;
private $parameters;
private $domain;
private string $message;
private array $parameters;
private ?string $domain;
public function __construct(string $message, array $parameters = [], string $domain = null)
{

View File

@@ -22,6 +22,7 @@ use Symfony\Component\Translation\Formatter\MessageFormatter;
use Symfony\Component\Translation\Formatter\MessageFormatterInterface;
use Symfony\Component\Translation\Loader\LoaderInterface;
use Symfony\Contracts\Translation\LocaleAwareInterface;
use Symfony\Contracts\Translation\TranslatableInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
// Help opcache.preload discover always-needed symbols
@@ -37,54 +38,33 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
*/
protected $catalogues = [];
/**
* @var string
*/
private $locale;
private string $locale;
/**
* @var string[]
*/
private $fallbackLocales = [];
private array $fallbackLocales = [];
/**
* @var LoaderInterface[]
*/
private $loaders = [];
private array $loaders = [];
/**
* @var array
*/
private $resources = [];
private array $resources = [];
/**
* @var MessageFormatterInterface
*/
private $formatter;
private MessageFormatterInterface $formatter;
/**
* @var string
*/
private $cacheDir;
private ?string $cacheDir;
/**
* @var bool
*/
private $debug;
private bool $debug;
private $cacheVary;
private array $cacheVary;
/**
* @var ConfigCacheFactoryInterface|null
*/
private $configCacheFactory;
private ?ConfigCacheFactoryInterface $configCacheFactory;
/**
* @var array|null
*/
private $parentLocales;
private array $parentLocales;
private $hasIntlFormatter;
private bool $hasIntlFormatter;
/**
* @throws InvalidArgumentException If a locale contains invalid characters
@@ -93,11 +73,7 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
{
$this->setLocale($locale);
if (null === $formatter) {
$formatter = new MessageFormatter();
}
$this->formatter = $formatter;
$this->formatter = $formatter ??= new MessageFormatter();
$this->cacheDir = $cacheDir;
$this->debug = $debug;
$this->cacheVary = $cacheVary;
@@ -127,11 +103,9 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
*
* @throws InvalidArgumentException If the locale contains invalid characters
*/
public function addResource(string $format, $resource, string $locale, string $domain = null)
public function addResource(string $format, mixed $resource, string $locale, string $domain = null)
{
if (null === $domain) {
$domain = 'messages';
}
$domain ??= 'messages';
$this->assertValidLocale($locale);
$locale ?: $locale = class_exists(\Locale::class) ? \Locale::getDefault() : 'en';
@@ -145,19 +119,13 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
}
}
/**
* {@inheritdoc}
*/
public function setLocale(string $locale)
{
$this->assertValidLocale($locale);
$this->locale = $locale;
}
/**
* {@inheritdoc}
*/
public function getLocale()
public function getLocale(): string
{
return $this->locale ?: (class_exists(\Locale::class) ? \Locale::getDefault() : 'en');
}
@@ -191,18 +159,13 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
return $this->fallbackLocales;
}
/**
* {@inheritdoc}
*/
public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null)
public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null): string
{
if (null === $id || '' === $id) {
return '';
}
if (null === $domain) {
$domain = 'messages';
}
$domain ??= 'messages';
$catalogue = $this->getCatalogue($locale);
$locale = $catalogue->getLocale();
@@ -215,6 +178,10 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
}
}
$parameters = array_map(function ($parameter) use ($locale) {
return $parameter instanceof TranslatableInterface ? $parameter->trans($this, $locale) : $parameter;
}, $parameters);
$len = \strlen(MessageCatalogue::INTL_DOMAIN_SUFFIX);
if ($this->hasIntlFormatter
&& ($catalogue->defines($id, $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX)
@@ -226,10 +193,7 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
return $this->formatter->format($catalogue->get($id, $domain), $locale, $parameters);
}
/**
* {@inheritdoc}
*/
public function getCatalogue(string $locale = null)
public function getCatalogue(string $locale = null): MessageCatalogueInterface
{
if (!$locale) {
$locale = $this->getLocale();
@@ -244,9 +208,6 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
return $this->catalogues[$locale];
}
/**
* {@inheritdoc}
*/
public function getCatalogues(): array
{
return array_values($this->catalogues);
@@ -257,7 +218,7 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
*
* @return LoaderInterface[]
*/
protected function getLoaders()
protected function getLoaders(): array
{
return $this->loaders;
}
@@ -409,9 +370,7 @@ EOF
protected function computeFallbackLocales(string $locale)
{
if (null === $this->parentLocales) {
$this->parentLocales = json_decode(file_get_contents(__DIR__.'/Resources/data/parents.json'), true);
}
$this->parentLocales ??= json_decode(file_get_contents(__DIR__.'/Resources/data/parents.json'), true);
$originLocale = $locale;
$locales = [];
@@ -468,9 +427,7 @@ EOF
*/
private function getConfigCacheFactory(): ConfigCacheFactoryInterface
{
if (!$this->configCacheFactory) {
$this->configCacheFactory = new ConfigCacheFactory($this->debug);
}
$this->configCacheFactory ??= new ConfigCacheFactory($this->debug);
return $this->configCacheFactory;
}

View File

@@ -17,7 +17,7 @@ use Symfony\Component\Translation\Catalogue\TargetOperation;
final class TranslatorBag implements TranslatorBagInterface
{
/** @var MessageCatalogue[] */
private $catalogues = [];
private array $catalogues = [];
public function addCatalogue(MessageCatalogue $catalogue): void
{
@@ -35,9 +35,6 @@ final class TranslatorBag implements TranslatorBagInterface
}
}
/**
* {@inheritdoc}
*/
public function getCatalogue(string $locale = null): MessageCatalogueInterface
{
if (null === $locale || !isset($this->catalogues[$locale])) {
@@ -47,9 +44,6 @@ final class TranslatorBag implements TranslatorBagInterface
return $this->catalogues[$locale];
}
/**
* {@inheritdoc}
*/
public function getCatalogues(): array
{
return array_values($this->catalogues);

View File

@@ -14,10 +14,6 @@ namespace Symfony\Component\Translation;
use Symfony\Component\Translation\Exception\InvalidArgumentException;
/**
* TranslatorBagInterface.
*
* @method MessageCatalogueInterface[] getCatalogues() Returns all catalogues of the instance
*
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*/
interface TranslatorBagInterface
@@ -27,9 +23,14 @@ interface TranslatorBagInterface
*
* @param string|null $locale The locale or null to use the default
*
* @return MessageCatalogueInterface
*
* @throws InvalidArgumentException If the locale contains invalid characters
*/
public function getCatalogue(string $locale = null);
public function getCatalogue(string $locale = null): MessageCatalogueInterface;
/**
* Returns all catalogues of the instance.
*
* @return MessageCatalogueInterface[]
*/
public function getCatalogues(): array;
}

View File

@@ -30,10 +30,8 @@ class ArrayConverter
* For example this array('foo.bar' => 'value') will be converted to ['foo' => ['bar' => 'value']].
*
* @param array $messages Linear messages array
*
* @return array
*/
public static function expandToTree(array $messages)
public static function expandToTree(array $messages): array
{
$tree = [];

View File

@@ -85,11 +85,6 @@ class XliffUtils
private static function shouldEnableEntityLoader(): bool
{
// Version prior to 8.0 can be enabled without deprecation
if (\PHP_VERSION_ID < 80000) {
return true;
}
static $dom, $schema;
if (null === $dom) {
$dom = new \DOMDocument();
@@ -134,7 +129,7 @@ class XliffUtils
private static function getSchema(string $xliffVersion): string
{
if ('1.2' === $xliffVersion) {
$schemaSource = file_get_contents(__DIR__.'/../Resources/schemas/xliff-core-1.2-strict.xsd');
$schemaSource = file_get_contents(__DIR__.'/../Resources/schemas/xliff-core-1.2-transitional.xsd');
$xmlUri = 'http://www.w3.org/2001/xml.xsd';
} elseif ('2.0' === $xliffVersion) {
$schemaSource = file_get_contents(__DIR__.'/../Resources/schemas/xliff-core-2.0.xsd');

View File

@@ -26,7 +26,7 @@ class TranslationWriter implements TranslationWriterInterface
/**
* @var array<string, DumperInterface>
*/
private $dumpers = [];
private array $dumpers = [];
/**
* Adds a dumper to the writer.
@@ -38,10 +38,8 @@ class TranslationWriter implements TranslationWriterInterface
/**
* Obtains the list of supported formats.
*
* @return array
*/
public function getFormats()
public function getFormats(): array
{
return array_keys($this->dumpers);
}

View File

@@ -16,37 +16,38 @@
}
],
"require": {
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1|^3",
"php": ">=8.1",
"symfony/polyfill-mbstring": "~1.0",
"symfony/polyfill-php80": "^1.16",
"symfony/translation-contracts": "^2.3"
"symfony/translation-contracts": "^2.3|^3.0"
},
"require-dev": {
"symfony/config": "^4.4|^5.0|^6.0",
"nikic/php-parser": "^4.13",
"symfony/config": "^5.4|^6.0",
"symfony/console": "^5.4|^6.0",
"symfony/dependency-injection": "^5.0|^6.0",
"symfony/dependency-injection": "^5.4|^6.0",
"symfony/http-client-contracts": "^1.1|^2.0|^3.0",
"symfony/http-kernel": "^5.0|^6.0",
"symfony/intl": "^4.4|^5.0|^6.0",
"symfony/http-kernel": "^5.4|^6.0",
"symfony/intl": "^5.4|^6.0",
"symfony/polyfill-intl-icu": "^1.21",
"symfony/routing": "^5.4|^6.0",
"symfony/service-contracts": "^1.1.2|^2|^3",
"symfony/yaml": "^4.4|^5.0|^6.0",
"symfony/finder": "^4.4|^5.0|^6.0",
"symfony/yaml": "^5.4|^6.0",
"symfony/finder": "^5.4|^6.0",
"psr/log": "^1|^2|^3"
},
"conflict": {
"symfony/config": "<4.4",
"symfony/dependency-injection": "<5.0",
"symfony/http-kernel": "<5.0",
"symfony/twig-bundle": "<5.0",
"symfony/yaml": "<4.4",
"symfony/console": "<5.3"
"symfony/config": "<5.4",
"symfony/dependency-injection": "<5.4",
"symfony/http-kernel": "<5.4",
"symfony/twig-bundle": "<5.4",
"symfony/yaml": "<5.4",
"symfony/console": "<5.4"
},
"provide": {
"symfony/translation-implementation": "2.3"
"symfony/translation-implementation": "2.3|3.0"
},
"suggest": {
"nikic/php-parser": "To use PhpAstExtractor",
"symfony/config": "",
"symfony/yaml": "",
"psr/log-implementation": "To use logging capability in translator"