1
0
mirror of https://github.com/mailcow/mailcow-dockerized.git synced 2025-12-22 14:21:31 +00:00

[Web] update directorytree/ldaprecord

This commit is contained in:
FreddleSpl0it
2024-02-20 10:30:11 +01:00
parent 40146839ef
commit d479d18507
481 changed files with 13919 additions and 6171 deletions

View File

@@ -25,7 +25,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-main": "3.3-dev"
"dev-main": "3.4-dev"
},
"thanks": {
"name": "symfony/contracts",

View File

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

View File

@@ -69,7 +69,7 @@ final class Mbstring
{
public const MB_CASE_FOLD = \PHP_INT_MAX;
private const CASE_FOLD = [
private const SIMPLE_CASE_FOLD = [
['µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"],
['μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'],
];
@@ -80,7 +80,7 @@ final class Mbstring
public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null)
{
if (\is_array($fromEncoding) || ($fromEncoding !== null && false !== strpos($fromEncoding, ','))) {
if (\is_array($fromEncoding) || (null !== $fromEncoding && false !== strpos($fromEncoding, ','))) {
$fromEncoding = self::mb_detect_encoding($s, $fromEncoding);
} else {
$fromEncoding = self::getEncoding($fromEncoding);
@@ -102,7 +102,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);
@@ -113,7 +113,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)
@@ -130,7 +130,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)
@@ -140,7 +140,7 @@ final class Mbstring
public static function mb_decode_numericentity($s, $convmap, $encoding = null)
{
if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) {
if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) {
trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING);
return null;
@@ -150,7 +150,7 @@ final class Mbstring
return false;
}
if (null !== $encoding && !is_scalar($encoding)) {
if (null !== $encoding && !\is_scalar($encoding)) {
trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING);
return ''; // Instead of null (cf. mb_encode_numericentity).
@@ -166,10 +166,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;
@@ -195,12 +195,12 @@ 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)
{
if (null !== $s && !is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) {
if (null !== $s && !\is_scalar($s) && !(\is_object($s) && method_exists($s, '__toString'))) {
trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', \E_USER_WARNING);
return null;
@@ -210,13 +210,13 @@ final class Mbstring
return false;
}
if (null !== $encoding && !is_scalar($encoding)) {
if (null !== $encoding && !\is_scalar($encoding)) {
trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', \E_USER_WARNING);
return null; // Instead of '' (cf. mb_decode_numericentity).
}
if (null !== $is_hex && !is_scalar($is_hex)) {
if (null !== $is_hex && !\is_scalar($is_hex)) {
trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', \E_USER_WARNING);
return null;
@@ -232,10 +232,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];
@@ -265,7 +265,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)
@@ -280,10 +280,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) {
@@ -301,7 +301,11 @@ final class Mbstring
$map = $upper;
} else {
if (self::MB_CASE_FOLD === $mode) {
$s = str_replace(self::CASE_FOLD[0], self::CASE_FOLD[1], $s);
static $caseFolding = null;
if (null === $caseFolding) {
$caseFolding = self::getData('caseFolding');
}
$s = strtr($s, $caseFolding);
}
static $lower = null;
@@ -343,7 +347,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)
@@ -354,7 +358,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;
@@ -406,6 +410,12 @@ final class Mbstring
public static function mb_check_encoding($var = null, $encoding = null)
{
if (PHP_VERSION_ID < 70200 && \is_array($var)) {
trigger_error('mb_check_encoding() expects parameter 1 to be string, array given', \E_USER_WARNING);
return null;
}
if (null === $encoding) {
if (null === $var) {
return false;
@@ -413,7 +423,21 @@ final class Mbstring
$encoding = self::$internalEncoding;
}
return self::mb_detect_encoding($var, [$encoding]) || false !== @\iconv($encoding, $encoding, $var);
if (!\is_array($var)) {
return self::mb_detect_encoding($var, [$encoding]) || false !== @iconv($encoding, $encoding, $var);
}
foreach ($var as $key => $value) {
if (!self::mb_check_encoding($key, $encoding)) {
return false;
}
if (!self::mb_check_encoding($value, $encoding)) {
return false;
}
}
return true;
}
public static function mb_detect_encoding($str, $encodingList = null, $strict = false)
@@ -488,7 +512,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)
@@ -509,7 +533,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)
@@ -533,7 +557,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;
@@ -541,7 +565,7 @@ final class Mbstring
public static function mb_str_split($string, $split_length = 1, $encoding = null)
{
if (null !== $string && !is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) {
if (null !== $string && !\is_scalar($string) && !(\is_object($string) && method_exists($string, '__toString'))) {
trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', \E_USER_WARNING);
return null;
@@ -550,6 +574,7 @@ final class Mbstring
if (1 > $split_length = (int) $split_length) {
if (80000 > \PHP_VERSION_ID) {
trigger_error('The length of each segment must be greater than zero', \E_USER_WARNING);
return false;
}
@@ -568,7 +593,7 @@ final class Mbstring
}
$rx .= '.{'.$split_length.'})/us';
return preg_split($rx, $string, null, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY);
return preg_split($rx, $string, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY);
}
$result = [];
@@ -617,7 +642,7 @@ final class Mbstring
}
if ($start < 0) {
$start = \iconv_strlen($s, $encoding) + $start;
$start = iconv_strlen($s, $encoding) + $start;
if ($start < 0) {
$start = 0;
}
@@ -626,19 +651,21 @@ 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)
{
$haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
$needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);
[$haystack, $needle] = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], [
self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding),
self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding),
]);
return self::mb_strpos($haystack, $needle, $offset, $encoding);
}
@@ -657,7 +684,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);
@@ -673,8 +700,11 @@ final class Mbstring
public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null)
{
$haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding);
$needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding);
$haystack = self::mb_convert_case($haystack, \MB_CASE_LOWER, $encoding);
$needle = self::mb_convert_case($needle, \MB_CASE_LOWER, $encoding);
$haystack = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $haystack);
$needle = str_replace(self::SIMPLE_CASE_FOLD[0], self::SIMPLE_CASE_FOLD[1], $needle);
return self::mb_strrpos($haystack, $needle, $offset, $encoding);
}
@@ -736,12 +766,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)
@@ -797,6 +827,50 @@ final class Mbstring
return $code;
}
public static function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = \STR_PAD_RIGHT, string $encoding = null): string
{
if (!\in_array($pad_type, [\STR_PAD_RIGHT, \STR_PAD_LEFT, \STR_PAD_BOTH], true)) {
throw new \ValueError('mb_str_pad(): Argument #4 ($pad_type) must be STR_PAD_LEFT, STR_PAD_RIGHT, or STR_PAD_BOTH');
}
if (null === $encoding) {
$encoding = self::mb_internal_encoding();
}
try {
$validEncoding = @self::mb_check_encoding('', $encoding);
} catch (\ValueError $e) {
throw new \ValueError(sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding));
}
// BC for PHP 7.3 and lower
if (!$validEncoding) {
throw new \ValueError(sprintf('mb_str_pad(): Argument #5 ($encoding) must be a valid encoding, "%s" given', $encoding));
}
if (self::mb_strlen($pad_string, $encoding) <= 0) {
throw new \ValueError('mb_str_pad(): Argument #3 ($pad_string) must be a non-empty string');
}
$paddingRequired = $length - self::mb_strlen($string, $encoding);
if ($paddingRequired < 1) {
return $string;
}
switch ($pad_type) {
case \STR_PAD_LEFT:
return self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding).$string;
case \STR_PAD_RIGHT:
return $string.self::mb_substr(str_repeat($pad_string, $paddingRequired), 0, $paddingRequired, $encoding);
default:
$leftPaddingLength = floor($paddingRequired / 2);
$rightPaddingLength = $paddingRequired - $leftPaddingLength;
return self::mb_substr(str_repeat($pad_string, $leftPaddingLength), 0, $leftPaddingLength, $encoding).$string.self::mb_substr(str_repeat($pad_string, $rightPaddingLength), 0, $rightPaddingLength, $encoding);
}
}
private static function getSubpart($pos, $part, $haystack, $encoding)
{
if (false === $pos) {

View File

@@ -5,7 +5,7 @@ This component provides a partial, native PHP implementation for the
[Mbstring](https://php.net/mbstring) extension.
More information can be found in the
[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md).
[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md).
License
=======

View File

@@ -0,0 +1,119 @@
<?php
return [
'İ' => 'i̇',
'µ' => 'μ',
'ſ' => 's',
'ͅ' => 'ι',
'ς' => 'σ',
'ϐ' => 'β',
'ϑ' => 'θ',
'ϕ' => 'φ',
'ϖ' => 'π',
'ϰ' => 'κ',
'ϱ' => 'ρ',
'ϵ' => 'ε',
'ẛ' => 'ṡ',
'' => 'ι',
'ß' => 'ss',
'ʼn' => 'ʼn',
'ǰ' => 'ǰ',
'ΐ' => 'ΐ',
'ΰ' => 'ΰ',
'և' => 'եւ',
'ẖ' => 'ẖ',
'ẗ' => 'ẗ',
'ẘ' => 'ẘ',
'ẙ' => 'ẙ',
'ẚ' => 'aʾ',
'ẞ' => 'ss',
'ὐ' => 'ὐ',
'ὒ' => 'ὒ',
'ὔ' => 'ὔ',
'ὖ' => 'ὖ',
'ᾀ' => 'ἀι',
'ᾁ' => 'ἁι',
'ᾂ' => 'ἂι',
'ᾃ' => 'ἃι',
'ᾄ' => 'ἄι',
'ᾅ' => 'ἅι',
'ᾆ' => 'ἆι',
'ᾇ' => 'ἇι',
'ᾈ' => 'ἀι',
'ᾉ' => 'ἁι',
'ᾊ' => 'ἂι',
'ᾋ' => 'ἃι',
'ᾌ' => 'ἄι',
'ᾍ' => 'ἅι',
'ᾎ' => 'ἆι',
'ᾏ' => 'ἇι',
'ᾐ' => 'ἠι',
'ᾑ' => 'ἡι',
'ᾒ' => 'ἢι',
'ᾓ' => 'ἣι',
'ᾔ' => 'ἤι',
'ᾕ' => 'ἥι',
'ᾖ' => 'ἦι',
'ᾗ' => 'ἧι',
'ᾘ' => 'ἠι',
'ᾙ' => 'ἡι',
'ᾚ' => 'ἢι',
'ᾛ' => 'ἣι',
'ᾜ' => 'ἤι',
'ᾝ' => 'ἥι',
'ᾞ' => 'ἦι',
'ᾟ' => 'ἧι',
'ᾠ' => 'ὠι',
'ᾡ' => 'ὡι',
'ᾢ' => 'ὢι',
'ᾣ' => 'ὣι',
'ᾤ' => 'ὤι',
'ᾥ' => 'ὥι',
'ᾦ' => 'ὦι',
'ᾧ' => 'ὧι',
'ᾨ' => 'ὠι',
'ᾩ' => 'ὡι',
'ᾪ' => 'ὢι',
'ᾫ' => 'ὣι',
'ᾬ' => 'ὤι',
'ᾭ' => 'ὥι',
'ᾮ' => 'ὦι',
'ᾯ' => 'ὧι',
'ᾲ' => 'ὰι',
'ᾳ' => 'αι',
'ᾴ' => 'άι',
'ᾶ' => 'ᾶ',
'ᾷ' => 'ᾶι',
'ᾼ' => 'αι',
'ῂ' => 'ὴι',
'ῃ' => 'ηι',
'ῄ' => 'ήι',
'ῆ' => 'ῆ',
'ῇ' => 'ῆι',
'ῌ' => 'ηι',
'ῒ' => 'ῒ',
'ῖ' => 'ῖ',
'ῗ' => 'ῗ',
'ῢ' => 'ῢ',
'ῤ' => 'ῤ',
'ῦ' => 'ῦ',
'ῧ' => 'ῧ',
'ῲ' => 'ὼι',
'ῳ' => 'ωι',
'ῴ' => 'ώι',
'ῶ' => 'ῶ',
'ῷ' => 'ῶι',
'ῼ' => 'ωι',
'ff' => 'ff',
'fi' => 'fi',
'fl' => 'fl',
'ffi' => 'ffi',
'ffl' => 'ffl',
'ſt' => 'st',
'st' => 'st',
'ﬓ' => 'մն',
'ﬔ' => 'մե',
'ﬕ' => 'մի',
'ﬖ' => 'վն',
'ﬗ' => 'մխ',
];

View File

@@ -132,6 +132,10 @@ if (!function_exists('mb_str_split')) {
function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); }
}
if (!function_exists('mb_str_pad')) {
function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
}
if (extension_loaded('mbstring')) {
return;
}

View File

@@ -128,6 +128,10 @@ if (!function_exists('mb_str_split')) {
function mb_str_split(?string $string, ?int $length = 1, ?string $encoding = null): array { return p\Mbstring::mb_str_split((string) $string, (int) $length, $encoding); }
}
if (!function_exists('mb_str_pad')) {
function mb_str_pad(string $string, int $length, string $pad_string = ' ', int $pad_type = STR_PAD_RIGHT, ?string $encoding = null): string { return p\Mbstring::mb_str_pad($string, $length, $pad_string, $pad_type, $encoding); }
}
if (extension_loaded('mbstring')) {
return;
}

View File

@@ -30,9 +30,6 @@
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"

View File

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

View File

@@ -100,6 +100,16 @@ final class Php80
public static function str_ends_with(string $haystack, string $needle): bool
{
return '' === $needle || ('' !== $haystack && 0 === substr_compare($haystack, $needle, -\strlen($needle)));
if ('' === $needle || $needle === $haystack) {
return true;
}
if ('' === $haystack) {
return false;
}
$needleLength = \strlen($needle);
return $needleLength <= \strlen($haystack) && 0 === substr_compare($haystack, $needle, -$needleLength);
}
}

