Issue #2546618 by plach, dawehner, chx, bojanz: Replace the Symfony Translation component with a custom interface since we don't use it

8.0.x
Nathaniel Catchpole 2015-09-09 21:48:46 +01:00
parent bc05470d2e
commit 3d2cf1a03f
144 changed files with 52 additions and 10994 deletions

View File

@ -120,7 +120,8 @@
"drupal/update": "self.version",
"drupal/user": "self.version",
"drupal/views": "self.version",
"drupal/views_ui": "self.version"
"drupal/views_ui": "self.version",
"symfony/translation": "~2.4"
},
"minimum-stability": "dev",
"prefer-stable": true,
@ -128,7 +129,8 @@
"psr-4": {
"Drupal\\Core\\": "lib/Drupal/Core",
"Drupal\\Component\\": "lib/Drupal/Component",
"Drupal\\Driver\\": "../drivers/lib/Drupal/Driver"
"Drupal\\Driver\\": "../drivers/lib/Drupal/Driver",
"Symfony\\Component\\Translation\\": "lib/Symfony/Component/Translation"
},
"files": [
"lib/Drupal.php"

63
core/composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
"hash": "e789e5736fbe96c9b9502e89b53dcebe",
"hash": "5916317ae84f5caeb1bb254f4fe65775",
"packages": [
{
"name": "behat/mink",
@ -2916,67 +2916,6 @@
"homepage": "https://symfony.com",
"time": "2015-08-31 16:44:53"
},
{
"name": "symfony/translation",
"version": "v2.7.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/Translation.git",
"reference": "485877661835e188cd78345c6d4eef1290d17571"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Translation/zipball/485877661835e188cd78345c6d4eef1290d17571",
"reference": "485877661835e188cd78345c6d4eef1290d17571",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"conflict": {
"symfony/config": "<2.7"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "~2.7",
"symfony/intl": "~2.4",
"symfony/phpunit-bridge": "~2.7",
"symfony/yaml": "~2.2"
},
"suggest": {
"psr/log": "To use logging capability in translator",
"symfony/config": "",
"symfony/yaml": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Translation\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Translation Component",
"homepage": "https://symfony.com",
"time": "2015-09-06 08:36:38"
},
{
"name": "symfony/validator",
"version": "v2.7.4",

View File

@ -7,7 +7,7 @@
namespace Drupal\Core\TypedData\Validation;
use Symfony\Component\Translation\TranslatorInterface;
use Drupal\Core\Validation\TranslatorInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
@ -68,7 +68,7 @@ class ConstraintViolationBuilder implements ConstraintViolationBuilderInterface
/**
* The translator.
*
* @var \Symfony\Component\Translation\TranslatorInterface
* @var \Drupal\Core\Validation\TranslatorInterface
*/
protected $translator;
@ -117,7 +117,7 @@ class ConstraintViolationBuilder implements ConstraintViolationBuilderInterface
* The property string.
* @param mixed $invalidValue
* The invalid value.
* @param \Symfony\Component\Translation\TranslatorInterface $translator
* @param \Drupal\Core\Validation\TranslatorInterface $translator
* The translator.
* @param null $translationDomain
* (optional) The translation domain.

View File

@ -7,7 +7,7 @@
namespace Drupal\Core\TypedData\Validation;
use Symfony\Component\Translation\TranslatorInterface;
use Drupal\Core\Validation\TranslatorInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
@ -38,7 +38,7 @@ class ExecutionContext implements ExecutionContextInterface {
protected $root;
/**
* @var \Symfony\Component\Translation\TranslatorInterface
* @var \Drupal\Core\Validation\TranslatorInterface
*/
protected $translator;
@ -117,7 +117,7 @@ class ExecutionContext implements ExecutionContextInterface {
* The validator.
* @param mixed $root
* The root.
* @param \Symfony\Component\Translation\TranslatorInterface $translator
* @param \Drupal\Core\Validation\TranslatorInterface $translator
* The translator.
* @param string $translationDomain
* (optional) The translation domain.

View File

@ -7,7 +7,7 @@
namespace Drupal\Core\TypedData\Validation;
use Symfony\Component\Translation\TranslatorInterface;
use Drupal\Core\Validation\TranslatorInterface;
use Symfony\Component\Validator\Context\ExecutionContextFactoryInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
@ -21,7 +21,7 @@ use Symfony\Component\Validator\Validator\ValidatorInterface;
class ExecutionContextFactory implements ExecutionContextFactoryInterface {
/**
* @var TranslatorInterface
* @var \Drupal\Core\Validation\TranslatorInterface
*/
protected $translator;
@ -33,7 +33,7 @@ class ExecutionContextFactory implements ExecutionContextFactoryInterface {
/**
* Constructs a new ExecutionContextFactory instance.
*
* @param \Symfony\Component\Translation\TranslatorInterface $translator
* @param \Drupal\Core\Validation\TranslatorInterface $translator
* The translator instance.
* @param string $translationDomain
* (optional) The translation domain.

View File

@ -7,8 +7,6 @@
namespace Drupal\Core\Validation;
use Symfony\Component\Translation\TranslatorInterface;
/**
* Translates strings using Drupal's translation system.
*

View File

@ -0,0 +1,24 @@
<?php
/**
* @file
* Contains \Drupal\Core\Validation\TranslatorInterface.
*/
namespace Drupal\Core\Validation;
use Symfony\Component\Translation\TranslatorInterface as SymfonyTranslatorInterface;
/**
* Defines an interface used in validation.
*
* This extends the interface used by the Symfony validator in order to indicate
* that the Drupal code is actually independent from the Symfony translation
* component.
*
* @see https://github.com/symfony/symfony/pull/6189
* @see https://github.com/symfony/symfony/issues/15714
*/
interface TranslatorInterface extends SymfonyTranslatorInterface {
}

View File

@ -0,0 +1,7 @@
Symfony component interfaces included as part of the Drupal codebase are used to
provide Drupal-specific implementations of Symfony subsystems that are not
available as standalone components and thus cannot be cherry-picked.
A composer.json "replace" entry is used in order to bypass the composer.json
dependency onto the Symfony components originally including the interfaces
provided here.

View File

@ -16,10 +16,8 @@ use Drupal\Core\TypedData\Validation\ExecutionContextFactory;
use Drupal\Core\TypedData\Validation\RecursiveValidator;
use Drupal\Core\Validation\ConstraintManager;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\Translation\IdentityTranslator;
use Symfony\Component\Validator\ConstraintValidatorFactory;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Symfony\Component\Validator\DefaultTranslator;
/**
* @coversDefaultClass \Drupal\Core\TypedData\Validation\RecursiveContextualValidator
@ -82,7 +80,12 @@ class RecursiveContextualValidatorTest extends UnitTestCase {
$container->set('typed_data_manager', $this->typedDataManager);
\Drupal::setContainer($container);
$translator = new IdentityTranslator();
$translator = $this->getMock('Drupal\Core\Validation\TranslatorInterface');
$translator->expects($this->any())
->method('trans')
->willReturnCallback(function($id) {
return $id;
});
$this->contextFactory = new ExecutionContextFactory($translator);
$this->validatorFactory = new ConstraintValidatorFactory();
$this->recursiveValidator = new RecursiveValidator($this->contextFactory, $this->validatorFactory, $this->typedDataManager);

View File

@ -12,7 +12,7 @@ return array(
'Zend\\Diactoros\\' => array($vendorDir . '/zendframework/zend-diactoros/src'),
'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'),
'Symfony\\Component\\Validator\\' => array($vendorDir . '/symfony/validator'),
'Symfony\\Component\\Translation\\' => array($vendorDir . '/symfony/translation'),
'Symfony\\Component\\Translation\\' => array($baseDir . '/lib/Symfony/Component/Translation'),
'Symfony\\Component\\Serializer\\' => array($vendorDir . '/symfony/serializer'),
'Symfony\\Component\\Routing\\' => array($vendorDir . '/symfony/routing'),
'Symfony\\Component\\Process\\' => array($vendorDir . '/symfony/process'),

View File

@ -3130,69 +3130,6 @@
"description": "Symfony Serializer Component",
"homepage": "https://symfony.com"
},
{
"name": "symfony/translation",
"version": "v2.7.4",
"version_normalized": "2.7.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/Translation.git",
"reference": "485877661835e188cd78345c6d4eef1290d17571"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Translation/zipball/485877661835e188cd78345c6d4eef1290d17571",
"reference": "485877661835e188cd78345c6d4eef1290d17571",
"shasum": ""
},
"require": {
"php": ">=5.3.9"
},
"conflict": {
"symfony/config": "<2.7"
},
"require-dev": {
"psr/log": "~1.0",
"symfony/config": "~2.7",
"symfony/intl": "~2.4",
"symfony/phpunit-bridge": "~2.7",
"symfony/yaml": "~2.2"
},
"suggest": {
"psr/log": "To use logging capability in translator",
"symfony/config": "",
"symfony/yaml": ""
},
"time": "2015-09-06 08:36:38",
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.7-dev"
}
},
"installation-source": "dist",
"autoload": {
"psr-4": {
"Symfony\\Component\\Translation\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Translation Component",
"homepage": "https://symfony.com"
},
{
"name": "symfony/validator",
"version": "v2.7.4",

View File

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

View File

@ -1,47 +0,0 @@
CHANGELOG
=========
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

View File

@ -1,146 +0,0 @@
<?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\MessageCatalogue;
use Symfony\Component\Translation\MessageCatalogueInterface;
/**
* Base catalogues binary operation class.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*/
abstract class AbstractOperation implements OperationInterface
{
/**
* @var MessageCatalogueInterface
*/
protected $source;
/**
* @var MessageCatalogueInterface
*/
protected $target;
/**
* @var MessageCatalogue
*/
protected $result;
/**
* @var null|array
*/
private $domains;
/**
* @var array
*/
protected $messages;
/**
* @param MessageCatalogueInterface $source
* @param MessageCatalogueInterface $target
*
* @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->domains = null;
$this->messages = array();
}
/**
* {@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($domain)
{
if (!in_array($domain, $this->getDomains())) {
throw new \InvalidArgumentException(sprintf('Invalid domain: %s.', $domain));
}
if (!isset($this->messages[$domain]['all'])) {
$this->processDomain($domain);
}
return $this->messages[$domain]['all'];
}
/**
* {@inheritdoc}
*/
public function getNewMessages($domain)
{
if (!in_array($domain, $this->getDomains())) {
throw new \InvalidArgumentException(sprintf('Invalid domain: %s.', $domain));
}
if (!isset($this->messages[$domain]['new'])) {
$this->processDomain($domain);
}
return $this->messages[$domain]['new'];
}
/**
* {@inheritdoc}
*/
public function getObsoleteMessages($domain)
{
if (!in_array($domain, $this->getDomains())) {
throw new \InvalidArgumentException(sprintf('Invalid domain: %s.', $domain));
}
if (!isset($this->messages[$domain]['obsolete'])) {
$this->processDomain($domain);
}
return $this->messages[$domain]['obsolete'];
}
/**
* {@inheritdoc}
*/
public function getResult()
{
foreach ($this->getDomains() as $domain) {
if (!isset($this->messages[$domain])) {
$this->processDomain($domain);
}
}
return $this->result;
}
/**
* @param string $domain
*/
abstract protected function processDomain($domain);
}

View File

@ -1,55 +0,0 @@
<?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;
/**
* Diff operation between two catalogues.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*/
class DiffOperation extends AbstractOperation
{
/**
* {@inheritdoc}
*/
protected function processDomain($domain)
{
$this->messages[$domain] = array(
'all' => array(),
'new' => array(),
'obsolete' => array(),
);
foreach ($this->source->all($domain) as $id => $message) {
if ($this->target->has($id, $domain)) {
$this->messages[$domain]['all'][$id] = $message;
$this->result->add(array($id => $message), $domain);
if (null !== $keyMetadata = $this->source->getMetadata($id, $domain)) {
$this->result->setMetadata($id, $keyMetadata, $domain);
}
} 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;
$this->result->add(array($id => $message), $domain);
if (null !== $keyMetadata = $this->target->getMetadata($id, $domain)) {
$this->result->setMetadata($id, $keyMetadata, $domain);
}
}
}
}
}

View File

@ -1,51 +0,0 @@
<?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;
/**
* Merge operation between two catalogues.
*
* @author Jean-François Simon <contact@jfsimon.fr>
*/
class MergeOperation extends AbstractOperation
{
/**
* {@inheritdoc}
*/
protected function processDomain($domain)
{
$this->messages[$domain] = array(
'all' => array(),
'new' => array(),
'obsolete' => array(),
);
foreach ($this->source->all($domain) as $id => $message) {
$this->messages[$domain]['all'][$id] = $message;
$this->result->add(array($id => $message), $domain);
if (null !== $keyMetadata = $this->source->getMetadata($id, $domain)) {
$this->result->setMetadata($id, $keyMetadata, $domain);
}
}
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;
$this->result->add(array($id => $message), $domain);
if (null !== $keyMetadata = $this->target->getMetadata($id, $domain)) {
$this->result->setMetadata($id, $keyMetadata, $domain);
}
}
}
}
}

View File

@ -1,63 +0,0 @@
<?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).
*
* @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 after operation.
*
* @param string $domain
*
* @return array
*/
public function getMessages($domain);
/**
* Returns new messages after operation.
*
* @param string $domain
*
* @return array
*/
public function getNewMessages($domain);
/**
* Returns obsolete messages after operation.
*
* @param string $domain
*
* @return array
*/
public function getObsoleteMessages($domain);
/**
* Returns resulting catalogue.
*
* @return MessageCatalogueInterface
*/
public function getResult();
}

View File

@ -1,145 +0,0 @@
<?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;
/**
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*/
class TranslationDataCollector extends DataCollector implements LateDataCollectorInterface
{
/**
* @var DataCollectorTranslator
*/
private $translator;
/**
* @param DataCollectorTranslator $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;
}
/**
* {@inheritdoc}
*/
public function collect(Request $request, Response $response, \Exception $exception = null)
{
}
/**
* @return array
*/
public function getMessages()
{
return isset($this->data['messages']) ? $this->data['messages'] : array();
}
/**
* @return int
*/
public function getCountMissings()
{
return isset($this->data[DataCollectorTranslator::MESSAGE_MISSING]) ? $this->data[DataCollectorTranslator::MESSAGE_MISSING] : 0;
}
/**
* @return int
*/
public function getCountFallbacks()
{
return isset($this->data[DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK]) ? $this->data[DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK] : 0;
}
/**
* @return int
*/
public function getCountDefines()
{
return isset($this->data[DataCollectorTranslator::MESSAGE_DEFINED]) ? $this->data[DataCollectorTranslator::MESSAGE_DEFINED] : 0;
}
/**
* {@inheritdoc}
*/
public function getName()
{
return 'translation';
}
private function sanitizeCollectedMessages($messages)
{
$result = array();
foreach ($messages as $key => $message) {
$messageId = $message['locale'].$message['domain'].$message['id'];
if (!isset($result[$messageId])) {
$message['count'] = 1;
$messages[$key]['translation'] = $this->sanitizeString($message['translation']);
$result[$messageId] = $message;
} else {
++$result[$messageId]['count'];
}
unset($messages[$key]);
}
return $result;
}
private function computeCount($messages)
{
$count = array(
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, $length = 80)
{
$string = trim(preg_replace('/\s+/', ' ', $string));
if (function_exists('mb_strlen') && false !== $encoding = mb_detect_encoding($string)) {
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;
}
}

View File

@ -1,152 +0,0 @@
<?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;
/**
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*/
class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInterface
{
const MESSAGE_DEFINED = 0;
const MESSAGE_MISSING = 1;
const MESSAGE_EQUALS_FALLBACK = 2;
/**
* @var TranslatorInterface|TranslatorBagInterface
*/
private $translator;
/**
* @var array
*/
private $messages = array();
/**
* @param TranslatorInterface $translator The translator must implement TranslatorBagInterface
*/
public function __construct(TranslatorInterface $translator)
{
if (!$translator instanceof TranslatorBagInterface) {
throw new \InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface and TranslatorBagInterface.', get_class($translator)));
}
$this->translator = $translator;
}
/**
* {@inheritdoc}
*/
public function trans($id, array $parameters = array(), $domain = null, $locale = null)
{
$trans = $this->translator->trans($id, $parameters, $domain, $locale);
$this->collectMessage($locale, $domain, $id, $trans);
return $trans;
}
/**
* {@inheritdoc}
*/
public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null)
{
$trans = $this->translator->transChoice($id, $number, $parameters, $domain, $locale);
$this->collectMessage($locale, $domain, $id, $trans);
return $trans;
}
/**
* {@inheritdoc}
*
* @api
*/
public function setLocale($locale)
{
$this->translator->setLocale($locale);
}
/**
* {@inheritdoc}
*
* @api
*/
public function getLocale()
{
return $this->translator->getLocale();
}
/**
* {@inheritdoc}
*/
public function getCatalogue($locale = null)
{
return $this->translator->getCatalogue($locale);
}
/**
* Passes through all unknown calls onto the translator object.
*/
public function __call($method, $args)
{
return call_user_func_array(array($this->translator, $method), $args);
}
/**
* @return array
*/
public function getCollectedMessages()
{
return $this->messages;
}
/**
* @param string|null $locale
* @param string|null $domain
* @param string $id
* @param string $trans
*/
private function collectMessage($locale, $domain, $id, $translation)
{
if (null === $domain) {
$domain = 'messages';
}
$id = (string) $id;
$catalogue = $this->translator->getCatalogue($locale);
$locale = $catalogue->getLocale();
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)) {
$locale = $fallbackCatalogue->getLocale();
break;
}
$fallbackCatalogue = $fallbackCatalogue->getFallBackCatalogue();
}
} else {
$state = self::MESSAGE_MISSING;
}
$this->messages[] = array(
'locale' => $locale,
'domain' => $domain,
'id' => $id,
'translation' => $translation,
'state' => $state,
);
}
}

View File

