Actualización

This commit is contained in:
Xes
2025-04-10 12:24:57 +02:00
parent 8969cc929d
commit 45420b6f0d
39760 changed files with 4303286 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Bundle\Compiler;
/**
* Compiles a resource bundle.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
interface BundleCompilerInterface
{
/**
* Compiles a resource bundle at the given source to the given target
* directory.
*
* @param string $sourcePath
* @param string $targetDir
*/
public function compile($sourcePath, $targetDir);
}

View File

@@ -0,0 +1,61 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Bundle\Compiler;
use Symfony\Component\Intl\Exception\RuntimeException;
/**
* Compiles .txt resource bundles to binary .res files.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
class GenrbCompiler implements BundleCompilerInterface
{
private $genrb;
/**
* Creates a new compiler based on the "genrb" executable.
*
* @param string $genrb Optional. The path to the "genrb" executable
* @param string $envVars Optional. Environment variables to be loaded when running "genrb".
*
* @throws RuntimeException if the "genrb" cannot be found
*/
public function __construct($genrb = 'genrb', $envVars = '')
{
exec('which '.$genrb, $output, $status);
if (0 !== $status) {
throw new RuntimeException(sprintf('The command "%s" is not installed.', $genrb));
}
$this->genrb = ($envVars ? $envVars.' ' : '').$genrb;
}
/**
* {@inheritdoc}
*/
public function compile($sourcePath, $targetDir)
{
if (is_dir($sourcePath)) {
$sourcePath .= '/*.txt';
}
exec($this->genrb.' --quiet -e UTF-8 -d '.$targetDir.' '.$sourcePath, $output, $status);
if (0 !== $status) {
throw new RuntimeException(sprintf('genrb failed with status %d while compiling "%s" to "%s".', $status, $sourcePath, $targetDir));
}
}
}

View File

@@ -0,0 +1,51 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Bundle\Reader;
use Symfony\Component\Intl\Data\Util\RingBuffer;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
class BufferedBundleReader implements BundleReaderInterface
{
private $reader;
private $buffer;
/**
* Buffers a given reader.
*
* @param BundleReaderInterface $reader The reader to buffer
* @param int $bufferSize The number of entries to store in the buffer
*/
public function __construct(BundleReaderInterface $reader, $bufferSize)
{
$this->reader = $reader;
$this->buffer = new RingBuffer($bufferSize);
}
/**
* {@inheritdoc}
*/
public function read($path, $locale)
{
$hash = $path.'//'.$locale;
if (!isset($this->buffer[$hash])) {
$this->buffer[$hash] = $this->reader->read($path, $locale);
}
return $this->buffer[$hash];
}
}

View File

@@ -0,0 +1,177 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Bundle\Reader;
use Symfony\Component\Intl\Data\Util\RecursiveArrayAccess;
use Symfony\Component\Intl\Exception\MissingResourceException;
use Symfony\Component\Intl\Exception\OutOfBoundsException;
use Symfony\Component\Intl\Exception\ResourceBundleNotFoundException;
use Symfony\Component\Intl\Locale;
/**
* Default implementation of {@link BundleEntryReaderInterface}.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @see BundleEntryReaderInterface
*
* @internal
*/
class BundleEntryReader implements BundleEntryReaderInterface
{
private $reader;
/**
* A mapping of locale aliases to locales.
*/
private $localeAliases = [];
/**
* Creates an entry reader based on the given resource bundle reader.
*/
public function __construct(BundleReaderInterface $reader)
{
$this->reader = $reader;
}
/**
* Stores a mapping of locale aliases to locales.
*
* This mapping is used when reading entries and merging them with their
* fallback locales. If an entry is read for a locale alias (e.g. "mo")
* that points to a locale with a fallback locale ("ro_MD"), the reader
* can continue at the correct fallback locale ("ro").
*
* @param array $localeAliases A mapping of locale aliases to locales
*/
public function setLocaleAliases($localeAliases)
{
$this->localeAliases = $localeAliases;
}
/**
* {@inheritdoc}
*/
public function read($path, $locale)
{
return $this->reader->read($path, $locale);
}
/**
* {@inheritdoc}
*/
public function readEntry($path, $locale, array $indices, $fallback = true)
{
$entry = null;
$isMultiValued = false;
$readSucceeded = false;
$exception = null;
$currentLocale = $locale;
$testedLocales = [];
while (null !== $currentLocale) {
// Resolve any aliases to their target locales
if (isset($this->localeAliases[$currentLocale])) {
$currentLocale = $this->localeAliases[$currentLocale];
}
try {
$data = $this->reader->read($path, $currentLocale);
$currentEntry = RecursiveArrayAccess::get($data, $indices);
$readSucceeded = true;
$isCurrentTraversable = $currentEntry instanceof \Traversable;
$isCurrentMultiValued = $isCurrentTraversable || \is_array($currentEntry);
// Return immediately if fallback is disabled or we are dealing
// with a scalar non-null entry
if (!$fallback || (!$isCurrentMultiValued && null !== $currentEntry)) {
return $currentEntry;
}
// =========================================================
// Fallback is enabled, entry is either multi-valued or NULL
// =========================================================
// If entry is multi-valued, convert to array
if ($isCurrentTraversable) {
$currentEntry = iterator_to_array($currentEntry);
}
// If previously read entry was multi-valued too, merge them
if ($isCurrentMultiValued && $isMultiValued) {
$currentEntry = array_merge($currentEntry, $entry);
}
// Keep the previous entry if the current entry is NULL
if (null !== $currentEntry) {
$entry = $currentEntry;
}
// If this or the previous entry was multi-valued, we are dealing
// with a merged, multi-valued entry now
$isMultiValued = $isMultiValued || $isCurrentMultiValued;
} catch (ResourceBundleNotFoundException $e) {
// Continue if there is a fallback locale for the current
// locale
$exception = $e;
} catch (OutOfBoundsException $e) {
// Remember exception and rethrow if we cannot find anything in
// the fallback locales either
$exception = $e;
}
// Remember which locales we tried
$testedLocales[] = $currentLocale;
// Check whether fallback is allowed
if (!$fallback) {
break;
}
// Then determine fallback locale
$currentLocale = Locale::getFallback($currentLocale);
}
// Multi-valued entry was merged
if ($isMultiValued) {
return $entry;
}
// Entry is still NULL, but no read error occurred
if ($readSucceeded) {
return $entry;
}
// Entry is still NULL, read error occurred. Throw an exception
// containing the detailed path and locale
$errorMessage = sprintf(
'Couldn\'t read the indices [%s] for the locale "%s" in "%s".',
implode('][', $indices),
$locale,
$path
);
// Append fallback locales, if any
if (\count($testedLocales) > 1) {
// Remove original locale
array_shift($testedLocales);
$errorMessage .= sprintf(
' The indices also couldn\'t be found for the fallback locale(s) "%s".',
implode('", "', $testedLocales)
);
}
throw new MissingResourceException($errorMessage, 0, $exception);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Bundle\Reader;
use Symfony\Component\Intl\Exception\MissingResourceException;
/**
* Reads individual entries of a resource file.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
interface BundleEntryReaderInterface extends BundleReaderInterface
{
/**
* Reads an entry from a resource bundle.
*
* An entry can be selected from the resource bundle by passing the path
* to that entry in the bundle. For example, if the bundle is structured
* like this:
*
* TopLevel
* NestedLevel
* Entry: Value
*
* Then the value can be read by calling:
*
* $reader->readEntry('...', 'en', ['TopLevel', 'NestedLevel', 'Entry']);
*
* @param string $path The path to the resource bundle
* @param string $locale The locale to read
* @param string[] $indices The indices to read from the bundle
* @param bool $fallback Whether to merge the value with the value from
* the fallback locale (e.g. "en" for "en_GB").
* Only applicable if the result is multivalued
* (i.e. array or \ArrayAccess) or cannot be found
* in the requested locale.
*
* @return mixed returns an array or {@link \ArrayAccess} instance for
* complex data and a scalar value for simple data
*
* @throws MissingResourceException If the indices cannot be accessed
*/
public function readEntry($path, $locale, array $indices, $fallback = true);
}

View File

@@ -0,0 +1,33 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Bundle\Reader;
/**
* Reads resource bundle files.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
interface BundleReaderInterface
{
/**
* Reads a resource bundle.
*
* @param string $path The path to the resource bundle
* @param string $locale The locale to read
*
* @return mixed returns an array or {@link \ArrayAccess} instance for
* complex data, a scalar value otherwise
*/
public function read($path, $locale);
}