View File

@@ -0,0 +1,103 @@
<?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\Polyfill\Php80;
/**
* @author Fedonyuk Anton <info@ensostudio.ru>
*
* @internal
*/
class PhpToken implements \Stringable
{
/**
* @var int
*/
public $id;
/**
* @var string
*/
public $text;
/**
* @var int
*/
public $line;
/**
* @var int
*/
public $pos;
public function __construct(int $id, string $text, int $line = -1, int $position = -1)
{
$this->id = $id;
$this->text = $text;
$this->line = $line;
$this->pos = $position;
}
public function getTokenName(): ?string
{
if ('UNKNOWN' === $name = token_name($this->id)) {
$name = \strlen($this->text) > 1 || \ord($this->text) < 32 ? null : $this->text;
}
return $name;
}
/**
* @param int|string|array $kind
*/
public function is($kind): bool
{
foreach ((array) $kind as $value) {
if (\in_array($value, [$this->id, $this->text], true)) {
return true;
}
}
return false;
}
public function isIgnorable(): bool
{
return \in_array($this->id, [\T_WHITESPACE, \T_COMMENT, \T_DOC_COMMENT, \T_OPEN_TAG], true);
}
public function __toString(): string
{
return (string) $this->text;
}
/**
* @return static[]
*/
public static function tokenize(string $code, int $flags = 0): array
{
$line = 1;
$position = 0;
$tokens = token_get_all($code, $flags);
foreach ($tokens as $index => $token) {
if (\is_string($token)) {
$id = \ord($token);
$text = $token;
} else {
[$id, $text, $line] = $token;
}
$tokens[$index] = new static($id, $text, $line, $position);
$position += \strlen($text);
}
return $tokens;
}
}

View File