@ -1,63 +0,0 @@
<?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 format(MessageCatalogue $messages, $domain = 'messages')
{
$handle = fopen('php://memory', 'rb+');
foreach ($messages->all($domain) as $source => $target) {
fputcsv($handle, array($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.
*
* @param string $delimiter delimiter character
* @param string $enclosure enclosure character
*/
public function setCsvControl($delimiter = ';', $enclosure = '"')
{
$this->delimiter = $delimiter;
$this->enclosure = $enclosure;
}
/**
* {@inheritdoc}
*/
protected function getExtension()
{
return 'csv';
}
}

View File

@ -1,31 +0,0 @@
<?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 MessageCatalogue $messages The message catalogue
* @param array $options Options that are used by the dumper
*/
public function dump(MessageCatalogue $messages, $options = array());
}

View File

@ -1,122 +0,0 @@
<?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;
/**
* FileDumper is an implementation of DumperInterface that dump a message catalogue to file(s).
* Performs backup of already existing files.
*
* 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%';
/**
* Make file backup before the dump.
*
* @var bool
*/
private $backup = true;
/**
* Sets the template for the relative paths to files.
*
* @param string $relativePathTemplate A template for the relative paths to files
*/
public function setRelativePathTemplate($relativePathTemplate)
{
$this->relativePathTemplate = $relativePathTemplate;
}
/**
* Sets backup flag.
*
* @param bool
*/
public function setBackup($backup)
{
$this->backup = $backup;
}
/**
* {@inheritdoc}
*/
public function dump(MessageCatalogue $messages, $options = array())
{
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) {
// backup
$fullpath = $options['path'].'/'.$this->getRelativePath($domain, $messages->getLocale());
if (file_exists($fullpath)) {
if ($this->backup) {
copy($fullpath, $fullpath.'~');
}
} else {
$directory = dirname($fullpath);
if (!file_exists($directory) && !@mkdir($directory, 0777, true)) {
throw new \RuntimeException(sprintf('Unable to create directory "%s".', $directory));
}
}
// save file
file_put_contents($fullpath, $this->format($messages, $domain));
}
}
/**
* Transforms a domain of a message catalogue to its string representation.
*
* @param MessageCatalogue $messages
* @param string $domain
*
* @return string representation
*/
abstract protected function format(MessageCatalogue $messages, $domain);
/**
* Gets the file extension of the dumper.
*
* @return string file extension
*/
abstract protected function getExtension();
/**
* Gets the relative file path using the template.
*
* @param string $domain The domain
* @param string $locale The locale
*
* @return string The relative file path
*/
private function getRelativePath($domain, $locale)
{
return strtr($this->relativePathTemplate, array(
'%domain%' => $domain,
'%locale%' => $locale,
'%extension%' => $this->getExtension(),
));
}
}

View File

@ -1,112 +0,0 @@
<?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 format(MessageCatalogue $messages, $domain = 'messages')
{
$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))
.$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), // 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
);
$output = $header
.$root
.$data;
return $output;
}
private function writePadding($data)
{
$padding = strlen($data) % 4;
if ($padding) {
return str_repeat("\xAA", 4 - $padding);
}
}
private function getPosition($data)
{
$position = (strlen($data) + 28) / 4;
return $position;
}
/**
* {@inheritdoc}
*/
protected function getExtension()
{
return 'res';
}
}

View File

@ -1,45 +0,0 @@
<?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 format(MessageCatalogue $messages, $domain = 'messages')
{
$output = '';
foreach ($messages->all($domain) as $source => $target) {
$escapeTarget = str_replace('"', '\"', $target);
$output .= $source.'="'.$escapeTarget."\"\n";
}
return $output;
}
/**
* {@inheritdoc}
*/
protected function getExtension()
{
return 'ini';
}
}

View File

@ -1,38 +0,0 @@
<?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 format(MessageCatalogue $messages, $domain = 'messages')
{
return json_encode($messages->all($domain), defined('JSON_PRETTY_PRINT') ? JSON_PRETTY_PRINT : 0);
}
/**
* {@inheritdoc}
*/
protected function getExtension()
{
return 'json';
}
}

View File

@ -1,82 +0,0 @@
<?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;
use Symfony\Component\Translation\Loader\MoFileLoader;
/**
* MoFileDumper generates a gettext formatted string representation of a message catalogue.
*
* @author Stealth35
*/
class MoFileDumper extends FileDumper
{
/**
* {@inheritdoc}
*/
public function format(MessageCatalogue $messages, $domain = 'messages')
{
$output = $sources = $targets = $sourceOffsets = $targetOffsets = '';
$offsets = array();
$size = 0;
foreach ($messages->all($domain) as $source => $target) {
$offsets[] = array_map('strlen', array($sources, $source, $targets, $target));
$sources .= "\0".$source;
$targets .= "\0".$target;
++$size;
}
$header = array(
'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(array($this, 'writeLong'), $header))
.$sourceOffsets
.$targetOffsets
.$sources
.$targets
;
return $output;
}
/**
* {@inheritdoc}
*/
protected function getExtension()
{
return 'mo';
}
private function writeLong($str)
{
return pack('V*', $str);
}
}

View File

@ -1,40 +0,0 @@
<?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}
*/
protected function format(MessageCatalogue $messages, $domain)
{
$output = "<?php\n\nreturn ".var_export($messages->all($domain), true).";\n";
return $output;
}
/**
* {@inheritdoc}
*/
protected function getExtension()
{
return 'php';
}
}

View File

@ -1,61 +0,0 @@
<?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 format(MessageCatalogue $messages, $domain = 'messages')
{
$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;
}
$output .= sprintf('msgid "%s"'."\n", $this->escape($source));
$output .= sprintf('msgstr "%s"', $this->escape($target));
}
return $output;
}
/**
* {@inheritdoc}
*/
protected function getExtension()
{
return 'po';
}
private function escape($str)
{
return addcslashes($str, "\0..\37\42\134");
}
}

View File

@ -1,50 +0,0 @@
<?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 format(MessageCatalogue $messages, $domain)
{
$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'));
$message->appendChild($dom->createElement('source', $source));
$message->appendChild($dom->createElement('translation', $target));
}
return $dom->saveXML();
}
/**
* {@inheritdoc}
*/
protected function getExtension()
{
return 'ts';
}
}

View File

@ -1,109 +0,0 @@
<?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;
/**
* XliffFileDumper generates xliff files from a message catalogue.
*
* @author Michel Salib <michelsalib@hotmail.com>
*/
class XliffFileDumper extends FileDumper
{
/**
* @var string
*/
private $defaultLocale;
/**
* {@inheritdoc}
*/
public function dump(MessageCatalogue $messages, $options = array())
{
if (array_key_exists('default_locale', $options)) {
$this->defaultLocale = $options['default_locale'];
} else {
$this->defaultLocale = \Locale::getDefault();
}
parent::dump($messages, $options);
}
/**
* {@inheritdoc}
*/
protected function format(MessageCatalogue $messages, $domain)
{
$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('_', '-', $this->defaultLocale));
$xliffFile->setAttribute('target-language', str_replace('_', '-', $messages->getLocale()));
$xliffFile->setAttribute('datatype', 'plaintext');
$xliffFile->setAttribute('original', 'file.ext');
$xliffBody = $xliffFile->appendChild($dom->createElement('body'));
foreach ($messages->all($domain) as $source => $target) {
$translation = $dom->createElement('trans-unit');
$translation->setAttribute('id', md5($source));
$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);
$t = $translation->appendChild($dom->createElement('target'));
$t->appendChild($text);
$metadata = $messages->getMetadata($source, $domain);
if (null !== $metadata && array_key_exists('notes', $metadata) && is_array($metadata['notes'])) {
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();
}
/**
* {@inheritdoc}
*/
protected function getExtension()
{
return 'xlf';
}
}

View File

@ -1,43 +0,0 @@
<?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;
use Symfony\Component\Yaml\Yaml;
/**
* YamlFileDumper generates yaml files from a message catalogue.
*
* @author Michel Salib <michelsalib@hotmail.com>
*/
class YamlFileDumper extends FileDumper
{
/**
* {@inheritdoc}
*/
protected function format(MessageCatalogue $messages, $domain)
{
if (!class_exists('Symfony\Component\Yaml\Yaml')) {
throw new \LogicException('Dumping translations in the YAML format requires the Symfony Yaml component.');
}
return Yaml::dump($messages->all($domain));
}
/**
* {@inheritdoc}
*/
protected function getExtension()
{
return 'yml';
}
}

View File

@ -1,23 +0,0 @@
<?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>
*
* @api
*/
interface ExceptionInterface
{
}

View File

@ -1,23 +0,0 @@
<?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>
*
* @api
*/
class InvalidResourceException extends \InvalidArgumentException implements ExceptionInterface
{
}

View File

@ -1,23 +0,0 @@
<?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>
*
* @api
*/
class NotFoundResourceException extends \InvalidArgumentException implements ExceptionInterface
{
}

View File

@ -1,83 +0,0 @@
<?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;
/**
* Base class used by classes that extract translation messages from files.
*
* @author Marcos D. Sánchez <marcosdsanchez@gmail.com>
*/
abstract class AbstractFileExtractor
{
/**
* @param string|array $resource files, a file or a directory
*
* @return array
*/
protected function extractFiles($resource)
{
if (is_array($resource) || $resource instanceof \Traversable) {
$files = array();
foreach ($resource as $file) {
if ($this->canBeExtracted($file)) {
$files[] = $this->toSplFileInfo($file);
}
}
} elseif (is_file($resource)) {
$files = $this->canBeExtracted($resource) ? array($this->toSplFileInfo($resource)) : array();
} else {
$files = $this->extractFromDirectory($resource);
}
return $files;
}
/**
* @param string $file
*
* @return \SplFileInfo
*/
private function toSplFileInfo($file)
{
return ($file instanceof \SplFileInfo) ? $file : new \SplFileInfo($file);
}
/**
* @param string $file
*
* @throws \InvalidArgumentException
*
* @return bool
*/
protected function isFile($file)
{
if (!is_file($file)) {
throw new \InvalidArgumentException(sprintf('The "%s" file does not exist.', $file));
}
return true;
}
/**
* @param string $file
*
* @return bool
*/
abstract protected function canBeExtracted($file);
/**
* @param string|array $resource files, a file or a directory
*
* @return array files to be extracted
*/
abstract protected function extractFromDirectory($resource);
}

View File

@ -1,60 +0,0 @@
<?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 = array();
/**
* Adds a loader to the translation extractor.
*
* @param string $format The format of the loader
* @param ExtractorInterface $extractor The loader
*/
public function addExtractor($format, ExtractorInterface $extractor)
{
$this->extractors[$format] = $extractor;
}
/**
* {@inheritdoc}
*/
public function setPrefix($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);
}
}
}

View File

@ -1,38 +0,0 @@
<?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|array $resource files, a file or a directory
* @param MessageCatalogue $catalogue The catalogue
*/
public function extract($resource, MessageCatalogue $catalogue);
/**
* Sets the prefix that should be used for new found messages.
*
* @param string $prefix The prefix
*/
public function setPrefix($prefix);
}

View File

@ -1,77 +0,0 @@
<?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;
/**
* IdentityTranslator does not translate anything.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class IdentityTranslator implements TranslatorInterface
{
private $selector;
private $locale;
/**
* Constructor.
*
* @param MessageSelector|null $selector The message selector for pluralization
*
* @api
*/
public function __construct(MessageSelector $selector = null)
{
$this->selector = $selector ?: new MessageSelector();
}
/**
* {@inheritdoc}
*
* @api
*/
public function setLocale($locale)
{
$this->locale = $locale;
}
/**
* {@inheritdoc}
*
* @api
*/
public function getLocale()
{
return $this->locale ?: \Locale::getDefault();
}
/**
* {@inheritdoc}
*
* @api
*/
public function trans($id, array $parameters = array(), $domain = null, $locale = null)
{
return strtr((string) $id, $parameters);
}
/**
* {@inheritdoc}
*
* @api
*/
public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null)
{
return strtr($this->selector->choose((string) $id, (int) $number, $locale ?: $this->getLocale()), $parameters);
}
}

View File

@ -1,107 +0,0 @@
<?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;
/**
* Tests if a given number belongs to a given math interval.
*
* 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.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @see http://en.wikipedia.org/wiki/Interval_%28mathematics%29#The_ISO_notation
*/
class Interval
{
/**
* Tests if the given number is in the math interval.
*
* @param int $number A number
* @param string $interval An interval
*
* @return bool
*
* @throws \InvalidArgumentException
*/
public static function test($number, $interval)
{
$interval = trim($interval);
if (!preg_match('/^'.self::getIntervalRegexp().'$/x', $interval, $matches)) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid interval.', $interval));
}
if ($matches[1]) {
foreach (explode(',', $matches[2]) as $n) {
if ($number == $n) {
return true;
}
}
} else {
$leftNumber = self::convertNumber($matches['left']);
$rightNumber = self::convertNumber($matches['right']);
return
('[' === $matches['left_delimiter'] ? $number >= $leftNumber : $number > $leftNumber)
&& (']' === $matches['right_delimiter'] ? $number <= $rightNumber : $number < $rightNumber)
;
}
return false;
}
/**
* Returns a Regexp that matches valid intervals.
*
* @return string A Regexp (without the delimiters)
*/
public static function getIntervalRegexp()
{
return <<<EOF
({\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>[\[\]])
EOF;
}
private static function convertNumber($number)
{
if ('-Inf' === $number) {
return log(0);
} elseif ('+Inf' === $number || 'Inf' === $number) {
return -log(0);
}
return (float) $number;
}
}

View File

@ -1,70 +0,0 @@
<?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>
*
* @api
*/
class ArrayLoader implements LoaderInterface
{
/**
* {@inheritdoc}
*
* @api
*/
public function load($resource, $locale, $domain = 'messages')
{
$this->flatten($resource);
$catalogue = new MessageCatalogue($locale);
$catalogue->add($resource, $domain);
return $catalogue;
}
/**
* Flattens an nested array of translations.
*
* The scheme used is:
* 'key' => array('key2' => array('key3' => 'value'))
* Becomes:
* 'key.key2.key3' => 'value'
*
* This function takes an array by reference and will modify it
*
* @param array &$messages The array that will be flattened
* @param array $subnode Current subnode being parsed, used internally for recursive calls
* @param string $path Current path being parsed, used internally for recursive calls
*/
private function flatten(array &$messages, array $subnode = null, $path = null)
{
if (null === $subnode) {
$subnode = &$messages;
}
foreach ($subnode as $key => $value) {
if (is_array($value)) {
$nodePath = $path ? $path.'.'.$key : $key;
$this->flatten($messages, $value, $nodePath);
if (null === $path) {
unset($messages[$key]);
}
} elseif (null !== $path) {
$messages[$path.'.'.$key] = $value;
}
}
}
}

View File

@ -1,95 +0,0 @@
<?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\Config\Resource\FileResource;
/**
* CsvFileLoader loads translations from CSV files.
*
* @author Saša Stamenković <umpirsky@gmail.com>
*
* @api
*/
class CsvFileLoader extends ArrayLoader
{
private $delimiter = ';';
private $enclosure = '"';
private $escape = '\\';
/**
* {@inheritdoc}
*
* @api
*/
public function load($resource, $locale, $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 = array();
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 (substr($data[0], 0, 1) === '#') {
continue;
}
if (!isset($data[1])) {
continue;
}
if (count($data) == 2) {
$messages[$data[0]] = $data[1];
} else {
continue;
}
}
$catalogue = parent::load($messages, $locale, $domain);
if (class_exists('Symfony\Component\Config\Resource\FileResource')) {
$catalogue->addResource(new FileResource($resource));
}
return $catalogue;
}
/**
* Sets the delimiter, enclosure, and escape character for CSV.
*
* @param string $delimiter delimiter character
* @param string $enclosure enclosure character
* @param string $escape escape character
*/
public function setCsvControl($delimiter = ';', $enclosure = '"', $escape = '\\')
{
$this->delimiter = $delimiter;
$this->enclosure = $enclosure;
$this->escape = $escape;
}
}

View File

@ -1,62 +0,0 @@
<?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;
use Symfony\Component\Translation\Exception\InvalidResourceException;
use Symfony\Component\Translation\Exception\NotFoundResourceException;
use Symfony\Component\Config\Resource\FileResource;
/**
* IcuResFileLoader loads translations from a resource bundle.
*
* @author stealth35
*/
class IcuDatFileLoader extends IcuResFileLoader
{
/**
* {@inheritdoc}
*/
public function load($resource, $locale, $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) {
// HHVM compatibility: constructor throws on invalid resource
$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('Symfony\Component\Config\Resource\FileResource')) {
$catalogue->addResource(new FileResource($resource.'.dat'));
}
return $catalogue;
}
}

View File

@ -1,92 +0,0 @@
<?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;
use Symfony\Component\Translation\Exception\InvalidResourceException;
use Symfony\Component\Translation\Exception\NotFoundResourceException;
use Symfony\Component\Config\Resource\DirectoryResource;
/**
* IcuResFileLoader loads translations from a resource bundle.
*
* @author stealth35
*/
class IcuResFileLoader implements LoaderInterface
{
/**
* {@inheritdoc}
*/
public function load($resource, $locale, $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) {
// HHVM compatibility: constructor throws on invalid resource
$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('Symfony\Component\Config\Resource\DirectoryResource')) {
$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 = array(), $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;
}
}

View File

@ -1,48 +0,0 @@
<?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\Config\Resource\FileResource;
/**
* IniFileLoader loads translations from an ini file.
*
* @author stealth35
*/
class IniFileLoader extends ArrayLoader
{
/**
* {@inheritdoc}
*/
public function load($resource, $locale, $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 = parse_ini_file($resource, true);
$catalogue = parent::load($messages, $locale, $domain);
if (class_exists('Symfony\Component\Config\Resource\FileResource')) {
$catalogue->addResource(new FileResource($resource));
}
return $catalogue;
}
}

View File

@ -1,81 +0,0 @@
<?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\Config\Resource\FileResource;
/**
* JsonFileLoader loads translations from an json file.
*
* @author singles
*/
class JsonFileLoader extends ArrayLoader
{
/**
* {@inheritdoc}
*/
public function load($resource, $locale, $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 = array();
if ($data = file_get_contents($resource)) {
$messages = json_decode($data, true);
if (0 < $errorCode = json_last_error()) {
throw new InvalidResourceException(sprintf('Error parsing JSON - %s', $this->getJSONErrorMessage($errorCode)));
}
}
if (null === $messages) {
$messages = array();
}
$catalogue = parent::load($messages, $locale, $domain);
$catalogue->addResource(new FileResource($resource));
return $catalogue;
}
/**
* Translates JSON_ERROR_* constant into meaningful message.
*
* @param int $errorCode Error code returned by json_last_error() call
*
* @return string Message string
*/
private function getJSONErrorMessage($errorCode)
{
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';
}
}
}

View File

@ -1,42 +0,0 @@
<?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;
use Symfony\Component\Translation\Exception\InvalidResourceException;
use Symfony\Component\Translation\Exception\NotFoundResourceException;
/**
* LoaderInterface is the interface implemented by all translation loaders.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
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
*
* @api
*
* @throws NotFoundResourceException when the resource cannot be found
* @throws InvalidResourceException when the resource cannot be loaded
*/
public function load($resource, $locale, $domain = 'messages');
}

