[Web] Update composer deps

This commit is contained in:
andryyy
2021-06-23 08:05:09 +02:00
parent d156a93a84
commit 5035e5bb42
136 changed files with 3020 additions and 805 deletions

View File

@@ -0,0 +1,3 @@
vendor/
composer.lock
phpunit.xml

View File

@@ -0,0 +1,5 @@
CHANGELOG
=========
The changelog is maintained for all Symfony contracts at the following URL:
https://github.com/symfony/contracts/blob/main/CHANGELOG.md

View File

@@ -0,0 +1,19 @@
Copyright (c) 2020-2021 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,26 @@
Symfony Deprecation Contracts
=============================
A generic function and convention to trigger deprecation notices.
This package provides a single global function named `trigger_deprecation()` that triggers silenced deprecation notices.
By using a custom PHP error handler such as the one provided by the Symfony ErrorHandler component,
the triggered deprecations can be caught and logged for later discovery, both on dev and prod environments.
The function requires at least 3 arguments:
- the name of the Composer package that is triggering the deprecation
- the version of the package that introduced the deprecation
- the message of the deprecation
- more arguments can be provided: they will be inserted in the message using `printf()` formatting
Example:
```php
trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use "%s" instead.', 'bitcoin', 'fabcoin');
```
This will generate the following message:
`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.`
While not necessarily recommended, the deprecation notices can be completely ignored by declaring an empty
`function trigger_deprecation() {}` in your application.

View File

@@ -0,0 +1,35 @@
{
"name": "symfony/deprecation-contracts",
"type": "library",
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=7.1"
},
"autoload": {
"files": [
"function.php"
]
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-main": "2.4-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
}
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
if (!function_exists('trigger_deprecation')) {
/**
* Triggers a silenced deprecation notice.
*
* @param string $package The name of the Composer package that is triggering the deprecation
* @param string $version The version of the package that introduced the deprecation
* @param string $message The message of the deprecation
* @param mixed ...$args Values to insert in the message using printf() formatting
*
* @author Nicolas Grekas <p@tchwork.com>
*/
function trigger_deprecation(string $package, string $version, string $message, ...$args): void
{
@trigger_error(($package || $version ? "Since $package $version: " : '').($args ? vsprintf($message, $args) : $message), \E_USER_DEPRECATED);
}
}

View File

@@ -101,7 +101,7 @@ final class Mbstring
$fromEncoding = 'Windows-1252';
}
if ('UTF-8' !== $fromEncoding) {
$s = iconv($fromEncoding, 'UTF-8//IGNORE', $s);
$s = \iconv($fromEncoding, 'UTF-8//IGNORE', $s);
}
return preg_replace_callback('/[\x80-\xFF]+/', [__CLASS__, 'html_encoding_callback'], $s);
@@ -112,7 +112,7 @@ final class Mbstring
$fromEncoding = 'UTF-8';
}
return iconv($fromEncoding, $toEncoding.'//IGNORE', $s);
return \iconv($fromEncoding, $toEncoding.'//IGNORE', $s);
}
public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars)
@@ -129,7 +129,7 @@ final class Mbstring
public static function mb_decode_mimeheader($s)
{
return iconv_mime_decode($s, 2, self::$internalEncoding);
return \iconv_mime_decode($s, 2, self::$internalEncoding);
}
public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null)
@@ -165,10 +165,10 @@ final class Mbstring
if ('UTF-8' === $encoding) {
$encoding = null;
if (!preg_match('//u', $s)) {
$s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
$s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s);
}
} else {
$s = iconv($encoding, 'UTF-8//IGNORE', $s);
$s = \iconv($encoding, 'UTF-8//IGNORE', $s);
}
$cnt = floor(\count($convmap) / 4) * 4;
@@ -194,7 +194,7 @@ final class Mbstring
return $s;
}
return iconv('UTF-8', $encoding.'//IGNORE', $s);
return \iconv('UTF-8', $encoding.'//IGNORE', $s);
}
public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false)
@@ -231,10 +231,10 @@ final class Mbstring
if ('UTF-8' === $encoding) {
$encoding = null;
if (!preg_match('//u', $s)) {
$s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
$s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s);
}
} else {
$s = iconv($encoding, 'UTF-8//IGNORE', $s);
$s = \iconv($encoding, 'UTF-8//IGNORE', $s);
}
static $ulenMask = ["\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4];
@@ -264,7 +264,7 @@ final class Mbstring
return $result;
}
return iconv('UTF-8', $encoding.'//IGNORE', $result);
return \iconv('UTF-8', $encoding.'//IGNORE', $result);
}
public static function mb_convert_case($s, $mode, $encoding = null)
@@ -279,10 +279,10 @@ final class Mbstring
if ('UTF-8' === $encoding) {
$encoding = null;
if (!preg_match('//u', $s)) {
$s = @iconv('UTF-8', 'UTF-8//IGNORE', $s);
$s = @\iconv('UTF-8', 'UTF-8//IGNORE', $s);
}
} else {
$s = iconv($encoding, 'UTF-8//IGNORE', $s);
$s = \iconv($encoding, 'UTF-8//IGNORE', $s);
}
if (\MB_CASE_TITLE == $mode) {
@@ -342,7 +342,7 @@ final class Mbstring
return $s;
}
return iconv('UTF-8', $encoding.'//IGNORE', $s);
return \iconv('UTF-8', $encoding.'//IGNORE', $s);
}
public static function mb_internal_encoding($encoding = null)
@@ -353,7 +353,7 @@ final class Mbstring
$normalizedEncoding = self::getEncoding($encoding);
if ('UTF-8' === $normalizedEncoding || false !== @iconv($normalizedEncoding, $normalizedEncoding, ' ')) {
if ('UTF-8' === $normalizedEncoding || false !== @\iconv($normalizedEncoding, $normalizedEncoding, ' ')) {
self::$internalEncoding = $normalizedEncoding;
return true;
@@ -412,7 +412,7 @@ final class Mbstring
$encoding = self::$internalEncoding;
}
return self::mb_detect_encoding($var, [$encoding]) || false !== @iconv($encoding, $encoding, $var);
return self::mb_detect_encoding($var, [$encoding]) || false !== @\iconv($encoding, $encoding, $var);
}
public static function mb_detect_encoding($str, $encodingList = null, $strict = false)
@@ -487,7 +487,7 @@ final class Mbstring
return \strlen($s);
}
return @iconv_strlen($s, $encoding);
return @\iconv_strlen($s, $encoding);
}
public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null)
@@ -508,7 +508,7 @@ final class Mbstring
return 0;
}
return iconv_strpos($haystack, $needle, $offset, $encoding);
return \iconv_strpos($haystack, $needle, $offset, $encoding);
}
public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null)
@@ -532,7 +532,7 @@ final class Mbstring
}
$pos = '' !== $needle || 80000 > \PHP_VERSION_ID
? iconv_strrpos($haystack, $needle, $encoding)
? \iconv_strrpos($haystack, $needle, $encoding)
: self::mb_strlen($haystack, $encoding);
return false !== $pos ? $offset + $pos : false;
@@ -613,7 +613,7 @@ final class Mbstring
}
if ($start < 0) {
$start = iconv_strlen($s, $encoding) + $start;
$start = \iconv_strlen($s, $encoding) + $start;
if ($start < 0) {
$start = 0;
}
@@ -622,13 +622,13 @@ final class Mbstring
if (null === $length) {
$length = 2147483647;
} elseif ($length < 0) {
$length = iconv_strlen($s, $encoding) + $length - $start;
$length = \iconv_strlen($s, $encoding) + $length - $start;
if ($length < 0) {
return '';
}
}
return (string) iconv_substr($s, $start, $length, $encoding);
return (string) \iconv_substr($s, $start, $length, $encoding);
}
public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null)
@@ -653,7 +653,7 @@ final class Mbstring
$pos = strrpos($haystack, $needle);
} else {
$needle = self::mb_substr($needle, 0, 1, $encoding);
$pos = iconv_strrpos($haystack, $needle, $encoding);
$pos = \iconv_strrpos($haystack, $needle, $encoding);
}
return self::getSubpart($pos, $part, $haystack, $encoding);
@@ -732,12 +732,12 @@ final class Mbstring
$encoding = self::getEncoding($encoding);
if ('UTF-8' !== $encoding) {
$s = iconv($encoding, 'UTF-8//IGNORE', $s);
$s = \iconv($encoding, 'UTF-8//IGNORE', $s);
}
$s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide);
return ($wide << 1) + iconv_strlen($s, 'UTF-8');
return ($wide << 1) + \iconv_strlen($s, 'UTF-8');
}
public static function mb_substr_count($haystack, $needle, $encoding = null)

