Sfoglia il codice sorgente

[FrameworkBundle] added support for the Translation component

Fabien Potencier 14 anni fa
parent
commit
d3aca1c04a

+ 54 - 0
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.
      *

+ 5 - 0
src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd

@@ -14,6 +14,7 @@
             <xsd:element name="profiler" type="profiler" minOccurs="0" maxOccurs="1" />
             <xsd:element name="user" type="user" minOccurs="0" maxOccurs="1" />
             <xsd:element name="templating" type="templating" minOccurs="0" maxOccurs="1" />
+            <xsd:element name="translator" type="translator" minOccurs="0" maxOccurs="1" />
         </xsd:sequence>
 
         <xsd:attribute name="ide" type="xsd:string" />
@@ -75,4 +76,8 @@
         <xsd:attribute name="path" type="xsd:string" />
         <xsd:attribute name="cache" type="xsd:string" />
     </xsd:complexType>
+
+    <xsd:complexType name="translator">
+        <xsd:attribute name="fallback" type="xsd:string" />
+    </xsd:complexType>
 </xsd:schema>

+ 6 - 0
src/Symfony/Bundle/FrameworkBundle/Resources/config/templating.xml

@@ -18,6 +18,7 @@
         <parameter key="templating.helper.request.class">Symfony\Bundle\FrameworkBundle\Templating\Helper\RequestHelper</parameter>
         <parameter key="templating.helper.session.class">Symfony\Bundle\FrameworkBundle\Templating\Helper\SessionHelper</parameter>
         <parameter key="templating.helper.code.class">Symfony\Bundle\FrameworkBundle\Templating\Helper\CodeHelper</parameter>
+        <parameter key="templating.helper.translator.class">Symfony\Bundle\FrameworkBundle\Templating\Helper\TranslatorHelper</parameter>
         <parameter key="templating.output_escaper">false</parameter>
         <parameter key="templating.assets.version">null</parameter>
         <parameter key="templating.assets.base_urls" type="collection"></parameter>
@@ -95,6 +96,11 @@
             <argument>%kernel.root_dir%</argument>
         </service>
 
+        <service id="templating.helper.translator" class="%templating.helper.translator.class%">
+            <tag name="templating.helper" alias="translator" />
+            <argument type="service" id="translator" />
+        </service>
+
         <service id="templating.loader" alias="templating.loader.filesystem" />
 
         <service id="templating" alias="templating.engine" />

+ 33 - 0
src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" ?>
+
+<container xmlns="http://www.symfony-project.org/schema/dic/services"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://www.symfony-project.org/schema/dic/services http://www.symfony-project.org/schema/dic/services/services-1.0.xsd">
+
+    <parameters>
+        <parameter key="translator.class">Symfony\Bundle\FrameworkBundle\Translation\Translator</parameter>
+        <parameter key="translation.loader.php.class">Symfony\Component\Translation\Loader\PhpFileLoader</parameter>
+        <parameter key="translation.loader.xliff.class">Symfony\Component\Translation\Loader\XliffFileLoader</parameter>
+        <parameter key="translator.fallback_locale">en</parameter>
+    </parameters>
+
+    <services>
+        <service id="translator" class="%translator.class%">
+            <argument type="service" id="service_container" />
+            <argument type="collection">
+                <argument key="cache_dir">%kernel.cache_dir%/translations</argument>
+                <argument key="debug">%kernel.debug%</argument>
+            </argument>
+            <argument type="service" id="session" on-invalid="ignore" />
+            <call method="setFallbackLocale"><argument>%translator.fallback_locale%</argument></call>
+        </service>
+
+        <service id="translation.loader.php" class="%translation.loader.php.class%">
+            <tag name="translation.loader" alias="php" />
+        </service>
+
+        <service id="translation.loader.xliff" class="%translation.loader.xliff.class%">
+            <tag name="translation.loader" alias="xliff" />
+        </service>
+    </services>
+</container>

+ 115 - 0
src/Symfony/Bundle/FrameworkBundle/Resources/translations/validators.fr.xliff

@@ -0,0 +1,115 @@
+<?xml version="1.0"?>
+<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
+    <file source-language="en" datatype="plaintext" original="file.ext">
+        <body>
+            <trans-unit id="1">
+                <source>This value should be false</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="2">
+                <source>This value should be true</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="3">
+                <source>This value should be of type {{ type }}</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="4">
+                <source>This value should be blank</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="5">
+                <source>This value should be one of the given choices</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="6">
+                <source>You should select at least {{ limit }} choices</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="7">
+                <source>You should select at most {{ limit }} choices</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="8">
+                <source>The fields {{ fields }} were not expected</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="9">
+                <source>The fields {{ fields }} are missing</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="10">
+                <source>This value is not a valid date</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="11">
+                <source>This value is not a valid datetime</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="12">
+                <source>This value is not a valid email address</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="13">
+                <source>The file could not be found</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="14">
+                <source>The file is not readable</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="15">
+                <source>The file is too large ({{ size }}). Allowed maximum size is {{ limit }}</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="16">
+                <source>The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="17">
+                <source>This value should be {{ limit }} or less</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="18">
+                <source>This value is too long. It should have {{ limit }} characters or less</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="19">
+                <source>This value should be {{ limit }} or more</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="20">
+                <source>This value is too short. It should have {{ limit }} characters or more</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="21">
+                <source>This value should not be blank</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="22">
+                <source>This value should not be null</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="23">
+                <source>This value should be null</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="24">
+                <source>This value is not valid</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="25">
+                <source>This value is not a valid time</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="26">
+                <source>This value is not a valid URL</source>
+                <target></target>
+            </trans-unit>
+            <trans-unit id="27">
+                <source>This value should be instance of class {{ class }}</source>
+                <target></target>
+            </trans-unit>
+        </body>
+    </file>
+</xliff>

+ 61 - 0
src/Symfony/Bundle/FrameworkBundle/Templating/Helper/TranslatorHelper.php

@@ -0,0 +1,61 @@
+<?php
+
+namespace Symfony\Bundle\FrameworkBundle\Templating\Helper;
+
+use Symfony\Component\Templating\Helper\Helper;
+use Symfony\Component\Translation\TranslatorInterface;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * TranslatorHelper.
+ *
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+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';
+    }
+}

+ 164 - 0
src/Symfony/Bundle/FrameworkBundle/Translation/Translator.php

@@ -0,0 +1,164 @@
+<?php
+
+namespace Symfony\Bundle\FrameworkBundle\Translation;
+
+use Symfony\Component\Translation\Translator as BaseTranslator;
+use Symfony\Component\Translation\Loader\LoaderInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Session;
+
+/*
+ * This file is part of the Symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * Translator.
+ *
+ * @author Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+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(
+            "<?php use Symfony\Component\Translation\MessageCatalogue; return new MessageCatalogue('%s', %s);",
+            $locale,
+            var_export($this->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));
+    }
+}