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));
+ }
+}