View File

@@ -0,0 +1,51 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Bundle\Reader;
use Symfony\Component\Intl\Data\Util\ArrayAccessibleResourceBundle;
use Symfony\Component\Intl\Exception\ResourceBundleNotFoundException;
/**
* Reads binary .res resource bundles.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
class IntlBundleReader implements BundleReaderInterface
{
/**
* {@inheritdoc}
*/
public function read($path, $locale)
{
// Point for future extension: Modify this class so that it works also
// if the \ResourceBundle class is not available.
try {
// Never enable fallback. We want to know if a bundle cannot be found
$bundle = new \ResourceBundle($locale, $path, false);
} catch (\Exception $e) {
// HHVM compatibility: constructor throws on invalid resource
$bundle = null;
}
// The bundle is NULL if the path does not look like a resource bundle
// (i.e. contain a bunch of *.res files)
if (null === $bundle) {
throw new ResourceBundleNotFoundException(sprintf('The resource bundle "%s/%s.res" could not be found.', $path, $locale));
}
// Other possible errors are U_USING_FALLBACK_WARNING and U_ZERO_ERROR,
// which are OK for us.
return new ArrayAccessibleResourceBundle($bundle);
}
}

View File

@@ -0,0 +1,54 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Bundle\Reader;
use Symfony\Component\Intl\Exception\ResourceBundleNotFoundException;
use Symfony\Component\Intl\Exception\RuntimeException;
/**
* Reads .json resource bundles.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
class JsonBundleReader implements BundleReaderInterface
{
/**
* {@inheritdoc}
*/
public function read($path, $locale)
{
$fileName = $path.'/'.$locale.'.json';
// prevent directory traversal attacks
if (\dirname($fileName) !== $path) {
throw new ResourceBundleNotFoundException(sprintf('The resource bundle "%s" does not exist.', $fileName));
}
if (!file_exists($fileName)) {
throw new ResourceBundleNotFoundException(sprintf('The resource bundle "%s" does not exist.', $fileName));
}
if (!is_file($fileName)) {
throw new RuntimeException(sprintf('The resource bundle "%s" is not a file.', $fileName));
}
$data = json_decode(file_get_contents($fileName), true);
if (null === $data) {
throw new RuntimeException(sprintf('The resource bundle "%s" contains invalid JSON: ', $fileName).json_last_error_msg());
}
return $data;
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Bundle\Reader;
use Symfony\Component\Intl\Exception\ResourceBundleNotFoundException;
use Symfony\Component\Intl\Exception\RuntimeException;
/**
* Reads .php resource bundles.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
class PhpBundleReader implements BundleReaderInterface
{
/**
* {@inheritdoc}
*/
public function read($path, $locale)
{
$fileName = $path.'/'.$locale.'.php';
// prevent directory traversal attacks
if (\dirname($fileName) !== $path) {
throw new ResourceBundleNotFoundException(sprintf('The resource bundle "%s" does not exist.', $fileName));
}
if (!file_exists($fileName)) {
throw new ResourceBundleNotFoundException(sprintf('The resource bundle "%s/%s.php" does not exist.', $path, $locale));
}
if (!is_file($fileName)) {
throw new RuntimeException(sprintf('The resource bundle "%s/%s.php" is not a file.', $path, $locale));
}
return include $fileName;
}
}

View File

@@ -0,0 +1,31 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Bundle\Writer;
/**
* Writes resource bundle files.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
interface BundleWriterInterface
{
/**
* Writes data to a resource bundle.
*
* @param string $path The path to the resource bundle
* @param string $locale The locale to (over-)write
* @param mixed $data The data to write
*/
public function write($path, $locale, $data);
}

View File

@@ -0,0 +1,42 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Bundle\Writer;
/**
* Writes .json resource bundles.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
class JsonBundleWriter implements BundleWriterInterface
{
/**
* {@inheritdoc}
*/
public function write($path, $locale, $data)
{
if ($data instanceof \Traversable) {
$data = iterator_to_array($data);
}
array_walk_recursive($data, function (&$value) {
if ($value instanceof \Traversable) {
$value = iterator_to_array($value);
}
});
$contents = json_encode($data, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_UNICODE)."\n";
file_put_contents($path.'/'.$locale.'.json', $contents);
}
}

View File

@@ -0,0 +1,55 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Bundle\Writer;
/**
* Writes .php resource bundles.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
class PhpBundleWriter implements BundleWriterInterface
{
/**
* {@inheritdoc}
*/
public function write($path, $locale, $data)
{
$template = <<<'TEMPLATE'
<?php
return %s;
TEMPLATE;
if ($data instanceof \Traversable) {
$data = iterator_to_array($data);
}
array_walk_recursive($data, function (&$value) {
if ($value instanceof \Traversable) {
$value = iterator_to_array($value);
}
});
$data = var_export($data, true);
$data = preg_replace('/array \(/', '[', $data);
$data = preg_replace('/\n {1,10}\[/', '[', $data);
$data = preg_replace('/ /', ' ', $data);
$data = preg_replace('/\),$/m', '],', $data);
$data = preg_replace('/\)$/', ']', $data);
$data = sprintf($template, $data);
file_put_contents($path.'/'.$locale.'.php', $data);
}
}

View File

