diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index e5f9f9b193..c6e99dea0b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -8,6 +8,7 @@ use Symfony\Component\DependencyInjection\Resource\FileResource; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\Finder\Finder; /* * This file is part of the Symfony framework. @@ -99,6 +100,10 @@ class FrameworkExtension extends Extension $this->registerUserConfiguration($config, $container); } + if (array_key_exists('translator', $config)) { + $this->registerTranslatorConfiguration($config, $container); + } + $this->addCompiledClasses($container, array( 'Symfony\\Component\\HttpFoundation\\ParameterBag', 'Symfony\\Component\\HttpFoundation\\HeaderBag', @@ -221,6 +226,55 @@ class FrameworkExtension extends Extension $loader->load('test.xml'); } + /** + * Loads the translator configuration. + * + * @param array $config A configuration array + * @param ContainerBuilder $container A ContainerBuilder instance + */ + protected function registerTranslatorConfiguration($config, ContainerBuilder $container) + { + $config = $config['translator']; + if (!is_array($config)) { + $config = array(); + } + + if (!$container->hasDefinition('translator')) { + $loader = new XmlFileLoader($container, array(__DIR__.'/../Resources/config', __DIR__.'/Resources/config')); + $loader->load('translation.xml'); + + // translation directories + $dirs = array(); + foreach (array_reverse($container->getParameter('kernel.bundles')) as $bundle) { + $reflection = new \ReflectionClass($bundle); + if (is_dir($dir = dirname($reflection->getFilename()).'/Resources/translations')) { + $dirs[] = $dir; + } + } + if (is_dir($dir = $container->getParameter('kernel.root_dir').'/translations')) { + $dirs[] = $dir; + } + + // translation resources + $resources = array(); + if ($dirs) { + $finder = new Finder(); + $finder->files()->filter(function (\SplFileInfo $file) { return 2 === substr_count($file->getBasename(), '.'); })->in($dirs); + foreach ($finder as $file) { + // filename is domain.locale.format + list($domain, $locale, $format) = explode('.', $file->getBasename()); + + $resources[] = array($format, (string) $file, $locale, $domain); + } + } + $container->setParameter('translation.resources', $resources); + } + + if (array_key_exists('fallback', $config)) { + $container->setParameter('translator.fallback_locale', $config['fallback']); + } + } + /** * Loads the session configuration. * diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd index 1f5eeaf7bd..f22014df6b 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd @@ -14,6 +14,7 @@ + @@ -75,4 +76,8 @@ + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating.xml index ddc5a9214e..87bd3b3b5f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating.xml @@ -18,6 +18,7 @@ Symfony\Bundle\FrameworkBundle\Templating\Helper\RequestHelper Symfony\Bundle\FrameworkBundle\Templating\Helper\SessionHelper Symfony\Bundle\FrameworkBundle\Templating\Helper\CodeHelper + Symfony\Bundle\FrameworkBundle\Templating\Helper\TranslatorHelper false null @@ -95,6 +96,11 @@ %kernel.root_dir% + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml new file mode 100644 index 0000000000..2a914004c1 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml @@ -0,0 +1,33 @@ + + + + + + Symfony\Bundle\FrameworkBundle\Translation\Translator + Symfony\Component\Translation\Loader\PhpFileLoader + Symfony\Component\Translation\Loader\XliffFileLoader + en + + + + + + + %kernel.cache_dir%/translations + %kernel.debug% + + + %translator.fallback_locale% + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/translations/validators.fr.xliff b/src/Symfony/Bundle/FrameworkBundle/Resources/translations/validators.fr.xliff new file mode 100644 index 0000000000..8bd35a1388 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/translations/validators.fr.xliff @@ -0,0 +1,115 @@ + + + + + + This value should be false + + + + This value should be true + + + + This value should be of type {{ type }} + + + + This value should be blank + + + + This value should be one of the given choices + + + + You should select at least {{ limit }} choices + + + + You should select at most {{ limit }} choices + + + + The fields {{ fields }} were not expected + + + + The fields {{ fields }} are missing + + + + This value is not a valid date + + + + This value is not a valid datetime + + + + This value is not a valid email address + + + + The file could not be found + + + + The file is not readable + + + + The file is too large ({{ size }}). Allowed maximum size is {{ limit }} + + + + The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }} + + + + This value should be {{ limit }} or less + + + + This value is too long. It should have {{ limit }} characters or less + + + + This value should be {{ limit }} or more + + + + This value is too short. It should have {{ limit }} characters or more + + + + This value should not be blank + + + + This value should not be null + + + + This value should be null + + + + This value is not valid + + + + This value is not a valid time + + + + This value is not a valid URL + + + + This value should be instance of class {{ class }} + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/TranslatorHelper.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/TranslatorHelper.php new file mode 100644 index 0000000000..0c73086dc0 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Helper/TranslatorHelper.php @@ -0,0 +1,61 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * TranslatorHelper. + * + * @author Fabien Potencier + */ +class TranslatorHelper extends Helper +{ + protected $translator; + + /** + * Constructor. + * + * @param TranslatorInterface $translator A TranslatorInterface instance + */ + public function __construct(TranslatorInterface $translator) + { + $this->translator = $translator; + } + + /** + * @see TranslatorInterface::trans() + */ + public function trans($id, array $parameters = array(), $domain = 'messages', $locale = null) + { + return $this->translator->trans($id, $parameters, $domain, $locale); + } + + /** + * @see TranslatorInterface::transChoice() + */ + public function transChoice($id, $number, array $parameters = array(), $domain = 'messages', $locale = null) + { + return $this->translator->transChoice($id, $number, $parameters, $domain, $locale); + } + + /** + * Returns the canonical name of this helper. + * + * @return string The canonical name + */ + public function getName() + { + return 'translator'; + } +} diff --git a/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php new file mode 100644 index 0000000000..acdbc33485 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php @@ -0,0 +1,164 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * Translator. + * + * @author Fabien Potencier + */ +class Translator extends BaseTranslator +{ + protected $container; + protected $options; + + /** + * Constructor. + * + * Available options: + * + * * cache_dir: The cache directory (or null to disable caching) + * * debug: Whether to enable debugging or not (false by default) + * + * @param ContainerInterface $container A ContainerInterface instance + * @param array $options An array of options + * @param Session $session A Session instance + */ + public function __construct(ContainerInterface $container, array $options = array(), Session $session = null) + { + if (null !== $session) { + parent::__construct($session->getLocale()); + } + + $this->container = $container; + + $this->options = array( + 'cache_dir' => null, + 'debug' => false, + ); + + // check option names + if ($diff = array_diff(array_keys($options), array_keys($this->options))) { + throw new \InvalidArgumentException(sprintf('The Router does not support the following options: \'%s\'.', implode('\', \'', $diff))); + } + + $this->options = array_merge($this->options, $options); + } + + /** + * {@inheritdoc} + */ + protected function loadCatalogue($locale) + { + if (isset($this->catalogues[$locale])) { + return; + } + + if (null === $this->options['cache_dir']) { + $this->initialize(); + + return parent::loadCatalogue($locale); + } + + if ($this->needsReload($locale)) { + $this->initialize(); + + parent::loadCatalogue($locale); + + $this->updateCache($locale); + + return; + } + + $this->catalogues[$locale] = include $this->getCacheFile($locale); + } + + protected function initialize() + { + foreach ($this->container->findTaggedServiceIds('translation.loader') as $id => $attributes) { + $this->addLoader($attributes[0]['alias'], $this->container->get($id)); + } + + foreach ($this->container->getParameter('translation.resources') as $resource) { + $this->addResource($resource[0], $resource[1], $resource[2], $resource[3]); + } + } + + protected function updateCache($locale) + { + $this->writeCacheFile($this->getCacheFile($locale), sprintf( + "catalogues[$locale]->getMessages(), true) + )); + + if ($this->options['debug']) { + $this->writeCacheFile($this->getCacheFile($locale, 'meta'), serialize($this->catalogues[$locale]->getResources())); + } + } + + protected function needsReload($locale) + { + $file = $this->getCacheFile($locale); + if (!file_exists($file)) { + return true; + } + + if (!$this->options['debug']) { + return false; + } + + $metadata = $this->getCacheFile($locale, 'meta'); + if (!file_exists($metadata)) { + return true; + } + + $time = filemtime($file); + $meta = unserialize(file_get_contents($metadata)); + foreach ($meta as $resource) { + if (!$resource->isUptodate($time)) { + return true; + } + } + + return false; + } + + protected function getCacheFile($locale, $extension = 'php') + { + return $this->options['cache_dir'].'/catalogue.'.$locale.'.'.$extension; + } + + /** + * @throws \RuntimeException When cache file can't be wrote + */ + protected function writeCacheFile($file, $content) + { + if (!is_dir(dirname($file))) { + @mkdir(dirname($file), 0777, true); + } + + $tmpFile = tempnam(dirname($file), basename($file)); + if (false !== @file_put_contents($tmpFile, $content) && @rename($tmpFile, $file)) { + chmod($file, 0644); + + return; + } + + throw new \RuntimeException(sprintf('Failed to write cache file "%s".', $file)); + } +}