Illuminate: ready. (critical)

- DB에서 illuminate를 가져올 수 있도록 설정
- 업그레이듵 불가, RootDB, DB를 직접 수정해야함
This commit is contained in:
2021-09-21 03:06:42 +09:00
parent 40a20a55d3
commit b4605958ad
1704 changed files with 147037 additions and 20 deletions
@@ -0,0 +1,3 @@
vendor/
composer.lock
phpunit.xml
+5
View File
@@ -0,0 +1,5 @@
CHANGELOG
=========
The changelog is maintained for all Symfony contracts at the following URL:
https://github.com/symfony/contracts/blob/main/CHANGELOG.md
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2018-2021 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
@@ -0,0 +1,31 @@
<?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\Contracts\Translation;
interface LocaleAwareInterface
{
/**
* Sets the current locale.
*
* @param string $locale The locale
*
* @throws \InvalidArgumentException If the locale contains invalid characters
*/
public function setLocale(string $locale);
/**
* Returns the current locale.
*
* @return string The locale
*/
public function getLocale();
}
+9
View File
@@ -0,0 +1,9 @@
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
that already have battle tested implementations.
See https://github.com/symfony/contracts/blob/main/README.md for more information.
@@ -0,0 +1,367 @@
<?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\Contracts\Translation\Test;
use PHPUnit\Framework\TestCase;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Contracts\Translation\TranslatorTrait;
/**
* Test should cover all languages mentioned on http://translate.sourceforge.net/wiki/l10n/pluralforms
* and Plural forms mentioned on http://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms.
*
* See also https://developer.mozilla.org/en/Localization_and_Plurals which mentions 15 rules having a maximum of 6 forms.
* The mozilla code is also interesting to check for.
*
* As mentioned by chx http://drupal.org/node/1273968 we can cover all by testing number from 0 to 199
*
* The goal to cover all languages is to far fetched so this test case is smaller.
*
* @author Clemens Tolboom clemens@build2be.nl
*/
class TranslatorTest extends TestCase
{
public function getTranslator()
{
return new class() implements TranslatorInterface {
use TranslatorTrait;
};
}
/**
* @dataProvider getTransTests
*/
public function testTrans($expected, $id, $parameters)
{
$translator = $this->getTranslator();
$this->assertEquals($expected, $translator->trans($id, $parameters));
}
/**
* @dataProvider getTransChoiceTests
*/
public function testTransChoiceWithExplicitLocale($expected, $id, $number)
{
$translator = $this->getTranslator();
$translator->setLocale('en');
$this->assertEquals($expected, $translator->trans($id, ['%count%' => $number]));
}
/**
* @requires extension intl
*
* @dataProvider getTransChoiceTests
*/
public function testTransChoiceWithDefaultLocale($expected, $id, $number)
{
\Locale::setDefault('en');
$translator = $this->getTranslator();
$this->assertEquals($expected, $translator->trans($id, ['%count%' => $number]));
}
public function testGetSetLocale()
{
$translator = $this->getTranslator();
$translator->setLocale('en');
$this->assertEquals('en', $translator->getLocale());
}
/**
* @requires extension intl
*/
public function testGetLocaleReturnsDefaultLocaleIfNotSet()
{
$translator = $this->getTranslator();
\Locale::setDefault('pt_BR');
$this->assertEquals('pt_BR', $translator->getLocale());
\Locale::setDefault('en');
$this->assertEquals('en', $translator->getLocale());
}
public function getTransTests()
{
return [
['Symfony is great!', 'Symfony is great!', []],
['Symfony is awesome!', 'Symfony is %what%!', ['%what%' => 'awesome']],
];
}
public function getTransChoiceTests()
{
return [
['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0],
['There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1],
['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10],
['There are 0 apples', 'There is 1 apple|There are %count% apples', 0],
['There is 1 apple', 'There is 1 apple|There are %count% apples', 1],
['There are 10 apples', 'There is 1 apple|There are %count% apples', 10],
// custom validation messages may be coded with a fixed value
['There are 2 apples', 'There are 2 apples', 2],
];
}
/**
* @dataProvider getInternal
*/
public function testInterval($expected, $number, $interval)
{
$translator = $this->getTranslator();
$this->assertEquals($expected, $translator->trans($interval.' foo|[1,Inf[ bar', ['%count%' => $number]));
}
public function getInternal()
{
return [
['foo', 3, '{1,2, 3 ,4}'],
['bar', 10, '{1,2, 3 ,4}'],
['bar', 3, '[1,2]'],
['foo', 1, '[1,2]'],
['foo', 2, '[1,2]'],
['bar', 1, ']1,2['],
['bar', 2, ']1,2['],
['foo', log(0), '[-Inf,2['],
['foo', -log(0), '[-2,+Inf]'],
];
}
/**
* @dataProvider getChooseTests
*/
public function testChoose($expected, $id, $number, $locale = null)
{
$translator = $this->getTranslator();
$this->assertEquals($expected, $translator->trans($id, ['%count%' => $number], null, $locale));
}
public function testReturnMessageIfExactlyOneStandardRuleIsGiven()
{
$translator = $this->getTranslator();
$this->assertEquals('There are two apples', $translator->trans('There are two apples', ['%count%' => 2]));
}
/**
* @dataProvider getNonMatchingMessages
*/
public function testThrowExceptionIfMatchingMessageCannotBeFound($id, $number)
{
$this->expectException(\InvalidArgumentException::class);
$translator = $this->getTranslator();
$translator->trans($id, ['%count%' => $number]);
}
public function getNonMatchingMessages()
{
return [
['{0} There are no apples|{1} There is one apple', 2],
['{1} There is one apple|]1,Inf] There are %count% apples', 0],
['{1} There is one apple|]2,Inf] There are %count% apples', 2],
['{0} There are no apples|There is one apple', 2],
];
}
public function getChooseTests()
{
return [
['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0],
['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0],
['There are no apples', '{0}There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0],
['There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1],
['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10],
['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf]There are %count% apples', 10],
['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10],
['There are 0 apples', 'There is one apple|There are %count% apples', 0],
['There is one apple', 'There is one apple|There are %count% apples', 1],
['There are 10 apples', 'There is one apple|There are %count% apples', 10],
['There are 0 apples', 'one: There is one apple|more: There are %count% apples', 0],
['There is one apple', 'one: There is one apple|more: There are %count% apples', 1],
['There are 10 apples', 'one: There is one apple|more: There are %count% apples', 10],
['There are no apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 0],
['There is one apple', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 1],
['There are 10 apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 10],
['', '{0}|{1} There is one apple|]1,Inf] There are %count% apples', 0],
['', '{0} There are no apples|{1}|]1,Inf] There are %count% apples', 1],
// Indexed only tests which are Gettext PoFile* compatible strings.
['There are 0 apples', 'There is one apple|There are %count% apples', 0],
['There is one apple', 'There is one apple|There are %count% apples', 1],
['There are 2 apples', 'There is one apple|There are %count% apples', 2],
// Tests for float numbers
['There is almost one apple', '{0} There are no apples|]0,1[ There is almost one apple|{1} There is one apple|[1,Inf] There is more than one apple', 0.7],
['There is one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1],
['There is more than one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1.7],
['There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0],
['There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0.0],
['There are no apples', '{0.0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0],
// Test texts with new-lines
// with double-quotes and \n in id & double-quotes and actual newlines in text
["This is a text with a\n new-line in it. Selector = 0.", '{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.', 0],
// with double-quotes and \n in id and single-quotes and actual newlines in text
["This is a text with a\n 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],
["This is a text with a\n 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.', 5],
// with double-quotes and id split accros 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
['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.', 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
["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
['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],
// Empty plural set (3 plural forms) from a .PO file
['', '||', 1],
// Floating values
['1.5 liters', '%count% liter|%count% liters', 1.5],
['1.5 litre', '%count% litre|%count% litres', 1.5, 'fr'],
// Negative values
['-1 degree', '%count% degree|%count% degrees', -1],
['-1 degré', '%count% degré|%count% degrés', -1],
['-1.5 degrees', '%count% degree|%count% degrees', -1.5],
['-1.5 degré', '%count% degré|%count% degrés', -1.5, 'fr'],
['-2 degrees', '%count% degree|%count% degrees', -2],
['-2 degrés', '%count% degré|%count% degrés', -2],
];
}
/**
* @dataProvider failingLangcodes
*/
public function testFailedLangcodes($nplural, $langCodes)
{
$matrix = $this->generateTestData($langCodes);
$this->validateMatrix($nplural, $matrix, false);
}
/**
* @dataProvider successLangcodes
*/
public function testLangcodes($nplural, $langCodes)
{
$matrix = $this->generateTestData($langCodes);
$this->validateMatrix($nplural, $matrix);
}
/**
* This array should contain all currently known langcodes.
*
* As it is impossible to have this ever complete we should try as hard as possible to have it almost complete.
*
* @return array
*/
public function successLangcodes()
{
return [
['1', ['ay', 'bo', 'cgg', 'dz', 'id', 'ja', 'jbo', 'ka', 'kk', 'km', 'ko', 'ky']],
['2', ['nl', 'fr', 'en', 'de', 'de_GE', 'hy', 'hy_AM']],
['3', ['be', 'bs', 'cs', 'hr']],
['4', ['cy', 'mt', 'sl']],
['6', ['ar']],
];
}
/**
* This array should be at least empty within the near future.
*
* This both depends on a complete list trying to add above as understanding
* the plural rules of the current failing languages.
*
* @return array with nplural together with langcodes
*/
public function failingLangcodes()
{
return [
['1', ['fa']],
['2', ['jbo']],
['3', ['cbs']],
['4', ['gd', 'kw']],
['5', ['ga']],
];
}
/**
* We validate only on the plural coverage. Thus the real rules is not tested.
*
* @param string $nplural Plural expected
* @param array $matrix Containing langcodes and their plural index values
* @param bool $expectSuccess
*/
protected function validateMatrix($nplural, $matrix, $expectSuccess = true)
{
foreach ($matrix as $langCode => $data) {
$indexes = array_flip($data);
if ($expectSuccess) {
$this->assertEquals($nplural, \count($indexes), "Langcode '$langCode' has '$nplural' plural forms.");
} else {
$this->assertNotEquals((int) $nplural, \count($indexes), "Langcode '$langCode' has '$nplural' plural forms.");
}
}
}
protected function generateTestData($langCodes)
{
$translator = new class() {
use TranslatorTrait {
getPluralizationRule as public;
}
};
$matrix = [];
foreach ($langCodes as $langCode) {
for ($count = 0; $count < 200; ++$count) {
$plural = $translator->getPluralizationRule($count, $langCode);
$matrix[$langCode][$count] = $plural;
}
}
return $matrix;
}
}
@@ -0,0 +1,20 @@
<?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\Contracts\Translation;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
interface TranslatableInterface
{
public function trans(TranslatorInterface $translator, string $locale = null): string;
}
@@ -0,0 +1,67 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Contracts\Translation;
/**
* @author Fabien Potencier <fabien@symfony.com>
*
* @method string getLocale() Returns the default locale
*/
interface TranslatorInterface
{
/**
* Translates the given message.
*
* When a number is provided as a parameter named "%count%", the message is parsed for plural
* forms and a translation is chosen according to this number using the following rules:
*
* Given a message with different plural translations separated by a
* pipe (|), this method returns the correct portion of the message based
* on the given number, locale and the pluralization rules in the message
* itself.
*
* The message supports two different types of pluralization rules:
*
* interval: {0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples
* indexed: There is one apple|There are %count% apples
*
* The indexed solution can also contain labels (e.g. one: There is one apple).
* This is purely for making the translations more clear - it does not
* affect the functionality.
*
* The two methods can also be mixed:
* {0} There are no apples|one: There is one apple|more: There are %count% apples
*
* An interval can represent a finite set of numbers:
* {1,2,3,4}
*
* An interval can represent numbers between two numbers:
* [1, +Inf]
* ]-1,2[
*
* The left delimiter can be [ (inclusive) or ] (exclusive).
* The right delimiter can be [ (exclusive) or ] (inclusive).
* Beside numbers, you can use -Inf and +Inf for the infinite.
*
* @see https://en.wikipedia.org/wiki/ISO_31-11
*
* @param string $id The message id (may also be an object that can be cast to string)
* @param array $parameters An array of parameters for the message
* @param string|null $domain The domain for the message or null to use the default
* @param string|null $locale The locale or null to use the default
*
* @return string The translated string
*
* @throws \InvalidArgumentException If the locale contains invalid characters
*/
public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null);
}
+261
View File
@@ -0,0 +1,261 @@
<?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\Contracts\Translation;
use Symfony\Component\Translation\Exception\InvalidArgumentException;
/**
* A trait to help implement TranslatorInterface and LocaleAwareInterface.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
trait TranslatorTrait
{
private $locale;
/**
* {@inheritdoc}
*/
public function setLocale(string $locale)
{
$this->locale = $locale;
}
/**
* {@inheritdoc}
*
* @return string
*/
public function getLocale()
{
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) {
return '';
}
if (!isset($parameters['%count%']) || !is_numeric($parameters['%count%'])) {
return strtr($id, $parameters);
}
$number = (float) $parameters['%count%'];
$locale = $locale ?: $this->getLocale();
$parts = [];
if (preg_match('/^\|++$/', $id)) {
$parts = explode('|', $id);
} elseif (preg_match_all('/(?:\|\||[^\|])++/', $id, $matches)) {
$parts = $matches[0];
}
$intervalRegexp = <<<'EOF'
/^(?P<interval>
({\s*
(\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*)
\s*})
|
(?P<left_delimiter>[\[\]])
\s*
(?P<left>-Inf|\-?\d+(\.\d+)?)
\s*,\s*
(?P<right>\+?Inf|\-?\d+(\.\d+)?)
\s*
(?P<right_delimiter>[\[\]])
)\s*(?P<message>.*?)$/xs
EOF;
$standardRules = [];
foreach ($parts as $part) {
$part = trim(str_replace('||', '|', $part));
// try to match an explicit rule, then fallback to the standard ones
if (preg_match($intervalRegexp, $part, $matches)) {
if ($matches[2]) {
foreach (explode(',', $matches[3]) as $n) {
if ($number == $n) {
return strtr($matches['message'], $parameters);
}
}
} else {
$leftNumber = '-Inf' === $matches['left'] ? -\INF : (float) $matches['left'];
$rightNumber = is_numeric($matches['right']) ? (float) $matches['right'] : \INF;
if (('[' === $matches['left_delimiter'] ? $number >= $leftNumber : $number > $leftNumber)
&& (']' === $matches['right_delimiter'] ? $number <= $rightNumber : $number < $rightNumber)
) {
return strtr($matches['message'], $parameters);
}
}
} elseif (preg_match('/^\w+\:\s*(.*?)$/', $part, $matches)) {
$standardRules[] = $matches[1];
} else {
$standardRules[] = $part;
}
}
$position = $this->getPluralizationRule($number, $locale);
if (!isset($standardRules[$position])) {
// when there's exactly one rule given, and that rule is a standard
// rule, use this rule
if (1 === \count($parts) && isset($standardRules[0])) {
return strtr($standardRules[0], $parameters);
}
$message = sprintf('Unable to choose a translation for "%s" with locale "%s" for value "%d". Double check that this translation has the correct plural options (e.g. "There is one apple|There are %%count%% apples").', $id, $locale, $number);
if (class_exists(InvalidArgumentException::class)) {
throw new InvalidArgumentException($message);
}
throw new \InvalidArgumentException($message);
}
return strtr($standardRules[$position], $parameters);
}
/**
* Returns the plural position to use for the given locale and number.
*
* The plural rules are derived from code of the Zend Framework (2010-09-25),
* which is subject to the new BSD license (http://framework.zend.com/license/new-bsd).
* Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
*/
private function getPluralizationRule(float $number, string $locale): int
{
$number = abs($number);
switch ('pt_BR' !== $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 '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;
}
}
}
+37
View File
@@ -0,0 +1,37 @@
{
"name": "symfony/translation-contracts",
"type": "library",
"description": "Generic abstractions related to translation",
"keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=7.2.5"
},
"suggest": {
"symfony/translation-implementation": ""
},
"autoload": {
"psr-4": { "Symfony\\Contracts\\Translation\\": "" }
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-main": "2.4-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
}
}
+169
View File
@@ -0,0 +1,169 @@
CHANGELOG
=========
5.3
---
* Add `translation:pull` and `translation:push` commands to manage translations with third-party providers
* Add `TranslatorBagInterface::getCatalogues` method
* Add support to load XLIFF string in `XliffFileLoader`
5.2.0
-----
* added support for calling `trans` with ICU formatted messages
* added `PseudoLocalizationTranslator`
* added `TranslatableMessage` objects that represent a message that can be translated
* added the `t()` function to easily create `TranslatableMessage` objects
* Added support for extracting messages from `TranslatableMessage` objects
5.1.0
-----
* added support for `name` attribute on `unit` element from xliff2 to be used as a translation key instead of always the `source` element
5.0.0
-----
* removed support for using `null` as the locale in `Translator`
* removed `TranslatorInterface`
* removed `MessageSelector`
* removed `ChoiceMessageFormatterInterface`
* removed `PluralizationRule`
* removed `Interval`
* removed `transChoice()` methods, use the trans() method instead with a %count% parameter
* removed `FileDumper::setBackup()` and `TranslationWriter::disableBackup()`
* removed `MessageFormatter::choiceFormat()`
* added argument `$filename` to `PhpExtractor::parseTokens()`
* removed support for implicit STDIN usage in the `lint:xliff` command, use `lint:xliff -` (append a dash) instead to make it explicit.
4.4.0
-----
* deprecated support for using `null` as the locale in `Translator`
* deprecated accepting STDIN implicitly when using the `lint:xliff` command, use `lint:xliff -` (append a dash) instead to make it explicit.
* Marked the `TranslationDataCollector` class as `@final`.
4.3.0
-----
* Improved Xliff 1.2 loader to load the original file's metadata
* Added `TranslatorPathsPass`
4.2.0
-----
* Started using ICU parent locales as fallback locales.
* allow using the ICU message format using domains with the "+intl-icu" suffix
* deprecated `Translator::transChoice()` in favor of using `Translator::trans()` with a `%count%` parameter
* deprecated `TranslatorInterface` in favor of `Symfony\Contracts\Translation\TranslatorInterface`
* deprecated `MessageSelector`, `Interval` and `PluralizationRules`; use `IdentityTranslator` instead
* Added `IntlFormatter` and `IntlFormatterInterface`
* added support for multiple files and directories in `XliffLintCommand`
* Marked `Translator::getFallbackLocales()` and `TranslationDataCollector::getFallbackLocales()` as internal
4.1.0
-----
* The `FileDumper::setBackup()` method is deprecated.
* The `TranslationWriter::disableBackup()` method is deprecated.
* The `XliffFileDumper` will write "name" on the "unit" node when dumping XLIFF 2.0.
4.0.0
-----
* removed the backup feature of the `FileDumper` class
* removed `TranslationWriter::writeTranslations()` method
* removed support for passing `MessageSelector` instances to the constructor of the `Translator` class
3.4.0
-----
* Added `TranslationDumperPass`
* Added `TranslationExtractorPass`
* Added `TranslatorPass`
* Added `TranslationReader` and `TranslationReaderInterface`
* Added `<notes>` section to the Xliff 2.0 dumper.
* Improved Xliff 2.0 loader to load `<notes>` section.
* Added `TranslationWriterInterface`
* Deprecated `TranslationWriter::writeTranslations` in favor of `TranslationWriter::write`
* added support for adding custom message formatter and decoupling the default one.
* Added `PhpExtractor`
* Added `PhpStringTokenParser`
3.2.0
-----
* Added support for escaping `|` in plural translations with double pipe.
3.1.0
-----
* Deprecated the backup feature of the file dumper classes.
3.0.0
-----
* removed `FileDumper::format()` method.
* Changed the visibility of the locale property in `Translator` from protected to private.
2.8.0
-----
* deprecated FileDumper::format(), overwrite FileDumper::formatCatalogue() instead.
* deprecated Translator::getMessages(), rely on TranslatorBagInterface::getCatalogue() instead.
* added `FileDumper::formatCatalogue` which allows format the catalogue without dumping it into file.
* added option `json_encoding` to JsonFileDumper
* added options `as_tree`, `inline` to YamlFileDumper
* added support for XLIFF 2.0.
* added support for XLIFF target and tool attributes.
* added message parameters to DataCollectorTranslator.
* [DEPRECATION] The `DiffOperation` class has been deprecated and
will be removed in Symfony 3.0, since its operation has nothing to do with 'diff',
so the class name is misleading. The `TargetOperation` class should be used for
this use-case instead.
2.7.0
-----
* added DataCollectorTranslator for collecting the translated messages.
2.6.0
-----
* added possibility to cache catalogues
* added TranslatorBagInterface
* added LoggingTranslator
* added Translator::getMessages() for retrieving the message catalogue as an array
2.5.0
-----
* added relative file path template to the file dumpers
* added optional backup to the file dumpers
* changed IcuResFileDumper to extend FileDumper
2.3.0
-----
* added classes to make operations on catalogues (like making a diff or a merge on 2 catalogues)
* added Translator::getFallbackLocales()
* deprecated Translator::setFallbackLocale() in favor of the new Translator::setFallbackLocales() method
2.2.0
-----
* QtTranslationsLoader class renamed to QtFileLoader. QtTranslationsLoader is deprecated and will be removed in 2.3.
* [BC BREAK] uniformized the exception thrown by the load() method when an error occurs. The load() method now
throws Symfony\Component\Translation\Exception\NotFoundResourceException when a resource cannot be found
and Symfony\Component\Translation\Exception\InvalidResourceException when a resource is invalid.
* changed the exception class thrown by some load() methods from \RuntimeException to \InvalidArgumentException
(IcuDatFileLoader, IcuResFileLoader and QtFileLoader)
2.1.0
-----
* added support for more than one fallback locale
* added support for extracting translation messages from templates (Twig and PHP)
* added dumpers for translation catalogs
* added support for QT, gettext, and ResourceBundles
@@ -0,0 +1,192 @@
<?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\Catalogue;
use Symfony\Component\Translation\Exception\InvalidArgumentException;
use Symfony\Component\Translation\Exception\LogicException;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\MessageCatalogueInterface;
/**
* Base catalogues binary operation class.
*
* A catalogue binary operation performs operation on
* source (the left argument) and target (the right argument) catalogues.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*/
abstract class AbstractOperation implements OperationInterface
{
public const OBSOLETE_BATCH = 'obsolete';
public const NEW_BATCH = 'new';
public const ALL_BATCH = 'all';
protected $source;
protected $target;
protected $result;
/**
* @var array|null The domains affected by this operation
*/
private $domains;
/**
* This array stores 'all', 'new' and 'obsolete' messages for all valid domains.
*
* The data structure of this array is as follows:
*
* [
* 'domain 1' => [
* 'all' => [...],
* 'new' => [...],
* 'obsolete' => [...]
* ],
* 'domain 2' => [
* 'all' => [...],
* 'new' => [...],
* 'obsolete' => [...]
* ],
* ...
* ]
*
* @var array The array that stores 'all', 'new' and 'obsolete' messages
*/
protected $messages;
/**
* @throws LogicException
*/
public function __construct(MessageCatalogueInterface $source, MessageCatalogueInterface $target)
{
if ($source->getLocale() !== $target->getLocale()) {
throw new LogicException('Operated catalogues must belong to the same locale.');
}
$this->source = $source;
$this->target = $target;
$this->result = new MessageCatalogue($source->getLocale());
$this->messages = [];
}
/**
* {@inheritdoc}
*/
public function getDomains()
{
if (null === $this->domains) {
$this->domains = array_values(array_unique(array_merge($this->source->getDomains(), $this->target->getDomains())));
}
return $this->domains;
}
/**
* {@inheritdoc}
*/
public function getMessages(string $domain)
{
if (!\in_array($domain, $this->getDomains())) {
throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain));
}
if (!isset($this->messages[$domain][self::ALL_BATCH])) {
$this->processDomain($domain);
}
return $this->messages[$domain][self::ALL_BATCH];
}
/**
* {@inheritdoc}
*/
public function getNewMessages(string $domain)
{
if (!\in_array($domain, $this->getDomains())) {
throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain));
}
if (!isset($this->messages[$domain][self::NEW_BATCH])) {
$this->processDomain($domain);
}
return $this->messages[$domain][self::NEW_BATCH];
}
/**
* {@inheritdoc}
*/
public function getObsoleteMessages(string $domain)
{
if (!\in_array($domain, $this->getDomains())) {
throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain));
}
if (!isset($this->messages[$domain][self::OBSOLETE_BATCH])) {
$this->processDomain($domain);
}
return $this->messages[$domain][self::OBSOLETE_BATCH];
}
/**
* {@inheritdoc}
*/
public function getResult()
{
foreach ($this->getDomains() as $domain) {
if (!isset($this->messages[$domain])) {
$this->processDomain($domain);
}
}
return $this->result;
}
/**
* @param self::*_BATCH $batch
*/
public function moveMessagesToIntlDomainsIfPossible(string $batch = self::ALL_BATCH): void
{
// If MessageFormatter class does not exists, intl domains are not supported.
if (!class_exists(\MessageFormatter::class)) {
return;
}
foreach ($this->getDomains() as $domain) {
$intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX;
switch ($batch) {
case self::OBSOLETE_BATCH: $messages = $this->getObsoleteMessages($domain); break;
case self::NEW_BATCH: $messages = $this->getNewMessages($domain); break;
case self::ALL_BATCH: $messages = $this->getMessages($domain); break;
default: throw new \InvalidArgumentException(sprintf('$batch argument must be one of ["%s", "%s", "%s"].', self::ALL_BATCH, self::NEW_BATCH, self::OBSOLETE_BATCH));
}
if (!$messages || (!$this->source->all($intlDomain) && $this->source->all($domain))) {
continue;
}
$result = $this->getResult();
$allIntlMessages = $result->all($intlDomain);
$currentMessages = array_diff_key($messages, $result->all($domain));
$result->replace($currentMessages, $domain);
$result->replace($allIntlMessages + $messages, $intlDomain);
}
}
/**
* Performs operation on source and target catalogues for the given domain and
* stores the results.
*
* @param string $domain The domain which the operation will be performed for
*/
abstract protected function processDomain(string $domain);
}
+60
View File
@@ -0,0 +1,60 @@
<?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\Catalogue;
use Symfony\Component\Translation\MessageCatalogueInterface;
/**
* Merge operation between two catalogues as follows:
* all = source target = {x: x ∈ source x ∈ target}
* new = all source = {x: x ∈ target ∧ x ∉ source}
* obsolete = source all = {x: x ∈ source ∧ x ∉ source ∧ x ∉ target} = ∅
* Basically, the result contains messages from both catalogues.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*/
class MergeOperation extends AbstractOperation
{
/**
* {@inheritdoc}
*/
protected function processDomain(string $domain)
{
$this->messages[$domain] = [
'all' => [],
'new' => [],
'obsolete' => [],
];
$intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX;
foreach ($this->source->all($domain) as $id => $message) {
$this->messages[$domain]['all'][$id] = $message;
$d = $this->source->defines($id, $intlDomain) ? $intlDomain : $domain;
$this->result->add([$id => $message], $d);
if (null !== $keyMetadata = $this->source->getMetadata($id, $d)) {
$this->result->setMetadata($id, $keyMetadata, $d);
}
}
foreach ($this->target->all($domain) as $id => $message) {
if (!$this->source->has($id, $domain)) {
$this->messages[$domain]['all'][$id] = $message;
$this->messages[$domain]['new'][$id] = $message;
$d = $this->target->defines($id, $intlDomain) ? $intlDomain : $domain;
$this->result->add([$id => $message], $d);
if (null !== $keyMetadata = $this->target->getMetadata($id, $d)) {
$this->result->setMetadata($id, $keyMetadata, $d);
}
}
}
}
}
@@ -0,0 +1,71 @@
<?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\Catalogue;
use Symfony\Component\Translation\MessageCatalogueInterface;
/**
* Represents an operation on catalogue(s).
*
* An instance of this interface performs an operation on one or more catalogues and
* stores intermediate and final results of the operation.
*
* The first catalogue in its argument(s) is called the 'source catalogue' or 'source' and
* the following results are stored:
*
* Messages: also called 'all', are valid messages for the given domain after the operation is performed.
*
* New Messages: also called 'new' (new = all source = {x: x ∈ all ∧ x ∉ source}).
*
* Obsolete Messages: also called 'obsolete' (obsolete = source all = {x: x ∈ source ∧ x ∉ all}).
*
* Result: also called 'result', is the resulting catalogue for the given domain that holds the same messages as 'all'.
*
* @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
*/
interface OperationInterface
{
/**
* Returns domains affected by operation.
*
* @return array
*/
public function getDomains();
/**
* Returns all valid messages ('all') after operation.
*
* @return array
*/
public function getMessages(string $domain);
/**
* Returns new messages ('new') after operation.
*
* @return array
*/
public function getNewMessages(string $domain);
/**
* Returns obsolete messages ('obsolete') after operation.
*
* @return array
*/
public function getObsoleteMessages(string $domain);
/**
* Returns resulting catalogue ('result').
*
* @return MessageCatalogueInterface
*/
public function getResult();
}
@@ -0,0 +1,74 @@
<?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\Catalogue;
use Symfony\Component\Translation\MessageCatalogueInterface;
/**
* Target operation between two catalogues:
* intersection = source ∩ target = {x: x ∈ source ∧ x ∈ target}
* all = intersection (target intersection) = target
* new = all source = {x: x ∈ target ∧ x ∉ source}
* obsolete = source all = source target = {x: x ∈ source ∧ x ∉ target}
* Basically, the result contains messages from the target catalogue.
*
* @author Michael Lee <michael.lee@zerustech.com>
*/
class TargetOperation extends AbstractOperation
{
/**
* {@inheritdoc}
*/
protected function processDomain(string $domain)
{
$this->messages[$domain] = [
'all' => [],
'new' => [],
'obsolete' => [],
];
$intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX;
// 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}
//
// For 'new' messages, the code can't be simplified as ``array_diff_assoc($this->target->all($domain), $this->source->all($domain));``
// because doing so will not exclude messages like {x: x ∈ target ∧ x ∉ source.all ∧ x ∈ source.fallback}
//
// For 'obsolete' messages, the code can't be simplified as ``array_diff_assoc($this->source->all($domain), $this->target->all($domain))``
// because doing so will not exclude messages like {x: x ∈ source ∧ x ∉ target.all ∧ x ∈ target.fallback}
foreach ($this->source->all($domain) as $id => $message) {
if ($this->target->has($id, $domain)) {
$this->messages[$domain]['all'][$id] = $message;
$d = $this->target->defines($id, $intlDomain) ? $intlDomain : $domain;
$this->result->add([$id => $message], $d);
if (null !== $keyMetadata = $this->source->getMetadata($id, $d)) {
$this->result->setMetadata($id, $keyMetadata, $d);
}
} else {
$this->messages[$domain]['obsolete'][$id] = $message;
}
}
foreach ($this->target->all($domain) as $id => $message) {
if (!$this->source->has($id, $domain)) {
$this->messages[$domain]['all'][$id] = $message;
$this->messages[$domain]['new'][$id] = $message;
$d = $this->target->defines($id, $intlDomain) ? $intlDomain : $domain;
$this->result->add([$id => $message], $d);
if (null !== $keyMetadata = $this->target->getMetadata($id, $d)) {
$this->result->setMetadata($id, $keyMetadata, $d);
}
}
}
}
}
@@ -0,0 +1,157 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Translation\Catalogue\TargetOperation;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Provider\TranslationProviderCollection;
use Symfony\Component\Translation\Reader\TranslationReaderInterface;
use Symfony\Component\Translation\Writer\TranslationWriterInterface;
/**
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*
* @experimental in 5.3
*/
final class TranslationPullCommand extends Command
{
use TranslationTrait;
protected static $defaultName = 'translation:pull';
protected static $defaultDescription = 'Pull translations from a given provider.';
private $providerCollection;
private $writer;
private $reader;
private $defaultLocale;
private $transPaths;
private $enabledLocales;
public function __construct(TranslationProviderCollection $providerCollection, TranslationWriterInterface $writer, TranslationReaderInterface $reader, string $defaultLocale, array $transPaths = [], array $enabledLocales = [])
{
$this->providerCollection = $providerCollection;
$this->writer = $writer;
$this->reader = $reader;
$this->defaultLocale = $defaultLocale;
$this->transPaths = $transPaths;
$this->enabledLocales = $enabledLocales;
parent::__construct();
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$keys = $this->providerCollection->keys();
$defaultProvider = 1 === \count($keys) ? $keys[0] : null;
$this
->setDefinition([
new InputArgument('provider', null !== $defaultProvider ? InputArgument::OPTIONAL : InputArgument::REQUIRED, 'The provider to pull translations from.', $defaultProvider),
new InputOption('force', null, InputOption::VALUE_NONE, 'Override existing translations with provider ones (it will delete not synchronized messages).'),
new InputOption('intl-icu', null, InputOption::VALUE_NONE, 'Associated to --force option, it will write messages in "%domain%+intl-icu.%locale%.xlf" files.'),
new InputOption('domains', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the domains to pull.'),
new InputOption('locales', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the locales to pull.'),
new InputOption('format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format.', 'xlf12'),
])
->setHelp(<<<'EOF'
The <info>%command.name%</> command pulls translations from the given provider. Only
new translations are pulled, existing ones are not overwritten.
You can overwrite existing translations (and remove the missing ones on local side) by using the <comment>--force</> flag:
<info>php %command.full_name% --force provider</>
Full example:
<info>php %command.full_name% provider --force --domains=messages,validators --locales=en</>
This command pulls all translations associated with the <comment>messages</> and <comment>validators</> domains for the <comment>en</> locale.
Local translations for the specified domains and locale are deleted if they're not present on the provider and overwritten if it's the case.
Local translations for others domains and locales are ignored.
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$provider = $this->providerCollection->get($input->getArgument('provider'));
$force = $input->getOption('force');
$intlIcu = $input->getOption('intl-icu');
$locales = $input->getOption('locales') ?: $this->enabledLocales;
$domains = $input->getOption('domains');
$format = $input->getOption('format');
$xliffVersion = '1.2';
if ($intlIcu && !$force) {
$io->note('--intl-icu option only has an effect when used with --force. Here, it will be ignored.');
}
switch ($format) {
case 'xlf20': $xliffVersion = '2.0';
// no break
case 'xlf12': $format = 'xlf';
}
$writeOptions = [
'path' => end($this->transPaths),
'xliff_version' => $xliffVersion,
];
if (!$domains) {
$domains = $provider->getDomains();
}
$providerTranslations = $provider->read($domains, $locales);
if ($force) {
foreach ($providerTranslations->getCatalogues() as $catalogue) {
$operation = new TargetOperation((new MessageCatalogue($catalogue->getLocale())), $catalogue);
if ($intlIcu) {
$operation->moveMessagesToIntlDomainsIfPossible();
}
$this->writer->write($operation->getResult(), $format, $writeOptions);
}
$io->success(sprintf('Local translations has been updated from "%s" (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains)));
return 0;
}
$localTranslations = $this->readLocalTranslations($locales, $domains, $this->transPaths);
// Append pulled translations to local ones.
$localTranslations->addBag($providerTranslations->diff($localTranslations));
foreach ($localTranslations->getCatalogues() as $catalogue) {
$this->writer->write($catalogue, $format, $writeOptions);
}
$io->success(sprintf('New translations from "%s" has been written locally (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains)));
return 0;
}
}
@@ -0,0 +1,158 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Translation\Provider\TranslationProviderCollection;
use Symfony\Component\Translation\Reader\TranslationReaderInterface;
use Symfony\Component\Translation\TranslatorBag;
/**
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*
* @experimental in 5.3
*/
final class TranslationPushCommand extends Command
{
use TranslationTrait;
protected static $defaultName = 'translation:push';
protected static $defaultDescription = 'Push translations to a given provider.';
private $providers;
private $reader;
private $transPaths;
private $enabledLocales;
public function __construct(TranslationProviderCollection $providers, TranslationReaderInterface $reader, array $transPaths = [], array $enabledLocales = [])
{
$this->providers = $providers;
$this->reader = $reader;
$this->transPaths = $transPaths;
$this->enabledLocales = $enabledLocales;
parent::__construct();
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$keys = $this->providers->keys();
$defaultProvider = 1 === \count($keys) ? $keys[0] : null;
$this
->setDefinition([
new InputArgument('provider', null !== $defaultProvider ? InputArgument::OPTIONAL : InputArgument::REQUIRED, 'The provider to push translations to.', $defaultProvider),
new InputOption('force', null, InputOption::VALUE_NONE, 'Override existing translations with local ones (it will delete not synchronized messages).'),
new InputOption('delete-missing', null, InputOption::VALUE_NONE, 'Delete translations available on provider but not locally.'),
new InputOption('domains', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the domains to push.'),
new InputOption('locales', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the locales to push.', $this->enabledLocales),
])
->setHelp(<<<'EOF'
The <info>%command.name%</> command pushes translations to the given provider. Only new
translations are pushed, existing ones are not overwritten.
You can overwrite existing translations by using the <comment>--force</> flag:
<info>php %command.full_name% --force provider</>
You can delete provider translations which are not present locally by using the <comment>--delete-missing</> flag:
<info>php %command.full_name% --delete-missing provider</>
Full example:
<info>php %command.full_name% provider --force --delete-missing --domains=messages,validators --locales=en</>
This command pushes all translations associated with the <comment>messages</> and <comment>validators</> domains for the <comment>en</> locale.
Provider translations for the specified domains and locale are deleted if they're not present locally and overwritten if it's the case.
Provider translations for others domains and locales are ignored.
EOF
)
;
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$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)));
}
$io = new SymfonyStyle($input, $output);
$domains = $input->getOption('domains');
$locales = $input->getOption('locales');
$force = $input->getOption('force');
$deleteMissing = $input->getOption('delete-missing');
$localTranslations = $this->readLocalTranslations($locales, $domains, $this->transPaths);
if (!$domains) {
$domains = $this->getDomainsFromTranslatorBag($localTranslations);
}
if (!$deleteMissing && $force) {
$provider->write($localTranslations);
$io->success(sprintf('All local translations has been sent to "%s" (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains)));
return 0;
}
$providerTranslations = $provider->read($domains, $locales);
if ($deleteMissing) {
$provider->delete($providerTranslations->diff($localTranslations));
$io->success(sprintf('Missing translations on "%s" has been deleted (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains)));
// Read provider translations again, after missing translations deletion,
// to avoid push freshly deleted translations.
$providerTranslations = $provider->read($domains, $locales);
}
$translationsToWrite = $localTranslations->diff($providerTranslations);
if ($force) {
$translationsToWrite->addBag($localTranslations->intersect($providerTranslations));
}
$provider->write($translationsToWrite);
$io->success(sprintf('%s local translations has been sent to "%s" (for "%s" locale(s), and "%s" domain(s)).', $force ? 'All' : 'New', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains)));
return 0;
}
private function getDomainsFromTranslatorBag(TranslatorBag $translatorBag): array
{
$domains = [];
foreach ($translatorBag->getCatalogues() as $catalogue) {
$domains += $catalogue->getDomains();
}
return array_unique($domains);
}
}
+78
View File
@@ -0,0 +1,78 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Command;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\MessageCatalogueInterface;
use Symfony\Component\Translation\TranslatorBag;
/**
* @internal
*/
trait TranslationTrait
{
private function readLocalTranslations(array $locales, array $domains, array $transPaths): TranslatorBag
{
$bag = new TranslatorBag();
foreach ($locales as $locale) {
$catalogue = new MessageCatalogue($locale);
foreach ($transPaths as $path) {
$this->reader->read($path, $catalogue);
}
if ($domains) {
foreach ($domains as $domain) {
$catalogue = $this->filterCatalogue($catalogue, $domain);
$bag->addCatalogue($catalogue);
}
} else {
$bag->addCatalogue($catalogue);
}
}
return $bag;
}
private function filterCatalogue(MessageCatalogue $catalogue, string $domain): MessageCatalogue
{
$filteredCatalogue = new MessageCatalogue($catalogue->getLocale());
// extract intl-icu messages only
$intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX;
if ($intlMessages = $catalogue->all($intlDomain)) {
$filteredCatalogue->add($intlMessages, $intlDomain);
}
// extract all messages and subtract intl-icu messages
if ($messages = array_diff($catalogue->all($domain), $intlMessages)) {
$filteredCatalogue->add($messages, $domain);
}
foreach ($catalogue->getResources() as $resource) {
$filteredCatalogue->addResource($resource);
}
if ($metadata = $catalogue->getMetadata('', $intlDomain)) {
foreach ($metadata as $k => $v) {
$filteredCatalogue->setMetadata($k, $v, $intlDomain);
}
}
if ($metadata = $catalogue->getMetadata('', $domain)) {
foreach ($metadata as $k => $v) {
$filteredCatalogue->setMetadata($k, $v, $domain);
}
}
return $filteredCatalogue;
}
}
+267
View File
@@ -0,0 +1,267 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\RuntimeException;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\Translation\Exception\InvalidArgumentException;
use Symfony\Component\Translation\Util\XliffUtils;
/**
* Validates XLIFF files syntax and outputs encountered errors.
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
* @author Robin Chalas <robin.chalas@gmail.com>
* @author Javier Eguiluz <javier.eguiluz@gmail.com>
*/
class XliffLintCommand extends Command
{
protected static $defaultName = 'lint:xliff';
protected static $defaultDescription = 'Lint an XLIFF file and outputs encountered errors';
private $format;
private $displayCorrectFiles;
private $directoryIteratorProvider;
private $isReadableProvider;
private $requireStrictFileNames;
public function __construct(string $name = null, callable $directoryIteratorProvider = null, callable $isReadableProvider = null, bool $requireStrictFileNames = true)
{
parent::__construct($name);
$this->directoryIteratorProvider = $directoryIteratorProvider;
$this->isReadableProvider = $isReadableProvider;
$this->requireStrictFileNames = $requireStrictFileNames;
}
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDescription(self::$defaultDescription)
->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN')
->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format', 'txt')
->setHelp(<<<EOF
The <info>%command.name%</info> command lints an XLIFF file and outputs to STDOUT
the first encountered syntax error.
You can validates XLIFF contents passed from STDIN:
<info>cat filename | php %command.full_name% -</info>
You can also validate the syntax of a file:
<info>php %command.full_name% filename</info>
Or of a whole directory:
<info>php %command.full_name% dirname</info>
<info>php %command.full_name% dirname --format=json</info>
EOF
)
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$filenames = (array) $input->getArgument('filename');
$this->format = $input->getOption('format');
$this->displayCorrectFiles = $output->isVerbose();
if (['-'] === $filenames) {
return $this->display($io, [$this->validate(file_get_contents('php://stdin'))]);
}
if (!$filenames) {
throw new RuntimeException('Please provide a filename or pipe file content to STDIN.');
}
$filesInfo = [];
foreach ($filenames as $filename) {
if (!$this->isReadable($filename)) {
throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename));
}
foreach ($this->getFiles($filename) as $file) {
$filesInfo[] = $this->validate(file_get_contents($file), $file);
}
}
return $this->display($io, $filesInfo);
}
private function validate(string $content, string $file = null): array
{
$errors = [];
// Avoid: Warning DOMDocument::loadXML(): Empty string supplied as input
if ('' === trim($content)) {
return ['file' => $file, 'valid' => true];
}
$internal = libxml_use_internal_errors(true);
$document = new \DOMDocument();
$document->loadXML($content);
if (null !== $targetLanguage = $this->getTargetLanguageFromFile($document)) {
$normalizedLocalePattern = sprintf('(%s|%s)', preg_quote($targetLanguage, '/'), preg_quote(str_replace('-', '_', $targetLanguage), '/'));
// strict file names require translation files to be named '____.locale.xlf'
// otherwise, both '____.locale.xlf' and 'locale.____.xlf' are allowed
// also, the regexp matching must be case-insensitive, as defined for 'target-language' values
// http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html#target-language
$expectedFilenamePattern = $this->requireStrictFileNames ? sprintf('/^.*\.(?i:%s)\.(?:xlf|xliff)/', $normalizedLocalePattern) : sprintf('/^(?:.*\.(?i:%s)|(?i:%s)\..*)\.(?:xlf|xliff)/', $normalizedLocalePattern, $normalizedLocalePattern);
if (0 === preg_match($expectedFilenamePattern, basename($file))) {
$errors[] = [
'line' => -1,
'column' => -1,
'message' => sprintf('There is a mismatch between the language included in the file name ("%s") and the "%s" value used in the "target-language" attribute of the file.', basename($file), $targetLanguage),
];
}
}
foreach (XliffUtils::validateSchema($document) as $xmlError) {
$errors[] = [
'line' => $xmlError['line'],
'column' => $xmlError['column'],
'message' => $xmlError['message'],
];
}
libxml_clear_errors();
libxml_use_internal_errors($internal);
return ['file' => $file, 'valid' => 0 === \count($errors), 'messages' => $errors];
}
private function display(SymfonyStyle $io, array $files)
{
switch ($this->format) {
case 'txt':
return $this->displayTxt($io, $files);
case 'json':
return $this->displayJson($io, $files);
default:
throw new InvalidArgumentException(sprintf('The format "%s" is not supported.', $this->format));
}
}
private function displayTxt(SymfonyStyle $io, array $filesInfo)
{
$countFiles = \count($filesInfo);
$erroredFiles = 0;
foreach ($filesInfo as $info) {
if ($info['valid'] && $this->displayCorrectFiles) {
$io->comment('<info>OK</info>'.($info['file'] ? sprintf(' in %s', $info['file']) : ''));
} elseif (!$info['valid']) {
++$erroredFiles;
$io->text('<error> ERROR </error>'.($info['file'] ? sprintf(' in %s', $info['file']) : ''));
$io->listing(array_map(function ($error) {
// general document errors have a '-1' line number
return -1 === $error['line'] ? $error['message'] : sprintf('Line %d, Column %d: %s', $error['line'], $error['column'], $error['message']);
}, $info['messages']));
}
}
if (0 === $erroredFiles) {
$io->success(sprintf('All %d XLIFF files contain valid syntax.', $countFiles));
} else {
$io->warning(sprintf('%d XLIFF files have valid syntax and %d contain errors.', $countFiles - $erroredFiles, $erroredFiles));
}
return min($erroredFiles, 1);
}
private function displayJson(SymfonyStyle $io, array $filesInfo)
{
$errors = 0;
array_walk($filesInfo, function (&$v) use (&$errors) {
$v['file'] = (string) $v['file'];
if (!$v['valid']) {
++$errors;
}
});
$io->writeln(json_encode($filesInfo, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES));
return min($errors, 1);
}
private function getFiles(string $fileOrDirectory)
{
if (is_file($fileOrDirectory)) {
yield new \SplFileInfo($fileOrDirectory);
return;
}
foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) {
if (!\in_array($file->getExtension(), ['xlf', 'xliff'])) {
continue;
}
yield $file;
}
}
private function getDirectoryIterator(string $directory)
{
$default = function ($directory) {
return new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
\RecursiveIteratorIterator::LEAVES_ONLY
);
};
if (null !== $this->directoryIteratorProvider) {
return ($this->directoryIteratorProvider)($directory, $default);
}
return $default($directory);
}
private function isReadable(string $fileOrDirectory)
{
$default = function ($fileOrDirectory) {
return is_readable($fileOrDirectory);
};
if (null !== $this->isReadableProvider) {
return ($this->isReadableProvider)($fileOrDirectory, $default);
}
return $default($fileOrDirectory);
}
private function getTargetLanguageFromFile(\DOMDocument $xliffContents): ?string
{
foreach ($xliffContents->getElementsByTagName('file')[0]->attributes ?? [] as $attribute) {
if ('target-language' === $attribute->nodeName) {
return $attribute->nodeValue;
}
}
return null;
}
}
@@ -0,0 +1,172 @@
<?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\DataCollector;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
use Symfony\Component\Translation\DataCollectorTranslator;
use Symfony\Component\VarDumper\Cloner\Data;
/**
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*
* @final
*/
class TranslationDataCollector extends DataCollector implements LateDataCollectorInterface
{
private $translator;
public function __construct(DataCollectorTranslator $translator)
{
$this->translator = $translator;
}
/**
* {@inheritdoc}
*/
public function lateCollect()
{
$messages = $this->sanitizeCollectedMessages($this->translator->getCollectedMessages());
$this->data += $this->computeCount($messages);
$this->data['messages'] = $messages;
$this->data = $this->cloneVar($this->data);
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Throwable $exception = null)
{
$this->data['locale'] = $this->translator->getLocale();
$this->data['fallback_locales'] = $this->translator->getFallbackLocales();
}
/**
* {@inheritdoc}
*/
public function reset()
{
$this->data = [];
}
/**
* @return array|Data
*/
public function getMessages()
{
return $this->data['messages'] ?? [];
}
/**
* @return int
*/
public function getCountMissings()
{
return $this->data[DataCollectorTranslator::MESSAGE_MISSING] ?? 0;
}
/**
* @return int
*/
public function getCountFallbacks()
{
return $this->data[DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK] ?? 0;
}
/**
* @return int
*/
public function getCountDefines()
{
return $this->data[DataCollectorTranslator::MESSAGE_DEFINED] ?? 0;
}
public function getLocale()
{
return !empty($this->data['locale']) ? $this->data['locale'] : null;
}
/**
* @internal
*/
public function getFallbackLocales()
{
return (isset($this->data['fallback_locales']) && \count($this->data['fallback_locales']) > 0) ? $this->data['fallback_locales'] : [];
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'translation';
}
private function sanitizeCollectedMessages(array $messages)
{
$result = [];
foreach ($messages as $key => $message) {
$messageId = $message['locale'].$message['domain'].$message['id'];
if (!isset($result[$messageId])) {
$message['count'] = 1;
$message['parameters'] = !empty($message['parameters']) ? [$message['parameters']] : [];
$messages[$key]['translation'] = $this->sanitizeString($message['translation']);
$result[$messageId] = $message;
} else {
if (!empty($message['parameters'])) {
$result[$messageId]['parameters'][] = $message['parameters'];
}
++$result[$messageId]['count'];
}
unset($messages[$key]);
}
return $result;
}
private function computeCount(array $messages)
{
$count = [
DataCollectorTranslator::MESSAGE_DEFINED => 0,
DataCollectorTranslator::MESSAGE_MISSING => 0,
DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK => 0,
];
foreach ($messages as $message) {
++$count[$message['state']];
}
return $count;
}
private function sanitizeString(string $string, int $length = 80)
{
$string = trim(preg_replace('/\s+/', ' ', $string));
if (false !== $encoding = mb_detect_encoding($string, null, true)) {
if (mb_strlen($string, $encoding) > $length) {
return mb_substr($string, 0, $length - 3, $encoding).'...';
}
} elseif (\strlen($string) > $length) {
return substr($string, 0, $length - 3).'...';
}
return $string;
}
}
+171
View File
@@ -0,0 +1,171 @@
<?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\HttpKernel\CacheWarmer\WarmableInterface;
use Symfony\Component\Translation\Exception\InvalidArgumentException;
use Symfony\Contracts\Translation\LocaleAwareInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*/
class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface, WarmableInterface
{
public const MESSAGE_DEFINED = 0;
public const MESSAGE_MISSING = 1;
public const MESSAGE_EQUALS_FALLBACK = 2;
/**
* @var TranslatorInterface|TranslatorBagInterface
*/
private $translator;
private $messages = [];
/**
* @param TranslatorInterface $translator The translator must implement TranslatorBagInterface
*/
public function __construct(TranslatorInterface $translator)
{
if (!$translator instanceof TranslatorBagInterface || !$translator instanceof LocaleAwareInterface) {
throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface, TranslatorBagInterface and LocaleAwareInterface.', get_debug_type($translator)));
}
$this->translator = $translator;
}
/**
* {@inheritdoc}
*/
public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null)
{
$trans = $this->translator->trans($id = (string) $id, $parameters, $domain, $locale);
$this->collectMessage($locale, $domain, $id, $trans, $parameters);
return $trans;
}
/**
* {@inheritdoc}
*/
public function setLocale(string $locale)
{
$this->translator->setLocale($locale);
}
/**
* {@inheritdoc}
*/
public function getLocale()
{
return $this->translator->getLocale();
}
/**
* {@inheritdoc}
*/
public function getCatalogue(string $locale = null)
{
return $this->translator->getCatalogue($locale);
}
/**
* {@inheritdoc}
*/
public function getCatalogues(): array
{
return $this->translator->getCatalogues();
}
/**
* {@inheritdoc}
*
* @return string[]
*/
public function warmUp(string $cacheDir)
{
if ($this->translator instanceof WarmableInterface) {
return (array) $this->translator->warmUp($cacheDir);
}
return [];
}
/**
* Gets the fallback locales.
*
* @return array The fallback locales
*/
public function getFallbackLocales()
{
if ($this->translator instanceof Translator || method_exists($this->translator, 'getFallbackLocales')) {
return $this->translator->getFallbackLocales();
}
return [];
}
/**
* Passes through all unknown calls onto the translator object.
*/
public function __call(string $method, array $args)
{
return $this->translator->{$method}(...$args);
}
/**
* @return array
*/
public function getCollectedMessages()
{
return $this->messages;
}
private function collectMessage(?string $locale, ?string $domain, string $id, string $translation, ?array $parameters = [])
{
if (null === $domain) {
$domain = 'messages';
}
$catalogue = $this->translator->getCatalogue($locale);
$locale = $catalogue->getLocale();
$fallbackLocale = null;
if ($catalogue->defines($id, $domain)) {
$state = self::MESSAGE_DEFINED;
} elseif ($catalogue->has($id, $domain)) {
$state = self::MESSAGE_EQUALS_FALLBACK;
$fallbackCatalogue = $catalogue->getFallbackCatalogue();
while ($fallbackCatalogue) {
if ($fallbackCatalogue->defines($id, $domain)) {
$fallbackLocale = $fallbackCatalogue->getLocale();
break;
}
$fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue();
}
} else {
$state = self::MESSAGE_MISSING;
}
$this->messages[] = [
'locale' => $locale,
'fallbackLocale' => $fallbackLocale,
'domain' => $domain,
'id' => $id,
'translation' => $translation,
'parameters' => $parameters,
'state' => $state,
'transChoiceNumber' => isset($parameters['%count%']) && is_numeric($parameters['%count%']) ? $parameters['%count%'] : null,
];
}
}
@@ -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\DependencyInjection;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
/**
* Adds tagged translation.formatter services to translation writer.
*/
class TranslationDumperPass implements CompilerPassInterface
{
private $writerServiceId;
private $dumperTag;
public function __construct(string $writerServiceId = 'translation.writer', string $dumperTag = 'translation.dumper')
{
if (1 < \func_num_args()) {
trigger_deprecation('symfony/translation', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
}
$this->writerServiceId = $writerServiceId;
$this->dumperTag = $dumperTag;
}
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->writerServiceId)) {
return;
}
$definition = $container->getDefinition($this->writerServiceId);
foreach ($container->findTaggedServiceIds($this->dumperTag, true) as $id => $attributes) {
$definition->addMethodCall('addDumper', [$attributes[0]['alias'], new Reference($id)]);
}
}
}
@@ -0,0 +1,53 @@
<?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\RuntimeException;
use Symfony\Component\DependencyInjection\Reference;
/**
* Adds tagged translation.extractor services to translation extractor.
*/
class TranslationExtractorPass implements CompilerPassInterface
{
private $extractorServiceId;
private $extractorTag;
public function __construct(string $extractorServiceId = 'translation.extractor', string $extractorTag = 'translation.extractor')
{
if (0 < \func_num_args()) {
trigger_deprecation('symfony/translation', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
}
$this->extractorServiceId = $extractorServiceId;
$this->extractorTag = $extractorTag;
}
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->extractorServiceId)) {
return;
}
$definition = $container->getDefinition($this->extractorServiceId);
foreach ($container->findTaggedServiceIds($this->extractorTag, true) as $id => $attributes) {
if (!isset($attributes[0]['alias'])) {
throw new RuntimeException(sprintf('The alias for the tag "translation.extractor" of service "%s" must be set.', $id));
}
$definition->addMethodCall('addExtractor', [$attributes[0]['alias'], new Reference($id)]);
}
}
}
@@ -0,0 +1,93 @@
<?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\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class TranslatorPass implements CompilerPassInterface
{
private $translatorServiceId;
private $readerServiceId;
private $loaderTag;
private $debugCommandServiceId;
private $updateCommandServiceId;
public function __construct(string $translatorServiceId = 'translator.default', string $readerServiceId = 'translation.reader', string $loaderTag = 'translation.loader', string $debugCommandServiceId = 'console.command.translation_debug', string $updateCommandServiceId = 'console.command.translation_update')
{
if (0 < \func_num_args()) {
trigger_deprecation('symfony/translation', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
}
$this->translatorServiceId = $translatorServiceId;
$this->readerServiceId = $readerServiceId;
$this->loaderTag = $loaderTag;
$this->debugCommandServiceId = $debugCommandServiceId;
$this->updateCommandServiceId = $updateCommandServiceId;
}
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->translatorServiceId)) {
return;
}
$loaders = [];
$loaderRefs = [];
foreach ($container->findTaggedServiceIds($this->loaderTag, true) as $id => $attributes) {
$loaderRefs[$id] = new Reference($id);
$loaders[$id][] = $attributes[0]['alias'];
if (isset($attributes[0]['legacy-alias'])) {
$loaders[$id][] = $attributes[0]['legacy-alias'];
}
}
if ($container->hasDefinition($this->readerServiceId)) {
$definition = $container->getDefinition($this->readerServiceId);
foreach ($loaders as $id => $formats) {
foreach ($formats as $format) {
$definition->addMethodCall('addLoader', [$format, $loaderRefs[$id]]);
}
}
}
$container
->findDefinition($this->translatorServiceId)
->replaceArgument(0, ServiceLocatorTagPass::register($container, $loaderRefs))
->replaceArgument(3, $loaders)
;
if (!$container->hasParameter('twig.default_path')) {
return;
}
$paths = array_keys($container->getDefinition('twig.template_iterator')->getArgument(1));
if ($container->hasDefinition($this->debugCommandServiceId)) {
$definition = $container->getDefinition($this->debugCommandServiceId);
$definition->replaceArgument(4, $container->getParameter('twig.default_path'));
if (\count($definition->getArguments()) > 6) {
$definition->replaceArgument(6, $paths);
}
}
if ($container->hasDefinition($this->updateCommandServiceId)) {
$definition = $container->getDefinition($this->updateCommandServiceId);
$definition->replaceArgument(5, $container->getParameter('twig.default_path'));
if (\count($definition->getArguments()) > 7) {
$definition->replaceArgument(7, $paths);
}
}
}
}
@@ -0,0 +1,151 @@
<?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\AbstractRecursivePass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\ServiceLocator;
/**
* @author Yonel Ceruto <yonelceruto@gmail.com>
*/
class TranslatorPathsPass extends AbstractRecursivePass
{
private $translatorServiceId;
private $debugCommandServiceId;
private $updateCommandServiceId;
private $resolverServiceId;
private $level = 0;
private $paths = [];
private $definitions = [];
private $controllers = [];
public function __construct(string $translatorServiceId = 'translator', string $debugCommandServiceId = 'console.command.translation_debug', string $updateCommandServiceId = 'console.command.translation_update', string $resolverServiceId = 'argument_resolver.service')
{
if (0 < \func_num_args()) {
trigger_deprecation('symfony/translation', '5.3', 'Configuring "%s" is deprecated.', __CLASS__);
}
$this->translatorServiceId = $translatorServiceId;
$this->debugCommandServiceId = $debugCommandServiceId;
$this->updateCommandServiceId = $updateCommandServiceId;
$this->resolverServiceId = $resolverServiceId;
}
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition($this->translatorServiceId)) {
return;
}
foreach ($this->findControllerArguments($container) as $controller => $argument) {
$id = substr($controller, 0, strpos($controller, ':') ?: \strlen($controller));
if ($container->hasDefinition($id)) {
[$locatorRef] = $argument->getValues();
$this->controllers[(string) $locatorRef][$container->getDefinition($id)->getClass()] = true;
}
}
try {
parent::process($container);
$paths = [];
foreach ($this->paths as $class => $_) {
if (($r = $container->getReflectionClass($class)) && !$r->isInterface()) {
$paths[] = $r->getFileName();
foreach ($r->getTraits() as $trait) {
$paths[] = $trait->getFileName();
}
}
}
if ($paths) {
if ($container->hasDefinition($this->debugCommandServiceId)) {
$definition = $container->getDefinition($this->debugCommandServiceId);
$definition->replaceArgument(6, array_merge($definition->getArgument(6), $paths));
}
if ($container->hasDefinition($this->updateCommandServiceId)) {
$definition = $container->getDefinition($this->updateCommandServiceId);
$definition->replaceArgument(7, array_merge($definition->getArgument(7), $paths));
}
}
} finally {
$this->level = 0;
$this->paths = [];
$this->definitions = [];
}
}
protected function processValue($value, bool $isRoot = false)
{
if ($value instanceof Reference) {
if ((string) $value === $this->translatorServiceId) {
for ($i = $this->level - 1; $i >= 0; --$i) {
$class = $this->definitions[$i]->getClass();
if (ServiceLocator::class === $class) {
if (!isset($this->controllers[$this->currentId])) {
continue;
}
foreach ($this->controllers[$this->currentId] as $class => $_) {
$this->paths[$class] = true;
}
} else {
$this->paths[$class] = true;
}
break;
}
}
return $value;
}
if ($value instanceof Definition) {
$this->definitions[$this->level++] = $value;
$value = parent::processValue($value, $isRoot);
unset($this->definitions[--$this->level]);
return $value;
}
return parent::processValue($value, $isRoot);
}
private function findControllerArguments(ContainerBuilder $container): array
{
if ($container->hasDefinition($this->resolverServiceId)) {
$argument = $container->getDefinition($this->resolverServiceId)->getArgument(0);
if ($argument instanceof Reference) {
$argument = $container->getDefinition($argument);
}
return $argument->getArgument(0);
}
if ($container->hasDefinition('debug.'.$this->resolverServiceId)) {
$argument = $container->getDefinition('debug.'.$this->resolverServiceId)->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);
}
return [];
}
}
+60
View File
@@ -0,0 +1,60 @@
<?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\Dumper;
use Symfony\Component\Translation\MessageCatalogue;
/**
* CsvFileDumper generates a csv formatted string representation of a message catalogue.
*
* @author Stealth35
*/
class CsvFileDumper extends FileDumper
{
private $delimiter = ';';
private $enclosure = '"';
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = [])
{
$handle = fopen('php://memory', 'r+');
foreach ($messages->all($domain) as $source => $target) {
fputcsv($handle, [$source, $target], $this->delimiter, $this->enclosure);
}
rewind($handle);
$output = stream_get_contents($handle);
fclose($handle);
return $output;
}
/**
* Sets the delimiter and escape character for CSV.
*/
public function setCsvControl(string $delimiter = ';', string $enclosure = '"')
{
$this->delimiter = $delimiter;
$this->enclosure = $enclosure;
}
/**
* {@inheritdoc}
*/
protected function getExtension()
{
return 'csv';
}
}
+30
View File
@@ -0,0 +1,30 @@
<?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\Dumper;
use Symfony\Component\Translation\MessageCatalogue;
/**
* DumperInterface is the interface implemented by all translation dumpers.
* There is no common option.
*
* @author Michel Salib <michelsalib@hotmail.com>
*/
interface DumperInterface
{
/**
* Dumps the message catalogue.
*
* @param array $options Options that are used by the dumper
*/
public function dump(MessageCatalogue $messages, array $options = []);
}
+112
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\Dumper;
use Symfony\Component\Translation\Exception\InvalidArgumentException;
use Symfony\Component\Translation\Exception\RuntimeException;
use Symfony\Component\Translation\MessageCatalogue;
/**
* FileDumper is an implementation of DumperInterface that dump a message catalogue to file(s).
*
* Options:
* - path (mandatory): the directory where the files should be saved
*
* @author Michel Salib <michelsalib@hotmail.com>
*/
abstract class FileDumper implements DumperInterface
{
/**
* A template for the relative paths to files.
*
* @var string
*/
protected $relativePathTemplate = '%domain%.%locale%.%extension%';
/**
* Sets the template for the relative paths to files.
*
* @param string $relativePathTemplate A template for the relative paths to files
*/
public function setRelativePathTemplate(string $relativePathTemplate)
{
$this->relativePathTemplate = $relativePathTemplate;
}
/**
* {@inheritdoc}
*/
public function dump(MessageCatalogue $messages, array $options = [])
{
if (!\array_key_exists('path', $options)) {
throw new InvalidArgumentException('The file dumper needs a path option.');
}
// save a file for each domain
foreach ($messages->getDomains() as $domain) {
$fullpath = $options['path'].'/'.$this->getRelativePath($domain, $messages->getLocale());
if (!file_exists($fullpath)) {
$directory = \dirname($fullpath);
if (!file_exists($directory) && !@mkdir($directory, 0777, true)) {
throw new RuntimeException(sprintf('Unable to create directory "%s".', $directory));
}
}
$intlDomain = $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX;
$intlMessages = $messages->all($intlDomain);
if ($intlMessages) {
$intlPath = $options['path'].'/'.$this->getRelativePath($intlDomain, $messages->getLocale());
file_put_contents($intlPath, $this->formatCatalogue($messages, $intlDomain, $options));
$messages->replace([], $intlDomain);
try {
if ($messages->all($domain)) {
file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options));
}
continue;
} finally {
$messages->replace($intlMessages, $intlDomain);
}
}
file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options));
}
}
/**
* Transforms a domain of a message catalogue to its string representation.
*
* @return string representation
*/
abstract public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []);
/**
* Gets the file extension of the dumper.
*
* @return string file extension
*/
abstract protected function getExtension();
/**
* Gets the relative file path using the template.
*/
private function getRelativePath(string $domain, string $locale): string
{
return strtr($this->relativePathTemplate, [
'%domain%' => $domain,
'%locale%' => $locale,
'%extension%' => $this->getExtension(),
]);
}
}
+104
View File
@@ -0,0 +1,104 @@
<?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\Dumper;
use Symfony\Component\Translation\MessageCatalogue;
/**
* IcuResDumper generates an ICU ResourceBundle formatted string representation of a message catalogue.
*
* @author Stealth35
*/
class IcuResFileDumper extends FileDumper
{
/**
* {@inheritdoc}
*/
protected $relativePathTemplate = '%domain%/%locale%.%extension%';
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = [])
{
$data = $indexes = $resources = '';
foreach ($messages->all($domain) as $source => $target) {
$indexes .= pack('v', \strlen($data) + 28);
$data .= $source."\0";
}
$data .= $this->writePadding($data);
$keyTop = $this->getPosition($data);
foreach ($messages->all($domain) as $source => $target) {
$resources .= pack('V', $this->getPosition($data));
$data .= pack('V', \strlen($target))
.mb_convert_encoding($target."\0", 'UTF-16LE', 'UTF-8')
.$this->writePadding($data)
;
}
$resOffset = $this->getPosition($data);
$data .= pack('v', \count($messages->all($domain)))
.$indexes
.$this->writePadding($data)
.$resources
;
$bundleTop = $this->getPosition($data);
$root = pack('V7',
$resOffset + (2 << 28), // Resource Offset + Resource Type
6, // Index length
$keyTop, // Index keys top
$bundleTop, // Index resources top
$bundleTop, // Index bundle top
\count($messages->all($domain)), // Index max table length
0 // Index attributes
);
$header = pack('vC2v4C12@32',
32, // Header size
0xDA, 0x27, // Magic number 1 and 2
20, 0, 0, 2, // Rest of the header, ..., Size of a char
0x52, 0x65, 0x73, 0x42, // Data format identifier
1, 2, 0, 0, // Data version
1, 4, 0, 0 // Unicode version
);
return $header.$root.$data;
}
private function writePadding(string $data): ?string
{
$padding = \strlen($data) % 4;
return $padding ? str_repeat("\xAA", 4 - $padding) : null;
}
private function getPosition(string $data)
{
return (\strlen($data) + 28) / 4;
}
/**
* {@inheritdoc}
*/
protected function getExtension()
{
return 'res';
}
}
+45
View File
@@ -0,0 +1,45 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Dumper;
use Symfony\Component\Translation\MessageCatalogue;
/**
* IniFileDumper generates an ini formatted string representation of a message catalogue.
*
* @author Stealth35
*/
class IniFileDumper extends FileDumper
{
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = [])
{
$output = '';
foreach ($messages->all($domain) as $source => $target) {
$escapeTarget = str_replace('"', '\"', $target);
$output .= $source.'="'.$escapeTarget."\"\n";
}
return $output;
}
/**
* {@inheritdoc}
*/
protected function getExtension()
{
return 'ini';
}
}
+40
View File
@@ -0,0 +1,40 @@
<?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\Dumper;
use Symfony\Component\Translation\MessageCatalogue;
/**
* JsonFileDumper generates an json formatted string representation of a message catalogue.
*
* @author singles
*/
class JsonFileDumper extends FileDumper
{
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = [])
{
$flags = $options['json_encoding'] ?? \JSON_PRETTY_PRINT;
return json_encode($messages->all($domain), $flags);
}
/**
* {@inheritdoc}
*/
protected function getExtension()
{
return 'json';
}
}
+82
View File
@@ -0,0 +1,82 @@
<?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\Dumper;
use Symfony\Component\Translation\Loader\MoFileLoader;
use Symfony\Component\Translation\MessageCatalogue;
/**
* MoFileDumper generates a gettext formatted string representation of a message catalogue.
*
* @author Stealth35
*/
class MoFileDumper extends FileDumper
{
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = [])
{
$sources = $targets = $sourceOffsets = $targetOffsets = '';
$offsets = [];
$size = 0;
foreach ($messages->all($domain) as $source => $target) {
$offsets[] = array_map('strlen', [$sources, $source, $targets, $target]);
$sources .= "\0".$source;
$targets .= "\0".$target;
++$size;
}
$header = [
'magicNumber' => MoFileLoader::MO_LITTLE_ENDIAN_MAGIC,
'formatRevision' => 0,
'count' => $size,
'offsetId' => MoFileLoader::MO_HEADER_SIZE,
'offsetTranslated' => MoFileLoader::MO_HEADER_SIZE + (8 * $size),
'sizeHashes' => 0,
'offsetHashes' => MoFileLoader::MO_HEADER_SIZE + (16 * $size),
];
$sourcesSize = \strlen($sources);
$sourcesStart = $header['offsetHashes'] + 1;
foreach ($offsets as $offset) {
$sourceOffsets .= $this->writeLong($offset[1])
.$this->writeLong($offset[0] + $sourcesStart);
$targetOffsets .= $this->writeLong($offset[3])
.$this->writeLong($offset[2] + $sourcesStart + $sourcesSize);
}
$output = implode('', array_map([$this, 'writeLong'], $header))
.$sourceOffsets
.$targetOffsets
.$sources
.$targets
;
return $output;
}
/**
* {@inheritdoc}
*/
protected function getExtension()
{
return 'mo';
}
private function writeLong($str): string
{
return pack('V*', $str);
}
}
+38
View File
@@ -0,0 +1,38 @@
<?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\Dumper;
use Symfony\Component\Translation\MessageCatalogue;
/**
* PhpFileDumper generates PHP files from a message catalogue.
*
* @author Michel Salib <michelsalib@hotmail.com>
*/
class PhpFileDumper extends FileDumper
{
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = [])
{
return "<?php\n\nreturn ".var_export($messages->all($domain), true).";\n";
}
/**
* {@inheritdoc}
*/
protected function getExtension()
{
return 'php';
}
}
+137
View File
@@ -0,0 +1,137 @@
<?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\Dumper;
use Symfony\Component\Translation\MessageCatalogue;
/**
* PoFileDumper generates a gettext formatted string representation of a message catalogue.
*
* @author Stealth35
*/
class PoFileDumper extends FileDumper
{
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = [])
{
$output = 'msgid ""'."\n";
$output .= 'msgstr ""'."\n";
$output .= '"Content-Type: text/plain; charset=UTF-8\n"'."\n";
$output .= '"Content-Transfer-Encoding: 8bit\n"'."\n";
$output .= '"Language: '.$messages->getLocale().'\n"'."\n";
$output .= "\n";
$newLine = false;
foreach ($messages->all($domain) as $source => $target) {
if ($newLine) {
$output .= "\n";
} else {
$newLine = true;
}
$metadata = $messages->getMetadata($source, $domain);
if (isset($metadata['comments'])) {
$output .= $this->formatComments($metadata['comments']);
}
if (isset($metadata['flags'])) {
$output .= $this->formatComments(implode(',', (array) $metadata['flags']), ',');
}
if (isset($metadata['sources'])) {
$output .= $this->formatComments(implode(' ', (array) $metadata['sources']), ':');
}
$sourceRules = $this->getStandardRules($source);
$targetRules = $this->getStandardRules($target);
if (2 == \count($sourceRules) && [] !== $targetRules) {
$output .= sprintf('msgid "%s"'."\n", $this->escape($sourceRules[0]));
$output .= sprintf('msgid_plural "%s"'."\n", $this->escape($sourceRules[1]));
foreach ($targetRules as $i => $targetRule) {
$output .= sprintf('msgstr[%d] "%s"'."\n", $i, $this->escape($targetRule));
}
} else {
$output .= sprintf('msgid "%s"'."\n", $this->escape($source));
$output .= sprintf('msgstr "%s"'."\n", $this->escape($target));
}
}
return $output;
}
private function getStandardRules(string $id)
{
// Partly copied from TranslatorTrait::trans.
$parts = [];
if (preg_match('/^\|++$/', $id)) {
$parts = explode('|', $id);
} elseif (preg_match_all('/(?:\|\||[^\|])++/', $id, $matches)) {
$parts = $matches[0];
}
$intervalRegexp = <<<'EOF'
/^(?P<interval>
({\s*
(\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*)
\s*})
|
(?P<left_delimiter>[\[\]])
\s*
(?P<left>-Inf|\-?\d+(\.\d+)?)
\s*,\s*
(?P<right>\+?Inf|\-?\d+(\.\d+)?)
\s*
(?P<right_delimiter>[\[\]])
)\s*(?P<message>.*?)$/xs
EOF;
$standardRules = [];
foreach ($parts as $part) {
$part = trim(str_replace('||', '|', $part));
if (preg_match($intervalRegexp, $part)) {
// Explicit rule is not a standard rule.
return [];
} else {
$standardRules[] = $part;
}
}
return $standardRules;
}
/**
* {@inheritdoc}
*/
protected function getExtension()
{
return 'po';
}
private function escape(string $str): string
{
return addcslashes($str, "\0..\37\42\134");
}
private function formatComments($comments, string $prefix = ''): ?string
{
$output = null;
foreach ((array) $comments as $comment) {
$output .= sprintf('#%s %s'."\n", $prefix, $comment);
}
return $output;
}
}
+61
View File
@@ -0,0 +1,61 @@
<?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\Dumper;
use Symfony\Component\Translation\MessageCatalogue;
/**
* QtFileDumper generates ts files from a message catalogue.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class QtFileDumper extends FileDumper
{
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = [])
{
$dom = new \DOMDocument('1.0', 'utf-8');
$dom->formatOutput = true;
$ts = $dom->appendChild($dom->createElement('TS'));
$context = $ts->appendChild($dom->createElement('context'));
$context->appendChild($dom->createElement('name', $domain));
foreach ($messages->all($domain) as $source => $target) {
$message = $context->appendChild($dom->createElement('message'));
$metadata = $messages->getMetadata($source, $domain);
if (isset($metadata['sources'])) {
foreach ((array) $metadata['sources'] as $location) {
$loc = explode(':', $location, 2);
$location = $message->appendChild($dom->createElement('location'));
$location->setAttribute('filename', $loc[0]);
if (isset($loc[1])) {
$location->setAttribute('line', $loc[1]);
}
}
}
$message->appendChild($dom->createElement('source', $source));
$message->appendChild($dom->createElement('translation', $target));
}
return $dom->saveXML();
}
/**
* {@inheritdoc}
*/
protected function getExtension()
{
return 'ts';
}
}
+203
View File
@@ -0,0 +1,203 @@
<?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\Dumper;
use Symfony\Component\Translation\Exception\InvalidArgumentException;
use Symfony\Component\Translation\MessageCatalogue;
/**
* XliffFileDumper generates xliff files from a message catalogue.
*
* @author Michel Salib <michelsalib@hotmail.com>
*/
class XliffFileDumper extends FileDumper
{
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = [])
{
$xliffVersion = '1.2';
if (\array_key_exists('xliff_version', $options)) {
$xliffVersion = $options['xliff_version'];
}
if (\array_key_exists('default_locale', $options)) {
$defaultLocale = $options['default_locale'];
} else {
$defaultLocale = \Locale::getDefault();
}
if ('1.2' === $xliffVersion) {
return $this->dumpXliff1($defaultLocale, $messages, $domain, $options);
}
if ('2.0' === $xliffVersion) {
return $this->dumpXliff2($defaultLocale, $messages, $domain);
}
throw new InvalidArgumentException(sprintf('No support implemented for dumping XLIFF version "%s".', $xliffVersion));
}
/**
* {@inheritdoc}
*/
protected function getExtension()
{
return 'xlf';
}
private function dumpXliff1(string $defaultLocale, MessageCatalogue $messages, ?string $domain, array $options = [])
{
$toolInfo = ['tool-id' => 'symfony', 'tool-name' => 'Symfony'];
if (\array_key_exists('tool_info', $options)) {
$toolInfo = array_merge($toolInfo, $options['tool_info']);
}
$dom = new \DOMDocument('1.0', 'utf-8');
$dom->formatOutput = true;
$xliff = $dom->appendChild($dom->createElement('xliff'));
$xliff->setAttribute('version', '1.2');
$xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:1.2');
$xliffFile = $xliff->appendChild($dom->createElement('file'));
$xliffFile->setAttribute('source-language', str_replace('_', '-', $defaultLocale));
$xliffFile->setAttribute('target-language', str_replace('_', '-', $messages->getLocale()));
$xliffFile->setAttribute('datatype', 'plaintext');
$xliffFile->setAttribute('original', 'file.ext');
$xliffHead = $xliffFile->appendChild($dom->createElement('header'));
$xliffTool = $xliffHead->appendChild($dom->createElement('tool'));
foreach ($toolInfo as $id => $value) {
$xliffTool->setAttribute($id, $value);
}
$xliffBody = $xliffFile->appendChild($dom->createElement('body'));
foreach ($messages->all($domain) as $source => $target) {
$translation = $dom->createElement('trans-unit');
$translation->setAttribute('id', strtr(substr(base64_encode(hash('sha256', $source, true)), 0, 7), '/+', '._'));
$translation->setAttribute('resname', $source);
$s = $translation->appendChild($dom->createElement('source'));
$s->appendChild($dom->createTextNode($source));
// Does the target contain characters requiring a CDATA section?
$text = 1 === preg_match('/[&<>]/', $target) ? $dom->createCDATASection($target) : $dom->createTextNode($target);
$targetElement = $dom->createElement('target');
$metadata = $messages->getMetadata($source, $domain);
if ($this->hasMetadataArrayInfo('target-attributes', $metadata)) {
foreach ($metadata['target-attributes'] as $name => $value) {
$targetElement->setAttribute($name, $value);
}
}
$t = $translation->appendChild($targetElement);
$t->appendChild($text);
if ($this->hasMetadataArrayInfo('notes', $metadata)) {
foreach ($metadata['notes'] as $note) {
if (!isset($note['content'])) {
continue;
}
$n = $translation->appendChild($dom->createElement('note'));
$n->appendChild($dom->createTextNode($note['content']));
if (isset($note['priority'])) {
$n->setAttribute('priority', $note['priority']);
}
if (isset($note['from'])) {
$n->setAttribute('from', $note['from']);
}
}
}
$xliffBody->appendChild($translation);
}
return $dom->saveXML();
}
private function dumpXliff2(string $defaultLocale, MessageCatalogue $messages, ?string $domain)
{
$dom = new \DOMDocument('1.0', 'utf-8');
$dom->formatOutput = true;
$xliff = $dom->appendChild($dom->createElement('xliff'));
$xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:2.0');
$xliff->setAttribute('version', '2.0');
$xliff->setAttribute('srcLang', str_replace('_', '-', $defaultLocale));
$xliff->setAttribute('trgLang', str_replace('_', '-', $messages->getLocale()));
$xliffFile = $xliff->appendChild($dom->createElement('file'));
if (str_ends_with($domain, MessageCatalogue::INTL_DOMAIN_SUFFIX)) {
$xliffFile->setAttribute('id', substr($domain, 0, -\strlen(MessageCatalogue::INTL_DOMAIN_SUFFIX)).'.'.$messages->getLocale());
} else {
$xliffFile->setAttribute('id', $domain.'.'.$messages->getLocale());
}
foreach ($messages->all($domain) as $source => $target) {
$translation = $dom->createElement('unit');
$translation->setAttribute('id', strtr(substr(base64_encode(hash('sha256', $source, true)), 0, 7), '/+', '._'));
if (\strlen($source) <= 80) {
$translation->setAttribute('name', $source);
}
$metadata = $messages->getMetadata($source, $domain);
// Add notes section
if ($this->hasMetadataArrayInfo('notes', $metadata)) {
$notesElement = $dom->createElement('notes');
foreach ($metadata['notes'] as $note) {
$n = $dom->createElement('note');
$n->appendChild($dom->createTextNode($note['content'] ?? ''));
unset($note['content']);
foreach ($note as $name => $value) {
$n->setAttribute($name, $value);
}
$notesElement->appendChild($n);
}
$translation->appendChild($notesElement);
}
$segment = $translation->appendChild($dom->createElement('segment'));
$s = $segment->appendChild($dom->createElement('source'));
$s->appendChild($dom->createTextNode($source));
// Does the target contain characters requiring a CDATA section?
$text = 1 === preg_match('/[&<>]/', $target) ? $dom->createCDATASection($target) : $dom->createTextNode($target);
$targetElement = $dom->createElement('target');
if ($this->hasMetadataArrayInfo('target-attributes', $metadata)) {
foreach ($metadata['target-attributes'] as $name => $value) {
$targetElement->setAttribute($name, $value);
}
}
$t = $segment->appendChild($targetElement);
$t->appendChild($text);
$xliffFile->appendChild($translation);
}
return $dom->saveXML();
}
private function hasMetadataArrayInfo(string $key, array $metadata = null): bool
{
return is_iterable($metadata[$key] ?? null);
}
}
+62
View File
@@ -0,0 +1,62 @@
<?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\Dumper;
use Symfony\Component\Translation\Exception\LogicException;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Util\ArrayConverter;
use Symfony\Component\Yaml\Yaml;
/**
* YamlFileDumper generates yaml files from a message catalogue.
*
* @author Michel Salib <michelsalib@hotmail.com>
*/
class YamlFileDumper extends FileDumper
{
private $extension;
public function __construct(string $extension = 'yml')
{
$this->extension = $extension;
}
/**
* {@inheritdoc}
*/
public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = [])
{
if (!class_exists(Yaml::class)) {
throw new LogicException('Dumping translations in the YAML format requires the Symfony Yaml component.');
}
$data = $messages->all($domain);
if (isset($options['as_tree']) && $options['as_tree']) {
$data = ArrayConverter::expandToTree($data);
}
if (isset($options['inline']) && ($inline = (int) $options['inline']) > 0) {
return Yaml::dump($data, $inline);
}
return Yaml::dump($data);
}
/**
* {@inheritdoc}
*/
protected function getExtension()
{
return $this->extension;
}
}
@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Exception;
/**
* Exception interface for all exceptions thrown by the component.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface ExceptionInterface extends \Throwable
{
}
@@ -0,0 +1,24 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Exception;
class IncompleteDsnException extends InvalidArgumentException
{
public function __construct(string $message, string $dsn = null, \Throwable $previous = null)
{
if ($dsn) {
$message = sprintf('Invalid "%s" provider DSN: ', $dsn).$message;
}
parent::__construct($message, 0, $previous);
}
}
@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Exception;
/**
* Base InvalidArgumentException for the Translation component.
*
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*/
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}
@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Exception;
/**
* Thrown when a resource cannot be loaded.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class InvalidResourceException extends \InvalidArgumentException implements ExceptionInterface
{
}
+21
View File
@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Exception;
/**
* Base LogicException for Translation component.
*
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*/
class LogicException extends \LogicException implements ExceptionInterface
{
}
@@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Exception;
/**
* @author Oskar Stark <oskarstark@googlemail.com>
*/
class MissingRequiredOptionException extends IncompleteDsnException
{
public function __construct(string $option, string $dsn = null, \Throwable $previous = null)
{
$message = sprintf('The option "%s" is required but missing.', $option);
parent::__construct($message, $dsn, $previous);
}
}
@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Exception;
/**
* Thrown when a resource does not exist.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class NotFoundResourceException extends \InvalidArgumentException implements ExceptionInterface
{
}
@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Exception;
use Symfony\Contracts\HttpClient\ResponseInterface;
/**
* @author Fabien Potencier <fabien@symfony.com>
*
* @experimental in 5.3
*/
class ProviderException extends RuntimeException implements ProviderExceptionInterface
{
private $response;
private $debug;
public function __construct(string $message, ResponseInterface $response, int $code = 0, \Exception $previous = null)
{
$this->response = $response;
$this->debug .= $response->getInfo('debug') ?? '';
parent::__construct($message, $code, $previous);
}
public function getResponse(): ResponseInterface
{
return $this->response;
}
public function getDebug(): string
{
return $this->debug;
}
}
@@ -0,0 +1,25 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Exception;
/**
* @author Fabien Potencier <fabien@symfony.com>
*
* @experimental in 5.3
*/
interface ProviderExceptionInterface extends ExceptionInterface
{
/*
* Returns debug info coming from the Symfony\Contracts\HttpClient\ResponseInterface
*/
public function getDebug(): string;
}
@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Exception;
/**
* Base RuntimeException for the Translation component.
*
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*/
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}
@@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Exception;
use Symfony\Component\Translation\Bridge;
use Symfony\Component\Translation\Provider\Dsn;
class UnsupportedSchemeException extends LogicException
{
private const SCHEME_TO_PACKAGE_MAP = [
'crowdin' => [
'class' => Bridge\Crowdin\CrowdinProviderFactory::class,
'package' => 'symfony/crowdin-translation-provider',
],
'loco' => [
'class' => Bridge\Loco\LocoProviderFactory::class,
'package' => 'symfony/loco-translation-provider',
],
'lokalise' => [
'class' => Bridge\Lokalise\LokaliseProviderFactory::class,
'package' => 'symfony/lokalise-translation-provider',
],
];
public function __construct(Dsn $dsn, string $name = null, array $supported = [])
{
$provider = $dsn->getScheme();
if (false !== $pos = strpos($provider, '+')) {
$provider = substr($provider, 0, $pos);
}
$package = self::SCHEME_TO_PACKAGE_MAP[$provider] ?? null;
if ($package && !class_exists($package['class'])) {
parent::__construct(sprintf('Unable to synchronize translations via "%s" as the provider is not installed; try running "composer require %s".', $provider, $package['package']));
return;
}
$message = sprintf('The "%s" scheme is not supported', $dsn->getScheme());
if ($name && $supported) {
$message .= sprintf('; supported schemes for translation provider "%s" are: "%s"', $name, implode('", "', $supported));
}
parent::__construct($message.'.');
}
}
@@ -0,0 +1,76 @@
<?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 Symfony\Component\Translation\Exception\InvalidArgumentException;
/**
* Base class used by classes that extract translation messages from files.
*
* @author Marcos D. Sánchez <marcosdsanchez@gmail.com>
*/
abstract class AbstractFileExtractor
{
/**
* @param string|iterable $resource Files, a file or a directory
*
* @return iterable
*/
protected function extractFiles($resource)
{
if (is_iterable($resource)) {
$files = [];
foreach ($resource as $file) {
if ($this->canBeExtracted($file)) {
$files[] = $this->toSplFileInfo($file);
}
}
} elseif (is_file($resource)) {
$files = $this->canBeExtracted($resource) ? [$this->toSplFileInfo($resource)] : [];
} else {
$files = $this->extractFromDirectory($resource);
}
return $files;
}
private function toSplFileInfo(string $file): \SplFileInfo
{
return new \SplFileInfo($file);
}
/**
* @return bool
*
* @throws InvalidArgumentException
*/
protected function isFile(string $file)
{
if (!is_file($file)) {
throw new InvalidArgumentException(sprintf('The "%s" file does not exist.', $file));
}
return true;
}
/**
* @return bool
*/
abstract protected function canBeExtracted(string $file);
/**
* @param string|array $resource Files, a file or a directory
*
* @return iterable files to be extracted
*/
abstract protected function extractFromDirectory($resource);
}
+57
View File
@@ -0,0 +1,57 @@
<?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 Symfony\Component\Translation\MessageCatalogue;
/**
* ChainExtractor extracts translation messages from template files.
*
* @author Michel Salib <michelsalib@hotmail.com>
*/
class ChainExtractor implements ExtractorInterface
{
/**
* The extractors.
*
* @var ExtractorInterface[]
*/
private $extractors = [];
/**
* Adds a loader to the translation extractor.
*/
public function addExtractor(string $format, ExtractorInterface $extractor)
{
$this->extractors[$format] = $extractor;
}
/**
* {@inheritdoc}
*/
public function setPrefix(string $prefix)
{
foreach ($this->extractors as $extractor) {
$extractor->setPrefix($prefix);
}
}
/**
* {@inheritdoc}
*/
public function extract($directory, MessageCatalogue $catalogue)
{
foreach ($this->extractors as $extractor) {
$extractor->extract($directory, $catalogue);
}
}
}
@@ -0,0 +1,35 @@
<?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 Symfony\Component\Translation\MessageCatalogue;
/**
* Extracts translation messages from a directory or files to the catalogue.
* New found messages are injected to the catalogue using the prefix.
*
* @author Michel Salib <michelsalib@hotmail.com>
*/
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
*/
public function extract($resource, MessageCatalogue $catalogue);
/**
* Sets the prefix that should be used for new found messages.
*/
public function setPrefix(string $prefix);
}
+336
View File
@@ -0,0 +1,336 @@
<?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 Symfony\Component\Finder\Finder;
use Symfony\Component\Translation\MessageCatalogue;
/**
* PhpExtractor extracts translation messages from a PHP template.
*
* @author Michel Salib <michelsalib@hotmail.com>
*/
class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
{
public const MESSAGE_TOKEN = 300;
public const METHOD_ARGUMENTS_TOKEN = 1000;
public const DOMAIN_TOKEN = 1001;
/**
* Prefix for new found message.
*
* @var string
*/
private $prefix = '';
/**
* The sequence that captures translation messages.
*
* @var array
*/
protected $sequences = [
[
'->',
'trans',
'(',
self::MESSAGE_TOKEN,
',',
self::METHOD_ARGUMENTS_TOKEN,
',',
self::DOMAIN_TOKEN,
],
[
'->',
'trans',
'(',
self::MESSAGE_TOKEN,
],
[
'new',
'TranslatableMessage',
'(',
self::MESSAGE_TOKEN,
',',
self::METHOD_ARGUMENTS_TOKEN,
',',
self::DOMAIN_TOKEN,
],
[
'new',
'TranslatableMessage',
'(',
self::MESSAGE_TOKEN,
],
[
'new',
'\\',
'Symfony',
'\\',
'Component',
'\\',
'Translation',
'\\',
'TranslatableMessage',
'(',
self::MESSAGE_TOKEN,
',',
self::METHOD_ARGUMENTS_TOKEN,
',',
self::DOMAIN_TOKEN,
],
[
'new',
'\Symfony\Component\Translation\TranslatableMessage',
'(',
self::MESSAGE_TOKEN,
',',
self::METHOD_ARGUMENTS_TOKEN,
',',
self::DOMAIN_TOKEN,
],
[
'new',
'\\',
'Symfony',
'\\',
'Component',
'\\',
'Translation',
'\\',
'TranslatableMessage',
'(',
self::MESSAGE_TOKEN,
],
[
'new',
'\Symfony\Component\Translation\TranslatableMessage',
'(',
self::MESSAGE_TOKEN,
],
[
't',
'(',
self::MESSAGE_TOKEN,
',',
self::METHOD_ARGUMENTS_TOKEN,
',',
self::DOMAIN_TOKEN,
],
[
't',
'(',
self::MESSAGE_TOKEN,
],
];
/**
* {@inheritdoc}
*/
public function extract($resource, MessageCatalogue $catalog)
{
$files = $this->extractFiles($resource);
foreach ($files as $file) {
$this->parseTokens(token_get_all(file_get_contents($file)), $catalog, $file);
gc_mem_caches();
}
}
/**
* {@inheritdoc}
*/
public function setPrefix(string $prefix)
{
$this->prefix = $prefix;
}
/**
* Normalizes a token.
*
* @param mixed $token
*
* @return string|null
*/
protected function normalizeToken($token)
{
if (isset($token[1]) && 'b"' !== $token) {
return $token[1];
}
return $token;
}
/**
* Seeks to a non-whitespace token.
*/
private function seekToNextRelevantToken(\Iterator $tokenIterator)
{
for (; $tokenIterator->valid(); $tokenIterator->next()) {
$t = $tokenIterator->current();
if (\T_WHITESPACE !== $t[0]) {
break;
}
}
}
private function skipMethodArgument(\Iterator $tokenIterator)
{
$openBraces = 0;
for (; $tokenIterator->valid(); $tokenIterator->next()) {
$t = $tokenIterator->current();
if ('[' === $t[0] || '(' === $t[0]) {
++$openBraces;
}
if (']' === $t[0] || ')' === $t[0]) {
--$openBraces;
}
if ((0 === $openBraces && ',' === $t[0]) || (-1 === $openBraces && ')' === $t[0])) {
break;
}
}
}
/**
* Extracts the message from the iterator while the tokens
* match allowed message tokens.
*/
private function getValue(\Iterator $tokenIterator)
{
$message = '';
$docToken = '';
$docPart = '';
for (; $tokenIterator->valid(); $tokenIterator->next()) {
$t = $tokenIterator->current();
if ('.' === $t) {
// Concatenate with next token
continue;
}
if (!isset($t[1])) {
break;
}
switch ($t[0]) {
case \T_START_HEREDOC:
$docToken = $t[1];
break;
case \T_ENCAPSED_AND_WHITESPACE:
case \T_CONSTANT_ENCAPSED_STRING:
if ('' === $docToken) {
$message .= PhpStringTokenParser::parse($t[1]);
} else {
$docPart = $t[1];
}
break;
case \T_END_HEREDOC:
if ($indentation = strspn($t[1], ' ')) {
$docPartWithLineBreaks = $docPart;
$docPart = '';
foreach (preg_split('~(\r\n|\n|\r)~', $docPartWithLineBreaks, -1, \PREG_SPLIT_DELIM_CAPTURE) as $str) {
if (\in_array($str, ["\r\n", "\n", "\r"], true)) {
$docPart .= $str;
} else {
$docPart .= substr($str, $indentation);
}
}
}
$message .= PhpStringTokenParser::parseDocString($docToken, $docPart);
$docToken = '';
$docPart = '';
break;
case \T_WHITESPACE:
break;
default:
break 2;
}
}
return $message;
}
/**
* Extracts trans message from PHP tokens.
*/
protected function parseTokens(array $tokens, MessageCatalogue $catalog, string $filename)
{
$tokenIterator = new \ArrayIterator($tokens);
for ($key = 0; $key < $tokenIterator->count(); ++$key) {
foreach ($this->sequences as $sequence) {
$message = '';
$domain = 'messages';
$tokenIterator->seek($key);
foreach ($sequence as $sequenceKey => $item) {
$this->seekToNextRelevantToken($tokenIterator);
if ($this->normalizeToken($tokenIterator->current()) === $item) {
$tokenIterator->next();
continue;
} elseif (self::MESSAGE_TOKEN === $item) {
$message = $this->getValue($tokenIterator);
if (\count($sequence) === ($sequenceKey + 1)) {
break;
}
} elseif (self::METHOD_ARGUMENTS_TOKEN === $item) {
$this->skipMethodArgument($tokenIterator);
} elseif (self::DOMAIN_TOKEN === $item) {
$domainToken = $this->getValue($tokenIterator);
if ('' !== $domainToken) {
$domain = $domainToken;
}
break;
} else {
break;
}
}
if ($message) {
$catalog->set($message, $this->prefix.$message, $domain);
$metadata = $catalog->getMetadata($message, $domain) ?? [];
$normalizedFilename = preg_replace('{[\\\\/]+}', '/', $filename);
$metadata['sources'][] = $normalizedFilename.':'.$tokens[$key][2];
$catalog->setMetadata($message, $metadata, $domain);
break;
}
}
}
}
/**
* @return bool
*
* @throws \InvalidArgumentException
*/
protected function canBeExtracted(string $file)
{
return $this->isFile($file) && 'php' === pathinfo($file, \PATHINFO_EXTENSION);
}
/**
* {@inheritdoc}
*/
protected function extractFromDirectory($directory)
{
$finder = new Finder();
return $finder->files()->name('*.php')->in($directory);
}
}
@@ -0,0 +1,142 @@
<?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;
/*
* The following is derived from code at http://github.com/nikic/PHP-Parser
*
* Copyright (c) 2011 by Nikita Popov
*
* Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
*
* * The names of the contributors may not be used to endorse or
* promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
class PhpStringTokenParser
{
protected static $replacements = [
'\\' => '\\',
'$' => '$',
'n' => "\n",
'r' => "\r",
't' => "\t",
'f' => "\f",
'v' => "\v",
'e' => "\x1B",
];
/**
* Parses a string token.
*
* @param string $str String token content
*
* @return string The parsed string
*/
public static function parse(string $str)
{
$bLength = 0;
if ('b' === $str[0]) {
$bLength = 1;
}
if ('\'' === $str[$bLength]) {
return str_replace(
['\\\\', '\\\''],
['\\', '\''],
substr($str, $bLength + 1, -1)
);
} else {
return self::parseEscapeSequences(substr($str, $bLength + 1, -1), '"');
}
}
/**
* Parses escape sequences in strings (all string types apart from single quoted).
*
* @param string $str String without quotes
* @param string|null $quote Quote type
*
* @return string String with escape sequences parsed
*/
public static function parseEscapeSequences(string $str, string $quote = null)
{
if (null !== $quote) {
$str = str_replace('\\'.$quote, $quote, $str);
}
return preg_replace_callback(
'~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3})~',
[__CLASS__, 'parseCallback'],
$str
);
}
private static function parseCallback(array $matches): string
{
$str = $matches[1];
if (isset(self::$replacements[$str])) {
return self::$replacements[$str];
} elseif ('x' === $str[0] || 'X' === $str[0]) {
return \chr(hexdec($str));
} else {
return \chr(octdec($str));
}
}
/**
* Parses a constant doc string.
*
* @param string $startToken Doc string start token content (<<<SMTHG)
* @param string $str String token content
*
* @return string Parsed string
*/
public static function parseDocString(string $startToken, string $str)
{
// strip last newline (thanks tokenizer for sticking it into the string!)
$str = preg_replace('~(\r\n|\n|\r)$~', '', $str);
// nowdoc string
if (str_contains($startToken, '\'')) {
return $str;
}
return self::parseEscapeSequences($str, null);
}
}
+60
View File
@@ -0,0 +1,60 @@
<?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\Formatter;
use Symfony\Component\Translation\Exception\InvalidArgumentException;
use Symfony\Component\Translation\Exception\LogicException;
/**
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*/
class IntlFormatter implements IntlFormatterInterface
{
private $hasMessageFormatter;
private $cache = [];
/**
* {@inheritdoc}
*/
public function formatIntl(string $message, string $locale, array $parameters = []): string
{
// MessageFormatter constructor throws an exception if the message is empty
if ('' === $message) {
return '';
}
if (!$formatter = $this->cache[$locale][$message] ?? null) {
if (!($this->hasMessageFormatter ?? $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 {
$this->cache[$locale][$message] = $formatter = new \MessageFormatter($locale, $message);
} catch (\IntlException $e) {
throw new InvalidArgumentException(sprintf('Invalid message format (error #%d): ', intl_get_error_code()).intl_get_error_message(), 0, $e);
}
}
foreach ($parameters as $key => $value) {
if (\in_array($key[0] ?? null, ['%', '{'], true)) {
unset($parameters[$key]);
$parameters[trim($key, '%{ }')] = $value;
}
}
if (false === $message = $formatter->format($parameters)) {
throw new InvalidArgumentException(sprintf('Unable to format message (error #%s): ', $formatter->getErrorCode()).$formatter->getErrorMessage());
}
return $message;
}
}
@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Formatter;
/**
* Formats ICU message patterns.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
interface IntlFormatterInterface
{
/**
* Formats a localized message using rules defined by ICU MessageFormat.
*
* @see http://icu-project.org/apiref/icu4c/classMessageFormat.html#details
*/
public function formatIntl(string $message, string $locale, array $parameters = []): string;
}
@@ -0,0 +1,56 @@
<?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\Formatter;
use Symfony\Component\Translation\IdentityTranslator;
use Symfony\Contracts\Translation\TranslatorInterface;
// Help opcache.preload discover always-needed symbols
class_exists(IntlFormatter::class);
/**
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*/
class MessageFormatter implements MessageFormatterInterface, IntlFormatterInterface
{
private $translator;
private $intlFormatter;
/**
* @param TranslatorInterface|null $translator An identity translator to use as selector for pluralization
*/
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 = [])
{
if ($this->translator instanceof TranslatorInterface) {
return $this->translator->trans($message, $parameters, null, $locale);
}
return strtr($message, $parameters);
}
/**
* {@inheritdoc}
*/
public function formatIntl(string $message, string $locale, array $parameters = []): string
{
return $this->intlFormatter->formatIntl($message, $locale, $parameters);
}
}
@@ -0,0 +1,30 @@
<?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\Formatter;
/**
* @author Guilherme Blanco <guilhermeblanco@hotmail.com>
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*/
interface MessageFormatterInterface
{
/**
* Formats a localized message pattern with given arguments.
*
* @param string $message The message (may also be an object that can be cast to string)
* @param string $locale The message locale
* @param array $parameters An array of parameters for the message
*
* @return string
*/
public function format(string $message, string $locale, array $parameters = []);
}
+26
View File
@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation;
use Symfony\Contracts\Translation\LocaleAwareInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Contracts\Translation\TranslatorTrait;
/**
* IdentityTranslator does not translate anything.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class IdentityTranslator implements TranslatorInterface, LocaleAwareInterface
{
use TranslatorTrait;
}
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2004-2021 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+58
View File
@@ -0,0 +1,58 @@
<?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\Loader;
use Symfony\Component\Translation\MessageCatalogue;
/**
* ArrayLoader loads translations from a PHP array.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class ArrayLoader implements LoaderInterface
{
/**
* {@inheritdoc}
*/
public function load($resource, string $locale, string $domain = 'messages')
{
$resource = $this->flatten($resource);
$catalogue = new MessageCatalogue($locale);
$catalogue->add($resource, $domain);
return $catalogue;
}
/**
* Flattens an nested array of translations.
*
* The scheme used is:
* 'key' => ['key2' => ['key3' => 'value']]
* Becomes:
* 'key.key2.key3' => 'value'
*/
private function flatten(array $messages): array
{
$result = [];
foreach ($messages as $key => $value) {
if (\is_array($value)) {
foreach ($this->flatten($value) as $k => $v) {
$result[$key.'.'.$k] = $v;
}
} else {
$result[$key] = $value;
}
}
return $result;
}
}
+65
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\Loader;
use Symfony\Component\Translation\Exception\NotFoundResourceException;
/**
* CsvFileLoader loads translations from CSV files.
*
* @author Saša Stamenković <umpirsky@gmail.com>
*/
class CsvFileLoader extends FileLoader
{
private $delimiter = ';';
private $enclosure = '"';
private $escape = '\\';
/**
* {@inheritdoc}
*/
protected function loadResource(string $resource)
{
$messages = [];
try {
$file = new \SplFileObject($resource, 'rb');
} catch (\RuntimeException $e) {
throw new NotFoundResourceException(sprintf('Error opening file "%s".', $resource), 0, $e);
}
$file->setFlags(\SplFileObject::READ_CSV | \SplFileObject::SKIP_EMPTY);
$file->setCsvControl($this->delimiter, $this->enclosure, $this->escape);
foreach ($file as $data) {
if (false === $data) {
continue;
}
if ('#' !== substr($data[0], 0, 1) && isset($data[1]) && 2 === \count($data)) {
$messages[$data[0]] = $data[1];
}
}
return $messages;
}
/**
* Sets the delimiter, enclosure, and escape character for CSV.
*/
public function setCsvControl(string $delimiter = ';', string $enclosure = '"', string $escape = '\\')
{
$this->delimiter = $delimiter;
$this->enclosure = $enclosure;
$this->escape = $escape;
}
}
+63
View File
@@ -0,0 +1,63 @@
<?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\Loader;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Translation\Exception\InvalidResourceException;
use Symfony\Component\Translation\Exception\NotFoundResourceException;
/**
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*/
abstract class FileLoader extends ArrayLoader
{
/**
* {@inheritdoc}
*/
public function load($resource, string $locale, string $domain = 'messages')
{
if (!stream_is_local($resource)) {
throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
}
if (!file_exists($resource)) {
throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
}
$messages = $this->loadResource($resource);
// empty resource
if (null === $messages) {
$messages = [];
}
// not an array
if (!\is_array($messages)) {
throw new InvalidResourceException(sprintf('Unable to load file "%s".', $resource));
}
$catalogue = parent::load($messages, $locale, $domain);
if (class_exists(FileResource::class)) {
$catalogue->addResource(new FileResource($resource));
}
return $catalogue;
}
/**
* @return array
*
* @throws InvalidResourceException if stream content has an invalid format
*/
abstract protected function loadResource(string $resource);
}
+61
View File
@@ -0,0 +1,61 @@
<?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\Loader;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Translation\Exception\InvalidResourceException;
use Symfony\Component\Translation\Exception\NotFoundResourceException;
use Symfony\Component\Translation\MessageCatalogue;
/**
* IcuResFileLoader loads translations from a resource bundle.
*
* @author stealth35
*/
class IcuDatFileLoader extends IcuResFileLoader
{
/**
* {@inheritdoc}
*/
public function load($resource, string $locale, string $domain = 'messages')
{
if (!stream_is_local($resource.'.dat')) {
throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
}
if (!file_exists($resource.'.dat')) {
throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
}
try {
$rb = new \ResourceBundle($locale, $resource);
} catch (\Exception $e) {
$rb = null;
}
if (!$rb) {
throw new InvalidResourceException(sprintf('Cannot load resource "%s".', $resource));
} elseif (intl_is_failure($rb->getErrorCode())) {
throw new InvalidResourceException($rb->getErrorMessage(), $rb->getErrorCode());
}
$messages = $this->flatten($rb);
$catalogue = new MessageCatalogue($locale);
$catalogue->add($messages, $domain);
if (class_exists(FileResource::class)) {
$catalogue->addResource(new FileResource($resource.'.dat'));
}
return $catalogue;
}
}
+91
View File
@@ -0,0 +1,91 @@
<?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\Loader;
use Symfony\Component\Config\Resource\DirectoryResource;
use Symfony\Component\Translation\Exception\InvalidResourceException;
use Symfony\Component\Translation\Exception\NotFoundResourceException;
use Symfony\Component\Translation\MessageCatalogue;
/**
* IcuResFileLoader loads translations from a resource bundle.
*
* @author stealth35
*/
class IcuResFileLoader implements LoaderInterface
{
/**
* {@inheritdoc}
*/
public function load($resource, string $locale, string $domain = 'messages')
{
if (!stream_is_local($resource)) {
throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
}
if (!is_dir($resource)) {
throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
}
try {
$rb = new \ResourceBundle($locale, $resource);
} catch (\Exception $e) {
$rb = null;
}
if (!$rb) {
throw new InvalidResourceException(sprintf('Cannot load resource "%s".', $resource));
} elseif (intl_is_failure($rb->getErrorCode())) {
throw new InvalidResourceException($rb->getErrorMessage(), $rb->getErrorCode());
}
$messages = $this->flatten($rb);
$catalogue = new MessageCatalogue($locale);
$catalogue->add($messages, $domain);
if (class_exists(DirectoryResource::class)) {
$catalogue->addResource(new DirectoryResource($resource));
}
return $catalogue;
}
/**
* Flattens an ResourceBundle.
*
* The scheme used is:
* key { key2 { key3 { "value" } } }
* Becomes:
* 'key.key2.key3' => 'value'
*
* This function takes an array by reference and will modify it
*
* @param \ResourceBundle $rb The ResourceBundle that will be flattened
* @param array $messages Used internally for recursive calls
* @param string $path Current path being parsed, used internally for recursive calls
*
* @return array the flattened ResourceBundle
*/
protected function flatten(\ResourceBundle $rb, array &$messages = [], string $path = null)
{
foreach ($rb as $key => $value) {
$nodePath = $path ? $path.'.'.$key : $key;
if ($value instanceof \ResourceBundle) {
$this->flatten($value, $messages, $nodePath);
} else {
$messages[$nodePath] = $value;
}
}
return $messages;
}
}
+28
View File
@@ -0,0 +1,28 @@
<?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\Loader;
/**
* IniFileLoader loads translations from an ini file.
*
* @author stealth35
*/
class IniFileLoader extends FileLoader
{
/**
* {@inheritdoc}
*/
protected function loadResource(string $resource)
{
return parse_ini_file($resource, true);
}
}
+60
View File
@@ -0,0 +1,60 @@
<?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\Loader;
use Symfony\Component\Translation\Exception\InvalidResourceException;
/**
* JsonFileLoader loads translations from an json file.
*
* @author singles
*/
class JsonFileLoader extends FileLoader
{
/**
* {@inheritdoc}
*/
protected function loadResource(string $resource)
{
$messages = [];
if ($data = file_get_contents($resource)) {
$messages = json_decode($data, true);
if (0 < $errorCode = json_last_error()) {
throw new InvalidResourceException('Error parsing JSON: '.$this->getJSONErrorMessage($errorCode));
}
}
return $messages;
}
/**
* Translates JSON_ERROR_* constant into meaningful message.
*/
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';
}
}
}
+38
View File
@@ -0,0 +1,38 @@
<?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\Loader;
use Symfony\Component\Translation\Exception\InvalidResourceException;
use Symfony\Component\Translation\Exception\NotFoundResourceException;
use Symfony\Component\Translation\MessageCatalogue;
/**
* LoaderInterface is the interface implemented by all translation loaders.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface LoaderInterface
{
/**
* Loads a locale.
*
* @param mixed $resource A resource
* @param string $locale A locale
* @param string $domain The domain
*
* @return MessageCatalogue A MessageCatalogue instance
*
* @throws NotFoundResourceException when the resource cannot be found
* @throws InvalidResourceException when the resource cannot be loaded
*/
public function load($resource, string $locale, string $domain = 'messages');
}
+140
View File
@@ -0,0 +1,140 @@
<?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\Loader;
use Symfony\Component\Translation\Exception\InvalidResourceException;
/**
* @copyright Copyright (c) 2010, Union of RAD http://union-of-rad.org (http://lithify.me/)
*/
class MoFileLoader extends FileLoader
{
/**
* Magic used for validating the format of an MO file as well as
* detecting if the machine used to create that file was little endian.
*/
public const MO_LITTLE_ENDIAN_MAGIC = 0x950412de;
/**
* Magic used for validating the format of an MO file as well as
* detecting if the machine used to create that file was big endian.
*/
public const MO_BIG_ENDIAN_MAGIC = 0xde120495;
/**
* The size of the header of an MO file in bytes.
*/
public const MO_HEADER_SIZE = 28;
/**
* 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)
{
$stream = fopen($resource, 'r');
$stat = fstat($stream);
if ($stat['size'] < self::MO_HEADER_SIZE) {
throw new InvalidResourceException('MO stream content has an invalid format.');
}
$magic = unpack('V1', fread($stream, 4));
$magic = hexdec(substr(dechex(current($magic)), -8));
if (self::MO_LITTLE_ENDIAN_MAGIC == $magic) {
$isBigEndian = false;
} elseif (self::MO_BIG_ENDIAN_MAGIC == $magic) {
$isBigEndian = true;
} else {
throw new InvalidResourceException('MO stream content has an invalid format.');
}
// formatRevision
$this->readLong($stream, $isBigEndian);
$count = $this->readLong($stream, $isBigEndian);
$offsetId = $this->readLong($stream, $isBigEndian);
$offsetTranslated = $this->readLong($stream, $isBigEndian);
// sizeHashes
$this->readLong($stream, $isBigEndian);
// offsetHashes
$this->readLong($stream, $isBigEndian);
$messages = [];
for ($i = 0; $i < $count; ++$i) {
$pluralId = null;
$translated = null;
fseek($stream, $offsetId + $i * 8);
$length = $this->readLong($stream, $isBigEndian);
$offset = $this->readLong($stream, $isBigEndian);
if ($length < 1) {
continue;
}
fseek($stream, $offset);
$singularId = fread($stream, $length);
if (str_contains($singularId, "\000")) {
[$singularId, $pluralId] = explode("\000", $singularId);
}
fseek($stream, $offsetTranslated + $i * 8);
$length = $this->readLong($stream, $isBigEndian);
$offset = $this->readLong($stream, $isBigEndian);
if ($length < 1) {
continue;
}
fseek($stream, $offset);
$translated = fread($stream, $length);
if (str_contains($translated, "\000")) {
$translated = explode("\000", $translated);
}
$ids = ['singular' => $singularId, 'plural' => $pluralId];
$item = compact('ids', 'translated');
if (!empty($item['ids']['singular'])) {
$id = $item['ids']['singular'];
if (isset($item['ids']['plural'])) {
$id .= '|'.$item['ids']['plural'];
}
$messages[$id] = stripcslashes(implode('|', (array) $item['translated']));
}
}
fclose($stream);
return array_filter($messages);
}
/**
* Reads an unsigned long from stream respecting endianness.
*
* @param resource $stream
*/
private function readLong($stream, bool $isBigEndian): int
{
$result = unpack($isBigEndian ? 'N1' : 'V1', fread($stream, 4));
$result = current($result);
return (int) substr($result, -8);
}
}
+42
View File
@@ -0,0 +1,42 @@
<?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\Loader;
/**
* PhpFileLoader loads translations from PHP files returning an array of translations.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class PhpFileLoader extends FileLoader
{
private static $cache = [];
/**
* {@inheritdoc}
*/
protected function loadResource(string $resource)
{
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))) {
self::$cache = null;
}
if (null === self::$cache) {
return require $resource;
}
if (isset(self::$cache[$resource])) {
return self::$cache[$resource];
}
return self::$cache[$resource] = require $resource;
}
}
+149
View File
@@ -0,0 +1,149 @@
<?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\Loader;
/**
* @copyright Copyright (c) 2010, Union of RAD https://github.com/UnionOfRAD/lithium
* @copyright Copyright (c) 2012, Clemens Tolboom
*/
class PoFileLoader extends FileLoader
{
/**
* Parses portable object (PO) format.
*
* From https://www.gnu.org/software/gettext/manual/gettext.html#PO-Files
* we should be able to parse files having:
*
* white-space
* # translator-comments
* #. extracted-comments
* #: reference...
* #, flag...
* #| msgid previous-untranslated-string
* msgid untranslated-string
* msgstr translated-string
*
* extra or different lines are:
*
* #| msgctxt previous-context
* #| msgid previous-untranslated-string
* msgctxt context
*
* #| msgid previous-untranslated-string-singular
* #| msgid_plural previous-untranslated-string-plural
* msgid untranslated-string-singular
* msgid_plural untranslated-string-plural
* msgstr[0] translated-string-case-0
* ...
* msgstr[N] translated-string-case-n
*
* The definition states:
* - white-space and comments are optional.
* - msgid "" that an empty singleline defines a header.
*
* This parser sacrifices some features of the reference implementation the
* differences to that implementation are as follows.
* - No support for comments spanning multiple lines.
* - Translator and extracted comments are treated as being the same type.
* - 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)
{
$stream = fopen($resource, 'r');
$defaults = [
'ids' => [],
'translated' => null,
];
$messages = [];
$item = $defaults;
$flags = [];
while ($line = fgets($stream)) {
$line = trim($line);
if ('' === $line) {
// Whitespace indicated current item is done
if (!\in_array('fuzzy', $flags)) {
$this->addMessage($messages, $item);
}
$item = $defaults;
$flags = [];
} elseif ('#,' === substr($line, 0, 2)) {
$flags = array_map('trim', explode(',', substr($line, 2)));
} elseif ('msgid "' === substr($line, 0, 7)) {
// 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)) {
$item['translated'] = substr($line, 8, -1);
} elseif ('"' === $line[0]) {
$continues = isset($item['translated']) ? 'translated' : 'ids';
if (\is_array($item[$continues])) {
end($item[$continues]);
$item[$continues][key($item[$continues])] .= substr($line, 1, -1);
} else {
$item[$continues] .= substr($line, 1, -1);
}
} elseif ('msgid_plural "' === substr($line, 0, 14)) {
$item['ids']['plural'] = substr($line, 14, -1);
} elseif ('msgstr[' === substr($line, 0, 7)) {
$size = strpos($line, ']');
$item['translated'][(int) substr($line, 7, 1)] = substr($line, $size + 3, -1);
}
}
// save last item
if (!\in_array('fuzzy', $flags)) {
$this->addMessage($messages, $item);
}
fclose($stream);
return $messages;
}
/**
* Save a translation item to the messages.
*
* 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)
{
if (!empty($item['ids']['singular'])) {
$id = stripcslashes($item['ids']['singular']);
if (isset($item['ids']['plural'])) {
$id .= '|'.stripcslashes($item['ids']['plural']);
}
$translated = (array) $item['translated'];
// PO are by definition indexed so sort by index.
ksort($translated);
// Make sure every index is filled.
end($translated);
$count = key($translated);
// Fill missing spots with '-'.
$empties = array_fill(0, $count + 1, '-');
$translated += $empties;
ksort($translated);
$messages[$id] = stripcslashes(implode('|', $translated));
}
}
}
+82
View File
@@ -0,0 +1,82 @@
<?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\Loader;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Config\Util\XmlUtils;
use Symfony\Component\Translation\Exception\InvalidResourceException;
use Symfony\Component\Translation\Exception\NotFoundResourceException;
use Symfony\Component\Translation\Exception\RuntimeException;
use Symfony\Component\Translation\MessageCatalogue;
/**
* QtFileLoader loads translations from QT Translations XML files.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*/
class QtFileLoader implements LoaderInterface
{
/**
* {@inheritdoc}
*/
public function load($resource, string $locale, string $domain = 'messages')
{
if (!class_exists(XmlUtils::class)) {
throw new RuntimeException('Loading translations from the QT format requires the Symfony Config component.');
}
if (!stream_is_local($resource)) {
throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
}
if (!file_exists($resource)) {
throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
}
try {
$dom = XmlUtils::loadFile($resource);
} catch (\InvalidArgumentException $e) {
throw new InvalidResourceException(sprintf('Unable to load "%s".', $resource), $e->getCode(), $e);
}
$internalErrors = libxml_use_internal_errors(true);
libxml_clear_errors();
$xpath = new \DOMXPath($dom);
$nodes = $xpath->evaluate('//TS/context/name[text()="'.$domain.'"]');
$catalogue = new MessageCatalogue($locale);
if (1 == $nodes->length) {
$translations = $nodes->item(0)->nextSibling->parentNode->parentNode->getElementsByTagName('message');
foreach ($translations as $translation) {
$translationValue = (string) $translation->getElementsByTagName('translation')->item(0)->nodeValue;
if (!empty($translationValue)) {
$catalogue->set(
(string) $translation->getElementsByTagName('source')->item(0)->nodeValue,
$translationValue,
$domain
);
}
$translation = $translation->nextSibling;
}
if (class_exists(FileResource::class)) {
$catalogue->addResource(new FileResource($resource));
}
}
libxml_use_internal_errors($internalErrors);
return $catalogue;
}
}
+232
View File
@@ -0,0 +1,232 @@
<?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\Loader;
use Symfony\Component\Config\Resource\FileResource;
use Symfony\Component\Config\Util\Exception\InvalidXmlException;
use Symfony\Component\Config\Util\Exception\XmlParsingException;
use Symfony\Component\Config\Util\XmlUtils;
use Symfony\Component\Translation\Exception\InvalidResourceException;
use Symfony\Component\Translation\Exception\NotFoundResourceException;
use Symfony\Component\Translation\Exception\RuntimeException;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Util\XliffUtils;
/**
* XliffFileLoader loads translations from XLIFF files.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class XliffFileLoader implements LoaderInterface
{
/**
* {@inheritdoc}
*/
public function load($resource, string $locale, string $domain = 'messages')
{
if (!class_exists(XmlUtils::class)) {
throw new RuntimeException('Loading translations from the Xliff format requires the Symfony Config component.');
}
if (!$this->isXmlString($resource)) {
if (!stream_is_local($resource)) {
throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
}
if (!file_exists($resource)) {
throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
}
if (!is_file($resource)) {
throw new InvalidResourceException(sprintf('This is neither a file nor an XLIFF string "%s".', $resource));
}
}
try {
if ($this->isXmlString($resource)) {
$dom = XmlUtils::parse($resource);
} else {
$dom = XmlUtils::loadFile($resource);
}
} catch (\InvalidArgumentException | XmlParsingException | InvalidXmlException $e) {
throw new InvalidResourceException(sprintf('Unable to load "%s": ', $resource).$e->getMessage(), $e->getCode(), $e);
}
if ($errors = XliffUtils::validateSchema($dom)) {
throw new InvalidResourceException(sprintf('Invalid resource provided: "%s"; Errors: ', $resource).XliffUtils::getErrorsAsString($errors));
}
$catalogue = new MessageCatalogue($locale);
$this->extract($dom, $catalogue, $domain);
if (is_file($resource) && class_exists(FileResource::class)) {
$catalogue->addResource(new FileResource($resource));
}
return $catalogue;
}
private function extract(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain)
{
$xliffVersion = XliffUtils::getVersionNumber($dom);
if ('1.2' === $xliffVersion) {
$this->extractXliff1($dom, $catalogue, $domain);
}
if ('2.0' === $xliffVersion) {
$this->extractXliff2($dom, $catalogue, $domain);
}
}
/**
* Extract messages and metadata from DOMDocument into a MessageCatalogue.
*/
private function extractXliff1(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain)
{
$xml = simplexml_import_dom($dom);
$encoding = $dom->encoding ? strtoupper($dom->encoding) : null;
$namespace = 'urn:oasis:names:tc:xliff:document:1.2';
$xml->registerXPathNamespace('xliff', $namespace);
foreach ($xml->xpath('//xliff:file') as $file) {
$fileAttributes = $file->attributes();
$file->registerXPathNamespace('xliff', $namespace);
foreach ($file->xpath('.//xliff:trans-unit') as $translation) {
$attributes = $translation->attributes();
if (!(isset($attributes['resname']) || isset($translation->source))) {
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
$target = $this->utf8ToCharset((string) ($translation->target ?? $translation->source), $encoding);
$catalogue->set((string) $source, $target, $domain);
$metadata = [
'source' => (string) $translation->source,
'file' => [
'original' => (string) $fileAttributes['original'],
],
];
if ($notes = $this->parseNotesMetadata($translation->note, $encoding)) {
$metadata['notes'] = $notes;
}
if (isset($translation->target) && $translation->target->attributes()) {
$metadata['target-attributes'] = [];
foreach ($translation->target->attributes() as $key => $value) {
$metadata['target-attributes'][$key] = (string) $value;
}
}
if (isset($attributes['id'])) {
$metadata['id'] = (string) $attributes['id'];
}
$catalogue->setMetadata((string) $source, $metadata, $domain);
}
}
}
private function extractXliff2(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain)
{
$xml = simplexml_import_dom($dom);
$encoding = $dom->encoding ? strtoupper($dom->encoding) : null;
$xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:2.0');
foreach ($xml->xpath('//xliff:unit') as $unit) {
foreach ($unit->segment as $segment) {
$attributes = $unit->attributes();
$source = $attributes['name'] ?? $segment->source;
// If the xlf file has another encoding specified, try to convert it because
// simple_xml will always return utf-8 encoded values
$target = $this->utf8ToCharset((string) ($segment->target ?? $segment->source), $encoding);
$catalogue->set((string) $source, $target, $domain);
$metadata = [];
if (isset($segment->target) && $segment->target->attributes()) {
$metadata['target-attributes'] = [];
foreach ($segment->target->attributes() as $key => $value) {
$metadata['target-attributes'][$key] = (string) $value;
}
}
if (isset($unit->notes)) {
$metadata['notes'] = [];
foreach ($unit->notes->note as $noteNode) {
$note = [];
foreach ($noteNode->attributes() as $key => $value) {
$note[$key] = (string) $value;
}
$note['content'] = (string) $noteNode;
$metadata['notes'][] = $note;
}
}
$catalogue->setMetadata((string) $source, $metadata, $domain);
}
}
}
/**
* Convert a UTF8 string to the specified encoding.
*/
private function utf8ToCharset(string $content, string $encoding = null): string
{
if ('UTF-8' !== $encoding && !empty($encoding)) {
return mb_convert_encoding($content, $encoding, 'UTF-8');
}
return $content;
}
private function parseNotesMetadata(\SimpleXMLElement $noteElement = null, string $encoding = null): array
{
$notes = [];
if (null === $noteElement) {
return $notes;
}
/** @var \SimpleXMLElement $xmlNote */
foreach ($noteElement as $xmlNote) {
$noteAttributes = $xmlNote->attributes();
$note = ['content' => $this->utf8ToCharset((string) $xmlNote, $encoding)];
if (isset($noteAttributes['priority'])) {
$note['priority'] = (int) $noteAttributes['priority'];
}
if (isset($noteAttributes['from'])) {
$note['from'] = (string) $noteAttributes['from'];
}
$notes[] = $note;
}
return $notes;
}
private function isXmlString(string $resource): bool
{
return 0 === strpos($resource, '<?xml');
}
}
+54
View File
@@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Loader;
use Symfony\Component\Translation\Exception\InvalidResourceException;
use Symfony\Component\Translation\Exception\LogicException;
use Symfony\Component\Yaml\Exception\ParseException;
use Symfony\Component\Yaml\Parser as YamlParser;
use Symfony\Component\Yaml\Yaml;
/**
* YamlFileLoader loads translations from Yaml files.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class YamlFileLoader extends FileLoader
{
private $yamlParser;
/**
* {@inheritdoc}
*/
protected function loadResource(string $resource)
{
if (null === $this->yamlParser) {
if (!class_exists(\Symfony\Component\Yaml\Parser::class)) {
throw new LogicException('Loading translations from the YAML format requires the Symfony Yaml component.');
}
$this->yamlParser = new YamlParser();
}
try {
$messages = $this->yamlParser->parseFile($resource, Yaml::PARSE_CONSTANT);
} catch (ParseException $e) {
throw new InvalidResourceException(sprintf('The file "%s" does not contain valid YAML: ', $resource).$e->getMessage(), 0, $e);
}
if (null !== $messages && !\is_array($messages)) {
throw new InvalidResourceException(sprintf('Unable to load file "%s".', $resource));
}
return $messages ?: [];
}
}
+135
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;
use Psr\Log\LoggerInterface;
use Symfony\Component\Translation\Exception\InvalidArgumentException;
use Symfony\Contracts\Translation\LocaleAwareInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*/
class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface
{
/**
* @var TranslatorInterface|TranslatorBagInterface
*/
private $translator;
private $logger;
/**
* @param TranslatorInterface $translator The translator must implement TranslatorBagInterface
*/
public function __construct(TranslatorInterface $translator, LoggerInterface $logger)
{
if (!$translator instanceof TranslatorBagInterface || !$translator instanceof LocaleAwareInterface) {
throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface, TranslatorBagInterface and LocaleAwareInterface.', get_debug_type($translator)));
}
$this->translator = $translator;
$this->logger = $logger;
}
/**
* {@inheritdoc}
*/
public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null)
{
$trans = $this->translator->trans($id = (string) $id, $parameters, $domain, $locale);
$this->log($id, $domain, $locale);
return $trans;
}
/**
* {@inheritdoc}
*/
public function setLocale(string $locale)
{
$prev = $this->translator->getLocale();
$this->translator->setLocale($locale);
if ($prev === $locale) {
return;
}
$this->logger->debug(sprintf('The locale of the translator has changed from "%s" to "%s".', $prev, $locale));
}
/**
* {@inheritdoc}
*/
public function getLocale()
{
return $this->translator->getLocale();
}
/**
* {@inheritdoc}
*/
public function getCatalogue(string $locale = null)
{
return $this->translator->getCatalogue($locale);
}
/**
* {@inheritdoc}
*/
public function getCatalogues(): array
{
return $this->translator->getCatalogues();
}
/**
* Gets the fallback locales.
*
* @return array The fallback locales
*/
public function getFallbackLocales()
{
if ($this->translator instanceof Translator || method_exists($this->translator, 'getFallbackLocales')) {
return $this->translator->getFallbackLocales();
}
return [];
}
/**
* Passes through all unknown calls onto the translator object.
*/
public function __call(string $method, array $args)
{
return $this->translator->{$method}(...$args);
}
/**
* Logs for missing translations.
*/
private function log(string $id, ?string $domain, ?string $locale)
{
if (null === $domain) {
$domain = 'messages';
}
$catalogue = $this->translator->getCatalogue($locale);
if ($catalogue->defines($id, $domain)) {
return;
}
if ($catalogue->has($id, $domain)) {
$this->logger->debug('Translation use fallback catalogue.', ['id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()]);
} else {
$this->logger->warning('Translation not found.', ['id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()]);
}
}
}
+314
View File
@@ -0,0 +1,314 @@
<?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\Config\Resource\ResourceInterface;
use Symfony\Component\Translation\Exception\LogicException;
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterface
{
private $messages = [];
private $metadata = [];
private $resources = [];
private $locale;
private $fallbackCatalogue;
private $parent;
/**
* @param array $messages An array of messages classified by domain
*/
public function __construct(string $locale, array $messages = [])
{
$this->locale = $locale;
$this->messages = $messages;
}
/**
* {@inheritdoc}
*/
public function getLocale()
{
return $this->locale;
}
/**
* {@inheritdoc}
*/
public function getDomains()
{
$domains = [];
foreach ($this->messages as $domain => $messages) {
if (str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) {
$domain = substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX));
}
$domains[$domain] = $domain;
}
return array_values($domains);
}
/**
* {@inheritdoc}
*/
public function all(string $domain = null)
{
if (null !== $domain) {
// skip messages merge if intl-icu requested explicitly
if (str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) {
return $this->messages[$domain] ?? [];
}
return ($this->messages[$domain.self::INTL_DOMAIN_SUFFIX] ?? []) + ($this->messages[$domain] ?? []);
}
$allMessages = [];
foreach ($this->messages as $domain => $messages) {
if (str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) {
$domain = substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX));
$allMessages[$domain] = $messages + ($allMessages[$domain] ?? []);
} else {
$allMessages[$domain] = ($allMessages[$domain] ?? []) + $messages;
}
}
return $allMessages;
}
/**
* {@inheritdoc}
*/
public function set(string $id, string $translation, string $domain = 'messages')
{
$this->add([$id => $translation], $domain);
}
/**
* {@inheritdoc}
*/
public function has(string $id, string $domain = 'messages')
{
if (isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id])) {
return true;
}
if (null !== $this->fallbackCatalogue) {
return $this->fallbackCatalogue->has($id, $domain);
}
return false;
}
/**
* {@inheritdoc}
*/
public function defines(string $id, string $domain = 'messages')
{
return isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id]);
}
/**
* {@inheritdoc}
*/
public function get(string $id, string $domain = 'messages')
{
if (isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id])) {
return $this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id];
}
if (isset($this->messages[$domain][$id])) {
return $this->messages[$domain][$id];
}
if (null !== $this->fallbackCatalogue) {
return $this->fallbackCatalogue->get($id, $domain);
}
return $id;
}
/**
* {@inheritdoc}
*/
public function replace(array $messages, string $domain = 'messages')
{
unset($this->messages[$domain], $this->messages[$domain.self::INTL_DOMAIN_SUFFIX]);
$this->add($messages, $domain);
}
/**
* {@inheritdoc}
*/
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;
}
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;
}
}
}
/**
* {@inheritdoc}
*/
public function addCatalogue(MessageCatalogueInterface $catalogue)
{
if ($catalogue->getLocale() !== $this->locale) {
throw new LogicException(sprintf('Cannot add a catalogue for locale "%s" as the current locale for this catalogue is "%s".', $catalogue->getLocale(), $this->locale));
}
foreach ($catalogue->all() as $domain => $messages) {
if ($intlMessages = $catalogue->all($domain.self::INTL_DOMAIN_SUFFIX)) {
$this->add($intlMessages, $domain.self::INTL_DOMAIN_SUFFIX);
$messages = array_diff_key($messages, $intlMessages);
}
$this->add($messages, $domain);
}
foreach ($catalogue->getResources() as $resource) {
$this->addResource($resource);
}
if ($catalogue instanceof MetadataAwareInterface) {
$metadata = $catalogue->getMetadata('', '');
$this->addMetadata($metadata);
}
}
/**
* {@inheritdoc}
*/
public function addFallbackCatalogue(MessageCatalogueInterface $catalogue)
{
// detect circular references
$c = $catalogue;
while ($c = $c->getFallbackCatalogue()) {
if ($c->getLocale() === $this->getLocale()) {
throw new LogicException(sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale()));
}
}
$c = $this;
do {
if ($c->getLocale() === $catalogue->getLocale()) {
throw new LogicException(sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale()));
}
foreach ($catalogue->getResources() as $resource) {
$c->addResource($resource);
}
} while ($c = $c->parent);
$catalogue->parent = $this;
$this->fallbackCatalogue = $catalogue;
foreach ($catalogue->getResources() as $resource) {
$this->addResource($resource);
}
}
/**
* {@inheritdoc}
*/
public function getFallbackCatalogue()
{
return $this->fallbackCatalogue;
}
/**
* {@inheritdoc}
*/
public function getResources()
{
return array_values($this->resources);
}
/**
* {@inheritdoc}
*/
public function addResource(ResourceInterface $resource)
{
$this->resources[$resource->__toString()] = $resource;
}
/**
* {@inheritdoc}
*/
public function getMetadata(string $key = '', string $domain = 'messages')
{
if ('' == $domain) {
return $this->metadata;
}
if (isset($this->metadata[$domain])) {
if ('' == $key) {
return $this->metadata[$domain];
}
if (isset($this->metadata[$domain][$key])) {
return $this->metadata[$domain][$key];
}
}
return null;
}
/**
* {@inheritdoc}
*/
public function setMetadata(string $key, $value, string $domain = 'messages')
{
$this->metadata[$domain][$key] = $value;
}
/**
* {@inheritdoc}
*/
public function deleteMetadata(string $key = '', string $domain = 'messages')
{
if ('' == $domain) {
$this->metadata = [];
} elseif ('' == $key) {
unset($this->metadata[$domain]);
} else {
unset($this->metadata[$domain][$key]);
}
}
/**
* Adds current values with the new values.
*
* @param array $values Values to add
*/
private function addMetadata(array $values)
{
foreach ($values as $domain => $keys) {
foreach ($keys as $key => $value) {
$this->setMetadata($key, $value, $domain);
}
}
}
}
+138
View File
@@ -0,0 +1,138 @@
<?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\Config\Resource\ResourceInterface;
/**
* MessageCatalogueInterface.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface MessageCatalogueInterface
{
public const INTL_DOMAIN_SUFFIX = '+intl-icu';
/**
* Gets the catalogue locale.
*
* @return string The locale
*/
public function getLocale();
/**
* Gets the domains.
*
* @return array An array of domains
*/
public function getDomains();
/**
* Gets the messages within a given domain.
*
* If $domain is null, it returns all messages.
*
* @param string $domain The domain name
*
* @return array An array of messages
*/
public function all(string $domain = null);
/**
* Sets a message translation.
*
* @param string $id The message id
* @param string $translation The messages translation
* @param string $domain The domain name
*/
public function set(string $id, string $translation, string $domain = 'messages');
/**
* Checks if a message has a translation.
*
* @param string $id The message id
* @param string $domain The domain name
*
* @return bool true if the message has a translation, false otherwise
*/
public function has(string $id, string $domain = 'messages');
/**
* Checks if a message has a translation (it does not take into account the fallback mechanism).
*
* @param string $id The message id
* @param string $domain The domain name
*
* @return bool true if the message has a translation, false otherwise
*/
public function defines(string $id, string $domain = 'messages');
/**
* Gets a message translation.
*
* @param string $id The message id
* @param string $domain The domain name
*
* @return string The message translation
*/
public function get(string $id, string $domain = 'messages');
/**
* Sets translations for a given domain.
*
* @param array $messages An array of translations
* @param string $domain The domain name
*/
public function replace(array $messages, string $domain = 'messages');
/**
* Adds translations for a given domain.
*
* @param array $messages An array of translations
* @param string $domain The domain name
*/
public function add(array $messages, string $domain = 'messages');
/**
* Merges translations from the given Catalogue into the current one.
*
* The two catalogues must have the same locale.
*/
public function addCatalogue(self $catalogue);
/**
* Merges translations from the given Catalogue into the current one
* only when the translation does not exist.
*
* This is used to provide default translations when they do not exist for the current locale.
*/
public function addFallbackCatalogue(self $catalogue);
/**
* Gets the fallback catalogue.
*
* @return self|null A MessageCatalogueInterface instance or null when no fallback has been set
*/
public function getFallbackCatalogue();
/**
* Returns an array of resources loaded to build this collection.
*
* @return ResourceInterface[] An array of resources
*/
public function getResources();
/**
* Adds a resource for this collection.
*/
public function addResource(ResourceInterface $resource);
}
+46
View File
@@ -0,0 +1,46 @@
<?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;
/**
* MetadataAwareInterface.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
interface MetadataAwareInterface
{
/**
* Gets metadata for the given domain and key.
*
* Passing an empty domain will return an array with all metadata indexed by
* domain and then by key. Passing an empty key will return an array with all
* metadata for the given domain.
*
* @return mixed The value that was set or an array with the domains/keys or null
*/
public function getMetadata(string $key = '', string $domain = 'messages');
/**
* Adds metadata to a message domain.
*
* @param mixed $value
*/
public function setMetadata(string $key, $value, string $domain = 'messages');
/**
* Deletes metadata for the given key and domain.
*
* Passing an empty domain will delete all metadata. Passing an empty key will
* delete all metadata for the given domain.
*/
public function deleteMetadata(string $key = '', string $domain = 'messages');
}
@@ -0,0 +1,45 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Provider;
use Symfony\Component\Translation\Exception\IncompleteDsnException;
abstract class AbstractProviderFactory implements ProviderFactoryInterface
{
public function supports(Dsn $dsn): bool
{
return \in_array($dsn->getScheme(), $this->getSupportedSchemes(), true);
}
/**
* @return string[]
*/
abstract protected function getSupportedSchemes(): array;
protected function getUser(Dsn $dsn): string
{
if (null === $user = $dsn->getUser()) {
throw new IncompleteDsnException('User is not set.', $dsn->getOriginalDsn());
}
return $user;
}
protected function getPassword(Dsn $dsn): string
{
if (null === $password = $dsn->getPassword()) {
throw new IncompleteDsnException('Password is not set.', $dsn->getOriginalDsn());
}
return $password;
}
}
+110
View File
@@ -0,0 +1,110 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Provider;
use Symfony\Component\Translation\Exception\InvalidArgumentException;
use Symfony\Component\Translation\Exception\MissingRequiredOptionException;
/**
* @author Fabien Potencier <fabien@symfony.com>
* @author Oskar Stark <oskarstark@googlemail.com>
*/
final class Dsn
{
private $scheme;
private $host;
private $user;
private $password;
private $port;
private $path;
private $options;
private $originalDsn;
public function __construct(string $dsn)
{
$this->originalDsn = $dsn;
if (false === $parsedDsn = parse_url($dsn)) {
throw new InvalidArgumentException(sprintf('The "%s" translation provider DSN is invalid.', $dsn));
}
if (!isset($parsedDsn['scheme'])) {
throw new InvalidArgumentException(sprintf('The "%s" translation provider DSN must contain a scheme.', $dsn));
}
$this->scheme = $parsedDsn['scheme'];
if (!isset($parsedDsn['host'])) {
throw new InvalidArgumentException(sprintf('The "%s" translation provider DSN must contain a host (use "default" by default).', $dsn));
}
$this->host = $parsedDsn['host'];
$this->user = '' !== ($parsedDsn['user'] ?? '') ? urldecode($parsedDsn['user']) : null;
$this->password = '' !== ($parsedDsn['pass'] ?? '') ? urldecode($parsedDsn['pass']) : null;
$this->port = $parsedDsn['port'] ?? null;
$this->path = $parsedDsn['path'] ?? null;
parse_str($parsedDsn['query'] ?? '', $this->options);
}
public function getScheme(): string
{
return $this->scheme;
}
public function getHost(): string
{
return $this->host;
}
public function getUser(): ?string
{
return $this->user;
}
public function getPassword(): ?string
{
return $this->password;
}
public function getPort(int $default = null): ?int
{
return $this->port ?? $default;
}
public function getOption(string $key, $default = null)
{
return $this->options[$key] ?? $default;
}
public function getRequiredOption(string $key)
{
if (!\array_key_exists($key, $this->options) || '' === trim($this->options[$key])) {
throw new MissingRequiredOptionException($key);
}
return $this->options[$key];
}
public function getOptions(): array
{
return $this->options;
}
public function getPath(): ?string
{
return $this->path;
}
public function getOriginalDsn(): string
{
return $this->originalDsn;
}
}
@@ -0,0 +1,67 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Provider;
use Symfony\Component\Translation\TranslatorBag;
use Symfony\Component\Translation\TranslatorBagInterface;
/**
* Filters domains and locales between the Translator config values and those specific to each provider.
*
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*
* @experimental in 5.3
*/
class FilteringProvider implements ProviderInterface
{
private $provider;
private $locales;
private $domains;
public function __construct(ProviderInterface $provider, array $locales, array $domains = [])
{
$this->provider = $provider;
$this->locales = $locales;
$this->domains = $domains;
}
public function __toString(): string
{
return (string) $this->provider;
}
/**
* {@inheritdoc}
*/
public function write(TranslatorBagInterface $translatorBag): void
{
$this->provider->write($translatorBag);
}
public function read(array $domains, array $locales): TranslatorBag
{
$domains = !$this->domains ? $domains : array_intersect($this->domains, $domains);
$locales = array_intersect($this->locales, $locales);
return $this->provider->read($domains, $locales);
}
public function delete(TranslatorBagInterface $translatorBag): void
{
$this->provider->delete($translatorBag);
}
public function getDomains(): array
{
return $this->domains;
}
}
+41
View File
@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Provider;
use Symfony\Component\Translation\TranslatorBag;
use Symfony\Component\Translation\TranslatorBagInterface;
/**
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*
* @experimental in 5.3
*/
class NullProvider implements ProviderInterface
{
public function __toString(): string
{
return 'null';
}
public function write(TranslatorBagInterface $translatorBag, bool $override = false): void
{
}
public function read(array $domains, array $locales): TranslatorBag
{
return new TranslatorBag();
}
public function delete(TranslatorBagInterface $translatorBag): void
{
}
}
@@ -0,0 +1,36 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Provider;
use Symfony\Component\Translation\Exception\UnsupportedSchemeException;
/**
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*
* @experimental in 5.3
*/
final class NullProviderFactory extends AbstractProviderFactory
{
public function create(Dsn $dsn): ProviderInterface
{
if ('null' === $dsn->getScheme()) {
return new NullProvider();
}
throw new UnsupportedSchemeException($dsn, 'null', $this->getSupportedSchemes());
}
protected function getSupportedSchemes(): array
{
return ['null'];
}
}
@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Provider;
use Symfony\Component\Translation\Exception\IncompleteDsnException;
use Symfony\Component\Translation\Exception\UnsupportedSchemeException;
interface ProviderFactoryInterface
{
/**
* @throws UnsupportedSchemeException
* @throws IncompleteDsnException
*/
public function create(Dsn $dsn): ProviderInterface;
public function supports(Dsn $dsn): bool;
}
@@ -0,0 +1,32 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Provider;
use Symfony\Component\Translation\TranslatorBag;
use Symfony\Component\Translation\TranslatorBagInterface;
interface ProviderInterface
{
public function __toString(): string;
/**
* Translations available in the TranslatorBag only must be created.
* Translations available in both the TranslatorBag and on the provider
* must be overwritten.
* Translations available on the provider only must be kept.
*/
public function write(TranslatorBagInterface $translatorBag): void;
public function read(array $domains, array $locales): TranslatorBag;
public function delete(TranslatorBagInterface $translatorBag): void;
}
@@ -0,0 +1,59 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Provider;
use Symfony\Component\Translation\Exception\InvalidArgumentException;
/**
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*
* @experimental in 5.3
*/
final class TranslationProviderCollection
{
private $providers;
/**
* @param array<string, ProviderInterface> $providers
*/
public function __construct(iterable $providers)
{
$this->providers = [];
foreach ($providers as $name => $provider) {
$this->providers[$name] = $provider;
}
}
public function __toString(): string
{
return '['.implode(',', array_keys($this->providers)).']';
}
public function has(string $name): bool
{
return isset($this->providers[$name]);
}
public function get(string $name): ProviderInterface
{
if (!$this->has($name)) {
throw new InvalidArgumentException(sprintf('Provider "%s" not found. Available: "%s".', $name, (string) $this));
}
return $this->providers[$name];
}
public function keys(): array
{
return array_keys($this->providers);
}
}
@@ -0,0 +1,59 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Provider;
use Symfony\Component\Translation\Exception\UnsupportedSchemeException;
/**
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*
* @experimental in 5.3
*/
class TranslationProviderCollectionFactory
{
private $factories;
private $enabledLocales;
/**
* @param ProviderFactoryInterface[] $factories
*/
public function __construct(iterable $factories, array $enabledLocales)
{
$this->factories = $factories;
$this->enabledLocales = $enabledLocales;
}
public function fromConfig(array $config): TranslationProviderCollection
{
$providers = [];
foreach ($config as $name => $currentConfig) {
$providers[$name] = $this->fromDsnObject(
new Dsn($currentConfig['dsn']),
!$currentConfig['locales'] ? $this->enabledLocales : $currentConfig['locales'],
!$currentConfig['domains'] ? [] : $currentConfig['domains']
);
}
return new TranslationProviderCollection($providers);
}
public function fromDsnObject(Dsn $dsn, array $locales, array $domains = []): ProviderInterface
{
foreach ($this->factories as $factory) {
if ($factory->supports($dsn)) {
return new FilteringProvider($factory->create($dsn), $locales, $domains);
}
}
throw new UnsupportedSchemeException($dsn);
}
}
@@ -0,0 +1,364 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* This translator should only be used in a development environment.
*/
final class PseudoLocalizationTranslator implements TranslatorInterface
{
private const EXPANSION_CHARACTER = '~';
private $translator;
private $accents;
private $expansionFactor;
private $brackets;
private $parseHTML;
private $localizableHTMLAttributes;
/**
* Available options:
* * accents:
* type: boolean
* default: true
* description: replace ASCII characters of the translated string with accented versions or similar characters
* example: if true, "foo" => "ƒöö".
*
* * expansion_factor:
* type: float
* default: 1
* validation: it must be greater than or equal to 1
* description: expand the translated string by the given factor with spaces and tildes
* example: if 2, "foo" => "~foo ~"
*
* * brackets:
* type: boolean
* default: true
* description: wrap the translated string with brackets
* example: if true, "foo" => "[foo]"
*
* * parse_html:
* type: boolean
* default: false
* description: parse the translated string as HTML - looking for HTML tags has a performance impact but allows to preserve them from alterations - it also allows to compute the visible translated string length which is useful to correctly expand ot when it contains HTML
* warning: unclosed tags are unsupported, they will be fixed (closed) by the parser - eg, "foo <div>bar" => "foo <div>bar</div>"
*
* * localizable_html_attributes:
* type: string[]
* default: []
* description: the list of HTML attributes whose values can be altered - it is only useful when the "parse_html" option is set to true
* example: if ["title"], and with the "accents" option set to true, "<a href="#" title="Go to your profile">Profile</a>" => "<a href="#" title="Ĝö ţö ýöûŕ þŕöƒîļé">Þŕöƒîļé</a>" - if "title" was not in the "localizable_html_attributes" list, the title attribute data would be left unchanged.
*/
public function __construct(TranslatorInterface $translator, array $options = [])
{
$this->translator = $translator;
$this->accents = $options['accents'] ?? true;
if (1.0 > ($this->expansionFactor = $options['expansion_factor'] ?? 1.0)) {
throw new \InvalidArgumentException('The expansion factor must be greater than or equal to 1.');
}
$this->brackets = $options['brackets'] ?? true;
$this->parseHTML = $options['parse_html'] ?? false;
if ($this->parseHTML && !$this->accents && 1.0 === $this->expansionFactor) {
$this->parseHTML = false;
}
$this->localizableHTMLAttributes = $options['localizable_html_attributes'] ?? [];
}
/**
* {@inheritdoc}
*/
public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null)
{
$trans = '';
$visibleText = '';
foreach ($this->getParts($this->translator->trans($id, $parameters, $domain, $locale)) as [$visible, $localizable, $text]) {
if ($visible) {
$visibleText .= $text;
}
if (!$localizable) {
$trans .= $text;
continue;
}
$this->addAccents($trans, $text);
}
$this->expand($trans, $visibleText);
$this->addBrackets($trans);
return $trans;
}
public function getLocale(): string
{
return $this->translator->getLocale();
}
private function getParts(string $originalTrans): array
{
if (!$this->parseHTML) {
return [[true, true, $originalTrans]];
}
$html = mb_convert_encoding($originalTrans, 'HTML-ENTITIES', mb_detect_encoding($originalTrans, null, true) ?: 'UTF-8');
$useInternalErrors = libxml_use_internal_errors(true);
$dom = new \DOMDocument();
$dom->loadHTML('<trans>'.$html.'</trans>');
libxml_clear_errors();
libxml_use_internal_errors($useInternalErrors);
return $this->parseNode($dom->childNodes->item(1)->childNodes->item(0)->childNodes->item(0));
}
private function parseNode(\DOMNode $node): array
{
$parts = [];
foreach ($node->childNodes as $childNode) {
if (!$childNode instanceof \DOMElement) {
$parts[] = [true, true, $childNode->nodeValue];
continue;
}
$parts[] = [false, false, '<'.$childNode->tagName];
/** @var \DOMAttr $attribute */
foreach ($childNode->attributes as $attribute) {
$parts[] = [false, false, ' '.$attribute->nodeName.'="'];
$localizableAttribute = \in_array($attribute->nodeName, $this->localizableHTMLAttributes, true);
foreach (preg_split('/(&(?:amp|quot|#039|lt|gt);+)/', htmlspecialchars($attribute->nodeValue, \ENT_QUOTES, 'UTF-8'), -1, \PREG_SPLIT_DELIM_CAPTURE) as $i => $match) {
if ('' === $match) {
continue;
}
$parts[] = [false, $localizableAttribute && 0 === $i % 2, $match];
}
$parts[] = [false, false, '"'];
}
$parts[] = [false, false, '>'];
$parts = array_merge($parts, $this->parseNode($childNode, $parts));
$parts[] = [false, false, '</'.$childNode->tagName.'>'];
}
return $parts;
}
private function addAccents(string &$trans, string $text): void
{
$trans .= $this->accents ? strtr($text, [
' ' => '',
'!' => '¡',
'"' => '″',
'#' => '♯',
'$' => '€',
'%' => '‰',
'&' => '⅋',
'\'' => '´',
'(' => '{',
')' => '}',
'*' => '',
'+' => '⁺',
',' => '،',
'-' => '',
'.' => '·',
'/' => '',
'0' => '⓪',
'1' => '①',
'2' => '②',
'3' => '③',
'4' => '④',
'5' => '⑤',
'6' => '⑥',
'7' => '⑦',
'8' => '⑧',
'9' => '⑨',
':' => '',
';' => '⁏',
'<' => '≤',
'=' => '≂',
'>' => '≥',
'?' => '¿',
'@' => '՞',
'A' => 'Å',
'B' => 'Ɓ',
'C' => 'Ç',
'D' => 'Ð',
'E' => 'É',
'F' => 'Ƒ',
'G' => 'Ĝ',
'H' => 'Ĥ',
'I' => 'Î',
'J' => 'Ĵ',
'K' => 'Ķ',
'L' => 'Ļ',
'M' => 'Ṁ',
'N' => 'Ñ',
'O' => 'Ö',
'P' => 'Þ',
'Q' => 'Ǫ',
'R' => 'Ŕ',
'S' => 'Š',
'T' => 'Ţ',
'U' => 'Û',
'V' => 'Ṽ',
'W' => 'Ŵ',
'X' => 'Ẋ',
'Y' => 'Ý',
'Z' => 'Ž',
'[' => '⁅',
'\\' => '',
']' => '⁆',
'^' => '˄',
'_' => '‿',
'`' => '',
'a' => 'å',
'b' => 'ƀ',
'c' => 'ç',
'd' => 'ð',
'e' => 'é',
'f' => 'ƒ',
'g' => 'ĝ',
'h' => 'ĥ',
'i' => 'î',
'j' => 'ĵ',
'k' => 'ķ',
'l' => 'ļ',
'm' => 'ɱ',
'n' => 'ñ',
'o' => 'ö',
'p' => 'þ',
'q' => 'ǫ',
'r' => 'ŕ',
's' => 'š',
't' => 'ţ',
'u' => 'û',
'v' => 'ṽ',
'w' => 'ŵ',
'x' => 'ẋ',
'y' => 'ý',
'z' => 'ž',
'{' => '(',
'|' => '¦',
'}' => ')',
'~' => '˞',
]) : $text;
}
private function expand(string &$trans, string $visibleText): void
{
if (1.0 >= $this->expansionFactor) {
return;
}
$visibleLength = $this->strlen($visibleText);
$missingLength = (int) (ceil($visibleLength * $this->expansionFactor)) - $visibleLength;
if ($this->brackets) {
$missingLength -= 2;
}
if (0 >= $missingLength) {
return;
}
$words = [];
$wordsCount = 0;
foreach (preg_split('/ +/', $visibleText, -1, \PREG_SPLIT_NO_EMPTY) as $word) {
$wordLength = $this->strlen($word);
if ($wordLength >= $missingLength) {
continue;
}
if (!isset($words[$wordLength])) {
$words[$wordLength] = 0;
}
++$words[$wordLength];
++$wordsCount;
}
if (!$words) {
$trans .= 1 === $missingLength ? self::EXPANSION_CHARACTER : ' '.str_repeat(self::EXPANSION_CHARACTER, $missingLength - 1);
return;
}
arsort($words, \SORT_NUMERIC);
$longestWordLength = max(array_keys($words));
while (true) {
$r = mt_rand(1, $wordsCount);
foreach ($words as $length => $count) {
$r -= $count;
if ($r <= 0) {
break;
}
}
$trans .= ' '.str_repeat(self::EXPANSION_CHARACTER, $length);
$missingLength -= $length + 1;
if (0 === $missingLength) {
return;
}
while ($longestWordLength >= $missingLength) {
$wordsCount -= $words[$longestWordLength];
unset($words[$longestWordLength]);
if (!$words) {
$trans .= 1 === $missingLength ? self::EXPANSION_CHARACTER : ' '.str_repeat(self::EXPANSION_CHARACTER, $missingLength - 1);
return;
}
$longestWordLength = max(array_keys($words));
}
}
}
private function addBrackets(string &$trans): void
{
if (!$this->brackets) {
return;
}
$trans = '['.$trans.']';
}
private function strlen(string $s): int
{
return false === ($encoding = mb_detect_encoding($s, null, true)) ? \strlen($s) : mb_strlen($s, $encoding);
}
}
+33
View File
@@ -0,0 +1,33 @@
Translation Component
=====================
The Translation component provides tools to internationalize your application.
Getting Started
---------------
```
$ composer require symfony/translation
```
```php
use Symfony\Component\Translation\Translator;
use Symfony\Component\Translation\Loader\ArrayLoader;
$translator = new Translator('fr_FR');
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', [
'Hello World!' => 'Bonjour !',
], 'fr_FR');
echo $translator->trans('Hello World!'); // outputs « Bonjour ! »
```
Resources
---------
* [Documentation](https://symfony.com/doc/current/translation.html)
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)
+62
View File
@@ -0,0 +1,62 @@
<?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\Reader;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Translation\Loader\LoaderInterface;
use Symfony\Component\Translation\MessageCatalogue;
/**
* TranslationReader reads translation messages from translation files.
*
* @author Michel Salib <michelsalib@hotmail.com>
*/
class TranslationReader implements TranslationReaderInterface
{
/**
* Loaders used for import.
*
* @var array
*/
private $loaders = [];
/**
* Adds a loader to the translation extractor.
*
* @param string $format The format of the loader
*/
public function addLoader(string $format, LoaderInterface $loader)
{
$this->loaders[$format] = $loader;
}
/**
* {@inheritdoc}
*/
public function read(string $directory, MessageCatalogue $catalogue)
{
if (!is_dir($directory)) {
return;
}
foreach ($this->loaders as $format => $loader) {
// load any existing translation files
$finder = new Finder();
$extension = $catalogue->getLocale().'.'.$format;
$files = $finder->files()->name('*.'.$extension)->in($directory);
foreach ($files as $file) {
$domain = substr($file->getFilename(), 0, -1 * \strlen($extension) - 1);
$catalogue->addCatalogue($loader->load($file->getPathname(), $catalogue->getLocale(), $domain));
}
}
}
}
@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Reader;
use Symfony\Component\Translation\MessageCatalogue;
/**
* TranslationReader reads translation messages from translation files.
*
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
interface TranslationReaderInterface
{
/**
* Reads translation messages from a directory to the catalogue.
*/
public function read(string $directory, MessageCatalogue $catalogue);
}
@@ -0,0 +1,274 @@
<?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.
*/
$usageInstructions = <<<END
Usage instructions
-------------------------------------------------------------------------------
$ cd symfony-code-root-directory/
# show the translation status of all locales
$ php translation-status.php
# only show the translation status of incomplete or erroneous locales
$ php translation-status.php --incomplete
# show the translation status of all locales, all their missing translations and mismatches between trans-unit id and source
$ php translation-status.php -v
# show the status of a single locale
$ php translation-status.php fr
# show the status of a single locale, missing translations and mismatches between trans-unit id and source
$ php translation-status.php fr -v
END;
$config = [
// if TRUE, the full list of missing translations is displayed
'verbose_output' => false,
// NULL = analyze all locales
'locale_to_analyze' => null,
// append --incomplete to only show incomplete languages
'include_completed_languages' => true,
// the reference files all the other translations are compared to
'original_files' => [
'src/Symfony/Component/Form/Resources/translations/validators.en.xlf',
'src/Symfony/Component/Security/Core/Resources/translations/security.en.xlf',
'src/Symfony/Component/Validator/Resources/translations/validators.en.xlf',
],
];
$argc = $_SERVER['argc'];
$argv = $_SERVER['argv'];
if ($argc > 4) {
echo str_replace('translation-status.php', $argv[0], $usageInstructions);
exit(1);
}
foreach (array_slice($argv, 1) as $argumentOrOption) {
if ('--incomplete' === $argumentOrOption) {
$config['include_completed_languages'] = false;
continue;
}
if (0 === strpos($argumentOrOption, '-')) {
$config['verbose_output'] = true;
} else {
$config['locale_to_analyze'] = $argumentOrOption;
}
}
foreach ($config['original_files'] as $originalFilePath) {
if (!file_exists($originalFilePath)) {
echo sprintf('The following file does not exist. Make sure that you execute this command at the root dir of the Symfony code repository.%s %s', \PHP_EOL, $originalFilePath);
exit(1);
}
}
$totalMissingTranslations = 0;
$totalTranslationMismatches = 0;
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)));
printTranslationStatus($originalFilePath, $translationStatus, $config['verbose_output'], $config['include_completed_languages']);
}
exit($totalTranslationMismatches > 0 ? 1 : 0);
function findTranslationFiles($originalFilePath, $localeToAnalyze)
{
$translations = [];
$translationsDir = dirname($originalFilePath);
$originalFileName = basename($originalFilePath);
$translationFileNamePattern = str_replace('.en.', '.*.', $originalFileName);
$translationFiles = glob($translationsDir.'/'.$translationFileNamePattern, \GLOB_NOSORT);
sort($translationFiles);
foreach ($translationFiles as $filePath) {
$locale = extractLocaleFromFilePath($filePath);
if (null !== $localeToAnalyze && $locale !== $localeToAnalyze) {
continue;
}
$translations[$locale] = $filePath;
}
return $translations;
}
function calculateTranslationStatus($originalFilePath, $translationFilePaths)
{
$translationStatus = [];
$allTranslationKeys = extractTranslationKeys($originalFilePath);
foreach ($translationFilePaths as $locale => $translationPath) {
$translatedKeys = extractTranslationKeys($translationPath);
$missingKeys = array_diff_key($allTranslationKeys, $translatedKeys);
$mismatches = findTransUnitMismatches($allTranslationKeys, $translatedKeys);
$translationStatus[$locale] = [
'total' => count($allTranslationKeys),
'translated' => count($translatedKeys),
'missingKeys' => $missingKeys,
'mismatches' => $mismatches,
];
$translationStatus[$locale]['is_completed'] = isTranslationCompleted($translationStatus[$locale]);
}
return $translationStatus;
}
function isTranslationCompleted(array $translationStatus): bool
{
return $translationStatus['total'] === $translationStatus['translated'] && 0 === count($translationStatus['mismatches']);
}
function printTranslationStatus($originalFilePath, $translationStatus, $verboseOutput, $includeCompletedLanguages)
{
printTitle($originalFilePath);
printTable($translationStatus, $verboseOutput, $includeCompletedLanguages);
echo \PHP_EOL.\PHP_EOL;
}
function extractLocaleFromFilePath($filePath)
{
$parts = explode('.', $filePath);
return $parts[count($parts) - 2];
}
function extractTranslationKeys($filePath)
{
$translationKeys = [];
$contents = new \SimpleXMLElement(file_get_contents($filePath));
foreach ($contents->file->body->{'trans-unit'} as $translationKey) {
$translationId = (string) $translationKey['id'];
$translationKey = (string) $translationKey->source;
$translationKeys[$translationId] = $translationKey;
}
return $translationKeys;
}
/**
* Check whether the trans-unit id and source match with the base translation.
*/
function findTransUnitMismatches(array $baseTranslationKeys, array $translatedKeys): array
{
$mismatches = [];
foreach ($baseTranslationKeys as $translationId => $translationKey) {
if (!isset($translatedKeys[$translationId])) {
continue;
}
if ($translatedKeys[$translationId] !== $translationKey) {
$mismatches[$translationId] = [
'found' => $translatedKeys[$translationId],
'expected' => $translationKey,
];
}
}
return $mismatches;
}
function printTitle($title)
{
echo $title.\PHP_EOL;
echo str_repeat('=', strlen($title)).\PHP_EOL.\PHP_EOL;
}
function printTable($translations, $verboseOutput, bool $includeCompletedLanguages)
{
if (0 === count($translations)) {
echo 'No translations found';
return;
}
$longestLocaleNameLength = max(array_map('strlen', array_keys($translations)));
foreach ($translations as $locale => $translation) {
if (!$includeCompletedLanguages && $translation['is_completed']) {
continue;
}
if ($translation['translated'] > $translation['total']) {
textColorRed();
} elseif (count($translation['mismatches']) > 0) {
textColorRed();
} elseif ($translation['is_completed']) {
textColorGreen();
}
echo sprintf(
'| Locale: %-'.$longestLocaleNameLength.'s | Translated: %2d/%2d | Mismatches: %d |',
$locale,
$translation['translated'],
$translation['total'],
count($translation['mismatches'])
).\PHP_EOL;
textColorNormal();
$shouldBeClosed = false;
if (true === $verboseOutput && count($translation['missingKeys']) > 0) {
echo '| Missing Translations:'.\PHP_EOL;
foreach ($translation['missingKeys'] as $id => $content) {
echo sprintf('| (id=%s) %s', $id, $content).\PHP_EOL;
}
$shouldBeClosed = true;
}
if (true === $verboseOutput && count($translation['mismatches']) > 0) {
echo '| Mismatches between trans-unit id and source:'.\PHP_EOL;
foreach ($translation['mismatches'] as $id => $content) {
echo sprintf('| (id=%s) Expected: %s', $id, $content['expected']).\PHP_EOL;
echo sprintf('| Found: %s', $content['found']).\PHP_EOL;
}
$shouldBeClosed = true;
}
if ($shouldBeClosed) {
echo str_repeat('-', 80).\PHP_EOL;
}
}
}
function textColorGreen()
{
echo "\033[32m";
}
function textColorRed()
{
echo "\033[31m";
}
function textColorNormal()
{
echo "\033[0m";
}
+140
View File
@@ -0,0 +1,140 @@
{
"az_Cyrl": "root",
"bs_Cyrl": "root",
"en_150": "en_001",
"en_AG": "en_001",
"en_AI": "en_001",
"en_AT": "en_150",
"en_AU": "en_001",
"en_BB": "en_001",
"en_BE": "en_150",
"en_BM": "en_001",
"en_BS": "en_001",
"en_BW": "en_001",
"en_BZ": "en_001",
"en_CA": "en_001",
"en_CC": "en_001",
"en_CH": "en_150",
"en_CK": "en_001",
"en_CM": "en_001",
"en_CX": "en_001",
"en_CY": "en_001",
"en_DE": "en_150",
"en_DG": "en_001",
"en_DK": "en_150",
"en_DM": "en_001",
"en_ER": "en_001",
"en_FI": "en_150",
"en_FJ": "en_001",
"en_FK": "en_001",
"en_FM": "en_001",
"en_GB": "en_001",
"en_GD": "en_001",
"en_GG": "en_001",
"en_GH": "en_001",
"en_GI": "en_001",
"en_GM": "en_001",
"en_GY": "en_001",
"en_HK": "en_001",
"en_IE": "en_001",
"en_IL": "en_001",
"en_IM": "en_001",
"en_IN": "en_001",
"en_IO": "en_001",
"en_JE": "en_001",
"en_JM": "en_001",
"en_KE": "en_001",
"en_KI": "en_001",
"en_KN": "en_001",
"en_KY": "en_001",
"en_LC": "en_001",
"en_LR": "en_001",
"en_LS": "en_001",
"en_MG": "en_001",
"en_MO": "en_001",
"en_MS": "en_001",
"en_MT": "en_001",
"en_MU": "en_001",
"en_MW": "en_001",
"en_MY": "en_001",
"en_NA": "en_001",
"en_NF": "en_001",
"en_NG": "en_001",
"en_NL": "en_150",
"en_NR": "en_001",
"en_NU": "en_001",
"en_NZ": "en_001",
"en_PG": "en_001",
"en_PH": "en_001",
"en_PK": "en_001",
"en_PN": "en_001",
"en_PW": "en_001",
"en_RW": "en_001",
"en_SB": "en_001",
"en_SC": "en_001",
"en_SD": "en_001",
"en_SE": "en_150",
"en_SG": "en_001",
"en_SH": "en_001",
"en_SI": "en_150",
"en_SL": "en_001",
"en_SS": "en_001",
"en_SX": "en_001",
"en_SZ": "en_001",
"en_TC": "en_001",
"en_TK": "en_001",
"en_TO": "en_001",
"en_TT": "en_001",
"en_TV": "en_001",
"en_TZ": "en_001",
"en_UG": "en_001",
"en_VC": "en_001",
"en_VG": "en_001",
"en_VU": "en_001",
"en_WS": "en_001",
"en_ZA": "en_001",
"en_ZM": "en_001",
"en_ZW": "en_001",
"es_AR": "es_419",
"es_BO": "es_419",
"es_BR": "es_419",
"es_BZ": "es_419",
"es_CL": "es_419",
"es_CO": "es_419",
"es_CR": "es_419",
"es_CU": "es_419",
"es_DO": "es_419",
"es_EC": "es_419",
"es_GT": "es_419",
"es_HN": "es_419",
"es_MX": "es_419",
"es_NI": "es_419",
"es_PA": "es_419",
"es_PE": "es_419",
"es_PR": "es_419",
"es_PY": "es_419",
"es_SV": "es_419",
"es_US": "es_419",
"es_UY": "es_419",
"es_VE": "es_419",
"ff_Adlm": "root",
"nb": "no",
"nn": "no",
"pa_Arab": "root",
"pt_AO": "pt_PT",
"pt_CH": "pt_PT",
"pt_CV": "pt_PT",
"pt_GQ": "pt_PT",
"pt_GW": "pt_PT",
"pt_LU": "pt_PT",
"pt_MO": "pt_PT",
"pt_MZ": "pt_PT",
"pt_ST": "pt_PT",
"pt_TL": "pt_PT",
"sd_Deva": "root",
"sr_Latn": "root",
"uz_Arab": "root",
"uz_Cyrl": "root",
"zh_Hant": "root",
"zh_Hant_MO": "zh_Hant_HK"
}
+22
View File
@@ -0,0 +1,22 @@
<?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;
if (!\function_exists(t::class)) {
/**
* @author Nate Wiebe <nate@northern.co>
*/
function t(string $message, array $parameters = [], string $domain = null): TranslatableMessage
{
return new TranslatableMessage($message, $parameters, $domain);
}
}
@@ -0,0 +1,2223 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
May-19-2004:
- Changed the <choice> for ElemType_header, moving minOccurs="0" maxOccurs="unbounded" from its elements
to <choice> itself.
- Added <choice> for ElemType_trans-unit to allow "any order" for <context-group>, <count-group>, <prop-group>, <note>, and
<alt-trans>.
Oct-2005
- updated version info to 1.2
- equiv-trans attribute to <trans-unit> element
- merged-trans attribute for <group> element
- Add the <seg-source> element as optional in the <trans-unit> and <alt-trans> content models, at the same level as <source>
- Create a new value "seg" for the mtype attribute of the <mrk> element
- Add mid as an optional attribute for the <alt-trans> element
Nov-14-2005
- Changed name attribute for <context-group> from required to optional
- Added extension point at <xliff>
Jan-9-2006
- Added alttranstype type attribute to <alt-trans>, and values
Jan-10-2006
- Corrected error with overwritten purposeValueList
- Corrected name="AttrType_Version", attribute should have been "name"
-->
<xsd:schema xmlns:xlf="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsd="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="urn:oasis:names:tc:xliff:document:1.2" xml:lang="en">
<!-- Import for xml:lang and xml:space -->
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" schemaLocation="http://www.w3.org/2001/xml.xsd"/>
<!-- Attributes Lists -->
<xsd:simpleType name="XTend">
<xsd:restriction base="xsd:string">
<xsd:pattern value="x-[^\s]+"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="context-typeValueList">
<xsd:annotation>
<xsd:documentation>Values for the attribute 'context-type'.</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="database">
<xsd:annotation>
<xsd:documentation>Indicates a database content.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="element">
<xsd:annotation>
<xsd:documentation>Indicates the content of an element within an XML document.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="elementtitle">
<xsd:annotation>
<xsd:documentation>Indicates the name of an element within an XML document.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="linenumber">
<xsd:annotation>
<xsd:documentation>Indicates the line number from the sourcefile (see context-type="sourcefile") where the &lt;source&gt; is found.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="numparams">
<xsd:annotation>
<xsd:documentation>Indicates a the number of parameters contained within the &lt;source&gt;.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="paramnotes">
<xsd:annotation>
<xsd:documentation>Indicates notes pertaining to the parameters in the &lt;source&gt;.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="record">
<xsd:annotation>
<xsd:documentation>Indicates the content of a record within a database.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="recordtitle">
<xsd:annotation>
<xsd:documentation>Indicates the name of a record within a database.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="sourcefile">
<xsd:annotation>
<xsd:documentation>Indicates the original source file in the case that multiple files are merged to form the original file from which the XLIFF file is created. This differs from the original &lt;file&gt; attribute in that this sourcefile is one of many that make up that file.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="count-typeValueList">
<xsd:annotation>
<xsd:documentation>Values for the attribute 'count-type'.</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="num-usages">
<xsd:annotation>
<xsd:documentation>Indicates the count units are items that are used X times in a certain context; example: this is a reusable text unit which is used 42 times in other texts.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="repetition">
<xsd:annotation>
<xsd:documentation>Indicates the count units are translation units existing already in the same document.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="total">
<xsd:annotation>
<xsd:documentation>Indicates a total count.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="InlineDelimitersValueList">
<xsd:annotation>
<xsd:documentation>Values for the attribute 'ctype' when used other elements than &lt;ph&gt; or &lt;x&gt;.</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="bold">
<xsd:annotation>
<xsd:documentation>Indicates a run of bolded text.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="italic">
<xsd:annotation>
<xsd:documentation>Indicates a run of text in italics.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="underlined">
<xsd:annotation>
<xsd:documentation>Indicates a run of underlined text.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="link">
<xsd:annotation>
<xsd:documentation>Indicates a run of hyper-text.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="InlinePlaceholdersValueList">
<xsd:annotation>
<xsd:documentation>Values for the attribute 'ctype' when used with &lt;ph&gt; or &lt;x&gt;.</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="image">
<xsd:annotation>
<xsd:documentation>Indicates a inline image.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="pb">
<xsd:annotation>
<xsd:documentation>Indicates a page break.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="lb">
<xsd:annotation>
<xsd:documentation>Indicates a line break.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="mime-typeValueList">
<xsd:restriction base="xsd:string">
<xsd:pattern value="(text|multipart|message|application|image|audio|video|model)(/.+)*"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="datatypeValueList">
<xsd:annotation>
<xsd:documentation>Values for the attribute 'datatype'.</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="asp">
<xsd:annotation>
<xsd:documentation>Indicates Active Server Page data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="c">
<xsd:annotation>
<xsd:documentation>Indicates C source file data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="cdf">
<xsd:annotation>
<xsd:documentation>Indicates Channel Definition Format (CDF) data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="cfm">
<xsd:annotation>
<xsd:documentation>Indicates ColdFusion data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="cpp">
<xsd:annotation>
<xsd:documentation>Indicates C++ source file data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="csharp">
<xsd:annotation>
<xsd:documentation>Indicates C-Sharp data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="cstring">
<xsd:annotation>
<xsd:documentation>Indicates strings from C, ASM, and driver files data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="csv">
<xsd:annotation>
<xsd:documentation>Indicates comma-separated values data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="database">
<xsd:annotation>
<xsd:documentation>Indicates database data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="documentfooter">
<xsd:annotation>
<xsd:documentation>Indicates portions of document that follows data and contains metadata.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="documentheader">
<xsd:annotation>
<xsd:documentation>Indicates portions of document that precedes data and contains metadata.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="filedialog">
<xsd:annotation>
<xsd:documentation>Indicates data from standard UI file operations dialogs (e.g., Open, Save, Save As, Export, Import).</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="form">
<xsd:annotation>
<xsd:documentation>Indicates standard user input screen data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="html">
<xsd:annotation>
<xsd:documentation>Indicates HyperText Markup Language (HTML) data - document instance.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="htmlbody">
<xsd:annotation>
<xsd:documentation>Indicates content within an HTML documents &lt;body&gt; element.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="ini">
<xsd:annotation>
<xsd:documentation>Indicates Windows INI file data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="interleaf">
<xsd:annotation>
<xsd:documentation>Indicates Interleaf data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="javaclass">
<xsd:annotation>
<xsd:documentation>Indicates Java source file data (extension '.java').</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="javapropertyresourcebundle">
<xsd:annotation>
<xsd:documentation>Indicates Java property resource bundle data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="javalistresourcebundle">
<xsd:annotation>
<xsd:documentation>Indicates Java list resource bundle data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="javascript">
<xsd:annotation>
<xsd:documentation>Indicates JavaScript source file data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="jscript">
<xsd:annotation>
<xsd:documentation>Indicates JScript source file data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="layout">
<xsd:annotation>
<xsd:documentation>Indicates information relating to formatting.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="lisp">
<xsd:annotation>
<xsd:documentation>Indicates LISP source file data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="margin">
<xsd:annotation>
<xsd:documentation>Indicates information relating to margin formats.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="menufile">
<xsd:annotation>
<xsd:documentation>Indicates a file containing menu.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="messagefile">
<xsd:annotation>
<xsd:documentation>Indicates numerically identified string table.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="mif">
<xsd:annotation>
<xsd:documentation>Indicates Maker Interchange Format (MIF) data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="mimetype">
<xsd:annotation>
<xsd:documentation>Indicates that the datatype attribute value is a MIME Type value and is defined in the mime-type attribute.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="mo">
<xsd:annotation>
<xsd:documentation>Indicates GNU Machine Object data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="msglib">
<xsd:annotation>
<xsd:documentation>Indicates Message Librarian strings created by Novell's Message Librarian Tool.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="pagefooter">
<xsd:annotation>
<xsd:documentation>Indicates information to be displayed at the bottom of each page of a document.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="pageheader">
<xsd:annotation>
<xsd:documentation>Indicates information to be displayed at the top of each page of a document.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="parameters">
<xsd:annotation>
<xsd:documentation>Indicates a list of property values (e.g., settings within INI files or preferences dialog).</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="pascal">
<xsd:annotation>
<xsd:documentation>Indicates Pascal source file data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="php">
<xsd:annotation>
<xsd:documentation>Indicates Hypertext Preprocessor data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="plaintext">
<xsd:annotation>
<xsd:documentation>Indicates plain text file (no formatting other than, possibly, wrapping).</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="po">
<xsd:annotation>
<xsd:documentation>Indicates GNU Portable Object file.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="report">
<xsd:annotation>
<xsd:documentation>Indicates dynamically generated user defined document. e.g. Oracle Report, Crystal Report, etc.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="resources">
<xsd:annotation>
<xsd:documentation>Indicates Windows .NET binary resources.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="resx">
<xsd:annotation>
<xsd:documentation>Indicates Windows .NET Resources.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="rtf">
<xsd:annotation>
<xsd:documentation>Indicates Rich Text Format (RTF) data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="sgml">
<xsd:annotation>
<xsd:documentation>Indicates Standard Generalized Markup Language (SGML) data - document instance.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="sgmldtd">
<xsd:annotation>
<xsd:documentation>Indicates Standard Generalized Markup Language (SGML) data - Document Type Definition (DTD).</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="svg">
<xsd:annotation>
<xsd:documentation>Indicates Scalable Vector Graphic (SVG) data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="vbscript">
<xsd:annotation>
<xsd:documentation>Indicates VisualBasic Script source file.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="warning">
<xsd:annotation>
<xsd:documentation>Indicates warning message.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="winres">
<xsd:annotation>
<xsd:documentation>Indicates Windows (Win32) resources (i.e. resources extracted from an RC script, a message file, or a compiled file).</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="xhtml">
<xsd:annotation>
<xsd:documentation>Indicates Extensible HyperText Markup Language (XHTML) data - document instance.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="xml">
<xsd:annotation>
<xsd:documentation>Indicates Extensible Markup Language (XML) data - document instance.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="xmldtd">
<xsd:annotation>
<xsd:documentation>Indicates Extensible Markup Language (XML) data - Document Type Definition (DTD).</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="xsl">
<xsd:annotation>
<xsd:documentation>Indicates Extensible Stylesheet Language (XSL) data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="xul">
<xsd:annotation>
<xsd:documentation>Indicates XUL elements.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="mtypeValueList">
<xsd:annotation>
<xsd:documentation>Values for the attribute 'mtype'.</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="abbrev">
<xsd:annotation>
<xsd:documentation>Indicates the marked text is an abbreviation.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="abbreviated-form">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.8: A term resulting from the omission of any part of the full term while designating the same concept.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="abbreviation">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.8.1: An abbreviated form of a simple term resulting from the omission of some of its letters (e.g. 'adj.' for 'adjective').</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="acronym">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.8.4: An abbreviated form of a term made up of letters from the full form of a multiword term strung together into a sequence pronounced only syllabically (e.g. 'radar' for 'radio detecting and ranging').</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="appellation">
<xsd:annotation>
<xsd:documentation>ISO-12620: A proper-name term, such as the name of an agency or other proper entity.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="collocation">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.18.1: A recurrent word combination characterized by cohesion in that the components of the collocation must co-occur within an utterance or series of utterances, even though they do not necessarily have to maintain immediate proximity to one another.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="common-name">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.5: A synonym for an international scientific term that is used in general discourse in a given language.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="datetime">
<xsd:annotation>
<xsd:documentation>Indicates the marked text is a date and/or time.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="equation">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.15: An expression used to represent a concept based on a statement that two mathematical expressions are, for instance, equal as identified by the equal sign (=), or assigned to one another by a similar sign.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="expanded-form">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.7: The complete representation of a term for which there is an abbreviated form.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="formula">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.14: Figures, symbols or the like used to express a concept briefly, such as a mathematical or chemical formula.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="head-term">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.1: The concept designation that has been chosen to head a terminological record.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="initialism">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.8.3: An abbreviated form of a term consisting of some of the initial letters of the words making up a multiword term or the term elements making up a compound term when these letters are pronounced individually (e.g. 'BSE' for 'bovine spongiform encephalopathy').</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="international-scientific-term">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.4: A term that is part of an international scientific nomenclature as adopted by an appropriate scientific body.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="internationalism">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.6: A term that has the same or nearly identical orthographic or phonemic form in many languages.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="logical-expression">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.16: An expression used to represent a concept based on mathematical or logical relations, such as statements of inequality, set relationships, Boolean operations, and the like.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="materials-management-unit">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.17: A unit to track object.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="name">
<xsd:annotation>
<xsd:documentation>Indicates the marked text is a name.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="near-synonym">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.3: A term that represents the same or a very similar concept as another term in the same language, but for which interchangeability is limited to some contexts and inapplicable in others.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="part-number">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.17.2: A unique alphanumeric designation assigned to an object in a manufacturing system.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="phrase">
<xsd:annotation>
<xsd:documentation>Indicates the marked text is a phrase.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="phraseological-unit">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.18: Any group of two or more words that form a unit, the meaning of which frequently cannot be deduced based on the combined sense of the words making up the phrase.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="protected">
<xsd:annotation>
<xsd:documentation>Indicates the marked text should not be translated.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="romanized-form">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.12: A form of a term resulting from an operation whereby non-Latin writing systems are converted to the Latin alphabet.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="seg">
<xsd:annotation>
<xsd:documentation>Indicates that the marked text represents a segment.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="set-phrase">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.18.2: A fixed, lexicalized phrase.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="short-form">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.8.2: A variant of a multiword term that includes fewer words than the full form of the term (e.g. 'Group of Twenty-four' for 'Intergovernmental Group of Twenty-four on International Monetary Affairs').</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="sku">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.17.1: Stock keeping unit, an inventory item identified by a unique alphanumeric designation assigned to an object in an inventory control system.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="standard-text">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.19: A fixed chunk of recurring text.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="symbol">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.13: A designation of a concept by letters, numerals, pictograms or any combination thereof.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="synonym">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.2: Any term that represents the same or a very similar concept as the main entry term in a term entry.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="synonymous-phrase">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.18.3: Phraseological unit in a language that expresses the same semantic content as another phrase in that same language.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="term">
<xsd:annotation>
<xsd:documentation>Indicates the marked text is a term.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="transcribed-form">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.11: A form of a term resulting from an operation whereby the characters of one writing system are represented by characters from another writing system, taking into account the pronunciation of the characters converted.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="transliterated-form">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.10: A form of a term resulting from an operation whereby the characters of an alphabetic writing system are represented by characters from another alphabetic writing system.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="truncated-term">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.8.5: An abbreviated form of a term resulting from the omission of one or more term elements or syllables (e.g. 'flu' for 'influenza').</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="variant">
<xsd:annotation>
<xsd:documentation>ISO-12620 2.1.9: One of the alternate forms of a term.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="restypeValueList">
<xsd:annotation>
<xsd:documentation>Values for the attribute 'restype'.</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="auto3state">
<xsd:annotation>
<xsd:documentation>Indicates a Windows RC AUTO3STATE control.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="autocheckbox">
<xsd:annotation>
<xsd:documentation>Indicates a Windows RC AUTOCHECKBOX control.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="autoradiobutton">
<xsd:annotation>
<xsd:documentation>Indicates a Windows RC AUTORADIOBUTTON control.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="bedit">
<xsd:annotation>
<xsd:documentation>Indicates a Windows RC BEDIT control.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="bitmap">
<xsd:annotation>
<xsd:documentation>Indicates a bitmap, for example a BITMAP resource in Windows.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="button">
<xsd:annotation>
<xsd:documentation>Indicates a button object, for example a BUTTON control Windows.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="caption">
<xsd:annotation>
<xsd:documentation>Indicates a caption, such as the caption of a dialog box.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="cell">
<xsd:annotation>
<xsd:documentation>Indicates the cell in a table, for example the content of the &lt;td&gt; element in HTML.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="checkbox">
<xsd:annotation>
<xsd:documentation>Indicates check box object, for example a CHECKBOX control in Windows.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="checkboxmenuitem">
<xsd:annotation>
<xsd:documentation>Indicates a menu item with an associated checkbox.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="checkedlistbox">
<xsd:annotation>
<xsd:documentation>Indicates a list box, but with a check-box for each item.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="colorchooser">
<xsd:annotation>
<xsd:documentation>Indicates a color selection dialog.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="combobox">
<xsd:annotation>
<xsd:documentation>Indicates a combination of edit box and listbox object, for example a COMBOBOX control in Windows.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="comboboxexitem">
<xsd:annotation>
<xsd:documentation>Indicates an initialization entry of an extended combobox DLGINIT resource block. (code 0x1234).</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="comboboxitem">
<xsd:annotation>
<xsd:documentation>Indicates an initialization entry of a combobox DLGINIT resource block (code 0x0403).</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="component">
<xsd:annotation>
<xsd:documentation>Indicates a UI base class element that cannot be represented by any other element.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="contextmenu">
<xsd:annotation>
<xsd:documentation>Indicates a context menu.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="ctext">
<xsd:annotation>
<xsd:documentation>Indicates a Windows RC CTEXT control.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="cursor">
<xsd:annotation>
<xsd:documentation>Indicates a cursor, for example a CURSOR resource in Windows.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="datetimepicker">
<xsd:annotation>
<xsd:documentation>Indicates a date/time picker.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="defpushbutton">
<xsd:annotation>
<xsd:documentation>Indicates a Windows RC DEFPUSHBUTTON control.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="dialog">
<xsd:annotation>
<xsd:documentation>Indicates a dialog box.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="dlginit">
<xsd:annotation>
<xsd:documentation>Indicates a Windows RC DLGINIT resource block.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="edit">
<xsd:annotation>
<xsd:documentation>Indicates an edit box object, for example an EDIT control in Windows.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="file">
<xsd:annotation>
<xsd:documentation>Indicates a filename.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="filechooser">
<xsd:annotation>
<xsd:documentation>Indicates a file dialog.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="fn">
<xsd:annotation>
<xsd:documentation>Indicates a footnote.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="font">
<xsd:annotation>
<xsd:documentation>Indicates a font name.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="footer">
<xsd:annotation>
<xsd:documentation>Indicates a footer.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="frame">
<xsd:annotation>
<xsd:documentation>Indicates a frame object.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="grid">
<xsd:annotation>
<xsd:documentation>Indicates a XUL grid element.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="groupbox">
<xsd:annotation>
<xsd:documentation>Indicates a groupbox object, for example a GROUPBOX control in Windows.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="header">
<xsd:annotation>
<xsd:documentation>Indicates a header item.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="heading">
<xsd:annotation>
<xsd:documentation>Indicates a heading, such has the content of &lt;h1&gt;, &lt;h2&gt;, etc. in HTML.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="hedit">
<xsd:annotation>
<xsd:documentation>Indicates a Windows RC HEDIT control.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="hscrollbar">
<xsd:annotation>
<xsd:documentation>Indicates a horizontal scrollbar.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="icon">
<xsd:annotation>
<xsd:documentation>Indicates an icon, for example an ICON resource in Windows.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="iedit">
<xsd:annotation>
<xsd:documentation>Indicates a Windows RC IEDIT control.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="keywords">
<xsd:annotation>
<xsd:documentation>Indicates keyword list, such as the content of the Keywords meta-data in HTML, or a K footnote in WinHelp RTF.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="label">
<xsd:annotation>
<xsd:documentation>Indicates a label object.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="linklabel">
<xsd:annotation>
<xsd:documentation>Indicates a label that is also a HTML link (not necessarily a URL).</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="list">
<xsd:annotation>
<xsd:documentation>Indicates a list (a group of list-items, for example an &lt;ol&gt; or &lt;ul&gt; element in HTML).</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="listbox">
<xsd:annotation>
<xsd:documentation>Indicates a listbox object, for example an LISTBOX control in Windows.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="listitem">
<xsd:annotation>
<xsd:documentation>Indicates an list item (an entry in a list).</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="ltext">
<xsd:annotation>
<xsd:documentation>Indicates a Windows RC LTEXT control.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="menu">
<xsd:annotation>
<xsd:documentation>Indicates a menu (a group of menu-items).</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="menubar">
<xsd:annotation>
<xsd:documentation>Indicates a toolbar containing one or more tope level menus.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="menuitem">
<xsd:annotation>
<xsd:documentation>Indicates a menu item (an entry in a menu).</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="menuseparator">
<xsd:annotation>
<xsd:documentation>Indicates a XUL menuseparator element.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="message">
<xsd:annotation>
<xsd:documentation>Indicates a message, for example an entry in a MESSAGETABLE resource in Windows.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="monthcalendar">
<xsd:annotation>
<xsd:documentation>Indicates a calendar control.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="numericupdown">
<xsd:annotation>
<xsd:documentation>Indicates an edit box beside a spin control.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="panel">
<xsd:annotation>
<xsd:documentation>Indicates a catch all for rectangular areas.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="popupmenu">
<xsd:annotation>
<xsd:documentation>Indicates a standalone menu not necessarily associated with a menubar.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="pushbox">
<xsd:annotation>
<xsd:documentation>Indicates a pushbox object, for example a PUSHBOX control in Windows.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="pushbutton">
<xsd:annotation>
<xsd:documentation>Indicates a Windows RC PUSHBUTTON control.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="radio">
<xsd:annotation>
<xsd:documentation>Indicates a radio button object.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="radiobuttonmenuitem">
<xsd:annotation>
<xsd:documentation>Indicates a menuitem with associated radio button.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="rcdata">
<xsd:annotation>
<xsd:documentation>Indicates raw data resources for an application.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="row">
<xsd:annotation>
<xsd:documentation>Indicates a row in a table.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="rtext">
<xsd:annotation>
<xsd:documentation>Indicates a Windows RC RTEXT control.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="scrollpane">
<xsd:annotation>
<xsd:documentation>Indicates a user navigable container used to show a portion of a document.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="separator">
<xsd:annotation>
<xsd:documentation>Indicates a generic divider object (e.g. menu group separator).</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="shortcut">
<xsd:annotation>
<xsd:documentation>Windows accelerators, shortcuts in resource or property files.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="spinner">
<xsd:annotation>
<xsd:documentation>Indicates a UI control to indicate process activity but not progress.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="splitter">
<xsd:annotation>
<xsd:documentation>Indicates a splitter bar.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="state3">
<xsd:annotation>
<xsd:documentation>Indicates a Windows RC STATE3 control.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="statusbar">
<xsd:annotation>
<xsd:documentation>Indicates a window for providing feedback to the users, like 'read-only', etc.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="string">
<xsd:annotation>
<xsd:documentation>Indicates a string, for example an entry in a STRINGTABLE resource in Windows.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="tabcontrol">
<xsd:annotation>
<xsd:documentation>Indicates a layers of controls with a tab to select layers.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="table">
<xsd:annotation>
<xsd:documentation>Indicates a display and edits regular two-dimensional tables of cells.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="textbox">
<xsd:annotation>
<xsd:documentation>Indicates a XUL textbox element.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="togglebutton">
<xsd:annotation>
<xsd:documentation>Indicates a UI button that can be toggled to on or off state.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="toolbar">
<xsd:annotation>
<xsd:documentation>Indicates an array of controls, usually buttons.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="tooltip">
<xsd:annotation>
<xsd:documentation>Indicates a pop up tool tip text.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="trackbar">
<xsd:annotation>
<xsd:documentation>Indicates a bar with a pointer indicating a position within a certain range.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="tree">
<xsd:annotation>
<xsd:documentation>Indicates a control that displays a set of hierarchical data.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="uri">
<xsd:annotation>
<xsd:documentation>Indicates a URI (URN or URL).</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="userbutton">
<xsd:annotation>
<xsd:documentation>Indicates a Windows RC USERBUTTON control.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="usercontrol">
<xsd:annotation>
<xsd:documentation>Indicates a user-defined control like CONTROL control in Windows.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="var">
<xsd:annotation>
<xsd:documentation>Indicates the text of a variable.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="versioninfo">
<xsd:annotation>
<xsd:documentation>Indicates version information about a resource like VERSIONINFO in Windows.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="vscrollbar">
<xsd:annotation>
<xsd:documentation>Indicates a vertical scrollbar.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="window">
<xsd:annotation>
<xsd:documentation>Indicates a graphical window.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="size-unitValueList">
<xsd:annotation>
<xsd:documentation>Values for the attribute 'size-unit'.</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="byte">
<xsd:annotation>
<xsd:documentation>Indicates a size in 8-bit bytes.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="char">
<xsd:annotation>
<xsd:documentation>Indicates a size in Unicode characters.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="col">
<xsd:annotation>
<xsd:documentation>Indicates a size in columns. Used for HTML text area.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="cm">
<xsd:annotation>
<xsd:documentation>Indicates a size in centimeters.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="dlgunit">
<xsd:annotation>
<xsd:documentation>Indicates a size in dialog units, as defined in Windows resources.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="em">
<xsd:annotation>
<xsd:documentation>Indicates a size in 'font-size' units (as defined in CSS).</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="ex">
<xsd:annotation>
<xsd:documentation>Indicates a size in 'x-height' units (as defined in CSS).</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="glyph">
<xsd:annotation>
<xsd:documentation>Indicates a size in glyphs. A glyph is considered to be one or more combined Unicode characters that represent a single displayable text character. Sometimes referred to as a 'grapheme cluster'</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="in">
<xsd:annotation>
<xsd:documentation>Indicates a size in inches.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="mm">
<xsd:annotation>
<xsd:documentation>Indicates a size in millimeters.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="percent">
<xsd:annotation>
<xsd:documentation>Indicates a size in percentage.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="pixel">
<xsd:annotation>
<xsd:documentation>Indicates a size in pixels.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="point">
<xsd:annotation>
<xsd:documentation>Indicates a size in point.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="row">
<xsd:annotation>
<xsd:documentation>Indicates a size in rows. Used for HTML text area.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="stateValueList">
<xsd:annotation>
<xsd:documentation>Values for the attribute 'state'.</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="final">
<xsd:annotation>
<xsd:documentation>Indicates the terminating state.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="needs-adaptation">
<xsd:annotation>
<xsd:documentation>Indicates only non-textual information needs adaptation.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="needs-l10n">
<xsd:annotation>
<xsd:documentation>Indicates both text and non-textual information needs adaptation.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="needs-review-adaptation">
<xsd:annotation>
<xsd:documentation>Indicates only non-textual information needs review.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="needs-review-l10n">
<xsd:annotation>
<xsd:documentation>Indicates both text and non-textual information needs review.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="needs-review-translation">
<xsd:annotation>
<xsd:documentation>Indicates that only the text of the item needs to be reviewed.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="needs-translation">
<xsd:annotation>
<xsd:documentation>Indicates that the item needs to be translated.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="new">
<xsd:annotation>
<xsd:documentation>Indicates that the item is new. For example, translation units that were not in a previous version of the document.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="signed-off">
<xsd:annotation>
<xsd:documentation>Indicates that changes are reviewed and approved.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="translated">
<xsd:annotation>
<xsd:documentation>Indicates that the item has been translated.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="state-qualifierValueList">
<xsd:annotation>
<xsd:documentation>Values for the attribute 'state-qualifier'.</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="exact-match">
<xsd:annotation>
<xsd:documentation>Indicates an exact match. An exact match occurs when a source text of a segment is exactly the same as the source text of a segment that was translated previously.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="fuzzy-match">
<xsd:annotation>
<xsd:documentation>Indicates a fuzzy match. A fuzzy match occurs when a source text of a segment is very similar to the source text of a segment that was translated previously (e.g. when the difference is casing, a few changed words, white-space discripancy, etc.).</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="id-match">
<xsd:annotation>
<xsd:documentation>Indicates a match based on matching IDs (in addition to matching text).</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="leveraged-glossary">
<xsd:annotation>
<xsd:documentation>Indicates a translation derived from a glossary.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="leveraged-inherited">
<xsd:annotation>
<xsd:documentation>Indicates a translation derived from existing translation.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="leveraged-mt">
<xsd:annotation>
<xsd:documentation>Indicates a translation derived from machine translation.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="leveraged-repository">
<xsd:annotation>
<xsd:documentation>Indicates a translation derived from a translation repository.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="leveraged-tm">
<xsd:annotation>
<xsd:documentation>Indicates a translation derived from a translation memory.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="mt-suggestion">
<xsd:annotation>
<xsd:documentation>Indicates the translation is suggested by machine translation.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="rejected-grammar">
<xsd:annotation>
<xsd:documentation>Indicates that the item has been rejected because of incorrect grammar.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="rejected-inaccurate">
<xsd:annotation>
<xsd:documentation>Indicates that the item has been rejected because it is incorrect.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="rejected-length">
<xsd:annotation>
<xsd:documentation>Indicates that the item has been rejected because it is too long or too short.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="rejected-spelling">
<xsd:annotation>
<xsd:documentation>Indicates that the item has been rejected because of incorrect spelling.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="tm-suggestion">
<xsd:annotation>
<xsd:documentation>Indicates the translation is suggested by translation memory.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="unitValueList">
<xsd:annotation>
<xsd:documentation>Values for the attribute 'unit'.</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="word">
<xsd:annotation>
<xsd:documentation>Refers to words.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="page">
<xsd:annotation>
<xsd:documentation>Refers to pages.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="trans-unit">
<xsd:annotation>
<xsd:documentation>Refers to &lt;trans-unit&gt; elements.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="bin-unit">
<xsd:annotation>
<xsd:documentation>Refers to &lt;bin-unit&gt; elements.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="glyph">
<xsd:annotation>
<xsd:documentation>Refers to glyphs.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="item">
<xsd:annotation>
<xsd:documentation>Refers to &lt;trans-unit&gt; and/or &lt;bin-unit&gt; elements.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="instance">
<xsd:annotation>
<xsd:documentation>Refers to the occurrences of instances defined by the count-type value.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="character">
<xsd:annotation>
<xsd:documentation>Refers to characters.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="line">
<xsd:annotation>
<xsd:documentation>Refers to lines.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="sentence">
<xsd:annotation>
<xsd:documentation>Refers to sentences.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="paragraph">
<xsd:annotation>
<xsd:documentation>Refers to paragraphs.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="segment">
<xsd:annotation>
<xsd:documentation>Refers to segments.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="placeable">
<xsd:annotation>
<xsd:documentation>Refers to placeables (inline elements).</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="priorityValueList">
<xsd:annotation>
<xsd:documentation>Values for the attribute 'priority'.</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:positiveInteger">
<xsd:enumeration value="1">
<xsd:annotation>
<xsd:documentation>Highest priority.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="2">
<xsd:annotation>
<xsd:documentation>High priority.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="3">
<xsd:annotation>
<xsd:documentation>High priority, but not as important as 2.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="4">
<xsd:annotation>
<xsd:documentation>High priority, but not as important as 3.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="5">
<xsd:annotation>
<xsd:documentation>Medium priority, but more important than 6.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="6">
<xsd:annotation>
<xsd:documentation>Medium priority, but less important than 5.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="7">
<xsd:annotation>
<xsd:documentation>Low priority, but more important than 8.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="8">
<xsd:annotation>
<xsd:documentation>Low priority, but more important than 9.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="9">
<xsd:annotation>
<xsd:documentation>Low priority.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="10">
<xsd:annotation>
<xsd:documentation>Lowest priority.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="reformatValueYesNo">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="yes">
<xsd:annotation>
<xsd:documentation>This value indicates that all properties can be reformatted. This value must be used alone.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="no">
<xsd:annotation>
<xsd:documentation>This value indicates that no properties should be reformatted. This value must be used alone.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="reformatValueList">
<xsd:list>
<xsd:simpleType>
<xsd:union memberTypes="xlf:XTend">
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="coord">
<xsd:annotation>
<xsd:documentation>This value indicates that all information in the coord attribute can be modified.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="coord-x">
<xsd:annotation>
<xsd:documentation>This value indicates that the x information in the coord attribute can be modified.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="coord-y">
<xsd:annotation>
<xsd:documentation>This value indicates that the y information in the coord attribute can be modified.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="coord-cx">
<xsd:annotation>
<xsd:documentation>This value indicates that the cx information in the coord attribute can be modified.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="coord-cy">
<xsd:annotation>
<xsd:documentation>This value indicates that the cy information in the coord attribute can be modified.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="font">
<xsd:annotation>
<xsd:documentation>This value indicates that all the information in the font attribute can be modified.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="font-name">
<xsd:annotation>
<xsd:documentation>This value indicates that the name information in the font attribute can be modified.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="font-size">
<xsd:annotation>
<xsd:documentation>This value indicates that the size information in the font attribute can be modified.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="font-weight">
<xsd:annotation>
<xsd:documentation>This value indicates that the weight information in the font attribute can be modified.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="css-style">
<xsd:annotation>
<xsd:documentation>This value indicates that the information in the css-style attribute can be modified.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="style">
<xsd:annotation>
<xsd:documentation>This value indicates that the information in the style attribute can be modified.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="ex-style">
<xsd:annotation>
<xsd:documentation>This value indicates that the information in the exstyle attribute can be modified.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
</xsd:union>
</xsd:simpleType>
</xsd:list>
</xsd:simpleType>
<xsd:simpleType name="purposeValueList">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="information">
<xsd:annotation>
<xsd:documentation>Indicates that the context is informational in nature, specifying for example, how a term should be translated. Thus, should be displayed to anyone editing the XLIFF document.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="location">
<xsd:annotation>
<xsd:documentation>Indicates that the context-group is used to specify where the term was found in the translatable source. Thus, it is not displayed.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="match">
<xsd:annotation>
<xsd:documentation>Indicates that the context information should be used during translation memory lookups. Thus, it is not displayed.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="alttranstypeValueList">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="proposal">
<xsd:annotation>
<xsd:documentation>Represents a translation proposal from a translation memory or other resource.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="previous-version">
<xsd:annotation>
<xsd:documentation>Represents a previous version of the target element.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="rejected">
<xsd:annotation>
<xsd:documentation>Represents a rejected version of the target element.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="reference">
<xsd:annotation>
<xsd:documentation>Represents a translation to be used for reference purposes only, for example from a related product or a different language.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="accepted">
<xsd:annotation>
<xsd:documentation>Represents a proposed translation that was used for the translation of the trans-unit, possibly modified.</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
<!-- Other Types -->
<xsd:complexType name="ElemType_ExternalReference">
<xsd:choice>
<xsd:element ref="xlf:internal-file"/>
<xsd:element ref="xlf:external-file"/>
</xsd:choice>
</xsd:complexType>
<xsd:simpleType name="AttrType_purpose">
<xsd:list>
<xsd:simpleType>
<xsd:union memberTypes="xlf:purposeValueList xlf:XTend"/>
</xsd:simpleType>
</xsd:list>
</xsd:simpleType>
<xsd:simpleType name="AttrType_datatype">
<xsd:union memberTypes="xlf:datatypeValueList xlf:XTend"/>
</xsd:simpleType>
<xsd:simpleType name="AttrType_restype">
<xsd:union memberTypes="xlf:restypeValueList xlf:XTend"/>
</xsd:simpleType>
<xsd:simpleType name="AttrType_alttranstype">
<xsd:union memberTypes="xlf:alttranstypeValueList xlf:XTend"/>
</xsd:simpleType>
<xsd:simpleType name="AttrType_context-type">
<xsd:union memberTypes="xlf:context-typeValueList xlf:XTend"/>
</xsd:simpleType>
<xsd:simpleType name="AttrType_state">
<xsd:union memberTypes="xlf:stateValueList xlf:XTend"/>
</xsd:simpleType>
<xsd:simpleType name="AttrType_state-qualifier">
<xsd:union memberTypes="xlf:state-qualifierValueList xlf:XTend"/>
</xsd:simpleType>
<xsd:simpleType name="AttrType_count-type">
<xsd:union memberTypes="xlf:restypeValueList xlf:count-typeValueList xlf:datatypeValueList xlf:stateValueList xlf:state-qualifierValueList xlf:XTend"/>
</xsd:simpleType>
<xsd:simpleType name="AttrType_InlineDelimiters">
<xsd:union memberTypes="xlf:InlineDelimitersValueList xlf:XTend"/>
</xsd:simpleType>
<xsd:simpleType name="AttrType_InlinePlaceholders">
<xsd:union memberTypes="xlf:InlinePlaceholdersValueList xlf:XTend"/>
</xsd:simpleType>
<xsd:simpleType name="AttrType_size-unit">
<xsd:union memberTypes="xlf:size-unitValueList xlf:XTend"/>
</xsd:simpleType>
<xsd:simpleType name="AttrType_mtype">
<xsd:union memberTypes="xlf:mtypeValueList xlf:XTend"/>
</xsd:simpleType>
<xsd:simpleType name="AttrType_unit">
<xsd:union memberTypes="xlf:unitValueList xlf:XTend"/>
</xsd:simpleType>
<xsd:simpleType name="AttrType_priority">
<xsd:union memberTypes="xlf:priorityValueList"/>
</xsd:simpleType>
<xsd:simpleType name="AttrType_reformat">
<xsd:union memberTypes="xlf:reformatValueYesNo xlf:reformatValueList"/>
</xsd:simpleType>
<xsd:simpleType name="AttrType_YesNo">
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="yes"/>
<xsd:enumeration value="no"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="AttrType_Position">
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="open"/>
<xsd:enumeration value="close"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="AttrType_assoc">
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="preceding"/>
<xsd:enumeration value="following"/>
<xsd:enumeration value="both"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="AttrType_annotates">
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="source"/>
<xsd:enumeration value="target"/>
<xsd:enumeration value="general"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="AttrType_Coordinates">
<xsd:annotation>
<xsd:documentation>Values for the attribute 'coord'.</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:string">
<xsd:pattern value="(-?\d+|#);(-?\d+|#);(-?\d+|#);(-?\d+|#)"/>
</xsd:restriction>
</xsd:simpleType>
<xsd:simpleType name="AttrType_Version">
<xsd:annotation>
<xsd:documentation>Version values: 1.0 and 1.1 are allowed for backward compatibility.</xsd:documentation>
</xsd:annotation>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="1.2"/>
<xsd:enumeration value="1.1"/>
<xsd:enumeration value="1.0"/>
</xsd:restriction>
</xsd:simpleType>
<!-- Groups -->
<xsd:group name="ElemGroup_TextContent">
<xsd:choice>
<xsd:element ref="xlf:g"/>
<xsd:element ref="xlf:bpt"/>
<xsd:element ref="xlf:ept"/>
<xsd:element ref="xlf:ph"/>
<xsd:element ref="xlf:it"/>
<xsd:element ref="xlf:mrk"/>
<xsd:element ref="xlf:x"/>
<xsd:element ref="xlf:bx"/>
<xsd:element ref="xlf:ex"/>
</xsd:choice>
</xsd:group>
<xsd:attributeGroup name="AttrGroup_TextContent">
<xsd:attribute name="id" type="xsd:string" use="required"/>
<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:attributeGroup>
<!-- XLIFF Structure -->
<xsd:element name="xliff">
<xsd:complexType>
<xsd:sequence maxOccurs="unbounded">
<xsd:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="strict"/>
<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:complexType>
</xsd:element>
<xsd:element name="file">
<xsd:complexType>
<xsd:sequence>
<xsd:element minOccurs="0" ref="xlf:header"/>
<xsd:element ref="xlf:body"/>
</xsd:sequence>
<xsd:attribute name="original" type="xsd:string" use="required"/>
<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 name="date" type="xsd:dateTime" use="optional"/>
<xsd:attribute ref="xml:space" 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:complexType>
<xsd:unique name="U_group_id">
<xsd:selector xpath=".//xlf:group"/>
<xsd:field xpath="@id"/>
</xsd:unique>
<xsd:key name="K_unit_id">
<xsd:selector xpath=".//xlf:trans-unit|.//xlf:bin-unit"/>
<xsd:field xpath="@id"/>
</xsd:key>
<xsd:keyref name="KR_unit_id" refer="xlf:K_unit_id">
<xsd:selector xpath=".//bpt|.//ept|.//it|.//ph|.//g|.//x|.//bx|.//ex|.//sub"/>
<xsd:field xpath="@xid"/>
</xsd:keyref>
<xsd:key name="K_tool-id">
<xsd:selector xpath="xlf:header/xlf:tool"/>
<xsd:field xpath="@tool-id"/>
</xsd:key>
<xsd:keyref name="KR_file_tool-id" refer="xlf:K_tool-id">
<xsd:selector xpath="."/>
<xsd:field xpath="@tool-id"/>
</xsd:keyref>
<xsd:keyref name="KR_phase_tool-id" refer="xlf:K_tool-id">
<xsd:selector xpath="xlf:header/xlf:phase-group/xlf:phase"/>
<xsd:field xpath="@tool-id"/>
</xsd:keyref>
<xsd:keyref name="KR_alt-trans_tool-id" refer="xlf:K_tool-id">
<xsd:selector xpath=".//xlf:trans-unit/xlf:alt-trans"/>
<xsd:field xpath="@tool-id"/>
</xsd:keyref>
<xsd:key name="K_count-group_name">
<xsd:selector xpath=".//xlf:count-group"/>
<xsd:field xpath="@name"/>
</xsd:key>
<xsd:unique name="U_context-group_name">
<xsd:selector xpath=".//xlf:context-group"/>
<xsd:field xpath="@name"/>
</xsd:unique>
<xsd:key name="K_phase-name">
<xsd:selector xpath="xlf:header/xlf:phase-group/xlf:phase"/>
<xsd:field xpath="@phase-name"/>
</xsd:key>
<xsd:keyref name="KR_phase-name" refer="xlf:K_phase-name">
<xsd:selector xpath=".//xlf:count|.//xlf:trans-unit|.//xlf:target|.//bin-unit|.//bin-target"/>
<xsd:field xpath="@phase-name"/>
</xsd:keyref>
<xsd:unique name="U_uid">
<xsd:selector xpath=".//xlf:external-file"/>
<xsd:field xpath="@uid"/>
</xsd:unique>
</xsd:element>
<xsd:element name="header">
<xsd:complexType>
<xsd:sequence>
<xsd:element minOccurs="0" name="skl" type="xlf:ElemType_ExternalReference"/>
<xsd:element minOccurs="0" ref="xlf:phase-group"/>
<xsd:choice maxOccurs="unbounded" minOccurs="0">
<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:note"/>
<xsd:element ref="xlf:tool"/>
</xsd:choice>
<xsd:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="strict"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="internal-file">
<xsd:complexType>
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="form" type="xsd:string"/>
<xsd:attribute name="crc" type="xsd:NMTOKEN"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="external-file">
<xsd:complexType>
<xsd:attribute name="href" type="xsd:string" use="required"/>
<xsd:attribute name="crc" type="xsd:NMTOKEN"/>
<xsd:attribute name="uid" type="xsd:NMTOKEN"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="note">
<xsd:complexType>
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute ref="xml:lang" use="optional"/>
<xsd:attribute default="1" name="priority" type="xlf:AttrType_priority" use="optional"/>
<xsd:attribute name="from" type="xsd:string" use="optional"/>
<xsd:attribute default="general" name="annotates" type="xlf:AttrType_annotates" use="optional"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="phase-group">
<xsd:complexType>
<xsd:sequence maxOccurs="unbounded">
<xsd:element ref="xlf:phase"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="phase">
<xsd:complexType>
<xsd:sequence maxOccurs="unbounded" minOccurs="0">
<xsd:element ref="xlf:note"/>
</xsd:sequence>
<xsd:attribute name="phase-name" type="xsd:string" use="required"/>
<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="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"/>
<xsd:attribute name="contact-email" type="xsd:string" use="optional"/>
<xsd:attribute name="contact-phone" type="xsd:string" use="optional"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="count-group">
<xsd:complexType>
<xsd:sequence maxOccurs="unbounded" minOccurs="0">
<xsd:element ref="xlf:count"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="count">
<xsd:complexType>
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="count-type" type="xlf:AttrType_count-type" use="optional"/>
<xsd:attribute name="phase-name" type="xsd:string" use="optional"/>
<xsd:attribute default="word" name="unit" type="xlf:AttrType_unit" use="optional"/>
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="context-group">
<xsd:complexType>
<xsd:sequence maxOccurs="unbounded">
<xsd:element ref="xlf:context"/>
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="optional"/>
<xsd:attribute name="crc" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="purpose" type="xlf:AttrType_purpose" use="optional"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="context">
<xsd:complexType>
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="context-type" type="xlf:AttrType_context-type" use="required"/>
<xsd:attribute default="no" name="match-mandatory" type="xlf:AttrType_YesNo" use="optional"/>
<xsd:attribute name="crc" type="xsd:NMTOKEN" 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: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:complexType>
</xsd:element>
<xsd:element name="body">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded" minOccurs="0">
<xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:group"/>
<xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:trans-unit"/>
<xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:bin-unit"/>
</xsd:choice>
</xsd:complexType>
</xsd:element>
<xsd:element name="group">
<xsd:complexType>
<xsd:sequence>
<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:note"/>
<xsd:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="strict"/>
</xsd:sequence>
<xsd:choice maxOccurs="unbounded">
<xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:group"/>
<xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:trans-unit"/>
<xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:bin-unit"/>
</xsd:choice>
</xsd:sequence>
<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="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"/>
<xsd:attribute name="extype" type="xsd:string" use="optional"/>
<xsd:attribute name="help-id" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="menu" type="xsd:string" use="optional"/>
<xsd:attribute name="menu-option" type="xsd:string" use="optional"/>
<xsd:attribute name="menu-name" type="xsd:string" use="optional"/>
<xsd:attribute name="coord" type="xlf:AttrType_Coordinates" use="optional"/>
<xsd:attribute name="font" type="xsd:string" use="optional"/>
<xsd:attribute name="css-style" type="xsd:string" use="optional"/>
<xsd:attribute name="style" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="exstyle" type="xsd:NMTOKEN" 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 default="pixel" name="size-unit" type="xlf:AttrType_size-unit" use="optional"/>
<xsd:attribute name="maxwidth" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="minwidth" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="maxheight" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="minheight" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="maxbytes" type="xsd:NMTOKEN" use="optional"/>
<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:complexType>
</xsd:element>
<xsd:element name="trans-unit">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="xlf:source"/>
<xsd:element minOccurs="0" ref="xlf:seg-source"/>
<xsd:element minOccurs="0" ref="xlf:target"/>
<xsd:choice maxOccurs="unbounded" minOccurs="0">
<xsd:element ref="xlf:context-group"/>
<xsd:element ref="xlf:count-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:sequence>
<xsd:attribute name="id" type="xsd:string" 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 default="default" ref="xml:space" use="optional"/>
<xsd:attribute name="datatype" type="xlf:AttrType_datatype" 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"/>
<xsd:attribute name="extradata" type="xsd:string" use="optional"/>
<xsd:attribute name="extype" type="xsd:string" use="optional"/>
<xsd:attribute name="help-id" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="menu" type="xsd:string" use="optional"/>
<xsd:attribute name="menu-option" type="xsd:string" use="optional"/>
<xsd:attribute name="menu-name" type="xsd:string" use="optional"/>
<xsd:attribute name="coord" type="xlf:AttrType_Coordinates" use="optional"/>
<xsd:attribute name="font" type="xsd:string" use="optional"/>
<xsd:attribute name="css-style" type="xsd:string" use="optional"/>
<xsd:attribute name="style" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="exstyle" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute default="pixel" name="size-unit" type="xlf:AttrType_size-unit" use="optional"/>
<xsd:attribute name="maxwidth" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="minwidth" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="maxheight" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="minheight" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="maxbytes" type="xsd:NMTOKEN" use="optional"/>
<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:complexType>
<xsd:unique name="U_tu_segsrc_mid">
<xsd:selector xpath="./xlf:seg-source/xlf:mrk"/>
<xsd:field xpath="@mid"/>
</xsd:unique>
<xsd:keyref name="KR_tu_segsrc_mid" refer="xlf:U_tu_segsrc_mid">
<xsd:selector xpath="./xlf:target/xlf:mrk|./xlf:alt-trans"/>
<xsd:field xpath="@mid"/>
</xsd:keyref>
</xsd:element>
<xsd:element name="source">
<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:complexType>
<xsd:unique name="U_source_bpt_rid">
<xsd:selector xpath=".//xlf:bpt"/>
<xsd:field xpath="@rid"/>
</xsd:unique>
<xsd:keyref name="KR_source_ept_rid" refer="xlf:U_source_bpt_rid">
<xsd:selector xpath=".//xlf:ept"/>
<xsd:field xpath="@rid"/>
</xsd:keyref>
<xsd:unique name="U_source_bx_rid">
<xsd:selector xpath=".//xlf:bx"/>
<xsd:field xpath="@rid"/>
</xsd:unique>
<xsd:keyref name="KR_source_ex_rid" refer="xlf:U_source_bx_rid">
<xsd:selector xpath=".//xlf:ex"/>
<xsd:field xpath="@rid"/>
</xsd:keyref>
</xsd:element>
<xsd:element name="seg-source">
<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:complexType>
<xsd:unique name="U_segsrc_bpt_rid">
<xsd:selector xpath=".//xlf:bpt"/>
<xsd:field xpath="@rid"/>
</xsd:unique>
<xsd:keyref name="KR_segsrc_ept_rid" refer="xlf:U_segsrc_bpt_rid">
<xsd:selector xpath=".//xlf:ept"/>
<xsd:field xpath="@rid"/>
</xsd:keyref>
<xsd:unique name="U_segsrc_bx_rid">
<xsd:selector xpath=".//xlf:bx"/>
<xsd:field xpath="@rid"/>
</xsd:unique>
<xsd:keyref name="KR_segsrc_ex_rid" refer="xlf:U_segsrc_bx_rid">
<xsd:selector xpath=".//xlf:ex"/>
<xsd:field xpath="@rid"/>
</xsd:keyref>
</xsd:element>
<xsd:element name="target">
<xsd:complexType mixed="true">
<xsd:group maxOccurs="unbounded" minOccurs="0" ref="xlf:ElemGroup_TextContent"/>
<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 ref="xml:lang" 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"/>
<xsd:attribute name="css-style" type="xsd:string" use="optional"/>
<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:complexType>
<xsd:unique name="U_target_bpt_rid">
<xsd:selector xpath=".//xlf:bpt"/>
<xsd:field xpath="@rid"/>
</xsd:unique>
<xsd:keyref name="KR_target_ept_rid" refer="xlf:U_target_bpt_rid">
<xsd:selector xpath=".//xlf:ept"/>
<xsd:field xpath="@rid"/>
</xsd:keyref>
<xsd:unique name="U_target_bx_rid">
<xsd:selector xpath=".//xlf:bx"/>
<xsd:field xpath="@rid"/>
</xsd:unique>
<xsd:keyref name="KR_target_ex_rid" refer="xlf:U_target_bx_rid">
<xsd:selector xpath=".//xlf:ex"/>
<xsd:field xpath="@rid"/>
</xsd:keyref>
</xsd:element>
<xsd:element name="alt-trans">
<xsd:complexType>
<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" minOccurs="0" ref="xlf:context-group"/>
<xsd:element maxOccurs="unbounded" minOccurs="0" ref="xlf:note"/>
<xsd:any maxOccurs="unbounded" minOccurs="0" namespace="##other" processContents="strict"/>
</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="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="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"/>
<xsd:attribute name="extype" type="xsd:string" use="optional"/>
<xsd:attribute name="help-id" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="menu" type="xsd:string" use="optional"/>
<xsd:attribute name="menu-option" type="xsd:string" use="optional"/>
<xsd:attribute name="menu-name" type="xsd:string" use="optional"/>
<xsd:attribute name="mid" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="coord" type="xlf:AttrType_Coordinates" use="optional"/>
<xsd:attribute name="font" type="xsd:string" use="optional"/>
<xsd:attribute name="css-style" type="xsd:string" use="optional"/>
<xsd:attribute name="style" type="xsd:NMTOKEN" use="optional"/>
<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:complexType>
<xsd:unique name="U_at_segsrc_mid">
<xsd:selector xpath="./xlf:seg-source/xlf:mrk"/>
<xsd:field xpath="@mid"/>
</xsd:unique>
<xsd:keyref name="KR_at_segsrc_mid" refer="xlf:U_at_segsrc_mid">
<xsd:selector xpath="./xlf:target/xlf:mrk"/>
<xsd:field xpath="@mid"/>
</xsd:keyref>
</xsd:element>
<xsd:element name="bin-unit">
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="xlf:bin-source"/>
<xsd:element minOccurs="0" ref="xlf:bin-target"/>
<xsd:choice maxOccurs="unbounded" minOccurs="0">
<xsd:element ref="xlf:context-group"/>
<xsd:element ref="xlf:count-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: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="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:complexType>
</xsd:element>
<xsd:element name="bin-source">
<xsd:complexType>
<xsd:choice>
<xsd:element ref="xlf:internal-file"/>
<xsd:element ref="xlf:external-file"/>
</xsd:choice>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="bin-target">
<xsd:complexType>
<xsd:choice>
<xsd:element ref="xlf:internal-file"/>
<xsd:element ref="xlf:external-file"/>
</xsd:choice>
<xsd:attribute name="mime-type" type="xlf:mime-typeValueList" 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:complexType>
</xsd:element>
<!-- Element for inline codes -->
<xsd:element name="g">
<xsd:complexType mixed="true">
<xsd:group maxOccurs="unbounded" minOccurs="0" ref="xlf:ElemGroup_TextContent"/>
<xsd:attribute name="ctype" type="xlf:AttrType_InlineDelimiters" use="optional"/>
<xsd:attribute default="yes" name="clone" type="xlf:AttrType_YesNo" use="optional"/>
<xsd:attributeGroup ref="xlf:AttrGroup_TextContent"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="x">
<xsd:complexType>
<xsd:attribute name="ctype" type="xlf:AttrType_InlinePlaceholders" use="optional"/>
<xsd:attribute default="yes" name="clone" type="xlf:AttrType_YesNo" use="optional"/>
<xsd:attributeGroup ref="xlf:AttrGroup_TextContent"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="bx">
<xsd:complexType>
<xsd:attribute name="rid" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="ctype" type="xlf:AttrType_InlineDelimiters" use="optional"/>
<xsd:attribute default="yes" name="clone" type="xlf:AttrType_YesNo" use="optional"/>
<xsd:attributeGroup ref="xlf:AttrGroup_TextContent"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="ex">
<xsd:complexType>
<xsd:attribute name="rid" type="xsd:NMTOKEN" use="optional"/>
<xsd:attributeGroup ref="xlf:AttrGroup_TextContent"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="ph">
<xsd:complexType mixed="true">
<xsd:sequence maxOccurs="unbounded" minOccurs="0">
<xsd:element ref="xlf:sub"/>
</xsd:sequence>
<xsd:attribute name="ctype" type="xlf:AttrType_InlinePlaceholders" use="optional"/>
<xsd:attribute name="crc" type="xsd:string" use="optional"/>
<xsd:attribute name="assoc" type="xlf:AttrType_assoc" use="optional"/>
<xsd:attributeGroup ref="xlf:AttrGroup_TextContent"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="bpt">
<xsd:complexType mixed="true">
<xsd:sequence maxOccurs="unbounded" minOccurs="0">
<xsd:element ref="xlf:sub"/>
</xsd:sequence>
<xsd:attribute name="rid" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="ctype" type="xlf:AttrType_InlineDelimiters" use="optional"/>
<xsd:attribute name="crc" type="xsd:string" use="optional"/>
<xsd:attributeGroup ref="xlf:AttrGroup_TextContent"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="ept">
<xsd:complexType mixed="true">
<xsd:sequence maxOccurs="unbounded" minOccurs="0">
<xsd:element ref="xlf:sub"/>
</xsd:sequence>
<xsd:attribute name="rid" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="crc" type="xsd:string" use="optional"/>
<xsd:attributeGroup ref="xlf:AttrGroup_TextContent"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="it">
<xsd:complexType mixed="true">
<xsd:sequence maxOccurs="unbounded" minOccurs="0">
<xsd:element ref="xlf:sub"/>
</xsd:sequence>
<xsd:attribute name="pos" type="xlf:AttrType_Position" use="required"/>
<xsd:attribute name="rid" type="xsd:NMTOKEN" use="optional"/>
<xsd:attribute name="ctype" type="xlf:AttrType_InlineDelimiters" use="optional"/>
<xsd:attribute name="crc" type="xsd:string" use="optional"/>
<xsd:attributeGroup ref="xlf:AttrGroup_TextContent"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="sub">
<xsd:complexType mixed="true">
<xsd:group maxOccurs="unbounded" minOccurs="0" ref="xlf:ElemGroup_TextContent"/>
<xsd:attribute name="datatype" type="xlf:AttrType_datatype" use="optional"/>
<xsd:attribute name="ctype" type="xlf:AttrType_InlineDelimiters" use="optional"/>
<xsd:attribute name="xid" type="xsd:string" use="optional"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="mrk">
<xsd:complexType mixed="true">
<xsd:group maxOccurs="unbounded" minOccurs="0" ref="xlf:ElemGroup_TextContent"/>
<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:complexType>
</xsd:element>
</xsd:schema>
@@ -0,0 +1,411 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
XLIFF Version 2.0
OASIS Standard
05 August 2014
Copyright (c) OASIS Open 2014. All rights reserved.
Source: http://docs.oasis-open.org/xliff/xliff-core/v2.0/os/schemas/
-->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
xmlns:xlf="urn:oasis:names:tc:xliff:document:2.0"
targetNamespace="urn:oasis:names:tc:xliff:document:2.0">
<!-- Import -->
<xs:import namespace="http://www.w3.org/XML/1998/namespace"
schemaLocation="informativeCopiesOf3rdPartySchemas/w3c/xml.xsd"/>
<!-- Element Group -->
<xs:group name="inline">
<xs:choice>
<xs:element ref="xlf:cp"/>
<xs:element ref="xlf:ph"/>
<xs:element ref="xlf:pc"/>
<xs:element ref="xlf:sc"/>
<xs:element ref="xlf:ec"/>
<xs:element ref="xlf:mrk"/>
<xs:element ref="xlf:sm"/>
<xs:element ref="xlf:em"/>
</xs:choice>
</xs:group>
<!-- Attribute Types -->
<xs:simpleType name="yesNo">
<xs:restriction base="xs:string">
<xs:enumeration value="yes"/>
<xs:enumeration value="no"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="yesNoFirstNo">
<xs:restriction base="xs:string">
<xs:enumeration value="yes"/>
<xs:enumeration value="firstNo"/>
<xs:enumeration value="no"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="dirValue">
<xs:restriction base="xs:string">
<xs:enumeration value="ltr"/>
<xs:enumeration value="rtl"/>
<xs:enumeration value="auto"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="appliesTo">
<xs:restriction base="xs:string">
<xs:enumeration value="source"/>
<xs:enumeration value="target"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="userDefinedValue">
<xs:restriction base="xs:string">
<xs:pattern value="[^\s:]+:[^\s:]+"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="attrType_type">
<xs:restriction base="xs:string">
<xs:enumeration value="fmt"/>
<xs:enumeration value="ui"/>
<xs:enumeration value="quote"/>
<xs:enumeration value="link"/>
<xs:enumeration value="image"/>
<xs:enumeration value="other"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="typeForMrkValues">
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="generic"/>
<xs:enumeration value="comment"/>
<xs:enumeration value="term"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="attrType_typeForMrk">
<xs:union memberTypes="xlf:typeForMrkValues xlf:userDefinedValue"/>
</xs:simpleType>
<xs:simpleType name="priorityValue">
<xs:restriction base="xs:positiveInteger">
<xs:minInclusive value="1"/>
<xs:maxInclusive value="10"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="stateType">
<xs:restriction base="xs:string">
<xs:enumeration value="initial"/>
<xs:enumeration value="translated"/>
<xs:enumeration value="reviewed"/>
<xs:enumeration value="final"/>
</xs:restriction>
</xs:simpleType>
<!-- Structural Elements -->
<xs:element name="xliff">
<xs:complexType mixed="false">
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="unbounded" ref="xlf:file"/>
</xs:sequence>
<xs:attribute name="version" use="required"/>
<xs:attribute name="srcLang" use="required"/>
<xs:attribute name="trgLang" use="optional"/>
<xs:attribute ref="xml:space" use="optional" default="default"/>
<xs:anyAttribute namespace="##other" processContents="lax"/>
</xs:complexType>
</xs:element>
<xs:element name="file">
<xs:complexType mixed="false">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="1" ref="xlf:skeleton"/>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"
processContents="lax"/>
<xs:element minOccurs="0" maxOccurs="1" ref="xlf:notes"/>
<xs:choice minOccurs="1" maxOccurs="unbounded">
<xs:element ref="xlf:unit"/>
<xs:element ref="xlf:group"/>
</xs:choice>
</xs:sequence>
<xs:attribute name="id" use="required" type="xs:NMTOKEN"/>
<xs:attribute name="canResegment" use="optional" type="xlf:yesNo" default="yes"/>
<xs:attribute name="original" use="optional"/>
<xs:attribute name="translate" use="optional" type="xlf:yesNo" default="yes"/>
<xs:attribute name="srcDir" use="optional" type="xlf:dirValue" default="auto"/>
<xs:attribute name="trgDir" use="optional" type="xlf:dirValue" default="auto"/>
<xs:attribute ref="xml:space" use="optional"/>
<xs:anyAttribute namespace="##other" processContents="lax"/>
</xs:complexType>
</xs:element>
<xs:element name="skeleton">
<xs:complexType mixed="true">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"
processContents="lax"/>
</xs:sequence>
<xs:attribute name="href" use="optional"/>
</xs:complexType>
</xs:element>
<xs:element name="group">
<xs:complexType mixed="false">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"
processContents="lax"/>
<xs:element minOccurs="0" maxOccurs="1" ref="xlf:notes"/>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="xlf:unit"/>
<xs:element ref="xlf:group"/>
</xs:choice>
</xs:sequence>
<xs:attribute name="id" use="required" type="xs:NMTOKEN"/>
<xs:attribute name="name" use="optional"/>
<xs:attribute name="canResegment" use="optional" type="xlf:yesNo"/>
<xs:attribute name="translate" use="optional" type="xlf:yesNo"/>
<xs:attribute name="srcDir" use="optional" type="xlf:dirValue"/>
<xs:attribute name="trgDir" use="optional" type="xlf:dirValue"/>
<xs:attribute name="type" use="optional" type="xlf:userDefinedValue"/>
<xs:attribute ref="xml:space" use="optional"/>
<xs:anyAttribute namespace="##other" processContents="lax"/>
</xs:complexType>
</xs:element>
<xs:element name="unit">
<xs:complexType mixed="false">
<xs:sequence>
<xs:any minOccurs="0" maxOccurs="unbounded" namespace="##other"
processContents="lax"/>
<xs:element minOccurs="0" maxOccurs="1" ref="xlf:notes"/>
<xs:element minOccurs="0" maxOccurs="1" ref="xlf:originalData"/>
<xs:choice minOccurs="1" maxOccurs="unbounded">
<xs:element ref="xlf:segment"/>
<xs:element ref="xlf:ignorable"/>
</xs:choice>
</xs:sequence>
<xs:attribute name="id" use="required" type="xs:NMTOKEN"/>
<xs:attribute name="name" use="optional"/>
<xs:attribute name="canResegment" use="optional" type="xlf:yesNo"/>
<xs:attribute name="translate" use="optional" type="xlf:yesNo"/>
<xs:attribute name="srcDir" use="optional" type="xlf:dirValue"/>
<xs:attribute name="trgDir" use="optional" type="xlf:dirValue"/>
<xs:attribute ref="xml:space" use="optional"/>
<xs:attribute name="type" use="optional" type="xlf:userDefinedValue"/>
<xs:anyAttribute namespace="##other" processContents="lax"/>
</xs:complexType>
</xs:element>
<xs:element name="segment">
<xs:complexType mixed="false">
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="1" ref="xlf:source"/>
<xs:element minOccurs="0" maxOccurs="1" ref="xlf:target"/>
</xs:sequence>
<xs:attribute name="id" use="optional" type="xs:NMTOKEN"/>
<xs:attribute name="canResegment" use="optional" type="xlf:yesNo"/>
<xs:attribute name="state" use="optional" type="xlf:stateType" default="initial"/>
<xs:attribute name="subState" use="optional"/>
</xs:complexType>
</xs:element>
<xs:element name="ignorable">
<xs:complexType mixed="false">
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="1" ref="xlf:source"/>
<xs:element minOccurs="0" maxOccurs="1" ref="xlf:target"/>
</xs:sequence>
<xs:attribute name="id" use="optional" type="xs:NMTOKEN"/>
</xs:complexType>
</xs:element>
<xs:element name="notes">
<xs:complexType mixed="false">
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="unbounded" ref="xlf:note"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="note">
<xs:complexType mixed="true">
<xs:attribute name="id" use="optional" type="xs:NMTOKEN"/>
<xs:attribute name="appliesTo" use="optional" type="xlf:appliesTo"/>
<xs:attribute name="category" use="optional"/>
<xs:attribute name="priority" use="optional" type="xlf:priorityValue" default="1"/>
<xs:anyAttribute namespace="##other" processContents="lax"/>
</xs:complexType>
</xs:element>
<xs:element name="originalData">
<xs:complexType mixed="false">
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="unbounded" ref="xlf:data"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="data">
<xs:complexType mixed="true">
<xs:sequence>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="xlf:cp"/>
</xs:sequence>
<xs:attribute name="id" use="required" type="xs:NMTOKEN"/>
<xs:attribute name="dir" use="optional" type="xlf:dirValue" default="auto"/>
<xs:attribute ref="xml:space" use="optional" fixed="preserve"/>
</xs:complexType>
</xs:element>
<xs:element name="source">
<xs:complexType mixed="true">
<xs:group ref="xlf:inline" minOccurs="0" maxOccurs="unbounded"/>
<xs:attribute ref="xml:lang" use="optional"/>
<xs:attribute ref="xml:space" use="optional"/>
</xs:complexType>
</xs:element>
<xs:element name="target">
<xs:complexType mixed="true">
<xs:group ref="xlf:inline" minOccurs="0" maxOccurs="unbounded"/>
<xs:attribute ref="xml:lang" use="optional"/>
<xs:attribute ref="xml:space" use="optional"/>
<xs:attribute name="order" use="optional" type="xs:positiveInteger"/>
</xs:complexType>
</xs:element>
<!-- Inline Elements -->
<xs:element name="cp">
<!-- Code Point -->
<xs:complexType mixed="false">
<xs:attribute name="hex" use="required" type="xs:hexBinary"/>
</xs:complexType>
</xs:element>
<xs:element name="ph">
<!-- Placeholder -->
<xs:complexType mixed="false">
<xs:attribute name="canCopy" use="optional" type="xlf:yesNo" default="yes"/>
<xs:attribute name="canDelete" use="optional" type="xlf:yesNo" default="yes"/>
<xs:attribute name="canReorder" use="optional" type="xlf:yesNoFirstNo" default="yes"/>
<xs:attribute name="copyOf" use="optional" type="xs:NMTOKEN"/>
<xs:attribute name="disp" use="optional"/>
<xs:attribute name="equiv" use="optional"/>
<xs:attribute name="id" use="required" type="xs:NMTOKEN"/>
<xs:attribute name="dataRef" use="optional" type="xs:NMTOKEN"/>
<xs:attribute name="subFlows" use="optional" type="xs:NMTOKENS"/>
<xs:attribute name="subType" use="optional" type="xlf:userDefinedValue"/>
<xs:attribute name="type" use="optional" type="xlf:attrType_type"/>
<xs:anyAttribute namespace="##other" processContents="lax"/>
</xs:complexType>
</xs:element>
<xs:element name="pc">
<!-- Paired Code -->
<xs:complexType mixed="true">
<xs:group ref="xlf:inline" minOccurs="0" maxOccurs="unbounded"/>
<xs:attribute name="canCopy" use="optional" type="xlf:yesNo" default="yes"/>
<xs:attribute name="canDelete" use="optional" type="xlf:yesNo" default="yes"/>
<xs:attribute name="canOverlap" use="optional" type="xlf:yesNo"/>
<xs:attribute name="canReorder" use="optional" type="xlf:yesNoFirstNo" default="yes"/>
<xs:attribute name="copyOf" use="optional" type="xs:NMTOKEN"/>
<xs:attribute name="dispEnd" use="optional"/>
<xs:attribute name="dispStart" use="optional"/>
<xs:attribute name="equivEnd" use="optional"/>
<xs:attribute name="equivStart" use="optional"/>
<xs:attribute name="id" use="required" type="xs:NMTOKEN"/>
<xs:attribute name="dataRefEnd" use="optional" type="xs:NMTOKEN"/>
<xs:attribute name="dataRefStart" use="optional" type="xs:NMTOKEN"/>
<xs:attribute name="subFlowsEnd" use="optional" type="xs:NMTOKENS"/>
<xs:attribute name="subFlowsStart" use="optional" type="xs:NMTOKENS"/>
<xs:attribute name="subType" use="optional" type="xlf:userDefinedValue"/>
<xs:attribute name="type" use="optional" type="xlf:attrType_type"/>
<xs:attribute name="dir" use="optional" type="xlf:dirValue"/>
<xs:anyAttribute namespace="##other" processContents="lax"/>
</xs:complexType>
</xs:element>
<xs:element name="sc">
<!-- Start Code -->
<xs:complexType mixed="false">
<xs:attribute name="canCopy" use="optional" type="xlf:yesNo" default="yes"/>
<xs:attribute name="canDelete" use="optional" type="xlf:yesNo" default="yes"/>
<xs:attribute name="canOverlap" use="optional" type="xlf:yesNo" default="yes"/>
<xs:attribute name="canReorder" use="optional" type="xlf:yesNoFirstNo" default="yes"/>
<xs:attribute name="copyOf" use="optional" type="xs:NMTOKEN"/>
<xs:attribute name="dataRef" use="optional" type="xs:NMTOKEN"/>
<xs:attribute name="dir" use="optional" type="xlf:dirValue"/>
<xs:attribute name="disp" use="optional"/>
<xs:attribute name="equiv" use="optional"/>
<xs:attribute name="id" use="required" type="xs:NMTOKEN"/>
<xs:attribute name="isolated" use="optional" type="xlf:yesNo" default="no"/>
<xs:attribute name="subFlows" use="optional" type="xs:NMTOKENS"/>
<xs:attribute name="subType" use="optional" type="xlf:userDefinedValue"/>
<xs:attribute name="type" use="optional" type="xlf:attrType_type"/>
<xs:anyAttribute namespace="##other" processContents="lax"/>
</xs:complexType>
</xs:element>
<xs:element name="ec">
<!-- End Code -->
<xs:complexType mixed="false">
<xs:attribute name="canCopy" use="optional" type="xlf:yesNo" default="yes"/>
<xs:attribute name="canDelete" use="optional" type="xlf:yesNo" default="yes"/>
<xs:attribute name="canOverlap" use="optional" type="xlf:yesNo" default="yes"/>
<xs:attribute name="canReorder" use="optional" type="xlf:yesNoFirstNo" default="yes"/>
<xs:attribute name="copyOf" use="optional" type="xs:NMTOKEN"/>
<xs:attribute name="dataRef" use="optional" type="xs:NMTOKEN"/>
<xs:attribute name="dir" use="optional" type="xlf:dirValue"/>
<xs:attribute name="disp" use="optional"/>
<xs:attribute name="equiv" use="optional"/>
<xs:attribute name="id" use="optional" type="xs:NMTOKEN"/>
<xs:attribute name="isolated" use="optional" type="xlf:yesNo" default="no"/>
<xs:attribute name="startRef" use="optional" type="xs:NMTOKEN"/>
<xs:attribute name="subFlows" use="optional" type="xs:NMTOKENS"/>
<xs:attribute name="subType" use="optional" type="xlf:userDefinedValue"/>
<xs:attribute name="type" use="optional" type="xlf:attrType_type"/>
<xs:anyAttribute namespace="##other" processContents="lax"/>
</xs:complexType>
</xs:element>
<xs:element name="mrk">
<!-- Annotation Marker -->
<xs:complexType mixed="true">
<xs:group ref="xlf:inline" minOccurs="0" maxOccurs="unbounded"/>
<xs:attribute name="id" use="required" type="xs:NMTOKEN"/>
<xs:attribute name="translate" use="optional" type="xlf:yesNo"/>
<xs:attribute name="type" use="optional" type="xlf:attrType_typeForMrk"/>
<xs:attribute name="ref" use="optional" type="xs:anyURI"/>
<xs:attribute name="value" use="optional"/>
<xs:anyAttribute namespace="##other" processContents="lax"/>
</xs:complexType>
</xs:element>
<xs:element name="sm">
<!-- Start Annotation Marker -->
<xs:complexType mixed="false">
<xs:attribute name="id" use="required" type="xs:NMTOKEN"/>
<xs:attribute name="translate" use="optional" type="xlf:yesNo"/>
<xs:attribute name="type" use="optional" type="xlf:attrType_typeForMrk"/>
<xs:attribute name="ref" use="optional" type="xs:anyURI"/>
<xs:attribute name="value" use="optional"/>
<xs:anyAttribute namespace="##other" processContents="lax"/>
</xs:complexType>
</xs:element>
<xs:element name="em">
<!-- End Annotation Marker -->
<xs:complexType mixed="false">
<xs:attribute name="startRef" use="required" type="xs:NMTOKEN"/>
</xs:complexType>
</xs:element>
</xs:schema>
+309
View File
@@ -0,0 +1,309 @@
<?xml version='1.0'?>
<?xml-stylesheet href="../2008/09/xsd.xsl" type="text/xsl"?>
<xs:schema targetNamespace="http://www.w3.org/XML/1998/namespace"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns ="http://www.w3.org/1999/xhtml"
xml:lang="en">
<xs:annotation>
<xs:documentation>
<div>
<h1>About the XML namespace</h1>
<div class="bodytext">
<p>
This schema document describes the XML namespace, in a form
suitable for import by other schema documents.
</p>
<p>
See <a href="http://www.w3.org/XML/1998/namespace.html">
http://www.w3.org/XML/1998/namespace.html</a> and
<a href="http://www.w3.org/TR/REC-xml">
http://www.w3.org/TR/REC-xml</a> for information
about this namespace.
</p>
<p>
Note that local names in this namespace are intended to be
defined only by the World Wide Web Consortium or its subgroups.
The names currently defined in this namespace are listed below.
They should not be used with conflicting semantics by any Working
Group, specification, or document instance.
</p>
<p>
See further below in this document for more information about <a
href="#usage">how to refer to this schema document from your own
XSD schema documents</a> and about <a href="#nsversioning">the
namespace-versioning policy governing this schema document</a>.
</p>
</div>
</div>
</xs:documentation>
</xs:annotation>
<xs:attribute name="lang">
<xs:annotation>
<xs:documentation>
<div>
<h3>lang (as an attribute name)</h3>
<p>
denotes an attribute whose value
is a language code for the natural language of the content of
any element; its value is inherited. This name is reserved
by virtue of its definition in the XML specification.</p>
</div>
<div>
<h4>Notes</h4>
<p>
Attempting to install the relevant ISO 2- and 3-letter
codes as the enumerated possible values is probably never
going to be a realistic possibility.
</p>
<p>
See BCP 47 at <a href="http://www.rfc-editor.org/rfc/bcp/bcp47.txt">
http://www.rfc-editor.org/rfc/bcp/bcp47.txt</a>
and the IANA language subtag registry at
<a href="http://www.iana.org/assignments/language-subtag-registry">
http://www.iana.org/assignments/language-subtag-registry</a>
for further information.
</p>
<p>
The union allows for the 'un-declaration' of xml:lang with
the empty string.
</p>
</div>
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="xs:language">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value=""/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="space">
<xs:annotation>
<xs:documentation>
<div>
<h3>space (as an attribute name)</h3>
<p>
denotes an attribute whose
value is a keyword indicating what whitespace processing
discipline is intended for the content of the element; its
value is inherited. This name is reserved by virtue of its
definition in the XML specification.</p>
</div>
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:NCName">
<xs:enumeration value="default"/>
<xs:enumeration value="preserve"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="base" type="xs:anyURI"> <xs:annotation>
<xs:documentation>
<div>
<h3>base (as an attribute name)</h3>
<p>
denotes an attribute whose value
provides a URI to be used as the base for interpreting any
relative URIs in the scope of the element on which it
appears; its value is inherited. This name is reserved
by virtue of its definition in the XML Base specification.</p>
<p>
See <a
href="http://www.w3.org/TR/xmlbase/">http://www.w3.org/TR/xmlbase/</a>
for information about this attribute.
</p>
</div>
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="id" type="xs:ID">
<xs:annotation>
<xs:documentation>
<div>
<h3>id (as an attribute name)</h3>
<p>
denotes an attribute whose value
should be interpreted as if declared to be of type ID.
This name is reserved by virtue of its definition in the
xml:id specification.</p>
<p>
See <a
href="http://www.w3.org/TR/xml-id/">http://www.w3.org/TR/xml-id/</a>
for information about this attribute.
</p>
</div>
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attributeGroup name="specialAttrs">
<xs:attribute ref="xml:base"/>
<xs:attribute ref="xml:lang"/>
<xs:attribute ref="xml:space"/>
<xs:attribute ref="xml:id"/>
</xs:attributeGroup>
<xs:annotation>
<xs:documentation>
<div>
<h3>Father (in any context at all)</h3>
<div class="bodytext">
<p>
denotes Jon Bosak, the chair of
the original XML Working Group. This name is reserved by
the following decision of the W3C XML Plenary and
XML Coordination groups:
</p>
<blockquote>
<p>
In appreciation for his vision, leadership and
dedication the W3C XML Plenary on this 10th day of
February, 2000, reserves for Jon Bosak in perpetuity
the XML name "xml:Father".
</p>
</blockquote>
</div>
</div>
</xs:documentation>
</xs:annotation>
<xs:annotation>
<xs:documentation>
<div xml:id="usage" id="usage">
<h2><a name="usage">About this schema document</a></h2>
<div class="bodytext">
<p>
This schema defines attributes and an attribute group suitable
for use by schemas wishing to allow <code>xml:base</code>,
<code>xml:lang</code>, <code>xml:space</code> or
<code>xml:id</code> attributes on elements they define.
</p>
<p>
To enable this, such a schema must import this schema for
the XML namespace, e.g. as follows:
</p>
<pre>
&lt;schema.. .>
.. .
&lt;import namespace="http://www.w3.org/XML/1998/namespace"
schemaLocation="http://www.w3.org/2001/xml.xsd"/>
</pre>
<p>
or
</p>
<pre>
&lt;import namespace="http://www.w3.org/XML/1998/namespace"
schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
</pre>
<p>
Subsequently, qualified reference to any of the attributes or the
group defined below will have the desired effect, e.g.
</p>
<pre>
&lt;type.. .>
.. .
&lt;attributeGroup ref="xml:specialAttrs"/>
</pre>
<p>
will define a type which will schema-validate an instance element
with any of those attributes.
</p>
</div>
</div>
</xs:documentation>
</xs:annotation>
<xs:annotation>
<xs:documentation>
<div id="nsversioning" xml:id="nsversioning">
<h2><a name="nsversioning">Versioning policy for this schema document</a></h2>
<div class="bodytext">
<p>
In keeping with the XML Schema WG's standard versioning
policy, this schema document will persist at
<a href="http://www.w3.org/2009/01/xml.xsd">
http://www.w3.org/2009/01/xml.xsd</a>.
</p>
<p>
At the date of issue it can also be found at
<a href="http://www.w3.org/2001/xml.xsd">
http://www.w3.org/2001/xml.xsd</a>.
</p>
<p>
The schema document at that URI may however change in the future,
in order to remain compatible with the latest version of XML
Schema itself, or with the XML namespace itself. In other words,
if the XML Schema or XML namespaces change, the version of this
document at <a href="http://www.w3.org/2001/xml.xsd">
http://www.w3.org/2001/xml.xsd
</a>
will change accordingly; the version at
<a href="http://www.w3.org/2009/01/xml.xsd">
http://www.w3.org/2009/01/xml.xsd
</a>
will not change.
</p>
<p>
Previous dated (and unchanging) versions of this schema
document are at:
</p>
<ul>
<li><a href="http://www.w3.org/2009/01/xml.xsd">
http://www.w3.org/2009/01/xml.xsd</a></li>
<li><a href="http://www.w3.org/2007/08/xml.xsd">
http://www.w3.org/2007/08/xml.xsd</a></li>
<li><a href="http://www.w3.org/2004/10/xml.xsd">
http://www.w3.org/2004/10/xml.xsd</a></li>
<li><a href="http://www.w3.org/2001/03/xml.xsd">
http://www.w3.org/2001/03/xml.xsd</a></li>
</ul>
</div>
</div>
</xs:documentation>
</xs:annotation>
</xs:schema>
@@ -0,0 +1,147 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Test;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\Translation\Dumper\XliffFileDumper;
use Symfony\Component\Translation\Exception\IncompleteDsnException;
use Symfony\Component\Translation\Exception\UnsupportedSchemeException;
use Symfony\Component\Translation\Loader\LoaderInterface;
use Symfony\Component\Translation\Provider\Dsn;
use Symfony\Component\Translation\Provider\ProviderFactoryInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
/**
* A test case to ease testing a translation provider factory.
*
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*
* @internal
*/
abstract class ProviderFactoryTestCase extends TestCase
{
protected $client;
protected $logger;
protected $defaultLocale;
protected $loader;
protected $xliffFileDumper;
abstract public function createFactory(): ProviderFactoryInterface;
/**
* @return iterable<array{0: bool, 1: string}>
*/
abstract public function supportsProvider(): iterable;
/**
* @return iterable<array{0: string, 1: string, 2: TransportInterface}>
*/
abstract public function createProvider(): iterable;
/**
* @return iterable<array{0: string, 1: string|null}>
*/
public function unsupportedSchemeProvider(): iterable
{
return [];
}
/**
* @return iterable<array{0: string, 1: string|null}>
*/
public function incompleteDsnProvider(): iterable
{
return [];
}
/**
* @dataProvider supportsProvider
*/
public function testSupports(bool $expected, string $dsn)
{
$factory = $this->createFactory();
$this->assertSame($expected, $factory->supports(new Dsn($dsn)));
}
/**
* @dataProvider createProvider
*/
public function testCreate(string $expected, string $dsn)
{
$factory = $this->createFactory();
$provider = $factory->create(new Dsn($dsn));
$this->assertSame($expected, (string) $provider);
}
/**
* @dataProvider unsupportedSchemeProvider
*/
public function testUnsupportedSchemeException(string $dsn, string $message = null)
{
$factory = $this->createFactory();
$dsn = new Dsn($dsn);
$this->expectException(UnsupportedSchemeException::class);
if (null !== $message) {
$this->expectExceptionMessage($message);
}
$factory->create($dsn);
}
/**
* @dataProvider incompleteDsnProvider
*/
public function testIncompleteDsnException(string $dsn, string $message = null)
{
$factory = $this->createFactory();
$dsn = new Dsn($dsn);
$this->expectException(IncompleteDsnException::class);
if (null !== $message) {
$this->expectExceptionMessage($message);
}
$factory->create($dsn);
}
protected function getClient(): HttpClientInterface
{
return $this->client ?? $this->client = new MockHttpClient();
}
protected function getLogger(): LoggerInterface
{
return $this->logger ?? $this->logger = $this->createMock(LoggerInterface::class);
}
protected function getDefaultLocale(): string
{
return $this->defaultLocale ?? $this->defaultLocale = 'en';
}
protected function getLoader(): LoaderInterface
{
return $this->loader ?? $this->loader = $this->createMock(LoaderInterface::class);
}
protected function getXliffFileDumper(): XliffFileDumper
{
return $this->xliffFileDumper ?? $this->xliffFileDumper = $this->createMock(XliffFileDumper::class);
}
}
+86
View File
@@ -0,0 +1,86 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation\Test;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\Translation\Dumper\XliffFileDumper;
use Symfony\Component\Translation\Loader\LoaderInterface;
use Symfony\Component\Translation\Provider\ProviderInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
/**
* A test case to ease testing a translation provider.
*
* @author Mathieu Santostefano <msantostefano@protonmail.com>
*
* @internal
*/
abstract class ProviderTestCase extends TestCase
{
protected $client;
protected $logger;
protected $defaultLocale;
protected $loader;
protected $xliffFileDumper;
abstract public function createProvider(HttpClientInterface $client, LoaderInterface $loader, LoggerInterface $logger, string $defaultLocale, string $endpoint): ProviderInterface;
/**
* @return iterable<array{0: string, 1: ProviderInterface}>
*/
abstract public function toStringProvider(): iterable;
/**
* @dataProvider toStringProvider
*/
public function testToString(ProviderInterface $provider, string $expected)
{
$this->assertSame($expected, (string) $provider);
}
protected function getClient(): MockHttpClient
{
return $this->client ?? $this->client = new MockHttpClient();
}
/**
* @return LoaderInterface&MockObject
*/
protected function getLoader(): LoaderInterface
{
return $this->loader ?? $this->loader = $this->createMock(LoaderInterface::class);
}
/**
* @return LoaderInterface&MockObject
*/
protected function getLogger(): LoggerInterface
{
return $this->logger ?? $this->logger = $this->createMock(LoggerInterface::class);
}
protected function getDefaultLocale(): string
{
return $this->defaultLocale ?? $this->defaultLocale = 'en';
}
/**
* @return LoaderInterface&MockObject
*/
protected function getXliffFileDumper(): XliffFileDumper
{
return $this->xliffFileDumper ?? $this->xliffFileDumper = $this->createMock(XliffFileDumper::class);
}
}
+57
View File
@@ -0,0 +1,57 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Translation;
use Symfony\Contracts\Translation\TranslatableInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @author Nate Wiebe <nate@northern.co>
*/
class TranslatableMessage implements TranslatableInterface
{
private $message;
private $parameters;
private $domain;
public function __construct(string $message, array $parameters = [], string $domain = null)
{
$this->message = $message;
$this->parameters = $parameters;
$this->domain = $domain;
}
public function __toString(): string
{
return $this->getMessage();
}
public function getMessage(): string
{
return $this->message;
}
public function getParameters(): array
{
return $this->parameters;
}
public function getDomain(): ?string
{
return $this->domain;
}
public function trans(TranslatorInterface $translator, string $locale = null): string
{
return $translator->trans($this->getMessage(), $this->getParameters(), $this->getDomain(), $locale);
}
}
+492
View File
@@ -0,0 +1,492 @@
<?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\Config\ConfigCacheFactory;
use Symfony\Component\Config\ConfigCacheFactoryInterface;
use Symfony\Component\Config\ConfigCacheInterface;
use Symfony\Component\Translation\Exception\InvalidArgumentException;
use Symfony\Component\Translation\Exception\NotFoundResourceException;
use Symfony\Component\Translation\Exception\RuntimeException;
use Symfony\Component\Translation\Formatter\IntlFormatterInterface;
use Symfony\Component\Translation\Formatter\MessageFormatter;
use Symfony\Component\Translation\Formatter\MessageFormatterInterface;
use Symfony\Component\Translation\Loader\LoaderInterface;
use Symfony\Contracts\Translation\LocaleAwareInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
// Help opcache.preload discover always-needed symbols
class_exists(MessageCatalogue::class);
/**
* @author Fabien Potencier <fabien@symfony.com>
*/
class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface
{
/**
* @var MessageCatalogueInterface[]
*/
protected $catalogues = [];
/**
* @var string
*/
private $locale;
/**
* @var array
*/
private $fallbackLocales = [];
/**
* @var LoaderInterface[]
*/
private $loaders = [];
/**
* @var array
*/
private $resources = [];
/**
* @var MessageFormatterInterface
*/
private $formatter;
/**
* @var string
*/
private $cacheDir;
/**
* @var bool
*/
private $debug;
private $cacheVary;
/**
* @var ConfigCacheFactoryInterface|null
*/
private $configCacheFactory;
/**
* @var array|null
*/
private $parentLocales;
private $hasIntlFormatter;
/**
* @throws InvalidArgumentException If a locale contains invalid characters
*/
public function __construct(string $locale, MessageFormatterInterface $formatter = null, string $cacheDir = null, bool $debug = false, array $cacheVary = [])
{
$this->setLocale($locale);
if (null === $formatter) {
$formatter = new MessageFormatter();
}
$this->formatter = $formatter;
$this->cacheDir = $cacheDir;
$this->debug = $debug;
$this->cacheVary = $cacheVary;
$this->hasIntlFormatter = $formatter instanceof IntlFormatterInterface;
}
public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory)
{
$this->configCacheFactory = $configCacheFactory;
}
/**
* Adds a Loader.
*
* @param string $format The name of the loader (@see addResource())
*/
public function addLoader(string $format, LoaderInterface $loader)
{
$this->loaders[$format] = $loader;
}
/**
* Adds a Resource.
*
* @param string $format The name of the loader (@see addLoader())
* @param mixed $resource The resource name
*
* @throws InvalidArgumentException If the locale contains invalid characters
*/
public function addResource(string $format, $resource, string $locale, string $domain = null)
{
if (null === $domain) {
$domain = 'messages';
}
$this->assertValidLocale($locale);
$locale ?: $locale = class_exists(\Locale::class) ? \Locale::getDefault() : 'en';
$this->resources[$locale][] = [$format, $resource, $domain];
if (\in_array($locale, $this->fallbackLocales)) {
$this->catalogues = [];
} else {
unset($this->catalogues[$locale]);
}
}
/**
* {@inheritdoc}
*/
public function setLocale(string $locale)
{
$this->assertValidLocale($locale);
$this->locale = $locale;
}
/**
* {@inheritdoc}
*/
public function getLocale()
{
return $this->locale ?: (class_exists(\Locale::class) ? \Locale::getDefault() : 'en');
}
/**
* Sets the fallback locales.
*
* @throws InvalidArgumentException If a locale contains invalid characters
*/
public function setFallbackLocales(array $locales)
{
// needed as the fallback locales are linked to the already loaded catalogues
$this->catalogues = [];
foreach ($locales as $locale) {
$this->assertValidLocale($locale);
}
$this->fallbackLocales = $this->cacheVary['fallback_locales'] = $locales;
}
/**
* Gets the fallback locales.
*
* @internal
*/
public function getFallbackLocales(): array
{
return $this->fallbackLocales;
}
/**
* {@inheritdoc}
*/
public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null)
{
if (null === $id || '' === $id) {
return '';
}
if (null === $domain) {
$domain = 'messages';
}
$catalogue = $this->getCatalogue($locale);
$locale = $catalogue->getLocale();
while (!$catalogue->defines($id, $domain)) {
if ($cat = $catalogue->getFallbackCatalogue()) {
$catalogue = $cat;
$locale = $catalogue->getLocale();
} else {
break;
}
}
$len = \strlen(MessageCatalogue::INTL_DOMAIN_SUFFIX);
if ($this->hasIntlFormatter
&& ($catalogue->defines($id, $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX)
|| (\strlen($domain) > $len && 0 === substr_compare($domain, MessageCatalogue::INTL_DOMAIN_SUFFIX, -$len, $len)))
) {
return $this->formatter->formatIntl($catalogue->get($id, $domain), $locale, $parameters);
}
return $this->formatter->format($catalogue->get($id, $domain), $locale, $parameters);
}
/**
* {@inheritdoc}
*/
public function getCatalogue(string $locale = null)
{
if (!$locale) {
$locale = $this->getLocale();
} else {
$this->assertValidLocale($locale);
}
if (!isset($this->catalogues[$locale])) {
$this->loadCatalogue($locale);
}
return $this->catalogues[$locale];
}
/**
* {@inheritdoc}
*/
public function getCatalogues(): array
{
return array_values($this->catalogues);
}
/**
* Gets the loaders.
*
* @return array LoaderInterface[]
*/
protected function getLoaders()
{
return $this->loaders;
}
protected function loadCatalogue(string $locale)
{
if (null === $this->cacheDir) {
$this->initializeCatalogue($locale);
} else {
$this->initializeCacheCatalogue($locale);
}
}
protected function initializeCatalogue(string $locale)
{
$this->assertValidLocale($locale);
try {
$this->doLoadCatalogue($locale);
} catch (NotFoundResourceException $e) {
if (!$this->computeFallbackLocales($locale)) {
throw $e;
}
}
$this->loadFallbackCatalogues($locale);
}
private function initializeCacheCatalogue(string $locale): void
{
if (isset($this->catalogues[$locale])) {
/* Catalogue already initialized. */
return;
}
$this->assertValidLocale($locale);
$cache = $this->getConfigCacheFactory()->cache($this->getCatalogueCachePath($locale),
function (ConfigCacheInterface $cache) use ($locale) {
$this->dumpCatalogue($locale, $cache);
}
);
if (isset($this->catalogues[$locale])) {
/* Catalogue has been initialized as it was written out to cache. */
return;
}
/* Read catalogue from cache. */
$this->catalogues[$locale] = include $cache->getPath();
}
private function dumpCatalogue(string $locale, ConfigCacheInterface $cache): void
{
$this->initializeCatalogue($locale);
$fallbackContent = $this->getFallbackContent($this->catalogues[$locale]);
$content = sprintf(<<<EOF
<?php
use Symfony\Component\Translation\MessageCatalogue;
\$catalogue = new MessageCatalogue('%s', %s);
%s
return \$catalogue;
EOF
,
$locale,
var_export($this->getAllMessages($this->catalogues[$locale]), true),
$fallbackContent
);
$cache->write($content, $this->catalogues[$locale]->getResources());
}
private function getFallbackContent(MessageCatalogue $catalogue): string
{
$fallbackContent = '';
$current = '';
$replacementPattern = '/[^a-z0-9_]/i';
$fallbackCatalogue = $catalogue->getFallbackCatalogue();
while ($fallbackCatalogue) {
$fallback = $fallbackCatalogue->getLocale();
$fallbackSuffix = ucfirst(preg_replace($replacementPattern, '_', $fallback));
$currentSuffix = ucfirst(preg_replace($replacementPattern, '_', $current));
$fallbackContent .= sprintf(<<<'EOF'
$catalogue%s = new MessageCatalogue('%s', %s);
$catalogue%s->addFallbackCatalogue($catalogue%s);
EOF
,
$fallbackSuffix,
$fallback,
var_export($this->getAllMessages($fallbackCatalogue), true),
$currentSuffix,
$fallbackSuffix
);
$current = $fallbackCatalogue->getLocale();
$fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue();
}
return $fallbackContent;
}
private function getCatalogueCachePath(string $locale): string
{
return $this->cacheDir.'/catalogue.'.$locale.'.'.strtr(substr(base64_encode(hash('sha256', serialize($this->cacheVary), true)), 0, 7), '/', '_').'.php';
}
/**
* @internal
*/
protected function doLoadCatalogue(string $locale): void
{
$this->catalogues[$locale] = new MessageCatalogue($locale);
if (isset($this->resources[$locale])) {
foreach ($this->resources[$locale] as $resource) {
if (!isset($this->loaders[$resource[0]])) {
if (\is_string($resource[1])) {
throw new RuntimeException(sprintf('No loader is registered for the "%s" format when loading the "%s" resource.', $resource[0], $resource[1]));
}
throw new RuntimeException(sprintf('No loader is registered for the "%s" format.', $resource[0]));
}
$this->catalogues[$locale]->addCatalogue($this->loaders[$resource[0]]->load($resource[1], $locale, $resource[2]));
}
}
}
private function loadFallbackCatalogues(string $locale): void
{
$current = $this->catalogues[$locale];
foreach ($this->computeFallbackLocales($locale) as $fallback) {
if (!isset($this->catalogues[$fallback])) {
$this->initializeCatalogue($fallback);
}
$fallbackCatalogue = new MessageCatalogue($fallback, $this->getAllMessages($this->catalogues[$fallback]));
foreach ($this->catalogues[$fallback]->getResources() as $resource) {
$fallbackCatalogue->addResource($resource);
}
$current->addFallbackCatalogue($fallbackCatalogue);
$current = $fallbackCatalogue;
}
}
protected function computeFallbackLocales(string $locale)
{
if (null === $this->parentLocales) {
$this->parentLocales = json_decode(file_get_contents(__DIR__.'/Resources/data/parents.json'), true);
}
$originLocale = $locale;
$locales = [];
while ($locale) {
$parent = $this->parentLocales[$locale] ?? null;
if ($parent) {
$locale = 'root' !== $parent ? $parent : null;
} elseif (\function_exists('locale_parse')) {
$localeSubTags = locale_parse($locale);
$locale = null;
if (1 < \count($localeSubTags)) {
array_pop($localeSubTags);
$locale = locale_compose($localeSubTags) ?: null;
}
} elseif ($i = strrpos($locale, '_') ?: strrpos($locale, '-')) {
$locale = substr($locale, 0, $i);
} else {
$locale = null;
}
if (null !== $locale) {
$locales[] = $locale;
}
}
foreach ($this->fallbackLocales as $fallback) {
if ($fallback === $originLocale) {
continue;
}
$locales[] = $fallback;
}
return array_unique($locales);
}
/**
* Asserts that the locale is valid, throws an Exception if not.
*
* @throws InvalidArgumentException If the locale contains invalid characters
*/
protected function assertValidLocale(string $locale)
{
if (!preg_match('/^[a-z0-9@_\\.\\-]*$/i', (string) $locale)) {
throw new InvalidArgumentException(sprintf('Invalid "%s" locale.', $locale));
}
}
/**
* Provides the ConfigCache factory implementation, falling back to a
* default implementation if necessary.
*/
private function getConfigCacheFactory(): ConfigCacheFactoryInterface
{
if (!$this->configCacheFactory) {
$this->configCacheFactory = new ConfigCacheFactory($this->debug);
}
return $this->configCacheFactory;
}
private function getAllMessages(MessageCatalogueInterface $catalogue): array
{
$allMessages = [];
foreach ($catalogue->all() as $domain => $messages) {
if ($intlMessages = $catalogue->all($domain.MessageCatalogue::INTL_DOMAIN_SUFFIX)) {
$allMessages[$domain.MessageCatalogue::INTL_DOMAIN_SUFFIX] = $intlMessages;
$messages = array_diff_key($messages, $intlMessages);
}
if ($messages) {
$allMessages[$domain] = $messages;
}
}
return $allMessages;
}
}

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