@@ -0,0 +1,236 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Bundle\Writer;
use Symfony\Component\Intl\Exception\UnexpectedTypeException;
/**
* Writes .txt resource bundles.
*
* The resulting files can be converted to binary .res files using a
* {@link \Symfony\Component\Intl\ResourceBundle\Compiler\BundleCompilerInterface}
* implementation.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @see http://source.icu-project.org/repos/icu/icuhtml/trunk/design/bnf_rb.txt
*
* @internal
*/
class TextBundleWriter implements BundleWriterInterface
{
/**
* {@inheritdoc}
*/
public function write($path, $locale, $data, $fallback = true)
{
$file = fopen($path.'/'.$locale.'.txt', 'w');
$this->writeResourceBundle($file, $locale, $data, $fallback);
fclose($file);
}
/**
* Writes a "resourceBundle" node.
*
* @param resource $file The file handle to write to
* @param string $bundleName The name of the bundle
* @param mixed $value The value of the node
* @param bool $fallback Whether the resource bundle should be merged
* with the fallback locale
*
* @see http://source.icu-project.org/repos/icu/icuhtml/trunk/design/bnf_rb.txt
*/
private function writeResourceBundle($file, $bundleName, $value, $fallback)
{
fwrite($file, $bundleName);
$this->writeTable($file, $value, 0, $fallback);
fwrite($file, "\n");
}
/**
* Writes a "resource" node.
*
* @param resource $file The file handle to write to
* @param mixed $value The value of the node
* @param int $indentation The number of levels to indent
* @param bool $requireBraces Whether to require braces to be printedaround the value
*
* @see http://source.icu-project.org/repos/icu/icuhtml/trunk/design/bnf_rb.txt
*/
private function writeResource($file, $value, $indentation, $requireBraces = true)
{
if (\is_int($value)) {
$this->writeInteger($file, $value);
return;
}
if ($value instanceof \Traversable) {
$value = iterator_to_array($value);
}
if (\is_array($value)) {
$intValues = \count($value) === \count(array_filter($value, 'is_int'));
$keys = array_keys($value);
// check that the keys are 0-indexed and ascending
$intKeys = $keys === range(0, \count($keys) - 1);
if ($intValues && $intKeys) {
$this->writeIntVector($file, $value, $indentation);
return;
}
if ($intKeys) {
$this->writeArray($file, $value, $indentation);
return;
}
$this->writeTable($file, $value, $indentation);
return;
}
if (\is_bool($value)) {
$value = $value ? 'true' : 'false';
}
$this->writeString($file, (string) $value, $requireBraces);
}
/**
* Writes an "integer" node.
*
* @param resource $file The file handle to write to
* @param int $value The value of the node
*
* @see http://source.icu-project.org/repos/icu/icuhtml/trunk/design/bnf_rb.txt
*/
private function writeInteger($file, $value)
{
fprintf($file, ':int{%d}', $value);
}
/**
* Writes an "intvector" node.
*
* @param resource $file The file handle to write to
* @param array $value The value of the node
* @param int $indentation The number of levels to indent
*
* @see http://source.icu-project.org/repos/icu/icuhtml/trunk/design/bnf_rb.txt
*/
private function writeIntVector($file, array $value, $indentation)
{
fwrite($file, ":intvector{\n");
foreach ($value as $int) {
fprintf($file, "%s%d,\n", str_repeat(' ', $indentation + 1), $int);
}
fprintf($file, '%s}', str_repeat(' ', $indentation));
}
/**
* Writes a "string" node.
*
* @param resource $file The file handle to write to
* @param string $value The value of the node
* @param bool $requireBraces Whether to require braces to be printed
* around the value
*
* @see http://source.icu-project.org/repos/icu/icuhtml/trunk/design/bnf_rb.txt
*/
private function writeString($file, $value, $requireBraces = true)
{
if ($requireBraces) {
fprintf($file, '{"%s"}', $value);
return;
}
fprintf($file, '"%s"', $value);
}
/**
* Writes an "array" node.
*
* @param resource $file The file handle to write to
* @param array $value The value of the node
* @param int $indentation The number of levels to indent
*
* @see http://source.icu-project.org/repos/icu/icuhtml/trunk/design/bnf_rb.txt
*/
private function writeArray($file, array $value, $indentation)
{
fwrite($file, "{\n");
foreach ($value as $entry) {
fwrite($file, str_repeat(' ', $indentation + 1));
$this->writeResource($file, $entry, $indentation + 1, false);
fwrite($file, ",\n");
}
fprintf($file, '%s}', str_repeat(' ', $indentation));
}
/**
* Writes a "table" node.
*
* @param resource $file The file handle to write to
* @param iterable $value The value of the node
* @param int $indentation The number of levels to indent
* @param bool $fallback Whether the table should be merged
* with the fallback locale
*
* @throws UnexpectedTypeException when $value is not an array and not a
* \Traversable instance
*/
private function writeTable($file, $value, $indentation, $fallback = true)
{
if (!\is_array($value) && !$value instanceof \Traversable) {
throw new UnexpectedTypeException($value, 'array or \Traversable');
}
if (!$fallback) {
fwrite($file, ':table(nofallback)');
}
fwrite($file, "{\n");
foreach ($value as $key => $entry) {
fwrite($file, str_repeat(' ', $indentation + 1));
// escape colons, otherwise they are interpreted as resource types
if (false !== strpos($key, ':') || false !== strpos($key, ' ')) {
$key = '"'.$key.'"';
}
fwrite($file, $key);
$this->writeResource($file, $entry, $indentation + 1);
fwrite($file, "\n");
}
fprintf($file, '%s}', str_repeat(' ', $indentation));
}
}

View File

@@ -0,0 +1,129 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Generator;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Intl\Data\Bundle\Compiler\BundleCompilerInterface;
use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReader;
use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReaderInterface;
use Symfony\Component\Intl\Data\Bundle\Reader\IntlBundleReader;
use Symfony\Component\Intl\Data\Util\LocaleScanner;
/**
* The rule for compiling the currency bundle.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
abstract class AbstractDataGenerator
{
private $compiler;
private $dirName;
public function __construct(BundleCompilerInterface $compiler, $dirName)
{
$this->compiler = $compiler;
$this->dirName = (string) $dirName;
}
public function generateData(GeneratorConfig $config)
{
$filesystem = new Filesystem();
$localeScanner = new LocaleScanner();
$reader = new BundleEntryReader(new IntlBundleReader());
$writers = $config->getBundleWriters();
$tempDir = sys_get_temp_dir().'/icu-data-'.$this->dirName;
// Prepare filesystem directories
foreach ($writers as $targetDir => $writer) {
$filesystem->remove($targetDir.'/'.$this->dirName);
$filesystem->mkdir($targetDir.'/'.$this->dirName);
}
$filesystem->remove($tempDir);
$filesystem->mkdir($tempDir);
$locales = $this->scanLocales($localeScanner, $config->getSourceDir());
$this->compileTemporaryBundles($this->compiler, $config->getSourceDir(), $tempDir);
$this->preGenerate();
foreach ($locales as $locale) {
$localeData = $this->generateDataForLocale($reader, $tempDir, $locale);
if (null !== $localeData) {
foreach ($writers as $targetDir => $writer) {
$writer->write($targetDir.'/'.$this->dirName, $locale, $localeData);
}
}
}
$rootData = $this->generateDataForRoot($reader, $tempDir);
if (null !== $rootData) {
foreach ($writers as $targetDir => $writer) {
$writer->write($targetDir.'/'.$this->dirName, 'root', $rootData);
}
}
$metaData = $this->generateDataForMeta($reader, $tempDir);
if (null !== $metaData) {
foreach ($writers as $targetDir => $writer) {
$writer->write($targetDir.'/'.$this->dirName, 'meta', $metaData);
}
}
// Clean up
$filesystem->remove($tempDir);
}
/**
* @param string $sourceDir
*
* @return string[]
*/
abstract protected function scanLocales(LocaleScanner $scanner, $sourceDir);
/**
* @param string $sourceDir
* @param string $tempDir
*/
abstract protected function compileTemporaryBundles(BundleCompilerInterface $compiler, $sourceDir, $tempDir);
abstract protected function preGenerate();
/**
* @param string $tempDir
* @param string $displayLocale
*
* @return array|null
*/
abstract protected function generateDataForLocale(BundleEntryReaderInterface $reader, $tempDir, $displayLocale);
/**
* @param string $tempDir
*
* @return array|null
*/
abstract protected function generateDataForRoot(BundleEntryReaderInterface $reader, $tempDir);
/**
* @param string $tempDir
*
* @return array|null
*/
abstract protected function generateDataForMeta(BundleEntryReaderInterface $reader, $tempDir);
}

View File

