This commit is contained in:
Paolo A
2024-08-13 13:44:16 +00:00
parent 1bbb23088d
commit e796d76612
4001 changed files with 30101 additions and 40075 deletions

View File

@@ -1,6 +1,35 @@
CHANGELOG
=========
6.4
---
* Give current locale to `LocaleSwitcher::runWithLocale()`'s callback
* Add `--as-tree` option to `translation:pull` command to write YAML messages as a tree-like structure
* [BC BREAK] Add argument `$buildDir` to `DataCollectorTranslator::warmUp()`
* Add `DataCollectorTranslatorPass` and `LoggingTranslatorPass` (moved from `FrameworkBundle`)
* Add `PhraseTranslationProvider`
6.2.7
-----
* [BC BREAK] The following data providers for `ProviderFactoryTestCase` are now static:
`supportsProvider()`, `createProvider()`, `unsupportedSchemeProvider()`and `incompleteDsnProvider()`
* [BC BREAK] `ProviderTestCase::toStringProvider()` is now static
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

@@ -34,11 +34,6 @@ abstract class AbstractOperation implements OperationInterface
protected $target;
protected $result;
/**
* @var array|null The domains affected by this operation
*/
private $domains;
/**
* This array stores 'all', 'new' and 'obsolete' messages for all valid domains.
*
@@ -62,6 +57,8 @@ abstract class AbstractOperation implements OperationInterface
*/
protected $messages;
private array $domains;
/**
* @throws LogicException
*/
@@ -77,12 +74,9 @@ abstract class AbstractOperation implements OperationInterface
$this->messages = [];
}
/**
* {@inheritdoc}
*/
public function getDomains(): array
{
if (null === $this->domains) {
if (!isset($this->domains)) {
$domains = [];
foreach ([$this->source, $this->target] as $catalogue) {
foreach ($catalogue->getDomains() as $domain) {
@@ -100,9 +94,6 @@ abstract class AbstractOperation implements OperationInterface
return $this->domains;
}
/**
* {@inheritdoc}
*/
public function getMessages(string $domain): array
{
if (!\in_array($domain, $this->getDomains())) {
@@ -116,9 +107,6 @@ abstract class AbstractOperation implements OperationInterface
return $this->messages[$domain][self::ALL_BATCH];
}
/**
* {@inheritdoc}
*/
public function getNewMessages(string $domain): array
{
if (!\in_array($domain, $this->getDomains())) {
@@ -132,9 +120,6 @@ abstract class AbstractOperation implements OperationInterface
return $this->messages[$domain][self::NEW_BATCH];
}
/**
* {@inheritdoc}
*/
public function getObsoleteMessages(string $domain): array
{
if (!\in_array($domain, $this->getDomains())) {
@@ -148,9 +133,6 @@ abstract class AbstractOperation implements OperationInterface
return $this->messages[$domain][self::OBSOLETE_BATCH];
}
/**
* {@inheritdoc}
*/
public function getResult(): MessageCatalogueInterface
{
foreach ($this->getDomains() as $domain) {
@@ -174,12 +156,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;
@@ -198,6 +180,8 @@ abstract class AbstractOperation implements OperationInterface
* stores the results.
*
* @param string $domain The domain which the operation will be performed for
*
* @return void
*/
abstract protected function processDomain(string $domain);
}

View File

@@ -25,7 +25,7 @@ use Symfony\Component\Translation\MessageCatalogueInterface;
class MergeOperation extends AbstractOperation
{
/**
* {@inheritdoc}
* @return void
*/
protected function processDomain(string $domain)
{
@@ -36,6 +36,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

@@ -26,7 +26,7 @@ use Symfony\Component\Translation\MessageCatalogueInterface;
class TargetOperation extends AbstractOperation
{
/**
* {@inheritdoc}
* @return void
*/
protected function processDomain(string $domain)
{
@@ -37,6 +37,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

@@ -34,9 +34,9 @@ final class TranslationPullCommand extends Command
{
use TranslationTrait;
private $providerCollection;
private $writer;
private $reader;
private TranslationProviderCollection $providerCollection;
private TranslationWriterInterface $writer;
private TranslationReaderInterface $reader;
private string $defaultLocale;
private array $transPaths;
private array $enabledLocales;
@@ -64,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;
@@ -83,10 +82,7 @@ final class TranslationPullCommand extends Command
}
}
/**
* {@inheritdoc}
*/
protected function configure()
protected function configure(): void
{
$keys = $this->providerCollection->keys();
$defaultProvider = 1 === \count($keys) ? $keys[0] : null;
@@ -99,6 +95,7 @@ final class TranslationPullCommand extends Command
new InputOption('domains', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the domains to pull.'),
new InputOption('locales', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the locales to pull.'),
new InputOption('format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format.', 'xlf12'),
new InputOption('as-tree', null, InputOption::VALUE_OPTIONAL, 'Write messages as a tree-like structure. Needs --format=yaml. The given value defines the level where to switch to inline YAML'),
])
->setHelp(<<<'EOF'
The <info>%command.name%</> command pulls translations from the given provider. Only
@@ -120,9 +117,6 @@ EOF
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
@@ -133,6 +127,7 @@ EOF
$locales = $input->getOption('locales') ?: $this->enabledLocales;
$domains = $input->getOption('domains');
$format = $input->getOption('format');
$asTree = (int) $input->getOption('as-tree');
$xliffVersion = '1.2';
if ($intlIcu && !$force) {
@@ -149,6 +144,8 @@ EOF
'path' => end($this->transPaths),
'xliff_version' => $xliffVersion,
'default_locale' => $this->defaultLocale,
'as_tree' => (bool) $asTree,
'inline' => $asTree,
];
if (!$domains) {

View File

@@ -34,8 +34,8 @@ final class TranslationPushCommand extends Command
{
use TranslationTrait;
private $providers;
private $reader;
private TranslationProviderCollection $providers;
private TranslationReaderInterface $reader;
private array $transPaths;
private array $enabledLocales;
@@ -73,10 +73,7 @@ final class TranslationPushCommand extends Command
}
}
/**
* {@inheritdoc}
*/
protected function configure()
protected function configure(): void
{
$keys = $this->providers->keys();
$defaultProvider = 1 === \count($keys) ? $keys[0] : null;
@@ -113,15 +110,12 @@ EOF
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$provider = $this->providers->get($input->getArgument('provider'));
if (!$this->enabledLocales) {
throw new InvalidArgumentException(sprintf('You must define "framework.translator.enabled_locales" or "framework.translator.providers.%s.locales" config key in order to work with translation providers.', parse_url($provider, \PHP_URL_SCHEME)));
throw new InvalidArgumentException(sprintf('You must define "framework.enabled_locales" or "framework.translator.providers.%s.locales" config key in order to work with translation providers.', parse_url($provider, \PHP_URL_SCHEME)));
}
$io = new SymfonyStyle($input, $output);

View File

@@ -41,23 +41,23 @@ class XliffLintCommand extends Command
private ?\Closure $isReadableProvider;
private bool $requireStrictFileNames;
public function __construct(string $name = null, callable $directoryIteratorProvider = null, callable $isReadableProvider = null, bool $requireStrictFileNames = true)
public function __construct(?string $name = null, ?callable $directoryIteratorProvider = null, ?callable $isReadableProvider = null, bool $requireStrictFileNames = true)
{
parent::__construct($name);
$this->directoryIteratorProvider = null === $directoryIteratorProvider || $directoryIteratorProvider instanceof \Closure ? $directoryIteratorProvider : \Closure::fromCallable($directoryIteratorProvider);
$this->isReadableProvider = null === $isReadableProvider || $isReadableProvider instanceof \Closure ? $isReadableProvider : \Closure::fromCallable($isReadableProvider);
$this->directoryIteratorProvider = null === $directoryIteratorProvider ? null : $directoryIteratorProvider(...);
$this->isReadableProvider = null === $isReadableProvider ? null : $isReadableProvider(...);
$this->requireStrictFileNames = $requireStrictFileNames;
}
/**
* {@inheritdoc}
* @return void
*/
protected function configure()
{
$this
->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN')
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format')
->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())))
->setHelp(<<<EOF
The <info>%command.name%</info> command lints an XLIFF file and outputs to STDOUT
the first encountered syntax error.
@@ -109,7 +109,7 @@ EOF
return $this->display($io, $filesInfo);
}
private function validate(string $content, string $file = null): array
private function validate(string $content, ?string $file = null): array
{
$errors = [];
@@ -154,21 +154,17 @@ EOF
return ['file' => $file, 'valid' => 0 === \count($errors), 'messages' => $errors];
}
private function display(SymfonyStyle $io, array $files)
private function display(SymfonyStyle $io, array $files): int
{
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('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))),
};
}
private function displayTxt(SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false)
private function displayTxt(SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false): int
{
$countFiles = \count($filesInfo);
$erroredFiles = 0;
@@ -184,9 +180,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']));
@@ -202,7 +196,7 @@ EOF
return min($erroredFiles, 1);
}
private function displayJson(SymfonyStyle $io, array $filesInfo)
private function displayJson(SymfonyStyle $io, array $filesInfo): int
{
$errors = 0;
@@ -218,7 +212,10 @@ EOF
return min($errors, 1);
}
private function getFiles(string $fileOrDirectory)
/**
* @return iterable<\SplFileInfo>
*/
private function getFiles(string $fileOrDirectory): iterable
{
if (is_file($fileOrDirectory)) {
yield new \SplFileInfo($fileOrDirectory);
@@ -235,14 +232,15 @@ EOF
}
}
private function getDirectoryIterator(string $directory)
/**
* @return iterable<\SplFileInfo>
*/
private function getDirectoryIterator(string $directory): iterable
{
$default = function ($directory) {
return new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
\RecursiveIteratorIterator::LEAVES_ONLY
);
};
$default = fn ($directory) => new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
\RecursiveIteratorIterator::LEAVES_ONLY
);
if (null !== $this->directoryIteratorProvider) {
return ($this->directoryIteratorProvider)($directory, $default);
@@ -251,11 +249,9 @@ EOF
return $default($directory);
}
private function isReadable(string $fileOrDirectory)
private function isReadable(string $fileOrDirectory): bool
{
$default = function ($fileOrDirectory) {
return is_readable($fileOrDirectory);
};
$default = fn ($fileOrDirectory) => is_readable($fileOrDirectory);
if (null !== $this->isReadableProvider) {
return ($this->isReadableProvider)($fileOrDirectory, $default);
@@ -278,7 +274,12 @@ EOF
public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
{
if ($input->mustSuggestOptionValuesFor('format')) {
$suggestions->suggestValues(['txt', 'json', 'github']);
$suggestions->suggestValues($this->getAvailableFormatOptions());
}
}
private function getAvailableFormatOptions(): array
{
return ['txt', 'json', 'github'];
}
}

View File

@@ -25,17 +25,14 @@ 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()
public function lateCollect(): void
{
$messages = $this->sanitizeCollectedMessages($this->translator->getCollectedMessages());
@@ -45,19 +42,13 @@ class TranslationDataCollector extends DataCollector implements LateDataCollecto
$this->data = $this->cloneVar($this->data);
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Throwable $exception = null)
public function collect(Request $request, Response $response, ?\Throwable $exception = null): void
{
$this->data['locale'] = $this->translator->getLocale();
$this->data['fallback_locales'] = $this->translator->getFallbackLocales();
}
/**
* {@inheritdoc}
*/
public function reset()
public function reset(): void
{
$this->data = [];
}
@@ -82,7 +73,7 @@ class TranslationDataCollector extends DataCollector implements LateDataCollecto
return $this->data[DataCollectorTranslator::MESSAGE_DEFINED] ?? 0;
}
public function getLocale()
public function getLocale(): ?string
{
return !empty($this->data['locale']) ? $this->data['locale'] : null;
}
@@ -90,20 +81,17 @@ class TranslationDataCollector extends DataCollector implements LateDataCollecto
/**
* @internal
*/
public function getFallbackLocales()
public function getFallbackLocales(): Data|array
{
return (isset($this->data['fallback_locales']) && \count($this->data['fallback_locales']) > 0) ? $this->data['fallback_locales'] : [];
}
/**
* {@inheritdoc}
*/
public function getName(): string
{
return 'translation';
}
private function sanitizeCollectedMessages(array $messages)
private function sanitizeCollectedMessages(array $messages): array
{
$result = [];
foreach ($messages as $key => $message) {
@@ -128,7 +116,7 @@ class TranslationDataCollector extends DataCollector implements LateDataCollecto
return $result;
}
private function computeCount(array $messages)
private function computeCount(array $messages): array
{
$count = [
DataCollectorTranslator::MESSAGE_DEFINED => 0,
@@ -143,7 +131,7 @@ class TranslationDataCollector extends DataCollector implements LateDataCollecto
return $count;
}
private function sanitizeString(string $string, int $length = 80)
private function sanitizeString(string $string, int $length = 80): string
{
$string = trim(preg_replace('/\s+/', ' ', $string));

View File

@@ -25,7 +25,7 @@ class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInter
public const MESSAGE_MISSING = 1;
public const MESSAGE_EQUALS_FALLBACK = 2;
private $translator;
private TranslatorInterface $translator;
private array $messages = [];
/**
@@ -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): string
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);
@@ -52,46 +49,32 @@ class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInter
}
/**
* {@inheritdoc}
* @return void
*/
public function setLocale(string $locale)
{
$this->translator->setLocale($locale);
}
/**
* {@inheritdoc}
*/
public function getLocale(): string
{
return $this->translator->getLocale();
}
/**
* {@inheritdoc}
*/
public function getCatalogue(string $locale = null): MessageCatalogueInterface
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): array
public function warmUp(string $cacheDir, ?string $buildDir = null): array
{
if ($this->translator instanceof WarmableInterface) {
return (array) $this->translator->warmUp($cacheDir);
return (array) $this->translator->warmUp($cacheDir, $buildDir);
}
return [];
@@ -110,7 +93,7 @@ class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInter
}
/**
* Passes through all unknown calls onto the translator object.
* @return mixed
*/
public function __call(string $method, array $args)
{
@@ -122,11 +105,9 @@ class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInter
return $this->messages;
}
private function collectMessage(?string $locale, ?string $domain, string $id, string $translation, ?array $parameters = [])
private function collectMessage(?string $locale, ?string $domain, string $id, string $translation, ?array $parameters = []): void
{
if (null === $domain) {
$domain = 'messages';
}
$domain ??= 'messages';
$catalogue = $this->translator->getCatalogue($locale);
$locale = $catalogue->getLocale();

View File

@@ -20,6 +20,9 @@ use Symfony\Component\DependencyInjection\Reference;
*/
class TranslationDumperPass implements CompilerPassInterface
{
/**
* @return void
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('translation.writer')) {

View File

@@ -21,6 +21,9 @@ use Symfony\Component\DependencyInjection\Reference;
*/
class TranslationExtractorPass implements CompilerPassInterface
{
/**
* @return void
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('translation.extractor')) {

View File

@@ -18,6 +18,9 @@ use Symfony\Component\DependencyInjection\Reference;
class TranslatorPass implements CompilerPassInterface
{
/**
* @return void
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('translator.default')) {
@@ -49,6 +52,23 @@ class TranslatorPass implements CompilerPassInterface
->replaceArgument(3, $loaders)
;
if ($container->hasDefinition('validator') && $container->hasDefinition('translation.extractor.visitor.constraint')) {
$constraintVisitorDefinition = $container->getDefinition('translation.extractor.visitor.constraint');
$constraintClassNames = [];
foreach ($container->getDefinitions() as $definition) {
if (!$definition->hasTag('validator.constraint_validator')) {
continue;
}
// Resolve constraint validator FQCN even if defined as %foo.validator.class% parameter
$className = $container->getParameterBag()->resolveValue($definition->getClass());
// Extraction of the constraint class name from the Constraint Validator FQCN
$constraintClassNames[] = str_replace('Validator', '', substr(strrchr($className, '\\'), 1));
}
$constraintVisitorDefinition->setArgument(0, $constraintClassNames);
}
if (!$container->hasParameter('twig.default_path')) {
return;
}

View File

@@ -16,12 +16,15 @@ use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver;
/**
* @author Yonel Ceruto <yonelceruto@gmail.com>
*/
class TranslatorPathsPass extends AbstractRecursivePass
{
protected bool $skipScalars = true;
private int $level = 0;
/**
@@ -39,6 +42,9 @@ class TranslatorPathsPass extends AbstractRecursivePass
*/
private array $controllers = [];
/**
* @return void
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('translator')) {
@@ -120,28 +126,20 @@ class TranslatorPathsPass extends AbstractRecursivePass
private function findControllerArguments(ContainerBuilder $container): array
{
if ($container->hasDefinition('argument_resolver.service')) {
$argument = $container->getDefinition('argument_resolver.service')->getArgument(0);
if ($argument instanceof Reference) {
$argument = $container->getDefinition($argument);
}
if (!$container->has('argument_resolver.service')) {
return [];
}
$resolverDef = $container->findDefinition('argument_resolver.service');
return $argument->getArgument(0);
if (TraceableValueResolver::class === $resolverDef->getClass()) {
$resolverDef = $container->getDefinition($resolverDef->getArgument(0));
}
if ($container->hasDefinition('debug.'.'argument_resolver.service')) {
$argument = $container->getDefinition('debug.'.'argument_resolver.service')->getArgument(0);
if ($argument instanceof Reference) {
$argument = $container->getDefinition($argument);
}
$argument = $argument->getArgument(0);
if ($argument instanceof Reference) {
$argument = $container->getDefinition($argument);
}
return $argument->getArgument(0);
$argument = $resolverDef->getArgument(0);
if ($argument instanceof Reference) {
$argument = $container->getDefinition($argument);
}
return [];
return $argument->getArgument(0);
}
}

View File

@@ -23,9 +23,6 @@ class CsvFileDumper extends FileDumper
private string $delimiter = ';';
private string $enclosure = '"';
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
{
$handle = fopen('php://memory', 'r+');
@@ -43,6 +40,8 @@ class CsvFileDumper extends FileDumper
/**
* Sets the delimiter and escape character for CSV.
*
* @return void
*/
public function setCsvControl(string $delimiter = ';', string $enclosure = '"')
{
@@ -50,9 +49,6 @@ class CsvFileDumper extends FileDumper
$this->enclosure = $enclosure;
}
/**
* {@inheritdoc}
*/
protected function getExtension(): string
{
return 'csv';

View File

@@ -25,6 +25,8 @@ interface DumperInterface
* Dumps the message catalogue.
*
* @param array $options Options that are used by the dumper
*
* @return void
*/
public function dump(MessageCatalogue $messages, array $options = []);
}

View File

@@ -35,7 +35,7 @@ abstract class FileDumper implements DumperInterface
/**
* Sets the template for the relative paths to files.
*
* @param string $relativePathTemplate A template for the relative paths to files
* @return void
*/
public function setRelativePathTemplate(string $relativePathTemplate)
{
@@ -43,7 +43,7 @@ abstract class FileDumper implements DumperInterface
}
/**
* {@inheritdoc}
* @return void
*/
public function dump(MessageCatalogue $messages, array $options = [])
{

View File

@@ -20,14 +20,8 @@ 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 = []): string
{
$data = $indexes = $resources = '';
@@ -89,14 +83,11 @@ class IcuResFileDumper extends FileDumper
return $padding ? str_repeat("\xAA", 4 - $padding) : null;
}
private function getPosition(string $data)
private function getPosition(string $data): float|int
{
return (\strlen($data) + 28) / 4;
}
/**
* {@inheritdoc}
*/
protected function getExtension(): string
{
return 'res';

View File

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

View File

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

View File

@@ -21,9 +21,6 @@ use Symfony\Component\Translation\MessageCatalogue;
*/
class MoFileDumper extends FileDumper
{
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
{
$sources = $targets = $sourceOffsets = $targetOffsets = '';
@@ -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,9 +64,6 @@ class MoFileDumper extends FileDumper
return $output;
}
/**
* {@inheritdoc}
*/
protected function getExtension(): string
{
return 'mo';

View File

@@ -20,17 +20,11 @@ use Symfony\Component\Translation\MessageCatalogue;
*/
class PhpFileDumper extends FileDumper
{
/**
* {@inheritdoc}
*/
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(): string
{
return 'php';

View File

@@ -20,9 +20,6 @@ use Symfony\Component\Translation\MessageCatalogue;
*/
class PoFileDumper extends FileDumper
{
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
{
$output = 'msgid ""'."\n";
@@ -68,7 +65,7 @@ class PoFileDumper extends FileDumper
return $output;
}
private function getStandardRules(string $id)
private function getStandardRules(string $id): array
{
// Partly copied from TranslatorTrait::trans.
$parts = [];
@@ -111,9 +108,6 @@ EOF;
return $standardRules;
}
/**
* {@inheritdoc}
*/
protected function getExtension(): string
{
return 'po';

View File

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

View File

@@ -21,9 +21,11 @@ use Symfony\Component\Translation\MessageCatalogue;
*/
class XliffFileDumper extends FileDumper
{
/**
* {@inheritdoc}
*/
public function __construct(
private string $extension = 'xlf',
) {
}
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
{
$xliffVersion = '1.2';
@@ -47,15 +49,12 @@ class XliffFileDumper extends FileDumper
throw new InvalidArgumentException(sprintf('No support implemented for dumping XLIFF version "%s".', $xliffVersion));
}
/**
* {@inheritdoc}
*/
protected function getExtension(): string
{
return 'xlf';
return $this->extension;
}
private function dumpXliff1(string $defaultLocale, MessageCatalogue $messages, ?string $domain, array $options = [])
private function dumpXliff1(string $defaultLocale, MessageCatalogue $messages, ?string $domain, array $options = []): string
{
$toolInfo = ['tool-id' => 'symfony', 'tool-name' => 'Symfony'];
if (\array_key_exists('tool_info', $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');
@@ -129,7 +137,7 @@ class XliffFileDumper extends FileDumper
return $dom->saveXML();
}
private function dumpXliff2(string $defaultLocale, MessageCatalogue $messages, ?string $domain)
private function dumpXliff2(string $defaultLocale, MessageCatalogue $messages, ?string $domain): string
{
$dom = new \DOMDocument('1.0', 'utf-8');
$dom->formatOutput = true;
@@ -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), '/+', '._'));
@@ -196,7 +214,7 @@ class XliffFileDumper extends FileDumper
return $dom->saveXML();
}
private function hasMetadataArrayInfo(string $key, array $metadata = null): bool
private function hasMetadataArrayInfo(string $key, ?array $metadata = null): bool
{
return is_iterable($metadata[$key] ?? null);
}

View File

@@ -30,9 +30,6 @@ class YamlFileDumper extends FileDumper
$this->extension = $extension;
}
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
{
if (!class_exists(Yaml::class)) {
@@ -52,9 +49,6 @@ class YamlFileDumper extends FileDumper
return Yaml::dump($data);
}
/**
* {@inheritdoc}
*/
protected function getExtension(): string
{
return $this->extension;

View File

@@ -13,7 +13,7 @@ namespace Symfony\Component\Translation\Exception;
class IncompleteDsnException extends InvalidArgumentException
{
public function __construct(string $message, string $dsn = null, \Throwable $previous = null)
public function __construct(string $message, ?string $dsn = null, ?\Throwable $previous = null)
{
if ($dsn) {
$message = sprintf('Invalid "%s" provider DSN: ', $dsn).$message;

View File

@@ -16,7 +16,7 @@ namespace Symfony\Component\Translation\Exception;
*/
class MissingRequiredOptionException extends IncompleteDsnException
{
public function __construct(string $option, string $dsn = null, \Throwable $previous = null)
public function __construct(string $option, ?string $dsn = null, ?\Throwable $previous = null)
{
$message = sprintf('The option "%s" is required but missing.', $option);

View File

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

View File

@@ -29,9 +29,13 @@ class UnsupportedSchemeException extends LogicException
'class' => Bridge\Lokalise\LokaliseProviderFactory::class,
'package' => 'symfony/lokalise-translation-provider',
],
'phrase' => [
'class' => Bridge\Phrase\PhraseProviderFactory::class,
'package' => 'symfony/phrase-translation-provider',
],
];
public function __construct(Dsn $dsn, string $name = null, array $supported = [])
public function __construct(Dsn $dsn, ?string $name = null, array $supported = [])
{
$provider = $dsn->getScheme();
if (false !== $pos = strpos($provider, '+')) {
@@ -39,7 +43,7 @@ class UnsupportedSchemeException extends LogicException
}
$package = self::SCHEME_TO_PACKAGE_MAP[$provider] ?? null;
if ($package && !class_exists($package['class'])) {
parent::__construct(sprintf('Unable to synchronize translations via "%s" as the provider is not installed; try running "composer require %s".', $provider, $package['package']));
parent::__construct(sprintf('Unable to synchronize translations via "%s" as the provider is not installed. Try running "composer require %s".', $provider, $package['package']));
return;
}

View File

@@ -29,6 +29,8 @@ class ChainExtractor implements ExtractorInterface
/**
* Adds a loader to the translation extractor.
*
* @return void
*/
public function addExtractor(string $format, ExtractorInterface $extractor)
{
@@ -36,7 +38,7 @@ class ChainExtractor implements ExtractorInterface
}
/**
* {@inheritdoc}
* @return void
*/
public function setPrefix(string $prefix)
{
@@ -46,7 +48,7 @@ class ChainExtractor implements ExtractorInterface
}
/**
* {@inheritdoc}
* @return void
*/
public function extract(string|iterable $directory, MessageCatalogue $catalogue)
{

View File

@@ -25,11 +25,15 @@ interface ExtractorInterface
* Extracts translation messages from files, a file or a directory to the catalogue.
*
* @param string|iterable<string> $resource Files, a file or a directory
*
* @return void
*/
public function extract(string|iterable $resource, MessageCatalogue $catalogue);
/**
* Sets the prefix that should be used for new found messages.
*
* @return void
*/
public function setPrefix(string $prefix);
}

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
{
@@ -129,7 +133,7 @@ class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
];
/**
* {@inheritdoc}
* @return void
*/
public function extract(string|iterable $resource, MessageCatalogue $catalog)
{
@@ -142,7 +146,7 @@ class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
}
/**
* {@inheritdoc}
* @return void
*/
public function setPrefix(string $prefix)
{
@@ -164,7 +168,7 @@ class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
/**
* Seeks to a non-whitespace token.
*/
private function seekToNextRelevantToken(\Iterator $tokenIterator)
private function seekToNextRelevantToken(\Iterator $tokenIterator): void
{
for (; $tokenIterator->valid(); $tokenIterator->next()) {
$t = $tokenIterator->current();
@@ -174,7 +178,7 @@ class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
}
}
private function skipMethodArgument(\Iterator $tokenIterator)
private function skipMethodArgument(\Iterator $tokenIterator): void
{
$openBraces = 0;
@@ -199,7 +203,7 @@ class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
* Extracts the message from the iterator while the tokens
* match allowed message tokens.
*/
private function getValue(\Iterator $tokenIterator)
private function getValue(\Iterator $tokenIterator): string
{
$message = '';
$docToken = '';
@@ -257,6 +261,8 @@ class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
/**
* Extracts trans message from PHP tokens.
*
* @return void
*/
protected function parseTokens(array $tokens, MessageCatalogue $catalog, string $filename)
{
@@ -314,9 +320,6 @@ class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
return $this->isFile($file) && 'php' === pathinfo($file, \PATHINFO_EXTENSION);
}
/**
* {@inheritdoc}
*/
protected function extractFromDirectory(string|array $directory): iterable
{
if (!class_exists(Finder::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 = [
@@ -89,7 +94,7 @@ class PhpStringTokenParser
* @param string $str String without quotes
* @param string|null $quote Quote type
*/
public static function parseEscapeSequences(string $str, string $quote = null): string
public static function parseEscapeSequences(string $str, ?string $quote = null): string
{
if (null !== $quote) {
$str = str_replace('\\'.$quote, $quote, $str);

View File

@@ -20,12 +20,9 @@ use Symfony\Component\Translation\Exception\LogicException;
*/
class IntlFormatter implements IntlFormatterInterface
{
private $hasMessageFormatter;
private $cache = [];
private bool $hasMessageFormatter;
private array $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,33 +22,23 @@ 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
*/
public function __construct(TranslatorInterface $translator = null, IntlFormatterInterface $intlFormatter = null)
public function __construct(?TranslatorInterface $translator = null, ?IntlFormatterInterface $intlFormatter = null)
{
$this->translator = $translator ?? new IdentityTranslator();
$this->intlFormatter = $intlFormatter ?? new IntlFormatter();
}
/**
* {@inheritdoc}
*/
public function format(string $message, string $locale, array $parameters = []): string
{
if ($this->translator instanceof TranslatorInterface) {
return $this->translator->trans($message, $parameters, null, $locale);
}
return strtr($message, $parameters);
return $this->translator->trans($message, $parameters, null, $locale);
}
/**
* {@inheritdoc}
*/
public function formatIntl(string $message, string $locale, array $parameters = []): string
{
return $this->intlFormatter->formatIntl($message, $locale, $parameters);

View File

@@ -1,4 +1,4 @@
Copyright (c) 2004-2023 Fabien Potencier
Copyright (c) 2004-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -20,9 +20,6 @@ use Symfony\Component\Translation\MessageCatalogue;
*/
class ArrayLoader implements LoaderInterface
{
/**
* {@inheritdoc}
*/
public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue
{
$resource = $this->flatten($resource);
@@ -46,9 +43,11 @@ class ArrayLoader implements LoaderInterface
foreach ($messages as $key => $value) {
if (\is_array($value)) {
foreach ($this->flatten($value) as $k => $v) {
$result[$key.'.'.$k] = $v;
if (null !== $v) {
$result[$key.'.'.$k] = $v;
}
}
} else {
} elseif (null !== $value) {
$result[$key] = $value;
}
}

View File

@@ -22,11 +22,8 @@ class CsvFileLoader extends FileLoader
{
private string $delimiter = ';';
private string $enclosure = '"';
private string $escape = '\\';
private string $escape = '';
/**
* {@inheritdoc}
*/
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];
}
}
@@ -55,8 +52,10 @@ class CsvFileLoader extends FileLoader
/**
* Sets the delimiter, enclosure, and escape character for CSV.
*
* @return void
*/
public function setCsvControl(string $delimiter = ';', string $enclosure = '"', string $escape = '\\')
public function setCsvControl(string $delimiter = ';', string $enclosure = '"', string $escape = '')
{
$this->delimiter = $delimiter;
$this->enclosure = $enclosure;

View File

@@ -21,9 +21,6 @@ use Symfony\Component\Translation\MessageCatalogue;
*/
abstract class FileLoader extends ArrayLoader
{
/**
* {@inheritdoc}
*/
public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue
{
if (!stream_is_local($resource)) {
@@ -37,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)) {

View File

@@ -23,9 +23,6 @@ use Symfony\Component\Translation\MessageCatalogue;
*/
class IcuDatFileLoader extends IcuResFileLoader
{
/**
* {@inheritdoc}
*/
public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue
{
if (!stream_is_local($resource.'.dat')) {
@@ -38,7 +35,7 @@ class IcuDatFileLoader extends IcuResFileLoader
try {
$rb = new \ResourceBundle($locale, $resource);
} catch (\Exception $e) {
} catch (\Exception) {
$rb = null;
}

View File

@@ -23,9 +23,6 @@ use Symfony\Component\Translation\MessageCatalogue;
*/
class IcuResFileLoader implements LoaderInterface
{
/**
* {@inheritdoc}
*/
public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue
{
if (!stream_is_local($resource)) {
@@ -38,7 +35,7 @@ class IcuResFileLoader implements LoaderInterface
try {
$rb = new \ResourceBundle($locale, $resource);
} catch (\Exception $e) {
} catch (\Exception) {
$rb = null;
}
@@ -71,9 +68,9 @@ 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
* @param string|null $path Current path being parsed, used internally for recursive calls
*/
protected function flatten(\ResourceBundle $rb, array &$messages = [], string $path = null): array
protected function flatten(\ResourceBundle $rb, array &$messages = [], ?string $path = null): array
{
foreach ($rb as $key => $value) {
$nodePath = $path ? $path.'.'.$key : $key;

View File

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

View File

@@ -20,9 +20,6 @@ use Symfony\Component\Translation\Exception\InvalidResourceException;
*/
class JsonFileLoader extends FileLoader
{
/**
* {@inheritdoc}
*/
protected function loadResource(string $resource): array
{
$messages = [];
@@ -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

@@ -38,8 +38,6 @@ 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): array
{

View File

@@ -20,12 +20,9 @@ class PhpFileLoader extends FileLoader
{
private static ?array $cache = [];
/**
* {@inheritdoc}
*/
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', 'embed'], 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,8 +57,6 @@ 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): array
{
@@ -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);
}
@@ -124,7 +122,7 @@ class PoFileLoader extends FileLoader
* A .po file could contain by error missing plural indexes. We need to
* fix these before saving them.
*/
private function addMessage(array &$messages, array $item)
private function addMessage(array &$messages, array $item): void
{
if (!empty($item['ids']['singular'])) {
$id = stripcslashes($item['ids']['singular']);

View File

@@ -25,9 +25,6 @@ use Symfony\Component\Translation\MessageCatalogue;
*/
class QtFileLoader implements LoaderInterface
{
/**
* {@inheritdoc}
*/
public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue
{
if (!class_exists(XmlUtils::class)) {
@@ -67,7 +64,6 @@ class QtFileLoader implements LoaderInterface
$domain
);
}
$translation = $translation->nextSibling;
}
if (class_exists(FileResource::class)) {

View File

@@ -28,9 +28,6 @@ use Symfony\Component\Translation\Util\XliffUtils;
*/
class XliffFileLoader implements LoaderInterface
{
/**
* {@inheritdoc}
*/
public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue
{
if (!class_exists(XmlUtils::class)) {
@@ -75,7 +72,7 @@ class XliffFileLoader implements LoaderInterface
return $catalogue;
}
private function extract(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain)
private function extract(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain): void
{
$xliffVersion = XliffUtils::getVersionNumber($dom);
@@ -91,7 +88,7 @@ class XliffFileLoader implements LoaderInterface
/**
* Extract messages and metadata from DOMDocument into a MessageCatalogue.
*/
private function extractXliff1(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain)
private function extractXliff1(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain): void
{
$xml = simplexml_import_dom($dom);
$encoding = $dom->encoding ? strtoupper($dom->encoding) : null;
@@ -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();
@@ -111,12 +112,20 @@ class XliffFileLoader implements LoaderInterface
continue;
}
$source = isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source;
$source = (string) (isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source);
if (isset($translation->target)
&& 'needs-translation' === (string) $translation->target->attributes()['state']
&& \in_array((string) $translation->target, [$source, (string) $translation->source], true)
) {
continue;
}
// If the xlf file has another encoding specified, try to convert it because
// simple_xml will always return utf-8 encoded values
$target = $this->utf8ToCharset((string) ($translation->target ?? $translation->source), $encoding);
$catalogue->set((string) $source, $target, $domain);
$catalogue->set($source, $target, $domain);
$metadata = [
'source' => (string) $translation->source,
@@ -139,12 +148,12 @@ class XliffFileLoader implements LoaderInterface
$metadata['id'] = (string) $attributes['id'];
}
$catalogue->setMetadata((string) $source, $metadata, $domain);
$catalogue->setMetadata($source, $metadata, $domain);
}
}
}
private function extractXliff2(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain)
private function extractXliff2(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain): void
{
$xml = simplexml_import_dom($dom);
$encoding = $dom->encoding ? strtoupper($dom->encoding) : null;
@@ -190,7 +199,7 @@ class XliffFileLoader implements LoaderInterface
/**
* Convert a UTF8 string to the specified encoding.
*/
private function utf8ToCharset(string $content, string $encoding = null): string
private function utf8ToCharset(string $content, ?string $encoding = null): string
{
if ('UTF-8' !== $encoding && !empty($encoding)) {
return mb_convert_encoding($content, $encoding, 'UTF-8');
@@ -199,7 +208,7 @@ class XliffFileLoader implements LoaderInterface
return $content;
}
private function parseNotesMetadata(\SimpleXMLElement $noteElement = null, string $encoding = null): array
private function parseNotesMetadata(?\SimpleXMLElement $noteElement = null, ?string $encoding = null): array
{
$notes = [];
@@ -227,6 +236,6 @@ class XliffFileLoader implements LoaderInterface
private function isXmlString(string $resource): bool
{
return 0 === strpos($resource, '<?xml');
return str_starts_with($resource, '<?xml');
}
}

View File

@@ -24,14 +24,11 @@ use Symfony\Component\Yaml\Yaml;
*/
class YamlFileLoader extends FileLoader
{
private $yamlParser;
private YamlParser $yamlParser;
/**
* {@inheritdoc}
*/
protected function loadResource(string $resource): array
{
if (null === $this->yamlParser) {
if (!isset($this->yamlParser)) {
if (!class_exists(\Symfony\Component\Yaml\Parser::class)) {
throw new LogicException('Loading translations from the YAML format requires the Symfony Yaml component.');
}

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): string
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);
@@ -49,7 +46,7 @@ class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface,
}
/**
* {@inheritdoc}
* @return void
*/
public function setLocale(string $locale)
{
@@ -62,25 +59,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(): string
{
return $this->translator->getLocale();
}
/**
* {@inheritdoc}
*/
public function getCatalogue(string $locale = null): MessageCatalogueInterface
public function getCatalogue(?string $locale = null): MessageCatalogueInterface
{
return $this->translator->getCatalogue($locale);
}
/**
* {@inheritdoc}
*/
public function getCatalogues(): array
{
return $this->translator->getCatalogues();
@@ -99,7 +87,7 @@ class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface,
}
/**
* Passes through all unknown calls onto the translator object.
* @return mixed
*/
public function __call(string $method, array $args)
{
@@ -109,11 +97,9 @@ class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface,
/**
* Logs for missing translations.
*/
private function log(string $id, ?string $domain, ?string $locale)
private function log(string $id, ?string $domain, ?string $locale): void
{
if (null === $domain) {
$domain = 'messages';
}
$domain ??= 'messages';
$catalogue = $this->translator->getCatalogue($locale);
if ($catalogue->defines($id, $domain)) {

View File

@@ -17,13 +17,14 @@ use Symfony\Component\Translation\Exception\LogicException;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterface
class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterface, CatalogueMetadataAwareInterface
{
private array $messages = [];
private array $metadata = [];
private array $catalogueMetadata = [];
private array $resources = [];
private string $locale;
private $fallbackCatalogue = null;
private ?MessageCatalogueInterface $fallbackCatalogue = null;
private ?self $parent = null;
/**
@@ -35,17 +36,11 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
$this->messages = $messages;
}
/**
* {@inheritdoc}
*/
public function getLocale(): string
{
return $this->locale;
}
/**
* {@inheritdoc}
*/
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): array
public function all(?string $domain = null): array
{
if (null !== $domain) {
// skip messages merge if intl-icu requested explicitly
@@ -89,16 +81,13 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
}
/**
* {@inheritdoc}
* @return void
*/
public function set(string $id, string $translation, string $domain = 'messages')
{
$this->add([$id => $translation], $domain);
}
/**
* {@inheritdoc}
*/
public function has(string $id, string $domain = 'messages'): bool
{
if (isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id])) {
@@ -112,17 +101,11 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
return false;
}
/**
* {@inheritdoc}
*/
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'): string
{
if (isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id])) {
@@ -141,7 +124,7 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
}
/**
* {@inheritdoc}
* @return void
*/
public function replace(array $messages, string $domain = 'messages')
{
@@ -151,7 +134,7 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
}
/**
* {@inheritdoc}
* @return void
*/
public function add(array $messages, string $domain = 'messages')
{
@@ -167,7 +150,7 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
}
/**
* {@inheritdoc}
* @return void
*/
public function addCatalogue(MessageCatalogueInterface $catalogue)
{
@@ -191,10 +174,15 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
$metadata = $catalogue->getMetadata('', '');
$this->addMetadata($metadata);
}
if ($catalogue instanceof CatalogueMetadataAwareInterface) {
$catalogueMetadata = $catalogue->getCatalogueMetadata('', '');
$this->addCatalogueMetadata($catalogueMetadata);
}
}
/**
* {@inheritdoc}
* @return void
*/
public function addFallbackCatalogue(MessageCatalogueInterface $catalogue)
{
@@ -225,33 +213,24 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
}
}
/**
* {@inheritdoc}
*/
public function getFallbackCatalogue(): ?MessageCatalogueInterface
{
return $this->fallbackCatalogue;
}
/**
* {@inheritdoc}
*/
public function getResources(): array
{
return array_values($this->resources);
}
/**
* {@inheritdoc}
* @return void
*/
public function addResource(ResourceInterface $resource)
{
$this->resources[$resource->__toString()] = $resource;
}
/**
* {@inheritdoc}
*/
public function getMetadata(string $key = '', string $domain = 'messages'): mixed
{
if ('' == $domain) {
@@ -272,7 +251,7 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
}
/**
* {@inheritdoc}
* @return void
*/
public function setMetadata(string $key, mixed $value, string $domain = 'messages')
{
@@ -280,7 +259,7 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
}
/**
* {@inheritdoc}
* @return void
*/
public function deleteMetadata(string $key = '', string $domain = 'messages')
{
@@ -293,12 +272,53 @@ 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;
}
/**
* @return void
*/
public function setCatalogueMetadata(string $key, mixed $value, string $domain = 'messages')
{
$this->catalogueMetadata[$domain][$key] = $value;
}
/**
* @return void
*/
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.
*
* @param array $values Values to add
*/
private function addMetadata(array $values)
private function addMetadata(array $values): void
{
foreach ($values as $domain => $keys) {
foreach ($keys as $key => $value) {
@@ -306,4 +326,13 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
}
}
}
private function addCatalogueMetadata(array $values): void
{
foreach ($values as $domain => $keys) {
foreach ($keys as $key => $value) {
$this->setCatalogueMetadata($key, $value, $domain);
}
}
}
}

View File

@@ -36,10 +36,8 @@ interface MessageCatalogueInterface
* Gets the messages within a given domain.
*
* If $domain is null, it returns all messages.
*
* @param string $domain The domain name
*/
public function all(string $domain = null): array;
public function all(?string $domain = null): array;
/**
* Sets a message translation.
@@ -47,6 +45,8 @@ interface MessageCatalogueInterface
* @param string $id The message id
* @param string $translation The messages translation
* @param string $domain The domain name
*
* @return void
*/
public function set(string $id, string $translation, string $domain = 'messages');
@@ -79,6 +79,8 @@ interface MessageCatalogueInterface
*
* @param array $messages An array of translations
* @param string $domain The domain name
*
* @return void
*/
public function replace(array $messages, string $domain = 'messages');
@@ -87,6 +89,8 @@ interface MessageCatalogueInterface
*
* @param array $messages An array of translations
* @param string $domain The domain name
*
* @return void
*/
public function add(array $messages, string $domain = 'messages');
@@ -94,6 +98,8 @@ interface MessageCatalogueInterface
* Merges translations from the given Catalogue into the current one.
*
* The two catalogues must have the same locale.
*
* @return void
*/
public function addCatalogue(self $catalogue);
@@ -102,6 +108,8 @@ interface MessageCatalogueInterface
* only when the translation does not exist.
*
* This is used to provide default translations when they do not exist for the current locale.
*
* @return void
*/
public function addFallbackCatalogue(self $catalogue);
@@ -119,6 +127,8 @@ interface MessageCatalogueInterface
/**
* Adds a resource for this collection.
*
* @return void
*/
public function addResource(ResourceInterface $resource);
}

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>
*/
@@ -31,6 +31,8 @@ interface MetadataAwareInterface
/**
* Adds metadata to a message domain.
*
* @return void
*/
public function setMetadata(string $key, mixed $value, string $domain = 'messages');
@@ -39,6 +41,8 @@ interface MetadataAwareInterface
*
* Passing an empty domain will delete all metadata. Passing an empty key will
* delete all metadata for the given domain.
*
* @return void
*/
public function deleteMetadata(string $key = '', string $domain = 'messages');
}

View File

@@ -27,19 +27,11 @@ abstract class AbstractProviderFactory implements ProviderFactoryInterface
protected function getUser(Dsn $dsn): string
{
if (null === $user = $dsn->getUser()) {
throw new IncompleteDsnException('User is not set.', $dsn->getOriginalDsn());
}
return $user;
return $dsn->getUser() ?? throw new IncompleteDsnException('User is not set.', $dsn->getScheme().'://'.$dsn->getHost());
}
protected function getPassword(Dsn $dsn): string
{
if (null === $password = $dsn->getPassword()) {
throw new IncompleteDsnException('Password is not set.', $dsn->getOriginalDsn());
}
return $password;
return $dsn->getPassword() ?? throw new IncompleteDsnException('Password is not set.', $dsn->getOriginalDsn());
}
}

View File

@@ -29,29 +29,29 @@ final class Dsn
private array $options = [];
private string $originalDsn;
public function __construct(string $dsn)
public function __construct(#[\SensitiveParameter] string $dsn)
{
$this->originalDsn = $dsn;
if (false === $parsedDsn = parse_url($dsn)) {
throw new InvalidArgumentException(sprintf('The "%s" translation provider DSN is invalid.', $dsn));
if (false === $params = parse_url($dsn)) {
throw new InvalidArgumentException('The translation provider DSN is invalid.');
}
if (!isset($parsedDsn['scheme'])) {
throw new InvalidArgumentException(sprintf('The "%s" translation provider DSN must contain a scheme.', $dsn));
if (!isset($params['scheme'])) {
throw new InvalidArgumentException('The translation provider DSN must contain a scheme.');
}
$this->scheme = $parsedDsn['scheme'];
$this->scheme = $params['scheme'];
if (!isset($parsedDsn['host'])) {
throw new InvalidArgumentException(sprintf('The "%s" translation provider DSN must contain a host (use "default" by default).', $dsn));
if (!isset($params['host'])) {
throw new InvalidArgumentException('The translation provider DSN must contain a host (use "default" by default).');
}
$this->host = $parsedDsn['host'];
$this->host = $params['host'];
$this->user = '' !== ($parsedDsn['user'] ?? '') ? urldecode($parsedDsn['user']) : null;
$this->password = '' !== ($parsedDsn['pass'] ?? '') ? urldecode($parsedDsn['pass']) : null;
$this->port = $parsedDsn['port'] ?? null;
$this->path = $parsedDsn['path'] ?? null;
parse_str($parsedDsn['query'] ?? '', $this->options);
$this->user = '' !== ($params['user'] ?? '') ? rawurldecode($params['user']) : null;
$this->password = '' !== ($params['pass'] ?? '') ? rawurldecode($params['pass']) : null;
$this->port = $params['port'] ?? null;
$this->path = $params['path'] ?? null;
parse_str($params['query'] ?? '', $this->options);
}
public function getScheme(): string
@@ -74,17 +74,17 @@ final class Dsn
return $this->password;
}
public function getPort(int $default = null): ?int
public function getPort(?int $default = null): ?int
{
return $this->port ?? $default;
}
public function getOption(string $key, mixed $default = null)
public function getOption(string $key, mixed $default = null): mixed
{
return $this->options[$key] ?? $default;
}
public function getRequiredOption(string $key)
public function getRequiredOption(string $key): mixed
{
if (!\array_key_exists($key, $this->options) || '' === trim($this->options[$key])) {
throw new MissingRequiredOptionException($key);

View File

@@ -21,7 +21,7 @@ use Symfony\Component\Translation\TranslatorBagInterface;
*/
class FilteringProvider implements ProviderInterface
{
private $provider;
private ProviderInterface $provider;
private array $locales;
private 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

@@ -14,10 +14,8 @@ namespace Symfony\Component\Translation\Provider;
use Symfony\Component\Translation\TranslatorBag;
use Symfony\Component\Translation\TranslatorBagInterface;
interface ProviderInterface
interface ProviderInterface extends \Stringable
{
public function __toString(): string;
/**
* Translations available in the TranslatorBag only must be created.
* Translations available in both the TranslatorBag and on the provider

View File

@@ -21,7 +21,7 @@ final class TranslationProviderCollection
/**
* @var array<string, ProviderInterface>
*/
private $providers;
private array $providers;
/**
* @param array<string, ProviderInterface> $providers

View File

@@ -20,7 +20,7 @@ final class PseudoLocalizationTranslator implements TranslatorInterface
{
private const EXPANSION_CHARACTER = '~';
private $translator;
private TranslatorInterface $translator;
private bool $accents;
private float $expansionFactor;
private bool $brackets;
@@ -83,10 +83,7 @@ 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
public function trans(string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string
{
$trans = '';
$visibleText = '';
@@ -123,7 +120,7 @@ final class PseudoLocalizationTranslator implements TranslatorInterface
return [[true, true, $originalTrans]];
}
$html = mb_encode_numericentity($originalTrans, [0x80, 0xFFFF, 0, 0xFFFF], mb_detect_encoding($originalTrans, null, true) ?: 'UTF-8');
$html = mb_encode_numericentity($originalTrans, [0x80, 0x10FFFF, 0, 0x1FFFFF], mb_detect_encoding($originalTrans, null, true) ?: 'UTF-8');
$useInternalErrors = libxml_use_internal_errors(true);

View File

@@ -26,12 +26,7 @@ echo $translator->trans('Hello World!'); // outputs « Bonjour ! »
Sponsor
-------
The Translation component for Symfony 5.4/6.0 is [backed][1] by:
* [Crowdin][2], a cloud-based localization management software helping teams to go global and stay agile.
* [Lokalise][3], a continuous localization and translation management platform that integrates into your development workflow so you can ship localized products, faster.
Help Symfony by [sponsoring][4] its development!
Help Symfony by [sponsoring][1] its development!
Resources
---------
@@ -42,7 +37,4 @@ Resources
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)
[1]: https://symfony.com/backers
[2]: https://crowdin.com
[3]: https://lokalise.com
[4]: https://symfony.com/sponsor
[1]: https://symfony.com/sponsor

View File

@@ -33,6 +33,8 @@ class TranslationReader implements TranslationReaderInterface
* Adds a loader to the translation extractor.
*
* @param string $format The format of the loader
*
* @return void
*/
public function addLoader(string $format, LoaderInterface $loader)
{
@@ -40,7 +42,7 @@ class TranslationReader implements TranslationReaderInterface
}
/**
* {@inheritdoc}
* @return void
*/
public function read(string $directory, MessageCatalogue $catalogue)
{

View File

@@ -22,6 +22,8 @@ interface TranslationReaderInterface
{
/**
* Reads translation messages from a directory to the catalogue.
*
* @return void
*/
public function read(string $directory, MessageCatalogue $catalogue);
}

View File

@@ -87,19 +87,15 @@ foreach ($config['original_files'] as $originalFilePath) {
$translationFilePaths = findTranslationFiles($originalFilePath, $config['locale_to_analyze']);
$translationStatus = calculateTranslationStatus($originalFilePath, $translationFilePaths);
$totalMissingTranslations += array_sum(array_map(function ($translation) {
return count($translation['missingKeys']);
}, array_values($translationStatus)));
$totalTranslationMismatches += array_sum(array_map(function ($translation) {
return count($translation['mismatches']);
}, array_values($translationStatus)));
$totalMissingTranslations += array_sum(array_map(fn ($translation) => count($translation['missingKeys']), array_values($translationStatus)));
$totalTranslationMismatches += array_sum(array_map(fn ($translation) => count($translation['mismatches']), array_values($translationStatus)));
printTranslationStatus($originalFilePath, $translationStatus, $config['verbose_output'], $config['include_completed_languages']);
}
exit($totalTranslationMismatches > 0 ? 1 : 0);
function findTranslationFiles($originalFilePath, $localeToAnalyze)
function findTranslationFiles($originalFilePath, $localeToAnalyze): array
{
$translations = [];
@@ -122,7 +118,7 @@ function findTranslationFiles($originalFilePath, $localeToAnalyze)
return $translations;
}
function calculateTranslationStatus($originalFilePath, $translationFilePaths)
function calculateTranslationStatus($originalFilePath, $translationFilePaths): array
{
$translationStatus = [];
$allTranslationKeys = extractTranslationKeys($originalFilePath);
@@ -163,14 +159,14 @@ function extractLocaleFromFilePath($filePath)
return $parts[count($parts) - 2];
}
function extractTranslationKeys($filePath)
function extractTranslationKeys($filePath): array
{
$translationKeys = [];
$contents = new \SimpleXMLElement(file_get_contents($filePath));
$contents = new SimpleXMLElement(file_get_contents($filePath));
foreach ($contents->file->body->{'trans-unit'} as $translationKey) {
$translationId = (string) $translationKey['id'];
$translationKey = (string) $translationKey->source;
$translationKey = (string) ($translationKey['resname'] ?? $translationKey->source);
$translationKeys[$translationId] = $translationKey;
}

View File

@@ -35,6 +35,7 @@
"en_GM": "en_001",
"en_GY": "en_001",
"en_HK": "en_001",
"en_ID": "en_001",
"en_IE": "en_001",
"en_IL": "en_001",
"en_IM": "en_001",

View File

@@ -15,7 +15,7 @@ if (!\function_exists(t::class)) {
/**
* @author Nate Wiebe <nate@northern.co>
*/
function t(string $message, array $parameters = [], string $domain = null): TranslatableMessage
function t(string $message, array $parameters = [], ?string $domain = null): TranslatableMessage
{
return new TranslatableMessage($message, $parameters, $domain);
}

File diff suppressed because it is too large Load Diff

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,39 +21,39 @@ 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;
/**
* A test case to ease testing a translation provider factory.
*
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*
* @internal
*/
abstract class ProviderFactoryTestCase extends TestCase
{
protected $client;
protected $logger;
protected HttpClientInterface $client;
protected LoggerInterface|MockObject $logger;
protected string $defaultLocale;
protected $loader;
protected $xliffFileDumper;
protected LoaderInterface|MockObject $loader;
protected XliffFileDumper|MockObject $xliffFileDumper;
protected TranslatorBagInterface|MockObject $translatorBag;
abstract public function createFactory(): ProviderFactoryInterface;
/**
* @return iterable<array{0: bool, 1: string}>
*/
abstract public function supportsProvider(): iterable;
abstract public static function supportsProvider(): iterable;
/**
* @return iterable<array{0: string, 1: string, 2: TransportInterface}>
* @return iterable<array{0: string, 1: string}>
*/
abstract public function createProvider(): iterable;
abstract public static function createProvider(): iterable;
/**
* @return iterable<array{0: string, 1: string|null}>
*/
public function unsupportedSchemeProvider(): iterable
public static function unsupportedSchemeProvider(): iterable
{
return [];
}
@@ -60,7 +61,7 @@ abstract class ProviderFactoryTestCase extends TestCase
/**
* @return iterable<array{0: string, 1: string|null}>
*/
public function incompleteDsnProvider(): iterable
public static function incompleteDsnProvider(): iterable
{
return [];
}
@@ -89,7 +90,7 @@ abstract class ProviderFactoryTestCase extends TestCase
/**
* @dataProvider unsupportedSchemeProvider
*/
public function testUnsupportedSchemeException(string $dsn, string $message = null)
public function testUnsupportedSchemeException(string $dsn, ?string $message = null)
{
$factory = $this->createFactory();
@@ -106,7 +107,7 @@ abstract class ProviderFactoryTestCase extends TestCase
/**
* @dataProvider incompleteDsnProvider
*/
public function testIncompleteDsnException(string $dsn, string $message = null)
public function testIncompleteDsnException(string $dsn, ?string $message = null)
{
$factory = $this->createFactory();
@@ -144,4 +145,9 @@ abstract class ProviderFactoryTestCase extends TestCase
{
return $this->xliffFileDumper ??= $this->createMock(XliffFileDumper::class);
}
protected function getTranslatorBag(): TranslatorBagInterface
{
return $this->translatorBag ??= $this->createMock(TranslatorBagInterface::class);
}
}

View File

@@ -11,35 +11,36 @@
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;
/**
* A test case to ease testing a translation provider.
*
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*
* @internal
*/
abstract class ProviderTestCase extends TestCase
{
protected $client;
protected $logger;
protected HttpClientInterface $client;
protected LoggerInterface|MockObject $logger;
protected string $defaultLocale;
protected $loader;
protected $xliffFileDumper;
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;
abstract public static function createProvider(HttpClientInterface $client, LoaderInterface $loader, LoggerInterface $logger, string $defaultLocale, string $endpoint): ProviderInterface;
/**
* @return iterable<array{0: string, 1: ProviderInterface}>
* @return iterable<array{0: ProviderInterface, 1: string}>
*/
abstract public function toStringProvider(): iterable;
abstract public static function toStringProvider(): iterable;
/**
* @dataProvider toStringProvider
@@ -73,4 +74,9 @@ abstract class ProviderTestCase extends TestCase
{
return $this->xliffFileDumper ??= $this->createMock(XliffFileDumper::class);
}
protected function getTranslatorBag(): TranslatorBagInterface
{
return $this->translatorBag ??= $this->createMock(TranslatorBagInterface::class);
}
}

View File

@@ -23,7 +23,7 @@ class TranslatableMessage implements TranslatableInterface
private array $parameters;
private ?string $domain;
public function __construct(string $message, array $parameters = [], string $domain = null)
public function __construct(string $message, array $parameters = [], ?string $domain = null)
{
$this->message = $message;
$this->parameters = $parameters;
@@ -50,12 +50,10 @@ class TranslatableMessage implements TranslatableInterface
return $this->domain;
}
public function trans(TranslatorInterface $translator, string $locale = null): string
public function trans(TranslatorInterface $translator, ?string $locale = null): string
{
return $translator->trans($this->getMessage(), array_map(
static function ($parameter) use ($translator, $locale) {
return $parameter instanceof TranslatableInterface ? $parameter->trans($translator, $locale) : $parameter;
},
static fn ($parameter) => $parameter instanceof TranslatableInterface ? $parameter->trans($translator, $locale) : $parameter,
$this->getParameters()
), $this->getDomain(), $locale);
}

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
@@ -51,7 +52,7 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
private array $resources = [];
private $formatter;
private MessageFormatterInterface $formatter;
private ?string $cacheDir;
@@ -59,7 +60,7 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
private array $cacheVary;
private $configCacheFactory;
private ?ConfigCacheFactoryInterface $configCacheFactory;
private array $parentLocales;
@@ -68,21 +69,20 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
/**
* @throws InvalidArgumentException If a locale contains invalid characters
*/
public function __construct(string $locale, MessageFormatterInterface $formatter = null, string $cacheDir = null, bool $debug = false, array $cacheVary = [])
public function __construct(string $locale, ?MessageFormatterInterface $formatter = null, ?string $cacheDir = null, bool $debug = false, array $cacheVary = [])
{
$this->setLocale($locale);
if (null === $formatter) {
$formatter = new MessageFormatter();
}
$this->formatter = $formatter;
$this->formatter = $formatter ??= new MessageFormatter();
$this->cacheDir = $cacheDir;
$this->debug = $debug;
$this->cacheVary = $cacheVary;
$this->hasIntlFormatter = $formatter instanceof IntlFormatterInterface;
}
/**
* @return void
*/
public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory)
{
$this->configCacheFactory = $configCacheFactory;
@@ -92,6 +92,8 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
* Adds a Loader.
*
* @param string $format The name of the loader (@see addResource())
*
* @return void
*/
public function addLoader(string $format, LoaderInterface $loader)
{
@@ -104,13 +106,13 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
* @param string $format The name of the loader (@see addLoader())
* @param mixed $resource The resource name
*
* @return void
*
* @throws InvalidArgumentException If the locale contains invalid characters
*/
public function addResource(string $format, mixed $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';
@@ -125,7 +127,7 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
}
/**
* {@inheritdoc}
* @return void
*/
public function setLocale(string $locale)
{
@@ -133,9 +135,6 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
$this->locale = $locale;
}
/**
* {@inheritdoc}
*/
public function getLocale(): string
{
return $this->locale ?: (class_exists(\Locale::class) ? \Locale::getDefault() : 'en');
@@ -146,6 +145,8 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
*
* @param string[] $locales
*
* @return void
*
* @throws InvalidArgumentException If a locale contains invalid characters
*/
public function setFallbackLocales(array $locales)
@@ -170,18 +171,13 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
return $this->fallbackLocales;
}
/**
* {@inheritdoc}
*/
public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null): string
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();
@@ -194,6 +190,8 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
}
}
$parameters = array_map(fn ($parameter) => $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)
@@ -205,10 +203,7 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
return $this->formatter->format($catalogue->get($id, $domain), $locale, $parameters);
}
/**
* {@inheritdoc}
*/
public function getCatalogue(string $locale = null): MessageCatalogueInterface
public function getCatalogue(?string $locale = null): MessageCatalogueInterface
{
if (!$locale) {
$locale = $this->getLocale();
@@ -223,9 +218,6 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
return $this->catalogues[$locale];
}
/**
* {@inheritdoc}
*/
public function getCatalogues(): array
{
return array_values($this->catalogues);
@@ -241,6 +233,9 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
return $this->loaders;
}
/**
* @return void
*/
protected function loadCatalogue(string $locale)
{
if (null === $this->cacheDir) {
@@ -250,6 +245,9 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
}
}
/**
* @return void
*/
protected function initializeCatalogue(string $locale)
{
$this->assertValidLocale($locale);
@@ -386,6 +384,9 @@ EOF
}
}
/**
* @return array
*/
protected function computeFallbackLocales(string $locale)
{
$this->parentLocales ??= json_decode(file_get_contents(__DIR__.'/Resources/data/parents.json'), true);
@@ -430,6 +431,8 @@ EOF
/**
* Asserts that the locale is valid, throws an Exception if not.
*
* @return void
*
* @throws InvalidArgumentException If the locale contains invalid characters
*/
protected function assertValidLocale(string $locale)

View File

@@ -35,10 +35,7 @@ final class TranslatorBag implements TranslatorBagInterface
}
}
/**
* {@inheritdoc}
*/
public function getCatalogue(string $locale = null): MessageCatalogueInterface
public function getCatalogue(?string $locale = null): MessageCatalogueInterface
{
if (null === $locale || !isset($this->catalogues[$locale])) {
$this->catalogues[$locale] = new MessageCatalogue($locale);
@@ -47,9 +44,6 @@ final class TranslatorBag implements TranslatorBagInterface
return $this->catalogues[$locale];
}
/**
* {@inheritdoc}
*/
public function getCatalogues(): array
{
return array_values($this->catalogues);
@@ -70,7 +64,7 @@ final class TranslatorBag implements TranslatorBagInterface
$operation->moveMessagesToIntlDomainsIfPossible(AbstractOperation::NEW_BATCH);
$newCatalogue = new MessageCatalogue($locale);
foreach ($operation->getDomains() as $domain) {
foreach ($catalogue->getDomains() as $domain) {
$newCatalogue->add($operation->getNewMessages($domain), $domain);
}

View File

@@ -25,7 +25,7 @@ interface TranslatorBagInterface
*
* @throws InvalidArgumentException If the locale contains invalid characters
*/
public function getCatalogue(string $locale = null): MessageCatalogueInterface;
public function getCatalogue(?string $locale = null): MessageCatalogueInterface;
/**
* Returns all catalogues of the instance.

View File

@@ -36,7 +36,7 @@ class ArrayConverter
$tree = [];
foreach ($messages as $id => $value) {
$referenceToElement = &self::getElementByPath($tree, explode('.', $id));
$referenceToElement = &self::getElementByPath($tree, self::getKeyParts($id));
$referenceToElement = $value;
@@ -46,7 +46,7 @@ class ArrayConverter
return $tree;
}
private static function &getElementByPath(array &$tree, array $parts)
private static function &getElementByPath(array &$tree, array $parts): mixed
{
$elem = &$tree;
$parentOfElem = null;
@@ -63,6 +63,7 @@ class ArrayConverter
$elem = &$elem[implode('.', \array_slice($parts, $i))];
break;
}
$parentOfElem = &$elem;
$elem = &$elem[$part];
}
@@ -82,7 +83,7 @@ class ArrayConverter
return $elem;
}
private static function cancelExpand(array &$tree, string $prefix, array $node)
private static function cancelExpand(array &$tree, string $prefix, array $node): void
{
$prefix .= '.';
@@ -94,4 +95,48 @@ class ArrayConverter
}
}
}
/**
* @return string[]
*/
private static function getKeyParts(string $key): array
{
$parts = explode('.', $key);
$partsCount = \count($parts);
$result = [];
$buffer = '';
foreach ($parts as $index => $part) {
if (0 === $index && '' === $part) {
$buffer = '.';
continue;
}
if ($index === $partsCount - 1 && '' === $part) {
$buffer .= '.';
$result[] = $buffer;
continue;
}
if (isset($parts[$index + 1]) && '' === $parts[$index + 1]) {
$buffer .= $part;
continue;
}
if ($buffer) {
$result[] = $buffer.$part;
$buffer = '';
continue;
}
$result[] = $part;
}
return $result;
}
}

View File

@@ -129,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

@@ -30,6 +30,8 @@ class TranslationWriter implements TranslationWriterInterface
/**
* Adds a dumper to the writer.
*
* @return void
*/
public function addDumper(string $format, DumperInterface $dumper)
{
@@ -50,6 +52,8 @@ class TranslationWriter implements TranslationWriterInterface
* @param string $format The format to use to dump the messages
* @param array $options Options that are passed to the dumper
*
* @return void
*
* @throws InvalidArgumentException
*/
public function write(MessageCatalogue $catalogue, string $format, array $options = [])

View File

@@ -27,6 +27,8 @@ interface TranslationWriterInterface
* @param string $format The format to use to dump the messages
* @param array $options Options that are passed to the dumper
*
* @return void
*
* @throws InvalidArgumentException
*/
public function write(MessageCatalogue $catalogue, string $format, array $options = []);

View File

@@ -16,27 +16,32 @@
}
],
"require": {
"php": ">=8.0.2",
"php": ">=8.1",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-mbstring": "~1.0",
"symfony/translation-contracts": "^2.3|^3.0"
"symfony/translation-contracts": "^2.5|^3.0"
},
"require-dev": {
"symfony/config": "^5.4|^6.0",
"symfony/console": "^5.4|^6.0",
"symfony/dependency-injection": "^5.4|^6.0",
"symfony/http-client-contracts": "^1.1|^2.0|^3.0",
"symfony/http-kernel": "^5.4|^6.0",
"symfony/intl": "^5.4|^6.0",
"nikic/php-parser": "^4.18|^5.0",
"symfony/config": "^5.4|^6.0|^7.0",
"symfony/console": "^5.4|^6.0|^7.0",
"symfony/dependency-injection": "^5.4|^6.0|^7.0",
"symfony/http-client-contracts": "^2.5|^3.0",
"symfony/http-kernel": "^5.4|^6.0|^7.0",
"symfony/intl": "^5.4|^6.0|^7.0",
"symfony/polyfill-intl-icu": "^1.21",
"symfony/service-contracts": "^1.1.2|^2|^3",
"symfony/yaml": "^5.4|^6.0",
"symfony/finder": "^5.4|^6.0",
"symfony/routing": "^5.4|^6.0|^7.0",
"symfony/service-contracts": "^2.5|^3",
"symfony/yaml": "^5.4|^6.0|^7.0",
"symfony/finder": "^5.4|^6.0|^7.0",
"psr/log": "^1|^2|^3"
},
"conflict": {
"symfony/config": "<5.4",
"symfony/dependency-injection": "<5.4",
"symfony/http-client-contracts": "<2.5",
"symfony/http-kernel": "<5.4",
"symfony/service-contracts": "<2.5",
"symfony/twig-bundle": "<5.4",
"symfony/yaml": "<5.4",
"symfony/console": "<5.4"
@@ -44,11 +49,6 @@
"provide": {
"symfony/translation-implementation": "2.3|3.0"
},
"suggest": {
"symfony/config": "",
"symfony/yaml": "",
"psr/log-implementation": "To use logging capability in translator"
},
"autoload": {
"files": [ "Resources/functions.php" ],
"psr-4": { "Symfony\\Component\\Translation\\": "" },