View File

@@ -81,7 +81,7 @@ return array (
'Ī' => 'ī',
'Ĭ' => 'ĭ',
'Į' => 'į',
'İ' => 'i',
'İ' => 'i̇',
'IJ' => 'ij',
'Ĵ' => 'ĵ',
'Ķ' => 'ķ',

View File

@@ -746,41 +746,41 @@ return array (
'ύ' => 'Ύ',
'ὼ' => 'Ὼ',
'ώ' => 'Ώ',
'ᾀ' => '',
'ᾁ' => '',
'ᾂ' => '',
'ᾃ' => '',
'ᾄ' => '',
'ᾅ' => '',
'ᾆ' => '',
'ᾇ' => '',
'ᾐ' => '',
'ᾑ' => '',
'ᾒ' => '',
'ᾓ' => '',
'ᾔ' => '',
'ᾕ' => '',
'ᾖ' => '',
'ᾗ' => '',
'ᾠ' => '',
'ᾡ' => '',
'ᾢ' => '',
'ᾣ' => '',
'ᾤ' => '',
'ᾥ' => '',
'ᾦ' => '',
'ᾧ' => '',
'ᾀ' => 'ἈΙ',
'ᾁ' => 'ἉΙ',
'ᾂ' => 'ἊΙ',
'ᾃ' => 'ἋΙ',
'ᾄ' => 'ἌΙ',
'ᾅ' => 'ἍΙ',
'ᾆ' => 'ἎΙ',
'ᾇ' => 'ἏΙ',
'ᾐ' => 'ἨΙ',
'ᾑ' => 'ἩΙ',
'ᾒ' => 'ἪΙ',
'ᾓ' => 'ἫΙ',
'ᾔ' => 'ἬΙ',
'ᾕ' => 'ἭΙ',
'ᾖ' => 'ἮΙ',
'ᾗ' => 'ἯΙ',
'ᾠ' => 'ὨΙ',
'ᾡ' => 'ὩΙ',
'ᾢ' => 'ὪΙ',
'ᾣ' => 'ὫΙ',
'ᾤ' => 'ὬΙ',
'ᾥ' => 'ὭΙ',
'ᾦ' => 'ὮΙ',
'ᾧ' => 'ὯΙ',
'ᾰ' => 'Ᾰ',
'ᾱ' => 'Ᾱ',
'ᾳ' => '',
'ᾳ' => 'ΑΙ',
'' => 'Ι',
'ῃ' => '',
'ῃ' => 'ΗΙ',
'ῐ' => 'Ῐ',
'ῑ' => 'Ῑ',
'ῠ' => 'Ῠ',
'ῡ' => 'Ῡ',
'ῥ' => 'Ῥ',
'ῳ' => '',
'ῳ' => 'ΩΙ',
'ⅎ' => 'Ⅎ',
'' => '',
'ⅱ' => 'Ⅱ',
@@ -1411,4 +1411,79 @@ return array (
'𞥁' => '𞤟',
'𞥂' => '𞤠',
'𞥃' => '𞤡',
'ß' => 'SS',
'ff' => 'FF',
'fi' => 'FI',
'fl' => 'FL',
'ffi' => 'FFI',
'ffl' => 'FFL',
'ſt' => 'ST',
'st' => 'ST',
'և' => 'ԵՒ',
'ﬓ' => 'ՄՆ',
'ﬔ' => 'ՄԵ',
'ﬕ' => 'ՄԻ',
'ﬖ' => 'ՎՆ',
'ﬗ' => 'ՄԽ',
'ʼn' => 'ʼN',
'ΐ' => 'Ϊ́',
'ΰ' => 'Ϋ́',
'ǰ' => 'J̌',
'ẖ' => 'H̱',
'ẗ' => 'T̈',
'ẘ' => 'W̊',
'ẙ' => 'Y̊',
'ẚ' => 'Aʾ',
'ὐ' => 'Υ̓',
'ὒ' => 'Υ̓̀',
'ὔ' => 'Υ̓́',
'ὖ' => 'Υ̓͂',
'ᾶ' => 'Α͂',
'ῆ' => 'Η͂',
'ῒ' => 'Ϊ̀',
'ΐ' => 'Ϊ́',
'ῖ' => 'Ι͂',
'ῗ' => 'Ϊ͂',
'ῢ' => 'Ϋ̀',
'ΰ' => 'Ϋ́',
'ῤ' => 'Ρ̓',
'ῦ' => 'Υ͂',
'ῧ' => 'Ϋ͂',
'ῶ' => 'Ω͂',
'ᾈ' => 'ἈΙ',
'ᾉ' => 'ἉΙ',
'ᾊ' => 'ἊΙ',
'ᾋ' => 'ἋΙ',
'ᾌ' => 'ἌΙ',
'ᾍ' => 'ἍΙ',
'ᾎ' => 'ἎΙ',
'ᾏ' => 'ἏΙ',
'ᾘ' => 'ἨΙ',
'ᾙ' => 'ἩΙ',
'ᾚ' => 'ἪΙ',
'ᾛ' => 'ἫΙ',
'ᾜ' => 'ἬΙ',
'ᾝ' => 'ἭΙ',
'ᾞ' => 'ἮΙ',
'ᾟ' => 'ἯΙ',
'ᾨ' => 'ὨΙ',
'ᾩ' => 'ὩΙ',
'ᾪ' => 'ὪΙ',
'ᾫ' => 'ὫΙ',
'ᾬ' => 'ὬΙ',
'ᾭ' => 'ὭΙ',
'ᾮ' => 'ὮΙ',
'ᾯ' => 'ὯΙ',
'ᾼ' => 'ΑΙ',
'ῌ' => 'ΗΙ',
'ῼ' => 'ΩΙ',
'ᾲ' => 'ᾺΙ',
'ᾴ' => 'ΆΙ',
'ῂ' => 'ῊΙ',
'ῄ' => 'ΉΙ',
'ῲ' => 'ῺΙ',
'ῴ' => 'ΏΙ',
'ᾷ' => 'Α͂Ι',
'ῇ' => 'Η͂Ι',
'ῷ' => 'Ω͂Ι',
);

View File

@@ -55,7 +55,7 @@ if (!function_exists('mb_detect_order')) {
function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); }
}
if (!function_exists('mb_parse_str')) {
function mb_parse_str($string, &$result = []) { parse_str($string, $result); }
function mb_parse_str($string, &$result = []) { parse_str($string, $result); return (bool) $result; }
}
if (!function_exists('mb_strlen')) {
function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); }

