Allow PHP-8.2 and up Compatibility instead of just PHP-8.4

This commit is contained in:
johnnyq
2026-06-12 17:06:10 -04:00
parent 2204bd52f4
commit d3a93652f3
220 changed files with 7198 additions and 2635 deletions

View File

@@ -1,6 +1,13 @@
CHANGELOG
=========
7.4
---
* Make the extractor alias optional
* Deprecate `TranslatableMessage::__toString`
* Add `Symfony\Component\Translation\StaticMessage`
7.3
---

View File

@@ -54,10 +54,10 @@ class TranslationLintCommand extends Command
new InputOption('locale', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Specify the locales to lint.', $this->enabledLocales),
])
->setHelp(<<<'EOF'
The <info>%command.name%</> command lint translations.
The <info>%command.name%</> command lint translations.
<info>php %command.full_name%</>
EOF
<info>php %command.full_name%</>
EOF
);
}
@@ -70,7 +70,7 @@ EOF
{
$locales = $input->getOption('locale');
/** @var array<string, array<string, array<string, \Throwable>> $errors */
/** @var array<string, array<string, array<string, \Throwable>>> $errors */
$errors = [];
$domainsByLocales = [];

View File

@@ -90,21 +90,22 @@ final class TranslationPullCommand extends Command
new InputOption('as-tree', null, InputOption::VALUE_REQUIRED, '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
new translations are pulled, existing ones are not overwritten.
The <info>%command.name%</> command pulls translations from the given provider. Only
new translations are pulled, existing ones are not overwritten.
You can overwrite existing translations (and remove the missing ones on local side) by using the <comment>--force</> flag:
You can overwrite existing translations (and remove the missing ones on local side) by using the <info>--force</> flag:
<info>php %command.full_name% --force provider</>
<info>php %command.full_name% --force provider</>
Full example:
Full example:
<info>php %command.full_name% provider --force --domains=messages --domains=validators --locales=en</>
<info>php %command.full_name% provider --force --domains=messages --domains=validators --locales=en</>
This command pulls all translations associated with the <comment>messages</> and <comment>validators</> domains for the <comment>en</> locale.
Local translations for the specified domains and locale are deleted if they're not present on the provider and overwritten if it's the case.
Local translations for others domains and locales are ignored.
EOF
This command pulls all translations associated with the <info>messages</> and <info>validators</> domains for the <info>en</> locale.
Local translations for the specified domains and locale are deleted if they're not present on the provider and overwritten if it's the case.
Local translations for others domains and locales are ignored.
EOF
)
;
}

View File

@@ -81,25 +81,26 @@ final class TranslationPushCommand extends Command
new InputOption('locales', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Specify the locales to push.', $this->enabledLocales),
])
->setHelp(<<<'EOF'
The <info>%command.name%</> command pushes translations to the given provider. Only new
translations are pushed, existing ones are not overwritten.
The <info>%command.name%</> command pushes translations to the given provider. Only new
translations are pushed, existing ones are not overwritten.
You can overwrite existing translations by using the <comment>--force</> flag:
You can overwrite existing translations by using the <info>--force</> flag:
<info>php %command.full_name% --force provider</>
<info>php %command.full_name% --force provider</>
You can delete provider translations which are not present locally by using the <comment>--delete-missing</> flag:
You can delete provider translations which are not present locally by using the <info>--delete-missing</> flag:
<info>php %command.full_name% --delete-missing provider</>
<info>php %command.full_name% --delete-missing provider</>
Full example:
Full example:
<info>php %command.full_name% provider --force --delete-missing --domains=messages --domains=validators --locales=en</>
<info>php %command.full_name% provider --force --delete-missing --domains=messages --domains=validators --locales=en</>
This command pushes all translations associated with the <comment>messages</> and <comment>validators</> domains for the <comment>en</> locale.
Provider translations for the specified domains and locale are deleted if they're not present locally and overwritten if it's the case.
Provider translations for others domains and locales are ignored.
EOF
This command pushes all translations associated with the <info>messages</> and <info>validators</> domains for the <info>en</> locale.
Provider translations for the specified domains and locale are deleted if they're not present locally and overwritten if it's the case.
Provider translations for others domains and locales are ignored.
EOF
)
;
}
@@ -168,7 +169,7 @@ EOF
$domains = [];
foreach ($translatorBag->getCatalogues() as $catalogue) {
$domains += $catalogue->getDomains();
$domains = array_merge($domains, $catalogue->getDomains());
}
return array_unique($domains);