@@ -0,0 +1,180 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Generator;
use Symfony\Component\Intl\Data\Bundle\Compiler\BundleCompilerInterface;
use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReaderInterface;
use Symfony\Component\Intl\Data\Util\ArrayAccessibleResourceBundle;
use Symfony\Component\Intl\Data\Util\LocaleScanner;
/**
* The rule for compiling the currency bundle.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
class CurrencyDataGenerator extends AbstractDataGenerator
{
private static $denylist = [
'XBA' => true, // European Composite Unit
'XBB' => true, // European Monetary Unit
'XBC' => true, // European Unit of Account (XBC)
'XBD' => true, // European Unit of Account (XBD)
'XUA' => true, // ADB Unit of Account
'XAU' => true, // Gold
'XAG' => true, // Silver
'XPT' => true, // Platinum
'XPD' => true, // Palladium
'XSU' => true, // Sucre
'XDR' => true, // Special Drawing Rights
'XTS' => true, // Testing Currency Code
'XXX' => true, // Unknown Currency
];
/**
* Collects all available currency codes.
*
* @var string[]
*/
private $currencyCodes = [];
/**
* {@inheritdoc}
*/
protected function scanLocales(LocaleScanner $scanner, $sourceDir)
{
return $scanner->scanLocales($sourceDir.'/curr');
}
/**
* {@inheritdoc}
*/
protected function compileTemporaryBundles(BundleCompilerInterface $compiler, $sourceDir, $tempDir)
{
$compiler->compile($sourceDir.'/curr', $tempDir);
$compiler->compile($sourceDir.'/misc/currencyNumericCodes.txt', $tempDir);
}
/**
* {@inheritdoc}
*/
protected function preGenerate()
{
$this->currencyCodes = [];
}
/**
* {@inheritdoc}
*/
protected function generateDataForLocale(BundleEntryReaderInterface $reader, $tempDir, $displayLocale)
{
$localeBundle = $reader->read($tempDir, $displayLocale);
if (isset($localeBundle['Currencies']) && null !== $localeBundle['Currencies']) {
$data = [
'Names' => $this->generateSymbolNamePairs($localeBundle),
];
$this->currencyCodes = array_merge($this->currencyCodes, array_keys($data['Names']));
return $data;
}
return null;
}
/**
* {@inheritdoc}
*/
protected function generateDataForRoot(BundleEntryReaderInterface $reader, $tempDir)
{
$rootBundle = $reader->read($tempDir, 'root');
return [
'Names' => $this->generateSymbolNamePairs($rootBundle),
];
}
/**
* {@inheritdoc}
*/
protected function generateDataForMeta(BundleEntryReaderInterface $reader, $tempDir)
{
$supplementalDataBundle = $reader->read($tempDir, 'supplementalData');
$numericCodesBundle = $reader->read($tempDir, 'currencyNumericCodes');
$this->currencyCodes = array_unique($this->currencyCodes);
sort($this->currencyCodes);
$data = [
'Currencies' => $this->currencyCodes,
'Meta' => $this->generateCurrencyMeta($supplementalDataBundle),
'Alpha3ToNumeric' => $this->generateAlpha3ToNumericMapping($numericCodesBundle, $this->currencyCodes),
];
$data['NumericToAlpha3'] = $this->generateNumericToAlpha3Mapping($data['Alpha3ToNumeric']);
return $data;
}
/**
* @return array
*/
private function generateSymbolNamePairs(ArrayAccessibleResourceBundle $rootBundle)
{
$symbolNamePairs = iterator_to_array($rootBundle['Currencies']);
// Remove unwanted currencies
$symbolNamePairs = array_diff_key($symbolNamePairs, self::$denylist);
return $symbolNamePairs;
}
private function generateCurrencyMeta(ArrayAccessibleResourceBundle $supplementalDataBundle)
{
// The metadata is already de-duplicated. It contains one key "DEFAULT"
// which is used for currencies that don't have dedicated entries.
return iterator_to_array($supplementalDataBundle['CurrencyMeta']);
}
private function generateAlpha3ToNumericMapping(ArrayAccessibleResourceBundle $numericCodesBundle, array $currencyCodes)
{
$alpha3ToNumericMapping = iterator_to_array($numericCodesBundle['codeMap']);
asort($alpha3ToNumericMapping);
// Filter unknown currencies (e.g. "AYM")
$alpha3ToNumericMapping = array_intersect_key($alpha3ToNumericMapping, array_flip($currencyCodes));
return $alpha3ToNumericMapping;
}
private function generateNumericToAlpha3Mapping(array $alpha3ToNumericMapping)
{
$numericToAlpha3Mapping = [];
foreach ($alpha3ToNumericMapping as $alpha3 => $numeric) {
// Make sure that the mapping is stored as table and not as array
$numeric = (string) $numeric;
if (!isset($numericToAlpha3Mapping[$numeric])) {
$numericToAlpha3Mapping[$numeric] = [];
}
$numericToAlpha3Mapping[$numeric][] = $alpha3;
}
return $numericToAlpha3Mapping;
}
}

View File

@@ -0,0 +1,73 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Generator;
use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReaderInterface;
use Symfony\Component\Intl\Locale;
/**
* @author Roland Franssen <franssen.roland@gmail.com>
*
* @internal
*/
trait FallbackTrait
{
private $fallbackCache = [];
private $generatingFallback = false;
/**
* @param string $tempDir
* @param string $displayLocale
*
* @return array|null
*
* @see AbstractDataGenerator::generateDataForLocale()
*/
abstract protected function generateDataForLocale(BundleEntryReaderInterface $reader, $tempDir, $displayLocale);
/**
* @param string $tempDir
*
* @return array|null
*
* @see AbstractDataGenerator::generateDataForRoot()
*/
abstract protected function generateDataForRoot(BundleEntryReaderInterface $reader, $tempDir);
/**
* @param string $tempDir
* @param string $displayLocale
*
* @return array
*/
private function generateFallbackData(BundleEntryReaderInterface $reader, $tempDir, $displayLocale)
{
if (null === $fallback = Locale::getFallback($displayLocale)) {
return [];
}
if (isset($this->fallbackCache[$fallback])) {
return $this->fallbackCache[$fallback];
}
$prevGeneratingFallback = $this->generatingFallback;
$this->generatingFallback = true;
try {
$data = 'root' === $fallback ? $this->generateDataForRoot($reader, $tempDir) : $this->generateDataForLocale($reader, $tempDir, $fallback);
} finally {
$this->generatingFallback = $prevGeneratingFallback;
}
return $this->fallbackCache[$fallback] = $data ?: [];
}
}

View File

@@ -0,0 +1,84 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Generator;
use Symfony\Component\Intl\Data\Bundle\Writer\BundleWriterInterface;
/**
* Stores contextual information for resource bundle generation.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
class GeneratorConfig
{
private $sourceDir;
private $icuVersion;
/**
* @var BundleWriterInterface[]
*/
private $bundleWriters = [];
/**
* @param string $sourceDir
* @param string $icuVersion
*/
public function __construct($sourceDir, $icuVersion)
{
$this->sourceDir = $sourceDir;
$this->icuVersion = $icuVersion;
}
/**
* Adds a writer to be used during the data conversion.
*
* @param string $targetDir The output directory
* @param BundleWriterInterface $writer The writer instance
*/
public function addBundleWriter($targetDir, BundleWriterInterface $writer)
{
$this->bundleWriters[$targetDir] = $writer;
}
/**
* Returns the writers indexed by their output directories.
*
* @return BundleWriterInterface[]
*/
public function getBundleWriters()
{
return $this->bundleWriters;
}
/**
* Returns the directory where the source versions of the resource bundles
* are stored.
*
* @return string An absolute path to a directory
*/
public function getSourceDir()
{
return $this->sourceDir;
}
/**
* Returns the ICU version of the bundles being converted.
*
* @return string The ICU version string
*/
public function getIcuVersion()
{
return $this->icuVersion;
}
}

