[FrameworkBundle] added support for the Translation component

This commit is contained in:
Fabien Potencier 2010-09-27 09:46:15 +02:00
parent d6f55c31d1
commit d3aca1c04a
7 changed files with 438 additions and 0 deletions

View File

@ -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.
*

View File

@ -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>

View File

@ -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" />

View File

@ -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>

View File

@ -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>

View File

@ -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';
}
}

View File

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