View File

@ -1,191 +0,0 @@
<?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\Config\Resource\FileResource;
/**
* @copyright Copyright (c) 2010, Union of RAD http://union-of-rad.org (http://lithify.me/)
*/
class MoFileLoader extends ArrayLoader
{
/**
* Magic used for validating the format of a MO file as well as
* detecting if the machine used to create that file was little endian.
*
* @var float
*/
const MO_LITTLE_ENDIAN_MAGIC = 0x950412de;
/**
* Magic used for validating the format of a MO file as well as
* detecting if the machine used to create that file was big endian.
*
* @var float
*/
const MO_BIG_ENDIAN_MAGIC = 0xde120495;
/**
* The size of the header of a MO file in bytes.
*
* @var int Number of bytes.
*/
const MO_HEADER_SIZE = 28;
public function load($resource, $locale, $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->parse($resource);
// empty file
if (null === $messages) {
$messages = array();
}
// not an array
if (!is_array($messages)) {
throw new InvalidResourceException(sprintf('The file "%s" must contain a valid mo file.', $resource));
}
$catalogue = parent::load($messages, $locale, $domain);
if (class_exists('Symfony\Component\Config\Resource\FileResource')) {
$catalogue->addResource(new FileResource($resource));
}
return $catalogue;
}
/**
* Parses machine object (MO) format, independent of the machine's endian it
* was created on. Both 32bit and 64bit systems are supported.
*
* @param resource $resource
*
* @return array
*
* @throws InvalidResourceException If stream content has an invalid format.
*/
private function parse($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 ($magic == self::MO_LITTLE_ENDIAN_MAGIC) {
$isBigEndian = false;
} elseif ($magic == self::MO_BIG_ENDIAN_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 = array();
for ($i = 0; $i < $count; ++$i) {
$singularId = $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 (strpos($singularId, "\000") !== false) {
list($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 (strpos($translated, "\000") !== false) {
$translated = explode("\000", $translated);
}
$ids = array('singular' => $singularId, 'plural' => $pluralId);
$item = compact('ids', 'translated');
if (is_array($item['translated'])) {
$messages[$item['ids']['singular']] = stripcslashes($item['translated'][0]);
if (isset($item['ids']['plural'])) {
$plurals = array();
foreach ($item['translated'] as $plural => $translated) {
$plurals[] = sprintf('{%d} %s', $plural, $translated);
}
$messages[$item['ids']['plural']] = stripcslashes(implode('|', $plurals));
}
} elseif (!empty($item['ids']['singular'])) {
$messages[$item['ids']['singular']] = stripcslashes($item['translated']);
}
}
fclose($stream);
return array_filter($messages);
}
/**
* Reads an unsigned long from stream respecting endianess.
*
* @param resource $stream
* @param bool $isBigEndian
*
* @return int
*/
private function readLong($stream, $isBigEndian)
{
$result = unpack($isBigEndian ? 'N1' : 'V1', fread($stream, 4));
$result = current($result);
return (int) substr($result, -8);
}
}

View File

@ -1,52 +0,0 @@
<?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\Config\Resource\FileResource;
/**
* PhpFileLoader loads translations from PHP files returning an array of translations.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class PhpFileLoader extends ArrayLoader
{
/**
* {@inheritdoc}
*
* @api
*/
public function load($resource, $locale, $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 = require $resource;
$catalogue = parent::load($messages, $locale, $domain);
if (class_exists('Symfony\Component\Config\Resource\FileResource')) {
$catalogue->addResource(new FileResource($resource));
}
return $catalogue;
}
}

View File

@ -1,180 +0,0 @@
<?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\Config\Resource\FileResource;
/**
* @copyright Copyright (c) 2010, Union of RAD http://union-of-rad.org (http://lithify.me/)
* @copyright Copyright (c) 2012, Clemens Tolboom
*/
class PoFileLoader extends ArrayLoader
{
public function load($resource, $locale, $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->parse($resource);
// empty file
if (null === $messages) {
$messages = array();
}
// not an array
if (!is_array($messages)) {
throw new InvalidResourceException(sprintf('The file "%s" must contain a valid po file.', $resource));
}
$catalogue = parent::load($messages, $locale, $domain);
if (class_exists('Symfony\Component\Config\Resource\FileResource')) {
$catalogue->addResource(new FileResource($resource));
}
return $catalogue;
}
/**
* Parses portable object (PO) format.
*
* From http://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.
*
* @param resource $resource
*
* @return array
*/
private function parse($resource)
{
$stream = fopen($resource, 'r');
$defaults = array(
'ids' => array(),
'translated' => null,
);
$messages = array();
$item = $defaults;
while ($line = fgets($stream)) {
$line = trim($line);
if ($line === '') {
// Whitespace indicated current item is done
$this->addMessage($messages, $item);
$item = $defaults;
} elseif (substr($line, 0, 7) === 'msgid "') {
// We start a new msg so save previous
// TODO: this fails when comments or contexts are added
$this->addMessage($messages, $item);
$item = $defaults;
$item['ids']['singular'] = substr($line, 7, -1);
} elseif (substr($line, 0, 8) === 'msgstr "') {
$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 (substr($line, 0, 14) === 'msgid_plural "') {
$item['ids']['plural'] = substr($line, 14, -1);
} elseif (substr($line, 0, 7) === 'msgstr[') {
$size = strpos($line, ']');
$item['translated'][(int) substr($line, 7, 1)] = substr($line, $size + 3, -1);
}
}
// save last item
$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.
*
* @param array $messages
* @param array $item
*/
private function addMessage(array &$messages, array $item)
{
if (is_array($item['translated'])) {
$messages[stripcslashes($item['ids']['singular'])] = stripcslashes($item['translated'][0]);
if (isset($item['ids']['plural'])) {
$plurals = $item['translated'];
// PO are by definition indexed so sort by index.
ksort($plurals);
// Make sure every index is filled.
end($plurals);
$count = key($plurals);
// Fill missing spots with '-'.
$empties = array_fill(0, $count + 1, '-');
$plurals += $empties;
ksort($plurals);
$messages[stripcslashes($item['ids']['plural'])] = stripcslashes(implode('|', $plurals));
}
} elseif (!empty($item['ids']['singular'])) {
$messages[stripcslashes($item['ids']['singular'])] = stripcslashes($item['translated']);
}
}
}

View File

@ -1,81 +0,0 @@
<?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\Util\XmlUtils;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Exception\InvalidResourceException;
use Symfony\Component\Translation\Exception\NotFoundResourceException;
use Symfony\Component\Config\Resource\FileResource;
/**
* QtFileLoader loads translations from QT Translations XML files.
*
* @author Benjamin Eberlei <kontakt@beberlei.de>
*
* @api
*/
class QtFileLoader implements LoaderInterface
{
/**
* {@inheritdoc}
*
* @api
*/
public function load($resource, $locale, $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));
}
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 ($nodes->length == 1) {
$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('Symfony\Component\Config\Resource\FileResource')) {
$catalogue->addResource(new FileResource($resource));
}
}
libxml_use_internal_errors($internalErrors);
return $catalogue;
}
}

View File

@ -1,188 +0,0 @@
<?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\Util\XmlUtils;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Exception\InvalidResourceException;
use Symfony\Component\Translation\Exception\NotFoundResourceException;
use Symfony\Component\Config\Resource\FileResource;
/**
* XliffFileLoader loads translations from XLIFF files.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class XliffFileLoader implements LoaderInterface
{
/**
* {@inheritdoc}
*
* @api
*/
public function load($resource, $locale, $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));
}
list($xml, $encoding) = $this->parseFile($resource);
$xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:1.2');
$catalogue = new MessageCatalogue($locale);
foreach ($xml->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) (isset($translation->target) ? $translation->target : $source), $encoding);
$catalogue->set((string) $source, $target, $domain);
if (isset($translation->note)) {
$notes = array();
foreach ($translation->note as $xmlNote) {
$noteAttributes = $xmlNote->attributes();
$note = array('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;
}
$catalogue->setMetadata((string) $source, array('notes' => $notes), $domain);
}
}
if (class_exists('Symfony\Component\Config\Resource\FileResource')) {
$catalogue->addResource(new FileResource($resource));
}
return $catalogue;
}
/**
* Convert a UTF8 string to the specified encoding.
*
* @param string $content String to decode
* @param string $encoding Target encoding
*
* @return string
*/
private function utf8ToCharset($content, $encoding = null)
{
if ('UTF-8' !== $encoding && !empty($encoding)) {
if (function_exists('mb_convert_encoding')) {
return mb_convert_encoding($content, $encoding, 'UTF-8');
}
if (function_exists('iconv')) {
return iconv('UTF-8', $encoding, $content);
}
throw new \RuntimeException('No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).');
}
return $content;
}
/**
* Validates and parses the given file into a SimpleXMLElement.
*
* @param string $file
*
* @throws \RuntimeException
*
* @return \SimpleXMLElement
*
* @throws InvalidResourceException
*/
private function parseFile($file)
{
try {
$dom = XmlUtils::loadFile($file);
} catch (\InvalidArgumentException $e) {
throw new InvalidResourceException(sprintf('Unable to load "%s": %s', $file, $e->getMessage()), $e->getCode(), $e);
}
$internalErrors = libxml_use_internal_errors(true);
$location = str_replace('\\', '/', __DIR__).'/schema/dic/xliff-core/xml.xsd';
$parts = explode('/', $location);
if (0 === stripos($location, 'phar://')) {
$tmpfile = tempnam(sys_get_temp_dir(), 'sf2');
if ($tmpfile) {
copy($location, $tmpfile);
$parts = explode('/', str_replace('\\', '/', $tmpfile));
}
}
$drive = '\\' === DIRECTORY_SEPARATOR ? array_shift($parts).'/' : '';
$location = 'file:///'.$drive.implode('/', array_map('rawurlencode', $parts));
$source = file_get_contents(__DIR__.'/schema/dic/xliff-core/xliff-core-1.2-strict.xsd');
$source = str_replace('http://www.w3.org/2001/xml.xsd', $location, $source);
if (!@$dom->schemaValidateSource($source)) {
throw new InvalidResourceException(sprintf('Invalid resource provided: "%s"; Errors: %s', $file, implode("\n", $this->getXmlErrors($internalErrors))));
}
$dom->normalizeDocument();
libxml_clear_errors();
libxml_use_internal_errors($internalErrors);
return array(simplexml_import_dom($dom), strtoupper($dom->encoding));
}
/**
* Returns the XML errors of the internal XML parser.
*
* @param bool $internalErrors
*
* @return array An array of errors
*/
private function getXmlErrors($internalErrors)
{
$errors = array();
foreach (libxml_get_errors() as $error) {
$errors[] = sprintf('[%s %s] %s (in %s - line %d, column %d)',
LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
$error->code,
trim($error->message),
$error->file ?: 'n/a',
$error->line,
$error->column
);
}
libxml_clear_errors();
libxml_use_internal_errors($internalErrors);
return $errors;
}
}

View File

@ -1,78 +0,0 @@
<?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\Config\Resource\FileResource;
use Symfony\Component\Yaml\Parser as YamlParser;
use Symfony\Component\Yaml\Exception\ParseException;
/**
* YamlFileLoader loads translations from Yaml files.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class YamlFileLoader extends ArrayLoader
{
private $yamlParser;
/**
* {@inheritdoc}
*
* @api
*/
public function load($resource, $locale, $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));
}
if (!class_exists('Symfony\Component\Yaml\Parser')) {
throw new \LogicException('Loading translations from the YAML format requires the Symfony Yaml component.');
}
if (null === $this->yamlParser) {
$this->yamlParser = new YamlParser();
}
try {
$messages = $this->yamlParser->parse(file_get_contents($resource));
} catch (ParseException $e) {
throw new InvalidResourceException(sprintf('Error parsing YAML, invalid file "%s"', $resource), 0, $e);
}
// empty file
if (null === $messages) {
$messages = array();
}
// not an array
if (!is_array($messages)) {
throw new InvalidResourceException(sprintf('The file "%s" must contain a YAML array.', $resource));
}
$catalogue = parent::load($messages, $locale, $domain);
if (class_exists('Symfony\Component\Config\Resource\FileResource')) {
$catalogue->addResource(new FileResource($resource));
}
return $catalogue;
}
}

View File

@ -1,309 +0,0 @@
<?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>

View File

@ -1,128 +0,0 @@
<?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;
/**
* @author Abdellatif Ait boudad <a.aitboudad@gmail.com>
*/
class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface
{
/**
* @var TranslatorInterface|TranslatorBagInterface
*/
private $translator;
/**
* @var LoggerInterface
*/
private $logger;
/**
* @param TranslatorInterface $translator The translator must implement TranslatorBagInterface
* @param LoggerInterface $logger
*/
public function __construct(TranslatorInterface $translator, LoggerInterface $logger)
{
if (!$translator instanceof TranslatorBagInterface) {
throw new \InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface and TranslatorBagInterface.', get_class($translator)));
}
$this->translator = $translator;
$this->logger = $logger;
}
/**
* {@inheritdoc}
*/
public function trans($id, array $parameters = array(), $domain = null, $locale = null)
{
$trans = $this->translator->trans($id, $parameters, $domain, $locale);
$this->log($id, $domain, $locale);
return $trans;
}
/**
* {@inheritdoc}
*/
public function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null)
{
$trans = $this->translator->transChoice($id, $number, $parameters, $domain, $locale);
$this->log($id, $domain, $locale);
return $trans;
}
/**
* {@inheritdoc}
*
* @api
*/
public function setLocale($locale)
{
$this->translator->setLocale($locale);
}
/**
* {@inheritdoc}
*
* @api
*/
public function getLocale()
{
return $this->translator->getLocale();
}
/**
* {@inheritdoc}
*/
public function getCatalogue($locale = null)
{
return $this->translator->getCatalogue($locale);
}
/**
* Passes through all unknown calls onto the translator object.
*/
public function __call($method, $args)
{
return call_user_func_array(array($this->translator, $method), $args);
}
/**
* Logs for missing translations.
*
* @param string $id
* @param string|null $domain
* @param string|null $locale
*/
private function log($id, $domain, $locale)
{
if (null === $domain) {
$domain = 'messages';
}
$id = (string) $id;
$catalogue = $this->translator->getCatalogue($locale);
if ($catalogue->defines($id, $domain)) {
return;
}
if ($catalogue->has($id, $domain)) {
$this->logger->debug('Translation use fallback catalogue.', array('id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()));
} else {
$this->logger->warning('Translation not found.', array('id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()));
}
}
}

View File

@ -1,300 +0,0 @@
<?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;
/**
* MessageCatalogue.
*
* @author Fabien Potencier <fabien@symfony.com>
*
* @api
*/
class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterface
{
private $messages = array();
private $metadata = array();
private $resources = array();
private $locale;
private $fallbackCatalogue;
private $parent;
/**
* Constructor.
*
* @param string $locale The locale
* @param array $messages An array of messages classified by domain
*
* @api
*/
public function __construct($locale, array $messages = array())
{
$this->locale = $locale;
$this->messages = $messages;
}
/**
* {@inheritdoc}
*
* @api
*/
public function getLocale()
{
return $this->locale;
}
/**
* {@inheritdoc}
*
* @api
*/
public function getDomains()
{
return array_keys($this->messages);
}
/**
* {@inheritdoc}
*
* @api
*/
public function all($domain = null)
{
if (null === $domain) {
return $this->messages;
}
return isset($this->messages[$domain]) ? $this->messages[$domain] : array();
}
/**
* {@inheritdoc}
*
* @api
*/
public function set($id, $translation, $domain = 'messages')
{
$this->add(array($id => $translation), $domain);
}
/**
* {@inheritdoc}
*
* @api
*/
public function has($id, $domain = 'messages')
{
if (isset($this->messages[$domain][$id])) {
return true;
}
if (null !== $this->fallbackCatalogue) {
return $this->fallbackCatalogue->has($id, $domain);
}
return false;
}
/**
* {@inheritdoc}
*/
public function defines($id, $domain = 'messages')
{
return isset($this->messages[$domain][$id]);
}
/**
* {@inheritdoc}
*
* @api
*/
public function get($id, $domain = 'messages')
{
if (isset($this->messages[$domain][$id])) {
return $this->messages[$domain][$id];
}
if (null !== $this->fallbackCatalogue) {
return $this->fallbackCatalogue->get($id, $domain);
}
return $id;
}
/**
* {@inheritdoc}
*
* @api
*/
public function replace($messages, $domain = 'messages')
{
$this->messages[$domain] = array();
$this->add($messages, $domain);
}
/**
* {@inheritdoc}
*
* @api
*/
public function add($messages, $domain = 'messages')
{
if (!isset($this->messages[$domain])) {
$this->messages[$domain] = $messages;
} else {
$this->messages[$domain] = array_replace($this->messages[$domain], $messages);
}
}
/**
* {@inheritdoc}
*
* @api
*/
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) {
$this->add($messages, $domain);
}
foreach ($catalogue->getResources() as $resource) {
$this->addResource($resource);
}
if ($catalogue instanceof MetadataAwareInterface) {
$metadata = $catalogue->getMetadata('', '');
$this->addMetadata($metadata);
}
}
/**
* {@inheritdoc}
*
* @api
*/
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()));
}
} while ($c = $c->parent);
$catalogue->parent = $this;
$this->fallbackCatalogue = $catalogue;
foreach ($catalogue->getResources() as $resource) {
$this->addResource($resource);
}
}
/**
* {@inheritdoc}
*
* @api
*/
public function getFallbackCatalogue()
{
return $this->fallbackCatalogue;
}
/**
* {@inheritdoc}
*
* @api
*/
public function getResources()
{
return array_values($this->resources);
}
/**
* {@inheritdoc}
*
* @api
*/
public function addResource(ResourceInterface $resource)
{
$this->resources[$resource->__toString()] = $resource;
}
/**
* {@inheritdoc}
*/
public function getMetadata($key = '', $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];
}
}
}
/**
* {@inheritdoc}
*/
public function setMetadata($key, $value, $domain = 'messages')
{
$this->metadata[$domain][$key] = $value;
}
/**
* {@inheritdoc}
*/
public function deleteMetadata($key = '', $domain = 'messages')
{
if ('' == $domain) {
$this->metadata = array();
} 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);
}
}
}
}

View File