View File

@@ -0,0 +1,198 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Generator;
use Symfony\Component\Intl\Data\Bundle\Compiler\BundleCompilerInterface;
use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReaderInterface;
use Symfony\Component\Intl\Data\Util\ArrayAccessibleResourceBundle;
use Symfony\Component\Intl\Data\Util\LocaleScanner;
use Symfony\Component\Intl\Exception\RuntimeException;
/**
* The rule for compiling the language bundle.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
class LanguageDataGenerator extends AbstractDataGenerator
{
/**
* Source: https://iso639-3.sil.org/code_tables/639/data.
*/
private static $preferredAlpha2ToAlpha3Mapping = [
'ak' => 'aka',
'ar' => 'ara',
'ay' => 'aym',
'az' => 'aze',
'bo' => 'bod',
'cr' => 'cre',
'cs' => 'ces',
'cy' => 'cym',
'de' => 'deu',
'dz' => 'dzo',
'el' => 'ell',
'et' => 'est',
'eu' => 'eus',
'fa' => 'fas',
'ff' => 'ful',
'fr' => 'fra',
'gn' => 'grn',
'hy' => 'hye',
'hr' => 'hrv',
'ik' => 'ipk',
'is' => 'isl',
'iu' => 'iku',
'ka' => 'kat',
'kr' => 'kau',
'kg' => 'kon',
'kv' => 'kom',
'ku' => 'kur',
'lv' => 'lav',
'mg' => 'mlg',
'mi' => 'mri',
'mk' => 'mkd',
'mn' => 'mon',
'ms' => 'msa',
'my' => 'mya',
'nb' => 'nob',
'ne' => 'nep',
'nl' => 'nld',
'oj' => 'oji',
'om' => 'orm',
'or' => 'ori',
'ps' => 'pus',
'qu' => 'que',
'ro' => 'ron',
'sc' => 'srd',
'sk' => 'slk',
'sq' => 'sqi',
'sr' => 'srp',
'sw' => 'swa',
'uz' => 'uzb',
'yi' => 'yid',
'za' => 'zha',
'zh' => 'zho',
];
/**
* Collects all available language codes.
*
* @var string[]
*/
private $languageCodes = [];
/**
* {@inheritdoc}
*/
protected function scanLocales(LocaleScanner $scanner, $sourceDir)
{
return $scanner->scanLocales($sourceDir.'/lang');
}
/**
* {@inheritdoc}
*/
protected function compileTemporaryBundles(BundleCompilerInterface $compiler, $sourceDir, $tempDir)
{
$compiler->compile($sourceDir.'/lang', $tempDir);
$compiler->compile($sourceDir.'/misc/metadata.txt', $tempDir);
}
/**
* {@inheritdoc}
*/
protected function preGenerate()
{
$this->languageCodes = [];
}
/**
* {@inheritdoc}
*/
protected function generateDataForLocale(BundleEntryReaderInterface $reader, $tempDir, $displayLocale)
{
$localeBundle = $reader->read($tempDir, $displayLocale);
// isset() on \ResourceBundle returns true even if the value is null
if (isset($localeBundle['Languages']) && null !== $localeBundle['Languages']) {
$data = [
'Names' => iterator_to_array($localeBundle['Languages']),
];
$this->languageCodes = array_merge($this->languageCodes, array_keys($data['Names']));
return $data;
}
return null;
}
/**
* {@inheritdoc}
*/
protected function generateDataForRoot(BundleEntryReaderInterface $reader, $tempDir)
{
}
/**
* {@inheritdoc}
*/
protected function generateDataForMeta(BundleEntryReaderInterface $reader, $tempDir)
{
$metadataBundle = $reader->read($tempDir, 'metadata');
$this->languageCodes = array_unique($this->languageCodes);
sort($this->languageCodes);
return [
'Languages' => $this->languageCodes,
'Alpha2ToAlpha3' => $this->generateAlpha2ToAlpha3Mapping($metadataBundle),
];
}
private function generateAlpha2ToAlpha3Mapping(ArrayAccessibleResourceBundle $metadataBundle)
{
$aliases = iterator_to_array($metadataBundle['alias']['language']);
$alpha2ToAlpha3 = [];
foreach ($aliases as $alias => $data) {
$language = $data['replacement'];
if (2 === \strlen($language) && 3 === \strlen($alias) && 'overlong' === $data['reason']) {
if (isset(self::$preferredAlpha2ToAlpha3Mapping[$language])) {
// Validate to prevent typos
if (!isset($aliases[self::$preferredAlpha2ToAlpha3Mapping[$language]])) {
throw new RuntimeException('The statically set three-letter mapping '.self::$preferredAlpha2ToAlpha3Mapping[$language].' for the language code '.$language.' seems to be invalid. Typo?');
}
$alpha3 = self::$preferredAlpha2ToAlpha3Mapping[$language];
$alpha2 = $aliases[$alpha3]['replacement'];
if ($language !== $alpha2) {
throw new RuntimeException('The statically set three-letter mapping '.$alpha3.' for the language code '.$language.' seems to be an alias for '.$alpha2.'. Wrong mapping?');
}
$alpha2ToAlpha3[$language] = $alpha3;
} elseif (isset($alpha2ToAlpha3[$language])) {
throw new RuntimeException('Multiple three-letter mappings exist for the language code '.$language.'. Please add one of them to the property $preferredAlpha2ToAlpha3Mapping.');
} else {
$alpha2ToAlpha3[$language] = $alias;
}
}
}
asort($alpha2ToAlpha3);
return $alpha2ToAlpha3;
}
}

View File

