[Translation] added the component
This commit is contained in:
parent
6317ddfbe1
commit
a7537906b4
33
src/Symfony/Component/Translation/Loader/ArrayLoader.php
Normal file
33
src/Symfony/Component/Translation/Loader/ArrayLoader.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Translation\Loader;
|
||||||
|
|
||||||
|
use Symfony\Component\Translation\MessageCatalogue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ArrayLoader loads translations from a PHP array.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||||
|
*/
|
||||||
|
class ArrayLoader implements LoaderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
function load($resource, $locale, $domain = 'messages')
|
||||||
|
{
|
||||||
|
$catalogue = new MessageCatalogue($locale);
|
||||||
|
$catalogue->addMessages($resource, $domain);
|
||||||
|
|
||||||
|
return $catalogue;
|
||||||
|
}
|
||||||
|
}
|
33
src/Symfony/Component/Translation/Loader/LoaderInterface.php
Normal file
33
src/Symfony/Component/Translation/Loader/LoaderInterface.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Translation\Loader;
|
||||||
|
|
||||||
|
use Symfony\Component\Translation\MessageCatalogue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LoaderInterface is the interface implemented by all translation loaders.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
function load($resource, $locale, $domain = 'messages');
|
||||||
|
}
|
35
src/Symfony/Component/Translation/Loader/PhpFileLoader.php
Normal file
35
src/Symfony/Component/Translation/Loader/PhpFileLoader.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Translation\Loader;
|
||||||
|
|
||||||
|
use Symfony\Component\Translation\MessageCatalogue;
|
||||||
|
use Symfony\Component\Translation\Resource\FileResource;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PhpFileLoader loads translations from PHP files returning an array of translations.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||||
|
*/
|
||||||
|
class PhpFileLoader implements LoaderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
function load($resource, $locale, $domain = 'messages')
|
||||||
|
{
|
||||||
|
$catalogue = new MessageCatalogue($locale);
|
||||||
|
$catalogue->addMessages(require($resource), $domain);
|
||||||
|
$catalogue->addResource(new FileResource($resource));
|
||||||
|
|
||||||
|
return $catalogue;
|
||||||
|
}
|
||||||
|
}
|
96
src/Symfony/Component/Translation/Loader/XliffFileLoader.php
Normal file
96
src/Symfony/Component/Translation/Loader/XliffFileLoader.php
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Translation\Loader;
|
||||||
|
|
||||||
|
use Symfony\Component\Translation\MessageCatalogue;
|
||||||
|
use Symfony\Component\Translation\Resource\FileResource;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* XliffFileLoader loads translations from XLIFF files.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||||
|
*/
|
||||||
|
class XliffFileLoader implements LoaderInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
function load($resource, $locale, $domain = 'messages')
|
||||||
|
{
|
||||||
|
$xml = $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) {
|
||||||
|
$catalogue->setMessage((string) $translation->source, (string) $translation->target, $domain);
|
||||||
|
}
|
||||||
|
$catalogue->addResource(new FileResource($resource));
|
||||||
|
|
||||||
|
return $catalogue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates and parses the given file into a SimpleXMLElement
|
||||||
|
*
|
||||||
|
* @param string $file
|
||||||
|
* @return SimpleXMLElement
|
||||||
|
*/
|
||||||
|
protected function parseFile($file)
|
||||||
|
{
|
||||||
|
$dom = new \DOMDocument();
|
||||||
|
$current = libxml_use_internal_errors(true);
|
||||||
|
if (!@$dom->load($file, LIBXML_COMPACT)) {
|
||||||
|
throw new \Exception(implode("\n", $this->getXmlErrors()));
|
||||||
|
}
|
||||||
|
|
||||||
|
$parts = explode('/', str_replace('\\', '/', __DIR__).'/schema/dic/xliff-core/xml.xsd');
|
||||||
|
$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 \Exception(implode("\n", $this->getXmlErrors()));
|
||||||
|
}
|
||||||
|
$dom->validateOnParse = true;
|
||||||
|
$dom->normalizeDocument();
|
||||||
|
libxml_use_internal_errors($current);
|
||||||
|
|
||||||
|
return simplexml_import_dom($dom);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the XML errors of the internal XML parser
|
||||||
|
*
|
||||||
|
* @return array An array of errors
|
||||||
|
*/
|
||||||
|
protected function getXmlErrors()
|
||||||
|
{
|
||||||
|
$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 ? $error->file : 'n/a',
|
||||||
|
$error->line,
|
||||||
|
$error->column
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
libxml_clear_errors();
|
||||||
|
libxml_use_internal_errors(false);
|
||||||
|
|
||||||
|
return $errors;
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,309 @@
|
|||||||
|
<?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>
|
||||||
|
<schema . . .>
|
||||||
|
. . .
|
||||||
|
<import namespace="http://www.w3.org/XML/1998/namespace"
|
||||||
|
schemaLocation="http://www.w3.org/2001/xml.xsd"/>
|
||||||
|
</pre>
|
||||||
|
<p>
|
||||||
|
or
|
||||||
|
</p>
|
||||||
|
<pre>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
<type . . .>
|
||||||
|
. . .
|
||||||
|
<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>
|
145
src/Symfony/Component/Translation/MessageCatalogue.php
Normal file
145
src/Symfony/Component/Translation/MessageCatalogue.php
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Translation;
|
||||||
|
|
||||||
|
use Symfony\Component\Translation\Resource\ResourceInterface;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MessageCatalogue.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||||
|
*/
|
||||||
|
class MessageCatalogue implements MessageCatalogueInterface
|
||||||
|
{
|
||||||
|
protected $messages = array();
|
||||||
|
protected $locale;
|
||||||
|
protected $resources;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param string $locale The locale
|
||||||
|
* @param array $messages An array of messages classified by domain
|
||||||
|
*/
|
||||||
|
public function __construct($locale, array $messages = array())
|
||||||
|
{
|
||||||
|
$this->locale = $locale;
|
||||||
|
$this->messages = $messages;
|
||||||
|
$this->resources = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getLocale()
|
||||||
|
{
|
||||||
|
return $this->locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getDomains()
|
||||||
|
{
|
||||||
|
return array_keys($this->messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getMessages($domain = null)
|
||||||
|
{
|
||||||
|
if (null === $domain) {
|
||||||
|
return $this->messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isset($this->messages[$domain]) ? $this->messages[$domain] : array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function setMessage($id, $translation, $domain = 'messages')
|
||||||
|
{
|
||||||
|
$this->addMessages(array($id => $translation), $domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function hasMessage($id, $domain = 'messages')
|
||||||
|
{
|
||||||
|
return isset($this->messages[$domain][$id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getMessage($id, $domain = 'messages')
|
||||||
|
{
|
||||||
|
return isset($this->messages[$domain][$id]) ? $this->messages[$domain][$id] : $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function setMessages($messages, $domain = 'messages')
|
||||||
|
{
|
||||||
|
if (isset($this->messages[$domain])) {
|
||||||
|
$this->messages[$domain] = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->addMessages($messages, $domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function addMessages($messages, $domain = 'messages')
|
||||||
|
{
|
||||||
|
if (!isset($this->messages[$domain])) {
|
||||||
|
$this->messages[$domain] = $messages;
|
||||||
|
} else {
|
||||||
|
$this->messages[$domain] = array_replace($this->messages[$domain], $messages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function addCatalogue(MessageCatalogueInterface $catalogue)
|
||||||
|
{
|
||||||
|
foreach ($catalogue->getMessages() as $domain => $messages) {
|
||||||
|
$this->addMessages($messages, $domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($catalogue->getResources() as $resource) {
|
||||||
|
$this->addResource($resource);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getResources()
|
||||||
|
{
|
||||||
|
return array_unique($this->resources);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function addResource(ResourceInterface $resource)
|
||||||
|
{
|
||||||
|
$this->resources[] = $resource;
|
||||||
|
}
|
||||||
|
}
|
113
src/Symfony/Component/Translation/MessageCatalogueInterface.php
Normal file
113
src/Symfony/Component/Translation/MessageCatalogueInterface.php
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Translation;
|
||||||
|
|
||||||
|
use Symfony\Component\Translation\Resource\ResourceInterface;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MessageCatalogueInterface.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||||
|
*/
|
||||||
|
interface MessageCatalogueInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Gets the catalogue locale.
|
||||||
|
*
|
||||||
|
* @return string The locale
|
||||||
|
*/
|
||||||
|
function getLocale();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the domains.
|
||||||
|
*
|
||||||
|
* @param array An array of domains
|
||||||
|
*/
|
||||||
|
function getDomains();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the messages within a given domain.
|
||||||
|
*
|
||||||
|
* If $domain is null, it returns all messages.
|
||||||
|
*
|
||||||
|
* @param string $domain The domain name
|
||||||
|
*
|
||||||
|
* @param array An array of messages
|
||||||
|
*/
|
||||||
|
function getMessages($domain = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a message translation.
|
||||||
|
*
|
||||||
|
* @param string $id The message id
|
||||||
|
* @param string $translation The messages translation
|
||||||
|
* @param string $domain The domain name
|
||||||
|
*/
|
||||||
|
function setMessage($id, $translation, $domain = 'messages');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a message has a translation.
|
||||||
|
*
|
||||||
|
* @param string $id The message id
|
||||||
|
* @param string $domain The domain name
|
||||||
|
*
|
||||||
|
* @return Boolean true if the message has a translation, false otherwise
|
||||||
|
*/
|
||||||
|
function hasMessage($id, $domain = 'messages');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a message translation.
|
||||||
|
*
|
||||||
|
* @param string $id The message id
|
||||||
|
* @param string $domain The domain name
|
||||||
|
*
|
||||||
|
* @return string The message translation
|
||||||
|
*/
|
||||||
|
function getMessage($id, $domain = 'messages');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets translations for a given domain.
|
||||||
|
*
|
||||||
|
* @param string $messages An array of translations
|
||||||
|
* @param string $domain The domain name
|
||||||
|
*/
|
||||||
|
function setMessages($messages, $domain = 'messages');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds translations for a given domain.
|
||||||
|
*
|
||||||
|
* @param string $messages An array of translations
|
||||||
|
* @param string $domain The domain name
|
||||||
|
*/
|
||||||
|
function addMessages($messages, $domain = 'messages');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Merges translations from the given Catalogue into the current one.
|
||||||
|
*
|
||||||
|
* @param MessageCatalogueInterface $catalogue A MessageCatalogueInterface instance
|
||||||
|
*/
|
||||||
|
function addCatalogue(MessageCatalogueInterface $catalogue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of resources loaded to build this collection.
|
||||||
|
*
|
||||||
|
* @return ResourceInterface[] An array of resources
|
||||||
|
*/
|
||||||
|
function getResources();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a resource for this collection.
|
||||||
|
*
|
||||||
|
* @param ResourceInterface $resource A resource instance
|
||||||
|
*/
|
||||||
|
function addResource(ResourceInterface $resource);
|
||||||
|
}
|
214
src/Symfony/Component/Translation/PluralizationRules.php
Normal file
214
src/Symfony/Component/Translation/PluralizationRules.php
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Translation;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the plural rules for a given locale.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||||
|
*/
|
||||||
|
class PluralizationRules
|
||||||
|
{
|
||||||
|
static protected $rules = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the plural position to use for the given locale and number.
|
||||||
|
*
|
||||||
|
* @param integer $number The number
|
||||||
|
* @param string $locale The locale
|
||||||
|
*
|
||||||
|
* @return integer The plural position
|
||||||
|
*/
|
||||||
|
static public function get($number, $locale)
|
||||||
|
{
|
||||||
|
if ($locale == "pt_BR") {
|
||||||
|
// temporary set a locale for brasilian
|
||||||
|
$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 '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 'az':
|
||||||
|
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 >= 3) && ($number <= 10)) ? 3 : ((($number >= 11) && ($number <= 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
|
||||||
|
*
|
||||||
|
* @return null
|
||||||
|
*/
|
||||||
|
static public function set($rule, $locale)
|
||||||
|
{
|
||||||
|
if ($locale == "pt_BR") {
|
||||||
|
// temporary set a locale for brasilian
|
||||||
|
$locale = "xbr";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strlen($locale) > 3) {
|
||||||
|
$locale = substr($locale, 0, -strlen(strrchr($locale, '_')));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_callable($rule)) {
|
||||||
|
throw new Exception('The given rule can not be called');
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$rules[$locale] = $rule;
|
||||||
|
}
|
||||||
|
}
|
102
src/Symfony/Component/Translation/Range.php
Normal file
102
src/Symfony/Component/Translation/Range.php
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Translation;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Range tests if a given number belongs to a given range.
|
||||||
|
*
|
||||||
|
* A range can represent a finite set of numbers:
|
||||||
|
*
|
||||||
|
* {1,2,3,4}
|
||||||
|
*
|
||||||
|
* A range 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.potencier@symfony-project.com>
|
||||||
|
*/
|
||||||
|
class Range
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Tests if the given number is in the range.
|
||||||
|
*
|
||||||
|
* @param integer $number A number
|
||||||
|
* @param string $range A range of numbers
|
||||||
|
*/
|
||||||
|
static public function test($number, $range)
|
||||||
|
{
|
||||||
|
$range = trim($range);
|
||||||
|
|
||||||
|
if (!preg_match('/^'.self::getRangeRegexp().'$/x', $range, $matches)) {
|
||||||
|
throw new \InvalidArgumentException(sprintf('"%s" is not a valid range expression.', $range));
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ranges.
|
||||||
|
*
|
||||||
|
* @return string A Regexp (without the delimiters)
|
||||||
|
*/
|
||||||
|
static public function getRangeRegexp()
|
||||||
|
{
|
||||||
|
return <<<EOF
|
||||||
|
({\s*
|
||||||
|
(\-?\d+[\s*,\s*\-?\d+]*)
|
||||||
|
\s*})
|
||||||
|
|
||||||
|
|
|
||||||
|
|
||||||
|
(?<left_delimiter>[\[\]])
|
||||||
|
\s*
|
||||||
|
(?<left>-Inf|\-?\d+)
|
||||||
|
\s*,\s*
|
||||||
|
(?<right>\+?Inf|\-?\d+)
|
||||||
|
\s*
|
||||||
|
(?<right_delimiter>[\[\]])
|
||||||
|
EOF;
|
||||||
|
}
|
||||||
|
|
||||||
|
static protected function convertNumber($number)
|
||||||
|
{
|
||||||
|
if ('-Inf' === $number) {
|
||||||
|
return log(0);
|
||||||
|
} elseif ('+Inf' === $number || 'Inf' === $number) {
|
||||||
|
return -log(0);
|
||||||
|
} else {
|
||||||
|
return (int) $number;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
68
src/Symfony/Component/Translation/Resource/FileResource.php
Normal file
68
src/Symfony/Component/Translation/Resource/FileResource.php
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Translation\Resource;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FileResource represents a resource stored on the filesystem.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||||
|
*/
|
||||||
|
class FileResource implements ResourceInterface
|
||||||
|
{
|
||||||
|
protected $resource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param string $resource The file path to the resource
|
||||||
|
*/
|
||||||
|
public function __construct($resource)
|
||||||
|
{
|
||||||
|
$this->resource = realpath($resource);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string representation of the Resource.
|
||||||
|
*
|
||||||
|
* @return string A string representation of the Resource
|
||||||
|
*/
|
||||||
|
public function __toString()
|
||||||
|
{
|
||||||
|
return (string) $this->resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the resource tied to this Resource.
|
||||||
|
*
|
||||||
|
* @return mixed The resource
|
||||||
|
*/
|
||||||
|
public function getResource()
|
||||||
|
{
|
||||||
|
return $this->resource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the resource has not been updated since the given timestamp.
|
||||||
|
*
|
||||||
|
* @param timestamp $timestamp The last time the resource was loaded
|
||||||
|
*
|
||||||
|
* @return Boolean true if the resource has not been updated, false otherwise
|
||||||
|
*/
|
||||||
|
public function isUptodate($timestamp)
|
||||||
|
{
|
||||||
|
if (!file_exists($this->resource)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return filemtime($this->resource) < $timestamp;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Translation\Resource;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ResourceInterface is the interface that must be implemented by all Resource classes.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||||
|
*/
|
||||||
|
interface ResourceInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Returns a string representation of the Resource.
|
||||||
|
*
|
||||||
|
* @return string A string representation of the Resource
|
||||||
|
*/
|
||||||
|
function __toString();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the resource has not been updated since the given timestamp.
|
||||||
|
*
|
||||||
|
* @param int $timestamp The last time the resource was loaded
|
||||||
|
*
|
||||||
|
* @return Boolean true if the resource has not been updated, false otherwise
|
||||||
|
*/
|
||||||
|
function isUptodate($timestamp);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the resource tied to this Resource.
|
||||||
|
*
|
||||||
|
* @return mixed The resource
|
||||||
|
*/
|
||||||
|
function getResource();
|
||||||
|
}
|
201
src/Symfony/Component/Translation/Translator.php
Normal file
201
src/Symfony/Component/Translation/Translator.php
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Translation;
|
||||||
|
|
||||||
|
use Symfony\Component\Translation\Loader\LoaderInterface;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 implements TranslatorInterface
|
||||||
|
{
|
||||||
|
protected $catalogues;
|
||||||
|
protected $locale;
|
||||||
|
protected $fallbackLocale;
|
||||||
|
protected $loaders;
|
||||||
|
protected $resources;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor.
|
||||||
|
*
|
||||||
|
* @param string $locale The locale
|
||||||
|
*/
|
||||||
|
public function __construct($locale = null)
|
||||||
|
{
|
||||||
|
$this->locale = $locale;
|
||||||
|
$this->loaders = array();
|
||||||
|
$this->resources = array();
|
||||||
|
$this->catalogues = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a Loader.
|
||||||
|
*
|
||||||
|
* @param string $format The name of the loader (@see addResource())
|
||||||
|
* @param LoaderInterface $loader A LoaderInterface instance
|
||||||
|
*/
|
||||||
|
public function addLoader($format, LoaderInterface $loader)
|
||||||
|
{
|
||||||
|
$this->loaders[$format] = $loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a Resource.
|
||||||
|
*
|
||||||
|
* @param string $format The name of the loader (@see addLoader())
|
||||||
|
* @param mixed $resource The resource name
|
||||||
|
* @param string $locale The locale
|
||||||
|
* @param string $domain The domain
|
||||||
|
*/
|
||||||
|
public function addResource($format, $resource, $locale, $domain = 'messages')
|
||||||
|
{
|
||||||
|
if (!isset($this->resources[$locale])) {
|
||||||
|
$this->resources[$locale] = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->resources[$locale][] = array($format, $resource, $domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function setLocale($locale)
|
||||||
|
{
|
||||||
|
$this->locale = $locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the fallback locale.
|
||||||
|
*
|
||||||
|
* @param string $locale The fallback locale
|
||||||
|
*/
|
||||||
|
public function setFallbackLocale($locale)
|
||||||
|
{
|
||||||
|
if (null !== $this->fallbackLocale) {
|
||||||
|
// needed as the fallback locale is used to fill-in non-yet translated messages
|
||||||
|
$this->catalogues = array();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->fallbackLocale = $locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function trans($id, array $parameters = array(), $domain = 'messages', $locale = null)
|
||||||
|
{
|
||||||
|
if (!isset($locale)) {
|
||||||
|
$locale = $this->locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($this->catalogues[$locale])) {
|
||||||
|
$this->loadCatalogue($locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
return strtr($this->catalogues[$locale]->getMessage($id, $domain), $parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function transChoice($id, $number, array $parameters = array(), $domain = 'messages', $locale = null)
|
||||||
|
{
|
||||||
|
if (!isset($locale)) {
|
||||||
|
$locale = $this->locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($this->catalogues[$locale])) {
|
||||||
|
$this->loadCatalogue($locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
return strtr($this->chooseMessage($this->catalogues[$locale]->getMessage($id, $domain), (int) $number, $locale), $parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function chooseMessage($message, $number, $locale)
|
||||||
|
{
|
||||||
|
$parts = explode('|', $message);
|
||||||
|
$explicitRules = array();
|
||||||
|
$standardRules = array();
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
$part = trim($part);
|
||||||
|
|
||||||
|
if (preg_match('/^(?<range>'.Range::getRangeRegexp().')\s+(?<message>.+?)$/x', $part, $matches)) {
|
||||||
|
$explicitRules[$matches['range']] = $matches['message'];
|
||||||
|
} elseif (preg_match('/^\w+\: +(.+)$/', $part, $matches)) {
|
||||||
|
$standardRules[] = $matches[1];
|
||||||
|
} else {
|
||||||
|
$standardRules[] = $part;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to match an explicit rule, then fallback to the standard ones
|
||||||
|
foreach ($explicitRules as $range => $m) {
|
||||||
|
if (Range::test($number, $range)) {
|
||||||
|
return $m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$position = PluralizationRules::get($number, $locale);
|
||||||
|
if (!isset($standardRules[$position])) {
|
||||||
|
throw new \InvalidArgumentException('Unable to choose a translation.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $standardRules[$position];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function loadCatalogue($locale)
|
||||||
|
{
|
||||||
|
if (isset($this->catalogues[$locale])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->catalogues[$locale] = new MessageCatalogue($locale);
|
||||||
|
if (!isset($this->resources[$locale])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->resources[$locale] as $resource) {
|
||||||
|
if (!isset($this->loaders[$resource[0]])) {
|
||||||
|
throw new \RuntimeException(sprintf('The "%s" translation loader is not registered.', $resource[0]));
|
||||||
|
}
|
||||||
|
$this->catalogues[$locale]->addCatalogue($this->loaders[$resource[0]]->load($resource[1], $locale, $resource[2]));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->optimizeCatalogue($locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function optimizeCatalogue($locale)
|
||||||
|
{
|
||||||
|
if (strlen($locale) > 3) {
|
||||||
|
$fallback = substr($locale, 0, -strlen(strrchr($locale, '_')));
|
||||||
|
} else {
|
||||||
|
$fallback = $this->fallbackLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($this->catalogues[$fallback])) {
|
||||||
|
$this->loadCatalogue($fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($this->catalogues[$fallback]->getResources() as $resource) {
|
||||||
|
$this->catalogues[$locale]->addResource($resource);
|
||||||
|
}
|
||||||
|
foreach ($this->catalogues[$fallback]->getDomains() as $domain) {
|
||||||
|
foreach ($this->catalogues[$fallback]->getMessages($domain) as $id => $translation) {
|
||||||
|
if (false === $this->catalogues[$locale]->hasMessage($id, $domain)) {
|
||||||
|
$this->catalogues[$locale]->setMessage($id, $translation, $domain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
52
src/Symfony/Component/Translation/TranslatorInterface.php
Normal file
52
src/Symfony/Component/Translation/TranslatorInterface.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Symfony\Component\Translation;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TranslatorInterface.
|
||||||
|
*
|
||||||
|
* @author Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||||
|
*/
|
||||||
|
interface TranslatorInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Translates the given message.
|
||||||
|
*
|
||||||
|
* @param string $id The message id
|
||||||
|
* @param array $parameters An array of parameters for the message
|
||||||
|
* @param string $domain The domain for the message
|
||||||
|
* @param string $locale The locale
|
||||||
|
*
|
||||||
|
* @return string The translated string
|
||||||
|
*/
|
||||||
|
function trans($id, array $parameters = array(), $domain = null, $locale = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translates the given choice message by choosing a translation according to a number.
|
||||||
|
*
|
||||||
|
* @param string $id The message id
|
||||||
|
* @param integer $number The number to use to find the indice of the message
|
||||||
|
* @param array $parameters An array of parameters for the message
|
||||||
|
* @param string $domain The domain for the message
|
||||||
|
* @param string $locale The locale
|
||||||
|
*
|
||||||
|
* @return string The translated string
|
||||||
|
*/
|
||||||
|
function transChoice($id, $number, array $parameters = array(), $domain = null, $locale = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current locale.
|
||||||
|
*
|
||||||
|
* @param string $locale The locale
|
||||||
|
*/
|
||||||
|
function setLocale($locale);
|
||||||
|
}
|
48
tests/Symfony/Tests/Component/Translation/RangeTest.php
Normal file
48
tests/Symfony/Tests/Component/Translation/RangeTest.php
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Tests\Component\Translation;
|
||||||
|
|
||||||
|
use Symfony\Component\Translation\Range;
|
||||||
|
|
||||||
|
class RangeTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider getTests
|
||||||
|
*/
|
||||||
|
public function testTest($expected, $number, $range)
|
||||||
|
{
|
||||||
|
$this->assertEquals($expected, Range::test($number, $range));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @expectedException \InvalidArgumentException
|
||||||
|
*/
|
||||||
|
public function testTestException()
|
||||||
|
{
|
||||||
|
Range::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]'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
71
tests/Symfony/Tests/Component/Translation/TranslatorTest.php
Normal file
71
tests/Symfony/Tests/Component/Translation/TranslatorTest.php
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of the Symfony package.
|
||||||
|
*
|
||||||
|
* (c) Fabien Potencier <fabien.potencier@symfony-project.com>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Symfony\Tests\Component\Translation;
|
||||||
|
|
||||||
|
use Symfony\Component\Translation\Translator;
|
||||||
|
use Symfony\Component\Translation\Loader\ArrayLoader;
|
||||||
|
|
||||||
|
class TranslatorTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @dataProvider getTransTests
|
||||||
|
*/
|
||||||
|
public function testTrans($expected, $id, $translation, $parameters, $locale, $domain)
|
||||||
|
{
|
||||||
|
$translator = new Translator();
|
||||||
|
$translator->addLoader('array', new ArrayLoader());
|
||||||
|
$translator->addResource('array', array($id => $translation), $locale, $domain);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $translator->trans($id, $parameters, $domain, $locale));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider getTransChoiceTests
|
||||||
|
*/
|
||||||
|
public function testTransChoice($expected, $id, $translation, $number, $parameters, $locale, $domain)
|
||||||
|
{
|
||||||
|
$translator = new Translator();
|
||||||
|
$translator->addLoader('array', new ArrayLoader());
|
||||||
|
$translator->addResource('array', array($id => $translation), $locale, $domain);
|
||||||
|
|
||||||
|
$this->assertEquals($expected, $translator->transChoice($id, $number, $parameters, $domain, $locale));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTransTests()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array('Symfony2 est super !', 'Symfony2 is great!', 'Symfony2 est super !', array(), 'fr', ''),
|
||||||
|
array('Symfony2 est awesome !', 'Symfony2 is %what%!', 'Symfony2 est %what% !', array('%what%' => 'awesome'), 'fr', ''),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTransChoiceTests()
|
||||||
|
{
|
||||||
|
return array(
|
||||||
|
array('Il y a 0 pomme', '{0} There is no apples|{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 is no apples|{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 is no apples|{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 is no apple|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 is no apple|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 is no apple|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', ''),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user