View File

@@ -58,26 +58,26 @@ class XliffLintCommand extends Command
->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN')
->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.
The <info>%command.name%</info> command lints an XLIFF file and outputs to STDOUT
the first encountered syntax error.
You can validates XLIFF contents passed from STDIN:
You can validates XLIFF contents passed from STDIN:
<info>cat filename | php %command.full_name% -</info>
<info>cat filename | php %command.full_name% -</info>
You can also validate the syntax of a file:
You can also validate the syntax of a file:
<info>php %command.full_name% filename</info>
<info>php %command.full_name% filename</info>
Or of a whole directory:
Or of a whole directory:
<info>php %command.full_name% dirname</info>
<info>php %command.full_name% dirname</info>
The <info>--format</info> option specifies the format of the command output:
The <info>--format</info> option specifies the format of the command output:
<info>php %command.full_name% dirname --format=json</info>
<info>php %command.full_name% dirname --format=json</info>
EOF
EOF
)
;
}
@@ -178,7 +178,7 @@ EOF
} elseif (!$info['valid']) {
++$erroredFiles;
$io->text('<error> ERROR </error>'.($info['file'] ? \sprintf(' in %s', $info['file']) : ''));
$io->listing(array_map(function ($error) use ($info, $githubReporter) {
$io->listing(array_map(static function ($error) use ($info, $githubReporter) {
// general document errors have a '-1' line number
$line = -1 === $error['line'] ? null : $error['line'];
@@ -202,7 +202,7 @@ EOF
{
$errors = 0;
array_walk($filesInfo, function (&$v) use (&$errors) {
array_walk($filesInfo, static function (&$v) use (&$errors) {
$v['file'] = (string) $v['file'];
if (!$v['valid']) {
++$errors;
@@ -226,7 +226,7 @@ EOF
}
foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) {
if (!\in_array($file->getExtension(), ['xlf', 'xliff'])) {
if (!\in_array($file->getExtension(), ['xlf', 'xliff'], true)) {
continue;
}
@@ -239,7 +239,7 @@ EOF
*/
private function getDirectoryIterator(string $directory): iterable
{
$default = fn ($directory) => new \RecursiveIteratorIterator(
$default = static fn ($directory) => new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
\RecursiveIteratorIterator::LEAVES_ONLY
);
@@ -253,7 +253,7 @@ EOF
private function isReadable(string $fileOrDirectory): bool
{
$default = fn ($fileOrDirectory) => is_readable($fileOrDirectory);
$default = static fn ($fileOrDirectory) => is_readable($fileOrDirectory);
if (null !== $this->isReadableProvider) {
return ($this->isReadableProvider)($fileOrDirectory, $default);

View File

@@ -12,6 +12,7 @@
namespace Symfony\Component\Translation;
use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
use Symfony\Contracts\Service\ResetInterface;
use Symfony\Contracts\Translation\LocaleAwareInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
@@ -20,7 +21,7 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*
* @final since Symfony 7.1
*/
class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface, WarmableInterface
class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface, WarmableInterface, ResetInterface
{
public const MESSAGE_DEFINED = 0;
public const MESSAGE_MISSING = 1;
@@ -33,6 +34,11 @@ class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInter
) {
}
public function reset(): void
{
$this->messages = [];
}
public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string
{
$trans = $this->translator->trans($id = (string) $id, $parameters, $domain, $locale);

View File

@@ -13,7 +13,6 @@ namespace Symfony\Component\Translation\DependencyInjection;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
use Symfony\Component\DependencyInjection\Reference;
/**
@@ -30,11 +29,7 @@ class TranslationExtractorPass implements CompilerPassInterface
$definition = $container->getDefinition('translation.extractor');
foreach ($container->findTaggedServiceIds('translation.extractor', true) as $id => $attributes) {
if (!isset($attributes[0]['alias'])) {
throw new RuntimeException(\sprintf('The alias for the tag "translation.extractor" of service "%s" must be set.', $id));
}
$definition->addMethodCall('addExtractor', [$attributes[0]['alias'], new Reference($id)]);
$definition->addMethodCall('addExtractor', [$attributes[0]['alias'] ?? $id, new Reference($id)]);
}
}
}

View File

@@ -93,10 +93,10 @@ class TranslatorPathsPass extends AbstractRecursivePass
$class = $this->definitions[$i]->getClass();
if (ServiceLocator::class === $class) {
if (!isset($this->controllers[$this->currentId])) {
if (!isset($this->controllers[$this->currentId ?? ''])) {
continue;
}
foreach ($this->controllers[$this->currentId] as $class => $_) {
foreach ($this->controllers[$this->currentId ?? ''] as $class => $_) {
$this->paths[$class] = true;
}
} else {

View File

@@ -49,7 +49,7 @@ abstract class FileDumper implements DumperInterface
$fullpath = $options['path'].'/'.$this->getRelativePath($domain, $messages->getLocale());
if (!file_exists($fullpath)) {
$directory = \dirname($fullpath);
if (!file_exists($directory) && !@mkdir($directory, 0777, true)) {
if (!file_exists($directory) && !@mkdir($directory, 0o777, true)) {
throw new RuntimeException(\sprintf('Unable to create directory "%s".', $directory));
}
}

View File

@@ -76,22 +76,22 @@ class PoFileDumper extends FileDumper
}
$intervalRegexp = <<<'EOF'
/^(?P<interval>
({\s*
(\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*)
\s*})
/^(?P<interval>
({\s*
(\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*)
\s*})
|
|
(?P<left_delimiter>[\[\]])
\s*
(?P<left>-Inf|\-?\d+(\.\d+)?)
\s*,\s*
(?P<right>\+?Inf|\-?\d+(\.\d+)?)
\s*
(?P<right_delimiter>[\[\]])
)\s*(?P<message>.*?)$/xs
EOF;
(?P<left_delimiter>[\[\]])
\s*
(?P<left>-Inf|\-?\d+(\.\d+)?)
\s*,\s*
(?P<right>\+?Inf|\-?\d+(\.\d+)?)
\s*
(?P<right_delimiter>[\[\]])
)\s*(?P<message>.*?)$/xs
EOF;
$standardRules = [];
foreach ($parts as $part) {

View File

@@ -51,7 +51,6 @@ final class PhpAstExtractor extends AbstractFileExtractor implements ExtractorIn
$nameResolver = new NodeVisitor\NameResolver();
$traverser->addVisitor($nameResolver);
/** @var AbstractVisitor&NodeVisitor $visitor */
foreach ($this->visitors as $visitor) {
$visitor->initialize($catalogue, $file, $this->prefix);
$traverser->addVisitor($visitor);

View File

@@ -78,7 +78,6 @@ final class ConstraintVisitor extends AbstractVisitor implements NodeVisitor
$messages = [];
$options = $arg->value;
/** @var Node\Expr\ArrayItem $item */
foreach ($options->items as $item) {
if (!$item->key instanceof Node\Scalar\String_) {
continue;

View File

@@ -37,7 +37,7 @@ class CsvFileLoader extends FileLoader
throw new NotFoundResourceException(\sprintf('Error opening file "%s".', $resource), 0, $e);
}
$file->setFlags(\SplFileObject::READ_CSV | \SplFileObject::SKIP_EMPTY);
$file->setFlags(\SplFileObject::READ_CSV | \SplFileObject::SKIP_EMPTY | \SplFileObject::DROP_NEW_LINE);
$file->setCsvControl($this->delimiter, $this->enclosure, $this->escape);
foreach ($file as $data) {

View File

@@ -55,7 +55,7 @@ final class PseudoLocalizationTranslator implements TranslatorInterface, Transla
* * parse_html:
* type: boolean
* default: false
* description: parse the translated string as HTML - looking for HTML tags has a performance impact but allows to preserve them from alterations - it also allows to compute the visible translated string length which is useful to correctly expand ot when it contains HTML
* description: parse the translated string as HTML - looking for HTML tags has a performance impact but allows to preserve them from alterations - it also allows to compute the visible translated string length which is useful to correctly expand or when it contains HTML
* warning: unclosed tags are unsupported, they will be fixed (closed) by the parser - eg, "foo <div>bar" => "foo <div>bar</div>"
*
* * localizable_html_attributes:
@@ -166,7 +166,6 @@ final class PseudoLocalizationTranslator implements TranslatorInterface, Transla
$parts[] = [false, false, '<'.$childNode->tagName];
/** @var \DOMAttr $attribute */
foreach ($childNode->attributes as $attribute) {
$parts[] = [false, false, ' '.$attribute->nodeName.'="'];
@@ -184,7 +183,7 @@ final class PseudoLocalizationTranslator implements TranslatorInterface, Transla
$parts[] = [false, false, '>'];
$parts = array_merge($parts, $this->parseNode($childNode, $parts));
$parts = array_merge($parts, $this->parseNode($childNode));
$parts[] = [false, false, '</'.$childNode->tagName.'>'];
}
@@ -383,3 +382,5 @@ final class PseudoLocalizationTranslator implements TranslatorInterface, Transla
return false === ($encoding = mb_detect_encoding($s, null, true)) ? \strlen($s) : mb_strlen($s, $encoding);
}
}
// @php-cs-fixer-ignore random_api_migration As logic is coupled with mt_srand() in tests

View File

@@ -26,10 +26,6 @@ echo $translator->trans('Hello World!'); // outputs « Bonjour ! »
Sponsor
-------
The Translation component for Symfony 7.1 is [backed][1] by:
* [Crowdin][2], a cloud-based localization management software helping teams to go global and stay agile.
Help Symfony by [sponsoring][3] its development!
Resources
@@ -41,6 +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://symfony.com/sponsor

View File

@@ -15,27 +15,27 @@ if ('cli' !== \PHP_SAPI) {
$usageInstructions = <<<END
Usage instructions
-------------------------------------------------------------------------------
Usage instructions
-------------------------------------------------------------------------------
$ cd symfony-code-root-directory/
$ cd symfony-code-root-directory/
# show the translation status of all locales
$ php translation-status.php
# show the translation status of all locales
$ php translation-status.php
# only show the translation status of incomplete or erroneous locales
$ php translation-status.php --incomplete
# only show the translation status of incomplete or erroneous locales
$ php translation-status.php --incomplete
# show the translation status of all locales, all their missing translations and mismatches between trans-unit id and source
$ php translation-status.php -v
# show the translation status of all locales, all their missing translations and mismatches between trans-unit id and source
$ php translation-status.php -v
# show the status of a single locale
$ php translation-status.php fr
# show the status of a single locale
$ php translation-status.php fr
# show the status of a single locale, missing translations and mismatches between trans-unit id and source
$ php translation-status.php fr -v
# show the status of a single locale, missing translations and mismatches between trans-unit id and source
$ php translation-status.php fr -v
END;
END;
$config = [
// if TRUE, the full list of missing translations is displayed
@@ -87,8 +87,8 @@ foreach ($config['original_files'] as $originalFilePath) {
$translationFilePaths = findTranslationFiles($originalFilePath, $config['locale_to_analyze']);
$translationStatus = calculateTranslationStatus($originalFilePath, $translationFilePaths);
$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)));
$totalMissingTranslations += array_sum(array_map(static fn ($translation) => count($translation['missingKeys']), array_values($translationStatus)));
$totalTranslationMismatches += array_sum(array_map(static fn ($translation) => count($translation['mismatches']), array_values($translationStatus)));
printTranslationStatus($originalFilePath, $translationStatus, $config['verbose_output'], $config['include_completed_languages']);
}

View File

@@ -23,6 +23,7 @@
"en_DG": "en_001",
"en_DK": "en_150",
"en_DM": "en_001",
"en_EE": "en_150",
"en_ER": "en_001",
"en_ES": "en_150",
"en_FI": "en_150",
@@ -32,6 +33,7 @@
"en_FR": "en_150",
"en_GB": "en_001",
"en_GD": "en_001",
"en_GE": "en_150",
"en_GG": "en_001",
"en_GH": "en_001",
"en_GI": "en_001",
@@ -56,6 +58,8 @@
"en_LC": "en_001",
"en_LR": "en_001",
"en_LS": "en_001",
"en_LT": "en_150",
"en_LV": "en_150",
"en_MG": "en_001",
"en_MO": "en_001",
"en_MS": "en_001",
@@ -98,6 +102,7 @@
"en_TT": "en_001",
"en_TV": "en_001",
"en_TZ": "en_001",
"en_UA": "en_150",
"en_UG": "en_001",
"en_VC": "en_001",
"en_VG": "en_001",
@@ -130,6 +135,7 @@
"es_VE": "es_419",
"ff_Adlm": "root",
"hi_Latn": "en_IN",
"kk_Arab": "root",
"ks_Deva": "root",
"nb": "no",
"nn": "no",

View File

@@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation;
use Symfony\Contracts\Translation\TranslatableInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
final class StaticMessage implements TranslatableInterface
{
public function __construct(
private string $message,
) {
}
public function getMessage(): string
{
return $this->message;
}
public function trans(TranslatorInterface $translator, ?string $locale = null): string
{
return $this->getMessage();
}
}

View File

@@ -15,10 +15,13 @@ use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\Translation\Dumper\XliffFileDumper;
use Symfony\Component\Translation\Loader\ArrayLoader;
use Symfony\Component\Translation\Loader\LoaderInterface;
use Symfony\Component\Translation\Provider\ProviderInterface;
use Symfony\Component\Translation\TranslatorBag;
use Symfony\Component\Translation\TranslatorBagInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
@@ -59,12 +62,12 @@ abstract class ProviderTestCase extends TestCase
protected function getLoader(): LoaderInterface
{
return $this->loader ??= $this->createMock(LoaderInterface::class);
return $this->loader ??= new ArrayLoader();
}
protected function getLogger(): LoggerInterface
{
return $this->logger ??= $this->createMock(LoggerInterface::class);
return $this->logger ??= new NullLogger();
}
protected function getDefaultLocale(): string
@@ -74,11 +77,11 @@ abstract class ProviderTestCase extends TestCase
protected function getXliffFileDumper(): XliffFileDumper
{
return $this->xliffFileDumper ??= $this->createMock(XliffFileDumper::class);
return $this->xliffFileDumper ??= new XliffFileDumper();
}
protected function getTranslatorBag(): TranslatorBagInterface
{
return $this->translatorBag ??= $this->createMock(TranslatorBagInterface::class);
return $this->translatorBag ??= new TranslatorBag();
}
}

View File

@@ -26,8 +26,13 @@ class TranslatableMessage implements TranslatableInterface
) {
}
/**
* @deprecated since Symfony 7.4
*/
public function __toString(): string
{
trigger_deprecation('symfony/translation', '7.4', 'Method "%s()" is deprecated.', __METHOD__);
return $this->getMessage();
}

View File

@@ -307,17 +307,16 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
$fallbackContent = $this->getFallbackContent($this->catalogues[$locale]);
$content = \sprintf(<<<EOF
<?php
<?php
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\MessageCatalogue;
\$catalogue = new MessageCatalogue('%s', %s);
\$catalogue = new MessageCatalogue('%s', %s);
%s
return \$catalogue;
%s
return \$catalogue;
EOF
,
EOF,
$locale,
var_export($this->getAllMessages($this->catalogues[$locale]), true),
$fallbackContent
@@ -338,11 +337,10 @@ EOF
$currentSuffix = ucfirst(preg_replace($replacementPattern, '_', $current));
$fallbackContent .= \sprintf(<<<'EOF'
$catalogue%s = new MessageCatalogue('%s', %s);
$catalogue%s->addFallbackCatalogue($catalogue%s);
$catalogue%s = new MessageCatalogue('%s', %s);
$catalogue%s->addFallbackCatalogue($catalogue%s);
EOF
,
EOF,
$fallbackSuffix,
$fallback,
var_export($this->getAllMessages($fallbackCatalogue), true),

View File

@@ -31,7 +31,6 @@ class XliffUtils
*/
public static function getVersionNumber(\DOMDocument $dom): string
{
/** @var \DOMNode $xliff */
foreach ($dom->getElementsByTagName('xliff') as $xliff) {
$version = $xliff->attributes->getNamedItem('version');
if ($version) {
@@ -83,31 +82,6 @@ class XliffUtils
return [];
}
private static function shouldEnableEntityLoader(): bool
{
static $dom, $schema;
if (null === $dom) {
$dom = new \DOMDocument();
$dom->loadXML('<?xml version="1.0"?><test/>');
$tmpfile = tempnam(sys_get_temp_dir(), 'symfony');
register_shutdown_function(static function () use ($tmpfile) {
@unlink($tmpfile);
});
$schema = '<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:include schemaLocation="file:///'.str_replace('\\', '/', $tmpfile).'" />
</xsd:schema>';
file_put_contents($tmpfile, '<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="test" type="testType" />
<xsd:complexType name="testType"/>
</xsd:schema>');
}
return !@$dom->schemaValidateSource($schema);
}
public static function getErrorsAsString(array $xmlErrors): string
{
$errorsAsString = '';
@@ -126,6 +100,44 @@ class XliffUtils
return $errorsAsString;
}
private static function shouldEnableEntityLoader(): bool
{
static $dom, $schema;
if (null === $dom) {
$dom = new \DOMDocument();
$dom->loadXML('<?xml version="1.0"?><test/>');
$tmpfile = tempnam(sys_get_temp_dir(), 'symfony');
register_shutdown_function(static function () use ($tmpfile) {
@unlink($tmpfile);
});
$schema = '<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:include schemaLocation="'.self::getFileUrl($tmpfile).'" />
</xsd:schema>';
file_put_contents($tmpfile, '<?xml version="1.0" encoding="utf-8"?>
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:element name="test" type="testType" />
<xsd:complexType name="testType"/>
</xsd:schema>');
}
return !@$dom->schemaValidateSource($schema);
}
private static function getFileUrl(string $path): string
{
if ('\\' === \DIRECTORY_SEPARATOR) {
$parts = explode('/', str_replace('\\', '/', $path));
$drive = array_shift($parts).'/';
} else {
$parts = explode('/', $path);
$drive = '';
}
return 'file:///'.$drive.implode('/', array_map('rawurlencode', $parts));
}
private static function getSchema(string $xliffVersion): string
{
if ('1.2' === $xliffVersion) {
@@ -146,22 +158,21 @@ class XliffUtils
*/
private static function fixXmlLocation(string $schemaSource, string $xmlUri): string
{
$newPath = str_replace('\\', '/', __DIR__).'/../Resources/schemas/xml.xsd';
$parts = explode('/', $newPath);
$locationstart = 'file:///';
if (0 === stripos($newPath, 'phar://')) {
$tmpfile = tempnam(sys_get_temp_dir(), 'symfony');
if ($tmpfile) {
copy($newPath, $tmpfile);
$parts = explode('/', str_replace('\\', '/', $tmpfile));
} else {
array_shift($parts);
$locationstart = 'phar:///';
}
}
$path = __DIR__.'/../Resources/schemas/xml.xsd';
$drive = '\\' === \DIRECTORY_SEPARATOR ? array_shift($parts).'/' : '';
$newPath = $locationstart.$drive.implode('/', array_map('rawurlencode', $parts));
if (0 === stripos($path, 'phar://')) {
if ($tmpfile = tempnam(sys_get_temp_dir(), 'symfony')) {
copy($path, $tmpfile);
$newPath = self::getFileUrl($tmpfile);
} else {
$parts = explode('/', '\\' === \DIRECTORY_SEPARATOR ? str_replace('\\', '/', $path) : $path);
array_shift($parts);
$drive = '\\' === \DIRECTORY_SEPARATOR ? array_shift($parts).'/' : '';
$newPath = 'phar:///'.$drive.implode('/', array_map('rawurlencode', $parts));
}
} else {
$newPath = self::getFileUrl($path);
}
return str_replace($xmlUri, $newPath, $schemaSource);
}

View File

@@ -61,7 +61,7 @@ class TranslationWriter implements TranslationWriterInterface
// get the right dumper
$dumper = $this->dumpers[$format];
if (isset($options['path']) && !is_dir($options['path']) && !@mkdir($options['path'], 0777, true) && !is_dir($options['path'])) {
if (isset($options['path']) && !is_dir($options['path']) && !@mkdir($options['path'], 0o777, true) && !is_dir($options['path'])) {
throw new RuntimeException(\sprintf('Translation Writer was not able to create directory "%s".', $options['path']));
}

View File

@@ -18,22 +18,22 @@
"require": {
"php": ">=8.2",
"symfony/polyfill-mbstring": "~1.0",
"symfony/translation-contracts": "^2.5|^3.0",
"symfony/translation-contracts": "^2.5.3|^3.3",
"symfony/deprecation-contracts": "^2.5|^3"
},
"require-dev": {
"nikic/php-parser": "^5.0",
"symfony/config": "^6.4|^7.0",
"symfony/console": "^6.4|^7.0",
"symfony/dependency-injection": "^6.4|^7.0",
"symfony/config": "^6.4|^7.0|^8.0",
"symfony/console": "^6.4|^7.0|^8.0",
"symfony/dependency-injection": "^6.4|^7.0|^8.0",
"symfony/http-client-contracts": "^2.5|^3.0",
"symfony/http-kernel": "^6.4|^7.0",
"symfony/intl": "^6.4|^7.0",
"symfony/http-kernel": "^6.4|^7.0|^8.0",
"symfony/intl": "^6.4|^7.0|^8.0",
"symfony/polyfill-intl-icu": "^1.21",
"symfony/routing": "^6.4|^7.0",
"symfony/routing": "^6.4|^7.0|^8.0",
"symfony/service-contracts": "^2.5|^3",
"symfony/yaml": "^6.4|^7.0",
"symfony/finder": "^6.4|^7.0",
"symfony/yaml": "^6.4|^7.0|^8.0",
"symfony/finder": "^6.4|^7.0|^8.0",
"psr/log": "^1|^2|^3"
},
"conflict": {