@@ -0,0 +1,180 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Generator;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Intl\Data\Bundle\Compiler\BundleCompilerInterface;
use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReaderInterface;
use Symfony\Component\Intl\Data\Util\LocaleScanner;
use Symfony\Component\Intl\Exception\MissingResourceException;
/**
* The rule for compiling the locale bundle.
*
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Roland Franssen <franssen.roland@gmail.com>
*
* @internal
*/
class LocaleDataGenerator extends AbstractDataGenerator
{
use FallbackTrait;
private $locales = [];
private $localeAliases = [];
/**
* {@inheritdoc}
*/
protected function scanLocales(LocaleScanner $scanner, $sourceDir)
{
$this->locales = $scanner->scanLocales($sourceDir.'/locales');
$this->localeAliases = $scanner->scanAliases($sourceDir.'/locales');
return $this->locales;
}
/**
* {@inheritdoc}
*/
protected function compileTemporaryBundles(BundleCompilerInterface $compiler, $sourceDir, $tempDir)
{
$filesystem = new Filesystem();
$filesystem->mkdir([
$tempDir.'/lang',
$tempDir.'/region',
]);
$compiler->compile($sourceDir.'/lang', $tempDir.'/lang');
$compiler->compile($sourceDir.'/region', $tempDir.'/region');
}
/**
* {@inheritdoc}
*/
protected function preGenerate()
{
}
/**
* {@inheritdoc}
*/
protected function generateDataForLocale(BundleEntryReaderInterface $reader, $tempDir, $displayLocale)
{
// Don't generate aliases, as they are resolved during runtime
// Unless an alias is needed as fallback for de-duplication purposes
if (isset($this->localeAliases[$displayLocale]) && !$this->generatingFallback) {
return null;
}
// Generate locale names for all locales that have translations in
// at least the language or the region bundle
$displayFormat = $reader->readEntry($tempDir.'/lang', $displayLocale, ['localeDisplayPattern']);
$pattern = $displayFormat['pattern'] ?? '{0} ({1})';
$separator = $displayFormat['separator'] ?? '{0}, {1}';
$localeNames = [];
foreach ($this->locales as $locale) {
// Ensure a normalized list of pure locales
if (\Locale::getAllVariants($locale)) {
continue;
}
try {
// Generate a locale name in the language of each display locale
// Each locale name has the form: "Language (Script, Region, Variant1, ...)
// Script, Region and Variants are optional. If none of them is
// available, the braces are not printed.
$localeNames[$locale] = $this->generateLocaleName($reader, $tempDir, $locale, $displayLocale, $pattern, $separator);
} catch (MissingResourceException $e) {
// Silently ignore incomplete locale names
// In this case one should configure at least one fallback locale that is complete (e.g. English) during
// runtime. Alternatively a translation for the missing resource can be proposed upstream.
}
}
$data = [
'Names' => $localeNames,
];
// Don't de-duplicate a fallback locale
// Ensures the display locale can be de-duplicated on itself
if ($this->generatingFallback) {
return $data;
}
// Process again to de-duplicate locale and its fallback locales
// Only keep the differences
$fallbackData = $this->generateFallbackData($reader, $tempDir, $displayLocale);
if (isset($fallbackData['Names'])) {
$data['Names'] = array_diff($data['Names'], $fallbackData['Names']);
}
if (!$data['Names']) {
return null;
}
return $data;
}
/**
* {@inheritdoc}
*/
protected function generateDataForRoot(BundleEntryReaderInterface $reader, $tempDir)
{
}
/**
* {@inheritdoc}
*/
protected function generateDataForMeta(BundleEntryReaderInterface $reader, $tempDir)
{
return [
'Locales' => $this->locales,
'Aliases' => $this->localeAliases,
];
}
/**
* @return string
*/
private function generateLocaleName(BundleEntryReaderInterface $reader, $tempDir, $locale, $displayLocale, $pattern, $separator)
{
// Apply generic notation using square brackets as described per http://cldr.unicode.org/translation/language-names
$name = str_replace(['(', ')'], ['[', ']'], $reader->readEntry($tempDir.'/lang', $displayLocale, ['Languages', \Locale::getPrimaryLanguage($locale)]));
$extras = [];
// Discover the name of the script part of the locale
// i.e. in zh_Hans_MO, "Hans" is the script
if ($script = \Locale::getScript($locale)) {
$extras[] = str_replace(['(', ')'], ['[', ']'], $reader->readEntry($tempDir.'/lang', $displayLocale, ['Scripts', $script]));
}
// Discover the name of the region part of the locale
// i.e. in de_AT, "AT" is the region
if ($region = \Locale::getRegion($locale)) {
if (!RegionDataGenerator::isValidCountryCode($region)) {
throw new MissingResourceException(sprintf('Skipping "%s" due an invalid country.', $locale));
}
$extras[] = str_replace(['(', ')'], ['[', ']'], $reader->readEntry($tempDir.'/region', $displayLocale, ['Countries', $region]));
}
if ($extras) {
$extra = array_shift($extras);
foreach ($extras as $part) {
$extra = str_replace(['{0}', '{1}'], [$extra, $part], $separator);
}
$name = str_replace(['{0}', '{1}'], [$name, $extra], $pattern);
}
return $name;
}
}

View File