@ -1,172 +0,0 @@
<?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>
*
* @api
*/
interface MessageCatalogueInterface
{
/**
* Gets the catalogue locale.
*
* @return string The locale
*
* @api
*/
public function getLocale();
/**
* Gets the domains.
*
* @return array An array of domains
*
* @api
*/
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
*
* @api
*/
public function all($domain = null);
/**
* Sets a message translation.
*
* @param string $id The message id
* @param string $translation The messages translation
* @param string $domain The domain name
*
* @api
*/
public function set($id, $translation, $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
*
* @api
*/
public function has($id, $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
*
* @api
*/
public function defines($id, $domain = 'messages');
/**
* Gets a message translation.
*
* @param string $id The message id
* @param string $domain The domain name
*
* @return string The message translation
*
* @api
*/
public function get($id, $domain = 'messages');
/**
* Sets translations for a given domain.
*
* @param array $messages An array of translations
* @param string $domain The domain name
*
* @api
*/
public function replace($messages, $domain = 'messages');
/**
* Adds translations for a given domain.
*
* @param array $messages An array of translations
* @param string $domain The domain name
*
* @api
*/
public function add($messages, $domain = 'messages');
/**
* Merges translations from the given Catalogue into the current one.
*
* The two catalogues must have the same locale.
*
* @param MessageCatalogueInterface $catalogue A MessageCatalogueInterface instance
*
* @api
*/
public function addCatalogue(MessageCatalogueInterface $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.
*
* @param MessageCatalogueInterface $catalogue A MessageCatalogueInterface instance
*
* @api
*/
public function addFallbackCatalogue(MessageCatalogueInterface $catalogue);
/**
* Gets the fallback catalogue.
*
* @return MessageCatalogueInterface|null A MessageCatalogueInterface instance or null when no fallback has been set
*
* @api
*/
public function getFallbackCatalogue();
/**
* Returns an array of resources loaded to build this collection.
*
* @return ResourceInterface[] An array of resources
*
* @api
*/
public function getResources();
/**
* Adds a resource for this collection.
*
* @param ResourceInterface $resource A resource instance
*
* @api
*/
public function addResource(ResourceInterface $resource);
}

View File

@ -1,90 +0,0 @@
<?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;
/**
* MessageSelector.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @api
*/
class MessageSelector
{
/**
* 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
*
* @param string $message The message being translated
* @param int $number The number of items represented for the message
* @param string $locale The locale to use for choosing
*
* @return string
*
* @throws \InvalidArgumentException
*
* @api
*/
public function choose($message, $number, $locale)
{
$parts = explode('|', $message);
$explicitRules = array();
$standardRules = array();
foreach ($parts as $part) {
$part = trim($part);
if (preg_match('/^(?P<interval>'.Interval::getIntervalRegexp().')\s*(?P<message>.*?)$/xs', $part, $matches)) {
$explicitRules[$matches['interval']] = $matches['message'];
} elseif (preg_match('/^\w+\:\s*(.*?)$/', $part, $matches)) {
$standardRules[] = $matches[1];
} else {
$standardRules[] = $part;
}
}
// try to match an explicit rule, then fallback to the standard ones
foreach ($explicitRules as $interval => $m) {
if (Interval::test($number, $interval)) {
return $m;
}
}
$position = PluralizationRules::get($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 $standardRules[0];
}
throw new \InvalidArgumentException(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").', $message, $locale, $number));
}
return $standardRules[$position];
}
}

View File

@ -1,54 +0,0 @@
<?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.
*
* @param string $key The key
* @param string $domain The domain name
*
* @return mixed The value that was set or an array with the domains/keys or null
*/
public function getMetadata($key = '', $domain = 'messages');
/**
* Adds metadata to a message domain.
*
* @param string $key The key
* @param mixed $value The value
* @param string $domain The domain name
*/
public function setMetadata($key, $value, $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.
*
* @param string $key The key
* @param string $domain The domain name
*/
public function deleteMetadata($key = '', $domain = 'messages');
}

View File

@ -1,214 +0,0 @@
<?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;
/**
* Returns the plural rules for a given locale.
*
* @author Fabien Potencier <fabien@symfony.com>
*/
class PluralizationRules
{
private static $rules = array();
/**
* Returns the plural position to use for the given locale and number.
*
* @param int $number The number
* @param string $locale The locale
*
* @return int The plural position
*/
public static function get($number, $locale)
{
if ('pt_BR' === $locale) {
// temporary set a locale for brazilian
$locale = 'xbr';
}
if (strlen($locale) > 3) {
$locale = substr($locale, 0, -strlen(strrchr($locale, '_')));
}
if (isset(self::$rules[$locale])) {
$return = call_user_func(self::$rules[$locale], $number);
if (!is_int($return) || $return < 0) {
return 0;
}
return $return;
}
/*
* 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)
*/
switch ($locale) {
case 'az':
case 'bo':
case 'dz':
case 'id':
case 'ja':
case 'jv':
case 'ka':
case 'km':
case 'kn':
case 'ko':
case 'ms':
case 'th':
case 'tr':
case 'vi':
case 'zh':
return 0;
break;
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 '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 ($number == 1) ? 0 : 1;
case 'am':
case 'bh':
case 'fil':
case 'fr':
case 'gun':
case 'hi':
case 'ln':
case 'mg':
case 'nso':
case 'xbr':
case 'ti':
case 'wa':
return (($number == 0) || ($number == 1)) ? 0 : 1;
case 'be':
case 'bs':
case 'hr':
case 'ru':
case 'sr':
case 'uk':
return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2);
case 'cs':
case 'sk':
return ($number == 1) ? 0 : ((($number >= 2) && ($number <= 4)) ? 1 : 2);
case 'ga':
return ($number == 1) ? 0 : (($number == 2) ? 1 : 2);
case 'lt':
return (($number % 10 == 1) && ($number % 100 != 11)) ? 0 : ((($number % 10 >= 2) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2);
case 'sl':
return ($number % 100 == 1) ? 0 : (($number % 100 == 2) ? 1 : ((($number % 100 == 3) || ($number % 100 == 4)) ? 2 : 3));
case 'mk':
return ($number % 10 == 1) ? 0 : 1;
case 'mt':
return ($number == 1) ? 0 : ((($number == 0) || (($number % 100 > 1) && ($number % 100 < 11))) ? 1 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 2 : 3));
case 'lv':
return ($number == 0) ? 0 : ((($number % 10 == 1) && ($number % 100 != 11)) ? 1 : 2);
case 'pl':
return ($number == 1) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 1 : 2);
case 'cy':
return ($number == 1) ? 0 : (($number == 2) ? 1 : ((($number == 8) || ($number == 11)) ? 2 : 3));
case 'ro':
return ($number == 1) ? 0 : ((($number == 0) || (($number % 100 > 0) && ($number % 100 < 20))) ? 1 : 2);
case 'ar':
return ($number == 0) ? 0 : (($number == 1) ? 1 : (($number == 2) ? 2 : ((($number % 100 >= 3) && ($number % 100 <= 10)) ? 3 : ((($number % 100 >= 11) && ($number % 100 <= 99)) ? 4 : 5))));
default:
return 0;
}
}
/**
* Overrides the default plural rule for a given locale.
*
* @param string $rule A PHP callable
* @param string $locale The locale
*
* @throws \LogicException
*/
public static function set($rule, $locale)
{
if ('pt_BR' === $locale) {
// temporary set a locale for brazilian
$locale = 'xbr';
}
if (strlen($locale) > 3) {
$locale = substr($locale, 0, -strlen(strrchr($locale, '_')));
}
if (!is_callable($rule)) {
throw new \LogicException('The given rule can not be called');
}
self::$rules[$locale] = $rule;
}
}

View File

@ -1,37 +0,0 @@
Translation Component
=====================
Translation provides tools for loading translation files and generating
translated strings from these including support for pluralization.
```php
use Symfony\Component\Translation\Translator;
use Symfony\Component\Translation\MessageSelector;
use Symfony\Component\Translation\Loader\ArrayLoader;
$translator = new Translator('fr_FR', new MessageSelector());
$translator->setFallbackLocales(array('fr'));
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', array(
'Hello World!' => 'Bonjour',
), 'fr');
echo $translator->trans('Hello World!')."\n";
```
Resources
---------
Silex integration:
https://github.com/silexphp/Silex/blob/master/src/Silex/Provider/TranslationServiceProvider.php
Documentation:
https://symfony.com/doc/2.7/book/translation.html
You can run the unit tests with the following command:
$ cd path/to/Symfony/Component/Translation/
$ composer install
$ phpunit

View File

@ -1,73 +0,0 @@
<?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\Tests\Catalogue;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\MessageCatalogueInterface;
abstract class AbstractOperationTest extends \PHPUnit_Framework_TestCase
{
public function testGetEmptyDomains()
{
$this->assertEquals(
array(),
$this->createOperation(
new MessageCatalogue('en'),
new MessageCatalogue('en')
)->getDomains()
);
}
public function testGetMergedDomains()
{
$this->assertEquals(
array('a', 'b', 'c'),
$this->createOperation(
new MessageCatalogue('en', array('a' => array(), 'b' => array())),
new MessageCatalogue('en', array('b' => array(), 'c' => array()))
)->getDomains()
);
}
public function testGetMessagesFromUnknownDomain()
{
$this->setExpectedException('InvalidArgumentException');
$this->createOperation(
new MessageCatalogue('en'),
new MessageCatalogue('en')
)->getMessages('domain');
}
public function testGetEmptyMessages()
{
$this->assertEquals(
array(),
$this->createOperation(
new MessageCatalogue('en', array('a' => array())),
new MessageCatalogue('en')
)->getMessages('a')
);
}
public function testGetEmptyResult()
{
$this->assertEquals(
new MessageCatalogue('en'),
$this->createOperation(
new MessageCatalogue('en'),
new MessageCatalogue('en')
)->getResult()
);
}
abstract protected function createOperation(MessageCatalogueInterface $source, MessageCatalogueInterface $target);
}

View File

@ -1,82 +0,0 @@
<?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\Tests\Catalogue;
use Symfony\Component\Translation\Catalogue\DiffOperation;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\MessageCatalogueInterface;
class DiffOperationTest extends AbstractOperationTest
{
public function testGetMessagesFromSingleDomain()
{
$operation = $this->createOperation(
new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b'))),
new MessageCatalogue('en', array('messages' => array('a' => 'new_a', 'c' => 'new_c')))
);
$this->assertEquals(
array('a' => 'old_a', 'c' => 'new_c'),
$operation->getMessages('messages')
);
$this->assertEquals(
array('c' => 'new_c'),
$operation->getNewMessages('messages')
);
$this->assertEquals(
array('b' => 'old_b'),
$operation->getObsoleteMessages('messages')
);
}
public function testGetResultFromSingleDomain()
{
$this->assertEquals(
new MessageCatalogue('en', array(
'messages' => array('a' => 'old_a', 'c' => 'new_c'),
)),
$this->createOperation(
new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b'))),
new MessageCatalogue('en', array('messages' => array('a' => 'new_a', 'c' => 'new_c')))
)->getResult()
);
}
public function testGetResultWithMetadata()
{
$leftCatalogue = new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b')));
$leftCatalogue->setMetadata('a', 'foo', 'messages');
$leftCatalogue->setMetadata('b', 'bar', 'messages');
$rightCatalogue = new MessageCatalogue('en', array('messages' => array('b' => 'new_b', 'c' => 'new_c')));
$rightCatalogue->setMetadata('b', 'baz', 'messages');
$rightCatalogue->setMetadata('c', 'qux', 'messages');
$diffCatalogue = new MessageCatalogue('en', array('messages' => array('b' => 'old_b', 'c' => 'new_c')));
$diffCatalogue->setMetadata('b', 'bar', 'messages');
$diffCatalogue->setMetadata('c', 'qux', 'messages');
$this->assertEquals(
$diffCatalogue,
$this->createOperation(
$leftCatalogue,
$rightCatalogue
)->getResult()
);
}
protected function createOperation(MessageCatalogueInterface $source, MessageCatalogueInterface $target)
{
return new DiffOperation($source, $target);
}
}

View File

@ -1,83 +0,0 @@
<?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\Tests\Catalogue;
use Symfony\Component\Translation\Catalogue\MergeOperation;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\MessageCatalogueInterface;
class MergeOperationTest extends AbstractOperationTest
{
public function testGetMessagesFromSingleDomain()
{
$operation = $this->createOperation(
new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b'))),
new MessageCatalogue('en', array('messages' => array('a' => 'new_a', 'c' => 'new_c')))
);
$this->assertEquals(
array('a' => 'old_a', 'b' => 'old_b', 'c' => 'new_c'),
$operation->getMessages('messages')
);
$this->assertEquals(
array('c' => 'new_c'),
$operation->getNewMessages('messages')
);
$this->assertEquals(
array(),
$operation->getObsoleteMessages('messages')
);
}
public function testGetResultFromSingleDomain()
{
$this->assertEquals(
new MessageCatalogue('en', array(
'messages' => array('a' => 'old_a', 'b' => 'old_b', 'c' => 'new_c'),
)),
$this->createOperation(
new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b'))),
new MessageCatalogue('en', array('messages' => array('a' => 'new_a', 'c' => 'new_c')))
)->getResult()
);
}
public function testGetResultWithMetadata()
{
$leftCatalogue = new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b')));
$leftCatalogue->setMetadata('a', 'foo', 'messages');
$leftCatalogue->setMetadata('b', 'bar', 'messages');
$rightCatalogue = new MessageCatalogue('en', array('messages' => array('b' => 'new_b', 'c' => 'new_c')));
$rightCatalogue->setMetadata('b', 'baz', 'messages');
$rightCatalogue->setMetadata('c', 'qux', 'messages');
$mergedCatalogue = new MessageCatalogue('en', array('messages' => array('a' => 'old_a', 'b' => 'old_b', 'c' => 'new_c')));
$mergedCatalogue->setMetadata('a', 'foo', 'messages');
$mergedCatalogue->setMetadata('b', 'bar', 'messages');
$mergedCatalogue->setMetadata('c', 'qux', 'messages');
$this->assertEquals(
$mergedCatalogue,
$this->createOperation(
$leftCatalogue,
$rightCatalogue
)->getResult()
);
}
protected function createOperation(MessageCatalogueInterface $source, MessageCatalogueInterface $target)
{
return new MergeOperation($source, $target);
}
}

View File

@ -1,121 +0,0 @@
<?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\Tests\DataCollector;
use Symfony\Component\Translation\DataCollectorTranslator;
use Symfony\Component\Translation\DataCollector\TranslationDataCollector;
class TranslationDataCollectorTest extends \PHPUnit_Framework_TestCase
{
protected function setUp()
{
if (!class_exists('Symfony\Component\HttpKernel\DataCollector\DataCollector')) {
$this->markTestSkipped('The "DataCollector" is not available');
}
}
public function testCollectEmptyMessages()
{
$translator = $this->getTranslator();
$translator->expects($this->any())->method('getCollectedMessages')->will($this->returnValue(array()));
$dataCollector = new TranslationDataCollector($translator);
$dataCollector->lateCollect();
$this->assertEquals(0, $dataCollector->getCountMissings());
$this->assertEquals(0, $dataCollector->getCountFallbacks());
$this->assertEquals(0, $dataCollector->getCountDefines());
$this->assertEquals(array(), $dataCollector->getMessages());
}
public function testCollect()
{
$collectedMessages = array(
array(
'id' => 'foo',
'translation' => 'foo (en)',
'locale' => 'en',
'domain' => 'messages',
'state' => DataCollectorTranslator::MESSAGE_DEFINED,
),
array(
'id' => 'bar',
'translation' => 'bar (fr)',
'locale' => 'fr',
'domain' => 'messages',
'state' => DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK,
),
array(
'id' => 'choice',
'translation' => 'choice',
'locale' => 'en',
'domain' => 'messages',
'state' => DataCollectorTranslator::MESSAGE_MISSING,
),
array(
'id' => 'choice',
'translation' => 'choice',
'locale' => 'en',
'domain' => 'messages',
'state' => DataCollectorTranslator::MESSAGE_MISSING,
),
);
$expectedMessages = array(
array(
'id' => 'foo',
'translation' => 'foo (en)',
'locale' => 'en',
'domain' => 'messages',
'state' => DataCollectorTranslator::MESSAGE_DEFINED,
'count' => 1,
),
array(
'id' => 'bar',
'translation' => 'bar (fr)',
'locale' => 'fr',
'domain' => 'messages',
'state' => DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK,
'count' => 1,
),
array(
'id' => 'choice',
'translation' => 'choice',
'locale' => 'en',
'domain' => 'messages',
'state' => DataCollectorTranslator::MESSAGE_MISSING,
'count' => 2,
),
);
$translator = $this->getTranslator();
$translator->expects($this->any())->method('getCollectedMessages')->will($this->returnValue($collectedMessages));
$dataCollector = new TranslationDataCollector($translator);
$dataCollector->lateCollect();
$this->assertEquals(1, $dataCollector->getCountMissings());
$this->assertEquals(1, $dataCollector->getCountFallbacks());
$this->assertEquals(1, $dataCollector->getCountDefines());
$this->assertEquals($expectedMessages, array_values($dataCollector->getMessages()));
}
private function getTranslator()
{
$translator = $this
->getMockBuilder('Symfony\Component\Translation\DataCollectorTranslator')
->disableOriginalConstructor()
->getMock()
;
return $translator;
}
}

View File

@ -1,81 +0,0 @@
<?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\Tests;
use Symfony\Component\Translation\Translator;
use Symfony\Component\Translation\DataCollectorTranslator;
use Symfony\Component\Translation\Loader\ArrayLoader;
class DataCollectorTranslatorTest extends \PHPUnit_Framework_TestCase
{
protected function setUp()
{
if (!class_exists('Symfony\Component\HttpKernel\DataCollector\DataCollector')) {
$this->markTestSkipped('The "DataCollector" is not available');
}
}
public function testCollectMessages()
{
$collector = $this->createCollector();
$collector->setFallbackLocales(array('fr', 'ru'));
$collector->trans('foo');
$collector->trans('bar');
$collector->transChoice('choice', 0);
$collector->trans('bar_ru');
$expectedMessages = array();
$expectedMessages[] = array(
'id' => 'foo',
'translation' => 'foo (en)',
'locale' => 'en',
'domain' => 'messages',
'state' => DataCollectorTranslator::MESSAGE_DEFINED,
);
$expectedMessages[] = array(
'id' => 'bar',
'translation' => 'bar (fr)',
'locale' => 'fr',
'domain' => 'messages',
'state' => DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK,
);
$expectedMessages[] = array(
'id' => 'choice',
'translation' => 'choice',
'locale' => 'en',
'domain' => 'messages',
'state' => DataCollectorTranslator::MESSAGE_MISSING,
);
$expectedMessages[] = array(
'id' => 'bar_ru',
'translation' => 'bar (ru)',
'locale' => 'ru',
'domain' => 'messages',
'state' => DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK,
);
$this->assertEquals($expectedMessages, $collector->getCollectedMessages());
}
private function createCollector()
{
$translator = new Translator('en');
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', array('foo' => 'foo (en)'), 'en');
$translator->addResource('array', array('bar' => 'bar (fr)'), 'fr');
$translator->addResource('array', array('bar_ru' => 'bar (ru)'), 'ru');
$collector = new DataCollectorTranslator($translator);
return $collector;
}
}

View File

@ -1,33 +0,0 @@
<?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\Tests\Dumper;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Dumper\CsvFileDumper;
class CsvFileDumperTest extends \PHPUnit_Framework_TestCase
{
public function testDump()
{
$catalogue = new MessageCatalogue('en');
$catalogue->add(array('foo' => 'bar', 'bar' => 'foo
foo', 'foo;foo' => 'bar'));
$tempDir = sys_get_temp_dir();
$dumper = new CsvFileDumper();
$dumper->dump($catalogue, array('path' => $tempDir));
$this->assertEquals(file_get_contents(__DIR__.'/../fixtures/valid.csv'), file_get_contents($tempDir.'/messages.en.csv'));
unlink($tempDir.'/messages.en.csv');
}
}

View File

@ -1,70 +0,0 @@
<?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\Tests\Dumper;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Dumper\FileDumper;
class FileDumperTest extends \PHPUnit_Framework_TestCase
{
public function testDumpBackupsFileIfExisting()
{
$tempDir = sys_get_temp_dir();
$file = $tempDir.'/messages.en.concrete';
$backupFile = $file.'~';
@touch($file);
$catalogue = new MessageCatalogue('en');
$catalogue->add(array('foo' => 'bar'));
$dumper = new ConcreteFileDumper();
$dumper->dump($catalogue, array('path' => $tempDir));
$this->assertTrue(file_exists($backupFile));
@unlink($file);
@unlink($backupFile);
}
public function testDumpCreatesNestedDirectoriesAndFile()
{
$tempDir = sys_get_temp_dir();
$translationsDir = $tempDir.'/test/translations';
$file = $translationsDir.'/messages.en.concrete';
$catalogue = new MessageCatalogue('en');
$catalogue->add(array('foo' => 'bar'));
$dumper = new ConcreteFileDumper();
$dumper->setRelativePathTemplate('test/translations/%domain%.%locale%.%extension%');
$dumper->dump($catalogue, array('path' => $tempDir));
$this->assertTrue(file_exists($file));
@unlink($file);
@rmdir($translationsDir);
}
}
class ConcreteFileDumper extends FileDumper
{
protected function format(MessageCatalogue $messages, $domain)
{
return '';
}
protected function getExtension()
{
return 'concrete';
}
}

View File

@ -1,38 +0,0 @@
<?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\Tests\Dumper;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Dumper\IcuResFileDumper;
class IcuResFileDumperTest extends \PHPUnit_Framework_TestCase
{
public function testDump()
{
if (!function_exists('mb_convert_encoding')) {
$this->markTestSkipped('This test requires mbstring to work.');
}
$catalogue = new MessageCatalogue('en');
$catalogue->add(array('foo' => 'bar'));
$tempDir = sys_get_temp_dir().'/IcuResFileDumperTest';
$dumper = new IcuResFileDumper();
$dumper->dump($catalogue, array('path' => $tempDir));
$this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resourcebundle/res/en.res'), file_get_contents($tempDir.'/messages/en.res'));
@unlink($tempDir.'/messages/en.res');
@rmdir($tempDir.'/messages');
@rmdir($tempDir);
}
}

View File

@ -1,32 +0,0 @@
<?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\Tests\Dumper;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Dumper\IniFileDumper;
class IniFileDumperTest extends \PHPUnit_Framework_TestCase
{
public function testDump()
{
$catalogue = new MessageCatalogue('en');
$catalogue->add(array('foo' => 'bar'));
$tempDir = sys_get_temp_dir();
$dumper = new IniFileDumper();
$dumper->dump($catalogue, array('path' => $tempDir));
$this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resources.ini'), file_get_contents($tempDir.'/messages.en.ini'));
unlink($tempDir.'/messages.en.ini');
}
}

View File

@ -1,36 +0,0 @@
<?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\Tests\Dumper;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Dumper\JsonFileDumper;
class JsonFileDumperTest extends \PHPUnit_Framework_TestCase
{
public function testDump()
{
if (PHP_VERSION_ID < 50400) {
$this->markTestIncomplete('PHP below 5.4 doesn\'t support JSON pretty printing');
}
$catalogue = new MessageCatalogue('en');
$catalogue->add(array('foo' => 'bar'));
$tempDir = sys_get_temp_dir();
$dumper = new JsonFileDumper();
$dumper->dump($catalogue, array('path' => $tempDir));
$this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resources.json'), file_get_contents($tempDir.'/messages.en.json'));
unlink($tempDir.'/messages.en.json');
}
}

View File

@ -1,31 +0,0 @@
<?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\Tests\Dumper;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Dumper\MoFileDumper;
class MoFileDumperTest extends \PHPUnit_Framework_TestCase
{
public function testDump()
{
$catalogue = new MessageCatalogue('en');
$catalogue->add(array('foo' => 'bar'));
$tempDir = sys_get_temp_dir();
$dumper = new MoFileDumper();
$dumper->dump($catalogue, array('path' => $tempDir));
$this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resources.mo'), file_get_contents($tempDir.'/messages.en.mo'));
unlink($tempDir.'/messages.en.mo');
}
}

View File

@ -1,32 +0,0 @@
<?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\Tests\Dumper;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Dumper\PhpFileDumper;
class PhpFileDumperTest extends \PHPUnit_Framework_TestCase
{
public function testDump()
{
$catalogue = new MessageCatalogue('en');
$catalogue->add(array('foo' => 'bar'));
$tempDir = sys_get_temp_dir();
$dumper = new PhpFileDumper();
$dumper->dump($catalogue, array('path' => $tempDir));
$this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resources.php'), file_get_contents($tempDir.'/messages.en.php'));
unlink($tempDir.'/messages.en.php');
}
}

View File

@ -1,31 +0,0 @@
<?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\Tests\Dumper;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Dumper\PoFileDumper;
class PoFileDumperTest extends \PHPUnit_Framework_TestCase
{
public function testDump()
{
$catalogue = new MessageCatalogue('en');
$catalogue->add(array('foo' => 'bar'));
$tempDir = sys_get_temp_dir();
$dumper = new PoFileDumper();
$dumper->dump($catalogue, array('path' => $tempDir));
$this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resources.po'), file_get_contents($tempDir.'/messages.en.po'));
unlink($tempDir.'/messages.en.po');
}
}

View File

@ -1,32 +0,0 @@
<?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\Tests\Dumper;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Dumper\QtFileDumper;
class QtFileDumperTest extends \PHPUnit_Framework_TestCase
{
public function testDump()
{
$catalogue = new MessageCatalogue('en');
$catalogue->add(array('foo' => 'bar'), 'resources');
$tempDir = sys_get_temp_dir();
$dumper = new QtFileDumper();
$dumper->dump($catalogue, array('path' => $tempDir));
$this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resources.ts'), file_get_contents($tempDir.'/resources.en.ts'));
unlink($tempDir.'/resources.en.ts');
}
}

View File

@ -1,41 +0,0 @@
<?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\Tests\Dumper;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Dumper\XliffFileDumper;
class XliffFileDumperTest extends \PHPUnit_Framework_TestCase
{
public function testDump()
{
$catalogue = new MessageCatalogue('en_US');
$catalogue->add(array(
'foo' => 'bar',
'key' => '',
'key.with.cdata' => '<source> & <target>',
));
$catalogue->setMetadata('foo', array('notes' => array(array('priority' => 1, 'from' => 'bar', 'content' => 'baz'))));
$catalogue->setMetadata('key', array('notes' => array(array('content' => 'baz'), array('content' => 'qux'))));
$tempDir = sys_get_temp_dir();
$dumper = new XliffFileDumper();
$dumper->dump($catalogue, array('path' => $tempDir, 'default_locale' => 'fr_FR'));
$this->assertEquals(
file_get_contents(__DIR__.'/../fixtures/resources-clean.xlf'),
file_get_contents($tempDir.'/messages.en_US.xlf')
);
unlink($tempDir.'/messages.en_US.xlf');
}
}

View File

@ -1,32 +0,0 @@
<?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\Tests\Dumper;
use Symfony\Component\Translation\MessageCatalogue;
use Symfony\Component\Translation\Dumper\YamlFileDumper;
class YamlFileDumperTest extends \PHPUnit_Framework_TestCase
{
public function testDump()
{
$catalogue = new MessageCatalogue('en');
$catalogue->add(array('foo' => 'bar'));
$tempDir = sys_get_temp_dir();
$dumper = new YamlFileDumper();
$dumper->dump($catalogue, array('path' => $tempDir));
$this->assertEquals(file_get_contents(__DIR__.'/../fixtures/resources.yml'), file_get_contents($tempDir.'/messages.en.yml'));
unlink($tempDir.'/messages.en.yml');
}
}

View File

@ -1,95 +0,0 @@
<?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\Tests;
use Symfony\Component\Intl\Util\IntlTestHelper;
use Symfony\Component\Translation\IdentityTranslator;
class IdentityTranslatorTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider getTransTests
*/
public function testTrans($expected, $id, $parameters)
{
$translator = new IdentityTranslator();
$this->assertEquals($expected, $translator->trans($id, $parameters));
}
/**
* @dataProvider getTransChoiceTests
*/
public function testTransChoiceWithExplicitLocale($expected, $id, $number, $parameters)
{
$translator = new IdentityTranslator();
$translator->setLocale('en');
$this->assertEquals($expected, $translator->transChoice($id, $number, $parameters));
}
/**
* @dataProvider getTransChoiceTests
*/
public function testTransChoiceWithDefaultLocale($expected, $id, $number, $parameters)
{
\Locale::setDefault('en');
$translator = new IdentityTranslator();
$this->assertEquals($expected, $translator->transChoice($id, $number, $parameters));
}
public function testGetSetLocale()
{
$translator = new IdentityTranslator();
$translator->setLocale('en');
$this->assertEquals('en', $translator->getLocale());
}
public function testGetLocaleReturnsDefaultLocaleIfNotSet()
{
// in order to test with "pt_BR"
IntlTestHelper::requireFullIntl($this);
$translator = new IdentityTranslator();
\Locale::setDefault('en');
$this->assertEquals('en', $translator->getLocale());
\Locale::setDefault('pt_BR');
$this->assertEquals('pt_BR', $translator->getLocale());
}
public function getTransTests()
{
return array(
array('Symfony is great!', 'Symfony is great!', array()),
array('Symfony is awesome!', 'Symfony is %what%!', array('%what%' => 'awesome')),
);
}
public function getTransChoiceTests()
{
return array(
array('There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0, array('%count%' => 0)),
array('There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1, array('%count%' => 1)),
array('There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10, array('%count%' => 10)),
array('There are 0 apples', 'There is 1 apple|There are %count% apples', 0, array('%count%' => 0)),
array('There is 1 apple', 'There is 1 apple|There are %count% apples', 1, array('%count%' => 1)),
array('There are 10 apples', 'There is 1 apple|There are %count% apples', 10, array('%count%' => 10)),
// custom validation messages may be coded with a fixed value
array('There are 2 apples', 'There are 2 apples', 2, array('%count%' => 2)),
);
}
}

View File

@ -1,48 +0,0 @@
<?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\Tests;
use Symfony\Component\Translation\Interval;
class IntervalTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider getTests
*/
public function testTest($expected, $number, $interval)
{
$this->assertEquals($expected, Interval::test($number, $interval));
}
/**
* @expectedException \InvalidArgumentException
*/
public function testTestException()
{
Interval::test(1, 'foobar');
}
public function getTests()
{
return array(
array(true, 3, '{1,2, 3 ,4}'),
array(false, 10, '{1,2, 3 ,4}'),
array(false, 3, '[1,2]'),
array(true, 1, '[1,2]'),
array(true, 2, '[1,2]'),
array(false, 1, ']1,2['),
array(false, 2, ']1,2['),
array(true, log(0), '[-Inf,2['),
array(true, -log(0), '[-2,+Inf]'),
);
}
}

View File

@ -1,60 +0,0 @@
<?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\Tests\Loader;
use Symfony\Component\Translation\Loader\CsvFileLoader;
use Symfony\Component\Config\Resource\FileResource;
class CsvFileLoaderTest extends \PHPUnit_Framework_TestCase
{
public function testLoad()
{
$loader = new CsvFileLoader();
$resource = __DIR__.'/../fixtures/resources.csv';
$catalogue = $loader->load($resource, 'en', 'domain1');
$this->assertEquals(array('foo' => 'bar'), $catalogue->all('domain1'));
$this->assertEquals('en', $catalogue->getLocale());
$this->assertEquals(array(new FileResource($resource)), $catalogue->getResources());
}
public function testLoadDoesNothingIfEmpty()
{
$loader = new CsvFileLoader();
$resource = __DIR__.'/../fixtures/empty.csv';
$catalogue = $loader->load($resource, 'en', 'domain1');
$this->assertEquals(array(), $catalogue->all('domain1'));
$this->assertEquals('en', $catalogue->getLocale());
$this->assertEquals(array(new FileResource($resource)), $catalogue->getResources());
}
/**
* @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
*/
public function testLoadNonExistingResource()
{
$loader = new CsvFileLoader();
$resource = __DIR__.'/../fixtures/not-exists.csv';
$loader->load($resource, 'en', 'domain1');
}
/**
* @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
*/
public function testLoadNonLocalResource()
{
$loader = new CsvFileLoader();
$resource = 'http://example.com/resources.csv';
$loader->load($resource, 'en', 'domain1');
}
}

View File

@ -1,68 +0,0 @@
<?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\Tests\Loader;
use Symfony\Component\Translation\Loader\IcuDatFileLoader;
use Symfony\Component\Config\Resource\FileResource;
class IcuDatFileLoaderTest extends LocalizedTestCase
{
protected function setUp()
{
if (!extension_loaded('intl')) {
$this->markTestSkipped('This test requires intl extension to work.');
}
}
/**
* @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
*/
public function testLoadInvalidResource()
{
$loader = new IcuDatFileLoader();
$loader->load(__DIR__.'/../fixtures/resourcebundle/corrupted/resources', 'es', 'domain2');
}
public function testDatEnglishLoad()
{
// bundled resource is build using pkgdata command which at least in ICU 4.2 comes in extremely! buggy form
// you must specify an temporary build directory which is not the same as current directory and
// MUST reside on the same partition. pkgdata -p resources -T /srv -d.packagelist.txt
$loader = new IcuDatFileLoader();
$resource = __DIR__.'/../fixtures/resourcebundle/dat/resources';
$catalogue = $loader->load($resource, 'en', 'domain1');
$this->assertEquals(array('symfony' => 'Symfony 2 is great'), $catalogue->all('domain1'));
$this->assertEquals('en', $catalogue->getLocale());
$this->assertEquals(array(new FileResource($resource.'.dat')), $catalogue->getResources());
}
public function testDatFrenchLoad()
{
$loader = new IcuDatFileLoader();
$resource = __DIR__.'/../fixtures/resourcebundle/dat/resources';
$catalogue = $loader->load($resource, 'fr', 'domain1');
$this->assertEquals(array('symfony' => 'Symfony 2 est génial'), $catalogue->all('domain1'));
$this->assertEquals('fr', $catalogue->getLocale());
$this->assertEquals(array(new FileResource($resource.'.dat')), $catalogue->getResources());
}
/**
* @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
*/
public function testLoadNonExistingResource()
{
$loader = new IcuDatFileLoader();
$loader->load(__DIR__.'/../fixtures/non-existing.txt', 'en', 'domain1');
}
}

View File

@ -1,55 +0,0 @@
<?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\Tests\Loader;
use Symfony\Component\Translation\Loader\IcuResFileLoader;
use Symfony\Component\Config\Resource\DirectoryResource;
class IcuResFileLoaderTest extends LocalizedTestCase
{
protected function setUp()
{
if (!extension_loaded('intl')) {
$this->markTestSkipped('This test requires intl extension to work.');
}
}
public function testLoad()
{
// resource is build using genrb command
$loader = new IcuResFileLoader();
$resource = __DIR__.'/../fixtures/resourcebundle/res';
$catalogue = $loader->load($resource, 'en', 'domain1');
$this->assertEquals(array('foo' => 'bar'), $catalogue->all('domain1'));
$this->assertEquals('en', $catalogue->getLocale());
$this->assertEquals(array(new DirectoryResource($resource)), $catalogue->getResources());
}
/**
* @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
*/
public function testLoadNonExistingResource()
{
$loader = new IcuResFileLoader();
$loader->load(__DIR__.'/../fixtures/non-existing.txt', 'en', 'domain1');
}
/**
* @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
*/
public function testLoadInvalidResource()
{
$loader = new IcuResFileLoader();
$loader->load(__DIR__.'/../fixtures/resourcebundle/corrupted', 'en', 'domain1');
}
}

View File

@ -1,50 +0,0 @@
<?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\Tests\Loader;
use Symfony\Component\Translation\Loader\IniFileLoader;
use Symfony\Component\Config\Resource\FileResource;
class IniFileLoaderTest extends \PHPUnit_Framework_TestCase
{
public function testLoad()
{
$loader = new IniFileLoader();
$resource = __DIR__.'/../fixtures/resources.ini';
$catalogue = $loader->load($resource, 'en', 'domain1');
$this->assertEquals(array('foo' => 'bar'), $catalogue->all('domain1'));
$this->assertEquals('en', $catalogue->getLocale());
$this->assertEquals(array(new FileResource($resource)), $catalogue->getResources());
}
public function testLoadDoesNothingIfEmpty()
{
$loader = new IniFileLoader();
$resource = __DIR__.'/../fixtures/empty.ini';
$catalogue = $loader->load($resource, 'en', 'domain1');
$this->assertEquals(array(), $catalogue->all('domain1'));
$this->assertEquals('en', $catalogue->getLocale());
$this->assertEquals(array(new FileResource($resource)), $catalogue->getResources());
}
/**
* @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
*/
public function testLoadNonExistingResource()
{
$loader = new IniFileLoader();
$resource = __DIR__.'/../fixtures/non-existing.ini';
$loader->load($resource, 'en', 'domain1');
}
}

View File

@ -1,68 +0,0 @@
<?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\Tests\Loader;
use Symfony\Component\Translation\Loader\JsonFileLoader;
use Symfony\Component\Config\Resource\FileResource;
class JsonFileLoaderTest extends \PHPUnit_Framework_TestCase
{
protected function setUp()
{
if (!class_exists('Symfony\Component\Config\Loader\Loader')) {
$this->markTestSkipped('The "Config" component is not available');
}
}
public function testLoad()
{
$loader = new JsonFileLoader();
$resource = __DIR__.'/../fixtures/resources.json';
$catalogue = $loader->load($resource, 'en', 'domain1');
$this->assertEquals(array('foo' => 'bar'), $catalogue->all('domain1'));
$this->assertEquals('en', $catalogue->getLocale());
$this->assertEquals(array(new FileResource($resource)), $catalogue->getResources());
}
public function testLoadDoesNothingIfEmpty()
{
$loader = new JsonFileLoader();
$resource = __DIR__.'/../fixtures/empty.json';
$catalogue = $loader->load($resource, 'en', 'domain1');
$this->assertEquals(array(), $catalogue->all('domain1'));
$this->assertEquals('en', $catalogue->getLocale());
$this->assertEquals(array(new FileResource($resource)), $catalogue->getResources());
}
/**
* @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
*/
public function testLoadNonExistingResource()
{
$loader = new JsonFileLoader();
$resource = __DIR__.'/../fixtures/non-existing.json';
$loader->load($resource, 'en', 'domain1');
}
/**
* @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
* @expectedExceptionMessage Error parsing JSON - Syntax error, malformed JSON
*/
public function testParseException()
{
$loader = new JsonFileLoader();
$resource = __DIR__.'/../fixtures/malformed.json';
$loader->load($resource, 'en', 'domain1');
}
}

View File

@ -1,22 +0,0 @@
<?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\Tests\Loader;
abstract class LocalizedTestCase extends \PHPUnit_Framework_TestCase
{
protected function setUp()
{
if (!extension_loaded('intl')) {
$this->markTestSkipped('The "intl" extension is not available');
}
}
}

View File

@ -1,71 +0,0 @@
<?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\Tests\Loader;
use Symfony\Component\Translation\Loader\MoFileLoader;
use Symfony\Component\Config\Resource\FileResource;
class MoFileLoaderTest extends \PHPUnit_Framework_TestCase
{
public function testLoad()
{
$loader = new MoFileLoader();
$resource = __DIR__.'/../fixtures/resources.mo';
$catalogue = $loader->load($resource, 'en', 'domain1');
$this->assertEquals(array('foo' => 'bar'), $catalogue->all('domain1'));
$this->assertEquals('en', $catalogue->getLocale());
$this->assertEquals(array(new FileResource($resource)), $catalogue->getResources());
}
public function testLoadPlurals()
{
$loader = new MoFileLoader();
$resource = __DIR__.'/../fixtures/plurals.mo';
$catalogue = $loader->load($resource, 'en', 'domain1');
$this->assertEquals(array('foo' => 'bar', 'foos' => '{0} bar|{1} bars'), $catalogue->all('domain1'));
$this->assertEquals('en', $catalogue->getLocale());
$this->assertEquals(array(new FileResource($resource)), $catalogue->getResources());
}
/**
* @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
*/
public function testLoadNonExistingResource()
{
$loader = new MoFileLoader();
$resource = __DIR__.'/../fixtures/non-existing.mo';
$loader->load($resource, 'en', 'domain1');
}
/**
* @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
*/
public function testLoadInvalidResource()
{
$loader = new MoFileLoader();
$resource = __DIR__.'/../fixtures/empty.mo';
$loader->load($resource, 'en', 'domain1');
}
public function testLoadEmptyTranslation()
{
$loader = new MoFileLoader();
$resource = __DIR__.'/../fixtures/empty-translation.mo';
$catalogue = $loader->load($resource, 'en', 'message');
$this->assertEquals(array(), $catalogue->all('message'));
$this->assertEquals('en', $catalogue->getLocale());
$this->assertEquals(array(new FileResource($resource)), $catalogue->getResources());
}
}

View File

@ -1,49 +0,0 @@
<?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\Tests\Loader;
use Symfony\Component\Translation\Loader\PhpFileLoader;
use Symfony\Component\Config\Resource\FileResource;
class PhpFileLoaderTest extends \PHPUnit_Framework_TestCase
{
public function testLoad()
{
$loader = new PhpFileLoader();
$resource = __DIR__.'/../fixtures/resources.php';
$catalogue = $loader->load($resource, 'en', 'domain1');
$this->assertEquals(array('foo' => 'bar'), $catalogue->all('domain1'));
$this->assertEquals('en', $catalogue->getLocale());
$this->assertEquals(array(new FileResource($resource)), $catalogue->getResources());
}
/**
* @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
*/
public function testLoadNonExistingResource()
{
$loader = new PhpFileLoader();
$resource = __DIR__.'/../fixtures/non-existing.php';
$loader->load($resource, 'en', 'domain1');
}
/**
* @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
*/
public function testLoadThrowsAnExceptionIfFileNotLocal()
{
$loader = new PhpFileLoader();
$resource = 'http://example.com/resources.php';
$loader->load($resource, 'en', 'domain1');
}
}

View File

@ -1,96 +0,0 @@
<?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\Tests\Loader;
use Symfony\Component\Translation\Loader\PoFileLoader;
use Symfony\Component\Config\Resource\FileResource;
class PoFileLoaderTest extends \PHPUnit_Framework_TestCase
{
public function testLoad()
{
$loader = new PoFileLoader();
$resource = __DIR__.'/../fixtures/resources.po';
$catalogue = $loader->load($resource, 'en', 'domain1');
$this->assertEquals(array('foo' => 'bar'), $catalogue->all('domain1'));
$this->assertEquals('en', $catalogue->getLocale());
$this->assertEquals(array(new FileResource($resource)), $catalogue->getResources());
}
public function testLoadPlurals()
{
$loader = new PoFileLoader();
$resource = __DIR__.'/../fixtures/plurals.po';
$catalogue = $loader->load($resource, 'en', 'domain1');
$this->assertEquals(array('foo' => 'bar', 'foos' => 'bar|bars'), $catalogue->all('domain1'));
$this->assertEquals('en', $catalogue->getLocale());
$this->assertEquals(array(new FileResource($resource)), $catalogue->getResources());
}
public function testLoadDoesNothingIfEmpty()
{
$loader = new PoFileLoader();
$resource = __DIR__.'/../fixtures/empty.po';
$catalogue = $loader->load($resource, 'en', 'domain1');
$this->assertEquals(array(), $catalogue->all('domain1'));
$this->assertEquals('en', $catalogue->getLocale());
$this->assertEquals(array(new FileResource($resource)), $catalogue->getResources());
}
/**
* @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
*/
public function testLoadNonExistingResource()
{
$loader = new PoFileLoader();
$resource = __DIR__.'/../fixtures/non-existing.po';
$loader->load($resource, 'en', 'domain1');
}
public function testLoadEmptyTranslation()
{
$loader = new PoFileLoader();
$resource = __DIR__.'/../fixtures/empty-translation.po';
$catalogue = $loader->load($resource, 'en', 'domain1');
$this->assertEquals(array('foo' => ''), $catalogue->all('domain1'));
$this->assertEquals('en', $catalogue->getLocale());
$this->assertEquals(array(new FileResource($resource)), $catalogue->getResources());
}
public function testEscapedId()
{
$loader = new PoFileLoader();
$resource = __DIR__.'/../fixtures/escaped-id.po';
$catalogue = $loader->load($resource, 'en', 'domain1');
$messages = $catalogue->all('domain1');
$this->assertArrayHasKey('escaped "foo"', $messages);
$this->assertEquals('escaped "bar"', $messages['escaped "foo"']);
}
public function testEscapedIdPlurals()
{
$loader = new PoFileLoader();
$resource = __DIR__.'/../fixtures/escaped-id-plurals.po';
$catalogue = $loader->load($resource, 'en', 'domain1');
$messages = $catalogue->all('domain1');
$this->assertArrayHasKey('escaped "foo"', $messages);
$this->assertArrayHasKey('escaped "foos"', $messages);
$this->assertEquals('escaped "bar"', $messages['escaped "foo"']);
$this->assertEquals('escaped "bar"|escaped "bars"', $messages['escaped "foos"']);
}
}

View File

@ -1,67 +0,0 @@
<?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\Tests\Loader;
use Symfony\Component\Translation\Loader\QtFileLoader;
use Symfony\Component\Config\Resource\FileResource;
class QtFileLoaderTest extends \PHPUnit_Framework_TestCase
{
public function testLoad()
{
$loader = new QtFileLoader();
$resource = __DIR__.'/../fixtures/resources.ts';
$catalogue = $loader->load($resource, 'en', 'resources');
$this->assertEquals(array('foo' => 'bar'), $catalogue->all('resources'));
$this->assertEquals('en', $catalogue->getLocale());
$this->assertEquals(array(new FileResource($resource)), $catalogue->getResources());
}
/**
* @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
*/
public function testLoadNonExistingResource()
{
$loader = new QtFileLoader();
$resource = __DIR__.'/../fixtures/non-existing.ts';
$loader->load($resource, 'en', 'domain1');
}
/**
* @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
*/
public function testLoadNonLocalResource()
{
$loader = new QtFileLoader();
$resource = 'http://domain1.com/resources.ts';
$loader->load($resource, 'en', 'domain1');
}
/**
* @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
*/
public function testLoadInvalidResource()
{
$loader = new QtFileLoader();
$resource = __DIR__.'/../fixtures/invalid-xml-resources.xlf';
$loader->load($resource, 'en', 'domain1');
}
public function testLoadEmptyResource()
{
$loader = new QtFileLoader();
$resource = __DIR__.'/../fixtures/empty.xlf';
$this->setExpectedException('Symfony\Component\Translation\Exception\InvalidResourceException', sprintf('Unable to load "%s".', $resource));
$loader->load($resource, 'en', 'domain1');
}
}

View File

@ -1,143 +0,0 @@
<?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\Tests\Loader;
use Symfony\Component\Translation\Loader\XliffFileLoader;
use Symfony\Component\Config\Resource\FileResource;
class XliffFileLoaderTest extends \PHPUnit_Framework_TestCase
{
public function testLoad()
{
$loader = new XliffFileLoader();
$resource = __DIR__.'/../fixtures/resources.xlf';
$catalogue = $loader->load($resource, 'en', 'domain1');
$this->assertEquals('en', $catalogue->getLocale());
$this->assertEquals(array(new FileResource($resource)), $catalogue->getResources());
$this->assertSame(array(), libxml_get_errors());
$this->assertContainsOnly('string', $catalogue->all('domain1'));
}
public function testLoadWithInternalErrorsEnabled()
{
libxml_use_internal_errors(true);
$this->assertSame(array(), libxml_get_errors());
$loader = new XliffFileLoader();
$resource = __DIR__.'/../fixtures/resources.xlf';
$catalogue = $loader->load($resource, 'en', 'domain1');
$this->assertEquals('en', $catalogue->getLocale());
$this->assertEquals(array(new FileResource($resource)), $catalogue->getResources());
$this->assertSame(array(), libxml_get_errors());
}
public function testLoadWithResname()
{
$loader = new XliffFileLoader();
$catalogue = $loader->load(__DIR__.'/../fixtures/resname.xlf', 'en', 'domain1');
$this->assertEquals(array('foo' => 'bar', 'bar' => 'baz', 'baz' => 'foo'), $catalogue->all('domain1'));
}
public function testIncompleteResource()
{
$loader = new XliffFileLoader();
$catalogue = $loader->load(__DIR__.'/../fixtures/resources.xlf', 'en', 'domain1');
$this->assertEquals(array('foo' => 'bar', 'extra' => 'extra', 'key' => '', 'test' => 'with'), $catalogue->all('domain1'));
}
public function testEncoding()
{
if (!function_exists('iconv') && !function_exists('mb_convert_encoding')) {
$this->markTestSkipped('The iconv and mbstring extensions are not available.');
}
$loader = new XliffFileLoader();
$catalogue = $loader->load(__DIR__.'/../fixtures/encoding.xlf', 'en', 'domain1');
$this->assertEquals(utf8_decode('föö'), $catalogue->get('bar', 'domain1'));
$this->assertEquals(utf8_decode('bär'), $catalogue->get('foo', 'domain1'));
$this->assertEquals(array('notes' => array(array('content' => utf8_decode('bäz')))), $catalogue->getMetadata('foo', 'domain1'));
}
/**
* @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
*/
public function testLoadInvalidResource()
{
$loader = new XliffFileLoader();
$loader->load(__DIR__.'/../fixtures/resources.php', 'en', 'domain1');
}
/**
* @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
*/
public function testLoadResourceDoesNotValidate()
{
$loader = new XliffFileLoader();
$loader->load(__DIR__.'/../fixtures/non-valid.xlf', 'en', 'domain1');
}
/**
* @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
*/
public function testLoadNonExistingResource()
{
$loader = new XliffFileLoader();
$resource = __DIR__.'/../fixtures/non-existing.xlf';
$loader->load($resource, 'en', 'domain1');
}
/**
* @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
*/
public function testLoadThrowsAnExceptionIfFileNotLocal()
{
$loader = new XliffFileLoader();
$resource = 'http://example.com/resources.xlf';
$loader->load($resource, 'en', 'domain1');
}
/**
* @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
* @expectedExceptionMessage Document types are not allowed.
*/
public function testDocTypeIsNotAllowed()
{
$loader = new XliffFileLoader();
$loader->load(__DIR__.'/../fixtures/withdoctype.xlf', 'en', 'domain1');
}
public function testParseEmptyFile()
{
$loader = new XliffFileLoader();
$resource = __DIR__.'/../fixtures/empty.xlf';
$this->setExpectedException('Symfony\Component\Translation\Exception\InvalidResourceException', sprintf('Unable to load "%s":', $resource));
$loader->load($resource, 'en', 'domain1');
}
public function testLoadNotes()
{
$loader = new XliffFileLoader();
$catalogue = $loader->load(__DIR__.'/../fixtures/withnote.xlf', 'en', 'domain1');
$this->assertEquals(array('notes' => array(array('priority' => 1, 'content' => 'foo'))), $catalogue->getMetadata('foo', 'domain1'));
// message without target
$this->assertEquals(array('notes' => array(array('content' => 'bar', 'from' => 'foo'))), $catalogue->getMetadata('extra', 'domain1'));
// message with empty target
$this->assertEquals(array('notes' => array(array('content' => 'baz'), array('priority' => 2, 'from' => 'bar', 'content' => 'qux'))), $catalogue->getMetadata('key', 'domain1'));
}
}

View File

@ -1,70 +0,0 @@
<?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\Tests\Loader;
use Symfony\Component\Translation\Loader\YamlFileLoader;
use Symfony\Component\Config\Resource\FileResource;
class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
{
public function testLoad()
{
$loader = new YamlFileLoader();
$resource = __DIR__.'/../fixtures/resources.yml';
$catalogue = $loader->load($resource, 'en', 'domain1');
$this->assertEquals(array('foo' => 'bar'), $catalogue->all('domain1'));
$this->assertEquals('en', $catalogue->getLocale());
$this->assertEquals(array(new FileResource($resource)), $catalogue->getResources());
}
public function testLoadDoesNothingIfEmpty()
{
$loader = new YamlFileLoader();
$resource = __DIR__.'/../fixtures/empty.yml';
$catalogue = $loader->load($resource, 'en', 'domain1');
$this->assertEquals(array(), $catalogue->all('domain1'));
$this->assertEquals('en', $catalogue->getLocale());
$this->assertEquals(array(new FileResource($resource)), $catalogue->getResources());
}
/**
* @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
*/
public function testLoadNonExistingResource()
{
$loader = new YamlFileLoader();
$resource = __DIR__.'/../fixtures/non-existing.yml';
$loader->load($resource, 'en', 'domain1');
}
/**
* @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
*/
public function testLoadThrowsAnExceptionIfFileNotLocal()
{
$loader = new YamlFileLoader();
$resource = 'http://example.com/resources.yml';
$loader->load($resource, 'en', 'domain1');
}
/**
* @expectedException \Symfony\Component\Translation\Exception\InvalidResourceException
*/
public function testLoadThrowsAnExceptionIfNotAnArray()
{
$loader = new YamlFileLoader();
$resource = __DIR__.'/../fixtures/non-valid.yml';
$loader->load($resource, 'en', 'domain1');
}
}

View File

@ -1,56 +0,0 @@
<?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\Tests;
use Symfony\Component\Translation\Translator;
use Symfony\Component\Translation\LoggingTranslator;
use Symfony\Component\Translation\Loader\ArrayLoader;
class LoggingTranslatorTest extends \PHPUnit_Framework_TestCase
{
protected function setUp()
{
if (!interface_exists('Psr\Log\LoggerInterface')) {
$this->markTestSkipped('The "LoggerInterface" is not available');
}
}
public function testTransWithNoTranslationIsLogged()
{
$logger = $this->getMock('Psr\Log\LoggerInterface');
$logger->expects($this->exactly(2))
->method('warning')
->with('Translation not found.')
;
$translator = new Translator('ar');
$loggableTranslator = new LoggingTranslator($translator, $logger);
$loggableTranslator->transChoice('some_message2', 10, array('%count%' => 10));
$loggableTranslator->trans('bar');
}
public function testTransChoiceFallbackIsLogged()
{
$logger = $this->getMock('Psr\Log\LoggerInterface');
$logger->expects($this->once())
->method('debug')
->with('Translation use fallback catalogue.')
;
$translator = new Translator('ar');
$translator->setFallbackLocales(array('en'));
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', array('some_message2' => 'one thing|%count% things'), 'en');
$loggableTranslator = new LoggingTranslator($translator, $logger);
$loggableTranslator->transChoice('some_message2', 10, array('%count%' => 10));
}
}

View File

@ -1,214 +0,0 @@
<?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\Tests;
use Symfony\Component\Translation\MessageCatalogue;
class MessageCatalogueTest extends \PHPUnit_Framework_TestCase
{
public function testGetLocale()
{
$catalogue = new MessageCatalogue('en');
$this->assertEquals('en', $catalogue->getLocale());
}
public function testGetDomains()
{
$catalogue = new MessageCatalogue('en', array('domain1' => array(), 'domain2' => array()));
$this->assertEquals(array('domain1', 'domain2'), $catalogue->getDomains());
}
public function testAll()
{
$catalogue = new MessageCatalogue('en', $messages = array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar')));
$this->assertEquals(array('foo' => 'foo'), $catalogue->all('domain1'));
$this->assertEquals(array(), $catalogue->all('domain88'));
$this->assertEquals($messages, $catalogue->all());
}
public function testHas()
{
$catalogue = new MessageCatalogue('en', array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar')));
$this->assertTrue($catalogue->has('foo', 'domain1'));
$this->assertFalse($catalogue->has('bar', 'domain1'));
$this->assertFalse($catalogue->has('foo', 'domain88'));
}
public function testGetSet()
{
$catalogue = new MessageCatalogue('en', array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar')));
$catalogue->set('foo1', 'foo1', 'domain1');
$this->assertEquals('foo', $catalogue->get('foo', 'domain1'));
$this->assertEquals('foo1', $catalogue->get('foo1', 'domain1'));
}
public function testAdd()
{
$catalogue = new MessageCatalogue('en', array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar')));
$catalogue->add(array('foo1' => 'foo1'), 'domain1');
$this->assertEquals('foo', $catalogue->get('foo', 'domain1'));
$this->assertEquals('foo1', $catalogue->get('foo1', 'domain1'));
$catalogue->add(array('foo' => 'bar'), 'domain1');
$this->assertEquals('bar', $catalogue->get('foo', 'domain1'));
$this->assertEquals('foo1', $catalogue->get('foo1', 'domain1'));
$catalogue->add(array('foo' => 'bar'), 'domain88');
$this->assertEquals('bar', $catalogue->get('foo', 'domain88'));
}
public function testReplace()
{
$catalogue = new MessageCatalogue('en', array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar')));
$catalogue->replace($messages = array('foo1' => 'foo1'), 'domain1');
$this->assertEquals($messages, $catalogue->all('domain1'));
}
public function testAddCatalogue()
{
$r = $this->getMock('Symfony\Component\Config\Resource\ResourceInterface');
$r->expects($this->any())->method('__toString')->will($this->returnValue('r'));
$r1 = $this->getMock('Symfony\Component\Config\Resource\ResourceInterface');
$r1->expects($this->any())->method('__toString')->will($this->returnValue('r1'));
$catalogue = new MessageCatalogue('en', array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar')));
$catalogue->addResource($r);
$catalogue1 = new MessageCatalogue('en', array('domain1' => array('foo1' => 'foo1')));
$catalogue1->addResource($r1);
$catalogue->addCatalogue($catalogue1);
$this->assertEquals('foo', $catalogue->get('foo', 'domain1'));
$this->assertEquals('foo1', $catalogue->get('foo1', 'domain1'));
$this->assertEquals(array($r, $r1), $catalogue->getResources());
}
public function testAddFallbackCatalogue()
{
$r = $this->getMock('Symfony\Component\Config\Resource\ResourceInterface');
$r->expects($this->any())->method('__toString')->will($this->returnValue('r'));
$r1 = $this->getMock('Symfony\Component\Config\Resource\ResourceInterface');
$r1->expects($this->any())->method('__toString')->will($this->returnValue('r1'));
$catalogue = new MessageCatalogue('en_US', array('domain1' => array('foo' => 'foo'), 'domain2' => array('bar' => 'bar')));
$catalogue->addResource($r);
$catalogue1 = new MessageCatalogue('en', array('domain1' => array('foo' => 'bar', 'foo1' => 'foo1')));
$catalogue1->addResource($r1);
$catalogue->addFallbackCatalogue($catalogue1);
$this->assertEquals('foo', $catalogue->get('foo', 'domain1'));
$this->assertEquals('foo1', $catalogue->get('foo1', 'domain1'));
$this->assertEquals(array($r, $r1), $catalogue->getResources());
}
/**
* @expectedException \LogicException
*/
public function testAddFallbackCatalogueWithParentCircularReference()
{
$main = new MessageCatalogue('en_US');
$fallback = new MessageCatalogue('fr_FR');
$fallback->addFallbackCatalogue($main);
$main->addFallbackCatalogue($fallback);
}
/**
* @expectedException \LogicException
*/
public function testAddFallbackCatalogueWithFallbackCircularReference()
{
$fr = new MessageCatalogue('fr');
$en = new MessageCatalogue('en');
$es = new MessageCatalogue('es');
$fr->addFallbackCatalogue($en);
$es->addFallbackCatalogue($en);
$en->addFallbackCatalogue($fr);
}
/**
* @expectedException \LogicException
*/
public function testAddCatalogueWhenLocaleIsNotTheSameAsTheCurrentOne()
{
$catalogue = new MessageCatalogue('en');
$catalogue->addCatalogue(new MessageCatalogue('fr', array()));
}
public function testGetAddResource()
{
$catalogue = new MessageCatalogue('en');
$r = $this->getMock('Symfony\Component\Config\Resource\ResourceInterface');
$r->expects($this->any())->method('__toString')->will($this->returnValue('r'));
$catalogue->addResource($r);
$catalogue->addResource($r);
$r1 = $this->getMock('Symfony\Component\Config\Resource\ResourceInterface');
$r1->expects($this->any())->method('__toString')->will($this->returnValue('r1'));
$catalogue->addResource($r1);
$this->assertEquals(array($r, $r1), $catalogue->getResources());
}
public function testMetadataDelete()
{
$catalogue = new MessageCatalogue('en');
$this->assertEquals(array(), $catalogue->getMetadata('', ''), 'Metadata is empty');
$catalogue->deleteMetadata('key', 'messages');
$catalogue->deleteMetadata('', 'messages');
$catalogue->deleteMetadata();
}
public function testMetadataSetGetDelete()
{
$catalogue = new MessageCatalogue('en');
$catalogue->setMetadata('key', 'value');
$this->assertEquals('value', $catalogue->getMetadata('key', 'messages'), "Metadata 'key' = 'value'");
$catalogue->setMetadata('key2', array());
$this->assertEquals(array(), $catalogue->getMetadata('key2', 'messages'), 'Metadata key2 is array');
$catalogue->deleteMetadata('key2', 'messages');
$this->assertNull($catalogue->getMetadata('key2', 'messages'), 'Metadata key2 should is deleted.');
$catalogue->deleteMetadata('key2', 'domain');
$this->assertNull($catalogue->getMetadata('key2', 'domain'), 'Metadata key2 should is deleted.');
}
public function testMetadataMerge()
{
$cat1 = new MessageCatalogue('en');
$cat1->setMetadata('a', 'b');
$this->assertEquals(array('messages' => array('a' => 'b')), $cat1->getMetadata('', ''), 'Cat1 contains messages metadata.');
$cat2 = new MessageCatalogue('en');
$cat2->setMetadata('b', 'c', 'domain');
$this->assertEquals(array('domain' => array('b' => 'c')), $cat2->getMetadata('', ''), 'Cat2 contains domain metadata.');
$cat1->addCatalogue($cat2);
$this->assertEquals(array('messages' => array('a' => 'b'), 'domain' => array('b' => 'c')), $cat1->getMetadata('', ''), 'Cat1 contains merged metadata.');
}
}

View File

@ -1,130 +0,0 @@
<?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\Tests;
use Symfony\Component\Translation\MessageSelector;
class MessageSelectorTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider getChooseTests
*/
public function testChoose($expected, $id, $number)
{
$selector = new MessageSelector();
$this->assertEquals($expected, $selector->choose($id, $number, 'en'));
}
public function testReturnMessageIfExactlyOneStandardRuleIsGiven()
{
$selector = new MessageSelector();
$this->assertEquals('There are two apples', $selector->choose('There are two apples', 2, 'en'));
}
/**
* @dataProvider getNonMatchingMessages
* @expectedException \InvalidArgumentException
*/
public function testThrowExceptionIfMatchingMessageCannotBeFound($id, $number)
{
$selector = new MessageSelector();
$selector->choose($id, $number, 'en');
}
public function getNonMatchingMessages()
{
return array(
array('{0} There are no apples|{1} There is one apple', 2),
array('{1} There is one apple|]1,Inf] There are %count% apples', 0),
array('{1} There is one apple|]2,Inf] There are %count% apples', 2),
array('{0} There are no apples|There is one apple', 2),
);
}
public function getChooseTests()
{
return array(
array('There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0),
array('There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0),
array('There are no apples', '{0}There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0),
array('There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1),
array('There are %count% apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10),
array('There are %count% apples', '{0} There are no apples|{1} There is one apple|]1,Inf]There are %count% apples', 10),
array('There are %count% apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10),
array('There are %count% apples', 'There is one apple|There are %count% apples', 0),
array('There is one apple', 'There is one apple|There are %count% apples', 1),
array('There are %count% apples', 'There is one apple|There are %count% apples', 10),
array('There are %count% apples', 'one: There is one apple|more: There are %count% apples', 0),
array('There is one apple', 'one: There is one apple|more: There are %count% apples', 1),
array('There are %count% apples', 'one: There is one apple|more: There are %count% apples', 10),
array('There are no apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 0),
array('There is one apple', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 1),
array('There are %count% apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 10),
array('', '{0}|{1} There is one apple|]1,Inf] There are %count% apples', 0),
array('', '{0} There are no apples|{1}|]1,Inf] There are %count% apples', 1),
// Indexed only tests which are Gettext PoFile* compatible strings.
array('There are %count% apples', 'There is one apple|There are %count% apples', 0),
array('There is one apple', 'There is one apple|There are %count% apples', 1),
array('There are %count% apples', 'There is one apple|There are %count% apples', 2),
// Tests for float numbers
array('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),
array('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),
array('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),
array('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),
array('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),
array('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
array("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
array("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),
array("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
array('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
array('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
array('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
array("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),
);
}
}

View File

@ -1,123 +0,0 @@
<?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\Tests;
use Symfony\Component\Translation\PluralizationRules;
/**
* 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 PluralizationRulesTest extends \PHPUnit_Framework_TestCase
{
/**
* We test failed langcode here.
*
* TODO: The languages mentioned in the data provide need to get fixed somehow within PluralizationRules.
*
* @dataProvider failingLangcodes
*/
public function testFailedLangcodes($nplural, $langCodes)
{
$matrix = $this->generateTestData($nplural, $langCodes);
$this->validateMatrix($nplural, $matrix, false);
}
/**
* @dataProvider successLangcodes
*/
public function testLangcodes($nplural, $langCodes)
{
$matrix = $this->generateTestData($nplural, $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 array(
array('1', array('ay','bo', 'cgg','dz','id', 'ja', 'jbo', 'ka','kk','km','ko','ky')),
array('2', array('nl', 'fr', 'en', 'de', 'de_GE')),
array('3', array('be','bs','cs','hr')),
array('4', array('cy','mt', 'sl')),
array('5', array()),
array('6', array('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 array(
array('1', array('fa')),
array('2', array('jbo')),
array('3', array('cbs')),
array('4', array('gd','kw')),
array('5', array('ga')),
array('6', array()),
);
}
/**
* 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($plural, $langCodes)
{
$matrix = array();
foreach ($langCodes as $langCode) {
for ($count = 0; $count < 200; ++$count) {
$plural = PluralizationRules::get($count, $langCode);
$matrix[$langCode][$count] = $plural;
}
}
return $matrix;
}
}

View File

@ -1,299 +0,0 @@
<?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\Tests;
use Symfony\Component\Config\Resource\ResourceInterface;
use Symfony\Component\Translation\Loader\ArrayLoader;
use Symfony\Component\Translation\Loader\LoaderInterface;
use Symfony\Component\Translation\Translator;
use Symfony\Component\Translation\MessageCatalogue;
class TranslatorCacheTest extends \PHPUnit_Framework_TestCase
{
protected $tmpDir;
protected function setUp()
{
$this->tmpDir = sys_get_temp_dir().'/sf2_translation';
$this->deleteTmpDir();
}
protected function tearDown()
{
$this->deleteTmpDir();
}
protected function deleteTmpDir()
{
if (!file_exists($dir = $this->tmpDir)) {
return;
}
$iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($this->tmpDir), \RecursiveIteratorIterator::CHILD_FIRST);
foreach ($iterator as $path) {
if (preg_match('#[/\\\\]\.\.?$#', $path->__toString())) {
continue;
}
if ($path->isDir()) {
rmdir($path->__toString());
} else {
unlink($path->__toString());
}
}
rmdir($this->tmpDir);
}
/**
* @dataProvider runForDebugAndProduction
*/
public function testThatACacheIsUsed($debug)
{
$locale = 'any_locale';
$format = 'some_format';
$msgid = 'test';
// Prime the cache
$translator = new Translator($locale, null, $this->tmpDir, $debug);
$translator->addLoader($format, new ArrayLoader());
$translator->addResource($format, array($msgid => 'OK'), $locale);
$translator->trans($msgid);
// Try again and see we get a valid result whilst no loader can be used
$translator = new Translator($locale, null, $this->tmpDir, $debug);
$translator->addLoader($format, $this->createFailingLoader());
$translator->addResource($format, array($msgid => 'OK'), $locale);
$this->assertEquals('OK', $translator->trans($msgid), '-> caching does not work in '.($debug ? 'debug' : 'production'));
}
public function testCatalogueIsReloadedWhenResourcesAreNoLongerFresh()
{
/*
* The testThatACacheIsUsed() test showed that we don't need the loader as long as the cache
* is fresh.
*
* Now we add a Resource that is never fresh and make sure that the
* cache is discarded (the loader is called twice).
*
* We need to run this for debug=true only because in production the cache
* will never be revalidated.
*/
$locale = 'any_locale';
$format = 'some_format';
$msgid = 'test';
$catalogue = new MessageCatalogue($locale, array());
$catalogue->addResource(new StaleResource()); // better use a helper class than a mock, because it gets serialized in the cache and re-loaded
/** @var LoaderInterface|\PHPUnit_Framework_MockObject_MockObject $loader */
$loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface');
$loader
->expects($this->exactly(2))
->method('load')
->will($this->returnValue($catalogue))
;
// 1st pass
$translator = new Translator($locale, null, $this->tmpDir, true);
$translator->addLoader($format, $loader);
$translator->addResource($format, null, $locale);
$translator->trans($msgid);
// 2nd pass
$translator = new Translator($locale, null, $this->tmpDir, true);
$translator->addLoader($format, $loader);
$translator->addResource($format, null, $locale);
$translator->trans($msgid);
}
/**
* @dataProvider runForDebugAndProduction
*/
public function testDifferentTranslatorsForSameLocaleDoNotOverwriteEachOthersCache($debug)
{
/*
* Similar to the previous test. After we used the second translator, make
* sure there's still a useable cache for the first one.
*/
$locale = 'any_locale';
$format = 'some_format';
$msgid = 'test';
// Create a Translator and prime its cache
$translator = new Translator($locale, null, $this->tmpDir, $debug);
$translator->addLoader($format, new ArrayLoader());
$translator->addResource($format, array($msgid => 'OK'), $locale);
$translator->trans($msgid);
// Create another Translator with a different catalogue for the same locale
$translator = new Translator($locale, null, $this->tmpDir, $debug);
$translator->addLoader($format, new ArrayLoader());
$translator->addResource($format, array($msgid => 'FAIL'), $locale);
$translator->trans($msgid);
// Now the first translator must still have a useable cache.
$translator = new Translator($locale, null, $this->tmpDir, $debug);
$translator->addLoader($format, $this->createFailingLoader());
$translator->addResource($format, array($msgid => 'OK'), $locale);
$this->assertEquals('OK', $translator->trans($msgid), '-> the cache was overwritten by another translator instance in '.($debug ? 'debug' : 'production'));
}
public function testDifferentCacheFilesAreUsedForDifferentSetsOfFallbackLocales()
{
/*
* Because the cache file contains a catalogue including all of its fallback
* catalogues, we must take the set of fallback locales into consideration when
* loading a catalogue from the cache.
*/
$translator = new Translator('a', null, $this->tmpDir);
$translator->setFallbackLocales(array('b'));
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', array('foo' => 'foo (a)'), 'a');
$translator->addResource('array', array('bar' => 'bar (b)'), 'b');
$this->assertEquals('bar (b)', $translator->trans('bar'));
// Remove fallback locale
$translator->setFallbackLocales(array());
$this->assertEquals('bar', $translator->trans('bar'));
// Use a fresh translator with no fallback locales, result should be the same
$translator = new Translator('a', null, $this->tmpDir);
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', array('foo' => 'foo (a)'), 'a');
$translator->addResource('array', array('bar' => 'bar (b)'), 'b');
$this->assertEquals('bar', $translator->trans('bar'));
}
public function testPrimaryAndFallbackCataloguesContainTheSameMessagesRegardlessOfCaching()
{
/*
* As a safeguard against potential BC breaks, make sure that primary and fallback
* catalogues (reachable via getFallbackCatalogue()) always contain the full set of
* messages provided by the loader. This must also be the case when these catalogues
* are (internally) read from a cache.
*
* Optimizations inside the translator must not change this behaviour.
*/
/*
* Create a translator that loads two catalogues for two different locales.
* The catalogues contain distinct sets of messages.
*/
$translator = new Translator('a', null, $this->tmpDir);
$translator->setFallbackLocales(array('b'));
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', array('foo' => 'foo (a)'), 'a');
$translator->addResource('array', array('foo' => 'foo (b)'), 'b');
$translator->addResource('array', array('bar' => 'bar (b)'), 'b');
$catalogue = $translator->getCatalogue('a');
$this->assertFalse($catalogue->defines('bar')); // Sure, the "a" catalogue does not contain that message.
$fallback = $catalogue->getFallbackCatalogue();
$this->assertTrue($fallback->defines('foo')); // "foo" is present in "a" and "b"
/*
* Now, repeat the same test.
* Behind the scenes, the cache is used. But that should not matter, right?
*/
$translator = new Translator('a', null, $this->tmpDir);
$translator->setFallbackLocales(array('b'));
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', array('foo' => 'foo (a)'), 'a');
$translator->addResource('array', array('foo' => 'foo (b)'), 'b');
$translator->addResource('array', array('bar' => 'bar (b)'), 'b');
$catalogue = $translator->getCatalogue('a');
$this->assertFalse($catalogue->defines('bar'));
$fallback = $catalogue->getFallbackCatalogue();
$this->assertTrue($fallback->defines('foo'));
}
public function testRefreshCacheWhenResourcesAreNoLongerFresh()
{
$resource = $this->getMock('Symfony\Component\Config\Resource\ResourceInterface');
$loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface');
$resource->method('isFresh')->will($this->returnValue(false));
$loader
->expects($this->exactly(2))
->method('load')
->will($this->returnValue($this->getCatalogue('fr', array(), array($resource))));
// prime the cache
$translator = new Translator('fr', null, $this->tmpDir, true);
$translator->addLoader('loader', $loader);
$translator->addResource('loader', 'foo', 'fr');
$translator->trans('foo');
// prime the cache second time
$translator = new Translator('fr', null, $this->tmpDir, true);
$translator->addLoader('loader', $loader);
$translator->addResource('loader', 'foo', 'fr');
$translator->trans('foo');
}
protected function getCatalogue($locale, $messages, $resources = array())
{
$catalogue = new MessageCatalogue($locale);
foreach ($messages as $key => $translation) {
$catalogue->set($key, $translation);
}
foreach ($resources as $resource) {
$catalogue->addResource($resource);
}
return $catalogue;
}
public function runForDebugAndProduction()
{
return array(array(true), array(false));
}
/**
* @return LoaderInterface
*/
private function createFailingLoader()
{
$loader = $this->getMock('Symfony\Component\Translation\Loader\LoaderInterface');
$loader
->expects($this->never())
->method('load');
return $loader;
}
}
class StaleResource implements ResourceInterface
{
public function isFresh($timestamp)
{
return false;
}
public function getResource()
{
}
public function __toString()
{
return '';
}
}

View File

@ -1,627 +0,0 @@
<?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\Tests;
use Symfony\Component\Translation\Translator;
use Symfony\Component\Translation\MessageSelector;
use Symfony\Component\Translation\Loader\ArrayLoader;
use Symfony\Component\Translation\MessageCatalogue;
class TranslatorTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider getInvalidLocalesTests
* @expectedException \InvalidArgumentException
*/
public function testConstructorInvalidLocale($locale)
{
$translator = new Translator($locale, new MessageSelector());
}
/**
* @dataProvider getValidLocalesTests
*/
public function testConstructorValidLocale($locale)
{
$translator = new Translator($locale, new MessageSelector());
$this->assertEquals($locale, $translator->getLocale());
}
public function testConstructorWithoutLocale()
{
$translator = new Translator(null, new MessageSelector());
$this->assertNull($translator->getLocale());
}
public function testSetGetLocale()
{
$translator = new Translator('en');
$this->assertEquals('en', $translator->getLocale());
$translator->setLocale('fr');
$this->assertEquals('fr', $translator->getLocale());
}
/**
* @dataProvider getInvalidLocalesTests
* @expectedException \InvalidArgumentException
*/
public function testSetInvalidLocale($locale)
{
$translator = new Translator('fr', new MessageSelector());
$translator->setLocale($locale);
}
/**
* @dataProvider getValidLocalesTests
*/
public function testSetValidLocale($locale)
{
$translator = new Translator($locale, new MessageSelector());
$translator->setLocale($locale);
$this->assertEquals($locale, $translator->getLocale());
}
public function testGetCatalogue()
{
$translator = new Translator('en');
$this->assertEquals(new MessageCatalogue('en'), $translator->getCatalogue());
$translator->setLocale('fr');
$this->assertEquals(new MessageCatalogue('fr'), $translator->getCatalogue('fr'));
}
public function testGetCatalogueReturnsConsolidatedCatalogue()
{
/*
* This will be useful once we refactor so that different domains will be loaded lazily (on-demand).
* In that case, getCatalogue() will probably have to load all missing domains in order to return
* one complete catalogue.
*/
$locale = 'whatever';
$translator = new Translator($locale);
$translator->addLoader('loader-a', new ArrayLoader());
$translator->addLoader('loader-b', new ArrayLoader());
$translator->addResource('loader-a', array('foo' => 'foofoo'), $locale, 'domain-a');
$translator->addResource('loader-b', array('bar' => 'foobar'), $locale, 'domain-b');
/*
* Test that we get a single catalogue comprising messages
* from different loaders and different domains
*/
$catalogue = $translator->getCatalogue($locale);
$this->assertTrue($catalogue->defines('foo', 'domain-a'));
$this->assertTrue($catalogue->defines('bar', 'domain-b'));
}
public function testSetFallbackLocales()
{
$translator = new Translator('en');
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', array('foo' => 'foofoo'), 'en');
$translator->addResource('array', array('bar' => 'foobar'), 'fr');
// force catalogue loading
$translator->trans('bar');
$translator->setFallbackLocales(array('fr'));
$this->assertEquals('foobar', $translator->trans('bar'));
}
public function testSetFallbackLocalesMultiple()
{
$translator = new Translator('en');
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', array('foo' => 'foo (en)'), 'en');
$translator->addResource('array', array('bar' => 'bar (fr)'), 'fr');
// force catalogue loading
$translator->trans('bar');
$translator->setFallbackLocales(array('fr_FR', 'fr'));
$this->assertEquals('bar (fr)', $translator->trans('bar'));
}
/**
* @dataProvider getInvalidLocalesTests
* @expectedException \InvalidArgumentException
*/
public function testSetFallbackInvalidLocales($locale)
{
$translator = new Translator('fr', new MessageSelector());
$translator->setFallbackLocales(array('fr', $locale));
}
/**
* @dataProvider getValidLocalesTests
*/
public function testSetFallbackValidLocales($locale)
{
$translator = new Translator($locale, new MessageSelector());
$translator->setFallbackLocales(array('fr', $locale));
// no assertion. this method just asserts that no exception is thrown
}
public function testTransWithFallbackLocale()
{
$translator = new Translator('fr_FR');
$translator->setFallbackLocales(array('en'));
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', array('bar' => 'foobar'), 'en');
$this->assertEquals('foobar', $translator->trans('bar'));
}
/**
* @dataProvider getInvalidLocalesTests
* @expectedException \InvalidArgumentException
*/
public function testAddResourceInvalidLocales($locale)
{
$translator = new Translator('fr', new MessageSelector());
$translator->addResource('array', array('foo' => 'foofoo'), $locale);
}
/**
* @dataProvider getValidLocalesTests
*/
public function testAddResourceValidLocales($locale)
{
$translator = new Translator('fr', new MessageSelector());
$translator->addResource('array', array('foo' => 'foofoo'), $locale);
// no assertion. this method just asserts that no exception is thrown
}
public function testAddResourceAfterTrans()
{
$translator = new Translator('fr');
$translator->addLoader('array', new ArrayLoader());
$translator->setFallbackLocales(array('en'));
$translator->addResource('array', array('foo' => 'foofoo'), 'en');
$this->assertEquals('foofoo', $translator->trans('foo'));
$translator->addResource('array', array('bar' => 'foobar'), 'en');
$this->assertEquals('foobar', $translator->trans('bar'));
}
/**
* @dataProvider getTransFileTests
* @expectedException \Symfony\Component\Translation\Exception\NotFoundResourceException
*/
public function testTransWithoutFallbackLocaleFile($format, $loader)
{
$loaderClass = 'Symfony\\Component\\Translation\\Loader\\'.$loader;
$translator = new Translator('en');
$translator->addLoader($format, new $loaderClass());
$translator->addResource($format, __DIR__.'/fixtures/non-existing', 'en');
$translator->addResource($format, __DIR__.'/fixtures/resources.'.$format, 'en');
// force catalogue loading
$translator->trans('foo');
}
/**
* @dataProvider getTransFileTests
*/
public function testTransWithFallbackLocaleFile($format, $loader)
{
$loaderClass = 'Symfony\\Component\\Translation\\Loader\\'.$loader;
$translator = new Translator('en_GB');
$translator->addLoader($format, new $loaderClass());
$translator->addResource($format, __DIR__.'/fixtures/non-existing', 'en_GB');
$translator->addResource($format, __DIR__.'/fixtures/resources.'.$format, 'en', 'resources');
$this->assertEquals('bar', $translator->trans('foo', array(), 'resources'));
}
public function testTransWithFallbackLocaleBis()
{
$translator = new Translator('en_US');
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', array('foo' => 'foofoo'), 'en_US');
$translator->addResource('array', array('bar' => 'foobar'), 'en');
$this->assertEquals('foobar', $translator->trans('bar'));
}
public function testTransWithFallbackLocaleTer()
{
$translator = new Translator('fr_FR');
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', array('foo' => 'foo (en_US)'), 'en_US');
$translator->addResource('array', array('bar' => 'bar (en)'), 'en');
$translator->setFallbackLocales(array('en_US', 'en'));
$this->assertEquals('foo (en_US)', $translator->trans('foo'));
$this->assertEquals('bar (en)', $translator->trans('bar'));
}
public function testTransNonExistentWithFallback()
{
$translator = new Translator('fr');
$translator->setFallbackLocales(array('en'));
$translator->addLoader('array', new ArrayLoader());
$this->assertEquals('non-existent', $translator->trans('non-existent'));
}
/**
* @expectedException \RuntimeException
*/
public function testWhenAResourceHasNoRegisteredLoader()
{
$translator = new Translator('en');
$translator->addResource('array', array('foo' => 'foofoo'), 'en');
$translator->trans('foo');
}
/**
* @dataProvider getTransTests
*/
public function testTrans($expected, $id, $translation, $parameters, $locale, $domain)
{
$translator = new Translator('en');
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', array((string) $id => $translation), $locale, $domain);
$this->assertEquals($expected, $translator->trans($id, $parameters, $domain, $locale));
}
/**
* @dataProvider getInvalidLocalesTests
* @expectedException \InvalidArgumentException
*/
public function testTransInvalidLocale($locale)
{
$translator = new Translator('en', new MessageSelector());
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', array('foo' => 'foofoo'), 'en');
$translator->trans('foo', array(), '', $locale);
}
/**
* @dataProvider getValidLocalesTests
*/
public function testTransValidLocale($locale)
{
$translator = new Translator($locale, new MessageSelector());
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', array('test' => 'OK'), $locale);
$this->assertEquals('OK', $translator->trans('test'));
$this->assertEquals('OK', $translator->trans('test', array(), null, $locale));
}
/**
* @dataProvider getFlattenedTransTests
*/
public function testFlattenedTrans($expected, $messages, $id)
{
$translator = new Translator('en');
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', $messages, 'fr', '');
$this->assertEquals($expected, $translator->trans($id, array(), '', 'fr'));
}
/**
* @dataProvider getTransChoiceTests
*/
public function testTransChoice($expected, $id, $translation, $number, $parameters, $locale, $domain)
{
$translator = new Translator('en');
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', array((string) $id => $translation), $locale, $domain);
$this->assertEquals($expected, $translator->transChoice($id, $number, $parameters, $domain, $locale));
}
/**
* @dataProvider getInvalidLocalesTests
* @expectedException \InvalidArgumentException
*/
public function testTransChoiceInvalidLocale($locale)
{
$translator = new Translator('en', new MessageSelector());
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', array('foo' => 'foofoo'), 'en');
$translator->transChoice('foo', 1, array(), '', $locale);
}
/**
* @dataProvider getValidLocalesTests
*/
public function testTransChoiceValidLocale($locale)
{
$translator = new Translator('en', new MessageSelector());
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', array('foo' => 'foofoo'), 'en');
$translator->transChoice('foo', 1, array(), '', $locale);
// no assertion. this method just asserts that no exception is thrown
}
public function getTransFileTests()
{
return array(
array('csv', 'CsvFileLoader'),
array('ini', 'IniFileLoader'),
array('mo', 'MoFileLoader'),
array('po', 'PoFileLoader'),
array('php', 'PhpFileLoader'),
array('ts', 'QtFileLoader'),
array('xlf', 'XliffFileLoader'),
array('yml', 'YamlFileLoader'),
array('json', 'JsonFileLoader'),
);
}
public function getTransTests()
{
return array(
array('Symfony est super !', 'Symfony is great!', 'Symfony est super !', array(), 'fr', ''),
array('Symfony est awesome !', 'Symfony is %what%!', 'Symfony est %what% !', array('%what%' => 'awesome'), 'fr', ''),
array('Symfony est super !', new StringClass('Symfony is great!'), 'Symfony est super !', array(), 'fr', ''),
);
}
public function getFlattenedTransTests()
{
$messages = array(
'symfony' => array(
'is' => array(
'great' => 'Symfony est super!',
),
),
'foo' => array(
'bar' => array(
'baz' => 'Foo Bar Baz',
),
'baz' => 'Foo Baz',
),
);
return array(
array('Symfony est super!', $messages, 'symfony.is.great'),
array('Foo Bar Baz', $messages, 'foo.bar.baz'),
array('Foo Baz', $messages, 'foo.baz'),
);
}
public function getTransChoiceTests()
{
return array(
array('Il y a 0 pomme', '{0} There are no appless|{1} There is one apple|]1,Inf] There is %count% apples', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''),
array('Il y a 1 pomme', '{0} There are no appless|{1} There is one apple|]1,Inf] There is %count% apples', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 1, array('%count%' => 1), 'fr', ''),
array('Il y a 10 pommes', '{0} There are no appless|{1} There is one apple|]1,Inf] There is %count% apples', '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 10, array('%count%' => 10), 'fr', ''),
array('Il y a 0 pomme', 'There is one apple|There is %count% apples', 'Il y a %count% pomme|Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''),
array('Il y a 1 pomme', 'There is one apple|There is %count% apples', 'Il y a %count% pomme|Il y a %count% pommes', 1, array('%count%' => 1), 'fr', ''),
array('Il y a 10 pommes', 'There is one apple|There is %count% apples', 'Il y a %count% pomme|Il y a %count% pommes', 10, array('%count%' => 10), 'fr', ''),
array('Il y a 0 pomme', 'one: There is one apple|more: There is %count% apples', 'one: Il y a %count% pomme|more: Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''),
array('Il y a 1 pomme', 'one: There is one apple|more: There is %count% apples', 'one: Il y a %count% pomme|more: Il y a %count% pommes', 1, array('%count%' => 1), 'fr', ''),
array('Il y a 10 pommes', 'one: There is one apple|more: There is %count% apples', 'one: Il y a %count% pomme|more: Il y a %count% pommes', 10, array('%count%' => 10), 'fr', ''),
array('Il n\'y a aucune pomme', '{0} There are no apples|one: There is one apple|more: There is %count% apples', '{0} Il n\'y a aucune pomme|one: Il y a %count% pomme|more: Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''),
array('Il y a 1 pomme', '{0} There are no apples|one: There is one apple|more: There is %count% apples', '{0} Il n\'y a aucune pomme|one: Il y a %count% pomme|more: Il y a %count% pommes', 1, array('%count%' => 1), 'fr', ''),
array('Il y a 10 pommes', '{0} There are no apples|one: There is one apple|more: There is %count% apples', '{0} Il n\'y a aucune pomme|one: Il y a %count% pomme|more: Il y a %count% pommes', 10, array('%count%' => 10), 'fr', ''),
array('Il y a 0 pomme', new StringClass('{0} There are no appless|{1} There is one apple|]1,Inf] There is %count% apples'), '[0,1] Il y a %count% pomme|]1,Inf] Il y a %count% pommes', 0, array('%count%' => 0), 'fr', ''),
);
}
public function getInvalidLocalesTests()
{
return array(
array('fr FR'),
array('français'),
array('fr+en'),
array('utf#8'),
array('fr&en'),
array('fr~FR'),
array(' fr'),
array('fr '),
array('fr*'),
array('fr/FR'),
array('fr\\FR'),
);
}
public function getValidLocalesTests()
{
return array(
array(''),
array(null),
array('fr'),
array('francais'),
array('FR'),
array('frFR'),
array('fr-FR'),
array('fr_FR'),
array('fr.FR'),
array('fr-FR.UTF8'),
array('sr@latin'),
);
}
public function testTransChoiceFallback()
{
$translator = new Translator('ru');
$translator->setFallbackLocales(array('en'));
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', array('some_message2' => 'one thing|%count% things'), 'en');
$this->assertEquals('10 things', $translator->transChoice('some_message2', 10, array('%count%' => 10)));
}
public function testTransChoiceFallbackBis()
{
$translator = new Translator('ru');
$translator->setFallbackLocales(array('en_US', 'en'));
$translator->addLoader('array', new ArrayLoader());
$translator->addResource('array', array('some_message2' => 'one thing|%count% things'), 'en_US');
$this->assertEquals('10 things', $translator->transChoice('some_message2', 10, array('%count%' => 10)));
}
public function testTransChoiceFallbackWithNoTranslation()
{
$translator = new Translator('ru');
$translator->setFallbackLocales(array('en'));
$translator->addLoader('array', new ArrayLoader());
// consistent behavior with Translator::trans(), which returns the string
// unchanged if it can't be found
$this->assertEquals('some_message2', $translator->transChoice('some_message2', 10, array('%count%' => 10)));
}
/**
* @dataProvider dataProviderGetMessages
*/
public function testGetMessages($resources, $locale, $expected)
{
$locales = array_keys($resources);
$_locale = null !== $locale ? $locale : reset($locales);
$locales = array_slice($locales, 0, array_search($_locale, $locales));
$translator = new Translator($_locale, new MessageSelector());
$translator->setFallbackLocales(array_reverse($locales));
$translator->addLoader('array', new ArrayLoader());
foreach ($resources as $_locale => $domainMessages) {
foreach ($domainMessages as $domain => $messages) {
$translator->addResource('array', $messages, $_locale, $domain);
}
}
$result = $translator->getMessages($locale);
$this->assertEquals($expected, $result);
}
public function dataProviderGetMessages()
{
$resources = array(
'en' => array(
'jsmessages' => array(
'foo' => 'foo (EN)',
'bar' => 'bar (EN)',
),
'messages' => array(
'foo' => 'foo messages (EN)',
),
'validators' => array(
'int' => 'integer (EN)',
),
),
'pt-PT' => array(
'messages' => array(
'foo' => 'foo messages (PT)',
),
'validators' => array(
'str' => 'integer (PT)',
),
),
'pt_BR' => array(
'validators' => array(
'int' => 'integer (BR)',
),
),
);
return array(
array($resources, null,
array(
'jsmessages' => array(
'foo' => 'foo (EN)',
'bar' => 'bar (EN)',
),
'messages' => array(
'foo' => 'foo messages (EN)',
),
'validators' => array(
'int' => 'integer (EN)',
),
),
),
array($resources, 'en',
array(
'jsmessages' => array(
'foo' => 'foo (EN)',
'bar' => 'bar (EN)',
),
'messages' => array(
'foo' => 'foo messages (EN)',
),
'validators' => array(
'int' => 'integer (EN)',
),
),
),
array($resources, 'pt-PT',
array(
'jsmessages' => array(
'foo' => 'foo (EN)',
'bar' => 'bar (EN)',
),
'messages' => array(
'foo' => 'foo messages (PT)',
),
'validators' => array(
'int' => 'integer (EN)',
'str' => 'integer (PT)',
),
),
),
array($resources, 'pt_BR',
array(
'jsmessages' => array(
'foo' => 'foo (EN)',
'bar' => 'bar (EN)',
),
'messages' => array(
'foo' => 'foo messages (PT)',
),
'validators' => array(
'int' => 'integer (BR)',
'str' => 'integer (PT)',
),
),
),
);
}
}
class StringClass
{
protected $str;
public function __construct($str)
{
$this->str = $str;
}
public function __toString()
{
return $this->str;
}
}

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