@@ -3,12 +3,13 @@ Symfony Polyfill / Php80
This component provides features added to PHP 8.0 core:
- `Stringable` interface
- [`Stringable`](https://php.net/stringable) interface
- [`fdiv`](https://php.net/fdiv)
- `ValueError` class
- `UnhandledMatchError` class
- [`ValueError`](https://php.net/valueerror) class
- [`UnhandledMatchError`](https://php.net/unhandledmatcherror) class
- `FILTER_VALIDATE_BOOL` constant
- [`get_debug_type`](https://php.net/get_debug_type)
- [`PhpToken`](https://php.net/phptoken) class
- [`preg_last_error_msg`](https://php.net/preg_last_error_msg)
- [`str_contains`](https://php.net/str_contains)
- [`str_starts_with`](https://php.net/str_starts_with)

View File

@@ -1,5 +1,14 @@
<?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.
*/
#[Attribute(Attribute::TARGET_CLASS)]
final class Attribute
{

View File

@@ -0,0 +1,16 @@
<?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 (\PHP_VERSION_ID < 80000 && extension_loaded('tokenizer')) {
class PhpToken extends Symfony\Polyfill\Php80\PhpToken
{
}
}

View File

@@ -1,5 +1,14 @@
<?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 (\PHP_VERSION_ID < 80000) {
interface Stringable
{

View File

@@ -1,5 +1,14 @@
<?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 (\PHP_VERSION_ID < 80000) {
class UnhandledMatchError extends Error
{

View File

@@ -1,5 +1,14 @@
<?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 (\PHP_VERSION_ID < 80000) {
class ValueError extends Error
{

View File

@@ -29,9 +29,6 @@
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-main": "1.23-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"

View File

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

View File

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

View File

@@ -16,6 +16,8 @@ interface LocaleAwareInterface
/**
* Sets the current locale.
*
* @return void
*
* @throws \InvalidArgumentException If the locale contains invalid characters
*/
public function setLocale(string $locale);

View File

@@ -3,7 +3,7 @@ Symfony Translation Contracts
A set of abstractions extracted out of the Symfony components.
Can be used to build on semantics that the Symfony components proved useful - and
Can be used to build on semantics that the Symfony components proved useful and
that already have battle tested implementations.
See https://github.com/symfony/contracts/blob/main/README.md for more information.

View File

@@ -30,7 +30,7 @@ use Symfony\Contracts\Translation\TranslatorTrait;
*/
class TranslatorTest extends TestCase
{
private $defaultLocale;
private string $defaultLocale;
protected function setUp(): void
{
@@ -114,7 +114,7 @@ class TranslatorTest extends TestCase
$this->assertEquals('en', $translator->getLocale());
}
public function getTransTests()
public static function getTransTests()
{
return [
['Symfony is great!', 'Symfony is great!', []],
@@ -122,7 +122,7 @@ class TranslatorTest extends TestCase
];
}
public function getTransChoiceTests()
public static function getTransChoiceTests()
{
return [
['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0],
@@ -137,7 +137,7 @@ class TranslatorTest extends TestCase
}
/**
* @dataProvider getInternal
* @dataProvider getInterval
*/
public function testInterval($expected, $number, $interval)
{
@@ -146,7 +146,7 @@ class TranslatorTest extends TestCase
$this->assertEquals($expected, $translator->trans($interval.' foo|[1,Inf[ bar', ['%count%' => $number]));
}
public function getInternal()
public static function getInterval()
{
return [
['foo', 3, '{1,2, 3 ,4}'],
@@ -183,13 +183,14 @@ class TranslatorTest extends TestCase
*/
public function testThrowExceptionIfMatchingMessageCannotBeFound($id, $number)
{
$this->expectException(\InvalidArgumentException::class);
$translator = $this->getTranslator();
$this->expectException(\InvalidArgumentException::class);
$translator->trans($id, ['%count%' => $number]);
}
public function getNonMatchingMessages()
public static function getNonMatchingMessages()
{
return [
['{0} There are no apples|{1} There is one apple', 2],
@@ -199,7 +200,7 @@ class TranslatorTest extends TestCase
];
}
public function getChooseTests()
public static function getChooseTests()
{
return [
['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0],
@@ -255,13 +256,13 @@ class TranslatorTest extends TestCase
new-line in it. Selector = 0.|{1}This is a text with a
new-line in it. Selector = 1.|[1,Inf]This is a text with a
new-line in it. Selector > 1.', 5],
// with double-quotes and id split accros lines
// with double-quotes and id split across lines
['This is a text with a
new-line in it. Selector = 1.', '{0}This is a text with a
new-line in it. Selector = 0.|{1}This is a text with a
new-line in it. Selector = 1.|[1,Inf]This is a text with a
new-line in it. Selector > 1.', 1],
// with single-quotes and id split accros lines
// with single-quotes and id split across lines
['This is a text with a
new-line in it. Selector > 1.', '{0}This is a text with a
new-line in it. Selector = 0.|{1}This is a text with a
@@ -269,9 +270,9 @@ class TranslatorTest extends TestCase
new-line in it. Selector > 1.', 5],
// with single-quotes and \n in text
['This is a text with a\nnew-line in it. Selector = 0.', '{0}This is a text with a\nnew-line in it. Selector = 0.|{1}This is a text with a\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\nnew-line in it. Selector > 1.', 0],
// with double-quotes and id split accros lines
// with double-quotes and id split across lines
["This is a text with a\nnew-line in it. Selector = 1.", "{0}This is a text with a\nnew-line in it. Selector = 0.|{1}This is a text with a\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\nnew-line in it. Selector > 1.", 1],
// esacape pipe
// escape pipe
['This is a text with | in it. Selector = 0.', '{0}This is a text with || in it. Selector = 0.|{1}This is a text with || in it. Selector = 1.', 0],
// Empty plural set (2 plural forms) from a .PO file
['', '|', 1],
@@ -315,7 +316,7 @@ class TranslatorTest extends TestCase
*
* As it is impossible to have this ever complete we should try as hard as possible to have it almost complete.
*/
public function successLangcodes(): array
public static function successLangcodes(): array
{
return [
['1', ['ay', 'bo', 'cgg', 'dz', 'id', 'ja', 'jbo', 'ka', 'kk', 'km', 'ko', 'ky']],
@@ -334,7 +335,7 @@ class TranslatorTest extends TestCase
*
* @return array with nplural together with langcodes
*/
public function failingLangcodes(): array
public static function failingLangcodes(): array
{
return [
['1', ['fa']],
@@ -356,7 +357,7 @@ class TranslatorTest extends TestCase
foreach ($matrix as $langCode => $data) {
$indexes = array_flip($data);
if ($expectSuccess) {
$this->assertEquals($nplural, \count($indexes), "Langcode '$langCode' has '$nplural' plural forms.");
$this->assertCount($nplural, $indexes, "Langcode '$langCode' has '$nplural' plural forms.");
} else {
$this->assertNotEquals((int) $nplural, \count($indexes), "Langcode '$langCode' has '$nplural' plural forms.");
}

View File

@@ -23,24 +23,18 @@ trait TranslatorTrait
private ?string $locale = null;
/**
* {@inheritdoc}
* @return void
*/
public function setLocale(string $locale)
{
$this->locale = $locale;
}
/**
* {@inheritdoc}
*/
public function getLocale(): string
{
return $this->locale ?: (class_exists(\Locale::class) ? \Locale::getDefault() : 'en');
}
/**
* {@inheritdoc}
*/
public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null): string
{
if (null === $id || '' === $id) {
@@ -140,121 +134,92 @@ EOF;
{
$number = abs($number);
switch ('pt_BR' !== $locale && 'en_US_POSIX' !== $locale && \strlen($locale) > 3 ? substr($locale, 0, strrpos($locale, '_')) : $locale) {
case 'af':
case 'bn':
case 'bg':
case 'ca':
case 'da':
case 'de':
case 'el':
case 'en':
case 'en_US_POSIX':
case 'eo':
case 'es':
case 'et':
case 'eu':
case 'fa':
case 'fi':
case 'fo':
case 'fur':
case 'fy':
case 'gl':
case 'gu':
case 'ha':
case 'he':
case 'hu':
case 'is':
case 'it':
case 'ku':
case 'lb':
case 'ml':
case 'mn':
case 'mr':
case 'nah':
case 'nb':
case 'ne':
case 'nl':
case 'nn':
case 'no':
case 'oc':
case 'om':
case 'or':
case 'pa':
case 'pap':
case 'ps':
case 'pt':
case 'so':
case 'sq':
case 'sv':
case 'sw':
case 'ta':
case 'te':
case 'tk':
case 'ur':
case 'zu':
return (1 == $number) ? 0 : 1;
case 'am':
case 'bh':
case 'fil':
case 'fr':
case 'gun':
case 'hi':
case 'hy':
case 'ln':
case 'mg':
case 'nso':
case 'pt_BR':
case 'ti':
case 'wa':
return ($number < 2) ? 0 : 1;
case 'be':
case 'bs':
case 'hr':
case 'ru':
case 'sh':
case 'sr':
case 'uk':
return ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2);
case 'cs':
case 'sk':
return (1 == $number) ? 0 : ((($number >= 2) && ($number <= 4)) ? 1 : 2);
case 'ga':
return (1 == $number) ? 0 : ((2 == $number) ? 1 : 2);
case 'lt':
return ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2);
case 'sl':
return (1 == $number % 100) ? 0 : ((2 == $number % 100) ? 1 : (((3 == $number % 100) || (4 == $number % 100)) ? 2 : 3));
case 'mk':
return (1 == $number % 10) ? 0 : 1;
case 'mt':
return (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 1) && ($number % 100 < 11))) ? 1 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 2 : 3));
case 'lv':
return (0 == $number) ? 0 : (((1 == $number % 10) && (11 != $number % 100)) ? 1 : 2);
case 'pl':
return (1 == $number) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 1 : 2);
case 'cy':
return (1 == $number) ? 0 : ((2 == $number) ? 1 : (((8 == $number) || (11 == $number)) ? 2 : 3));
case 'ro':
return (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 0) && ($number % 100 < 20))) ? 1 : 2);
case 'ar':
return (0 == $number) ? 0 : ((1 == $number) ? 1 : ((2 == $number) ? 2 : ((($number % 100 >= 3) && ($number % 100 <= 10)) ? 3 : ((($number % 100 >= 11) && ($number % 100 <= 99)) ? 4 : 5))));
default:
return 0;
}
return match ('pt_BR' !== $locale && 'en_US_POSIX' !== $locale && \strlen($locale) > 3 ? substr($locale, 0, strrpos($locale, '_')) : $locale) {
'af',
'bn',
'bg',
'ca',
'da',
'de',
'el',
'en',
'en_US_POSIX',
'eo',
'es',
'et',
'eu',
'fa',
'fi',
'fo',
'fur',
'fy',
'gl',
'gu',
'ha',
'he',
'hu',
'is',
'it',
'ku',
'lb',
'ml',
'mn',
'mr',
'nah',
'nb',
'ne',
'nl',
'nn',
'no',
'oc',
'om',
'or',
'pa',
'pap',
'ps',
'pt',
'so',
'sq',
'sv',
'sw',
'ta',
'te',
'tk',
'ur',
'zu' => (1 == $number) ? 0 : 1,
'am',
'bh',
'fil',
'fr',
'gun',
'hi',
'hy',
'ln',
'mg',
'nso',
'pt_BR',
'ti',
'wa' => ($number < 2) ? 0 : 1,
'be',
'bs',
'hr',
'ru',
'sh',
'sr',
'uk' => ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2),
'cs',
'sk' => (1 == $number) ? 0 : ((($number >= 2) && ($number <= 4)) ? 1 : 2),
'ga' => (1 == $number) ? 0 : ((2 == $number) ? 1 : 2),
'lt' => ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2),
'sl' => (1 == $number % 100) ? 0 : ((2 == $number % 100) ? 1 : (((3 == $number % 100) || (4 == $number % 100)) ? 2 : 3)),
'mk' => (1 == $number % 10) ? 0 : 1,
'mt' => (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 1) && ($number % 100 < 11))) ? 1 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 2 : 3)),
'lv' => (0 == $number) ? 0 : (((1 == $number % 10) && (11 != $number % 100)) ? 1 : 2),
'pl' => (1 == $number) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 1 : 2),
'cy' => (1 == $number) ? 0 : ((2 == $number) ? 1 : (((8 == $number) || (11 == $number)) ? 2 : 3)),
'ro' => (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 0) && ($number % 100 < 20))) ? 1 : 2),
'ar' => (0 == $number) ? 0 : ((1 == $number) ? 1 : ((2 == $number) ? 2 : ((($number % 100 >= 3) && ($number % 100 <= 10)) ? 3 : ((($number % 100 >= 11) && ($number % 100 <= 99)) ? 4 : 5)))),
default => 0,
};
}
}

View File

@@ -16,18 +16,18 @@
}
],
"require": {
"php": ">=8.0.2"
},
"suggest": {
"symfony/translation-implementation": ""
"php": ">=8.1"
},
"autoload": {
"psr-4": { "Symfony\\Contracts\\Translation\\": "" }
"psr-4": { "Symfony\\Contracts\\Translation\\": "" },
"exclude-from-classmap": [
"/Test/"
]
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-main": "3.0-dev"
"dev-main": "3.4-dev"
},
"thanks": {
"name": "symfony/contracts",

View File

@@ -1,6 +1,35 @@
CHANGELOG
=========
6.4
---
* Give current locale to `LocaleSwitcher::runWithLocale()`'s callback
* Add `--as-tree` option to `translation:pull` command to write YAML messages as a tree-like structure
* [BC BREAK] Add argument `$buildDir` to `DataCollectorTranslator::warmUp()`
* Add `DataCollectorTranslatorPass` and `LoggingTranslatorPass` (moved from `FrameworkBundle`)
* Add `PhraseTranslationProvider`
6.2.7
-----
* [BC BREAK] The following data providers for `ProviderFactoryTestCase` are now static:
`supportsProvider()`, `createProvider()`, `unsupportedSchemeProvider()`and `incompleteDsnProvider()`
* [BC BREAK] `ProviderTestCase::toStringProvider()` is now static
6.2
---
* Deprecate `PhpStringTokenParser`
* Deprecate `PhpExtractor` in favor of `PhpAstExtractor`
* Add `PhpAstExtractor` (requires [nikic/php-parser](https://github.com/nikic/php-parser) to be installed)
6.1
---
* Parameters implementing `TranslatableInterface` are processed
* Add the file extension to the `XliffFileDumper` constructor
5.4
---

View File

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

View File

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

View File

@@ -26,7 +26,7 @@ use Symfony\Component\Translation\MessageCatalogueInterface;
class TargetOperation extends AbstractOperation
{
/**
* {@inheritdoc}
* @return void
*/
protected function processDomain(string $domain)
{
@@ -37,6 +37,18 @@ class TargetOperation extends AbstractOperation
];
$intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX;
foreach ($this->target->getCatalogueMetadata('', $domain) ?? [] as $key => $value) {
if (null === $this->result->getCatalogueMetadata($key, $domain)) {
$this->result->setCatalogueMetadata($key, $value, $domain);
}
}
foreach ($this->target->getCatalogueMetadata('', $intlDomain) ?? [] as $key => $value) {
if (null === $this->result->getCatalogueMetadata($key, $intlDomain)) {
$this->result->setCatalogueMetadata($key, $value, $intlDomain);
}
}
// For 'all' messages, the code can't be simplified as ``$this->messages[$domain]['all'] = $target->all($domain);``,
// because doing so will drop messages like {x: x ∈ source ∧ x ∉ target.all ∧ x ∈ target.fallback}
//

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation;
/**
* This interface is used to get, set, and delete metadata about the Catalogue.
*
* @author Hugo Alliaume <hugo@alliau.me>
*/
interface CatalogueMetadataAwareInterface
{
/**
* Gets catalogue metadata for the given domain and key.
*
* Passing an empty domain will return an array with all catalogue metadata indexed by
* domain and then by key. Passing an empty key will return an array with all
* catalogue metadata for the given domain.
*
* @return mixed The value that was set or an array with the domains/keys or null
*/
public function getCatalogueMetadata(string $key = '', string $domain = 'messages'): mixed;
/**
* Adds catalogue metadata to a message domain.
*
* @return void
*/
public function setCatalogueMetadata(string $key, mixed $value, string $domain = 'messages');
/**
* Deletes catalogue metadata for the given key and domain.
*
* Passing an empty domain will delete all catalogue metadata. Passing an empty key will
* delete all metadata for the given domain.
*
* @return void
*/
public function deleteCatalogueMetadata(string $key = '', string $domain = 'messages');
}

View File

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

View File

@@ -21,6 +21,7 @@ use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Translation\Provider\FilteringProvider;
use Symfony\Component\Translation\Provider\TranslationProviderCollection;
use Symfony\Component\Translation\Reader\TranslationReaderInterface;
use Symfony\Component\Translation\TranslatorBag;
@@ -33,8 +34,8 @@ final class TranslationPushCommand extends Command
{
use TranslationTrait;
private $providers;
private $reader;
private TranslationProviderCollection $providers;
private TranslationReaderInterface $reader;
private array $transPaths;
private array $enabledLocales;
@@ -72,10 +73,7 @@ final class TranslationPushCommand extends Command
}
}
/**
* {@inheritdoc}
*/
protected function configure()
protected function configure(): void
{
$keys = $this->providers->keys();
$defaultProvider = 1 === \count($keys) ? $keys[0] : null;
@@ -112,15 +110,12 @@ EOF
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$provider = $this->providers->get($input->getArgument('provider'));
if (!$this->enabledLocales) {
throw new InvalidArgumentException(sprintf('You must define "framework.translator.enabled_locales" or "framework.translator.providers.%s.locales" config key in order to work with translation providers.', parse_url($provider, \PHP_URL_SCHEME)));
throw new InvalidArgumentException(sprintf('You must define "framework.enabled_locales" or "framework.translator.providers.%s.locales" config key in order to work with translation providers.', parse_url($provider, \PHP_URL_SCHEME)));
}
$io = new SymfonyStyle($input, $output);
@@ -129,6 +124,12 @@ EOF
$force = $input->getOption('force');
$deleteMissing = $input->getOption('delete-missing');
if (!$domains && $provider instanceof FilteringProvider) {
$domains = $provider->getDomains();
}
// Reading local translations must be done after retrieving the domains from the provider
// in order to manage only translations from configured domains
$localTranslations = $this->readLocalTranslations($locales, $domains, $this->transPaths);
if (!$domains) {

View File

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

View File

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

View File

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

View File

@@ -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\DependencyInjection;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Translation\TranslatorBagInterface;
/**
* @author Christian Flothmann <christian.flothmann@sensiolabs.de>
*/
class DataCollectorTranslatorPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (!$container->has('translator')) {
return;
}
$translatorClass = $container->getParameterBag()->resolveValue($container->findDefinition('translator')->getClass());
if (!is_subclass_of($translatorClass, TranslatorBagInterface::class)) {
$container->removeDefinition('translator.data_collector');
$container->removeDefinition('data_collector.translation');
}
}
}

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\DependencyInjection;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
use Symfony\Component\Translation\TranslatorBagInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*/
class LoggingTranslatorPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
if (!$container->hasAlias('logger') || !$container->hasAlias('translator')) {
return;
}
if (!$container->hasParameter('translator.logging') || !$container->getParameter('translator.logging')) {
return;
}
$translatorAlias = $container->getAlias('translator');
$definition = $container->getDefinition((string) $translatorAlias);
$class = $container->getParameterBag()->resolveValue($definition->getClass());
if (!$r = $container->getReflectionClass($class)) {
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $class, $translatorAlias));
}
if (!$r->isSubclassOf(TranslatorInterface::class) || !$r->isSubclassOf(TranslatorBagInterface::class)) {
return;
}
$container->getDefinition('translator.logging')->setDecoratedService('translator');
$warmer = $container->getDefinition('translation.warmer');
$subscriberAttributes = $warmer->getTag('container.service_subscriber');
$warmer->clearTag('container.service_subscriber');
foreach ($subscriberAttributes as $k => $v) {
if ((!isset($v['id']) || 'translator' !== $v['id']) && (!isset($v['key']) || 'translator' !== $v['key'])) {
$warmer->addTag('container.service_subscriber', $v);
}
}
$warmer->addTag('container.service_subscriber', ['key' => 'translator', 'id' => 'translator.logging.inner']);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -20,14 +20,8 @@ use Symfony\Component\Translation\MessageCatalogue;
*/
class IcuResFileDumper extends FileDumper
{
/**
* {@inheritdoc}
*/
protected $relativePathTemplate = '%domain%/%locale%.%extension%';
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
{
$data = $indexes = $resources = '';
@@ -47,7 +41,7 @@ class IcuResFileDumper extends FileDumper
$data .= pack('V', \strlen($target))
.mb_convert_encoding($target."\0", 'UTF-16LE', 'UTF-8')
.$this->writePadding($data)
;
;
}
$resOffset = $this->getPosition($data);
@@ -56,7 +50,7 @@ class IcuResFileDumper extends FileDumper
.$indexes
.$this->writePadding($data)
.$resources
;
;
$bundleTop = $this->getPosition($data);
@@ -89,14 +83,11 @@ class IcuResFileDumper extends FileDumper
return $padding ? str_repeat("\xAA", 4 - $padding) : null;
}
private function getPosition(string $data)
private function getPosition(string $data): float|int
{
return (\strlen($data) + 28) / 4;
}
/**
* {@inheritdoc}
*/
protected function getExtension(): string
{
return 'res';

View File

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

View File

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

View File

@@ -21,9 +21,6 @@ use Symfony\Component\Translation\MessageCatalogue;
*/
class MoFileDumper extends FileDumper
{
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
{
$sources = $targets = $sourceOffsets = $targetOffsets = '';
@@ -57,19 +54,16 @@ class MoFileDumper extends FileDumper
.$this->writeLong($offset[2] + $sourcesStart + $sourcesSize);
}
$output = implode('', array_map([$this, 'writeLong'], $header))
$output = implode('', array_map($this->writeLong(...), $header))
.$sourceOffsets
.$targetOffsets
.$sources
.$targets
;
;
return $output;
}
/**
* {@inheritdoc}
*/
protected function getExtension(): string
{
return 'mo';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,85 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Extractor;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor;
use PhpParser\Parser;
use PhpParser\ParserFactory;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Translation\Extractor\Visitor\AbstractVisitor;
use Symfony\Component\Translation\MessageCatalogue;
/**
* PhpAstExtractor extracts translation messages from a PHP AST.
*
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*/
final class PhpAstExtractor extends AbstractFileExtractor implements ExtractorInterface
{
private Parser $parser;
public function __construct(
/**
* @param iterable<AbstractVisitor&NodeVisitor> $visitors
*/
private readonly iterable $visitors,
private string $prefix = '',
) {
if (!class_exists(ParserFactory::class)) {
throw new \LogicException(sprintf('You cannot use "%s" as the "nikic/php-parser" package is not installed. Try running "composer require nikic/php-parser".', static::class));
}
$this->parser = (new ParserFactory())->createForHostVersion();
}
public function extract(iterable|string $resource, MessageCatalogue $catalogue): void
{
foreach ($this->extractFiles($resource) as $file) {
$traverser = new NodeTraverser();
// This is needed to resolve namespaces in class methods/constants.
$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);
}
$nodes = $this->parser->parse(file_get_contents($file));
$traverser->traverse($nodes);
}
}
public function setPrefix(string $prefix): void
{
$this->prefix = $prefix;
}
protected function canBeExtracted(string $file): bool
{
return 'php' === pathinfo($file, \PATHINFO_EXTENSION)
&& $this->isFile($file)
&& preg_match('/\bt\(|->trans\(|TranslatableMessage|Symfony\\\\Component\\\\Validator\\\\Constraints/i', file_get_contents($file));
}
protected function extractFromDirectory(array|string $resource): iterable|Finder
{
if (!class_exists(Finder::class)) {
throw new \LogicException(sprintf('You cannot use "%s" as the "symfony/finder" package is not installed. Try running "composer require symfony/finder".', static::class));
}
return (new Finder())->files()->name('*.php')->in($resource);
}
}

View File

@@ -11,6 +11,8 @@
namespace Symfony\Component\Translation\Extractor;
trigger_deprecation('symfony/translation', '6.2', '"%s" is deprecated, use "%s" instead.', PhpExtractor::class, PhpAstExtractor::class);
use Symfony\Component\Finder\Finder;
use Symfony\Component\Translation\MessageCatalogue;
@@ -18,6 +20,8 @@ use Symfony\Component\Translation\MessageCatalogue;
* PhpExtractor extracts translation messages from a PHP template.
*
* @author Michel Salib <michelsalib@hotmail.com>
*
* @deprecated since Symfony 6.2, use the PhpAstExtractor instead
*/
class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
{
@@ -129,7 +133,7 @@ class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
];
/**
* {@inheritdoc}
* @return void
*/
public function extract(string|iterable $resource, MessageCatalogue $catalog)
{
@@ -142,7 +146,7 @@ class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
}
/**
* {@inheritdoc}
* @return void
*/
public function setPrefix(string $prefix)
{
@@ -164,7 +168,7 @@ class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
/**
* Seeks to a non-whitespace token.
*/
private function seekToNextRelevantToken(\Iterator $tokenIterator)
private function seekToNextRelevantToken(\Iterator $tokenIterator): void
{
for (; $tokenIterator->valid(); $tokenIterator->next()) {
$t = $tokenIterator->current();
@@ -174,7 +178,7 @@ class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
}
}
private function skipMethodArgument(\Iterator $tokenIterator)
private function skipMethodArgument(\Iterator $tokenIterator): void
{
$openBraces = 0;
@@ -199,7 +203,7 @@ class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
* Extracts the message from the iterator while the tokens
* match allowed message tokens.
*/
private function getValue(\Iterator $tokenIterator)
private function getValue(\Iterator $tokenIterator): string
{
$message = '';
$docToken = '';
@@ -257,6 +261,8 @@ class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
/**
* Extracts trans message from PHP tokens.
*
* @return void
*/
protected function parseTokens(array $tokens, MessageCatalogue $catalog, string $filename)
{
@@ -314,9 +320,6 @@ class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
return $this->isFile($file) && 'php' === pathinfo($file, \PATHINFO_EXTENSION);
}
/**
* {@inheritdoc}
*/
protected function extractFromDirectory(string|array $directory): iterable
{
if (!class_exists(Finder::class)) {

View File

@@ -11,6 +11,8 @@
namespace Symfony\Component\Translation\Extractor;
trigger_deprecation('symfony/translation', '6.2', '"%s" is deprecated.', PhpStringTokenParser::class);
/*
* The following is derived from code at http://github.com/nikic/PHP-Parser
*
@@ -47,6 +49,9 @@ namespace Symfony\Component\Translation\Extractor;
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @deprecated since Symfony 6.2
*/
class PhpStringTokenParser
{
protected static $replacements = [
@@ -89,7 +94,7 @@ class PhpStringTokenParser
* @param string $str String without quotes
* @param string|null $quote Quote type
*/
public static function parseEscapeSequences(string $str, string $quote = null): string
public static function parseEscapeSequences(string $str, ?string $quote = null): string
{
if (null !== $quote) {
$str = str_replace('\\'.$quote, $quote, $str);

View File

@@ -0,0 +1,135 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Extractor\Visitor;
use PhpParser\Node;
use Symfony\Component\Translation\MessageCatalogue;
/**
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*/
abstract class AbstractVisitor
{
private MessageCatalogue $catalogue;
private \SplFileInfo $file;
private string $messagePrefix;
public function initialize(MessageCatalogue $catalogue, \SplFileInfo $file, string $messagePrefix): void
{
$this->catalogue = $catalogue;
$this->file = $file;
$this->messagePrefix = $messagePrefix;
}
protected function addMessageToCatalogue(string $message, ?string $domain, int $line): void
{
$domain ??= 'messages';
$this->catalogue->set($message, $this->messagePrefix.$message, $domain);
$metadata = $this->catalogue->getMetadata($message, $domain) ?? [];
$normalizedFilename = preg_replace('{[\\\\/]+}', '/', $this->file);
$metadata['sources'][] = $normalizedFilename.':'.$line;
$this->catalogue->setMetadata($message, $metadata, $domain);
}
protected function getStringArguments(Node\Expr\CallLike|Node\Attribute|Node\Expr\New_ $node, int|string $index, bool $indexIsRegex = false): array
{
if (\is_string($index)) {
return $this->getStringNamedArguments($node, $index, $indexIsRegex);
}
$args = $node instanceof Node\Expr\CallLike ? $node->getRawArgs() : $node->args;
if (!($arg = $args[$index] ?? null) instanceof Node\Arg) {
return [];
}
return (array) $this->getStringValue($arg->value);
}
protected function hasNodeNamedArguments(Node\Expr\CallLike|Node\Attribute|Node\Expr\New_ $node): bool
{
$args = $node instanceof Node\Expr\CallLike ? $node->getRawArgs() : $node->args;
foreach ($args as $arg) {
if ($arg instanceof Node\Arg && null !== $arg->name) {
return true;
}
}
return false;
}
protected function nodeFirstNamedArgumentIndex(Node\Expr\CallLike|Node\Attribute|Node\Expr\New_ $node): int
{
$args = $node instanceof Node\Expr\CallLike ? $node->getRawArgs() : $node->args;
foreach ($args as $i => $arg) {
if ($arg instanceof Node\Arg && null !== $arg->name) {
return $i;
}
}
return \PHP_INT_MAX;
}
private function getStringNamedArguments(Node\Expr\CallLike|Node\Attribute $node, ?string $argumentName = null, bool $isArgumentNamePattern = false): array
{
$args = $node instanceof Node\Expr\CallLike ? $node->getArgs() : $node->args;
$argumentValues = [];
foreach ($args as $arg) {
if (!$isArgumentNamePattern && $arg->name?->toString() === $argumentName) {
$argumentValues[] = $this->getStringValue($arg->value);
} elseif ($isArgumentNamePattern && preg_match($argumentName, $arg->name?->toString() ?? '') > 0) {
$argumentValues[] = $this->getStringValue($arg->value);
}
}
return array_filter($argumentValues);
}
private function getStringValue(Node $node): ?string
{
if ($node instanceof Node\Scalar\String_) {
return $node->value;
}
if ($node instanceof Node\Expr\BinaryOp\Concat) {
if (null === $left = $this->getStringValue($node->left)) {
return null;
}
if (null === $right = $this->getStringValue($node->right)) {
return null;
}
return $left.$right;
}
if ($node instanceof Node\Expr\Assign && $node->expr instanceof Node\Scalar\String_) {
return $node->expr->value;
}
if ($node instanceof Node\Expr\ClassConstFetch) {
try {
$reflection = new \ReflectionClass($node->class->toString());
$constant = $reflection->getReflectionConstant($node->name->toString());
if (false !== $constant && \is_string($constant->getValue())) {
return $constant->getValue();
}
} catch (\ReflectionException) {
}
}
return null;
}
}

View File

@@ -0,0 +1,112 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Extractor\Visitor;
use PhpParser\Node;
use PhpParser\NodeVisitor;
/**
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*
* Code mostly comes from https://github.com/php-translation/extractor/blob/master/src/Visitor/Php/Symfony/Constraint.php
*/
final class ConstraintVisitor extends AbstractVisitor implements NodeVisitor
{
public function __construct(
private readonly array $constraintClassNames = []
) {
}
public function beforeTraverse(array $nodes): ?Node
{
return null;
}
public function enterNode(Node $node): ?Node
{
return null;
}
public function leaveNode(Node $node): ?Node
{
if (!$node instanceof Node\Expr\New_ && !$node instanceof Node\Attribute) {
return null;
}
$className = $node instanceof Node\Attribute ? $node->name : $node->class;
if (!$className instanceof Node\Name) {
return null;
}
$parts = $className->getParts();
$isConstraintClass = false;
foreach ($parts as $part) {
if (\in_array($part, $this->constraintClassNames, true)) {
$isConstraintClass = true;
break;
}
}
if (!$isConstraintClass) {
return null;
}
$arg = $node->args[0] ?? null;
if (!$arg instanceof Node\Arg) {
return null;
}
if ($this->hasNodeNamedArguments($node)) {
$messages = $this->getStringArguments($node, '/message/i', true);
} else {
if (!$arg->value instanceof Node\Expr\Array_) {
// There is no way to guess which argument is a message to be translated.
return null;
}
$messages = [];
$options = $arg->value;
/** @var Node\Expr\ArrayItem $item */
foreach ($options->items as $item) {
if (!$item->key instanceof Node\Scalar\String_) {
continue;
}
if (false === stripos($item->key->value ?? '', 'message')) {
continue;
}
if (!$item->value instanceof Node\Scalar\String_) {
continue;
}
$messages[] = $item->value->value;
break;
}
}
foreach ($messages as $message) {
$this->addMessageToCatalogue($message, 'validators', $node->getStartLine());
}
return null;
}
public function afterTraverse(array $nodes): ?Node
{
return null;
}
}

View File

@@ -0,0 +1,65 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Extractor\Visitor;
use PhpParser\Node;
use PhpParser\NodeVisitor;
/**
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*/
final class TransMethodVisitor extends AbstractVisitor implements NodeVisitor
{
public function beforeTraverse(array $nodes): ?Node
{
return null;
}
public function enterNode(Node $node): ?Node
{
return null;
}
public function leaveNode(Node $node): ?Node
{
if (!$node instanceof Node\Expr\MethodCall && !$node instanceof Node\Expr\FuncCall) {
return null;
}
if (!\is_string($node->name) && !$node->name instanceof Node\Identifier && !$node->name instanceof Node\Name) {
return null;
}
$name = (string) $node->name;
if ('trans' === $name || 't' === $name) {
$firstNamedArgumentIndex = $this->nodeFirstNamedArgumentIndex($node);
if (!$messages = $this->getStringArguments($node, 0 < $firstNamedArgumentIndex ? 0 : 'message')) {
return null;
}
$domain = $this->getStringArguments($node, 2 < $firstNamedArgumentIndex ? 2 : 'domain')[0] ?? null;
foreach ($messages as $message) {
$this->addMessageToCatalogue($message, $domain, $node->getStartLine());
}
}
return null;
}
public function afterTraverse(array $nodes): ?Node
{
return null;
}
}

View File

@@ -0,0 +1,65 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Extractor\Visitor;
use PhpParser\Node;
use PhpParser\NodeVisitor;
/**
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*/
final class TranslatableMessageVisitor extends AbstractVisitor implements NodeVisitor
{
public function beforeTraverse(array $nodes): ?Node
{
return null;
}
public function enterNode(Node $node): ?Node
{
return null;
}
public function leaveNode(Node $node): ?Node
{
if (!$node instanceof Node\Expr\New_) {
return null;
}
if (!($className = $node->class) instanceof Node\Name) {
return null;
}
if (!\in_array('TranslatableMessage', $className->getParts(), true)) {
return null;
}
$firstNamedArgumentIndex = $this->nodeFirstNamedArgumentIndex($node);
if (!$messages = $this->getStringArguments($node, 0 < $firstNamedArgumentIndex ? 0 : 'message')) {
return null;
}
$domain = $this->getStringArguments($node, 2 < $firstNamedArgumentIndex ? 2 : 'domain')[0] ?? null;
foreach ($messages as $message) {
$this->addMessageToCatalogue($message, $domain, $node->getStartLine());
}
return null;
}
public function afterTraverse(array $nodes): ?Node
{
return null;
}
}

View File

@@ -20,12 +20,9 @@ use Symfony\Component\Translation\Exception\LogicException;
*/
class IntlFormatter implements IntlFormatterInterface
{
private $hasMessageFormatter;
private $cache = [];
private bool $hasMessageFormatter;
private array $cache = [];
/**
* {@inheritdoc}
*/
public function formatIntl(string $message, string $locale, array $parameters = []): string
{
// MessageFormatter constructor throws an exception if the message is empty
@@ -34,7 +31,7 @@ class IntlFormatter implements IntlFormatterInterface
}
if (!$formatter = $this->cache[$locale][$message] ?? null) {
if (!($this->hasMessageFormatter ?? $this->hasMessageFormatter = class_exists(\MessageFormatter::class))) {
if (!$this->hasMessageFormatter ??= class_exists(\MessageFormatter::class)) {
throw new LogicException('Cannot parse message translation: please install the "intl" PHP extension or the "symfony/polyfill-intl-messageformatter" package.');
}
try {

View File

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

View File

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

View File

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

View File

@@ -24,9 +24,6 @@ class CsvFileLoader extends FileLoader
private string $enclosure = '"';
private string $escape = '\\';
/**
* {@inheritdoc}
*/
protected function loadResource(string $resource): array
{
$messages = [];
@@ -45,7 +42,7 @@ class CsvFileLoader extends FileLoader
continue;
}
if ('#' !== substr($data[0], 0, 1) && isset($data[1]) && 2 === \count($data)) {
if (!str_starts_with($data[0], '#') && isset($data[1]) && 2 === \count($data)) {
$messages[$data[0]] = $data[1];
}
}
@@ -55,6 +52,8 @@ class CsvFileLoader extends FileLoader
/**
* Sets the delimiter, enclosure, and escape character for CSV.
*
* @return void
*/
public function setCsvControl(string $delimiter = ';', string $enclosure = '"', string $escape = '\\')
{

View File

@@ -21,9 +21,6 @@ use Symfony\Component\Translation\MessageCatalogue;
*/
abstract class FileLoader extends ArrayLoader
{
/**
* {@inheritdoc}
*/
public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue
{
if (!stream_is_local($resource)) {
@@ -37,9 +34,7 @@ abstract class FileLoader extends ArrayLoader
$messages = $this->loadResource($resource);
// empty resource
if (null === $messages) {
$messages = [];
}
$messages ??= [];
// not an array
if (!\is_array($messages)) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -20,12 +20,9 @@ class PhpFileLoader extends FileLoader
{
private static ?array $cache = [];
/**
* {@inheritdoc}
*/
protected function loadResource(string $resource): array
{
if ([] === self::$cache && \function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) || filter_var(ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOLEAN))) {
if ([] === self::$cache && \function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOL) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) || filter_var(\ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOL))) {
self::$cache = null;
}
@@ -33,10 +30,6 @@ class PhpFileLoader extends FileLoader
return require $resource;
}
if (isset(self::$cache[$resource])) {
return self::$cache[$resource];
}
return self::$cache[$resource] = require $resource;
return self::$cache[$resource] ??= require $resource;
}
}

View File

@@ -57,8 +57,6 @@ class PoFileLoader extends FileLoader
* - Message IDs are allowed to have other encodings as just US-ASCII.
*
* Items with an empty id are ignored.
*
* {@inheritdoc}
*/
protected function loadResource(string $resource): array
{
@@ -83,15 +81,15 @@ class PoFileLoader extends FileLoader
}
$item = $defaults;
$flags = [];
} elseif ('#,' === substr($line, 0, 2)) {
} elseif (str_starts_with($line, '#,')) {
$flags = array_map('trim', explode(',', substr($line, 2)));
} elseif ('msgid "' === substr($line, 0, 7)) {
} elseif (str_starts_with($line, 'msgid "')) {
// We start a new msg so save previous
// TODO: this fails when comments or contexts are added
$this->addMessage($messages, $item);
$item = $defaults;
$item['ids']['singular'] = substr($line, 7, -1);
} elseif ('msgstr "' === substr($line, 0, 8)) {
} elseif (str_starts_with($line, 'msgstr "')) {
$item['translated'] = substr($line, 8, -1);
} elseif ('"' === $line[0]) {
$continues = isset($item['translated']) ? 'translated' : 'ids';
@@ -102,9 +100,9 @@ class PoFileLoader extends FileLoader
} else {
$item[$continues] .= substr($line, 1, -1);
}
} elseif ('msgid_plural "' === substr($line, 0, 14)) {
} elseif (str_starts_with($line, 'msgid_plural "')) {
$item['ids']['plural'] = substr($line, 14, -1);
} elseif ('msgstr[' === substr($line, 0, 7)) {
} elseif (str_starts_with($line, 'msgstr[')) {
$size = strpos($line, ']');
$item['translated'][(int) substr($line, 7, 1)] = substr($line, $size + 3, -1);
}
@@ -124,7 +122,7 @@ class PoFileLoader extends FileLoader
* A .po file could contain by error missing plural indexes. We need to
* fix these before saving them.
*/
private function addMessage(array &$messages, array $item)
private function addMessage(array &$messages, array $item): void
{
if (!empty($item['ids']['singular'])) {
$id = stripcslashes($item['ids']['singular']);

View File

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

View File

@@ -28,9 +28,6 @@ use Symfony\Component\Translation\Util\XliffUtils;
*/
class XliffFileLoader implements LoaderInterface
{
/**
* {@inheritdoc}
*/
public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue
{
if (!class_exists(XmlUtils::class)) {
@@ -75,7 +72,7 @@ class XliffFileLoader implements LoaderInterface
return $catalogue;
}
private function extract(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain)
private function extract(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain): void
{
$xliffVersion = XliffUtils::getVersionNumber($dom);
@@ -91,7 +88,7 @@ class XliffFileLoader implements LoaderInterface
/**
* Extract messages and metadata from DOMDocument into a MessageCatalogue.
*/
private function extractXliff1(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain)
private function extractXliff1(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain): void
{
$xml = simplexml_import_dom($dom);
$encoding = $dom->encoding ? strtoupper($dom->encoding) : null;
@@ -104,6 +101,10 @@ class XliffFileLoader implements LoaderInterface
$file->registerXPathNamespace('xliff', $namespace);
foreach ($file->xpath('.//xliff:prop') as $prop) {
$catalogue->setCatalogueMetadata($prop->attributes()['prop-type'], (string) $prop, $domain);
}
foreach ($file->xpath('.//xliff:trans-unit') as $translation) {
$attributes = $translation->attributes();
@@ -111,6 +112,10 @@ class XliffFileLoader implements LoaderInterface
continue;
}
if (isset($translation->target) && 'needs-translation' === (string) $translation->target->attributes()['state']) {
continue;
}
$source = isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source;
// If the xlf file has another encoding specified, try to convert it because
// simple_xml will always return utf-8 encoded values
@@ -144,7 +149,7 @@ class XliffFileLoader implements LoaderInterface
}
}
private function extractXliff2(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain)
private function extractXliff2(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain): void
{
$xml = simplexml_import_dom($dom);
$encoding = $dom->encoding ? strtoupper($dom->encoding) : null;
@@ -190,7 +195,7 @@ class XliffFileLoader implements LoaderInterface
/**
* Convert a UTF8 string to the specified encoding.
*/
private function utf8ToCharset(string $content, string $encoding = null): string
private function utf8ToCharset(string $content, ?string $encoding = null): string
{
if ('UTF-8' !== $encoding && !empty($encoding)) {
return mb_convert_encoding($content, $encoding, 'UTF-8');
@@ -199,7 +204,7 @@ class XliffFileLoader implements LoaderInterface
return $content;
}
private function parseNotesMetadata(\SimpleXMLElement $noteElement = null, string $encoding = null): array
private function parseNotesMetadata(?\SimpleXMLElement $noteElement = null, ?string $encoding = null): array
{
$notes = [];
@@ -227,6 +232,6 @@ class XliffFileLoader implements LoaderInterface
private function isXmlString(string $resource): bool
{
return 0 === strpos($resource, '<?xml');
return str_starts_with($resource, '<?xml');
}
}

View File

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

View File

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

View File

@@ -21,8 +21,8 @@ use Symfony\Contracts\Translation\TranslatorInterface;
*/
class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface
{
private $translator;
private $logger;
private TranslatorInterface $translator;
private LoggerInterface $logger;
/**
* @param TranslatorInterface&TranslatorBagInterface&LocaleAwareInterface $translator The translator must implement TranslatorBagInterface
@@ -37,10 +37,7 @@ class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface,
$this->logger = $logger;
}
/**
* {@inheritdoc}
*/
public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null): string
public function trans(?string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string
{
$trans = $this->translator->trans($id = (string) $id, $parameters, $domain, $locale);
$this->log($id, $domain, $locale);
@@ -49,7 +46,7 @@ class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface,
}
/**
* {@inheritdoc}
* @return void
*/
public function setLocale(string $locale)
{
@@ -62,25 +59,16 @@ class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface,
$this->logger->debug(sprintf('The locale of the translator has changed from "%s" to "%s".', $prev, $locale));
}
/**
* {@inheritdoc}
*/
public function getLocale(): string
{
return $this->translator->getLocale();
}
/**
* {@inheritdoc}
*/
public function getCatalogue(string $locale = null): MessageCatalogueInterface
public function getCatalogue(?string $locale = null): MessageCatalogueInterface
{
return $this->translator->getCatalogue($locale);
}
/**
* {@inheritdoc}
*/
public function getCatalogues(): array
{
return $this->translator->getCatalogues();
@@ -99,7 +87,7 @@ class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface,
}
/**
* Passes through all unknown calls onto the translator object.
* @return mixed
*/
public function __call(string $method, array $args)
{
@@ -109,11 +97,9 @@ class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface,
/**
* Logs for missing translations.
*/
private function log(string $id, ?string $domain, ?string $locale)
private function log(string $id, ?string $domain, ?string $locale): void
{
if (null === $domain) {
$domain = 'messages';
}
$domain ??= 'messages';
$catalogue = $this->translator->getCatalogue($locale);
if ($catalogue->defines($id, $domain)) {

View File

@@ -17,13 +17,14 @@ use Symfony\Component\Translation\Exception\LogicException;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterface
class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterface, CatalogueMetadataAwareInterface
{
private array $messages = [];
private array $metadata = [];
private array $catalogueMetadata = [];
private array $resources = [];
private string $locale;
private $fallbackCatalogue = null;
private ?MessageCatalogueInterface $fallbackCatalogue = null;
private ?self $parent = null;
/**
@@ -35,17 +36,11 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
$this->messages = $messages;
}
/**
* {@inheritdoc}
*/
public function getLocale(): string
{
return $this->locale;
}
/**
* {@inheritdoc}
*/
public function getDomains(): array
{
$domains = [];
@@ -60,10 +55,7 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
return array_values($domains);
}
/**
* {@inheritdoc}
*/
public function all(string $domain = null): array
public function all(?string $domain = null): array
{
if (null !== $domain) {
// skip messages merge if intl-icu requested explicitly
@@ -89,16 +81,13 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
}
/**
* {@inheritdoc}
* @return void
*/
public function set(string $id, string $translation, string $domain = 'messages')
{
$this->add([$id => $translation], $domain);
}
/**
* {@inheritdoc}
*/
public function has(string $id, string $domain = 'messages'): bool
{
if (isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id])) {
@@ -112,17 +101,11 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
return false;
}
/**
* {@inheritdoc}
*/
public function defines(string $id, string $domain = 'messages'): bool
{
return isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id]);
}
/**
* {@inheritdoc}
*/
public function get(string $id, string $domain = 'messages'): string
{
if (isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id])) {
@@ -141,7 +124,7 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
}
/**
* {@inheritdoc}
* @return void
*/
public function replace(array $messages, string $domain = 'messages')
{
@@ -151,28 +134,23 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
}
/**
* {@inheritdoc}
* @return void
*/
public function add(array $messages, string $domain = 'messages')
{
if (!isset($this->messages[$domain])) {
$this->messages[$domain] = [];
}
$intlDomain = $domain;
if (!str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) {
$intlDomain .= self::INTL_DOMAIN_SUFFIX;
}
$altDomain = str_ends_with($domain, self::INTL_DOMAIN_SUFFIX) ? substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX)) : $domain.self::INTL_DOMAIN_SUFFIX;
foreach ($messages as $id => $message) {
if (isset($this->messages[$intlDomain]) && \array_key_exists($id, $this->messages[$intlDomain])) {
$this->messages[$intlDomain][$id] = $message;
} else {
$this->messages[$domain][$id] = $message;
}
unset($this->messages[$altDomain][$id]);
$this->messages[$domain][$id] = $message;
}
if ([] === ($this->messages[$altDomain] ?? null)) {
unset($this->messages[$altDomain]);
}
}
/**
* {@inheritdoc}
* @return void
*/
public function addCatalogue(MessageCatalogueInterface $catalogue)
{
@@ -196,10 +174,15 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
$metadata = $catalogue->getMetadata('', '');
$this->addMetadata($metadata);
}
if ($catalogue instanceof CatalogueMetadataAwareInterface) {
$catalogueMetadata = $catalogue->getCatalogueMetadata('', '');
$this->addCatalogueMetadata($catalogueMetadata);
}
}
/**
* {@inheritdoc}
* @return void
*/
public function addFallbackCatalogue(MessageCatalogueInterface $catalogue)
{
@@ -230,33 +213,24 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
}
}
/**
* {@inheritdoc}
*/
public function getFallbackCatalogue(): ?MessageCatalogueInterface
{
return $this->fallbackCatalogue;
}
/**
* {@inheritdoc}
*/
public function getResources(): array
{
return array_values($this->resources);
}
/**
* {@inheritdoc}
* @return void
*/
public function addResource(ResourceInterface $resource)
{
$this->resources[$resource->__toString()] = $resource;
}
/**
* {@inheritdoc}
*/
public function getMetadata(string $key = '', string $domain = 'messages'): mixed
{
if ('' == $domain) {
@@ -277,7 +251,7 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
}
/**
* {@inheritdoc}
* @return void
*/
public function setMetadata(string $key, mixed $value, string $domain = 'messages')
{
@@ -285,7 +259,7 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
}
/**
* {@inheritdoc}
* @return void
*/
public function deleteMetadata(string $key = '', string $domain = 'messages')
{
@@ -298,12 +272,53 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
}
}
public function getCatalogueMetadata(string $key = '', string $domain = 'messages'): mixed
{
if (!$domain) {
return $this->catalogueMetadata;
}
if (isset($this->catalogueMetadata[$domain])) {
if (!$key) {
return $this->catalogueMetadata[$domain];
}
if (isset($this->catalogueMetadata[$domain][$key])) {
return $this->catalogueMetadata[$domain][$key];
}
}
return null;
}
/**
* @return void
*/
public function setCatalogueMetadata(string $key, mixed $value, string $domain = 'messages')
{
$this->catalogueMetadata[$domain][$key] = $value;
}
/**
* @return void
*/
public function deleteCatalogueMetadata(string $key = '', string $domain = 'messages')
{
if (!$domain) {
$this->catalogueMetadata = [];
} elseif (!$key) {
unset($this->catalogueMetadata[$domain]);
} else {
unset($this->catalogueMetadata[$domain][$key]);
}
}
/**
* Adds current values with the new values.
*
* @param array $values Values to add
*/
private function addMetadata(array $values)
private function addMetadata(array $values): void
{
foreach ($values as $domain => $keys) {
foreach ($keys as $key => $value) {
@@ -311,4 +326,13 @@ class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterf
}
}
}
private function addCatalogueMetadata(array $values): void
{
foreach ($values as $domain => $keys) {
foreach ($keys as $key => $value) {
$this->setCatalogueMetadata($key, $value, $domain);
}
}
}
}

View File

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

View File

@@ -12,7 +12,7 @@
namespace Symfony\Component\Translation;
/**
* MetadataAwareInterface.
* This interface is used to get, set, and delete metadata about the translation messages.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
@@ -31,6 +31,8 @@ interface MetadataAwareInterface
/**
* Adds metadata to a message domain.
*
* @return void
*/
public function setMetadata(string $key, mixed $value, string $domain = 'messages');
@@ -39,6 +41,8 @@ interface MetadataAwareInterface
*
* Passing an empty domain will delete all metadata. Passing an empty key will
* delete all metadata for the given domain.
*
* @return void
*/
public function deleteMetadata(string $key = '', string $domain = 'messages');
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -20,7 +20,7 @@ final class PseudoLocalizationTranslator implements TranslatorInterface
{
private const EXPANSION_CHARACTER = '~';
private $translator;
private TranslatorInterface $translator;
private bool $accents;
private float $expansionFactor;
private bool $brackets;
@@ -83,10 +83,7 @@ final class PseudoLocalizationTranslator implements TranslatorInterface
$this->localizableHTMLAttributes = $options['localizable_html_attributes'] ?? [];
}
/**
* {@inheritdoc}
*/
public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null): string
public function trans(string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string
{
$trans = '';
$visibleText = '';
@@ -123,7 +120,7 @@ final class PseudoLocalizationTranslator implements TranslatorInterface
return [[true, true, $originalTrans]];
}
$html = mb_convert_encoding($originalTrans, 'HTML-ENTITIES', mb_detect_encoding($originalTrans, null, true) ?: 'UTF-8');
$html = mb_encode_numericentity($originalTrans, [0x80, 0x10FFFF, 0, 0x1FFFFF], mb_detect_encoding($originalTrans, null, true) ?: 'UTF-8');
$useInternalErrors = libxml_use_internal_errors(true);
@@ -283,7 +280,7 @@ final class PseudoLocalizationTranslator implements TranslatorInterface
}
$visibleLength = $this->strlen($visibleText);
$missingLength = (int) (ceil($visibleLength * $this->expansionFactor)) - $visibleLength;
$missingLength = (int) ceil($visibleLength * $this->expansionFactor) - $visibleLength;
if ($this->brackets) {
$missingLength -= 2;
}

View File

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

View File

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

View File

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

View File

@@ -9,6 +9,10 @@
* file that was distributed with this source code.
*/
if ('cli' !== \PHP_SAPI) {
throw new Exception('This script must be run from the command line.');
}
$usageInstructions = <<<END
Usage instructions
@@ -83,19 +87,15 @@ foreach ($config['original_files'] as $originalFilePath) {
$translationFilePaths = findTranslationFiles($originalFilePath, $config['locale_to_analyze']);
$translationStatus = calculateTranslationStatus($originalFilePath, $translationFilePaths);
$totalMissingTranslations += array_sum(array_map(function ($translation) {
return count($translation['missingKeys']);
}, array_values($translationStatus)));
$totalTranslationMismatches += array_sum(array_map(function ($translation) {
return count($translation['mismatches']);
}, array_values($translationStatus)));
$totalMissingTranslations += array_sum(array_map(fn ($translation) => count($translation['missingKeys']), array_values($translationStatus)));
$totalTranslationMismatches += array_sum(array_map(fn ($translation) => count($translation['mismatches']), array_values($translationStatus)));
printTranslationStatus($originalFilePath, $translationStatus, $config['verbose_output'], $config['include_completed_languages']);
}
exit($totalTranslationMismatches > 0 ? 1 : 0);
function findTranslationFiles($originalFilePath, $localeToAnalyze)
function findTranslationFiles($originalFilePath, $localeToAnalyze): array
{
$translations = [];
@@ -118,7 +118,7 @@ function findTranslationFiles($originalFilePath, $localeToAnalyze)
return $translations;
}
function calculateTranslationStatus($originalFilePath, $translationFilePaths)
function calculateTranslationStatus($originalFilePath, $translationFilePaths): array
{
$translationStatus = [];
$allTranslationKeys = extractTranslationKeys($originalFilePath);
@@ -159,7 +159,7 @@ function extractLocaleFromFilePath($filePath)
return $parts[count($parts) - 2];
}
function extractTranslationKeys($filePath)
function extractTranslationKeys($filePath): array
{
$translationKeys = [];
$contents = new \SimpleXMLElement(file_get_contents($filePath));

View File

@@ -35,6 +35,7 @@
"en_GM": "en_001",
"en_GY": "en_001",
"en_HK": "en_001",
"en_ID": "en_001",
"en_IE": "en_001",
"en_IL": "en_001",
"en_IM": "en_001",
@@ -54,6 +55,7 @@
"en_MS": "en_001",
"en_MT": "en_001",
"en_MU": "en_001",
"en_MV": "en_001",
"en_MW": "en_001",
"en_MY": "en_001",
"en_NA": "en_001",
@@ -116,6 +118,8 @@
"es_UY": "es_419",
"es_VE": "es_419",
"ff_Adlm": "root",
"hi_Latn": "en_IN",
"ks_Deva": "root",
"nb": "no",
"nn": "no",
"pa_Arab": "root",

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More