@@ -0,0 +1,149 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Generator;
use Symfony\Component\Intl\Data\Bundle\Compiler\BundleCompilerInterface;
use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReaderInterface;
use Symfony\Component\Intl\Data\Util\ArrayAccessibleResourceBundle;
use Symfony\Component\Intl\Data\Util\LocaleScanner;
/**
* The rule for compiling the region bundle.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @see http://source.icu-project.org/repos/icu/icu4j/trunk/main/classes/core/src/com/ibm/icu/util/Region.java
*
* @internal
*/
class RegionDataGenerator extends AbstractDataGenerator
{
private static $denylist = [
// Look like countries, but are sub-continents
'QO' => true, // Outlying Oceania
'EU' => true, // European Union
'EZ' => true, // Eurozone
'UN' => true, // United Nations
// Uninhabited islands
'BV' => true, // Bouvet Island
'HM' => true, // Heard & McDonald Islands
'CP' => true, // Clipperton Island
// Misc
'ZZ' => true, // Unknown Region
];
/**
* Collects all available language codes.
*
* @var string[]
*/
private $regionCodes = [];
public static function isValidCountryCode($region)
{
if (isset(self::$denylist[$region])) {
return false;
}
// WORLD/CONTINENT/SUBCONTINENT/GROUPING
if (ctype_digit($region) || \is_int($region)) {
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
protected function scanLocales(LocaleScanner $scanner, $sourceDir)
{
return $scanner->scanLocales($sourceDir.'/region');
}
/**
* {@inheritdoc}
*/
protected function compileTemporaryBundles(BundleCompilerInterface $compiler, $sourceDir, $tempDir)
{
$compiler->compile($sourceDir.'/region', $tempDir);
}
/**
* {@inheritdoc}
*/
protected function preGenerate()
{
$this->regionCodes = [];
}
/**
* {@inheritdoc}
*/
protected function generateDataForLocale(BundleEntryReaderInterface $reader, $tempDir, $displayLocale)
{
$localeBundle = $reader->read($tempDir, $displayLocale);
// isset() on \ResourceBundle returns true even if the value is null
if (isset($localeBundle['Countries']) && null !== $localeBundle['Countries']) {
$data = [
'Names' => $this->generateRegionNames($localeBundle),
];
$this->regionCodes = array_merge($this->regionCodes, array_keys($data['Names']));
return $data;
}
return null;
}
/**
* {@inheritdoc}
*/
protected function generateDataForRoot(BundleEntryReaderInterface $reader, $tempDir)
{
}
/**
* {@inheritdoc}
*/
protected function generateDataForMeta(BundleEntryReaderInterface $reader, $tempDir)
{
$this->regionCodes = array_unique($this->regionCodes);
sort($this->regionCodes);
return [
'Regions' => $this->regionCodes,
];
}
/**
* @return array
*/
protected function generateRegionNames(ArrayAccessibleResourceBundle $localeBundle)
{
$unfilteredRegionNames = iterator_to_array($localeBundle['Countries']);
$regionNames = [];
foreach ($unfilteredRegionNames as $region => $regionName) {
if (!self::isValidCountryCode($region)) {
continue;
}
$regionNames[$region] = $regionName;
}
return $regionNames;
}
}

View File

@@ -0,0 +1,99 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Generator;
use Symfony\Component\Intl\Data\Bundle\Compiler\BundleCompilerInterface;
use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReaderInterface;
use Symfony\Component\Intl\Data\Util\LocaleScanner;
/**
* The rule for compiling the script bundle.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
class ScriptDataGenerator extends AbstractDataGenerator
{
/**
* Collects all available language codes.
*
* @var string[]
*/
private $scriptCodes = [];
/**
* {@inheritdoc}
*/
protected function scanLocales(LocaleScanner $scanner, $sourceDir)
{
return $scanner->scanLocales($sourceDir.'/lang');
}
/**
* {@inheritdoc}
*/
protected function compileTemporaryBundles(BundleCompilerInterface $compiler, $sourceDir, $tempDir)
{
$compiler->compile($sourceDir.'/lang', $tempDir);
}
/**
* {@inheritdoc}
*/
protected function preGenerate()
{
$this->scriptCodes = [];
}
/**
* {@inheritdoc}
*/
protected function generateDataForLocale(BundleEntryReaderInterface $reader, $tempDir, $displayLocale)
{
$localeBundle = $reader->read($tempDir, $displayLocale);
// isset() on \ResourceBundle returns true even if the value is null
if (isset($localeBundle['Scripts']) && null !== $localeBundle['Scripts']) {
$data = [
'Names' => iterator_to_array($localeBundle['Scripts']),
];
$this->scriptCodes = array_merge($this->scriptCodes, array_keys($data['Names']));
return $data;
}
return null;
}
/**
* {@inheritdoc}
*/
protected function generateDataForRoot(BundleEntryReaderInterface $reader, $tempDir)
{
}
/**
* {@inheritdoc}
*/
protected function generateDataForMeta(BundleEntryReaderInterface $reader, $tempDir)
{
$this->scriptCodes = array_unique($this->scriptCodes);
sort($this->scriptCodes);
return [
'Scripts' => $this->scriptCodes,
];
}
}

View File

@@ -0,0 +1,141 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Provider;
use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReaderInterface;
use Symfony\Component\Intl\Exception\MissingResourceException;
/**
* Data provider for currency-related data.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
class CurrencyDataProvider
{
const INDEX_SYMBOL = 0;
const INDEX_NAME = 1;
const INDEX_FRACTION_DIGITS = 0;
const INDEX_ROUNDING_INCREMENT = 1;
private $path;
private $reader;
/**
* Creates a data provider that reads currency-related data from a
* resource bundle.
*
* @param string $path The path to the resource bundle
* @param BundleEntryReaderInterface $reader The reader for reading the resource bundle
*/
public function __construct($path, BundleEntryReaderInterface $reader)
{
$this->path = $path;
$this->reader = $reader;
}
public function getCurrencies()
{
return $this->reader->readEntry($this->path, 'meta', ['Currencies']);
}
public function getSymbol($currency, $displayLocale = null)
{
if (null === $displayLocale) {
$displayLocale = \Locale::getDefault();
}
return $this->reader->readEntry($this->path, $displayLocale, ['Names', $currency, static::INDEX_SYMBOL]);
}
public function getName($currency, $displayLocale = null)
{
if (null === $displayLocale) {
$displayLocale = \Locale::getDefault();
}
return $this->reader->readEntry($this->path, $displayLocale, ['Names', $currency, static::INDEX_NAME]);
}
public function getNames($displayLocale = null)
{
if (null === $displayLocale) {
$displayLocale = \Locale::getDefault();
}
// ====================================================================
// For reference: It is NOT possible to return names indexed by
// numeric code here, because some numeric codes map to multiple
// 3-letter codes (e.g. 32 => "ARA", "ARP", "ARS")
// ====================================================================
$names = $this->reader->readEntry($this->path, $displayLocale, ['Names']);
if ($names instanceof \Traversable) {
$names = iterator_to_array($names);
}
$index = static::INDEX_NAME;
array_walk($names, function (&$value) use ($index) {
$value = $value[$index];
});
// Sorting by value cannot be done during bundle generation, because
// binary bundles are always sorted by keys
$collator = new \Collator($displayLocale);
$collator->asort($names);
return $names;
}
/**
* Data provider for {@link \Symfony\Component\Intl\Currency::getFractionDigits()}.
*/
public function getFractionDigits($currency)
{
try {
return $this->reader->readEntry($this->path, 'meta', ['Meta', $currency, static::INDEX_FRACTION_DIGITS]);
} catch (MissingResourceException $e) {
return $this->reader->readEntry($this->path, 'meta', ['Meta', 'DEFAULT', static::INDEX_FRACTION_DIGITS]);
}
}
/**
* Data provider for {@link \Symfony\Component\Intl\Currency::getRoundingIncrement()}.
*/
public function getRoundingIncrement($currency)
{
try {
return $this->reader->readEntry($this->path, 'meta', ['Meta', $currency, static::INDEX_ROUNDING_INCREMENT]);
} catch (MissingResourceException $e) {
return $this->reader->readEntry($this->path, 'meta', ['Meta', 'DEFAULT', static::INDEX_ROUNDING_INCREMENT]);
}
}
/**
* Data provider for {@link \Symfony\Component\Intl\Currency::getNumericCode()}.
*/
public function getNumericCode($currency)
{
return $this->reader->readEntry($this->path, 'meta', ['Alpha3ToNumeric', $currency]);
}
/**
* Data provider for {@link \Symfony\Component\Intl\Currency::forNumericCode()}.
*/
public function forNumericCode($numericCode)
{
return $this->reader->readEntry($this->path, 'meta', ['NumericToAlpha3', (string) $numericCode]);
}
}

View File

@@ -0,0 +1,81 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Provider;
use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReaderInterface;
/**
* Data provider for language-related ICU data.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
class LanguageDataProvider
{
private $path;
private $reader;
/**
* Creates a data provider that reads locale-related data from .res files.
*
* @param string $path The path to the directory containing the .res files
* @param BundleEntryReaderInterface $reader The reader for reading the .res files
*/
public function __construct($path, BundleEntryReaderInterface $reader)
{
$this->path = $path;
$this->reader = $reader;
}
public function getLanguages()
{
return $this->reader->readEntry($this->path, 'meta', ['Languages']);
}
public function getAliases()
{
return $this->reader->readEntry($this->path, 'root', ['Aliases']);
}
public function getName($language, $displayLocale = null)
{
if (null === $displayLocale) {
$displayLocale = \Locale::getDefault();
}
return $this->reader->readEntry($this->path, $displayLocale, ['Names', $language]);
}
public function getNames($displayLocale = null)
{
if (null === $displayLocale) {
$displayLocale = \Locale::getDefault();
}
$languages = $this->reader->readEntry($this->path, $displayLocale, ['Names']);
if ($languages instanceof \Traversable) {
$languages = iterator_to_array($languages);
}
$collator = new \Collator($displayLocale);
$collator->asort($languages);
return $languages;
}
public function getAlpha3Code($language)
{
return $this->reader->readEntry($this->path, 'meta', ['Alpha2ToAlpha3', $language]);
}
}

View File

@@ -0,0 +1,82 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Provider;
use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReaderInterface;
/**
* Data provider for locale-related ICU data.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
class LocaleDataProvider
{
private $path;
private $reader;
/**
* Creates a data provider that reads locale-related data from .res files.
*
* @param string $path The path to the directory containing the .res files
* @param BundleEntryReaderInterface $reader The reader for reading the .res files
*/
public function __construct($path, BundleEntryReaderInterface $reader)
{
$this->path = $path;
$this->reader = $reader;
}
public function getLocales()
{
return $this->reader->readEntry($this->path, 'meta', ['Locales']);
}
public function getAliases()
{
$aliases = $this->reader->readEntry($this->path, 'meta', ['Aliases']);
if ($aliases instanceof \Traversable) {
$aliases = iterator_to_array($aliases);
}
return $aliases;
}
public function getName($locale, $displayLocale = null)
{
if (null === $displayLocale) {
$displayLocale = \Locale::getDefault();
}
return $this->reader->readEntry($this->path, $displayLocale, ['Names', $locale]);
}
public function getNames($displayLocale = null)
{
if (null === $displayLocale) {
$displayLocale = \Locale::getDefault();
}
$names = $this->reader->readEntry($this->path, $displayLocale, ['Names']);
if ($names instanceof \Traversable) {
$names = iterator_to_array($names);
}
$collator = new \Collator($displayLocale);
$collator->asort($names);
return $names;
}
}

View File