View File

@@ -48,10 +48,10 @@ if (!function_exists('mb_detect_encoding')) {
function mb_detect_encoding(?string $string, array|string|null $encodings = null, ?bool $strict = false): string|false { return p\Mbstring::mb_detect_encoding((string) $string, $encodings, (bool) $strict); }
}
if (!function_exists('mb_detect_order')) {
function mb_detect_order(array|string|null $encoding = null): array|bool { return p\Mbstring::mb_detect_order((string) $encoding); }
function mb_detect_order(array|string|null $encoding = null): array|bool { return p\Mbstring::mb_detect_order($encoding); }
}
if (!function_exists('mb_parse_str')) {
function mb_parse_str(?string $string, &$result = []): bool { parse_str((string) $string, $result); }
function mb_parse_str(?string $string, &$result = []): bool { parse_str((string) $string, $result); return (bool) $result; }
}
if (!function_exists('mb_strlen')) {
function mb_strlen(?string $string, ?string $encoding = null): int { return p\Mbstring::mb_strlen((string) $string, $encoding); }
@@ -78,7 +78,7 @@ if (!function_exists('mb_stristr')) {
function mb_stristr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_stristr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); }
}
if (!function_exists('mb_strrchr')) {
function mb_strrchr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrchr((string) $haystack, (string) $needle, $before_needle, (bool) $encoding); }
function mb_strrchr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrchr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); }
}
if (!function_exists('mb_strrichr')) {
function mb_strrichr(?string $haystack, ?string $needle, ?bool $before_needle = false, ?string $encoding = null): string|false { return p\Mbstring::mb_strrichr((string) $haystack, (string) $needle, (bool) $before_needle, $encoding); }

View File

@@ -28,7 +28,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-main": "1.22-dev"
"dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",

View File

@@ -30,7 +30,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-main": "1.22-dev"
"dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",

View File

@@ -1,6 +1,13 @@
CHANGELOG
=========
5.3
---
* Add `translation:pull` and `translation:push` commands to manage translations with third-party providers
* Add `TranslatorBagInterface::getCatalogues` method
* Add support to load XLIFF string in `XliffFileLoader`
5.2.0
-----

View File

@@ -26,6 +26,10 @@ use Symfony\Component\Translation\MessageCatalogueInterface;
*/
abstract class AbstractOperation implements OperationInterface
{
public const OBSOLETE_BATCH = 'obsolete';
public const NEW_BATCH = 'new';
public const ALL_BATCH = 'all';
protected $source;
protected $target;
protected $result;
@@ -94,11 +98,11 @@ abstract class AbstractOperation implements OperationInterface
throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain));
}
if (!isset($this->messages[$domain]['all'])) {
if (!isset($this->messages[$domain][self::ALL_BATCH])) {
$this->processDomain($domain);
}
return $this->messages[$domain]['all'];
return $this->messages[$domain][self::ALL_BATCH];
}
/**
@@ -110,11 +114,11 @@ abstract class AbstractOperation implements OperationInterface
throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain));
}
if (!isset($this->messages[$domain]['new'])) {
if (!isset($this->messages[$domain][self::NEW_BATCH])) {
$this->processDomain($domain);
}
return $this->messages[$domain]['new'];
return $this->messages[$domain][self::NEW_BATCH];
}
/**
@@ -126,11 +130,11 @@ abstract class AbstractOperation implements OperationInterface
throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain));
}
if (!isset($this->messages[$domain]['obsolete'])) {
if (!isset($this->messages[$domain][self::OBSOLETE_BATCH])) {
$this->processDomain($domain);
}
return $this->messages[$domain]['obsolete'];
return $this->messages[$domain][self::OBSOLETE_BATCH];
}
/**
@@ -147,6 +151,37 @@ abstract class AbstractOperation implements OperationInterface
return $this->result;
}
/**
* @param self::*_BATCH $batch
*/
public function moveMessagesToIntlDomainsIfPossible(string $batch = self::ALL_BATCH): void
{
// If MessageFormatter class does not exists, intl domains are not supported.
if (!class_exists(\MessageFormatter::class)) {
return;
}
foreach ($this->getDomains() as $domain) {
$intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX;
switch ($batch) {
case self::OBSOLETE_BATCH: $messages = $this->getObsoleteMessages($domain); break;
case self::NEW_BATCH: $messages = $this->getNewMessages($domain); break;
case self::ALL_BATCH: $messages = $this->getMessages($domain); break;
default: throw new \InvalidArgumentException(sprintf('$batch argument must be one of ["%s", "%s", "%s"].', self::ALL_BATCH, self::NEW_BATCH, self::OBSOLETE_BATCH));
}
if (!$messages || (!$this->source->all($intlDomain) && $this->source->all($domain))) {
continue;
}
$result = $this->getResult();
$allIntlMessages = $result->all($intlDomain);
$currentMessages = array_diff_key($messages, $result->all($domain));
$result->replace($currentMessages, $domain);
$result->replace($allIntlMessages + $messages, $intlDomain);
}
}
/**
* Performs operation on source and target catalogues for the given domain and
* stores the results.

View File

@@ -0,0 +1,157 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Translation\Catalogue\TargetOperation;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Provider\TranslationProviderCollection;
use Symfony\Component\Translation\Reader\TranslationReaderInterface;
use Symfony\Component\Translation\Writer\TranslationWriterInterface;
/**
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*
* @experimental in 5.3
*/
final class TranslationPullCommand extends Command
{
use TranslationTrait;
protected static $defaultName = 'translation:pull';
protected static $defaultDescription = 'Pull translations from a given provider.';
private $providerCollection;
private $writer;
private $reader;
private $defaultLocale;
private $transPaths;
private $enabledLocales;
public function __construct(TranslationProviderCollection $providerCollection, TranslationWriterInterface $writer, TranslationReaderInterface $reader, string $defaultLocale, array $transPaths = [], array $enabledLocales = [])
{
$this->providerCollection = $providerCollection;
$this->writer = $writer;
$this->reader = $reader;
$this->defaultLocale = $defaultLocale;
$this->transPaths = $transPaths;
$this->enabledLocales = $enabledLocales;
parent::__construct();
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$keys = $this->providerCollection->keys();
$defaultProvider = 1 === \count($keys) ? $keys[0] : null;
$this
->setDefinition([
new InputArgument('provider', null !== $defaultProvider ? InputArgument::OPTIONAL : InputArgument::REQUIRED, 'The provider to pull translations from.', $defaultProvider),
new InputOption('force', null, InputOption::VALUE_NONE, 'Override existing translations with provider ones (it will delete not synchronized messages).'),
new InputOption('intl-icu', null, InputOption::VALUE_NONE, 'Associated to --force option, it will write messages in "%domain%+intl-icu.%locale%.xlf" files.'),
new InputOption('domains', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the domains to pull.'),
new InputOption('locales', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the locales to pull.'),
new InputOption('format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format.', 'xlf12'),
])
->setHelp(<<<'EOF'
The <info>%command.name%</> command pulls translations from the given provider. Only
new translations are pulled, existing ones are not overwritten.
You can overwrite existing translations (and remove the missing ones on local side) by using the <comment>--force</> flag:
<info>php %command.full_name% --force provider</>
Full example:
<info>php %command.full_name% provider --force --domains=messages,validators --locales=en</>
This command pulls all translations associated with the <comment>messages</> and <comment>validators</> domains for the <comment>en</> locale.
Local translations for the specified domains and locale are deleted if they're not present on the provider and overwritten if it's the case.
Local translations for others domains and locales are ignored.
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$provider = $this->providerCollection->get($input->getArgument('provider'));
$force = $input->getOption('force');
$intlIcu = $input->getOption('intl-icu');
$locales = $input->getOption('locales') ?: $this->enabledLocales;
$domains = $input->getOption('domains');
$format = $input->getOption('format');
$xliffVersion = '1.2';
if ($intlIcu && !$force) {
$io->note('--intl-icu option only has an effect when used with --force. Here, it will be ignored.');
}
switch ($format) {
case 'xlf20': $xliffVersion = '2.0';
// no break
case 'xlf12': $format = 'xlf';
}
$writeOptions = [
'path' => end($this->transPaths),
'xliff_version' => $xliffVersion,
];
if (!$domains) {
$domains = $provider->getDomains();
}
$providerTranslations = $provider->read($domains, $locales);
if ($force) {
foreach ($providerTranslations->getCatalogues() as $catalogue) {
$operation = new TargetOperation((new MessageCatalogue($catalogue->getLocale())), $catalogue);
if ($intlIcu) {
$operation->moveMessagesToIntlDomainsIfPossible();
}
$this->writer->write($operation->getResult(), $format, $writeOptions);
}
$io->success(sprintf('Local translations has been updated from "%s" (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains)));
return 0;
}
$localTranslations = $this->readLocalTranslations($locales, $domains, $this->transPaths);
// Append pulled translations to local ones.
$localTranslations->addBag($providerTranslations->diff($localTranslations));
foreach ($localTranslations->getCatalogues() as $catalogue) {
$this->writer->write($catalogue, $format, $writeOptions);
}
$io->success(sprintf('New translations from "%s" has been written locally (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains)));
return 0;
}
}

View File

@@ -0,0 +1,158 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Translation\Provider\TranslationProviderCollection;
use Symfony\Component\Translation\Reader\TranslationReaderInterface;
use Symfony\Component\Translation\TranslatorBag;
/**
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*
* @experimental in 5.3
*/
final class TranslationPushCommand extends Command
{
use TranslationTrait;
protected static $defaultName = 'translation:push';
protected static $defaultDescription = 'Push translations to a given provider.';
private $providers;
private $reader;
private $transPaths;
private $enabledLocales;
public function __construct(TranslationProviderCollection $providers, TranslationReaderInterface $reader, array $transPaths = [], array $enabledLocales = [])
{
$this->providers = $providers;
$this->reader = $reader;
$this->transPaths = $transPaths;
$this->enabledLocales = $enabledLocales;
parent::__construct();
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$keys = $this->providers->keys();
$defaultProvider = 1 === \count($keys) ? $keys[0] : null;
$this
->setDefinition([
new InputArgument('provider', null !== $defaultProvider ? InputArgument::OPTIONAL : InputArgument::REQUIRED, 'The provider to push translations to.', $defaultProvider),
new InputOption('force', null, InputOption::VALUE_NONE, 'Override existing translations with local ones (it will delete not synchronized messages).'),
new InputOption('delete-missing', null, InputOption::VALUE_NONE, 'Delete translations available on provider but not locally.'),
new InputOption('domains', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the domains to push.'),
new InputOption('locales', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the locales to push.', $this->enabledLocales),
])
->setHelp(<<<'EOF'
The <info>%command.name%</> command pushes translations to the given provider. Only new
translations are pushed, existing ones are not overwritten.
You can overwrite existing translations by using the <comment>--force</> flag:
<info>php %command.full_name% --force provider</>
You can delete provider translations which are not present locally by using the <comment>--delete-missing</> flag:
<info>php %command.full_name% --delete-missing provider</>
Full example:
<info>php %command.full_name% provider --force --delete-missing --domains=messages,validators --locales=en</>
This command pushes all translations associated with the <comment>messages</> and <comment>validators</> domains for the <comment>en</> locale.
Provider translations for the specified domains and locale are deleted if they're not present locally and overwritten if it's the case.
Provider translations for others domains and locales are ignored.
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
if (!$this->enabledLocales) {
throw new InvalidArgumentException('You must define "framework.translator.enabled_locales" or "framework.translator.providers.%s.locales" config key in order to work with translation providers.');
}
$io = new SymfonyStyle($input, $output);
$provider = $this->providers->get($input->getArgument('provider'));
$domains = $input->getOption('domains');
$locales = $input->getOption('locales');
$force = $input->getOption('force');
$deleteMissing = $input->getOption('delete-missing');
$localTranslations = $this->readLocalTranslations($locales, $domains, $this->transPaths);
if (!$domains) {
$domains = $this->getDomainsFromTranslatorBag($localTranslations);
}
if (!$deleteMissing && $force) {
$provider->write($localTranslations);
$io->success(sprintf('All local translations has been sent to "%s" (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains)));
return 0;
}
$providerTranslations = $provider->read($domains, $locales);
if ($deleteMissing) {
$provider->delete($providerTranslations->diff($localTranslations));
$io->success(sprintf('Missing translations on "%s" has been deleted (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains)));
// Read provider translations again, after missing translations deletion,
// to avoid push freshly deleted translations.
$providerTranslations = $provider->read($domains, $locales);
}
$translationsToWrite = $localTranslations->diff($providerTranslations);
if ($force) {
$translationsToWrite->addBag($localTranslations->intersect($providerTranslations));
}
$provider->write($translationsToWrite);
$io->success(sprintf('%s local translations has been sent to "%s" (for "%s" locale(s), and "%s" domain(s)).', $force ? 'All' : 'New', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains)));
return 0;
}
private function getDomainsFromTranslatorBag(TranslatorBag $translatorBag): array
{
$domains = [];
foreach ($translatorBag->getCatalogues() as $catalogue) {
$domains += $catalogue->getDomains();
}
return array_unique($domains);
}
}

View File

@@ -0,0 +1,78 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Command;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\MessageCatalogueInterface;
use Symfony\Component\Translation\TranslatorBag;
/**
* @internal
*/
trait TranslationTrait
{
private function readLocalTranslations(array $locales, array $domains, array $transPaths): TranslatorBag
{
$bag = new TranslatorBag();
foreach ($locales as $locale) {
$catalogue = new MessageCatalogue($locale);
foreach ($transPaths as $path) {
$this->reader->read($path, $catalogue);
}
if ($domains) {
foreach ($domains as $domain) {
$catalogue = $this->filterCatalogue($catalogue, $domain);
$bag->addCatalogue($catalogue);
}
} else {
$bag->addCatalogue($catalogue);
}
}
return $bag;
}
private function filterCatalogue(MessageCatalogue $catalogue, string $domain): MessageCatalogue
{
$filteredCatalogue = new MessageCatalogue($catalogue->getLocale());
// extract intl-icu messages only
$intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX;
if ($intlMessages = $catalogue->all($intlDomain)) {
$filteredCatalogue->add($intlMessages, $intlDomain);
}
// extract all messages and subtract intl-icu messages
if ($messages = array_diff($catalogue->all($domain), $intlMessages)) {
$filteredCatalogue->add($messages, $domain);
}
foreach ($catalogue->getResources() as $resource) {
$filteredCatalogue->addResource($resource);
}
if ($metadata = $catalogue->getMetadata('', $intlDomain)) {
foreach ($metadata as $k => $v) {
$filteredCatalogue->setMetadata($k, $v, $intlDomain);
}
}
if ($metadata = $catalogue->getMetadata('', $domain)) {
foreach ($metadata as $k => $v) {
$filteredCatalogue->setMetadata($k, $v, $domain);
}
}
return $filteredCatalogue;
}
}

View File

@@ -31,6 +31,7 @@ use Symfony\Component\Translation\Util\XliffUtils;
class XliffLintCommand extends Command
{
protected static $defaultName = 'lint:xliff';
protected static $defaultDescription = 'Lint an XLIFF file and outputs encountered errors';
private $format;
private $displayCorrectFiles;
@@ -53,7 +54,7 @@ class XliffLintCommand extends Command
protected function configure()
{
$this
->setDescription('Lint an XLIFF file and outputs encountered errors')
->setDescription(self::$defaultDescription)
->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN')
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt')
->setHelp(<<<EOF

View File

@@ -79,6 +79,14 @@ class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInter
return $this->translator->getCatalogue($locale);
}
/**
* {@inheritdoc}
*/
public function getCatalogues(): array
{
return $this->translator->getCatalogues();
}
/**
* {@inheritdoc}
*

View File

@@ -25,6 +25,10 @@ class TranslationDumperPass implements CompilerPassInterface
public function __construct(string $writerServiceId = 'translation.writer', string $dumperTag = 'translation.dumper')
{
if (1 < \func_num_args()) {
trigger_deprecation('symfony/translation', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
}
$this->writerServiceId = $writerServiceId;
$this->dumperTag = $dumperTag;
}

View File

@@ -26,6 +26,10 @@ class TranslationExtractorPass implements CompilerPassInterface
public function __construct(string $extractorServiceId = 'translation.extractor', string $extractorTag = 'translation.extractor')
{
if (0 < \func_num_args()) {
trigger_deprecation('symfony/translation', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
}
$this->extractorServiceId = $extractorServiceId;
$this->extractorTag = $extractorTag;
}

View File

@@ -26,6 +26,10 @@ class TranslatorPass implements CompilerPassInterface
public function __construct(string $translatorServiceId = 'translator.default', string $readerServiceId = 'translation.reader', string $loaderTag = 'translation.loader', string $debugCommandServiceId = 'console.command.translation_debug', string $updateCommandServiceId = 'console.command.translation_update')
{
if (0 < \func_num_args()) {
trigger_deprecation('symfony/translation', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
}
$this->translatorServiceId = $translatorServiceId;
$this->readerServiceId = $readerServiceId;
$this->loaderTag = $loaderTag;

View File

@@ -33,6 +33,10 @@ class TranslatorPathsPass extends AbstractRecursivePass
public function __construct(string $translatorServiceId = 'translator', string $debugCommandServiceId = 'console.command.translation_debug', string $updateCommandServiceId = 'console.command.translation_update', string $resolverServiceId = 'argument_resolver.service')
{
if (0 < \func_num_args()) {
trigger_deprecation('symfony/translation', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
}
$this->translatorServiceId = $translatorServiceId;
$this->debugCommandServiceId = $debugCommandServiceId;
$this->updateCommandServiceId = $updateCommandServiceId;

View File

@@ -0,0 +1,24 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Exception;
class IncompleteDsnException extends InvalidArgumentException
{
public function __construct(string $message, string $dsn = null, ?\Throwable $previous = null)
{
if ($dsn) {
$message = sprintf('Invalid "%s" provider DSN: ', $dsn).$message;
}
parent::__construct($message, 0, $previous);
}
}

View File

@@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Exception;
/**
* @author Oskar Stark <oskarstark@googlemail.com>
*/
class MissingRequiredOptionException extends IncompleteDsnException
{
public function __construct(string $option, string $dsn = null, ?\Throwable $previous = null)
{
$message = sprintf('The option "%s" is required but missing.', $option);
parent::__construct($message, $dsn, $previous);
}
}

View File

@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Exception;
use Symfony\Contracts\HttpClient\ResponseInterface;
/**
* @author Fabien Potencier <fabien@symfony.com>
*
* @experimental in 5.3
*/
class ProviderException extends RuntimeException implements ProviderExceptionInterface
{
private $response;
private $debug;
public function __construct(string $message, ResponseInterface $response, int $code = 0, \Exception $previous = null)
{
$this->response = $response;
$this->debug .= $response->getInfo('debug') ?? '';
parent::__construct($message, $code, $previous);
}
public function getResponse(): ResponseInterface
{
return $this->response;
}
public function getDebug(): string
{
return $this->debug;
}
}

View File

@@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Exception;
/**
* @author Fabien Potencier <fabien@symfony.com>
*
* @experimental in 5.3
*/
interface ProviderExceptionInterface extends ExceptionInterface
{
/*
* Returns debug info coming from the Symfony\Contracts\HttpClient\ResponseInterface
*/
public function getDebug(): string;
}

View File

@@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Exception;
use Symfony\Component\Translation\Bridge;
use Symfony\Component\Translation\Provider\Dsn;
class UnsupportedSchemeException extends LogicException
{
private const SCHEME_TO_PACKAGE_MAP = [
'crowdin' => [
'class' => Bridge\Crowdin\CrowdinProviderFactory::class,
'package' => 'symfony/crowdin-translation-provider',
],
'loco' => [
'class' => Bridge\Loco\LocoProviderFactory::class,
'package' => 'symfony/loco-translation-provider',
],
'lokalise' => [
'class' => Bridge\Lokalise\LokaliseProviderFactory::class,
'package' => 'symfony/lokalise-translation-provider',
],
];
public function __construct(Dsn $dsn, string $name = null, array $supported = [])
{
$provider = $dsn->getScheme();
if (false !== $pos = strpos($provider, '+')) {
$provider = substr($provider, 0, $pos);
}
$package = self::SCHEME_TO_PACKAGE_MAP[$provider] ?? null;
if ($package && !class_exists($package['class'])) {
parent::__construct(sprintf('Unable to synchronize translations via "%s" as the provider is not installed; try running "composer require %s".', $provider, $package['package']));
return;
}
$message = sprintf('The "%s" scheme is not supported', $dsn->getScheme());
if ($name && $supported) {
$message .= sprintf('; supported schemes for translation provider "%s" are: "%s"', $name, implode('", "', $supported));
}
parent::__construct($message.'.');
}
}

View File

@@ -24,7 +24,7 @@ interface ExtractorInterface
/**
* Extracts translation messages from files, a file or a directory to the catalogue.
*
* @param string|array $resource Files, a file or a directory
* @param string|string[] $resource Files, a file or a directory
*/
public function extract($resource, MessageCatalogue $catalogue);

View File

@@ -12,6 +12,8 @@
namespace Symfony\Component\Translation\Loader;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Config\Util\Exception\InvalidXmlException;
use Symfony\Component\Config\Util\Exception\XmlParsingException;
use Symfony\Component\Config\Util\XmlUtils;
use Symfony\Component\Translation\Exception\InvalidResourceException;
use Symfony\Component\Translation\Exception\NotFoundResourceException;
@@ -35,36 +37,47 @@ class XliffFileLoader implements LoaderInterface
throw new RuntimeException('Loading translations from the Xliff format requires the Symfony Config component.');
}
if (!stream_is_local($resource)) {
throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
if (!$this->isXmlString($resource)) {
if (!stream_is_local($resource)) {
throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
}
if (!file_exists($resource)) {
throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
}
if (!is_file($resource)) {
throw new InvalidResourceException(sprintf('This is neither a file nor an XLIFF string "%s".', $resource));
}
}
if (!file_exists($resource)) {
throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
try {
if ($this->isXmlString($resource)) {
$dom = XmlUtils::parse($resource);
} else {
$dom = XmlUtils::loadFile($resource);
}
} catch (\InvalidArgumentException | XmlParsingException | InvalidXmlException $e) {
throw new InvalidResourceException(sprintf('Unable to load "%s": ', $resource).$e->getMessage(), $e->getCode(), $e);
}
if ($errors = XliffUtils::validateSchema($dom)) {
throw new InvalidResourceException(sprintf('Invalid resource provided: "%s"; Errors: ', $resource).XliffUtils::getErrorsAsString($errors));
}
$catalogue = new MessageCatalogue($locale);
$this->extract($resource, $catalogue, $domain);
$this->extract($dom, $catalogue, $domain);
if (class_exists(FileResource::class)) {
if (is_file($resource) && class_exists(FileResource::class)) {
$catalogue->addResource(new FileResource($resource));
}
return $catalogue;
}
private function extract($resource, MessageCatalogue $catalogue, string $domain)
private function extract($dom, MessageCatalogue $catalogue, string $domain)
{
try {
$dom = XmlUtils::loadFile($resource);
} catch (\InvalidArgumentException $e) {
throw new InvalidResourceException(sprintf('Unable to load "%s": ', $resource).$e->getMessage(), $e->getCode(), $e);
}
$xliffVersion = XliffUtils::getVersionNumber($dom);
if ($errors = XliffUtils::validateSchema($dom)) {
throw new InvalidResourceException(sprintf('Invalid resource provided: "%s"; Errors: ', $resource).XliffUtils::getErrorsAsString($errors));
}
if ('1.2' === $xliffVersion) {
$this->extractXliff1($dom, $catalogue, $domain);
@@ -81,7 +94,7 @@ class XliffFileLoader implements LoaderInterface
private function extractXliff1(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain)
{
$xml = simplexml_import_dom($dom);
$encoding = strtoupper($dom->encoding);
$encoding = $dom->encoding ? strtoupper($dom->encoding) : null;
$namespace = 'urn:oasis:names:tc:xliff:document:1.2';
$xml->registerXPathNamespace('xliff', $namespace);
@@ -211,4 +224,9 @@ class XliffFileLoader implements LoaderInterface
return $notes;
}
private function isXmlString(string $resource): bool
{
return 0 === strpos($resource, '<?xml');
}
}

View File

@@ -82,6 +82,14 @@ class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface,
return $this->translator->getCatalogue($locale);
}
/**
* {@inheritdoc}
*/
public function getCatalogues(): array
{
return $this->translator->getCatalogues();
}
/**
* Gets the fallback locales.
*

View File

@@ -0,0 +1,45 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Provider;
use Symfony\Component\Translation\Exception\IncompleteDsnException;
abstract class AbstractProviderFactory implements ProviderFactoryInterface
{
public function supports(Dsn $dsn): bool
{
return \in_array($dsn->getScheme(), $this->getSupportedSchemes(), true);
}
/**
* @return string[]
*/
abstract protected function getSupportedSchemes(): array;
protected function getUser(Dsn $dsn): string
{
if (null === $user = $dsn->getUser()) {
throw new IncompleteDsnException('User is not set.', $dsn->getOriginalDsn());
}
return $user;
}
protected function getPassword(Dsn $dsn): string
{
if (null === $password = $dsn->getPassword()) {
throw new IncompleteDsnException('Password is not set.', $dsn->getOriginalDsn());
}
return $password;
}
}

View File

@@ -0,0 +1,110 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Provider;
use Symfony\Component\Translation\Exception\InvalidArgumentException;
use Symfony\Component\Translation\Exception\MissingRequiredOptionException;
/**
* @author Fabien Potencier <fabien@symfony.com>
* @author Oskar Stark <oskarstark@googlemail.com>
*/
final class Dsn
{
private $scheme;
private $host;
private $user;
private $password;
private $port;
private $path;
private $options;
private $originalDsn;
public function __construct(string $dsn)
{
$this->originalDsn = $dsn;
if (false === $parsedDsn = parse_url($dsn)) {
throw new InvalidArgumentException(sprintf('The "%s" translation provider DSN is invalid.', $dsn));
}
if (!isset($parsedDsn['scheme'])) {
throw new InvalidArgumentException(sprintf('The "%s" translation provider DSN must contain a scheme.', $dsn));
}
$this->scheme = $parsedDsn['scheme'];
if (!isset($parsedDsn['host'])) {
throw new InvalidArgumentException(sprintf('The "%s" translation provider DSN must contain a host (use "default" by default).', $dsn));
}
$this->host = $parsedDsn['host'];
$this->user = '' !== ($parsedDsn['user'] ?? '') ? urldecode($parsedDsn['user']) : null;
$this->password = '' !== ($parsedDsn['pass'] ?? '') ? urldecode($parsedDsn['pass']) : null;
$this->port = $parsedDsn['port'] ?? null;
$this->path = $parsedDsn['path'] ?? null;
parse_str($parsedDsn['query'] ?? '', $this->options);
}
public function getScheme(): string
{
return $this->scheme;
}
public function getHost(): string
{
return $this->host;
}
public function getUser(): ?string
{
return $this->user;
}
public function getPassword(): ?string
{
return $this->password;
}
public function getPort(int $default = null): ?int
{
return $this->port ?? $default;
}
public function getOption(string $key, $default = null)
{
return $this->options[$key] ?? $default;
}
public function getRequiredOption(string $key)
{
if (!\array_key_exists($key, $this->options) || '' === trim($this->options[$key])) {
throw new MissingRequiredOptionException($key);
}
return $this->options[$key];
}
public function getOptions(): array
{
return $this->options;
}
public function getPath(): ?string
{
return $this->path;
}
public function getOriginalDsn(): string
{
return $this->originalDsn;
}
}

View File

@@ -0,0 +1,67 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Provider;
use Symfony\Component\Translation\TranslatorBag;
use Symfony\Component\Translation\TranslatorBagInterface;
/**
* Filters domains and locales between the Translator config values and those specific to each provider.
*
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*
* @experimental in 5.3
*/
class FilteringProvider implements ProviderInterface
{
private $provider;
private $locales;
private $domains;
public function __construct(ProviderInterface $provider, array $locales, array $domains = [])
{
$this->provider = $provider;
$this->locales = $locales;
$this->domains = $domains;
}
public function __toString(): string
{
return (string) $this->provider;
}
/**
* {@inheritdoc}
*/
public function write(TranslatorBagInterface $translatorBag): void
{
$this->provider->write($translatorBag);
}
public function read(array $domains, array $locales): TranslatorBag
{
$domains = !$this->domains ? $domains : array_intersect($this->domains, $domains);
$locales = array_intersect($this->locales, $locales);
return $this->provider->read($domains, $locales);
}
public function delete(TranslatorBagInterface $translatorBag): void
{
$this->provider->delete($translatorBag);
}
public function getDomains(): array
{
return $this->domains;
}
}

View File

@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Provider;
use Symfony\Component\Translation\TranslatorBag;
use Symfony\Component\Translation\TranslatorBagInterface;
/**
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*
* @experimental in 5.3
*/
class NullProvider implements ProviderInterface
{
public function __toString(): string
{
return 'null';
}
public function write(TranslatorBagInterface $translatorBag, bool $override = false): void
{
}
public function read(array $domains, array $locales): TranslatorBag
{
return new TranslatorBag();
}
public function delete(TranslatorBagInterface $translatorBag): void
{
}
}

View File

@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Provider;
use Symfony\Component\Translation\Exception\UnsupportedSchemeException;
/**
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*
* @experimental in 5.3
*/
final class NullProviderFactory extends AbstractProviderFactory
{
public function create(Dsn $dsn): ProviderInterface
{
if ('null' === $dsn->getScheme()) {
return new NullProvider();
}
throw new UnsupportedSchemeException($dsn, 'null', $this->getSupportedSchemes());
}
protected function getSupportedSchemes(): array
{
return ['null'];
}
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Provider;
use Symfony\Component\Translation\Exception\IncompleteDsnException;
use Symfony\Component\Translation\Exception\UnsupportedSchemeException;
interface ProviderFactoryInterface
{
/**
* @throws UnsupportedSchemeException
* @throws IncompleteDsnException
*/
public function create(Dsn $dsn): ProviderInterface;
public function supports(Dsn $dsn): bool;
}

View File

@@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Provider;
use Symfony\Component\Translation\TranslatorBag;
use Symfony\Component\Translation\TranslatorBagInterface;
interface ProviderInterface
{
public function __toString(): string;
/**
* Translations available in the TranslatorBag only must be created.
* Translations available in both the TranslatorBag and on the provider
* must be overwritten.
* Translations available on the provider only must be kept.
*/
public function write(TranslatorBagInterface $translatorBag): void;
public function read(array $domains, array $locales): TranslatorBag;
public function delete(TranslatorBagInterface $translatorBag): void;
}

View File

@@ -0,0 +1,59 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Provider;
use Symfony\Component\Translation\Exception\InvalidArgumentException;
/**
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*
* @experimental in 5.3
*/
final class TranslationProviderCollection
{
private $providers;
/**
* @param array<string, ProviderInterface> $providers
*/
public function __construct(iterable $providers)
{
$this->providers = [];
foreach ($providers as $name => $provider) {
$this->providers[$name] = $provider;
}
}
public function __toString(): string
{
return '['.implode(',', array_keys($this->providers)).']';
}
public function has(string $name): bool
{
return isset($this->providers[$name]);
}
public function get(string $name): ProviderInterface
{
if (!$this->has($name)) {
throw new InvalidArgumentException(sprintf('Provider "%s" not found. Available: "%s".', $name, (string) $this));
}
return $this->providers[$name];
}
public function keys(): array
{
return array_keys($this->providers);
}
}

View File

@@ -0,0 +1,59 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Provider;
use Symfony\Component\Translation\Exception\UnsupportedSchemeException;
/**
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*
* @experimental in 5.3
*/
class TranslationProviderCollectionFactory
{
private $factories;
private $enabledLocales;
/**
* @param ProviderFactoryInterface[] $factories
*/
public function __construct(iterable $factories, array $enabledLocales)
{
$this->factories = $factories;
$this->enabledLocales = $enabledLocales;
}
public function fromConfig(array $config): TranslationProviderCollection
{
$providers = [];
foreach ($config as $name => $currentConfig) {
$providers[$name] = $this->fromDsnObject(
new Dsn($currentConfig['dsn']),
!$currentConfig['locales'] ? $this->enabledLocales : $currentConfig['locales'],
!$currentConfig['domains'] ? [] : $currentConfig['domains']
);
}
return new TranslationProviderCollection($providers);
}
public function fromDsnObject(Dsn $dsn, array $locales, array $domains = []): ProviderInterface
{
foreach ($this->factories as $factory) {
if ($factory->supports($dsn)) {
return new FilteringProvider($factory->create($dsn), $locales, $domains);
}
}
throw new UnsupportedSchemeException($dsn);
}
}

View File

@@ -108,6 +108,11 @@ final class PseudoLocalizationTranslator implements TranslatorInterface
return $trans;
}
public function getLocale(): string
{
return $this->translator->getLocale();
}
private function getParts(string $originalTrans): array
{
if (!$this->parseHTML) {

View File

@@ -26,8 +26,8 @@ echo $translator->trans('Hello World!'); // outputs « Bonjour ! »
Resources
---------
* [Documentation](https://symfony.com/doc/current/translation.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)
* [Documentation](https://symfony.com/doc/current/translation.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)

View File

@@ -118,6 +118,8 @@
"es_UY": "es_419",
"es_VE": "es_419",
"ff_Adlm": "root",
"nb": "no",
"nn": "no",
"pa_Arab": "root",
"pt_AO": "pt_PT",
"pt_CH": "pt_PT",

View File

@@ -0,0 +1,147 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Test;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\Translation\Dumper\XliffFileDumper;
use Symfony\Component\Translation\Exception\IncompleteDsnException;
use Symfony\Component\Translation\Exception\UnsupportedSchemeException;
use Symfony\Component\Translation\Loader\LoaderInterface;
use Symfony\Component\Translation\Provider\Dsn;
use Symfony\Component\Translation\Provider\ProviderFactoryInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
/**
* A test case to ease testing a translation provider factory.
*
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*
* @internal
*/
abstract class ProviderFactoryTestCase extends TestCase
{
protected $client;
protected $logger;
protected $defaultLocale;
protected $loader;
protected $xliffFileDumper;
abstract public function createFactory(): ProviderFactoryInterface;
/**
* @return iterable<array{0: bool, 1: string}>
*/
abstract public function supportsProvider(): iterable;
/**
* @return iterable<array{0: string, 1: string, 2: TransportInterface}>
*/
abstract public function createProvider(): iterable;
/**
* @return iterable<array{0: string, 1: string|null}>
*/
public function unsupportedSchemeProvider(): iterable
{
return [];
}
/**
* @return iterable<array{0: string, 1: string|null}>
*/
public function incompleteDsnProvider(): iterable
{
return [];
}
/**
* @dataProvider supportsProvider
*/
public function testSupports(bool $expected, string $dsn)
{
$factory = $this->createFactory();
$this->assertSame($expected, $factory->supports(new Dsn($dsn)));
}
/**
* @dataProvider createProvider
*/
public function testCreate(string $expected, string $dsn)
{
$factory = $this->createFactory();
$provider = $factory->create(new Dsn($dsn));
$this->assertSame($expected, (string) $provider);
}
/**
* @dataProvider unsupportedSchemeProvider
*/
public function testUnsupportedSchemeException(string $dsn, string $message = null)
{
$factory = $this->createFactory();
$dsn = new Dsn($dsn);
$this->expectException(UnsupportedSchemeException::class);
if (null !== $message) {
$this->expectExceptionMessage($message);
}
$factory->create($dsn);
}
/**
* @dataProvider incompleteDsnProvider
*/
public function testIncompleteDsnException(string $dsn, string $message = null)
{
$factory = $this->createFactory();
$dsn = new Dsn($dsn);
$this->expectException(IncompleteDsnException::class);
if (null !== $message) {
$this->expectExceptionMessage($message);
}
$factory->create($dsn);
}
protected function getClient(): HttpClientInterface
{
return $this->client ?? $this->client = new MockHttpClient();
}
protected function getLogger(): LoggerInterface
{
return $this->logger ?? $this->logger = $this->createMock(LoggerInterface::class);
}
protected function getDefaultLocale(): string
{
return $this->defaultLocale ?? $this->defaultLocale = 'en';
}
protected function getLoader(): LoaderInterface
{
return $this->loader ?? $this->loader = $this->createMock(LoaderInterface::class);
}
protected function getXliffFileDumper(): XliffFileDumper
{
return $this->xliffFileDumper ?? $this->xliffFileDumper = $this->createMock(XliffFileDumper::class);
}
}

View File

@@ -0,0 +1,86 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Test;
use PHPUnit\Framework\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\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 $defaultLocale;
protected $loader;
protected $xliffFileDumper;
abstract public function createProvider(HttpClientInterface $client, LoaderInterface $loader, LoggerInterface $logger, string $defaultLocale, string $endpoint): ProviderInterface;
/**
* @return iterable<array{0: string, 1: ProviderInterface}>
*/
abstract public function toStringProvider(): iterable;
/**
* @dataProvider toStringProvider
*/
public function testToString(ProviderInterface $provider, string $expected)
{
$this->assertSame($expected, (string) $provider);
}
protected function getClient(): MockHttpClient
{
return $this->client ?? $this->client = new MockHttpClient();
}
/**
* @return LoaderInterface&MockObject
*/
protected function getLoader(): LoaderInterface
{
return $this->loader ?? $this->loader = $this->createMock(LoaderInterface::class);
}
/**
* @return LoaderInterface&MockObject
*/
protected function getLogger(): LoggerInterface
{
return $this->logger ?? $this->logger = $this->createMock(LoggerInterface::class);
}
protected function getDefaultLocale(): string
{
return $this->defaultLocale ?? $this->defaultLocale = 'en';
}
/**
* @return LoaderInterface&MockObject
*/
protected function getXliffFileDumper(): XliffFileDumper
{
return $this->xliffFileDumper ?? $this->xliffFileDumper = $this->createMock(XliffFileDumper::class);
}
}

View File

@@ -243,6 +243,14 @@ class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleA
return $this->catalogues[$locale];
}
/**
* {@inheritdoc}
*/
public function getCatalogues(): array
{
return array_values($this->catalogues);
}
/**
* Gets the loaders.
*

View File

@@ -0,0 +1,105 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation;
use Symfony\Component\Translation\Catalogue\AbstractOperation;
use Symfony\Component\Translation\Catalogue\TargetOperation;
final class TranslatorBag implements TranslatorBagInterface
{
/** @var MessageCatalogue[] */
private $catalogues = [];
public function addCatalogue(MessageCatalogue $catalogue): void
{
if (null !== $existingCatalogue = $this->getCatalogue($catalogue->getLocale())) {
$catalogue->addCatalogue($existingCatalogue);
}
$this->catalogues[$catalogue->getLocale()] = $catalogue;
}
public function addBag(TranslatorBagInterface $bag): void
{
foreach ($bag->getCatalogues() as $catalogue) {
$this->addCatalogue($catalogue);
}
}
/**
* {@inheritdoc}
*/
public function getCatalogue(string $locale = null)
{
if (null === $locale || !isset($this->catalogues[$locale])) {
$this->catalogues[$locale] = new MessageCatalogue($locale);
}
return $this->catalogues[$locale];
}
/**
* {@inheritdoc}
*/
public function getCatalogues(): array
{
return array_values($this->catalogues);
}
public function diff(TranslatorBagInterface $diffBag): self
{
$diff = new self();
foreach ($this->catalogues as $locale => $catalogue) {
if (null === $diffCatalogue = $diffBag->getCatalogue($locale)) {
$diff->addCatalogue($catalogue);
continue;
}
$operation = new TargetOperation($diffCatalogue, $catalogue);
$operation->moveMessagesToIntlDomainsIfPossible(AbstractOperation::NEW_BATCH);
$newCatalogue = new MessageCatalogue($locale);
foreach ($operation->getDomains() as $domain) {
$newCatalogue->add($operation->getNewMessages($domain), $domain);
}
$diff->addCatalogue($newCatalogue);
}
return $diff;
}
public function intersect(TranslatorBagInterface $intersectBag): self
{
$diff = new self();
foreach ($this->catalogues as $locale => $catalogue) {
if (null === $intersectCatalogue = $intersectBag->getCatalogue($locale)) {
continue;
}
$operation = new TargetOperation($catalogue, $intersectCatalogue);
$operation->moveMessagesToIntlDomainsIfPossible(AbstractOperation::OBSOLETE_BATCH);
$obsoleteCatalogue = new MessageCatalogue($locale);
foreach ($operation->getDomains() as $domain) {
$obsoleteCatalogue->add($operation->getObsoleteMessages($domain), $domain);
}
$diff->addCatalogue($obsoleteCatalogue);
}
return $diff;
}
}

View File

@@ -16,6 +16,8 @@ use Symfony\Component\Translation\Exception\InvalidArgumentException;
/**
* TranslatorBagInterface.
*
* @method MessageCatalogueInterface[] getCatalogues() Returns all catalogues of the instance
*
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*/
interface TranslatorBagInterface

View File

@@ -17,6 +17,7 @@
],
"require": {
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1",
"symfony/polyfill-mbstring": "~1.0",
"symfony/polyfill-php80": "^1.15",
"symfony/translation-contracts": "^2.3"
@@ -27,6 +28,7 @@
"symfony/dependency-injection": "^5.0",
"symfony/http-kernel": "^5.0",
"symfony/intl": "^4.4|^5.0",
"symfony/polyfill-intl-icu": "^1.21",
"symfony/service-contracts": "^1.1.2|^2",
"symfony/yaml": "^4.4|^5.0",
"symfony/finder": "^4.4|^5.0",

View File

@@ -42,7 +42,7 @@ class CliDescriptor implements DumpDescriptorInterface
$io = $output instanceof SymfonyStyle ? $output : new SymfonyStyle(new ArrayInput([]), $output);
$this->dumper->setColors($output->isDecorated());
$rows = [['date', date('r', $context['timestamp'])]];
$rows = [['date', date('r', (int) $context['timestamp'])]];
$lastIdentifier = $this->lastIdentifier;
$this->lastIdentifier = $clientId;

View File

@@ -94,7 +94,7 @@ HTML
private function extractDate(array $context, string $format = 'r'): string
{
return date($format, $context['timestamp']);
return date($format, (int) $context['timestamp']);
}
private function renderTags(array $tags): string

View File

@@ -35,6 +35,7 @@ use Symfony\Component\VarDumper\Server\DumpServer;
class ServerDumpCommand extends Command
{
protected static $defaultName = 'server:dump';
protected static $defaultDescription = 'Start a dump server that collects and displays dumps in a single place';
private $server;
@@ -58,7 +59,7 @@ class ServerDumpCommand extends Command
$this
->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format (%s)', $availableFormats), 'cli')
->setDescription('Start a dump server that collects and displays dumps in a single place')
->setDescription(self::$defaultDescription)
->setHelp(<<<'EOF'
<info>%command.name%</info> starts a dump server that collects and displays
dumps in a single place for debugging you application:

View File

@@ -8,8 +8,8 @@ of `var_dump`.
Resources
---------
* [Documentation](https://symfony.com/doc/current/components/var_dumper/introduction.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)
* [Documentation](https://symfony.com/doc/current/components/var_dumper/introduction.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)

View File

@@ -71,7 +71,7 @@ class VarDumper
$dumper = new CliDumper();
break;
case 'server' === $format:
case 'tcp' === parse_url($format, \PHP_URL_SCHEME):
case $format && 'tcp' === parse_url($format, \PHP_URL_SCHEME):
$host = 'server' === $format ? $_SERVER['VAR_DUMPER_SERVER'] ?? '127.0.0.1:9912' : $format;
$dumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? new CliDumper() : new HtmlDumper();
$dumper = new ServerDumper($host, $dumper, self::getDefaultContextProviders());