@@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Provider;
use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReaderInterface;
/**
* Data provider for region-related ICU data.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
class RegionDataProvider
{
private $path;
private $reader;
/**
* Creates a data provider that reads locale-related data from .res files.
*
* @param string $path The path to the directory containing the .res files
* @param BundleEntryReaderInterface $reader The reader for reading the .res files
*/
public function __construct($path, BundleEntryReaderInterface $reader)
{
$this->path = $path;
$this->reader = $reader;
}
public function getRegions()
{
return $this->reader->readEntry($this->path, 'meta', ['Regions']);
}
public function getName($region, $displayLocale = null)
{
if (null === $displayLocale) {
$displayLocale = \Locale::getDefault();
}
return $this->reader->readEntry($this->path, $displayLocale, ['Names', $region]);
}
public function getNames($displayLocale = null)
{
if (null === $displayLocale) {
$displayLocale = \Locale::getDefault();
}
$names = $this->reader->readEntry($this->path, $displayLocale, ['Names']);
if ($names instanceof \Traversable) {
$names = iterator_to_array($names);
}
$collator = new \Collator($displayLocale);
$collator->asort($names);
return $names;
}
}

View File

@@ -0,0 +1,71 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Provider;
use Symfony\Component\Intl\Data\Bundle\Reader\BundleEntryReaderInterface;
/**
* Data provider for script-related ICU data.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
class ScriptDataProvider
{
private $path;
private $reader;
/**
* Creates a data provider that reads locale-related data from .res files.
*
* @param string $path The path to the directory containing the .res files
* @param BundleEntryReaderInterface $reader The reader for reading the .res files
*/
public function __construct($path, BundleEntryReaderInterface $reader)
{
$this->path = $path;
$this->reader = $reader;
}
public function getScripts()
{
return $this->reader->readEntry($this->path, 'meta', ['Scripts']);
}
public function getName($script, $displayLocale = null)
{
if (null === $displayLocale) {
$displayLocale = \Locale::getDefault();
}
return $this->reader->readEntry($this->path, $displayLocale, ['Names', $script]);
}
public function getNames($displayLocale = null)
{
if (null === $displayLocale) {
$displayLocale = \Locale::getDefault();
}
$names = $this->reader->readEntry($this->path, $displayLocale, ['Names']);
if ($names instanceof \Traversable) {
$names = iterator_to_array($names);
}
$collator = new \Collator($displayLocale);
$collator->asort($names);
return $names;
}
}

View File

@@ -0,0 +1,81 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Util;
use Symfony\Component\Intl\Exception\BadMethodCallException;
/**
* Work-around for a bug in PHP's \ResourceBundle implementation.
*
* More information can be found on https://bugs.php.net/64356.
* This class can be removed once that bug is fixed.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
class ArrayAccessibleResourceBundle implements \ArrayAccess, \IteratorAggregate, \Countable
{
private $bundleImpl;
public function __construct(\ResourceBundle $bundleImpl)
{
$this->bundleImpl = $bundleImpl;
}
public function get($offset)
{
$value = $this->bundleImpl->get($offset);
return $value instanceof \ResourceBundle ? new static($value) : $value;
}
public function offsetExists($offset)
{
return null !== $this->bundleImpl->get($offset);
}
public function offsetGet($offset)
{
return $this->get($offset);
}
public function offsetSet($offset, $value)
{
throw new BadMethodCallException('Resource bundles cannot be modified.');
}
public function offsetUnset($offset)
{
throw new BadMethodCallException('Resource bundles cannot be modified.');
}
public function getIterator()
{
return $this->bundleImpl;
}
public function count()
{
return $this->bundleImpl->count();
}
public function getErrorCode()
{
return $this->bundleImpl->getErrorCode();
}
public function getErrorMessage()
{
return $this->bundleImpl->getErrorMessage();
}
}

View File

@@ -0,0 +1,85 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Util;
/**
* Scans a directory with data files for locales.
*
* The name of each file with the extension ".txt" is considered, if it "looks"
* like a locale:
*
* - the name must start with two letters;
* - the two letters may optionally be followed by an underscore and any
* sequence of other symbols.
*
* For example, "de" and "de_DE" are considered to be locales. "root" and "meta"
* are not.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
class LocaleScanner
{
/**
* Returns all locales found in the given directory.
*
* @param string $sourceDir The directory with ICU files
*
* @return array An array of locales. The result also contains locales that
* are in fact just aliases for other locales. Use
* {@link scanAliases()} to determine which of the locales
* are aliases
*/
public function scanLocales($sourceDir)
{
$locales = glob($sourceDir.'/*.txt', \GLOB_NOSORT);
// Remove file extension and sort
array_walk($locales, function (&$locale) { $locale = basename($locale, '.txt'); });
// Remove non-locales
$locales = array_filter($locales, function ($locale) {
return preg_match('/^[a-z]{2}(_.+)?$/', $locale);
});
sort($locales);
return $locales;
}
/**
* Returns all locale aliases found in the given directory.
*
* @param string $sourceDir The directory with ICU files
*
* @return array An array with the locale aliases as keys and the aliased
* locales as values
*/
public function scanAliases($sourceDir)
{
$locales = $this->scanLocales($sourceDir);
$aliases = [];
// Delete locales that are no aliases
foreach ($locales as $locale) {
$content = file_get_contents($sourceDir.'/'.$locale.'.txt');
// Aliases contain the text "%%ALIAS" followed by the aliased locale
if (preg_match('/"%%ALIAS"\{"([^"]+)"\}/', $content, $matches)) {
$aliases[$locale] = $matches[1];
}
}
return $aliases;
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Util;
use Symfony\Component\Intl\Exception\OutOfBoundsException;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
class RecursiveArrayAccess
{
public static function get($array, array $indices)
{
foreach ($indices as $index) {
// Use array_key_exists() for arrays, isset() otherwise
if (\is_array($array)) {
if (\array_key_exists($index, $array)) {
$array = $array[$index];
continue;
}
} elseif ($array instanceof \ArrayAccess) {
if (isset($array[$index])) {
$array = $array[$index];
continue;
}
}
throw new OutOfBoundsException(sprintf('The index "%s" does not exist.', $index));
}
return $array;
}
private function __construct()
{
}
}

View File

@@ -0,0 +1,87 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Intl\Data\Util;
use Symfony\Component\Intl\Exception\OutOfBoundsException;
/**
* Implements a ring buffer.
*
* A ring buffer is an array-like structure with a fixed size. If the buffer
* is full, the next written element overwrites the first bucket in the buffer,
* then the second and so on.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @internal
*/
class RingBuffer implements \ArrayAccess
{
private $values = [];
private $indices = [];
private $cursor = 0;
private $size;
public function __construct($size)
{
$this->size = $size;
}
/**
* {@inheritdoc}
*/
public function offsetExists($key)
{
return isset($this->indices[$key]);
}
/**
* {@inheritdoc}
*/
public function offsetGet($key)
{
if (!isset($this->indices[$key])) {
throw new OutOfBoundsException(sprintf('The index "%s" does not exist.', $key));
}
return $this->values[$this->indices[$key]];
}
/**
* {@inheritdoc}
*/
public function offsetSet($key, $value)
{
if (false !== ($keyToRemove = array_search($this->cursor, $this->indices))) {
unset($this->indices[$keyToRemove]);
}
$this->values[$this->cursor] = $value;
$this->indices[$key] = $this->cursor;
$this->cursor = ($this->cursor + 1) % $this->size;
}
/**
* {@inheritdoc}
*/
public function offsetUnset($key)
{
if (isset($this->indices[$key])) {
$this->values[$this->indices[$key]] = null;
unset($this->indices[$key]);
}
}
}