From 77cbd7320b9813f2896ca55301ef327d9c484bb5 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Wed, 2 Oct 2013 14:26:38 +0200 Subject: [PATCH 01/48] [Intl] Made TextBundleWriter able to write "nofallback" resource bundles --- .../ResourceBundle/Writer/TextBundleWriter.php | 18 +++++++++++++----- .../Writer/Fixtures/en_nofallback.txt | 3 +++ .../Writer/TextBundleWriterTest.php | 11 +++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/en_nofallback.txt diff --git a/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php b/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php index 342ee2dc5cd1c..3ede1fac89cc5 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php @@ -26,11 +26,11 @@ class TextBundleWriter implements BundleWriterInterface /** * {@inheritdoc} */ - public function write($path, $locale, $data) + public function write($path, $locale, $data, $fallback = true) { $file = fopen($path.'/'.$locale.'.txt', 'w'); - $this->writeResourceBundle($file, $locale, $data); + $this->writeResourceBundle($file, $locale, $data, $fallback); fclose($file); } @@ -41,14 +41,16 @@ public function write($path, $locale, $data) * @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 Boolean $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) + private function writeResourceBundle($file, $bundleName, $value, $fallback) { fwrite($file, $bundleName); - $this->writeTable($file, $value, 0); + $this->writeTable($file, $value, 0, $fallback); fwrite($file, "\n"); } @@ -183,9 +185,15 @@ private function writeArray($file, array $value, $indentation) * @param resource $file The file handle to write to. * @param array $value The value of the node. * @param integer $indentation The number of levels to indent. + * @param Boolean $fallback Whether the table should be merged with the + * fallback locale. */ - private function writeTable($file, array $value, $indentation) + private function writeTable($file, array $value, $indentation, $fallback = true) { + if (!$fallback) { + fwrite($file, ":table(nofallback)"); + } + fwrite($file, "{\n"); foreach ($value as $key => $entry) { diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/en_nofallback.txt b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/en_nofallback.txt new file mode 100644 index 0000000000000..85386f2074dc2 --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/en_nofallback.txt @@ -0,0 +1,3 @@ +en_nofallback:table(nofallback){ + Entry{"Value"} +} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php index cbe0c8d8bfc4b..fc8d696a52ad0 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php @@ -64,4 +64,15 @@ public function testWrite() $this->assertFileEquals(__DIR__ . '/Fixtures/en.txt', $this->directory . '/en.txt'); } + + public function testWriteNoFallback() + { + $data = array( + 'Entry' => 'Value' + ); + + $this->writer->write($this->directory, 'en_nofallback', $data, $fallback = false); + + $this->assertFileEquals(__DIR__ . '/Fixtures/en_nofallback.txt', $this->directory . '/en_nofallback.txt'); + } } From ba12cd4d70c635fe92e45c354ce57d06f57b0ca0 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Wed, 2 Oct 2013 14:36:03 +0200 Subject: [PATCH 02/48] [Intl] Made TextBundleWriter able to write traversables --- .../Exception/UnexpectedTypeException.php | 26 +++++++++++++++++++ .../Writer/TextBundleWriter.php | 25 +++++++++++++----- .../Writer/TextBundleWriterTest.php | 18 +++++++++++++ 3 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 src/Symfony/Component/Intl/Exception/UnexpectedTypeException.php diff --git a/src/Symfony/Component/Intl/Exception/UnexpectedTypeException.php b/src/Symfony/Component/Intl/Exception/UnexpectedTypeException.php new file mode 100644 index 0000000000000..20a4517443d1b --- /dev/null +++ b/src/Symfony/Component/Intl/Exception/UnexpectedTypeException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Exception; + +/** + * Thrown when a method argument had an unexpected type. + * + * @since 2.4 + * @author Bernhard Schussek + */ +class UnexpectedTypeException extends InvalidArgumentException +{ + public function __construct($value, $expectedType) + { + parent::__construct(sprintf('Expected argument of type "%s", "%s" given', $expectedType, is_object($value) ? get_class($value) : gettype($value))); + } +} diff --git a/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php b/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php index 3ede1fac89cc5..48096c1a1a527 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Intl\ResourceBundle\Writer; +use Symfony\Component\Intl\Exception\UnexpectedTypeException; + /** * Writes .txt resource bundles. * @@ -74,6 +76,10 @@ private function writeResource($file, $value, $indentation, $requireBraces = tru return; } + if ($value instanceof \Traversable) { + $value = iterator_to_array($value); + } + if (is_array($value)) { if (count($value) === count(array_filter($value, 'is_int'))) { $this->writeIntVector($file, $value, $indentation); @@ -182,14 +188,21 @@ private function writeArray($file, array $value, $indentation) /** * Writes a "table" node. * - * @param resource $file The file handle to write to. - * @param array $value The value of the node. - * @param integer $indentation The number of levels to indent. - * @param Boolean $fallback Whether the table should be merged with the - * fallback locale. + * @param resource $file The file handle to write to. + * @param array|\Traversable $value The value of the node. + * @param integer $indentation The number of levels to indent. + * @param Boolean $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, array $value, $indentation, $fallback = true) + 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)"); } diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php index fc8d696a52ad0..5595035818dab 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php @@ -65,6 +65,24 @@ public function testWrite() $this->assertFileEquals(__DIR__ . '/Fixtures/en.txt', $this->directory . '/en.txt'); } + public function testWriteTraversable() + { + $this->writer->write($this->directory, 'en', new \ArrayIterator(array( + 'Entry1' => new \ArrayIterator(array( + 'Array' => array('foo', 'bar', array('Key' => 'value')), + 'Integer' => 5, + 'IntVector' => array(0, 1, 2, 3), + 'FalseBoolean' => false, + 'TrueBoolean' => true, + 'Null' => null, + 'Float' => 1.23, + )), + 'Entry2' => 'String', + ))); + + $this->assertFileEquals(__DIR__ . '/Fixtures/en.txt', $this->directory . '/en.txt'); + } + public function testWriteNoFallback() { $data = array( From e14dd3a01f39b1601c2fc57b0258911a88046453 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Wed, 2 Oct 2013 15:54:49 +0200 Subject: [PATCH 03/48] [Intl] The various Intl methods now throw a NoSuchLocaleException whenever an invalid locale is given --- src/Symfony/Component/Intl/CHANGELOG.md | 8 +++++++ .../Intl/Exception/NoSuchLocaleException.php | 21 ++++++++++++++++++ .../Reader/BinaryBundleReader.php | 19 ++++++++++++++++ .../ResourceBundle/Reader/PhpBundleReader.php | 4 ++-- .../Reader/BinaryBundleReaderTest.php | 8 +++---- .../ResourceBundle/Reader/Fixtures/root.res | Bin 0 -> 84 bytes .../ResourceBundle/Reader/Fixtures/root.txt | 3 +++ .../Reader/PhpBundleReaderTest.php | 2 +- 8 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 src/Symfony/Component/Intl/CHANGELOG.md create mode 100644 src/Symfony/Component/Intl/Exception/NoSuchLocaleException.php create mode 100644 src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/root.res create mode 100644 src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/root.txt diff --git a/src/Symfony/Component/Intl/CHANGELOG.md b/src/Symfony/Component/Intl/CHANGELOG.md new file mode 100644 index 0000000000000..e4e36dcbcb6c5 --- /dev/null +++ b/src/Symfony/Component/Intl/CHANGELOG.md @@ -0,0 +1,8 @@ +CHANGELOG +========= + +2.4.0 +----- + + * [BC BREAK] the various Intl methods now throw a `NoSuchLocaleException` + whenever an invalid locale is given diff --git a/src/Symfony/Component/Intl/Exception/NoSuchLocaleException.php b/src/Symfony/Component/Intl/Exception/NoSuchLocaleException.php new file mode 100644 index 0000000000000..4853481c3952e --- /dev/null +++ b/src/Symfony/Component/Intl/Exception/NoSuchLocaleException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Exception; + +/** + * Thrown when an invalid locale was requested. + * + * @author Bernhard Schussek + */ +class NoSuchLocaleException extends RuntimeException +{ +} diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/BinaryBundleReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/BinaryBundleReader.php index 56cef806da5b7..a23adf35d8b64 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/BinaryBundleReader.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/BinaryBundleReader.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Intl\ResourceBundle\Reader; use Symfony\Component\Intl\Exception\RuntimeException; +use Symfony\Component\Intl\Exception\NoSuchLocaleException; use Symfony\Component\Intl\ResourceBundle\Util\ArrayAccessibleResourceBundle; /** @@ -30,6 +31,8 @@ public function read($path, $locale) // if the \ResourceBundle class is not available. $bundle = new \ResourceBundle($locale, $path); + // 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 RuntimeException(sprintf( 'Could not load the resource bundle "%s/%s.res".', @@ -38,6 +41,22 @@ public function read($path, $locale) )); } + // The error U_USING_DEFAULT_WARNING appears if the locale is not found, + // no fallback can be used and the current default locale is used + // instead. + // Note that fallback to default is only working when a bundle contains + // a root.res file. + if (U_USING_DEFAULT_WARNING === $bundle->getErrorCode()) { + throw new NoSuchLocaleException(sprintf( + 'Could not load the resource bundle "%s" for locale "%s".', + $path, + $locale + )); + } + + // Other possible errors are U_USING_FALLBACK_WARNING and U_ZERO_ERROR, + // which are OK for us. + return new ArrayAccessibleResourceBundle($bundle); } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/PhpBundleReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/PhpBundleReader.php index 663bcc9d789d8..eaf26c7b64651 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/PhpBundleReader.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/PhpBundleReader.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Intl\ResourceBundle\Reader; -use Symfony\Component\Intl\Exception\InvalidArgumentException; +use Symfony\Component\Intl\Exception\NoSuchLocaleException; use Symfony\Component\Intl\Exception\RuntimeException; /** @@ -27,7 +27,7 @@ class PhpBundleReader extends AbstractBundleReader implements BundleReaderInterf public function read($path, $locale) { if ('en' !== $locale) { - throw new InvalidArgumentException('Only the locale "en" is supported.'); + throw new NoSuchLocaleException('Only the locale "en" is supported.'); } $fileName = $path . '/' . $locale . '.php'; diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BinaryBundleReaderTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BinaryBundleReaderTest.php index 3aefbae7fd911..8312ac36b166c 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BinaryBundleReaderTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BinaryBundleReaderTest.php @@ -33,7 +33,7 @@ protected function setUp() public function testReadReturnsArrayAccess() { - $data = $this->reader->read(__DIR__ . '/Fixtures', 'en'); + $data = $this->reader->read(__DIR__.'/Fixtures', 'en'); $this->assertInstanceOf('\ArrayAccess', $data); $this->assertSame('Bar', $data['Foo']); @@ -41,11 +41,11 @@ public function testReadReturnsArrayAccess() } /** - * @expectedException \Symfony\Component\Intl\Exception\RuntimeException + * @expectedException \Symfony\Component\Intl\Exception\NoSuchLocaleException */ public function testReadFailsIfNonExistingLocale() { - $this->reader->read(__DIR__ . '/Fixtures', 'foo'); + $this->reader->read(__DIR__.'/Fixtures', 'foo'); } /** @@ -53,6 +53,6 @@ public function testReadFailsIfNonExistingLocale() */ public function testReadFailsIfNonExistingDirectory() { - $this->reader->read(__DIR__ . '/foo', 'en'); + $this->reader->read(__DIR__.'/foo', 'en'); } } diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/root.res b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/root.res new file mode 100644 index 0000000000000000000000000000000000000000..c78e9045bf2b5a8a8cd8e71fad2c34c93d0581c3 GIT binary patch literal 84 zcmY#jxTP+_00K-5L8-+~Oh6VR3s?Y5ure?Numf3~K+Frpj9?iE-z`5M$a7*yWGDjC Jj0_4u3;+cH1(N^( literal 0 HcmV?d00001 diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/root.txt b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/root.txt new file mode 100644 index 0000000000000..9d1349bac5e20 --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/root.txt @@ -0,0 +1,3 @@ +root{ + Version{"1.0"} +} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/PhpBundleReaderTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/PhpBundleReaderTest.php index 2fee35599fd43..cb4b144d2d3d1 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/PhpBundleReaderTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/PhpBundleReaderTest.php @@ -38,7 +38,7 @@ public function testReadReturnsArray() } /** - * @expectedException \Symfony\Component\Intl\Exception\InvalidArgumentException + * @expectedException \Symfony\Component\Intl\Exception\NoSuchLocaleException */ public function testReadFailsIfLocaleOtherThanEn() { From 953a29cb20feaecd7327e7555c9f32523610a633 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Wed, 2 Oct 2013 18:21:21 +0200 Subject: [PATCH 04/48] [Intl] Changed Intl methods to throw NoSuchEntriesExceptions when non-existing languages, currencies, etc. are accessed --- src/Symfony/Component/Intl/CHANGELOG.md | 2 + .../Intl/Exception/NoSuchEntryException.php | 21 +++ src/Symfony/Component/Intl/Intl.php | 16 +++ .../Intl/ResourceBundle/LanguageBundle.php | 38 ++---- .../Reader/StructuredBundleReader.php | 121 ++++++++++++------ .../Rule/LocaleBundleTransformationRule.php | 8 +- .../Util/RecursiveArrayAccess.php | 7 +- .../ResourceBundle/LanguageBundleTest.php | 117 ++++------------- .../Reader/StructuredBundleReaderTest.php | 100 ++++++++++----- 9 files changed, 235 insertions(+), 195 deletions(-) create mode 100644 src/Symfony/Component/Intl/Exception/NoSuchEntryException.php diff --git a/src/Symfony/Component/Intl/CHANGELOG.md b/src/Symfony/Component/Intl/CHANGELOG.md index e4e36dcbcb6c5..84cb9d5f79583 100644 --- a/src/Symfony/Component/Intl/CHANGELOG.md +++ b/src/Symfony/Component/Intl/CHANGELOG.md @@ -6,3 +6,5 @@ CHANGELOG * [BC BREAK] the various Intl methods now throw a `NoSuchLocaleException` whenever an invalid locale is given + * [BC BREAK] the various Intl methods now throw a `NoSuchEntryException` + whenever a non-existing language, currency, etc. is accessed diff --git a/src/Symfony/Component/Intl/Exception/NoSuchEntryException.php b/src/Symfony/Component/Intl/Exception/NoSuchEntryException.php new file mode 100644 index 0000000000000..494d816887993 --- /dev/null +++ b/src/Symfony/Component/Intl/Exception/NoSuchEntryException.php @@ -0,0 +1,21 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Exception; + +/** + * Thrown when an invalid entry of a resource bundle was requested. + * + * @author Bernhard Schussek + */ +class NoSuchEntryException extends RuntimeException +{ +} diff --git a/src/Symfony/Component/Intl/Intl.php b/src/Symfony/Component/Intl/Intl.php index d0373988d2fd4..e72bb2c564b9f 100644 --- a/src/Symfony/Component/Intl/Intl.php +++ b/src/Symfony/Component/Intl/Intl.php @@ -187,6 +187,22 @@ public static function getIcuStubVersion() return '51.2'; } + /** + * Returns the fallback locale for a given locale, if any + * + * @param string $locale The locale to find the fallback for. + * + * @return string|null The fallback locale, or null if no parent exists + */ + public static function getFallbackLocale($locale) + { + if (false === $pos = strrpos($locale, '_')) { + return null; + } + + return substr($locale, 0, $pos); + } + /** * Returns a resource bundle reader for .php resource bundle files. * diff --git a/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php b/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php index 6b98a29e39741..3c0262ce2f3a2 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Intl\ResourceBundle; +use Symfony\Component\Intl\Exception\NoSuchEntryException; + /** * Default implementation of {@link LanguageBundleInterface}. * @@ -27,17 +29,16 @@ public function getLanguageName($lang, $region = null, $locale = null) $locale = \Locale::getDefault(); } - if (null === ($languages = $this->readEntry($locale, array('Languages'), true))) { - return null; - } - // Some languages are translated together with their region, // i.e. "en_GB" is translated as "British English" - if (null !== $region && isset($languages[$lang.'_'.$region])) { - return $languages[$lang.'_'.$region]; + if (null !== $region) { + try { + return $this->readEntry($locale, array('Languages', $lang.'_'.$region), true); + } catch (NoSuchEntryException $e) { + } } - return $languages[$lang]; + return $this->readEntry($locale, array('Languages', $lang), true); } /** @@ -69,28 +70,7 @@ public function getScriptName($script, $lang = null, $locale = null) $locale = \Locale::getDefault(); } - $data = $this->read($locale); - - // Some languages are translated together with their script, - // e.g. "zh_Hans" is translated as "Simplified Chinese" - if (null !== $lang && isset($data['Languages'][$lang.'_'.$script])) { - $langName = $data['Languages'][$lang.'_'.$script]; - - // If the script is appended in braces, extract it, e.g. "zh_Hans" - // is translated as "Chinesisch (vereinfacht)" in locale "de" - if (strpos($langName, '(') !== false) { - list($langName, $scriptName) = preg_split('/[\s()]/', $langName, null, PREG_SPLIT_NO_EMPTY); - - return $scriptName; - } - } - - // "af" (Afrikaans) has no "Scripts" block - if (!isset($data['Scripts'][$script])) { - return null; - } - - return $data['Scripts'][$script]; + return $this->readEntry($locale, array('Scripts', $script)); } /** diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReader.php index e3656fe2ebdf6..ea687e166189b 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReader.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReader.php @@ -11,6 +11,9 @@ namespace Symfony\Component\Intl\ResourceBundle\Reader; +use Symfony\Component\Intl\Intl; +use Symfony\Component\Intl\Exception\NoSuchEntryException; +use Symfony\Component\Intl\Exception\OutOfBoundsException; use Symfony\Component\Intl\ResourceBundle\Util\RecursiveArrayAccess; /** @@ -58,56 +61,96 @@ public function getLocales($path) */ public function readEntry($path, $locale, array $indices, $fallback = true) { - $data = $this->reader->read($path, $locale); - - $entry = RecursiveArrayAccess::get($data, $indices); - $multivalued = is_array($entry) || $entry instanceof \Traversable; - - if (!($fallback && (null === $entry || $multivalued))) { - return $entry; - } + $entry = null; + $isMultiValued = false; + $readSucceeded = false; + $exception = null; + $currentLocale = $locale; + $testedLocales = array(); + + while (null !== $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; + } - if (null !== ($fallbackLocale = $this->getFallbackLocale($locale))) { - $parentEntry = $this->readEntry($path, $fallbackLocale, $indices, true); + // ========================================================= + // Fallback is enabled, entry is either multi-valued or NULL + // ========================================================= - if ($entry || $parentEntry) { - $multivalued = $multivalued || is_array($parentEntry) || $parentEntry instanceof \Traversable; + // If entry is multi-valued, convert to array + if ($isCurrentTraversable) { + $currentEntry = iterator_to_array($currentEntry); + } - if ($multivalued) { - if ($entry instanceof \Traversable) { - $entry = iterator_to_array($entry); - } + // If previously read entry was multi-valued too, merge them + if ($isCurrentMultiValued && $isMultiValued) { + $currentEntry = array_merge($currentEntry, $entry); + } - if ($parentEntry instanceof \Traversable) { - $parentEntry = iterator_to_array($parentEntry); - } + // Keep the previous entry if the current entry is NULL + if (null !== $currentEntry) { + $entry = $currentEntry; + } - $entry = array_merge( - $parentEntry ?: array(), - $entry ?: array() - ); - } else { - $entry = null === $entry ? $parentEntry : $entry; + // If this or the previous entry was multi-valued, we are dealing + // with a merged, multi-valued entry now + $isMultiValued = $isMultiValued || $isCurrentMultiValued; + } catch (OutOfBoundsException $e) { + // Remember exception and rethrow if we cannot find anything in + // the fallback locales either + if (null === $exception) { + $exception = $e; } } + + // Remember which locales we tried + $testedLocales[] = $currentLocale.'.res'; + + // Go to fallback locale + $currentLocale = Intl::getFallbackLocale($currentLocale); } - return $entry; - } + // Multi-valued entry was merged + if ($isMultiValued) { + return $entry; + } - /** - * Returns the fallback locale for a given locale, if any - * - * @param string $locale The locale to find the fallback for. - * - * @return string|null The fallback locale, or null if no parent exists - */ - private function getFallbackLocale($locale) - { - if (false === $pos = strrpos($locale, '_')) { - return null; + // 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( + 'Error while reading the indices [%s] in "%s/%s.res": %s.', + implode('][', $indices), + $path, + $locale, + $exception->getMessage() + ); + + // Append fallback locales, if any + if (count($testedLocales) > 1) { + // Remove original locale + array_shift($testedLocales); + + $errorMessage .= sprintf( + ' The index could also be found in neither of the fallback locales: "%s".', + implode('", "', $testedLocales) + ); } - return substr($locale, 0, $pos); + throw new NoSuchEntryException($errorMessage, 0, $exception); } } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php index b2576d6eec826..69797167b25ba 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Intl\ResourceBundle\Transformer\Rule; +use Symfony\Component\Intl\Exception\NoSuchEntryException; use Symfony\Component\Intl\Exception\RuntimeException; use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContextInterface; @@ -164,8 +165,11 @@ private function generateTextFiles($targetDirectory, array $locales) continue; } - if (null !== ($name = $this->generateLocaleName($locale, $displayLocale))) { - $names[$locale] = $name; + try { + if (null !== ($name = $this->generateLocaleName($locale, $displayLocale))) { + $names[$locale] = $name; + } + } catch (NoSuchEntryException $e) { } } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Util/RecursiveArrayAccess.php b/src/Symfony/Component/Intl/ResourceBundle/Util/RecursiveArrayAccess.php index e1feaa2ce0551..8e932561d8a23 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Util/RecursiveArrayAccess.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Util/RecursiveArrayAccess.php @@ -11,6 +11,8 @@ namespace Symfony\Component\Intl\ResourceBundle\Util; +use Symfony\Component\Intl\Exception\OutOfBoundsException; + /** * @author Bernhard Schussek */ @@ -19,8 +21,9 @@ class RecursiveArrayAccess public static function get($array, array $indices) { foreach ($indices as $index) { - if (!$array instanceof \ArrayAccess && !is_array($array)) { - return null; + // Use array_key_exists() for arrays, isset() otherwise + if (is_array($array) && !array_key_exists($index, $array) || !is_array($array) && !isset($array[$index])) { + throw new OutOfBoundsException('The index '.$index.' does not exist'); } $array = $array[$index]; diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/LanguageBundleTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/LanguageBundleTest.php index 96031fc7c01f0..25326b7ff6cd5 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/LanguageBundleTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/LanguageBundleTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Intl\Tests\ResourceBundle; +use Symfony\Component\Intl\Exception\NoSuchEntryException; use Symfony\Component\Intl\ResourceBundle\LanguageBundle; /** @@ -38,46 +39,35 @@ protected function setUp() public function testGetLanguageName() { - $languages = array( - 'de' => 'German', - 'en' => 'English', - ); - $this->reader->expects($this->once()) ->method('readEntry') - ->with(self::RES_DIR, 'en', array('Languages')) - ->will($this->returnValue($languages)); + ->with(self::RES_DIR, 'en', array('Languages', 'de')) + ->will($this->returnValue('German')); $this->assertSame('German', $this->bundle->getLanguageName('de', null, 'en')); } public function testGetLanguageNameWithRegion() { - $languages = array( - 'de' => 'German', - 'en' => 'English', - 'en_GB' => 'British English', - ); - $this->reader->expects($this->once()) ->method('readEntry') - ->with(self::RES_DIR, 'en', array('Languages')) - ->will($this->returnValue($languages)); + ->with(self::RES_DIR, 'en', array('Languages', 'en_GB')) + ->will($this->returnValue('British English')); $this->assertSame('British English', $this->bundle->getLanguageName('en', 'GB', 'en')); } public function testGetLanguageNameWithUntranslatedRegion() { - $languages = array( - 'de' => 'German', - 'en' => 'English', - ); + $this->reader->expects($this->at(0)) + ->method('readEntry') + ->with(self::RES_DIR, 'en', array('Languages', 'en_US')) + ->will($this->throwException(new NoSuchEntryException())); - $this->reader->expects($this->once()) + $this->reader->expects($this->at(1)) ->method('readEntry') - ->with(self::RES_DIR, 'en', array('Languages')) - ->will($this->returnValue($languages)); + ->with(self::RES_DIR, 'en', array('Languages', 'en')) + ->will($this->returnValue('English')); $this->assertSame('English', $this->bundle->getLanguageName('en', 'US', 'en')); } @@ -99,85 +89,24 @@ public function testGetLanguageNames() public function testGetScriptName() { - $data = array( - 'Languages' => array( - 'de' => 'German', - 'en' => 'English', - ), - 'Scripts' => array( - 'Latn' => 'latin', - 'Cyrl' => 'cyrillique', - ), - ); - - $this->reader->expects($this->once()) - ->method('read') - ->with(self::RES_DIR, 'en') - ->will($this->returnValue($data)); - - $this->assertSame('latin', $this->bundle->getScriptName('Latn', null, 'en')); - } - - public function testGetScriptNameIncludedInLanguage() - { - $data = array( - 'Languages' => array( - 'de' => 'German', - 'en' => 'English', - 'zh_Hans' => 'Simplified Chinese', - ), - 'Scripts' => array( - 'Latn' => 'latin', - 'Cyrl' => 'cyrillique', - ), - ); - - $this->reader->expects($this->once()) - ->method('read') - ->with(self::RES_DIR, 'en') - ->will($this->returnValue($data)); - - // Null because the script is included in the language anyway - $this->assertNull($this->bundle->getScriptName('Hans', 'zh', 'en')); - } - - public function testGetScriptNameIncludedInLanguageInBraces() - { - $data = array( - 'Languages' => array( - 'de' => 'German', - 'en' => 'English', - 'zh_Hans' => 'Chinese (simplified)', - ), - 'Scripts' => array( - 'Latn' => 'latin', - 'Cyrl' => 'cyrillique', - ), - ); - $this->reader->expects($this->once()) - ->method('read') - ->with(self::RES_DIR, 'en') - ->will($this->returnValue($data)); + ->method('readEntry') + ->with(self::RES_DIR, 'en', array('Scripts', 'Latn')) + ->will($this->returnValue('Latin')); - $this->assertSame('simplified', $this->bundle->getScriptName('Hans', 'zh', 'en')); + $this->assertSame('Latin', $this->bundle->getScriptName('Latn', null, 'en')); } - public function testGetScriptNameNoScriptsBlock() + public function testGetScriptNameIncludedInLanguageBC() { - $data = array( - 'Languages' => array( - 'de' => 'German', - 'en' => 'English', - ), - ); - $this->reader->expects($this->once()) - ->method('read') - ->with(self::RES_DIR, 'en') - ->will($this->returnValue($data)); + ->method('readEntry') + ->with(self::RES_DIR, 'en', array('Scripts', 'Latn')) + ->will($this->returnValue('Latin')); - $this->assertNull($this->bundle->getScriptName('Latn', null, 'en')); + // the second argument once was used, but is now ignored since it + // doesn't make a difference anyway + $this->assertSame('Latin', $this->bundle->getScriptName('Latn', 'zh', 'en')); } public function testGetScriptNames() diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/StructuredBundleReaderTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/StructuredBundleReaderTest.php index 600236eb3ec56..28c376309108f 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/StructuredBundleReaderTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/StructuredBundleReaderTest.php @@ -48,7 +48,7 @@ public function testGetLocales() $this->assertSame($locales, $this->reader->getLocales(self::RES_DIR)); } - public function testRead() + public function testForwardCallToRead() { $data = array('foo', 'bar'); @@ -60,7 +60,7 @@ public function testRead() $this->assertSame($data, $this->reader->read(self::RES_DIR, 'en')); } - public function testReadEntryNoParams() + public function testReadCompleteDataFile() { $data = array('foo', 'bar'); @@ -72,7 +72,7 @@ public function testReadEntryNoParams() $this->assertSame($data, $this->reader->readEntry(self::RES_DIR, 'en', array())); } - public function testReadEntryWithParam() + public function testReadExistingEntry() { $data = array('Foo' => array('Bar' => 'Baz')); @@ -84,7 +84,10 @@ public function testReadEntryWithParam() $this->assertSame('Baz', $this->reader->readEntry(self::RES_DIR, 'en', array('Foo', 'Bar'))); } - public function testReadEntryWithUnresolvablePath() + /** + * @expectedException \Symfony\Component\Intl\Exception\NoSuchEntryException + */ + public function testReadNonExistingEntry() { $data = array('Foo' => 'Baz'); @@ -93,10 +96,29 @@ public function testReadEntryWithUnresolvablePath() ->with(self::RES_DIR, 'en') ->will($this->returnValue($data)); - $this->assertNull($this->reader->readEntry(self::RES_DIR, 'en', array('Foo', 'Bar'))); + $this->reader->readEntry(self::RES_DIR, 'en', array('Foo', 'Bar')); + } + + public function testFallbackIfEntryDoesNotExist() + { + $data = array('Foo' => 'Bar'); + + $this->readerImpl->expects($this->at(0)) + ->method('read') + ->with(self::RES_DIR, 'en_GB') + ->will($this->returnValue($data)); + + $fallbackData = array('Foo' => array('Bar' => 'Baz')); + + $this->readerImpl->expects($this->at(1)) + ->method('read') + ->with(self::RES_DIR, 'en') + ->will($this->returnValue($fallbackData)); + + $this->assertSame('Baz', $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'))); } - public function readMergedEntryProvider() + public function provideMergeableValues() { return array( array('foo', null, 'foo'), @@ -110,9 +132,9 @@ public function readMergedEntryProvider() } /** - * @dataProvider readMergedEntryProvider + * @dataProvider provideMergeableValues */ - public function testReadMergedEntryNoParams($childData, $parentData, $result) + public function testMergeDataWithFallbackData($childData, $parentData, $result) { $this->readerImpl->expects($this->at(0)) ->method('read') @@ -130,9 +152,22 @@ public function testReadMergedEntryNoParams($childData, $parentData, $result) } /** - * @dataProvider readMergedEntryProvider + * @dataProvider provideMergeableValues + */ + public function testDontMergeDataIfFallbackDisabled($childData, $parentData, $result) + { + $this->readerImpl->expects($this->once()) + ->method('read') + ->with(self::RES_DIR, 'en_GB') + ->will($this->returnValue($childData)); + + $this->assertSame($childData, $this->reader->readEntry(self::RES_DIR, 'en_GB', array(), false)); + } + + /** + * @dataProvider provideMergeableValues */ - public function testReadMergedEntryWithParams($childData, $parentData, $result) + public function testMergeExistingEntryWithExistingFallbackEntry($childData, $parentData, $result) { $this->readerImpl->expects($this->at(0)) ->method('read') @@ -149,7 +184,10 @@ public function testReadMergedEntryWithParams($childData, $parentData, $result) $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'), true)); } - public function testReadMergedEntryWithUnresolvablePath() + /** + * @dataProvider provideMergeableValues + */ + public function testMergeNonExistingEntryWithExistingFallbackEntry($childData, $parentData, $result) { $this->readerImpl->expects($this->at(0)) ->method('read') @@ -159,29 +197,35 @@ public function testReadMergedEntryWithUnresolvablePath() $this->readerImpl->expects($this->at(1)) ->method('read') ->with(self::RES_DIR, 'en') - ->will($this->returnValue(array('Foo' => 'Bar'))); + ->will($this->returnValue(array('Foo' => array('Bar' => $parentData)))); - $this->assertNull($this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'), true)); + $this->assertSame($parentData, $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'), true)); } - public function testReadMergedEntryWithUnresolvablePathInParent() + /** + * @dataProvider provideMergeableValues + */ + public function testMergeExistingEntryWithNonExistingFallbackEntry($childData, $parentData, $result) { $this->readerImpl->expects($this->at(0)) ->method('read') ->with(self::RES_DIR, 'en_GB') - ->will($this->returnValue(array('Foo' => array('Bar' => array('three'))))); - - $this->readerImpl->expects($this->at(1)) - ->method('read') - ->with(self::RES_DIR, 'en') - ->will($this->returnValue(array('Foo' => 'Bar'))); + ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); - $result = array('three'); + if (null === $childData || is_array($childData)) { + $this->readerImpl->expects($this->at(1)) + ->method('read') + ->with(self::RES_DIR, 'en') + ->will($this->returnValue(array('Foo' => 'Bar'))); + } - $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'), true)); + $this->assertSame($childData, $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'), true)); } - public function testReadMergedEntryWithUnresolvablePathInChild() + /** + * @expectedException \Symfony\Component\Intl\Exception\NoSuchEntryException + */ + public function testFailIfEntryFoundNeitherInParentNorChild() { $this->readerImpl->expects($this->at(0)) ->method('read') @@ -191,17 +235,15 @@ public function testReadMergedEntryWithUnresolvablePathInChild() $this->readerImpl->expects($this->at(1)) ->method('read') ->with(self::RES_DIR, 'en') - ->will($this->returnValue(array('Foo' => array('Bar' => array('one', 'two'))))); - - $result = array('one', 'two'); + ->will($this->returnValue(array('Foo' => 'Bar'))); - $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'), true)); + $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'), true); } /** - * @dataProvider readMergedEntryProvider + * @dataProvider provideMergeableValues */ - public function testReadMergedEntryWithTraversables($childData, $parentData, $result) + public function testMergeTraversables($childData, $parentData, $result) { $parentData = is_array($parentData) ? new \ArrayObject($parentData) : $parentData; $childData = is_array($childData) ? new \ArrayObject($childData) : $childData; From 0b5408be840e549c5538766b25b2519cb0c3c55c Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Wed, 2 Oct 2013 18:55:32 +0200 Subject: [PATCH 05/48] [Intl] The available locales of each resource bundle are now stored in a generic misc.res file to improve reading performance --- src/Symfony/Component/Intl/CHANGELOG.md | 2 + .../ResourceBundle/Scanner/LocaleScanner.php | 91 +++++++ .../Transformer/CompilationContext.php | 19 +- .../CompilationContextInterface.php | 7 + .../Rule/CurrencyBundleTransformationRule.php | 49 +++- .../Rule/LanguageBundleTransformationRule.php | 13 +- .../Rule/LocaleBundleTransformationRule.php | 59 +---- .../Rule/RegionBundleTransformationRule.php | 13 +- .../Resources/bin/update-icu-component.php | 225 ++++++++++-------- 9 files changed, 307 insertions(+), 171 deletions(-) create mode 100644 src/Symfony/Component/Intl/ResourceBundle/Scanner/LocaleScanner.php diff --git a/src/Symfony/Component/Intl/CHANGELOG.md b/src/Symfony/Component/Intl/CHANGELOG.md index 84cb9d5f79583..19223ff6af3ea 100644 --- a/src/Symfony/Component/Intl/CHANGELOG.md +++ b/src/Symfony/Component/Intl/CHANGELOG.md @@ -8,3 +8,5 @@ CHANGELOG whenever an invalid locale is given * [BC BREAK] the various Intl methods now throw a `NoSuchEntryException` whenever a non-existing language, currency, etc. is accessed + * the available locales of each resource bundle are now stored in a generic + "misc.res" file in order to improve reading performance diff --git a/src/Symfony/Component/Intl/ResourceBundle/Scanner/LocaleScanner.php b/src/Symfony/Component/Intl/ResourceBundle/Scanner/LocaleScanner.php new file mode 100644 index 0000000000000..b8c49e64a247a --- /dev/null +++ b/src/Symfony/Component/Intl/ResourceBundle/Scanner/LocaleScanner.php @@ -0,0 +1,91 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\ResourceBundle\Scanner; + +/** + * Scans a directory with text data files for locales. + * + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * Do NOT use this class in your own code. Backwards compatibility can NOT be + * guaranteed and BC breaks will NOT be documented. + * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + * + * The name of each *.txt file (without suffix) in the given source directory + * is considered a locale. + * + * @author Bernhard Schussek + */ +class LocaleScanner +{ + /** + * A list of known non-locales. + * + * @var array + */ + private static $blackList = array( + 'root', + 'misc', + 'supplementalData', + 'supplementaldata', + ); + + /** + * Returns all locales found in the given directory. + * + * @param string $sourceDir The directory with ICU *.txt 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'); + + // Remove non-locales + $locales = array_diff($locales, static::$blackList); + + // Remove file extension and sort + array_walk($locales, function (&$locale) { $locale = basename($locale, '.txt'); }); + sort($locales); + + return $locales; + } + + /** + * Returns all locale aliases found in the given directory. + * + * @param string $sourceDir The directory with ICU *.txt 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 = array(); + + // 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; + } + +} diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/CompilationContext.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/CompilationContext.php index cdc1951b96bcc..fc71bf58bf98c 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/CompilationContext.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/CompilationContext.php @@ -13,6 +13,7 @@ use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Intl\ResourceBundle\Compiler\BundleCompilerInterface; +use Symfony\Component\Intl\ResourceBundle\Scanner\LocaleScanner; /** * Default implementation of {@link CompilationContextInterface}. @@ -32,7 +33,7 @@ class CompilationContext implements CompilationContextInterface private $binaryDir; /** - * @var FileSystem + * @var Filesystem */ private $filesystem; @@ -46,13 +47,19 @@ class CompilationContext implements CompilationContextInterface */ private $icuVersion; - public function __construct($sourceDir, $binaryDir, Filesystem $filesystem, BundleCompilerInterface $compiler, $icuVersion) + /** + * @var LocaleScanner + */ + private $localeScanner; + + public function __construct($sourceDir, $binaryDir, Filesystem $filesystem, BundleCompilerInterface $compiler, $icuVersion, LocaleScanner $localeScanner = null) { $this->sourceDir = $sourceDir; $this->binaryDir = $binaryDir; $this->filesystem = $filesystem; $this->compiler = $compiler; $this->icuVersion = $icuVersion; + $this->localeScanner = $localeScanner ?: new LocaleScanner(); } /** @@ -94,4 +101,12 @@ public function getIcuVersion() { return $this->icuVersion; } + + /** + * {@inheritdoc} + */ + public function getLocaleScanner() + { + return $this->localeScanner; + } } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/CompilationContextInterface.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/CompilationContextInterface.php index f05c28079a211..06e8d9c6a8ae2 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/CompilationContextInterface.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/CompilationContextInterface.php @@ -53,4 +53,11 @@ public function getCompiler(); * @return string The ICU version string. */ public function getIcuVersion(); + + /** + * Returns a locale scanner. + * + * @return \Symfony\Component\Intl\ResourceBundle\Scanner\LocaleScanner The locale scanner. + */ + public function getLocaleScanner(); } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php index 95783b3b06a19..11e2b1b1d06e8 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php @@ -13,8 +13,10 @@ use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\ResourceBundle\CurrencyBundle; +use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader; use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContextInterface; use Symfony\Component\Intl\ResourceBundle\Transformer\StubbingContextInterface; +use Symfony\Component\Intl\ResourceBundle\Writer\TextBundleWriter; use Symfony\Component\Intl\Util\IcuVersion; /** @@ -37,16 +39,44 @@ public function getBundleName() */ public function beforeCompile(CompilationContextInterface $context) { + $tempDir = sys_get_temp_dir() . '/icu-data-currencies-source'; + // The currency data is contained in the locales and misc bundles // in ICU <= 4.2 if (IcuVersion::compare($context->getIcuVersion(), '4.2', '<=', 1)) { - return array( - $context->getSourceDir() . '/misc/supplementalData.txt', - $context->getSourceDir() . '/locales' - ); + $supplementalData = $context->getSourceDir().'/misc/supplementalData.txt'; + $sourceDir = $context->getSourceDir().'/locales'; + } else { + $supplementalData = $context->getSourceDir().'/curr/supplementalData.txt'; + $sourceDir = $context->getSourceDir().'/curr'; } - return $context->getSourceDir() . '/curr'; + $context->getFilesystem()->remove($tempDir); + $context->getFilesystem()->mkdir(array( + $tempDir, + $tempDir.'/txt', + $tempDir.'/res' + )); + $context->getFilesystem()->copy($supplementalData, $tempDir.'/txt/misc.txt'); + + // Replace "supplementalData" in the file by "misc" before compilation + file_put_contents($tempDir.'/txt/misc.txt', str_replace('supplementalData', 'misc', file_get_contents($tempDir.'/txt/misc.txt'))); + + $context->getCompiler()->compile($tempDir.'/txt', $tempDir.'/res'); + + // Read file, add locales and write again + $reader = new BinaryBundleReader(); + $data = iterator_to_array($reader->read($tempDir.'/res', 'misc')); + + // Key must not exist + assert(!isset($data['Locales'])); + + $data['Locales'] = $context->getLocaleScanner()->scanLocales($sourceDir); + + $writer = new TextBundleWriter(); + $writer->write($sourceDir, 'misc', $data, false); + + return $sourceDir; } /** @@ -54,13 +84,8 @@ public function beforeCompile(CompilationContextInterface $context) */ public function afterCompile(CompilationContextInterface $context) { - // \ResourceBundle does not like locale names with uppercase chars, so rename - // the resource file - // See: http://bugs.php.net/bug.php?id=54025 - $fileName = $context->getBinaryDir() . '/curr/supplementalData.res'; - $fileNameLower = $context->getBinaryDir() . '/curr/supplementaldata.res'; - - $context->getFilesystem()->rename($fileName, $fileNameLower); + // Remove supplementalData.res, whose content is contained within misc.res + $context->getFilesystem()->remove($context->getBinaryDir().'/curr/supplementalData.res'); } /** diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php index 5e6f901849dac..af42fac81b235 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php @@ -14,6 +14,7 @@ use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContextInterface; use Symfony\Component\Intl\ResourceBundle\Transformer\StubbingContextInterface; +use Symfony\Component\Intl\ResourceBundle\Writer\TextBundleWriter; use Symfony\Component\Intl\Util\IcuVersion; /** @@ -38,10 +39,18 @@ public function beforeCompile(CompilationContextInterface $context) { // The language data is contained in the locales bundle in ICU <= 4.2 if (IcuVersion::compare($context->getIcuVersion(), '4.2', '<=', 1)) { - return $context->getSourceDir() . '/locales'; + $sourceDir = $context->getSourceDir() . '/locales'; + } else { + $sourceDir = $context->getSourceDir() . '/lang'; } - return $context->getSourceDir() . '/lang'; + // Create misc file with all available locales + $writer = new TextBundleWriter(); + $writer->write($sourceDir, 'misc', array( + 'Locales' => $context->getLocaleScanner()->scanLocales($sourceDir), + ), false); + + return $sourceDir; } /** diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php index 69797167b25ba..1694410c203e2 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php @@ -59,7 +59,15 @@ public function beforeCompile(CompilationContextInterface $context) $context->getFilesystem()->remove($tempDir); $context->getFilesystem()->mkdir($tempDir); - $this->generateTextFiles($tempDir, $this->scanLocales($context)); + $locales = $context->getLocaleScanner()->scanLocales($context->getSourceDir().'/locales'); + + $this->generateTextFiles($tempDir, $locales); + + // Create misc file with all available locales + $writer = new TextBundleWriter(); + $writer->write($tempDir, 'misc', array( + 'Locales' => $locales, + ), false); return $tempDir; } @@ -89,55 +97,6 @@ public function afterCreateStub(StubbingContextInterface $context) { } - private function scanLocales(CompilationContextInterface $context) - { - $tempDir = sys_get_temp_dir() . '/icu-data-locales-source'; - - $context->getFilesystem()->remove($tempDir); - $context->getFilesystem()->mkdir($tempDir); - - // Temporarily generate the resource bundles - $context->getCompiler()->compile($context->getSourceDir() . '/locales', $tempDir); - - // Discover the list of supported locales, which are the names of the resource - // bundles in the "locales" directory - $locales = glob($tempDir . '/*.res'); - - // Remove file extension and sort - array_walk($locales, function (&$locale) { $locale = basename($locale, '.res'); }); - sort($locales); - - // Delete unneeded locales - foreach ($locales as $key => $locale) { - // Delete all aliases from the list - // i.e., "az_AZ" is an alias for "az_Latn_AZ" - $content = file_get_contents($context->getSourceDir() . '/locales/' . $locale . '.txt'); - - // The key "%%ALIAS" is not accessible through the \ResourceBundle class, - // so look in the original .txt file instead - if (strpos($content, '%%ALIAS') !== false) { - unset($locales[$key]); - } - - // Delete locales that have no content (i.e. only "Version" key) - $bundle = new \ResourceBundle($locale, $tempDir); - - if (null === $bundle) { - throw new RuntimeException('The resource bundle for locale ' . $locale . ' could not be loaded from directory ' . $tempDir); - } - - // There seems to be no other way for identifying all keys in this specific - // resource bundle - if (array_keys(iterator_to_array($bundle)) === array('Version')) { - unset($locales[$key]); - } - } - - $context->getFilesystem()->remove($tempDir); - - return $locales; - } - private function generateTextFiles($targetDirectory, array $locales) { $displayLocales = array_unique(array_merge( diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/RegionBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/RegionBundleTransformationRule.php index 52fdbed8c3384..1d66f5ea48e11 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/RegionBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/RegionBundleTransformationRule.php @@ -14,6 +14,7 @@ use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContextInterface; use Symfony\Component\Intl\ResourceBundle\Transformer\StubbingContextInterface; +use Symfony\Component\Intl\ResourceBundle\Writer\TextBundleWriter; use Symfony\Component\Intl\Util\IcuVersion; /** @@ -38,10 +39,18 @@ public function beforeCompile(CompilationContextInterface $context) { // The region data is contained in the locales bundle in ICU <= 4.2 if (IcuVersion::compare($context->getIcuVersion(), '4.2', '<=', 1)) { - return $context->getSourceDir() . '/locales'; + $sourceDir = $context->getSourceDir() . '/locales'; + } else { + $sourceDir = $context->getSourceDir() . '/region'; } - return $context->getSourceDir() . '/region'; + // Create misc file with all available locales + $writer = new TextBundleWriter(); + $writer->write($sourceDir, 'misc', array( + 'Locales' => $context->getLocaleScanner()->scanLocales($sourceDir), + ), false); + + return $sourceDir; } /** diff --git a/src/Symfony/Component/Intl/Resources/bin/update-icu-component.php b/src/Symfony/Component/Intl/Resources/bin/update-icu-component.php index 2b94fe417fe43..89f4072d5e942 100644 --- a/src/Symfony/Component/Intl/Resources/bin/update-icu-component.php +++ b/src/Symfony/Component/Intl/Resources/bin/update-icu-component.php @@ -12,6 +12,7 @@ use Symfony\Component\Icu\IcuData; use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\ResourceBundle\Compiler\BundleCompiler; +use Symfony\Component\Intl\ResourceBundle\Scanner\LocaleScanner; use Symfony\Component\Intl\ResourceBundle\Transformer\BundleTransformer; use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContext; use Symfony\Component\Intl\ResourceBundle\Transformer\Rule\CurrencyBundleTransformationRule; @@ -24,8 +25,9 @@ require_once __DIR__ . '/common.php'; require_once __DIR__ . '/autoload.php'; -if ($GLOBALS['argc'] > 3 || 2 === $GLOBALS['argc'] && '-h' === $GLOBALS['argv'][1]) { - bailout(<< 3 || 2 === $GLOBALS['argc'] && '-h' === $GLOBALS['argv'][1]) { + bailout(<< Updates the ICU data for Symfony2 to the latest version of the ICU version @@ -45,148 +47,149 @@ composer install --dev MESSAGE - ); -} + ); + } -echo LINE; -echo centered("ICU Resource Bundle Compilation") . "\n"; -echo LINE; + echo LINE; + echo centered("ICU Resource Bundle Compilation") . "\n"; + echo LINE; -if (!Intl::isExtensionLoaded()) { - bailout('The intl extension for PHP is not installed.'); -} + if (!Intl::isExtensionLoaded()) { + bailout('The intl extension for PHP is not installed.'); + } -if (!class_exists('\Symfony\Component\Icu\IcuData')) { - bailout('You must run "composer update --dev" before running this script.'); -} + if (!class_exists('\Symfony\Component\Icu\IcuData')) { + bailout('You must run "composer update --dev" before running this script.'); + } -$filesystem = new Filesystem(); + $filesystem = new Filesystem(); -$icuVersionInPhp = Intl::getIcuVersion(); + $icuVersionInPhp = Intl::getIcuVersion(); -echo "Found intl extension with ICU version $icuVersionInPhp.\n"; + echo "Found intl extension with ICU version $icuVersionInPhp.\n"; -$shortIcuVersion = strip_minor_versions($icuVersionInPhp); -$urls = parse_ini_file(__DIR__ . '/icu.ini'); + $shortIcuVersion = strip_minor_versions($icuVersionInPhp); + $urls = parse_ini_file(__DIR__ . '/icu.ini'); -if (!isset($urls[$shortIcuVersion])) { - bailout('The version ' . $shortIcuVersion . ' is not available in the icu.ini file.'); -} + if (!isset($urls[$shortIcuVersion])) { + bailout('The version ' . $shortIcuVersion . ' is not available in the icu.ini file.'); + } -echo "icu.ini parsed. Available versions:\n"; + echo "icu.ini parsed. Available versions:\n"; -foreach ($urls as $urlVersion => $url) { - echo " $urlVersion\n"; -} + foreach ($urls as $urlVersion => $url) { + echo " $urlVersion\n"; + } -if ($GLOBALS['argc'] >= 2) { - $sourceDir = $GLOBALS['argv'][1]; - $svn = new SvnRepository($sourceDir); + if ($GLOBALS['argc'] >= 2) { + $sourceDir = $GLOBALS['argv'][1]; + $svn = new SvnRepository($sourceDir); - echo "Using existing SVN repository at {$sourceDir}.\n"; -} else { - echo "Starting SVN checkout for version $shortIcuVersion. This may take a while...\n"; + echo "Using existing SVN repository at {$sourceDir}.\n"; + } else { + echo "Starting SVN checkout for version $shortIcuVersion. This may take a while...\n"; - $sourceDir = sys_get_temp_dir() . '/icu-data/' . $shortIcuVersion . '/source'; - $svn = SvnRepository::download($urls[$shortIcuVersion], $sourceDir); + $sourceDir = sys_get_temp_dir() . '/icu-data/' . $shortIcuVersion . '/source'; + $svn = SvnRepository::download($urls[$shortIcuVersion], $sourceDir); - echo "SVN checkout to {$sourceDir} complete.\n"; -} + echo "SVN checkout to {$sourceDir} complete.\n"; + } -if ($GLOBALS['argc'] >= 3) { - $buildDir = $GLOBALS['argv'][2]; -} else { - // Always build genrb so that we can determine the ICU version of the - // download by running genrb --version - echo "Building genrb.\n"; + if ($GLOBALS['argc'] >= 3) { + $buildDir = $GLOBALS['argv'][2]; + } else { + // Always build genrb so that we can determine the ICU version of the + // download by running genrb --version + echo "Building genrb.\n"; - cd($sourceDir); + cd($sourceDir); - echo "Running configure...\n"; + echo "Running configure...\n"; - $buildDir = sys_get_temp_dir() . '/icu-data/' . $shortIcuVersion . '/build'; + $buildDir = sys_get_temp_dir() . '/icu-data/' . $shortIcuVersion . '/build'; - $filesystem->remove($buildDir); - $filesystem->mkdir($buildDir); + $filesystem->remove($buildDir); + $filesystem->mkdir($buildDir); - run('./configure --prefix=' . $buildDir . ' 2>&1'); + run('./configure --prefix=' . $buildDir . ' 2>&1'); - echo "Running make...\n"; + echo "Running make...\n"; - // If the directory "lib" does not exist in the download, create it or we - // will run into problems when building libicuuc.so. - $filesystem->mkdir($sourceDir . '/lib'); + // If the directory "lib" does not exist in the download, create it or we + // will run into problems when building libicuuc.so. + $filesystem->mkdir($sourceDir . '/lib'); - // If the directory "bin" does not exist in the download, create it or we - // will run into problems when building genrb. - $filesystem->mkdir($sourceDir . '/bin'); + // If the directory "bin" does not exist in the download, create it or we + // will run into problems when building genrb. + $filesystem->mkdir($sourceDir . '/bin'); - echo "[1/5] libicudata.so..."; + echo "[1/5] libicudata.so..."; - cd($sourceDir . '/stubdata'); - run('make 2>&1 && make install 2>&1'); + cd($sourceDir . '/stubdata'); + run('make 2>&1 && make install 2>&1'); - echo " ok.\n"; + echo " ok.\n"; - echo "[2/5] libicuuc.so..."; + echo "[2/5] libicuuc.so..."; - cd($sourceDir . '/common'); - run('make 2>&1 && make install 2>&1'); + cd($sourceDir . '/common'); + run('make 2>&1 && make install 2>&1'); - echo " ok.\n"; + echo " ok.\n"; - echo "[3/5] libicui18n.so..."; + echo "[3/5] libicui18n.so..."; - cd($sourceDir . '/i18n'); - run('make 2>&1 && make install 2>&1'); + cd($sourceDir . '/i18n'); + run('make 2>&1 && make install 2>&1'); - echo " ok.\n"; + echo " ok.\n"; - echo "[4/5] libicutu.so..."; + echo "[4/5] libicutu.so..."; - cd($sourceDir . '/tools/toolutil'); - run('make 2>&1 && make install 2>&1'); + cd($sourceDir . '/tools/toolutil'); + run('make 2>&1 && make install 2>&1'); - echo " ok.\n"; + echo " ok.\n"; - echo "[5/5] genrb..."; + echo "[5/5] genrb..."; - cd($sourceDir . '/tools/genrb'); - run('make 2>&1 && make install 2>&1'); + cd($sourceDir . '/tools/genrb'); + run('make 2>&1 && make install 2>&1'); - echo " ok.\n"; -} + echo " ok.\n"; + } -$genrb = $buildDir . '/bin/genrb'; -$genrbEnv = 'LD_LIBRARY_PATH=' . $buildDir . '/lib '; + $genrb = $buildDir . '/bin/genrb'; + $genrbEnv = 'LD_LIBRARY_PATH=' . $buildDir . '/lib '; -echo "Using $genrb.\n"; + echo "Using $genrb.\n"; -$icuVersionInDownload = get_icu_version_from_genrb($genrbEnv . ' ' . $genrb); + $icuVersionInDownload = get_icu_version_from_genrb($genrbEnv . ' ' . $genrb); -echo "Preparing resource bundle compilation (version $icuVersionInDownload)...\n"; + echo "Preparing resource bundle compilation (version $icuVersionInDownload)...\n"; -$context = new CompilationContext( - $sourceDir . '/data', - IcuData::getResourceDirectory(), - $filesystem, - new BundleCompiler($genrb, $genrbEnv), - $icuVersionInDownload -); + $context = new CompilationContext( + $sourceDir . '/data', + IcuData::getResourceDirectory(), + $filesystem, + new BundleCompiler($genrb, $genrbEnv), + $icuVersionInDownload, + new LocaleScanner() + ); -$transformer = new BundleTransformer(); -$transformer->addRule(new LanguageBundleTransformationRule()); -$transformer->addRule(new RegionBundleTransformationRule()); -$transformer->addRule(new CurrencyBundleTransformationRule()); -$transformer->addRule(new LocaleBundleTransformationRule()); + $transformer = new BundleTransformer(); + $transformer->addRule(new LanguageBundleTransformationRule()); + $transformer->addRule(new RegionBundleTransformationRule()); + $transformer->addRule(new CurrencyBundleTransformationRule()); + $transformer->addRule(new LocaleBundleTransformationRule()); -echo "Starting resource bundle compilation. This may take a while...\n"; + echo "Starting resource bundle compilation. This may take a while...\n"; -$transformer->compileBundles($context); + $transformer->compileBundles($context); -echo "Resource bundle compilation complete.\n"; + echo "Resource bundle compilation complete.\n"; -$svnInfo = <<getBinaryDir() . '/svn-info.txt'; + $svnInfoFile = $context->getBinaryDir() . '/svn-info.txt'; + + file_put_contents($svnInfoFile, $svnInfo); -file_put_contents($svnInfoFile, $svnInfo); + echo "Wrote $svnInfoFile.\n"; -echo "Wrote $svnInfoFile.\n"; + $versionFile = $context->getBinaryDir() . '/version.txt'; -$versionFile = $context->getBinaryDir() . '/version.txt'; + file_put_contents($versionFile, "$icuVersionInDownload\n"); -file_put_contents($versionFile, "$icuVersionInDownload\n"); + echo "Wrote $versionFile.\n"; -echo "Wrote $versionFile.\n"; + echo "Done.\n"; +} catch (\Exception $e) { + echo "\n"; -echo "Done.\n"; + $cause = $e; + $introduction = 'Uncaught exception'; + + while (null !== $cause) { + echo $introduction." '".get_class($cause)."' with message '".$cause->getMessage()."'\n"; + echo "\n"; + echo $cause->getTraceAsString(); + echo "\n\n"; + + $cause = $cause->getPrevious(); + $introduction = 'Caused by'; + } +} From 2ad4d32a5ce1b0b9679b38bf8e671eda4c5cbc03 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Wed, 2 Oct 2013 19:17:39 +0200 Subject: [PATCH 06/48] [Intl] Fixed LocaleScanner to actually remove blacklisted locales --- .../Component/Intl/ResourceBundle/Scanner/LocaleScanner.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Intl/ResourceBundle/Scanner/LocaleScanner.php b/src/Symfony/Component/Intl/ResourceBundle/Scanner/LocaleScanner.php index b8c49e64a247a..1608b8b9f7ea3 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Scanner/LocaleScanner.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Scanner/LocaleScanner.php @@ -52,11 +52,12 @@ public function scanLocales($sourceDir) { $locales = glob($sourceDir.'/*.txt'); + // Remove file extension and sort + array_walk($locales, function (&$locale) { $locale = basename($locale, '.txt'); }); + // Remove non-locales $locales = array_diff($locales, static::$blackList); - // Remove file extension and sort - array_walk($locales, function (&$locale) { $locale = basename($locale, '.txt'); }); sort($locales); return $locales; From d6805b48c99046c8ea5521e06b600da92e0b02f7 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Wed, 2 Oct 2013 19:28:53 +0200 Subject: [PATCH 07/48] [Intl] Improved LocaleBundleTransformationRule to not generate duplicate locale names when fallback (en_GB->en) is possible --- src/Symfony/Component/Intl/CHANGELOG.md | 4 + .../Rule/LocaleBundleTransformationRule.php | 92 ++++++++++++++----- 2 files changed, 74 insertions(+), 22 deletions(-) diff --git a/src/Symfony/Component/Intl/CHANGELOG.md b/src/Symfony/Component/Intl/CHANGELOG.md index 19223ff6af3ea..3c2b66b5587ed 100644 --- a/src/Symfony/Component/Intl/CHANGELOG.md +++ b/src/Symfony/Component/Intl/CHANGELOG.md @@ -10,3 +10,7 @@ CHANGELOG whenever a non-existing language, currency, etc. is accessed * the available locales of each resource bundle are now stored in a generic "misc.res" file in order to improve reading performance + * improved `LocaleBundleTransformationRule` to not generate duplicate locale + names when fallback (e.g. "en_GB"->"en") is possible anyway. This reduced + the Resources/ directory file size of the Icu 1.2.x branch from 14M to 12M at + the time of this writing diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php index 1694410c203e2..8b457332961f4 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php @@ -60,13 +60,22 @@ public function beforeCompile(CompilationContextInterface $context) $context->getFilesystem()->mkdir($tempDir); $locales = $context->getLocaleScanner()->scanLocales($context->getSourceDir().'/locales'); + $aliases = $context->getLocaleScanner()->scanAliases($context->getSourceDir().'/locales'); - $this->generateTextFiles($tempDir, $locales); + $writer = new TextBundleWriter(); + + $this->generateTextFiles($writer, $tempDir, $locales, $aliases); + + // Generate aliases, needed to enable proper fallback from alias to its + // target + foreach ($aliases as $alias => $aliasOf) { + $writer->write($tempDir, $alias, array('%%ALIAS' => $aliasOf)); + } // Create misc file with all available locales - $writer = new TextBundleWriter(); $writer->write($tempDir, 'misc', array( 'Locales' => $locales, + 'Aliases' => $aliases, ), false); return $tempDir; @@ -97,47 +106,86 @@ public function afterCreateStub(StubbingContextInterface $context) { } - private function generateTextFiles($targetDirectory, array $locales) + private function generateTextFiles(TextBundleWriter $writer, $targetDirectory, array $locales, array $aliases) { + // Collect locales for which translations exist $displayLocales = array_unique(array_merge( $this->languageBundle->getLocales(), $this->regionBundle->getLocales() )); - $txtWriter = new TextBundleWriter(); + // Flip to facilitate lookup + $displayLocales = array_flip($displayLocales); + $locales = array_flip($locales); - // Generate a list of locale names 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. - foreach ($displayLocales as $displayLocale) { - // Don't include ICU's root resource bundle - if ('root' === $displayLocale) { - continue; - } + // Don't generate names for aliases (names will be generated for the + // locale they are duplicating) + $displayLocales = array_diff_key($displayLocales, $aliases); + + // Generate a list of (existing) locale fallbacks + $fallbacks = array(); + + foreach ($displayLocales as $displayLocale => $_) { + $fallbacks[$displayLocale] = null; + $fallback = $displayLocale; - $names = array(); + // Recursively search for a fallback locale until one is found + while (null !== ($fallback = Intl::getFallbackLocale($fallback))) { + // Currently, no locale has an alias as fallback locale. + // If this starts to be the case, we need to add code here. + assert(!isset($aliases[$fallback])); - foreach ($locales as $locale) { - // Don't include ICU's root resource bundle - if ($locale === 'root') { - continue; + // Check whether the fallback exists + if (isset($displayLocales[$fallback])) { + $fallbacks[$displayLocale] = $fallback; + break; } + } + } + + // Since fallbacks are always shorter than their source, we can sort + // the display locales so that fallbacks are always processed before + // their variants + ksort($displayLocales); + + $localeNames = array(); + // Generate locale names for all locales that have translations in + // at least the language or the region bundle + foreach ($displayLocales as $displayLocale => $_) { + $localeNames[$displayLocale] = array(); + + foreach ($locales as $locale => $__) { 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. if (null !== ($name = $this->generateLocaleName($locale, $displayLocale))) { - $names[$locale] = $name; + $localeNames[$displayLocale][$locale] = $name; } } catch (NoSuchEntryException $e) { } } - // If no names could be generated for the current locale, skip it - if (0 === count($names)) { + // Compare names with the names of the fallback locales and only + // keep the differences + $fallback = $displayLocale; + + while (isset($fallbacks[$fallback])) { + $fallback = $fallbacks[$fallback]; + $localeNames[$displayLocale] = array_diff( + $localeNames[$displayLocale], + $localeNames[$fallback] + ); + } + + // If no names remain to be saved for the current locale, skip it + if (0 === count($localeNames[$displayLocale])) { continue; } - $txtWriter->write($targetDirectory, $displayLocale, array('Locales' => $names)); + $writer->write($targetDirectory, $displayLocale, array('Locales' => $localeNames[$displayLocale])); } } From 958a4dcc6a88ee48f818a488c0ef0a3c0a369cc2 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Wed, 2 Oct 2013 19:56:30 +0200 Subject: [PATCH 08/48] [Intl] Added LocaleScannerTest --- .../ResourceBundle/Scanner/LocaleScanner.php | 2 +- .../Scanner/LocaleScannerTest.php | 76 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/Intl/Tests/ResourceBundle/Scanner/LocaleScannerTest.php diff --git a/src/Symfony/Component/Intl/ResourceBundle/Scanner/LocaleScanner.php b/src/Symfony/Component/Intl/ResourceBundle/Scanner/LocaleScanner.php index 1608b8b9f7ea3..2751365c5a518 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Scanner/LocaleScanner.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Scanner/LocaleScanner.php @@ -81,7 +81,7 @@ public function scanAliases($sourceDir) $content = file_get_contents($sourceDir.'/'.$locale.'.txt'); // Aliases contain the text "%%ALIAS" followed by the aliased locale - if (preg_match('/"%%ALIAS"{"([^"]+)"}/', $content, $matches)) { + if (preg_match('/"%%ALIAS"\{"([^"]+)"\}/', $content, $matches)) { $aliases[$locale] = $matches[1]; } } diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Scanner/LocaleScannerTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Scanner/LocaleScannerTest.php new file mode 100644 index 0000000000000..aef40d484e340 --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Scanner/LocaleScannerTest.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Tests\ResourceBundle\Reader; + +use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Intl\ResourceBundle\Scanner\LocaleScanner; + +/** + * @author Bernhard Schussek + */ +class LocaleScannerTest extends \PHPUnit_Framework_TestCase +{ + private $directory; + + /** + * @var Filesystem + */ + private $filesystem; + + /** + * @var LocaleScanner + */ + private $scanner; + + protected function setUp() + { + $this->directory = sys_get_temp_dir().'/LocaleScannerTest/'.rand(1000, 9999); + $this->filesystem = new Filesystem(); + $this->scanner = new LocaleScanner(); + + $this->filesystem->mkdir($this->directory); + + $this->filesystem->touch($this->directory.'/en.txt'); + $this->filesystem->touch($this->directory.'/en_alias.txt'); + $this->filesystem->touch($this->directory.'/de.txt'); + $this->filesystem->touch($this->directory.'/de_alias.txt'); + $this->filesystem->touch($this->directory.'/fr.txt'); + $this->filesystem->touch($this->directory.'/fr_alias.txt'); + $this->filesystem->touch($this->directory.'/root.txt'); + $this->filesystem->touch($this->directory.'/supplementalData.txt'); + $this->filesystem->touch($this->directory.'/supplementaldata.txt'); + $this->filesystem->touch($this->directory.'/misc.txt'); + + file_put_contents($this->directory.'/en_alias.txt', 'en_alias{"%%ALIAS"{"en"}}'); + file_put_contents($this->directory.'/de_alias.txt', 'de_alias{"%%ALIAS"{"de"}}'); + file_put_contents($this->directory.'/fr_alias.txt', 'fr_alias{"%%ALIAS"{"fr"}}'); + } + + protected function tearDown() + { + $this->filesystem->remove($this->directory); + } + + public function testScanLocales() + { + $sortedLocales = array('de', 'de_alias', 'en', 'en_alias', 'fr', 'fr_alias'); + + $this->assertSame($sortedLocales, $this->scanner->scanLocales($this->directory)); + } + + public function testScanAliases() + { + $sortedAliases = array('de_alias' => 'de', 'en_alias' => 'en', 'fr_alias' => 'fr'); + + $this->assertSame($sortedAliases, $this->scanner->scanAliases($this->directory)); + } +} From b3bd0aa243d435f8ebcb394f4f7df82dbd247a48 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Wed, 2 Oct 2013 19:58:43 +0200 Subject: [PATCH 09/48] [Intl] Removed unneeded method BundleReaderInterface::getLocales() --- .../Intl/ResourceBundle/AbstractBundle.php | 8 --- .../Intl/ResourceBundle/CurrencyBundle.php | 14 ++++ .../Intl/ResourceBundle/LanguageBundle.php | 14 ++++ .../Intl/ResourceBundle/LocaleBundle.php | 16 ++++- .../Reader/AbstractBundleReader.php | 42 ------------ .../Reader/BinaryBundleReader.php | 10 +-- .../Reader/BufferedBundleReader.php | 8 --- .../Reader/BundleReaderInterface.php | 9 --- .../ResourceBundle/Reader/PhpBundleReader.php | 10 +-- .../Reader/StructuredBundleReader.php | 8 --- .../Intl/ResourceBundle/RegionBundle.php | 14 ++++ .../ResourceBundle/AbstractBundleTest.php | 55 ---------------- .../Reader/AbstractBundleReaderTest.php | 64 ------------------- .../Reader/StructuredBundleReaderTest.php | 12 ---- 14 files changed, 59 insertions(+), 225 deletions(-) delete mode 100644 src/Symfony/Component/Intl/ResourceBundle/Reader/AbstractBundleReader.php delete mode 100644 src/Symfony/Component/Intl/Tests/ResourceBundle/AbstractBundleTest.php delete mode 100644 src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/AbstractBundleReaderTest.php diff --git a/src/Symfony/Component/Intl/ResourceBundle/AbstractBundle.php b/src/Symfony/Component/Intl/ResourceBundle/AbstractBundle.php index d1d523c40acd1..66842a69a1fad 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/AbstractBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/AbstractBundle.php @@ -45,14 +45,6 @@ public function __construct($path, StructuredBundleReaderInterface $reader) $this->reader = $reader; } - /** - * {@inheritdoc} - */ - public function getLocales() - { - return $this->reader->getLocales($this->path); - } - /** * Proxy method for {@link StructuredBundleReaderInterface#read}. */ diff --git a/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundle.php b/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundle.php index 6f2a0ed39507b..0534cee87f484 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundle.php @@ -26,6 +26,20 @@ class CurrencyBundle extends AbstractBundle implements CurrencyBundleInterface const INDEX_ROUNDING_INCREMENT = 3; + /** + * {@inheritdoc} + */ + public function getLocales() + { + $locales = $this->readEntry('misc', array('Locales')); + + if ($locales instanceof \Traversable) { + $locales = iterator_to_array($locales); + } + + return $locales; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php b/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php index 3c0262ce2f3a2..c60a0919f6de2 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php @@ -20,6 +20,20 @@ */ class LanguageBundle extends AbstractBundle implements LanguageBundleInterface { + /** + * {@inheritdoc} + */ + public function getLocales() + { + $locales = $this->readEntry('misc', array('Locales')); + + if ($locales instanceof \Traversable) { + $locales = iterator_to_array($locales); + } + + return $locales; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php b/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php index 6f6cdfcb189c2..ea50fb921a72b 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php @@ -18,6 +18,20 @@ */ class LocaleBundle extends AbstractBundle implements LocaleBundleInterface { + /** + * {@inheritdoc} + */ + public function getLocales() + { + $locales = $this->readEntry('misc', array('Locales')); + + if ($locales instanceof \Traversable) { + $locales = iterator_to_array($locales); + } + + return $locales; + } + /** * {@inheritdoc} */ @@ -39,7 +53,7 @@ public function getLocaleNames($locale = null) $locale = \Locale::getDefault(); } - if (null === ($locales = $this->readEntry($locale, array('Locales')))) { + if (null === ($locales = $this->readEntry($locale, array('Locales'), true))) { return array(); } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/AbstractBundleReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/AbstractBundleReader.php deleted file mode 100644 index c30693ac57a20..0000000000000 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/AbstractBundleReader.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Intl\ResourceBundle\Reader; - -/** - * Base class for {@link BundleReaderInterface} implementations. - * - * @author Bernhard Schussek - */ -abstract class AbstractBundleReader implements BundleReaderInterface -{ - /** - * {@inheritdoc} - */ - public function getLocales($path) - { - $extension = '.' . $this->getFileExtension(); - $locales = glob($path . '/*' . $extension); - - // Remove file extension and sort - array_walk($locales, function (&$locale) use ($extension) { $locale = basename($locale, $extension); }); - sort($locales); - - return $locales; - } - - /** - * Returns the extension of locale files in this bundle. - * - * @return string The file extension (without leading dot). - */ - abstract protected function getFileExtension(); -} diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/BinaryBundleReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/BinaryBundleReader.php index a23adf35d8b64..c88489a4ce247 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/BinaryBundleReader.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/BinaryBundleReader.php @@ -20,7 +20,7 @@ * * @author Bernhard Schussek */ -class BinaryBundleReader extends AbstractBundleReader implements BundleReaderInterface +class BinaryBundleReader implements BundleReaderInterface { /** * {@inheritdoc} @@ -59,12 +59,4 @@ public function read($path, $locale) return new ArrayAccessibleResourceBundle($bundle); } - - /** - * {@inheritdoc} - */ - protected function getFileExtension() - { - return 'res'; - } } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/BufferedBundleReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/BufferedBundleReader.php index e44074b168b4d..8a3d12bda33a8 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/BufferedBundleReader.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/BufferedBundleReader.php @@ -51,12 +51,4 @@ public function read($path, $locale) return $this->buffer[$hash]; } - - /** - * {@inheritdoc} - */ - public function getLocales($path) - { - return $this->reader->getLocales($path); - } } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleReaderInterface.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleReaderInterface.php index bc485cd5267b4..80f84c9189453 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleReaderInterface.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleReaderInterface.php @@ -28,13 +28,4 @@ interface BundleReaderInterface * complex data, a scalar value otherwise. */ public function read($path, $locale); - - /** - * Reads the available locales of a resource bundle. - * - * @param string $path The path to the resource bundle. - * - * @return string[] A list of supported locale codes. - */ - public function getLocales($path); } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/PhpBundleReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/PhpBundleReader.php index eaf26c7b64651..af0db80e60c0b 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/PhpBundleReader.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/PhpBundleReader.php @@ -19,7 +19,7 @@ * * @author Bernhard Schussek */ -class PhpBundleReader extends AbstractBundleReader implements BundleReaderInterface +class PhpBundleReader implements BundleReaderInterface { /** * {@inheritdoc} @@ -50,12 +50,4 @@ public function read($path, $locale) return include $fileName; } - - /** - * {@inheritdoc} - */ - protected function getFileExtension() - { - return 'php'; - } } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReader.php index ea687e166189b..369741ddc2d4a 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReader.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReader.php @@ -48,14 +48,6 @@ public function read($path, $locale) return $this->reader->read($path, $locale); } - /** - * {@inheritdoc} - */ - public function getLocales($path) - { - return $this->reader->getLocales($path); - } - /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Intl/ResourceBundle/RegionBundle.php b/src/Symfony/Component/Intl/ResourceBundle/RegionBundle.php index bbfbedeed9ddf..1f1b35a48d18b 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/RegionBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/RegionBundle.php @@ -18,6 +18,20 @@ */ class RegionBundle extends AbstractBundle implements RegionBundleInterface { + /** + * {@inheritdoc} + */ + public function getLocales() + { + $locales = $this->readEntry('misc', array('Locales')); + + if ($locales instanceof \Traversable) { + $locales = iterator_to_array($locales); + } + + return $locales; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/AbstractBundleTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/AbstractBundleTest.php deleted file mode 100644 index 6b07586572935..0000000000000 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/AbstractBundleTest.php +++ /dev/null @@ -1,55 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Intl\Tests\ResourceBundle; - -/** - * @author Bernhard Schussek - */ -class AbstractBundleTest extends \PHPUnit_Framework_TestCase -{ - const RES_DIR = '/base/dirName'; - - /** - * @var \Symfony\Component\Intl\ResourceBundle\AbstractBundle - */ - private $bundle; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $reader; - - protected function setUp() - { - $this->reader = $this->getMock('Symfony\Component\Intl\ResourceBundle\Reader\StructuredBundleReaderInterface'); - $this->bundle = $this->getMockForAbstractClass( - 'Symfony\Component\Intl\ResourceBundle\AbstractBundle', - array(self::RES_DIR, $this->reader) - ); - - $this->bundle->expects($this->any()) - ->method('getDirectoryName') - ->will($this->returnValue('dirName')); - } - - public function testGetLocales() - { - $locales = array('de', 'en', 'fr'); - - $this->reader->expects($this->once()) - ->method('getLocales') - ->with(self::RES_DIR) - ->will($this->returnValue($locales)); - - $this->assertSame($locales, $this->bundle->getLocales()); - } -} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/AbstractBundleReaderTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/AbstractBundleReaderTest.php deleted file mode 100644 index 2da7f90de49e3..0000000000000 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/AbstractBundleReaderTest.php +++ /dev/null @@ -1,64 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Intl\Tests\ResourceBundle\Reader; - -use Symfony\Component\Filesystem\Filesystem; - -/** - * @author Bernhard Schussek - */ -class AbstractBundleReaderTest extends \PHPUnit_Framework_TestCase -{ - private $directory; - - /** - * @var Filesystem - */ - private $filesystem; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $reader; - - protected function setUp() - { - $this->directory = sys_get_temp_dir() . '/AbstractBundleReaderTest/' . rand(1000, 9999); - $this->filesystem = new Filesystem(); - $this->reader = $this->getMockForAbstractClass('Symfony\Component\Intl\ResourceBundle\Reader\AbstractBundleReader'); - - $this->filesystem->mkdir($this->directory); - } - - protected function tearDown() - { - $this->filesystem->remove($this->directory); - } - - public function testGetLocales() - { - $this->filesystem->touch($this->directory . '/en.foo'); - $this->filesystem->touch($this->directory . '/de.foo'); - $this->filesystem->touch($this->directory . '/fr.foo'); - $this->filesystem->touch($this->directory . '/bo.txt'); - $this->filesystem->touch($this->directory . '/gu.bin'); - $this->filesystem->touch($this->directory . '/s.lol'); - - $this->reader->expects($this->any()) - ->method('getFileExtension') - ->will($this->returnValue('foo')); - - $sortedLocales = array('de', 'en', 'fr'); - - $this->assertSame($sortedLocales, $this->reader->getLocales($this->directory)); - } -} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/StructuredBundleReaderTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/StructuredBundleReaderTest.php index 28c376309108f..cc985c4df5b9b 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/StructuredBundleReaderTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/StructuredBundleReaderTest.php @@ -36,18 +36,6 @@ protected function setUp() $this->reader = new StructuredBundleReader($this->readerImpl); } - public function testGetLocales() - { - $locales = array('en', 'de', 'fr'); - - $this->readerImpl->expects($this->once()) - ->method('getLocales') - ->with(self::RES_DIR) - ->will($this->returnValue($locales)); - - $this->assertSame($locales, $this->reader->getLocales(self::RES_DIR)); - } - public function testForwardCallToRead() { $data = array('foo', 'bar'); From 15419bb5dae22310ba2fc512adab594ae9373fd8 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 3 Oct 2013 10:26:19 +0200 Subject: [PATCH 10/48] [Intl] Fixed CS --- .../Transformer/Rule/LocaleBundleTransformationRule.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php index 8b457332961f4..7a9068872e543 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php @@ -54,7 +54,7 @@ public function getBundleName() */ public function beforeCompile(CompilationContextInterface $context) { - $tempDir = sys_get_temp_dir() . '/icu-data-locales'; + $tempDir = sys_get_temp_dir().'/icu-data-locales'; $context->getFilesystem()->remove($tempDir); $context->getFilesystem()->mkdir($tempDir); @@ -86,7 +86,7 @@ public function beforeCompile(CompilationContextInterface $context) */ public function afterCompile(CompilationContextInterface $context) { - $context->getFilesystem()->remove(sys_get_temp_dir() . '/icu-data-locales'); + $context->getFilesystem()->remove(sys_get_temp_dir().'/icu-data-locales'); } /** From fbe285380923fe715d5c2c51d95a0a503fe97ac8 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 3 Oct 2013 10:27:02 +0200 Subject: [PATCH 11/48] [Intl] Fixed currency transformation rule to clean up after the transformation --- .../Transformer/Rule/CurrencyBundleTransformationRule.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php index 11e2b1b1d06e8..4e77f9319b98f 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php @@ -39,7 +39,7 @@ public function getBundleName() */ public function beforeCompile(CompilationContextInterface $context) { - $tempDir = sys_get_temp_dir() . '/icu-data-currencies-source'; + $tempDir = sys_get_temp_dir().'/icu-data-currencies-source'; // The currency data is contained in the locales and misc bundles // in ICU <= 4.2 @@ -86,6 +86,9 @@ public function afterCompile(CompilationContextInterface $context) { // Remove supplementalData.res, whose content is contained within misc.res $context->getFilesystem()->remove($context->getBinaryDir().'/curr/supplementalData.res'); + + // Remove the temporary directory + $context->getFilesystem()->remove(sys_get_temp_dir().'/icu-data-currencies-source'); } /** From 95de4382b75a0f21665ff33016332e397195d2b6 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 3 Oct 2013 10:28:54 +0200 Subject: [PATCH 12/48] [Intl] Fixed StructuredBundleReader to follow locale aliases when looking for fallback locales --- UPGRADE-2.4.md | 7 +++++ src/Symfony/Component/Intl/CHANGELOG.md | 2 ++ src/Symfony/Component/Intl/Intl.php | 4 +++ .../Intl/ResourceBundle/LocaleBundle.php | 14 +++++++++ .../ResourceBundle/LocaleBundleInterface.php | 8 +++++ .../Reader/StructuredBundleReader.php | 29 ++++++++++++++++++- .../Reader/StructuredBundleReaderTest.php | 23 +++++++++++++++ 7 files changed, 86 insertions(+), 1 deletion(-) diff --git a/UPGRADE-2.4.md b/UPGRADE-2.4.md index bd9ecc3c4dae4..4596b8646afbe 100644 --- a/UPGRADE-2.4.md +++ b/UPGRADE-2.4.md @@ -7,3 +7,10 @@ Form * The constructor parameter `$precision` in `IntegerToLocalizedStringTransformer` is now ignored completely, because a precision does not make sense for integers. + +Intl +---- + + * A new method `getLocaleAliases()` was added to `LocaleBundleInterface`. If + any of your classes implements this interface, you should add an implementation + of this method. diff --git a/src/Symfony/Component/Intl/CHANGELOG.md b/src/Symfony/Component/Intl/CHANGELOG.md index 3c2b66b5587ed..b228bbbbd92c6 100644 --- a/src/Symfony/Component/Intl/CHANGELOG.md +++ b/src/Symfony/Component/Intl/CHANGELOG.md @@ -14,3 +14,5 @@ CHANGELOG names when fallback (e.g. "en_GB"->"en") is possible anyway. This reduced the Resources/ directory file size of the Icu 1.2.x branch from 14M to 12M at the time of this writing + * `StructuredBundleReader` now follows aliases when looking for fallback locales + * [BC BREAK] a new method `getLocaleAliases()` was added to `LocaleBundleInterface` diff --git a/src/Symfony/Component/Intl/Intl.php b/src/Symfony/Component/Intl/Intl.php index e72bb2c564b9f..51725844ce14a 100644 --- a/src/Symfony/Component/Intl/Intl.php +++ b/src/Symfony/Component/Intl/Intl.php @@ -215,6 +215,10 @@ private static function getBundleReader() IcuData::getBundleReader(), self::BUFFER_SIZE )); + + // Make sure that self::$bundleReader is already set to prevent + // a cycle + self::$bundleReader->setLocaleAliases(self::getLocaleBundle()->getLocaleAliases()); } return self::$bundleReader; diff --git a/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php b/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php index ea50fb921a72b..fd69a20272710 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php @@ -32,6 +32,20 @@ public function getLocales() return $locales; } + /** + * {@inheritdoc} + */ + public function getLocaleAliases() + { + $aliases = $this->readEntry('misc', array('Aliases')); + + if ($aliases instanceof \Traversable) { + $aliases = iterator_to_array($aliases); + } + + return $aliases; + } + /** * {@inheritdoc} */ diff --git a/src/Symfony/Component/Intl/ResourceBundle/LocaleBundleInterface.php b/src/Symfony/Component/Intl/ResourceBundle/LocaleBundleInterface.php index daf5be68a5125..4905c6a4f7b48 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/LocaleBundleInterface.php +++ b/src/Symfony/Component/Intl/ResourceBundle/LocaleBundleInterface.php @@ -38,4 +38,12 @@ public function getLocaleName($ofLocale, $locale = null); * @return string[] A list of locale names indexed by locale codes. */ public function getLocaleNames($locale = null); + + /** + * Returns a list of locale aliases. + * + * @return array An array with aliases as keys and aliased locales as + * values. + */ + public function getLocaleAliases(); } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReader.php index 369741ddc2d4a..7684b647a4f3b 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReader.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReader.php @@ -30,6 +30,13 @@ class StructuredBundleReader implements StructuredBundleReaderInterface */ private $reader; + /** + * A mapping of locale aliases to locales + * + * @var array + */ + private $localeAliases = array(); + /** * Creates an entry reader based on the given resource bundle reader. * @@ -40,6 +47,21 @@ 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} */ @@ -108,7 +130,12 @@ public function readEntry($path, $locale, array $indices, $fallback = true) // Remember which locales we tried $testedLocales[] = $currentLocale.'.res'; - // Go to fallback locale + // First check whether the locale is an alias + if (isset($this->localeAliases[$currentLocale])) { + $currentLocale = $this->localeAliases[$currentLocale]; + } + + // Then determine fallback locale $currentLocale = Intl::getFallbackLocale($currentLocale); } diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/StructuredBundleReaderTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/StructuredBundleReaderTest.php index cc985c4df5b9b..7678e22b72678 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/StructuredBundleReaderTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/StructuredBundleReaderTest.php @@ -250,4 +250,27 @@ public function testMergeTraversables($childData, $parentData, $result) $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'), true)); } + + /** + * @dataProvider provideMergeableValues + */ + public function testFollowLocaleAliases($childData, $parentData, $result) + { + $this->reader->setLocaleAliases(array('mo' => 'ro_MD')); + + $this->readerImpl->expects($this->at(0)) + ->method('read') + ->with(self::RES_DIR, 'mo') + ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); + + if (null === $childData || is_array($childData)) { + // Read fallback locale of aliased locale ("ro_MD" -> "ro") + $this->readerImpl->expects($this->at(1)) + ->method('read') + ->with(self::RES_DIR, 'ro') + ->will($this->returnValue(array('Foo' => array('Bar' => $parentData)))); + } + + $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'mo', array('Foo', 'Bar'), true)); + } } From 5d1446584f42d029ee5230e708b50ae04e4e9bc2 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 3 Oct 2013 10:43:53 +0200 Subject: [PATCH 13/48] [Intl] Added exception handler to data generation scripts --- .../Component/Intl/Resources/bin/common.php | 17 ++ .../Resources/bin/update-icu-component.php | 227 ++++++++---------- 2 files changed, 122 insertions(+), 122 deletions(-) diff --git a/src/Symfony/Component/Intl/Resources/bin/common.php b/src/Symfony/Component/Intl/Resources/bin/common.php index 4fadbe82336b5..d6fd94dd2716f 100644 --- a/src/Symfony/Component/Intl/Resources/bin/common.php +++ b/src/Symfony/Component/Intl/Resources/bin/common.php @@ -67,3 +67,20 @@ function get_icu_version_from_genrb($genrb) return $matches[1]; } + +set_exception_handler(function (\Exception $exception) { + echo "\n"; + + $cause = $exception; + $introduction = 'Uncaught exception'; + + while (null !== $cause) { + echo $introduction." '".get_class($cause)."' with message '".$cause->getMessage()."'\n"; + echo "\n"; + echo $cause->getTraceAsString(); + echo "\n\n"; + + $cause = $cause->getPrevious(); + $introduction = 'Caused by'; + } +}); diff --git a/src/Symfony/Component/Intl/Resources/bin/update-icu-component.php b/src/Symfony/Component/Intl/Resources/bin/update-icu-component.php index 89f4072d5e942..3926d30ef0e1b 100644 --- a/src/Symfony/Component/Intl/Resources/bin/update-icu-component.php +++ b/src/Symfony/Component/Intl/Resources/bin/update-icu-component.php @@ -25,9 +25,8 @@ require_once __DIR__ . '/common.php'; require_once __DIR__ . '/autoload.php'; -try { - if ($GLOBALS['argc'] > 3 || 2 === $GLOBALS['argc'] && '-h' === $GLOBALS['argv'][1]) { - bailout(<< 3 || 2 === $GLOBALS['argc'] && '-h' === $GLOBALS['argv'][1]) { + bailout(<< Updates the ICU data for Symfony2 to the latest version of the ICU version @@ -44,152 +43,152 @@ For running this script, the intl extension must be loaded and all vendors must have been installed through composer: - composer install --dev +composer install --dev MESSAGE - ); - } + ); +} - echo LINE; - echo centered("ICU Resource Bundle Compilation") . "\n"; - echo LINE; +echo LINE; +echo centered("ICU Resource Bundle Compilation") . "\n"; +echo LINE; - if (!Intl::isExtensionLoaded()) { - bailout('The intl extension for PHP is not installed.'); - } +if (!Intl::isExtensionLoaded()) { + bailout('The intl extension for PHP is not installed.'); +} - if (!class_exists('\Symfony\Component\Icu\IcuData')) { - bailout('You must run "composer update --dev" before running this script.'); - } +if (!class_exists('\Symfony\Component\Icu\IcuData')) { + bailout('You must run "composer update --dev" before running this script.'); +} - $filesystem = new Filesystem(); +$filesystem = new Filesystem(); - $icuVersionInPhp = Intl::getIcuVersion(); +$icuVersionInPhp = Intl::getIcuVersion(); - echo "Found intl extension with ICU version $icuVersionInPhp.\n"; +echo "Found intl extension with ICU version $icuVersionInPhp.\n"; - $shortIcuVersion = strip_minor_versions($icuVersionInPhp); - $urls = parse_ini_file(__DIR__ . '/icu.ini'); +$shortIcuVersion = strip_minor_versions($icuVersionInPhp); +$urls = parse_ini_file(__DIR__ . '/icu.ini'); - if (!isset($urls[$shortIcuVersion])) { - bailout('The version ' . $shortIcuVersion . ' is not available in the icu.ini file.'); - } +if (!isset($urls[$shortIcuVersion])) { + bailout('The version ' . $shortIcuVersion . ' is not available in the icu.ini file.'); +} - echo "icu.ini parsed. Available versions:\n"; +echo "icu.ini parsed. Available versions:\n"; - foreach ($urls as $urlVersion => $url) { - echo " $urlVersion\n"; - } +foreach ($urls as $urlVersion => $url) { + echo " $urlVersion\n"; +} - if ($GLOBALS['argc'] >= 2) { - $sourceDir = $GLOBALS['argv'][1]; - $svn = new SvnRepository($sourceDir); +if ($GLOBALS['argc'] >= 2) { + $sourceDir = $GLOBALS['argv'][1]; + $svn = new SvnRepository($sourceDir); - echo "Using existing SVN repository at {$sourceDir}.\n"; - } else { - echo "Starting SVN checkout for version $shortIcuVersion. This may take a while...\n"; + echo "Using existing SVN repository at {$sourceDir}.\n"; +} else { + echo "Starting SVN checkout for version $shortIcuVersion. This may take a while...\n"; - $sourceDir = sys_get_temp_dir() . '/icu-data/' . $shortIcuVersion . '/source'; - $svn = SvnRepository::download($urls[$shortIcuVersion], $sourceDir); + $sourceDir = sys_get_temp_dir() . '/icu-data/' . $shortIcuVersion . '/source'; + $svn = SvnRepository::download($urls[$shortIcuVersion], $sourceDir); - echo "SVN checkout to {$sourceDir} complete.\n"; - } + echo "SVN checkout to {$sourceDir} complete.\n"; +} - if ($GLOBALS['argc'] >= 3) { - $buildDir = $GLOBALS['argv'][2]; - } else { - // Always build genrb so that we can determine the ICU version of the - // download by running genrb --version - echo "Building genrb.\n"; +if ($GLOBALS['argc'] >= 3) { + $buildDir = $GLOBALS['argv'][2]; +} else { + // Always build genrb so that we can determine the ICU version of the + // download by running genrb --version + echo "Building genrb.\n"; - cd($sourceDir); + cd($sourceDir); - echo "Running configure...\n"; + echo "Running configure...\n"; - $buildDir = sys_get_temp_dir() . '/icu-data/' . $shortIcuVersion . '/build'; + $buildDir = sys_get_temp_dir() . '/icu-data/' . $shortIcuVersion . '/build'; - $filesystem->remove($buildDir); - $filesystem->mkdir($buildDir); + $filesystem->remove($buildDir); + $filesystem->mkdir($buildDir); - run('./configure --prefix=' . $buildDir . ' 2>&1'); + run('./configure --prefix=' . $buildDir . ' 2>&1'); - echo "Running make...\n"; + echo "Running make...\n"; - // If the directory "lib" does not exist in the download, create it or we - // will run into problems when building libicuuc.so. - $filesystem->mkdir($sourceDir . '/lib'); + // If the directory "lib" does not exist in the download, create it or we + // will run into problems when building libicuuc.so. + $filesystem->mkdir($sourceDir . '/lib'); - // If the directory "bin" does not exist in the download, create it or we - // will run into problems when building genrb. - $filesystem->mkdir($sourceDir . '/bin'); + // If the directory "bin" does not exist in the download, create it or we + // will run into problems when building genrb. + $filesystem->mkdir($sourceDir . '/bin'); - echo "[1/5] libicudata.so..."; + echo "[1/5] libicudata.so..."; - cd($sourceDir . '/stubdata'); - run('make 2>&1 && make install 2>&1'); + cd($sourceDir . '/stubdata'); + run('make 2>&1 && make install 2>&1'); - echo " ok.\n"; + echo " ok.\n"; - echo "[2/5] libicuuc.so..."; + echo "[2/5] libicuuc.so..."; - cd($sourceDir . '/common'); - run('make 2>&1 && make install 2>&1'); + cd($sourceDir . '/common'); + run('make 2>&1 && make install 2>&1'); - echo " ok.\n"; + echo " ok.\n"; - echo "[3/5] libicui18n.so..."; + echo "[3/5] libicui18n.so..."; - cd($sourceDir . '/i18n'); - run('make 2>&1 && make install 2>&1'); + cd($sourceDir . '/i18n'); + run('make 2>&1 && make install 2>&1'); - echo " ok.\n"; + echo " ok.\n"; - echo "[4/5] libicutu.so..."; + echo "[4/5] libicutu.so..."; - cd($sourceDir . '/tools/toolutil'); - run('make 2>&1 && make install 2>&1'); + cd($sourceDir . '/tools/toolutil'); + run('make 2>&1 && make install 2>&1'); - echo " ok.\n"; + echo " ok.\n"; - echo "[5/5] genrb..."; + echo "[5/5] genrb..."; - cd($sourceDir . '/tools/genrb'); - run('make 2>&1 && make install 2>&1'); + cd($sourceDir . '/tools/genrb'); + run('make 2>&1 && make install 2>&1'); - echo " ok.\n"; - } + echo " ok.\n"; +} - $genrb = $buildDir . '/bin/genrb'; - $genrbEnv = 'LD_LIBRARY_PATH=' . $buildDir . '/lib '; +$genrb = $buildDir . '/bin/genrb'; +$genrbEnv = 'LD_LIBRARY_PATH=' . $buildDir . '/lib '; - echo "Using $genrb.\n"; +echo "Using $genrb.\n"; - $icuVersionInDownload = get_icu_version_from_genrb($genrbEnv . ' ' . $genrb); +$icuVersionInDownload = get_icu_version_from_genrb($genrbEnv . ' ' . $genrb); - echo "Preparing resource bundle compilation (version $icuVersionInDownload)...\n"; +echo "Preparing resource bundle compilation (version $icuVersionInDownload)...\n"; - $context = new CompilationContext( - $sourceDir . '/data', - IcuData::getResourceDirectory(), - $filesystem, - new BundleCompiler($genrb, $genrbEnv), - $icuVersionInDownload, - new LocaleScanner() - ); +$context = new CompilationContext( + $sourceDir . '/data', + IcuData::getResourceDirectory(), + $filesystem, + new BundleCompiler($genrb, $genrbEnv), + $icuVersionInDownload, + new LocaleScanner() +); - $transformer = new BundleTransformer(); - $transformer->addRule(new LanguageBundleTransformationRule()); - $transformer->addRule(new RegionBundleTransformationRule()); - $transformer->addRule(new CurrencyBundleTransformationRule()); - $transformer->addRule(new LocaleBundleTransformationRule()); +$transformer = new BundleTransformer(); +$transformer->addRule(new LanguageBundleTransformationRule()); +$transformer->addRule(new RegionBundleTransformationRule()); +$transformer->addRule(new CurrencyBundleTransformationRule()); +$transformer->addRule(new LocaleBundleTransformationRule()); - echo "Starting resource bundle compilation. This may take a while...\n"; +echo "Starting resource bundle compilation. This may take a while...\n"; - $transformer->compileBundles($context); +$transformer->compileBundles($context); - echo "Resource bundle compilation complete.\n"; +echo "Resource bundle compilation complete.\n"; - $svnInfo = <<getBinaryDir() . '/svn-info.txt'; - - file_put_contents($svnInfoFile, $svnInfo); +$svnInfoFile = $context->getBinaryDir() . '/svn-info.txt'; - echo "Wrote $svnInfoFile.\n"; +file_put_contents($svnInfoFile, $svnInfo); - $versionFile = $context->getBinaryDir() . '/version.txt'; +echo "Wrote $svnInfoFile.\n"; - file_put_contents($versionFile, "$icuVersionInDownload\n"); +$versionFile = $context->getBinaryDir() . '/version.txt'; - echo "Wrote $versionFile.\n"; +file_put_contents($versionFile, "$icuVersionInDownload\n"); - echo "Done.\n"; -} catch (\Exception $e) { - echo "\n"; +echo "Wrote $versionFile.\n"; - $cause = $e; - $introduction = 'Uncaught exception'; - - while (null !== $cause) { - echo $introduction." '".get_class($cause)."' with message '".$cause->getMessage()."'\n"; - echo "\n"; - echo $cause->getTraceAsString(); - echo "\n\n"; - - $cause = $cause->getPrevious(); - $introduction = 'Caused by'; - } -} +echo "Done.\n"; From 72398a3dc0d652f75d42b932163899a88edd108e Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 3 Oct 2013 10:50:28 +0200 Subject: [PATCH 14/48] [Intl] Decoupled transformation rules from Intl class --- .../Rule/CurrencyBundleTransformationRule.php | 20 +++++++++++---- .../Rule/LanguageBundleTransformationRule.php | 15 +++++++++-- .../Rule/LocaleBundleTransformationRule.php | 21 +++++++++++----- .../Rule/RegionBundleTransformationRule.php | 13 +++++++++- .../Intl/Resources/bin/create-stubs.php | 25 ++++++++++++++++--- .../Resources/bin/update-icu-component.php | 21 +++++++++++++--- 6 files changed, 93 insertions(+), 22 deletions(-) diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php index 4e77f9319b98f..40e1127f80a29 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php @@ -13,6 +13,7 @@ use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\ResourceBundle\CurrencyBundle; +use Symfony\Component\Intl\ResourceBundle\CurrencyBundleInterface; use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader; use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContextInterface; use Symfony\Component\Intl\ResourceBundle\Transformer\StubbingContextInterface; @@ -26,6 +27,16 @@ */ class CurrencyBundleTransformationRule implements TransformationRuleInterface { + /** + * @var CurrencyBundleInterface + */ + private $currencyBundle; + + public function __construct(CurrencyBundleInterface $currencyBundle) + { + $this->currencyBundle = $currencyBundle; + } + /** * {@inheritdoc} */ @@ -97,14 +108,13 @@ public function afterCompile(CompilationContextInterface $context) public function beforeCreateStub(StubbingContextInterface $context) { $currencies = array(); - $currencyBundle = Intl::getCurrencyBundle(); - foreach ($currencyBundle->getCurrencyNames('en') as $code => $name) { + foreach ($this->currencyBundle->getCurrencyNames('en') as $code => $name) { $currencies[$code] = array( CurrencyBundle::INDEX_NAME => $name, - CurrencyBundle::INDEX_SYMBOL => $currencyBundle->getCurrencySymbol($code, 'en'), - CurrencyBundle::INDEX_FRACTION_DIGITS => $currencyBundle->getFractionDigits($code), - CurrencyBundle::INDEX_ROUNDING_INCREMENT => $currencyBundle->getRoundingIncrement($code), + CurrencyBundle::INDEX_SYMBOL => $this->currencyBundle->getCurrencySymbol($code, 'en'), + CurrencyBundle::INDEX_FRACTION_DIGITS => $this->currencyBundle->getFractionDigits($code), + CurrencyBundle::INDEX_ROUNDING_INCREMENT => $this->currencyBundle->getRoundingIncrement($code), ); } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php index af42fac81b235..9559d860e8c0c 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Intl\ResourceBundle\Transformer\Rule; use Symfony\Component\Intl\Intl; +use Symfony\Component\Intl\ResourceBundle\LanguageBundleInterface; use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContextInterface; use Symfony\Component\Intl\ResourceBundle\Transformer\StubbingContextInterface; use Symfony\Component\Intl\ResourceBundle\Writer\TextBundleWriter; @@ -24,6 +25,16 @@ */ class LanguageBundleTransformationRule implements TransformationRuleInterface { + /** + * @var LanguageBundleInterface + */ + private $languageBundle; + + public function __construct(LanguageBundleInterface $languageBundle) + { + $this->languageBundle = $languageBundle; + } + /** * {@inheritdoc} */ @@ -66,8 +77,8 @@ public function afterCompile(CompilationContextInterface $context) public function beforeCreateStub(StubbingContextInterface $context) { return array( - 'Languages' => Intl::getLanguageBundle()->getLanguageNames('en'), - 'Scripts' => Intl::getLanguageBundle()->getScriptNames('en'), + 'Languages' => $this->languageBundle->getLanguageNames('en'), + 'Scripts' => $this->languageBundle->getScriptNames('en'), ); } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php index 7a9068872e543..ea46d2f66b457 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php @@ -14,6 +14,9 @@ use Symfony\Component\Intl\Exception\NoSuchEntryException; use Symfony\Component\Intl\Exception\RuntimeException; use Symfony\Component\Intl\Intl; +use Symfony\Component\Intl\ResourceBundle\LanguageBundleInterface; +use Symfony\Component\Intl\ResourceBundle\LocaleBundleInterface; +use Symfony\Component\Intl\ResourceBundle\RegionBundleInterface; use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContextInterface; use Symfony\Component\Intl\ResourceBundle\Transformer\StubbingContextInterface; use Symfony\Component\Intl\ResourceBundle\Writer\TextBundleWriter; @@ -26,19 +29,25 @@ class LocaleBundleTransformationRule implements TransformationRuleInterface { /** - * @var \Symfony\Component\Intl\ResourceBundle\LanguageBundleInterface + * @var LocaleBundleInterface + */ + private $localeBundle; + + /** + * @var LanguageBundleInterface */ private $languageBundle; /** - * @var \Symfony\Component\Intl\ResourceBundle\RegionBundleInterface + * @var RegionBundleInterface */ private $regionBundle; - public function __construct() + public function __construct(LocaleBundleInterface $localeBundle, LanguageBundleInterface $languageBundle, RegionBundleInterface $regionBundle) { - $this->languageBundle = Intl::getLanguageBundle(); - $this->regionBundle = Intl::getRegionBundle(); + $this->localeBundle = $localeBundle; + $this->languageBundle = $languageBundle; + $this->regionBundle = $regionBundle; } /** @@ -95,7 +104,7 @@ public function afterCompile(CompilationContextInterface $context) public function beforeCreateStub(StubbingContextInterface $context) { return array( - 'Locales' => Intl::getLocaleBundle()->getLocaleNames('en'), + 'Locales' => $this->localeBundle->getLocaleNames('en'), ); } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/RegionBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/RegionBundleTransformationRule.php index 1d66f5ea48e11..77324327cff7a 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/RegionBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/RegionBundleTransformationRule.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Intl\ResourceBundle\Transformer\Rule; use Symfony\Component\Intl\Intl; +use Symfony\Component\Intl\ResourceBundle\RegionBundleInterface; use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContextInterface; use Symfony\Component\Intl\ResourceBundle\Transformer\StubbingContextInterface; use Symfony\Component\Intl\ResourceBundle\Writer\TextBundleWriter; @@ -24,6 +25,16 @@ */ class RegionBundleTransformationRule implements TransformationRuleInterface { + /** + * @var RegionBundleInterface + */ + private $regionBundle; + + public function __construct(RegionBundleInterface $regionBundle) + { + $this->regionBundle = $regionBundle; + } + /** * {@inheritdoc} */ @@ -66,7 +77,7 @@ public function afterCompile(CompilationContextInterface $context) public function beforeCreateStub(StubbingContextInterface $context) { return array( - 'Countries' => Intl::getRegionBundle()->getCountryNames('en'), + 'Countries' => $this->regionBundle->getCountryNames('en'), ); } diff --git a/src/Symfony/Component/Intl/Resources/bin/create-stubs.php b/src/Symfony/Component/Intl/Resources/bin/create-stubs.php index d330d6b5fb5cf..b21599252a4f8 100644 --- a/src/Symfony/Component/Intl/Resources/bin/create-stubs.php +++ b/src/Symfony/Component/Intl/Resources/bin/create-stubs.php @@ -10,8 +10,14 @@ */ use Symfony\Component\Filesystem\Filesystem; +use Symfony\Component\Icu\IcuCurrencyBundle; use Symfony\Component\Icu\IcuData; +use Symfony\Component\Icu\IcuLanguageBundle; +use Symfony\Component\Icu\IcuLocaleBundle; +use Symfony\Component\Icu\IcuRegionBundle; use Symfony\Component\Intl\Intl; +use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader; +use Symfony\Component\Intl\ResourceBundle\Reader\StructuredBundleReader; use Symfony\Component\Intl\ResourceBundle\Transformer\BundleTransformer; use Symfony\Component\Intl\ResourceBundle\Transformer\Rule\CurrencyBundleTransformationRule; use Symfony\Component\Intl\ResourceBundle\Transformer\Rule\LanguageBundleTransformationRule; @@ -87,11 +93,22 @@ $icuVersionInIcuComponent ); +$reader = new StructuredBundleReader(new BinaryBundleReader()); + +$localeBundle = new IcuLocaleBundle($reader); +$languageBundle = new IcuLanguageBundle($reader); +$regionBundle = new IcuRegionBundle($reader); +$currencyBundle = new IcuCurrencyBundle($reader); + +// Make sure that the lookup of fallback locales follows locale aliases +// correctly (see setLocaleAliases()) +$reader->setLocaleAliases($localeBundle->getLocaleAliases()); + $transformer = new BundleTransformer(); -$transformer->addRule(new LanguageBundleTransformationRule()); -$transformer->addRule(new RegionBundleTransformationRule()); -$transformer->addRule(new CurrencyBundleTransformationRule()); -$transformer->addRule(new LocaleBundleTransformationRule()); +$transformer->addRule(new LanguageBundleTransformationRule($languageBundle)); +$transformer->addRule(new RegionBundleTransformationRule($regionBundle)); +$transformer->addRule(new CurrencyBundleTransformationRule($currencyBundle)); +$transformer->addRule(new LocaleBundleTransformationRule($localeBundle, $languageBundle, $regionBundle)); echo "Starting stub creation...\n"; diff --git a/src/Symfony/Component/Intl/Resources/bin/update-icu-component.php b/src/Symfony/Component/Intl/Resources/bin/update-icu-component.php index 3926d30ef0e1b..e8d83c76b05ce 100644 --- a/src/Symfony/Component/Intl/Resources/bin/update-icu-component.php +++ b/src/Symfony/Component/Intl/Resources/bin/update-icu-component.php @@ -9,9 +9,15 @@ * file that was distributed with this source code. */ +use Symfony\Component\Icu\IcuCurrencyBundle; use Symfony\Component\Icu\IcuData; +use Symfony\Component\Icu\IcuLanguageBundle; +use Symfony\Component\Icu\IcuLocaleBundle; +use Symfony\Component\Icu\IcuRegionBundle; use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\ResourceBundle\Compiler\BundleCompiler; +use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader; +use Symfony\Component\Intl\ResourceBundle\Reader\StructuredBundleReader; use Symfony\Component\Intl\ResourceBundle\Scanner\LocaleScanner; use Symfony\Component\Intl\ResourceBundle\Transformer\BundleTransformer; use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContext; @@ -176,11 +182,18 @@ new LocaleScanner() ); +$reader = new StructuredBundleReader(new BinaryBundleReader()); + +$localeBundle = new IcuLocaleBundle($reader); +$languageBundle = new IcuLanguageBundle($reader); +$regionBundle = new IcuRegionBundle($reader); +$currencyBundle = new IcuCurrencyBundle($reader); + $transformer = new BundleTransformer(); -$transformer->addRule(new LanguageBundleTransformationRule()); -$transformer->addRule(new RegionBundleTransformationRule()); -$transformer->addRule(new CurrencyBundleTransformationRule()); -$transformer->addRule(new LocaleBundleTransformationRule()); +$transformer->addRule(new LanguageBundleTransformationRule($languageBundle)); +$transformer->addRule(new RegionBundleTransformationRule($regionBundle)); +$transformer->addRule(new CurrencyBundleTransformationRule($currencyBundle)); +$transformer->addRule(new LocaleBundleTransformationRule($localeBundle, $languageBundle, $regionBundle)); echo "Starting resource bundle compilation. This may take a while...\n"; From 2bb250879ced8bb03d1f50f7ba835146b48f8dea Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 3 Oct 2013 12:17:32 +0200 Subject: [PATCH 15/48] [Intl] Improved test coverage of BinaryBundleReader --- .../Reader/BinaryBundleReader.php | 2 +- .../Reader/BinaryBundleReaderTest.php | 48 ++++++++++++++++-- .../ResourceBundle/Reader/Fixtures/build.sh | 17 +++++++ .../Reader/Fixtures/{ => php}/en.php | 0 .../Reader/Fixtures/res/alias.res | Bin 0 -> 88 bytes .../ResourceBundle/Reader/Fixtures/res/mo.res | Bin 0 -> 92 bytes .../Reader/Fixtures/{en.res => res/ro.res} | Bin .../Fixtures/{root.res => res/ro_MD.res} | Bin 84 -> 84 bytes .../Reader/Fixtures/res/root.res | Bin 0 -> 76 bytes .../ResourceBundle/Reader/Fixtures/root.txt | 3 -- .../Reader/Fixtures/txt/alias.txt | 3 ++ .../ResourceBundle/Reader/Fixtures/txt/mo.txt | 3 ++ .../Reader/Fixtures/{en.txt => txt/ro.txt} | 2 +- .../Reader/Fixtures/txt/ro_MD.txt | 3 ++ .../Reader/Fixtures/txt/root.txt | 6 +++ .../Reader/PhpBundleReaderTest.php | 4 +- 16 files changed, 81 insertions(+), 10 deletions(-) create mode 100755 src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/build.sh rename src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/{ => php}/en.php (100%) create mode 100644 src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/res/alias.res create mode 100644 src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/res/mo.res rename src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/{en.res => res/ro.res} (100%) rename src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/{root.res => res/ro_MD.res} (76%) create mode 100644 src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/res/root.res delete mode 100644 src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/root.txt create mode 100644 src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/alias.txt create mode 100644 src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/mo.txt rename src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/{en.txt => txt/ro.txt} (80%) create mode 100644 src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/ro_MD.txt create mode 100644 src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/root.txt diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/BinaryBundleReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/BinaryBundleReader.php index c88489a4ce247..b4e08e6ea97ac 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/BinaryBundleReader.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/BinaryBundleReader.php @@ -46,7 +46,7 @@ public function read($path, $locale) // instead. // Note that fallback to default is only working when a bundle contains // a root.res file. - if (U_USING_DEFAULT_WARNING === $bundle->getErrorCode()) { + if (in_array($bundle->getErrorCode(), array(U_USING_DEFAULT_WARNING, U_USING_FALLBACK_WARNING), true)) { throw new NoSuchLocaleException(sprintf( 'Could not load the resource bundle "%s" for locale "%s".', $path, diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BinaryBundleReaderTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BinaryBundleReaderTest.php index 8312ac36b166c..eeb24daee36c6 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BinaryBundleReaderTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BinaryBundleReaderTest.php @@ -33,19 +33,61 @@ protected function setUp() public function testReadReturnsArrayAccess() { - $data = $this->reader->read(__DIR__.'/Fixtures', 'en'); + $data = $this->reader->read(__DIR__.'/Fixtures/res', 'ro'); $this->assertInstanceOf('\ArrayAccess', $data); $this->assertSame('Bar', $data['Foo']); $this->assertFalse(isset($data['ExistsNot'])); } + public function testReadFollowsAlias() + { + // "alias" = "ro" + $data = $this->reader->read(__DIR__.'/Fixtures/res', 'alias'); + + $this->assertInstanceOf('\ArrayAccess', $data); + $this->assertSame('Bar', $data['Foo']); + $this->assertFalse(isset($data['ExistsNot'])); + } + + public function testReadFollowsFallback() + { + // "ro_MD" -> "ro" + $data = $this->reader->read(__DIR__.'/Fixtures/res', 'ro_MD'); + + $this->assertInstanceOf('\ArrayAccess', $data); + $this->assertSame('Bam', $data['Baz']); + $this->assertTrue(isset($data['Foo']), 'entries from the fallback locale are reported to be set...'); + $this->assertNull($data['Foo'], '...but are always NULL. WTF.'); + $this->assertFalse(isset($data['ExistsNot'])); + } + + public function testReadFollowsFallbackAlias() + { + // "mo" = "ro_MD" -> "ro" + $data = $this->reader->read(__DIR__.'/Fixtures/res', 'mo'); + + $this->assertInstanceOf('\ArrayAccess', $data); + $this->assertSame('Bam', $data['Baz'], 'data from the aliased locale can be accessed'); + $this->assertTrue(isset($data['Foo']), 'entries from the fallback locale are reported to be set...'); + $this->assertNull($data['Foo'], '...but are always NULL. WTF.'); + $this->assertFalse(isset($data['ExistsNot'])); + } + /** * @expectedException \Symfony\Component\Intl\Exception\NoSuchLocaleException */ public function testReadFailsIfNonExistingLocale() { - $this->reader->read(__DIR__.'/Fixtures', 'foo'); + $this->reader->read(__DIR__.'/Fixtures/res', 'foo'); + } + + /** + * @expectedException \Symfony\Component\Intl\Exception\NoSuchLocaleException + */ + public function testReadFailsIfNonExistingFallbackLocale() + { + $this->reader->read(__DIR__.'/Fixtures/res', 'ro_AT'); } /** @@ -53,6 +95,6 @@ public function testReadFailsIfNonExistingLocale() */ public function testReadFailsIfNonExistingDirectory() { - $this->reader->read(__DIR__.'/foo', 'en'); + $this->reader->read(__DIR__.'/foo', 'ro'); } } diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/build.sh b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/build.sh new file mode 100755 index 0000000000000..50513e7a946c3 --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/build.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +if [ -z "$ICU_BUILD_DIR" ]; then + echo "Please set the ICU_BUILD_DIR environment variable" + exit +fi + +if [ ! -d "$ICU_BUILD_DIR" ]; then + echo "The directory $ICU_BUILD_DIR pointed at by ICU_BUILD_DIR does not exist" + exit +fi + +DIR=`dirname $0` + +rm $DIR/res/*.res + +LD_LIBRARY_PATH=$ICU_BUILD_DIR/lib $ICU_BUILD_DIR/bin/genrb -d $DIR/res $DIR/txt/*.txt diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/en.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/php/en.php similarity index 100% rename from src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/en.php rename to src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/php/en.php diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/res/alias.res b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/res/alias.res new file mode 100644 index 0000000000000000000000000000000000000000..4f0ab7eaa316685f0bf153446f37aeb0221fdd10 GIT binary patch literal 88 zcmY#jxTP+_00K-5L8-+~Oh6VR3s?Y50GR>oKo%De^Fc8qSO&sZRdw|7bPNWH6fxuj MNk#?*AYQc!03g)|MgRZ+ literal 0 HcmV?d00001 diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/res/mo.res b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/res/mo.res new file mode 100644 index 0000000000000000000000000000000000000000..3f8911a7317ed2f6e19b291c131ca68bb7f7cad1 GIT binary patch literal 92 zcmY#jxTP+_00K-5L8-+~Oh6VR3s?Y5urn|O05Jm>5c5MZBUlE)S5hSz~ID?$WR2N85tCS7yw4-1Lyz% diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/res/root.res b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/res/root.res new file mode 100644 index 0000000000000000000000000000000000000000..81ba7eaedb0f082b1de3b3f618fc112d7392acef GIT binary patch literal 76 zcmY#jxTP+_00K-5L8-+~Oh6VR3s?Y5FfuR%umf3~K+FxrKrt`?@#Ev;fg~e?0+0j% D)CUA? literal 0 HcmV?d00001 diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/root.txt b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/root.txt deleted file mode 100644 index 9d1349bac5e20..0000000000000 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/root.txt +++ /dev/null @@ -1,3 +0,0 @@ -root{ - Version{"1.0"} -} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/alias.txt b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/alias.txt new file mode 100644 index 0000000000000..d6e216f4cbc08 --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/alias.txt @@ -0,0 +1,3 @@ +alias{ + "%%ALIAS"{"ro"} +} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/mo.txt b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/mo.txt new file mode 100644 index 0000000000000..3ce23bcc639d5 --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/mo.txt @@ -0,0 +1,3 @@ +mo{ + "%%ALIAS"{"ro_MD"} +} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/en.txt b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/ro.txt similarity index 80% rename from src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/en.txt rename to src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/ro.txt index c788e996acb1d..80d28889cf391 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/en.txt +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/ro.txt @@ -1,3 +1,3 @@ -en{ +ro{ Foo{"Bar"} } diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/ro_MD.txt b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/ro_MD.txt new file mode 100644 index 0000000000000..fcbb3bc07d538 --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/ro_MD.txt @@ -0,0 +1,3 @@ +ro_MD{ + Baz{"Bam"} +} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/root.txt b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/root.txt new file mode 100644 index 0000000000000..4d8265997f712 --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/Fixtures/txt/root.txt @@ -0,0 +1,6 @@ +root{ + /** + * so genrb doesn't issue warnings + */ + ___{""} +} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/PhpBundleReaderTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/PhpBundleReaderTest.php index cb4b144d2d3d1..a2a21d656c074 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/PhpBundleReaderTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/PhpBundleReaderTest.php @@ -30,7 +30,7 @@ protected function setUp() public function testReadReturnsArray() { - $data = $this->reader->read(__DIR__ . '/Fixtures', 'en'); + $data = $this->reader->read(__DIR__ . '/Fixtures/php', 'en'); $this->assertTrue(is_array($data)); $this->assertSame('Bar', $data['Foo']); @@ -42,7 +42,7 @@ public function testReadReturnsArray() */ public function testReadFailsIfLocaleOtherThanEn() { - $this->reader->read(__DIR__ . '/Fixtures', 'foo'); + $this->reader->read(__DIR__ . '/Fixtures/php', 'foo'); } /** From 9d202dc156e2cf71b9a95707f4423b86af615907 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 3 Oct 2013 12:41:55 +0200 Subject: [PATCH 16/48] [Intl] Improved exception formatting in transformation scripts --- .../Component/Intl/Resources/bin/common.php | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Intl/Resources/bin/common.php b/src/Symfony/Component/Intl/Resources/bin/common.php index d6fd94dd2716f..ca86f31f16b2c 100644 --- a/src/Symfony/Component/Intl/Resources/bin/common.php +++ b/src/Symfony/Component/Intl/Resources/bin/common.php @@ -72,15 +72,22 @@ function get_icu_version_from_genrb($genrb) echo "\n"; $cause = $exception; - $introduction = 'Uncaught exception'; + $root = true; while (null !== $cause) { - echo $introduction." '".get_class($cause)."' with message '".$cause->getMessage()."'\n"; + if (!$root) { + echo "Caused by\n"; + } + + echo get_class($cause).": ".$cause->getMessage()."\n"; + echo "\n"; + echo $cause->getFile().":".$cause->getLine()."\n"; + foreach ($cause->getTrace() as $trace) { + echo $trace['file'].":".$trace['line']."\n"; + } echo "\n"; - echo $cause->getTraceAsString(); - echo "\n\n"; $cause = $cause->getPrevious(); - $introduction = 'Caused by'; + $root = false; } }); From fec3933f516cfe3c89b7ba45ee09f461b67b9d1f Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 3 Oct 2013 14:16:31 +0200 Subject: [PATCH 17/48] [Intl] StructuredBundleReader now catches NoSuchLocaleExceptions and continues at fallback locale (unless disabled) --- .../Reader/StructuredBundleReader.php | 27 ++-- .../Reader/StructuredBundleReaderTest.php | 123 ++++++++++++++---- 2 files changed, 114 insertions(+), 36 deletions(-) diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReader.php index 7684b647a4f3b..5564c38f92d2e 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReader.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReader.php @@ -13,6 +13,7 @@ use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\Exception\NoSuchEntryException; +use Symfony\Component\Intl\Exception\NoSuchLocaleException; use Symfony\Component\Intl\Exception\OutOfBoundsException; use Symfony\Component\Intl\ResourceBundle\Util\RecursiveArrayAccess; @@ -83,6 +84,11 @@ public function readEntry($path, $locale, array $indices, $fallback = true) $testedLocales = array(); 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); @@ -119,20 +125,22 @@ public function readEntry($path, $locale, array $indices, $fallback = true) // If this or the previous entry was multi-valued, we are dealing // with a merged, multi-valued entry now $isMultiValued = $isMultiValued || $isCurrentMultiValued; + } catch (NoSuchLocaleException $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 - if (null === $exception) { - $exception = $e; - } + $exception = $e; } // Remember which locales we tried $testedLocales[] = $currentLocale.'.res'; - // First check whether the locale is an alias - if (isset($this->localeAliases[$currentLocale])) { - $currentLocale = $this->localeAliases[$currentLocale]; + // Check whether fallback is allowed + if (!$fallback) { + break; } // Then determine fallback locale @@ -152,11 +160,10 @@ public function readEntry($path, $locale, array $indices, $fallback = true) // Entry is still NULL, read error occurred. Throw an exception // containing the detailed path and locale $errorMessage = sprintf( - 'Error while reading the indices [%s] in "%s/%s.res": %s.', + 'Couldn\'t read the indices [%s] from "%s/%s.res".', implode('][', $indices), $path, - $locale, - $exception->getMessage() + $locale ); // Append fallback locales, if any @@ -165,7 +172,7 @@ public function readEntry($path, $locale, array $indices, $fallback = true) array_shift($testedLocales); $errorMessage .= sprintf( - ' The index could also be found in neither of the fallback locales: "%s".', + ' The indices also couldn\'t be found in the fallback locale(s) "%s".', implode('", "', $testedLocales) ); } diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/StructuredBundleReaderTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/StructuredBundleReaderTest.php index 7678e22b72678..b64fb129af312 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/StructuredBundleReaderTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/StructuredBundleReaderTest.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Intl\Tests\ResourceBundle\Reader; +use Symfony\Component\Intl\Exception\NoSuchLocaleException; use Symfony\Component\Intl\ResourceBundle\Reader\StructuredBundleReader; /** @@ -48,7 +49,7 @@ public function testForwardCallToRead() $this->assertSame($data, $this->reader->read(self::RES_DIR, 'en')); } - public function testReadCompleteDataFile() + public function testReadEntireDataFileIfNoIndicesGiven() { $data = array('foo', 'bar'); @@ -106,6 +107,51 @@ public function testFallbackIfEntryDoesNotExist() $this->assertSame('Baz', $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'))); } + /** + * @expectedException \Symfony\Component\Intl\Exception\NoSuchEntryException + */ + public function testDontFallbackIfEntryDoesNotExistAndFallbackDisabled() + { + $data = array('Foo' => 'Bar'); + + $this->readerImpl->expects($this->once()) + ->method('read') + ->with(self::RES_DIR, 'en_GB') + ->will($this->returnValue($data)); + + $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'), false); + } + + public function testFallbackIfLocaleDoesNotExist() + { + $this->readerImpl->expects($this->at(0)) + ->method('read') + ->with(self::RES_DIR, 'en_GB') + ->will($this->throwException(new NoSuchLocaleException())); + + $fallbackData = array('Foo' => array('Bar' => 'Baz')); + + $this->readerImpl->expects($this->at(1)) + ->method('read') + ->with(self::RES_DIR, 'en') + ->will($this->returnValue($fallbackData)); + + $this->assertSame('Baz', $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'))); + } + + /** + * @expectedException \Symfony\Component\Intl\Exception\NoSuchEntryException + */ + public function testDontFallbackIfLocaleDoesNotExistAndFallbackDisabled() + { + $this->readerImpl->expects($this->once()) + ->method('read') + ->with(self::RES_DIR, 'en_GB') + ->will($this->throwException(new NoSuchLocaleException())); + + $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'), false); + } + public function provideMergeableValues() { return array( @@ -124,16 +170,21 @@ public function provideMergeableValues() */ public function testMergeDataWithFallbackData($childData, $parentData, $result) { - $this->readerImpl->expects($this->at(0)) - ->method('read') - ->with(self::RES_DIR, 'en_GB') - ->will($this->returnValue($childData)); - if (null === $childData || is_array($childData)) { + $this->readerImpl->expects($this->at(0)) + ->method('read') + ->with(self::RES_DIR, 'en_GB') + ->will($this->returnValue($childData)); + $this->readerImpl->expects($this->at(1)) ->method('read') ->with(self::RES_DIR, 'en') ->will($this->returnValue($parentData)); + } else { + $this->readerImpl->expects($this->once()) + ->method('read') + ->with(self::RES_DIR, 'en_GB') + ->will($this->returnValue($childData)); } $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'en_GB', array(), true)); @@ -157,16 +208,21 @@ public function testDontMergeDataIfFallbackDisabled($childData, $parentData, $re */ public function testMergeExistingEntryWithExistingFallbackEntry($childData, $parentData, $result) { - $this->readerImpl->expects($this->at(0)) - ->method('read') - ->with(self::RES_DIR, 'en_GB') - ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); - if (null === $childData || is_array($childData)) { + $this->readerImpl->expects($this->at(0)) + ->method('read') + ->with(self::RES_DIR, 'en_GB') + ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); + $this->readerImpl->expects($this->at(1)) ->method('read') ->with(self::RES_DIR, 'en') ->will($this->returnValue(array('Foo' => array('Bar' => $parentData)))); + } else { + $this->readerImpl->expects($this->once()) + ->method('read') + ->with(self::RES_DIR, 'en_GB') + ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); } $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'), true)); @@ -195,16 +251,21 @@ public function testMergeNonExistingEntryWithExistingFallbackEntry($childData, $ */ public function testMergeExistingEntryWithNonExistingFallbackEntry($childData, $parentData, $result) { - $this->readerImpl->expects($this->at(0)) - ->method('read') - ->with(self::RES_DIR, 'en_GB') - ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); - if (null === $childData || is_array($childData)) { + $this->readerImpl->expects($this->at(0)) + ->method('read') + ->with(self::RES_DIR, 'en_GB') + ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); + $this->readerImpl->expects($this->at(1)) ->method('read') ->with(self::RES_DIR, 'en') ->will($this->returnValue(array('Foo' => 'Bar'))); + } else { + $this->readerImpl->expects($this->once()) + ->method('read') + ->with(self::RES_DIR, 'en_GB') + ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); } $this->assertSame($childData, $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'), true)); @@ -236,16 +297,21 @@ public function testMergeTraversables($childData, $parentData, $result) $parentData = is_array($parentData) ? new \ArrayObject($parentData) : $parentData; $childData = is_array($childData) ? new \ArrayObject($childData) : $childData; - $this->readerImpl->expects($this->at(0)) - ->method('read') - ->with(self::RES_DIR, 'en_GB') - ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); - if (null === $childData || $childData instanceof \ArrayObject) { + $this->readerImpl->expects($this->at(0)) + ->method('read') + ->with(self::RES_DIR, 'en_GB') + ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); + $this->readerImpl->expects($this->at(1)) ->method('read') ->with(self::RES_DIR, 'en') ->will($this->returnValue(array('Foo' => array('Bar' => $parentData)))); + } else { + $this->readerImpl->expects($this->once()) + ->method('read') + ->with(self::RES_DIR, 'en_GB') + ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); } $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'), true)); @@ -258,17 +324,22 @@ public function testFollowLocaleAliases($childData, $parentData, $result) { $this->reader->setLocaleAliases(array('mo' => 'ro_MD')); - $this->readerImpl->expects($this->at(0)) - ->method('read') - ->with(self::RES_DIR, 'mo') - ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); - if (null === $childData || is_array($childData)) { + $this->readerImpl->expects($this->at(0)) + ->method('read') + ->with(self::RES_DIR, 'ro_MD') + ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); + // Read fallback locale of aliased locale ("ro_MD" -> "ro") $this->readerImpl->expects($this->at(1)) ->method('read') ->with(self::RES_DIR, 'ro') ->will($this->returnValue(array('Foo' => array('Bar' => $parentData)))); + } else { + $this->readerImpl->expects($this->once()) + ->method('read') + ->with(self::RES_DIR, 'ro_MD') + ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); } $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'mo', array('Foo', 'Bar'), true)); From 945cfef351c482b844bf73401df37647742e4030 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 3 Oct 2013 14:17:06 +0200 Subject: [PATCH 18/48] [Intl] Changed LocaleBundleTransformationRule to generate a root.res file to enable proper locale fallback --- .../Transformer/Rule/LocaleBundleTransformationRule.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php index ea46d2f66b457..c73b16e28fdd3 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Intl\ResourceBundle\Transformer\Rule; use Symfony\Component\Intl\Exception\NoSuchEntryException; +use Symfony\Component\Intl\Exception\NoSuchLocaleException; use Symfony\Component\Intl\Exception\RuntimeException; use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\ResourceBundle\LanguageBundleInterface; @@ -87,6 +88,9 @@ public function beforeCompile(CompilationContextInterface $context) 'Aliases' => $aliases, ), false); + // Create empty root file, other wise locale fallback is not working + $writer->write($tempDir, 'root', array('___' => '')); + return $tempDir; } From d19e20aef8bf07fc754e788048ce13b33436acc7 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 3 Oct 2013 14:17:31 +0200 Subject: [PATCH 19/48] [Intl] Improved error messages in BinaryBundleReader --- .../Intl/ResourceBundle/Reader/BinaryBundleReader.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/BinaryBundleReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/BinaryBundleReader.php index b4e08e6ea97ac..bfb5993739bfa 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/BinaryBundleReader.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/BinaryBundleReader.php @@ -35,7 +35,7 @@ public function read($path, $locale) // (i.e. contain a bunch of *.res files) if (null === $bundle) { throw new RuntimeException(sprintf( - 'Could not load the resource bundle "%s/%s.res".', + 'The resource bundle "%s/%s.res" could not be found.', $path, $locale )); @@ -48,7 +48,7 @@ public function read($path, $locale) // a root.res file. if (in_array($bundle->getErrorCode(), array(U_USING_DEFAULT_WARNING, U_USING_FALLBACK_WARNING), true)) { throw new NoSuchLocaleException(sprintf( - 'Could not load the resource bundle "%s" for locale "%s".', + 'The resource bundle "%s/%s.res" could not be found.', $path, $locale )); From 127d9c03447060892b6819002dfb141d436ef437 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 3 Oct 2013 14:19:29 +0200 Subject: [PATCH 20/48] [Intl] Changed AbstractEntry::readEntry() to fallback by default --- src/Symfony/Component/Intl/CHANGELOG.md | 3 +++ .../Component/Intl/ResourceBundle/AbstractBundle.php | 4 ++-- .../Component/Intl/ResourceBundle/LanguageBundle.php | 8 ++++---- .../Component/Intl/ResourceBundle/LocaleBundle.php | 2 +- .../Component/Intl/ResourceBundle/RegionBundle.php | 4 ++-- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/Symfony/Component/Intl/CHANGELOG.md b/src/Symfony/Component/Intl/CHANGELOG.md index b228bbbbd92c6..6f0af5ee4e24c 100644 --- a/src/Symfony/Component/Intl/CHANGELOG.md +++ b/src/Symfony/Component/Intl/CHANGELOG.md @@ -16,3 +16,6 @@ CHANGELOG the time of this writing * `StructuredBundleReader` now follows aliases when looking for fallback locales * [BC BREAK] a new method `getLocaleAliases()` was added to `LocaleBundleInterface` + * [BC BREAK] changed default value of the argument `$fallback` in the protected + method `AbstractBundle::readEntry()` to `true` in order to be consistent with + the proxied `StructuredBundleReaderInterface::readEntry()` method diff --git a/src/Symfony/Component/Intl/ResourceBundle/AbstractBundle.php b/src/Symfony/Component/Intl/ResourceBundle/AbstractBundle.php index 66842a69a1fad..7f2240797b379 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/AbstractBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/AbstractBundle.php @@ -56,8 +56,8 @@ protected function read($locale) /** * Proxy method for {@link StructuredBundleReaderInterface#readEntry}. */ - protected function readEntry($locale, array $indices, $mergeFallback = false) + protected function readEntry($locale, array $indices, $fallback = true) { - return $this->reader->readEntry($this->path, $locale, $indices, $mergeFallback); + return $this->reader->readEntry($this->path, $locale, $indices, $fallback); } } diff --git a/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php b/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php index c60a0919f6de2..10c369e58a08f 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php @@ -47,12 +47,12 @@ public function getLanguageName($lang, $region = null, $locale = null) // i.e. "en_GB" is translated as "British English" if (null !== $region) { try { - return $this->readEntry($locale, array('Languages', $lang.'_'.$region), true); + return $this->readEntry($locale, array('Languages', $lang.'_'.$region)); } catch (NoSuchEntryException $e) { } } - return $this->readEntry($locale, array('Languages', $lang), true); + return $this->readEntry($locale, array('Languages', $lang)); } /** @@ -64,7 +64,7 @@ public function getLanguageNames($locale = null) $locale = \Locale::getDefault(); } - if (null === ($languages = $this->readEntry($locale, array('Languages'), true))) { + if (null === ($languages = $this->readEntry($locale, array('Languages')))) { return array(); } @@ -96,7 +96,7 @@ public function getScriptNames($locale = null) $locale = \Locale::getDefault(); } - if (null === ($scripts = $this->readEntry($locale, array('Scripts'), true))) { + if (null === ($scripts = $this->readEntry($locale, array('Scripts')))) { return array(); } diff --git a/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php b/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php index fd69a20272710..fac0c10d558fb 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php @@ -67,7 +67,7 @@ public function getLocaleNames($locale = null) $locale = \Locale::getDefault(); } - if (null === ($locales = $this->readEntry($locale, array('Locales'), true))) { + if (null === ($locales = $this->readEntry($locale, array('Locales')))) { return array(); } diff --git a/src/Symfony/Component/Intl/ResourceBundle/RegionBundle.php b/src/Symfony/Component/Intl/ResourceBundle/RegionBundle.php index 1f1b35a48d18b..e9156c58c9790 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/RegionBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/RegionBundle.php @@ -41,7 +41,7 @@ public function getCountryName($country, $locale = null) $locale = \Locale::getDefault(); } - return $this->readEntry($locale, array('Countries', $country), true); + return $this->readEntry($locale, array('Countries', $country)); } /** @@ -53,7 +53,7 @@ public function getCountryNames($locale = null) $locale = \Locale::getDefault(); } - if (null === ($countries = $this->readEntry($locale, array('Countries'), true))) { + if (null === ($countries = $this->readEntry($locale, array('Countries')))) { return array(); } From 921f9fb0adbecc6755a7b8d7c07c902ed04740da Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 3 Oct 2013 14:19:58 +0200 Subject: [PATCH 21/48] [Intl] Added period to exception message in RecursiveArrayAccess --- .../Component/Intl/ResourceBundle/Util/RecursiveArrayAccess.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Intl/ResourceBundle/Util/RecursiveArrayAccess.php b/src/Symfony/Component/Intl/ResourceBundle/Util/RecursiveArrayAccess.php index 8e932561d8a23..28ba138383ba1 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Util/RecursiveArrayAccess.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Util/RecursiveArrayAccess.php @@ -23,7 +23,7 @@ public static function get($array, array $indices) foreach ($indices as $index) { // Use array_key_exists() for arrays, isset() otherwise if (is_array($array) && !array_key_exists($index, $array) || !is_array($array) && !isset($array[$index])) { - throw new OutOfBoundsException('The index '.$index.' does not exist'); + throw new OutOfBoundsException('The index '.$index.' does not exist.'); } $array = $array[$index]; From 6251c7e77ba12d8774a78c140d70723f47703825 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 3 Oct 2013 14:36:01 +0200 Subject: [PATCH 22/48] [Intl] Renamed BundleCompiler to GenrbBundleCompiler --- UPGRADE-3.0.md | 24 +++++++ src/Symfony/Component/Intl/CHANGELOG.md | 1 + .../Compiler/BundleCompiler.php | 57 ++------------- .../Compiler/GenrbBundleCompiler.php | 72 +++++++++++++++++++ .../Writer/TextBundleWriter.php | 5 +- .../Resources/bin/update-icu-component.php | 4 +- 6 files changed, 107 insertions(+), 56 deletions(-) create mode 100644 src/Symfony/Component/Intl/ResourceBundle/Compiler/GenrbBundleCompiler.php diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index be37fabc2c7c3..f994e4cc35888 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -257,6 +257,30 @@ UPGRADE FROM 2.x to 3.0 * The `Symfony\Component\HttpKernel\EventListener\ExceptionListener` now passes the Request format as the `_format` argument instead of `format`. +### Intl + + * The class `BundleCompiler` was renamed to `GenrbBundleCompiler`. + + Before: + + ``` + use Symfony\Component\Intl\ResourceBundle\Compiler\BundleCompiler; + + $genrb = '/path/to/icu/build/bin/genrb'; + $genrbEnv = 'LD_LIBRARY_PATH=/path/to/icu/build/lib' + $compiler = new BundleCompiler($genrb, $genrbEnv); + ``` + + After: + + ``` + use Symfony\Component\Intl\ResourceBundle\Compiler\GenrbBundleCompiler; + + $genrb = '/path/to/icu/build/bin/genrb'; + $genrbEnv = 'LD_LIBRARY_PATH=/path/to/icu/build/lib' + $compiler = new GenrbBundleCompiler($genrb, $genrbEnv); + ``` + ### Locale * The Locale component was removed and replaced by the Intl component. diff --git a/src/Symfony/Component/Intl/CHANGELOG.md b/src/Symfony/Component/Intl/CHANGELOG.md index 6f0af5ee4e24c..196909d33ca63 100644 --- a/src/Symfony/Component/Intl/CHANGELOG.md +++ b/src/Symfony/Component/Intl/CHANGELOG.md @@ -19,3 +19,4 @@ CHANGELOG * [BC BREAK] changed default value of the argument `$fallback` in the protected method `AbstractBundle::readEntry()` to `true` in order to be consistent with the proxied `StructuredBundleReaderInterface::readEntry()` method + * deprecated `BundleCompiler` in favor of `GenrbBundleCompiler` diff --git a/src/Symfony/Component/Intl/ResourceBundle/Compiler/BundleCompiler.php b/src/Symfony/Component/Intl/ResourceBundle/Compiler/BundleCompiler.php index 174aa179f4067..311ca10bf3516 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Compiler/BundleCompiler.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Compiler/BundleCompiler.php @@ -11,61 +11,14 @@ namespace Symfony\Component\Intl\ResourceBundle\Compiler; -use Symfony\Component\Intl\Exception\RuntimeException; - /** - * Compiles .txt resource bundles to binary .res files. + * Alias of {@link GenrbBundleCompiler}. * * @author Bernhard Schussek + * + * @deprecated Deprecated since version 2.4, to be removed in Symfony 3.0. Use + * the equivalent {@link GenrbBundleCompiler} instead. */ -class BundleCompiler implements BundleCompilerInterface +class BundleCompiler extends GenrbBundleCompiler { - /** - * @var string The path to the "genrb" executable. - */ - 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 ($status !== 0) { - throw new RuntimeException(sprintf( - 'genrb failed with status %d while compiling %s to %s.', - $status, - $sourcePath, - $targetDir - )); - } - } } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Compiler/GenrbBundleCompiler.php b/src/Symfony/Component/Intl/ResourceBundle/Compiler/GenrbBundleCompiler.php new file mode 100644 index 0000000000000..d5d4f05b5cf28 --- /dev/null +++ b/src/Symfony/Component/Intl/ResourceBundle/Compiler/GenrbBundleCompiler.php @@ -0,0 +1,72 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\ResourceBundle\Compiler; + +use Symfony\Component\Intl\Exception\RuntimeException; + +/** + * Compiles .txt resource bundles to binary .res files. + * + * @since 2.4 + * @author Bernhard Schussek + */ +class GenrbBundleCompiler implements BundleCompilerInterface +{ + /** + * @var string The path to the "genrb" executable. + */ + 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 ($status !== 0) { + throw new RuntimeException(sprintf( + 'genrb failed with status %d while compiling %s to %s.', + $status, + $sourcePath, + $targetDir + )); + } + } +} diff --git a/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php b/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php index 48096c1a1a527..08bf0a27a5d48 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php @@ -16,8 +16,9 @@ /** * Writes .txt resource bundles. * - * The resulting files can be converted to binary .res files using the - * {@link \Symfony\Component\Intl\ResourceBundle\Transformer\BundleCompiler}. + * The resulting files can be converted to binary .res files using a + * {@link \Symfony\Component\Intl\ResourceBundle\Transformer\BundleCompilerInterface} + * implementation. * * @author Bernhard Schussek * diff --git a/src/Symfony/Component/Intl/Resources/bin/update-icu-component.php b/src/Symfony/Component/Intl/Resources/bin/update-icu-component.php index e8d83c76b05ce..dca7a6e0c3e02 100644 --- a/src/Symfony/Component/Intl/Resources/bin/update-icu-component.php +++ b/src/Symfony/Component/Intl/Resources/bin/update-icu-component.php @@ -15,7 +15,7 @@ use Symfony\Component\Icu\IcuLocaleBundle; use Symfony\Component\Icu\IcuRegionBundle; use Symfony\Component\Intl\Intl; -use Symfony\Component\Intl\ResourceBundle\Compiler\BundleCompiler; +use Symfony\Component\Intl\ResourceBundle\Compiler\GenrbBundleCompiler; use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader; use Symfony\Component\Intl\ResourceBundle\Reader\StructuredBundleReader; use Symfony\Component\Intl\ResourceBundle\Scanner\LocaleScanner; @@ -177,7 +177,7 @@ $sourceDir . '/data', IcuData::getResourceDirectory(), $filesystem, - new BundleCompiler($genrb, $genrbEnv), + new GenrbBundleCompiler($genrb, $genrbEnv), $icuVersionInDownload, new LocaleScanner() ); From 1b3d920f117f321ff930757d2c1df9cd748fcd18 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 3 Oct 2013 14:50:13 +0200 Subject: [PATCH 23/48] [Intl] Added missing entries to the UPGRADE file --- UPGRADE-2.4.md | 80 +++++++++++++++++++++++++ src/Symfony/Component/Intl/CHANGELOG.md | 4 +- 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/UPGRADE-2.4.md b/UPGRADE-2.4.md index 4596b8646afbe..0c8d3d991cd5c 100644 --- a/UPGRADE-2.4.md +++ b/UPGRADE-2.4.md @@ -14,3 +14,83 @@ Intl * A new method `getLocaleAliases()` was added to `LocaleBundleInterface`. If any of your classes implements this interface, you should add an implementation of this method. + + * The methods in the various resource bundles of the `Intl` class used to + return `null` when invalid arguments were given. These methods throw a + `NoSuchEntryException` now. + + Before: + + ``` + use Symfony\Component\Intl\Intl; + + // invalid language code + $language = Intl::getLanguageBundle()->getLanguageName('foo', null, 'en'); + + // invalid locale + $language = Intl::getLanguageBundle()->getLanguageName('de', null, 'foo'); + + if (null === $language) { + // error handling... + } + ``` + + After: + + ``` + use Symfony\Component\Intl\Intl; + use Symfony\Component\Intl\Exception\NoSuchEntryException; + use Symfony\Component\Intl\Exception\NoSuchLocaleException; + + try { + // invalid language code + $language = Intl::getLanguageBundle()->getLanguageName('foo', null, 'en'); + + // invalid locale + $language = Intl::getLanguageBundle()->getLanguageName('de', null, 'foo'); + } catch (NoSuchEntryException $e) { + if ($e->getPrevious() instanceof NoSuchLocaleException) { + // locale was invalid... + } else { + // locale was valid, but entry not found... + } + } + ``` + + * The `$fallback` argument of the protected method `AbstractBundle::readEntry()` + was changed to be `true` by default. This way the signature is consistent + with the proxied `StructuredBundleReaderInterface::readEntry()` method. + Consequently, if an entry cannot be found for the accessed locale (e.g. "en_GB"), + it is looked for in the fallback locale (if any, e.g. "en"). + + If you extend this class and explicitly want to disable locale fallback, you + should pass `false` as last argument. + + Before: + + ``` + use Symfony\Component\Intl\ResourceBundle\AbstractBundle; + + class MyBundle extends AbstractBundle + { + public function getEntry($key, $locale) + { + return $this->readEntry($locale, array('Entries', $key)); + } + } + ``` + + After: + + ``` + use Symfony\Component\Intl\ResourceBundle\AbstractBundle; + + class MyBundle extends AbstractBundle + { + public function getEntry($key, $locale) + { + // disable locale fallback! + return $this->readEntry($locale, array('Entries', $key), false); + } + } + ``` diff --git a/src/Symfony/Component/Intl/CHANGELOG.md b/src/Symfony/Component/Intl/CHANGELOG.md index 196909d33ca63..26f68f8d1207d 100644 --- a/src/Symfony/Component/Intl/CHANGELOG.md +++ b/src/Symfony/Component/Intl/CHANGELOG.md @@ -4,10 +4,8 @@ CHANGELOG 2.4.0 ----- - * [BC BREAK] the various Intl methods now throw a `NoSuchLocaleException` - whenever an invalid locale is given * [BC BREAK] the various Intl methods now throw a `NoSuchEntryException` - whenever a non-existing language, currency, etc. is accessed + whenever a non-existing locale, language, currency, etc. is accessed * the available locales of each resource bundle are now stored in a generic "misc.res" file in order to improve reading performance * improved `LocaleBundleTransformationRule` to not generate duplicate locale From e002ebaa961b7ca7655bc02d5fc6d9e332bd5e3e Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 3 Oct 2013 14:58:08 +0200 Subject: [PATCH 24/48] [Intl] Renamed StructuredBundleReader[Interface] to BundleEntryReader[Interface] --- UPGRADE-2.4.md | 2 +- UPGRADE-3.0.md | 40 ++++ src/Symfony/Component/Intl/CHANGELOG.md | 6 +- src/Symfony/Component/Intl/Intl.php | 8 +- .../Intl/ResourceBundle/AbstractBundle.php | 4 +- .../Reader/BundleEntryReader.php | 182 ++++++++++++++++++ .../Reader/BundleEntryReaderInterface.php | 51 +++++ .../Reader/StructuredBundleReader.php | 166 +--------------- .../StructuredBundleReaderInterface.php | 36 +--- .../Resources/bin/update-icu-component.php | 4 +- ...aderTest.php => BundleEntryReaderTest.php} | 2 +- 11 files changed, 296 insertions(+), 205 deletions(-) create mode 100644 src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReader.php create mode 100644 src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReaderInterface.php rename src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/{StructuredBundleReaderTest.php => BundleEntryReaderTest.php} (99%) diff --git a/UPGRADE-2.4.md b/UPGRADE-2.4.md index 0c8d3d991cd5c..9f9bdfe46d9d0 100644 --- a/UPGRADE-2.4.md +++ b/UPGRADE-2.4.md @@ -59,7 +59,7 @@ Intl * The `$fallback` argument of the protected method `AbstractBundle::readEntry()` was changed to be `true` by default. This way the signature is consistent - with the proxied `StructuredBundleReaderInterface::readEntry()` method. + with the proxied `BundleEntryReaderInterface::readEntry()` method. Consequently, if an entry cannot be found for the accessed locale (e.g. "en_GB"), it is looked for in the fallback locale (if any, e.g. "en"). diff --git a/UPGRADE-3.0.md b/UPGRADE-3.0.md index f994e4cc35888..774b6fda8a1d8 100644 --- a/UPGRADE-3.0.md +++ b/UPGRADE-3.0.md @@ -281,6 +281,46 @@ UPGRADE FROM 2.x to 3.0 $compiler = new GenrbBundleCompiler($genrb, $genrbEnv); ``` + * The class `StructuredBundleReader` was renamed to `BundleEntryReader`. The + corresponding interface `StructuredBundleReaderInterface` was renamed to + `BundleEntryReaderInterface`. + + Before: + + ``` + class MyEntryReader extends StructuredBundleReader + { + // ... + } + ``` + + After: + + ``` + class MyEntryReader extends BundleEntryReader + { + // ... + } + ``` + + Before: + + ``` + public function __construct(StructuredBundleReaderInterface $entryReader) + { + // ... + } + ``` + + After: + + ``` + public function __construct(BundleEntryReaderInterface $entryReader) + { + // ... + } + ``` + ### Locale * The Locale component was removed and replaced by the Intl component. diff --git a/src/Symfony/Component/Intl/CHANGELOG.md b/src/Symfony/Component/Intl/CHANGELOG.md index 26f68f8d1207d..d55731ee2ee41 100644 --- a/src/Symfony/Component/Intl/CHANGELOG.md +++ b/src/Symfony/Component/Intl/CHANGELOG.md @@ -12,9 +12,11 @@ CHANGELOG names when fallback (e.g. "en_GB"->"en") is possible anyway. This reduced the Resources/ directory file size of the Icu 1.2.x branch from 14M to 12M at the time of this writing - * `StructuredBundleReader` now follows aliases when looking for fallback locales * [BC BREAK] a new method `getLocaleAliases()` was added to `LocaleBundleInterface` + * deprecated `StructuredBundleReader` and `StructuredBundleReaderInterface` in + favor of `BundleEntryReader` and `BundleEntryReaderInterface` + * `BundleEntryReader` now follows aliases when looking for fallback locales * [BC BREAK] changed default value of the argument `$fallback` in the protected method `AbstractBundle::readEntry()` to `true` in order to be consistent with - the proxied `StructuredBundleReaderInterface::readEntry()` method + the proxied `BundleEntryReaderInterface::readEntry()` method * deprecated `BundleCompiler` in favor of `GenrbBundleCompiler` diff --git a/src/Symfony/Component/Intl/Intl.php b/src/Symfony/Component/Intl/Intl.php index 51725844ce14a..1fd006c342b52 100644 --- a/src/Symfony/Component/Intl/Intl.php +++ b/src/Symfony/Component/Intl/Intl.php @@ -17,7 +17,7 @@ use Symfony\Component\Icu\IcuLocaleBundle; use Symfony\Component\Icu\IcuRegionBundle; use Symfony\Component\Intl\ResourceBundle\Reader\BufferedBundleReader; -use Symfony\Component\Intl\ResourceBundle\Reader\StructuredBundleReader; +use Symfony\Component\Intl\ResourceBundle\Reader\BundleEntryReader; /** * Gives access to internationalization data. @@ -63,7 +63,7 @@ class Intl private static $icuDataVersion = false; /** - * @var ResourceBundle\Reader\StructuredBundleReaderInterface + * @var BundleEntryReader */ private static $bundleReader; @@ -206,12 +206,12 @@ public static function getFallbackLocale($locale) /** * Returns a resource bundle reader for .php resource bundle files. * - * @return ResourceBundle\Reader\StructuredBundleReaderInterface The resource reader. + * @return ResourceBundle\Reader\BundleEntryReaderInterface The resource reader. */ private static function getBundleReader() { if (null === self::$bundleReader) { - self::$bundleReader = new StructuredBundleReader(new BufferedBundleReader( + self::$bundleReader = new BundleEntryReader(new BufferedBundleReader( IcuData::getBundleReader(), self::BUFFER_SIZE )); diff --git a/src/Symfony/Component/Intl/ResourceBundle/AbstractBundle.php b/src/Symfony/Component/Intl/ResourceBundle/AbstractBundle.php index 7f2240797b379..ac775ee0b22ef 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/AbstractBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/AbstractBundle.php @@ -46,7 +46,7 @@ public function __construct($path, StructuredBundleReaderInterface $reader) } /** - * Proxy method for {@link StructuredBundleReaderInterface#read}. + * Proxy method for {@link BundleEntryReaderInterface#read}. */ protected function read($locale) { @@ -54,7 +54,7 @@ protected function read($locale) } /** - * Proxy method for {@link StructuredBundleReaderInterface#readEntry}. + * Proxy method for {@link BundleEntryReaderInterface#readEntry}. */ protected function readEntry($locale, array $indices, $fallback = true) { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReader.php new file mode 100644 index 0000000000000..f2d666a6788f3 --- /dev/null +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReader.php @@ -0,0 +1,182 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\ResourceBundle\Reader; + +use Symfony\Component\Intl\Intl; +use Symfony\Component\Intl\Exception\NoSuchEntryException; +use Symfony\Component\Intl\Exception\NoSuchLocaleException; +use Symfony\Component\Intl\Exception\OutOfBoundsException; +use Symfony\Component\Intl\ResourceBundle\Util\RecursiveArrayAccess; + +/** + * A structured reader wrapping an existing resource bundle reader. + * + * @since 2.4 + * @author Bernhard Schussek + * @see BundleEntryReaderInterface + */ +class BundleEntryReader implements BundleEntryReaderInterface, StructuredBundleReaderInterface +{ + /** + * @var BundleReaderInterface + */ + private $reader; + + /** + * A mapping of locale aliases to locales + * + * @var array + */ + private $localeAliases = array(); + + /** + * Creates an entry reader based on the given resource bundle reader. + * + * @param BundleReaderInterface $reader A resource bundle reader to use. + */ + 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 = array(); + + 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 (NoSuchLocaleException $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.'.res'; + + // Check whether fallback is allowed + if (!$fallback) { + break; + } + + // Then determine fallback locale + $currentLocale = Intl::getFallbackLocale($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] from "%s/%s.res".', + implode('][', $indices), + $path, + $locale + ); + + // Append fallback locales, if any + if (count($testedLocales) > 1) { + // Remove original locale + array_shift($testedLocales); + + $errorMessage .= sprintf( + ' The indices also couldn\'t be found in the fallback locale(s) "%s".', + implode('", "', $testedLocales) + ); + } + + throw new NoSuchEntryException($errorMessage, 0, $exception); + } +} diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReaderInterface.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReaderInterface.php new file mode 100644 index 0000000000000..f52ced36ec862 --- /dev/null +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReaderInterface.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\ResourceBundle\Reader; + +/** + * Reads individual entries of a resource file. + * + * @since 2.4 + * @author Bernhard Schussek + */ +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', array('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 Boolean $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, a scalar value for simple data and NULL + * if the given path could not be accessed. + */ + public function readEntry($path, $locale, array $indices, $fallback = true); +} diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReader.php index 5564c38f92d2e..fb5ee300b4c91 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReader.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReader.php @@ -11,172 +11,14 @@ namespace Symfony\Component\Intl\ResourceBundle\Reader; -use Symfony\Component\Intl\Intl; -use Symfony\Component\Intl\Exception\NoSuchEntryException; -use Symfony\Component\Intl\Exception\NoSuchLocaleException; -use Symfony\Component\Intl\Exception\OutOfBoundsException; -use Symfony\Component\Intl\ResourceBundle\Util\RecursiveArrayAccess; - /** - * A structured reader wrapping an existing resource bundle reader. + * Alias of {@link BundleEntryReaderInterface}. * * @author Bernhard Schussek * - * @see StructuredResourceBundleBundleReaderInterface + * @deprecated Deprecated since version 2.4, to be removed in Symfony 3.0. Use + * {@link BundleEntryReader} instead. */ -class StructuredBundleReader implements StructuredBundleReaderInterface +class StructuredBundleReader extends BundleEntryReader { - /** - * @var BundleReaderInterface - */ - private $reader; - - /** - * A mapping of locale aliases to locales - * - * @var array - */ - private $localeAliases = array(); - - /** - * Creates an entry reader based on the given resource bundle reader. - * - * @param BundleReaderInterface $reader A resource bundle reader to use. - */ - 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 = array(); - - 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 (NoSuchLocaleException $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.'.res'; - - // Check whether fallback is allowed - if (!$fallback) { - break; - } - - // Then determine fallback locale - $currentLocale = Intl::getFallbackLocale($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] from "%s/%s.res".', - implode('][', $indices), - $path, - $locale - ); - - // Append fallback locales, if any - if (count($testedLocales) > 1) { - // Remove original locale - array_shift($testedLocales); - - $errorMessage .= sprintf( - ' The indices also couldn\'t be found in the fallback locale(s) "%s".', - implode('", "', $testedLocales) - ); - } - - throw new NoSuchEntryException($errorMessage, 0, $exception); - } } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReaderInterface.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReaderInterface.php index c22ad93b97dad..8978ca3b6e74d 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReaderInterface.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/StructuredBundleReaderInterface.php @@ -12,39 +12,13 @@ namespace Symfony\Component\Intl\ResourceBundle\Reader; /** - * Reads individual entries of a resource file. + * Alias of {@link BundleEntryReaderInterface}. * * @author Bernhard Schussek + * + * @deprecated Deprecated since version 2.4, to be removed in Symfony 3.0. Use + * {@link BundleEntryReaderInterface} instead. */ -interface StructuredBundleReaderInterface extends BundleReaderInterface +interface StructuredBundleReaderInterface extends BundleEntryReaderInterface { - /** - * 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', array('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 Boolean $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, a scalar value for simple data and NULL - * if the given path could not be accessed. - */ - public function readEntry($path, $locale, array $indices, $fallback = true); } diff --git a/src/Symfony/Component/Intl/Resources/bin/update-icu-component.php b/src/Symfony/Component/Intl/Resources/bin/update-icu-component.php index dca7a6e0c3e02..213eeea70a6d2 100644 --- a/src/Symfony/Component/Intl/Resources/bin/update-icu-component.php +++ b/src/Symfony/Component/Intl/Resources/bin/update-icu-component.php @@ -17,7 +17,7 @@ use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\ResourceBundle\Compiler\GenrbBundleCompiler; use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader; -use Symfony\Component\Intl\ResourceBundle\Reader\StructuredBundleReader; +use Symfony\Component\Intl\ResourceBundle\Reader\BundleEntryReader; use Symfony\Component\Intl\ResourceBundle\Scanner\LocaleScanner; use Symfony\Component\Intl\ResourceBundle\Transformer\BundleTransformer; use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContext; @@ -182,7 +182,7 @@ new LocaleScanner() ); -$reader = new StructuredBundleReader(new BinaryBundleReader()); +$reader = new BundleEntryReader(new BinaryBundleReader()); $localeBundle = new IcuLocaleBundle($reader); $languageBundle = new IcuLanguageBundle($reader); diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/StructuredBundleReaderTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BundleEntryReaderTest.php similarity index 99% rename from src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/StructuredBundleReaderTest.php rename to src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BundleEntryReaderTest.php index b64fb129af312..b81065455fcd5 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/StructuredBundleReaderTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BundleEntryReaderTest.php @@ -17,7 +17,7 @@ /** * @author Bernhard Schussek */ -class StructuredBundleReaderTest extends \PHPUnit_Framework_TestCase +class BundleEntryReaderTest extends \PHPUnit_Framework_TestCase { const RES_DIR = '/res/dir'; From c1252ef7b06f8d6b0ee1d49d4706e48bdd515cbf Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 3 Oct 2013 16:59:39 +0200 Subject: [PATCH 25/48] [Intl] Added integration tests to check the resource bundles --- phpunit.xml.dist | 1 + .../Component/Intl/Test/ConsistencyTest.php | 61 +++++ .../CurrencyBundleConsistencyTest.php | 160 +++++++++++ .../LanguageBundleConsistencyTest.php | 259 ++++++++++++++++++ .../LocaleBundleConsistencyTest.php | 117 ++++++++ .../RegionBundleConsistencyTest.php | 196 +++++++++++++ src/Symfony/Component/Intl/phpunit.xml.dist | 6 + 7 files changed, 800 insertions(+) create mode 100644 src/Symfony/Component/Intl/Test/ConsistencyTest.php create mode 100644 src/Symfony/Component/Intl/Tests/ResourceBundle/CurrencyBundleConsistencyTest.php create mode 100644 src/Symfony/Component/Intl/Tests/ResourceBundle/LanguageBundleConsistencyTest.php create mode 100644 src/Symfony/Component/Intl/Tests/ResourceBundle/LocaleBundleConsistencyTest.php create mode 100644 src/Symfony/Component/Intl/Tests/ResourceBundle/RegionBundleConsistencyTest.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 66323e43d16bc..5ce6d483e47ae 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -29,6 +29,7 @@ benchmark + icu-consistency diff --git a/src/Symfony/Component/Intl/Test/ConsistencyTest.php b/src/Symfony/Component/Intl/Test/ConsistencyTest.php new file mode 100644 index 0000000000000..92eaf1295102b --- /dev/null +++ b/src/Symfony/Component/Intl/Test/ConsistencyTest.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Test; + +use Symfony\Component\Intl\Intl; + +/** + * @author Bernhard Schussek + */ +class ConsistencyTest extends \PHPUnit_Framework_TestCase +{ + public function provideLocales() + { + $parameters = array(); + + foreach (Intl::getLocaleBundle()->getLocales() as $locale) { + $parameters[] = array($locale); + } + + return $parameters; + } + + public function provideRootLocales() + { + $parameters = array(); + $locales = Intl::getLocaleBundle()->getLocales(); + $aliases = Intl::getLocaleBundle()->getLocaleAliases(); + + $locales = array_filter($locales, function ($locale) use ($aliases) { + // no aliases + // no locales for which fallback is possible (e.g "en_GB") + return !isset($aliases[$locale]) && false === strpos($locale, '_'); + }); + + foreach ($locales as $locale) { + $parameters[] = array($locale); + } + + return $parameters; + } + + public function provideLocaleAliases() + { + $parameters = array(); + + foreach (Intl::getLocaleBundle()->getLocaleAliases() as $alias => $ofLocale) { + $parameters[] = array($alias, $ofLocale); + } + + return $parameters; + } +} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/CurrencyBundleConsistencyTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/CurrencyBundleConsistencyTest.php new file mode 100644 index 0000000000000..c8207f1f5f2a9 --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/CurrencyBundleConsistencyTest.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Tests\ResourceBundle; + +use Symfony\Component\Intl\Intl; +use Symfony\Component\Intl\Test\ConsistencyTest; + +/** + * @author Bernhard Schussek + */ +class CurrencyBundleConsistencyTest extends ConsistencyTest +{ + // The below arrays document the current state of the ICU data. + // This state is verified in the tests below. + // You can add arbitrary rules here if you want to document the availability + // of other currencies. + private static $localesWithoutTranslationForUSD = array('as', 'bem', 'bo', 'dua', 'dyo', 'eo', 'fo', 'gv', 'ig', 'ii', 'kl', 'kok', 'kw', 'mgh', 'mgo', 'mt', 'nus', 'or', 'pa', 'ps', 'to', 'rw', 'uz', 'yav'); + private static $localesWithoutTranslationForEUR = array('as', 'bem', 'bo', 'dua', 'eo', 'fo', 'gv', 'haw', 'ig', 'ii', 'kok', 'kw', 'mgh', 'mgo', 'nus', 'or', 'pa', 'ps', 'to', 'rw', 'uz'); + private static $localesWithoutTranslationForGBP = array('as', 'bem', 'bo', 'dua', 'dyo', 'eo', 'fo', 'gv', 'haw', 'ig', 'ii', 'jgo', 'kl', 'kok', 'kw', 'mgh', 'mgo', 'mt', 'nus', 'or', 'pa', 'ps', 'so', 'to', 'rw', 'uz'); + private static $localesWithoutTranslationForJPY = array('as', 'bem', 'bo', 'dua', 'eo', 'fo', 'gv', 'haw', 'ig', 'ii', 'jgo', 'kl', 'kok', 'kw', 'mgh', 'mgo', 'mt', 'nus', 'or', 'pa', 'ps', 'so', 'to', 'rw', 'uz'); + private static $localesWithoutTranslationForCNY = array('as', 'bem', 'dua', 'eo', 'fo', 'gv', 'haw', 'ig', 'jgo', 'kl', 'kok', 'kw', 'mgh', 'mgo', 'mt', 'nus', 'or', 'pa', 'ps', 'so', 'to', 'rw', 'uz'); + + /** + * @var \Symfony\Component\Intl\ResourceBundle\CurrencyBundleInterface + */ + private static $currencyBundle; + + public static function setUpBeforeClass() + { + static::$currencyBundle = Intl::getCurrencyBundle(); + } + + public function provideCurrencies() + { + $locales = Intl::getLocaleBundle()->getLocales(); + $aliases = Intl::getLocaleBundle()->getLocaleAliases(); + + // Filter non-root locales + $locales = array_filter($locales, function ($locale) use ($aliases) { + return false === strpos($locale, '_') && !isset($aliases[$locale]); + }); + + $currencies = array(); + $currencyBundle = Intl::getCurrencyBundle(); + + // Merge all currency codes that can be found for any locale + foreach ($locales as $locale) { + $currencies = array_replace($currencies, $currencyBundle->getCurrencyNames($locale)); + } + + $currencies = array_keys($currencies); + $parameters = array(); + + foreach ($currencies as $currency) { + $parameters[] = array($currency); + } + + return $parameters; + } + + /** + * @dataProvider provideRootLocales + */ + public function testGetCurrencyNames($displayLocale) + { + $currencyNames = static::$currencyBundle->getCurrencyNames($displayLocale); + + if (in_array($displayLocale, static::$localesWithoutTranslationForUSD)) { + $this->assertArrayNotHasKey('USD', $currencyNames); + } else { + $this->assertArrayHasKey('USD', $currencyNames); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForEUR)) { + $this->assertArrayNotHasKey('EUR', $currencyNames); + } else { + $this->assertArrayHasKey('EUR', $currencyNames); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForGBP)) { + $this->assertArrayNotHasKey('GBP', $currencyNames); + } else { + $this->assertArrayHasKey('GBP', $currencyNames); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForJPY)) { + $this->assertArrayNotHasKey('JPY', $currencyNames); + } else { + $this->assertArrayHasKey('JPY', $currencyNames); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForCNY)) { + $this->assertArrayNotHasKey('CNY', $currencyNames); + } else { + $this->assertArrayHasKey('CNY', $currencyNames); + } + } + + /** + * @dataProvider provideLocaleAliases + */ + public function testGetCurrencyNamesSupportsAliases($alias, $ofLocale) + { + $this->assertEquals( + static::$currencyBundle->getCurrencyNames($ofLocale), + static::$currencyBundle->getCurrencyNames($alias) + ); + } + + /** + * @dataProvider provideCurrencies + */ + public function testGetFractionDigits($currency) + { + $this->assertTrue(is_numeric(static::$currencyBundle->getFractionDigits($currency))); + } + + /** + * @dataProvider provideCurrencies + */ + public function testGetRoundingIncrement($currency) + { + $this->assertTrue(is_numeric(static::$currencyBundle->getRoundingIncrement($currency))); + } + + /** + * @dataProvider provideLocales + * @group icu-consistency + */ + public function testGetCurrencyNamesAndGetCurrencyNameAreConsistent($displayLocale) + { + $names = static::$currencyBundle->getCurrencyNames($displayLocale); + + foreach ($names as $currency => $name) { + $this->assertSame($name, static::$currencyBundle->getCurrencyName($currency, $displayLocale)); + } + } + + /** + * @dataProvider provideLocales + * @group icu-consistency + */ + public function testGetCurrencyNamesAndGetCurrencySymbolAreConsistent($displayLocale) + { + $names = static::$currencyBundle->getCurrencyNames($displayLocale); + + foreach ($names as $currency => $name) { + $this->assertGreaterThan(0, mb_strlen(static::$currencyBundle->getCurrencySymbol($currency, $displayLocale))); + } + } +} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/LanguageBundleConsistencyTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/LanguageBundleConsistencyTest.php new file mode 100644 index 0000000000000..a1fc92e50c9c7 --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/LanguageBundleConsistencyTest.php @@ -0,0 +1,259 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Tests\ResourceBundle; + +use Symfony\Component\Intl\Exception\NoSuchEntryException; +use Symfony\Component\Intl\Intl; +use Symfony\Component\Intl\Test\ConsistencyTest; + +/** + * @author Bernhard Schussek + */ +class LanguageBundleConsistencyTest extends ConsistencyTest +{ + // The below arrays document the current state of the ICU data. + // This state is verified in the tests below. + // You can add arbitrary rules here if you want to document the availability + // of other languages. + private static $localesWithoutTranslationForThemselves = array('nmg'); + private static $localesWithoutTranslationForEnglish = array('as', 'bo', 'dua', 'fo', 'gv', 'kl', 'kw', 'mgo', 'uz'); + private static $localesWithoutTranslationForFrench = array('as', 'bo', 'dua', 'fo', 'gv', 'kl', 'kw', 'mgo', 'uz'); + private static $localesWithoutTranslationForSpanish = array('as', 'bo', 'dua', 'fo', 'gv', 'jgo', 'kl', 'kw', 'lo', 'mgo', 'ps', 'uz'); + private static $localesWithoutTranslationForRussian = array('as', 'dua', 'fo', 'gv', 'jgo', 'kl', 'kw', 'mgo', 'pa', 'uz'); + private static $localesWithoutTranslationForChinese = array('as', 'dua', 'fo', 'gv', 'kl', 'kw', 'mgo', 'pa', 'rw', 'ti', 'uz'); + private static $localesWithoutTranslationForGerman = array('as', 'bo', 'dua', 'fo', 'gv', 'kl', 'kw', 'mgo', 'uz'); + + private static $localesWithoutTranslationForAnyScript = array( + 'agq', 'ak', 'asa', 'bas', 'bem', 'bez', 'bm', 'cgg', 'dav', 'dje', 'dua', + 'dyo', 'ebu', 'eo', 'ewo', 'ff', 'fo', 'ga', 'guz', 'gv', 'ha', 'haw', + 'ig', 'jmc', 'kab', 'kam', 'kde', 'khq', 'ki', 'kl', 'kln', 'kok', 'ksb', + 'ksf', 'kw', 'lag', 'lg', 'ln', 'lu', 'luo', 'luy', 'mas', 'mer', 'mfe', + 'mg', 'mgh', 'mua', 'naq', 'nd', 'nmg', 'nus', 'nyn', 'pa', 'rn', 'rof', + 'rw', 'rwk', 'saq', 'sbp', 'seh', 'ses', 'sg', 'shi', 'sn', 'swc', 'teo', + 'twq', 'tzm', 'uz', 'vai', 'vun', 'xog', 'yav', 'yo' + ); + + private static $localesWithoutTranslationForLatinScript = array('as', 'bo', 'lo', 'ps', 'so'); + private static $localesWithoutTranslationForSimplifiedScript = array('as', 'bo', 'jgo', 'mgo', 'om', 'ps', 'so', 'sq', 'ti'); + private static $localesWithoutTranslationForTraditionalScript = array('as', 'bo', 'jgo', 'mgo', 'om', 'ps', 'so', 'sq', 'ti'); + private static $localesWithoutTranslationForCyrillicScript = array('as', 'bo', 'jgo', 'lo', 'mgo', 'om', 'ps', 'so', 'mt', 'sq', 'ti'); + + /** + * @var \Symfony\Component\Intl\ResourceBundle\LanguageBundleInterface + */ + private static $languageBundle; + + public static function setUpBeforeClass() + { + static::$languageBundle = Intl::getLanguageBundle(); + } + + public function provideLocalesWithScripts() + { + $parameters = array(); + + $localesWithoutScript = array_flip(static::$localesWithoutTranslationForAnyScript); + $aliasesWithoutScripts = Intl::getLocaleBundle()->getLocaleAliases(); + $aliasesWithoutScripts = array_intersect_assoc($aliasesWithoutScripts, static::$localesWithoutTranslationForAnyScript); + + $locales = Intl::getLocaleBundle()->getLocales(); + + // remove locales that have no "Scripts" block or are an alias to a locale + // without "Scripts" block + $locales = array_filter($locales, function ($locale) use ($localesWithoutScript, $aliasesWithoutScripts) { + while (null !== $locale) { + if (isset($localesWithoutScript[$locale]) || isset($aliasesWithoutScripts[$locale])) { + return false; + } + + $locale = Intl::getFallbackLocale($locale); + } + + return true; + }); + + foreach ($locales as $locale) { + $parameters[] = array($locale); + } + + return $parameters; + } + + public function provideLocaleAliasesWithScripts() + { + $parameters = array(); + + $aliases = Intl::getLocaleBundle()->getLocaleAliases(); + $localesWithoutScript = array_flip(static::$localesWithoutTranslationForAnyScript); + + // Remove aliases that point to a locale without "Scripts" block + $aliases = array_filter($aliases, function ($targetLocale) use ($localesWithoutScript) { + while (null !== $targetLocale) { + if (isset($localesWithoutScript[$targetLocale])) { + return false; + } + + $targetLocale = Intl::getFallbackLocale($targetLocale); + } + + return true; + }); + + foreach ($aliases as $alias => $ofLocale) { + $parameters[] = array($alias, $ofLocale); + } + + return $parameters; + } + + /** + * @dataProvider provideRootLocales + */ + public function testGetLanguageNames($displayLocale) + { + $languageNames = static::$languageBundle->getLanguageNames($displayLocale); + + if (in_array($displayLocale, static::$localesWithoutTranslationForThemselves)) { + $this->assertArrayNotHasKey($displayLocale, $languageNames); + } else { + $this->assertArrayHasKey($displayLocale, $languageNames); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForEnglish)) { + $this->assertArrayNotHasKey('en', $languageNames); + } else { + $this->assertArrayHasKey('en', $languageNames); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForFrench)) { + $this->assertArrayNotHasKey('fr', $languageNames); + } else { + $this->assertArrayHasKey('fr', $languageNames); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForSpanish)) { + $this->assertArrayNotHasKey('es', $languageNames); + } else { + $this->assertArrayHasKey('es', $languageNames); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForRussian)) { + $this->assertArrayNotHasKey('ru', $languageNames); + } else { + $this->assertArrayHasKey('ru', $languageNames); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForChinese)) { + $this->assertArrayNotHasKey('zh', $languageNames); + } else { + $this->assertArrayHasKey('zh', $languageNames); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForGerman)) { + $this->assertArrayNotHasKey('de', $languageNames); + } else { + $this->assertArrayHasKey('de', $languageNames); + } + } + + /** + * @dataProvider provideRootLocales + */ + public function testGetScriptNames($displayLocale) + { + try { + $scriptNames = static::$languageBundle->getScriptNames($displayLocale); + + if (in_array($displayLocale, static::$localesWithoutTranslationForAnyScript)) { + $this->fail('Did not expect any script translations for locale '.$displayLocale); + } + } catch (NoSuchEntryException $e) { + if (in_array($displayLocale, static::$localesWithoutTranslationForAnyScript)) { + return; + } + + throw $e; + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForLatinScript)) { + $this->assertArrayNotHasKey('Latn', $scriptNames); + } else { + $this->assertArrayHasKey('Latn', $scriptNames); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForSimplifiedScript)) { + $this->assertArrayNotHasKey('Hans', $scriptNames); + } else { + $this->assertArrayHasKey('Hans', $scriptNames); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForTraditionalScript)) { + $this->assertArrayNotHasKey('Hant', $scriptNames); + } else { + $this->assertArrayHasKey('Hant', $scriptNames); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForCyrillicScript)) { + $this->assertArrayNotHasKey('Cyrl', $scriptNames); + } else { + $this->assertArrayHasKey('Cyrl', $scriptNames); + } + } + + /** + * @dataProvider provideLocaleAliases + */ + public function testGetLanguageNamesSupportsAliases($alias, $ofLocale) + { + $this->assertEquals( + static::$languageBundle->getLanguageNames($ofLocale), + static::$languageBundle->getLanguageNames($alias) + ); + } + + /** + * @dataProvider provideLocaleAliasesWithScripts + */ + public function testGetScriptNamesSupportsAliases($alias, $ofLocale) + { + $this->assertEquals( + static::$languageBundle->getScriptNames($ofLocale), + static::$languageBundle->getScriptNames($alias) + ); + } + + /** + * @dataProvider provideLocales + * @group icu-consistency + */ + public function testGetLanguageNamesAndGetLanguageNameAreConsistent($displayLocale) + { + $names = static::$languageBundle->getLanguageNames($displayLocale); + + foreach ($names as $language => $name) { + $this->assertSame($name, static::$languageBundle->getLanguageName($language, null, $displayLocale)); + } + } + + /** + * @dataProvider provideLocalesWithScripts + * @group icu-consistency + */ + public function testGetScriptNamesAndGetScriptNameAreConsistent($displayLocale) + { + $names = static::$languageBundle->getScriptNames($displayLocale); + + foreach ($names as $script => $name) { + $this->assertSame($name, static::$languageBundle->getScriptName($script, null, $displayLocale)); + } + } +} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/LocaleBundleConsistencyTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/LocaleBundleConsistencyTest.php new file mode 100644 index 0000000000000..7f7c7a387248d --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/LocaleBundleConsistencyTest.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Tests\ResourceBundle; + +use Symfony\Component\Intl\Intl; +use Symfony\Component\Intl\Test\ConsistencyTest; + +/** + * @author Bernhard Schussek + */ +class LocaleBundleConsistencyTest extends ConsistencyTest +{ + // The below arrays document the current state of the ICU data. + // This state is verified in the tests below. + // You can add arbitrary rules here if you want to document the availability + // of other locale names. + private static $localesWithoutTranslationForThemselves = array('nmg'); + private static $localesWithoutTranslationForEnglish = array('as', 'bo', 'dua', 'fo', 'gv', 'kl', 'kw', 'mgo', 'uz'); + private static $localesWithoutTranslationForFrench = array('as', 'bo', 'dua', 'fo', 'gv', 'kl', 'kw', 'mgo', 'uz'); + private static $localesWithoutTranslationForSpanish = array('as', 'bo', 'dua', 'fo', 'gv', 'jgo', 'kl', 'kw', 'lo', 'mgo', 'ps', 'uz'); + private static $localesWithoutTranslationForRussian = array('as', 'dua', 'fo', 'gv', 'jgo', 'kl', 'kw', 'mgo', 'pa', 'uz'); + private static $localesWithoutTranslationForChinese = array('as', 'dua', 'fo', 'gv', 'kl', 'kw', 'mgo', 'pa', 'rw', 'ti', 'uz'); + private static $localesWithoutTranslationForGerman = array('as', 'bo', 'dua', 'fo', 'gv', 'kl', 'kw', 'mgo', 'uz'); + + /** + * @var \Symfony\Component\Intl\ResourceBundle\LocaleBundleInterface + */ + private static $localeBundle; + + public static function setUpBeforeClass() + { + static::$localeBundle = Intl::getLocaleBundle(); + } + + /** + * @dataProvider provideRootLocales + */ + public function testGetLocaleNames($displayLocale) + { + $locales = static::$localeBundle->getLocaleNames($displayLocale); + + if (in_array($displayLocale, static::$localesWithoutTranslationForThemselves)) { + $this->assertArrayNotHasKey($displayLocale, $locales); + } else { + $this->assertArrayHasKey($displayLocale, $locales); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForEnglish)) { + $this->assertArrayNotHasKey('en', $locales); + } else { + $this->assertArrayHasKey('en', $locales); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForFrench)) { + $this->assertArrayNotHasKey('fr', $locales); + } else { + $this->assertArrayHasKey('fr', $locales); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForSpanish)) { + $this->assertArrayNotHasKey('es', $locales); + } else { + $this->assertArrayHasKey('es', $locales); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForRussian)) { + $this->assertArrayNotHasKey('ru', $locales); + } else { + $this->assertArrayHasKey('ru', $locales); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForChinese)) { + $this->assertArrayNotHasKey('zh', $locales); + } else { + $this->assertArrayHasKey('zh', $locales); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForGerman)) { + $this->assertArrayNotHasKey('de', $locales); + } else { + $this->assertArrayHasKey('de', $locales); + } + } + + /** + * @dataProvider provideLocaleAliases + */ + public function testGetLocaleNamesSupportsAliases($alias, $ofLocale) + { + $this->assertEquals( + static::$localeBundle->getLocaleNames($ofLocale), + static::$localeBundle->getLocaleNames($alias) + ); + } + + /** + * @dataProvider provideLocales + * @group icu-consistency + */ + public function testGetLocaleNamesAndGetLocaleNameAreConsistent($displayLocale) + { + $names = static::$localeBundle->getLocaleNames($displayLocale); + + foreach ($names as $locale => $name) { + $this->assertSame($name, static::$localeBundle->getLocaleName($locale, $displayLocale)); + } + } +} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/RegionBundleConsistencyTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/RegionBundleConsistencyTest.php new file mode 100644 index 0000000000000..a7c1e8fb21f56 --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/RegionBundleConsistencyTest.php @@ -0,0 +1,196 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Tests\ResourceBundle; + +use Symfony\Component\Intl\Exception\NoSuchEntryException; +use Symfony\Component\Intl\Intl; +use Symfony\Component\Intl\Test\ConsistencyTest; + +/** + * @author Bernhard Schussek + */ +class RegionBundleConsistencyTest extends ConsistencyTest +{ + // The below arrays document the current state of the ICU data. + // This state is verified in the tests below. + // You can add arbitrary rules here if you want to document the availability + // of other countries. + private static $localesWithoutTranslationForAnyCountry = array('ti'); + private static $localesWithoutTranslationForUS = array('bem', 'dua', 'dyo', 'gv', 'ig', 'jgo', 'kl', 'kok', 'kw', 'mgo', 'nus', 'pa', 'ps', 'rw', 'uz'); + private static $localesWithoutTranslationForDE = array('bem', 'dua', 'gv', 'ig', 'kl', 'kok', 'kw', 'mgh', 'mgo', 'nus', 'pa', 'rw', 'uz'); + private static $localesWithoutTranslationForGB = array('bem', 'dua', 'dyo', 'fo', 'ig', 'jgo', 'kl', 'kok', 'mgh', 'mgo', 'nus', 'pa', 'rw', 'uz'); + private static $localesWithoutTranslationForFR = array('bem', 'bo', 'dua', 'gv', 'ig', 'kl', 'kok', 'kw', 'mgo', 'nus', 'pa', 'rw', 'uz'); + private static $localesWithoutTranslationForIT = array('bem', 'dua', 'gv', 'ig', 'kl', 'kok', 'kw', 'mgo', 'nus', 'pa', 'rw', 'uz'); + private static $localesWithoutTranslationForBR = array('bem', 'bo', 'dua', 'gv', 'haw', 'ig', 'kl', 'kok', 'kw', 'mgh', 'mgo', 'pa', 'ps', 'rw', 'uz'); + private static $localesWithoutTranslationForRU = array('bem', 'dua', 'dyo', 'gv', 'ig', 'kl', 'kok', 'kw', 'mgh', 'mgo', 'nus', 'pa', 'rw', 'uz'); + private static $localesWithoutTranslationForCN = array('bem', 'dua', 'gv', 'kl', 'kok', 'kw', 'mgo', 'pa', 'rw', 'uz'); + + /** + * @var \Symfony\Component\Intl\ResourceBundle\RegionBundleInterface + */ + private static $regionBundle; + + public static function setUpBeforeClass() + { + static::$regionBundle = Intl::getRegionBundle(); + } + + public function provideLocalesWithCountries() + { + $parameters = array(); + + $localesWithoutScript = array_flip(static::$localesWithoutTranslationForAnyCountry); + $aliasesWithoutCountries = Intl::getLocaleBundle()->getLocaleAliases(); + $aliasesWithoutCountries = array_intersect_assoc($aliasesWithoutCountries, static::$localesWithoutTranslationForAnyCountry); + + $locales = Intl::getLocaleBundle()->getLocales(); + + // remove locales that have no "Countries" block or are an alias to a locale + // without "Countries" block + $locales = array_filter($locales, function ($locale) use ($localesWithoutScript, $aliasesWithoutCountries) { + while (null !== $locale) { + if (isset($localesWithoutScript[$locale]) || isset($aliasesWithoutCountries[$locale])) { + return false; + } + + $locale = Intl::getFallbackLocale($locale); + } + + return true; + }); + + foreach ($locales as $locale) { + $parameters[] = array($locale); + } + + return $parameters; + } + + public function provideLocaleAliasesWithCountries() + { + $parameters = array(); + + $aliases = Intl::getLocaleBundle()->getLocaleAliases(); + $localesWithoutScript = array_flip(static::$localesWithoutTranslationForAnyCountry); + + // Remove aliases that point to a locale without "Countries" block + $aliases = array_filter($aliases, function ($targetLocale) use ($localesWithoutScript) { + while (null !== $targetLocale) { + if (isset($localesWithoutScript[$targetLocale])) { + return false; + } + + $targetLocale = Intl::getFallbackLocale($targetLocale); + } + + return true; + }); + + foreach ($aliases as $alias => $ofLocale) { + $parameters[] = array($alias, $ofLocale); + } + + return $parameters; + } + + /** + * @dataProvider provideRootLocales + */ + public function testGetCountryNames($displayLocale) + { + try { + $countryNames = static::$regionBundle->getCountryNames($displayLocale); + + if (in_array($displayLocale, static::$localesWithoutTranslationForAnyCountry)) { + $this->fail('Did not expect any country translations for locale '.$displayLocale); + } + } catch (NoSuchEntryException $e) { + if (in_array($displayLocale, static::$localesWithoutTranslationForAnyCountry)) { + return; + } + + throw $e; + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForUS)) { + $this->assertArrayNotHasKey('US', $countryNames); + } else { + $this->assertArrayHasKey('US', $countryNames); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForDE)) { + $this->assertArrayNotHasKey('DE', $countryNames); + } else { + $this->assertArrayHasKey('DE', $countryNames); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForGB)) { + $this->assertArrayNotHasKey('GB', $countryNames); + } else { + $this->assertArrayHasKey('GB', $countryNames); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForFR)) { + $this->assertArrayNotHasKey('FR', $countryNames); + } else { + $this->assertArrayHasKey('FR', $countryNames); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForIT)) { + $this->assertArrayNotHasKey('IT', $countryNames); + } else { + $this->assertArrayHasKey('IT', $countryNames); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForBR)) { + $this->assertArrayNotHasKey('BR', $countryNames); + } else { + $this->assertArrayHasKey('BR', $countryNames); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForRU)) { + $this->assertArrayNotHasKey('RU', $countryNames); + } else { + $this->assertArrayHasKey('RU', $countryNames); + } + + if (in_array($displayLocale, static::$localesWithoutTranslationForCN)) { + $this->assertArrayNotHasKey('CN', $countryNames); + } else { + $this->assertArrayHasKey('CN', $countryNames); + } + } + + /** + * @dataProvider provideLocaleAliasesWithCountries + */ + public function testGetCountryNamesSupportsAliases($alias, $ofLocale) + { + $this->assertEquals( + static::$regionBundle->getCountryNames($ofLocale), + static::$regionBundle->getCountryNames($alias) + ); + } + + /** + * @dataProvider provideLocalesWithCountries + * @group icu-consistency + */ + public function testGetCountryNamesAndGetCountryNameAreConsistent($displayLocale) + { + $names = static::$regionBundle->getCountryNames($displayLocale); + + foreach ($names as $country => $name) { + $this->assertSame($name, static::$regionBundle->getCountryName($country, $displayLocale)); + } + } +} diff --git a/src/Symfony/Component/Intl/phpunit.xml.dist b/src/Symfony/Component/Intl/phpunit.xml.dist index 5e709f137f60a..21c67fc622604 100644 --- a/src/Symfony/Component/Intl/phpunit.xml.dist +++ b/src/Symfony/Component/Intl/phpunit.xml.dist @@ -17,6 +17,12 @@ + + + icu-consistency + + + ./ From a014f7d3471319371fce1a1f5342c8777486b26c Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Fri, 4 Oct 2013 10:55:16 +0200 Subject: [PATCH 26/48] [Intl] Fixed TextBundleWriter to escape keys that contain colons (":") --- src/Symfony/Component/Intl/CHANGELOG.md | 1 + .../Writer/TextBundleWriter.php | 6 ++++++ .../Writer/Fixtures/escaped.txt | 3 +++ .../Writer/TextBundleWriterTest.php | 19 +++++++++++++++---- 4 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/escaped.txt diff --git a/src/Symfony/Component/Intl/CHANGELOG.md b/src/Symfony/Component/Intl/CHANGELOG.md index d55731ee2ee41..a1b102b3ccc45 100644 --- a/src/Symfony/Component/Intl/CHANGELOG.md +++ b/src/Symfony/Component/Intl/CHANGELOG.md @@ -20,3 +20,4 @@ CHANGELOG method `AbstractBundle::readEntry()` to `true` in order to be consistent with the proxied `BundleEntryReaderInterface::readEntry()` method * deprecated `BundleCompiler` in favor of `GenrbBundleCompiler` + * fixed `TextBundleWriter` to correctly escape keys that contain colons (":") diff --git a/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php b/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php index 08bf0a27a5d48..c95718decba89 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php @@ -212,6 +212,12 @@ private function writeTable($file, $value, $indentation, $fallback = true) foreach ($value as $key => $entry) { fwrite($file, str_repeat(' ', $indentation + 1)); + + // escape colons, otherwise they are interpreted as resource types + if (false !== strpos($key, ':')) { + $key = '"'.$key.'"'; + } + fwrite($file, $key); $this->writeResource($file, $entry, $indentation + 1); diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/escaped.txt b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/escaped.txt new file mode 100644 index 0000000000000..149b9fa44a78a --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/escaped.txt @@ -0,0 +1,3 @@ +escaped{ + "EntryWith:Colon"{"Value"} +} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php index 5595035818dab..7dbbf66fff712 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php @@ -36,7 +36,7 @@ class TextBundleWriterTest extends \PHPUnit_Framework_TestCase protected function setUp() { $this->writer = new TextBundleWriter(); - $this->directory = sys_get_temp_dir() . '/TextBundleWriterTest/' . rand(1000, 9999); + $this->directory = sys_get_temp_dir().'/TextBundleWriterTest/'.rand(1000, 9999); $this->filesystem = new Filesystem(); $this->filesystem->mkdir($this->directory); @@ -62,7 +62,7 @@ public function testWrite() 'Entry2' => 'String', )); - $this->assertFileEquals(__DIR__ . '/Fixtures/en.txt', $this->directory . '/en.txt'); + $this->assertFileEquals(__DIR__.'/Fixtures/en.txt', $this->directory.'/en.txt'); } public function testWriteTraversable() @@ -80,7 +80,7 @@ public function testWriteTraversable() 'Entry2' => 'String', ))); - $this->assertFileEquals(__DIR__ . '/Fixtures/en.txt', $this->directory . '/en.txt'); + $this->assertFileEquals(__DIR__.'/Fixtures/en.txt', $this->directory.'/en.txt'); } public function testWriteNoFallback() @@ -91,6 +91,17 @@ public function testWriteNoFallback() $this->writer->write($this->directory, 'en_nofallback', $data, $fallback = false); - $this->assertFileEquals(__DIR__ . '/Fixtures/en_nofallback.txt', $this->directory . '/en_nofallback.txt'); + $this->assertFileEquals(__DIR__.'/Fixtures/en_nofallback.txt', $this->directory.'/en_nofallback.txt'); + } + + public function testEscapeKeysIfNecessary() + { + $this->writer->write($this->directory, 'escaped', array( + // Keys with colons must be escaped, otherwise the part after the + // colon is interpreted as resource type + 'EntryWith:Colon' => 'Value', + )); + + $this->assertFileEquals(__DIR__.'/Fixtures/escaped.txt', $this->directory.'/escaped.txt'); } } From 7e36a29098722d0eb57e110e39386521c06ba67a Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Fri, 4 Oct 2013 10:55:35 +0200 Subject: [PATCH 27/48] [Intl] Fixed transformation rules not to generate files in the source data directories --- .../Rule/CurrencyBundleTransformationRule.php | 33 ++++++++----------- .../Rule/LanguageBundleTransformationRule.php | 11 +++++-- .../Rule/RegionBundleTransformationRule.php | 11 +++++-- 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php index 40e1127f80a29..f8efdaf9acd73 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php @@ -50,30 +50,25 @@ public function getBundleName() */ public function beforeCompile(CompilationContextInterface $context) { - $tempDir = sys_get_temp_dir().'/icu-data-currencies-source'; + $tempDir = sys_get_temp_dir().'/icu-data-currencies'; + + $context->getFilesystem()->remove($tempDir); + $context->getFilesystem()->mkdir(array($tempDir, $tempDir.'/res')); // The currency data is contained in the locales and misc bundles // in ICU <= 4.2 if (IcuVersion::compare($context->getIcuVersion(), '4.2', '<=', 1)) { - $supplementalData = $context->getSourceDir().'/misc/supplementalData.txt'; - $sourceDir = $context->getSourceDir().'/locales'; + $context->getFilesystem()->mirror($context->getSourceDir().'/locales', $tempDir.'/txt'); + $context->getFilesystem()->copy($context->getSourceDir().'/misc/supplementalData.txt', $tempDir.'/txt/misc.txt'); } else { - $supplementalData = $context->getSourceDir().'/curr/supplementalData.txt'; - $sourceDir = $context->getSourceDir().'/curr'; + $context->getFilesystem()->mirror($context->getSourceDir().'/curr', $tempDir.'/txt'); + $context->getFilesystem()->rename($tempDir.'/txt/supplementalData.txt', $tempDir.'/txt/misc.txt'); } - $context->getFilesystem()->remove($tempDir); - $context->getFilesystem()->mkdir(array( - $tempDir, - $tempDir.'/txt', - $tempDir.'/res' - )); - $context->getFilesystem()->copy($supplementalData, $tempDir.'/txt/misc.txt'); - // Replace "supplementalData" in the file by "misc" before compilation file_put_contents($tempDir.'/txt/misc.txt', str_replace('supplementalData', 'misc', file_get_contents($tempDir.'/txt/misc.txt'))); - $context->getCompiler()->compile($tempDir.'/txt', $tempDir.'/res'); + $context->getCompiler()->compile($tempDir.'/txt/misc.txt', $tempDir.'/res'); // Read file, add locales and write again $reader = new BinaryBundleReader(); @@ -82,12 +77,13 @@ public function beforeCompile(CompilationContextInterface $context) // Key must not exist assert(!isset($data['Locales'])); - $data['Locales'] = $context->getLocaleScanner()->scanLocales($sourceDir); + $data['Locales'] = $context->getLocaleScanner()->scanLocales($tempDir.'/txt'); $writer = new TextBundleWriter(); - $writer->write($sourceDir, 'misc', $data, false); + $writer->write($tempDir.'/txt', 'misc', $data, false); - return $sourceDir; + // The temporary directory now contains all sources to be compiled + return $tempDir.'/txt'; } /** @@ -95,9 +91,6 @@ public function beforeCompile(CompilationContextInterface $context) */ public function afterCompile(CompilationContextInterface $context) { - // Remove supplementalData.res, whose content is contained within misc.res - $context->getFilesystem()->remove($context->getBinaryDir().'/curr/supplementalData.res'); - // Remove the temporary directory $context->getFilesystem()->remove(sys_get_temp_dir().'/icu-data-currencies-source'); } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php index 9559d860e8c0c..f6d202b7c6e75 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php @@ -48,6 +48,8 @@ public function getBundleName() */ public function beforeCompile(CompilationContextInterface $context) { + $tempDir = sys_get_temp_dir().'/icu-data-languages'; + // The language data is contained in the locales bundle in ICU <= 4.2 if (IcuVersion::compare($context->getIcuVersion(), '4.2', '<=', 1)) { $sourceDir = $context->getSourceDir() . '/locales'; @@ -55,13 +57,16 @@ public function beforeCompile(CompilationContextInterface $context) $sourceDir = $context->getSourceDir() . '/lang'; } + $context->getFilesystem()->remove($tempDir); + $context->getFilesystem()->mirror($sourceDir, $tempDir); + // Create misc file with all available locales $writer = new TextBundleWriter(); - $writer->write($sourceDir, 'misc', array( - 'Locales' => $context->getLocaleScanner()->scanLocales($sourceDir), + $writer->write($tempDir, 'misc', array( + 'Locales' => $context->getLocaleScanner()->scanLocales($tempDir), ), false); - return $sourceDir; + return $tempDir; } /** diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/RegionBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/RegionBundleTransformationRule.php index 77324327cff7a..b383ceff0520d 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/RegionBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/RegionBundleTransformationRule.php @@ -48,6 +48,8 @@ public function getBundleName() */ public function beforeCompile(CompilationContextInterface $context) { + $tempDir = sys_get_temp_dir().'/icu-data-languages'; + // The region data is contained in the locales bundle in ICU <= 4.2 if (IcuVersion::compare($context->getIcuVersion(), '4.2', '<=', 1)) { $sourceDir = $context->getSourceDir() . '/locales'; @@ -55,13 +57,16 @@ public function beforeCompile(CompilationContextInterface $context) $sourceDir = $context->getSourceDir() . '/region'; } + $context->getFilesystem()->remove($tempDir); + $context->getFilesystem()->mirror($sourceDir, $tempDir); + // Create misc file with all available locales $writer = new TextBundleWriter(); - $writer->write($sourceDir, 'misc', array( - 'Locales' => $context->getLocaleScanner()->scanLocales($sourceDir), + $writer->write($tempDir, 'misc', array( + 'Locales' => $context->getLocaleScanner()->scanLocales($tempDir), ), false); - return $sourceDir; + return $tempDir; } /** From a2b31f6bbf3c1c93b0f7a3e40a160c8a73f1fb5d Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Fri, 4 Oct 2013 11:46:08 +0200 Subject: [PATCH 28/48] [Intl] Fixed TextBundleWriter to escape keys with spaces (" ") --- src/Symfony/Component/Intl/CHANGELOG.md | 1 + .../Component/Intl/ResourceBundle/Writer/TextBundleWriter.php | 2 +- .../Intl/Tests/ResourceBundle/Writer/Fixtures/escaped.txt | 1 + .../Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php | 2 ++ 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Intl/CHANGELOG.md b/src/Symfony/Component/Intl/CHANGELOG.md index a1b102b3ccc45..138fb17629275 100644 --- a/src/Symfony/Component/Intl/CHANGELOG.md +++ b/src/Symfony/Component/Intl/CHANGELOG.md @@ -21,3 +21,4 @@ CHANGELOG the proxied `BundleEntryReaderInterface::readEntry()` method * deprecated `BundleCompiler` in favor of `GenrbBundleCompiler` * fixed `TextBundleWriter` to correctly escape keys that contain colons (":") + * fixed `TextBundleWriter` to correctly escape keys that contain spaces (" ") diff --git a/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php b/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php index c95718decba89..563b3ba141ab5 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php @@ -214,7 +214,7 @@ private function writeTable($file, $value, $indentation, $fallback = true) fwrite($file, str_repeat(' ', $indentation + 1)); // escape colons, otherwise they are interpreted as resource types - if (false !== strpos($key, ':')) { + if (false !== strpos($key, ':') || false !== strpos($key, ' ')) { $key = '"'.$key.'"'; } diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/escaped.txt b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/escaped.txt index 149b9fa44a78a..6669bfdd8306e 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/escaped.txt +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/escaped.txt @@ -1,3 +1,4 @@ escaped{ "EntryWith:Colon"{"Value"} + "Entry With Spaces"{"Value"} } diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php index 7dbbf66fff712..f55497a622966 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php @@ -100,6 +100,8 @@ public function testEscapeKeysIfNecessary() // Keys with colons must be escaped, otherwise the part after the // colon is interpreted as resource type 'EntryWith:Colon' => 'Value', + // Keys with spaces must be escaped + 'Entry With Spaces' => 'Value', )); $this->assertFileEquals(__DIR__.'/Fixtures/escaped.txt', $this->directory.'/escaped.txt'); From 9b34d51ade8f7d5e3c914317b3b9c951b5105d73 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Fri, 4 Oct 2013 12:42:57 +0200 Subject: [PATCH 29/48] [Intl] Removed the interfaces CompilationContextInterface and StubbingContextInterface --- UPGRADE-2.4.md | 38 +++++++++++ src/Symfony/Component/Intl/CHANGELOG.md | 2 + .../Transformer/BundleTransformer.php | 8 +-- .../Transformer/CompilationContext.php | 29 ++++++--- .../CompilationContextInterface.php | 63 ------------------- .../Rule/CurrencyBundleTransformationRule.php | 12 ++-- .../Rule/LanguageBundleTransformationRule.php | 12 ++-- .../Rule/LocaleBundleTransformationRule.php | 12 ++-- .../Rule/RegionBundleTransformationRule.php | 12 ++-- .../Rule/TransformationRuleInterface.php | 20 +++--- .../Transformer/StubbingContext.php | 20 ++++-- .../Transformer/StubbingContextInterface.php | 46 -------------- 12 files changed, 114 insertions(+), 160 deletions(-) delete mode 100644 src/Symfony/Component/Intl/ResourceBundle/Transformer/CompilationContextInterface.php delete mode 100644 src/Symfony/Component/Intl/ResourceBundle/Transformer/StubbingContextInterface.php diff --git a/UPGRADE-2.4.md b/UPGRADE-2.4.md index 9f9bdfe46d9d0..aa7679e5e5cd3 100644 --- a/UPGRADE-2.4.md +++ b/UPGRADE-2.4.md @@ -94,3 +94,41 @@ Intl } } ``` + + * The interfaces `CompilationContextInterface` and `StubbingContextInterface` + were removed. Code against their implementations `CompilationContext` and + `StubbingContext` in the same namespace instead. + + Before: + + ``` + use Symfony\Component\Intl\ResourceBundle\Transformation\CompilationContextInterface; + use Symfony\Component\Intl\ResourceBundle\Transformation\StubbingContextInterface; + + public function beforeCompile(CompilationContextInterface $context) + { + // ... + } + + public function beforeCreateStub(StubbingContextInterface $context) + { + // ... + } + ``` + + After: + + ``` + use Symfony\Component\Intl\ResourceBundle\Transformation\CompilationContext; + use Symfony\Component\Intl\ResourceBundle\Transformation\StubbingContext; + + public function beforeCompile(CompilationContext $context) + { + // ... + } + + public function beforeCreateStub(StubbingContext $context) + { + // ... + } + ``` diff --git a/src/Symfony/Component/Intl/CHANGELOG.md b/src/Symfony/Component/Intl/CHANGELOG.md index 138fb17629275..00021602a06be 100644 --- a/src/Symfony/Component/Intl/CHANGELOG.md +++ b/src/Symfony/Component/Intl/CHANGELOG.md @@ -22,3 +22,5 @@ CHANGELOG * deprecated `BundleCompiler` in favor of `GenrbBundleCompiler` * fixed `TextBundleWriter` to correctly escape keys that contain colons (":") * fixed `TextBundleWriter` to correctly escape keys that contain spaces (" ") + * [BC BREAK] removed the pointless interfaces `CompilationContextInterface` + and `StubbingContextInterface`. You can depend on their implementation instead diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/BundleTransformer.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/BundleTransformer.php index 0692d6fe50a05..1c5b874cdd0d7 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/BundleTransformer.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/BundleTransformer.php @@ -40,13 +40,13 @@ public function addRule(TransformationRuleInterface $rule) /** * Runs the compilation with the given compilation context. * - * @param CompilationContextInterface $context The context storing information - * needed to run the compilation. + * @param CompilationContext $context The context storing information + * needed to run the compilation. * * @throws RuntimeException If any of the files to be compiled by the loaded * compilation rules does not exist. */ - public function compileBundles(CompilationContextInterface $context) + public function compileBundles(CompilationContext $context) { $filesystem = $context->getFilesystem(); $compiler = $context->getCompiler(); @@ -75,7 +75,7 @@ public function compileBundles(CompilationContextInterface $context) } } - public function createStubs(StubbingContextInterface $context) + public function createStubs(StubbingContext $context) { $filesystem = $context->getFilesystem(); $phpWriter = new PhpBundleWriter(); diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/CompilationContext.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/CompilationContext.php index fc71bf58bf98c..bc6ede8aa401b 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/CompilationContext.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/CompilationContext.php @@ -16,11 +16,11 @@ use Symfony\Component\Intl\ResourceBundle\Scanner\LocaleScanner; /** - * Default implementation of {@link CompilationContextInterface}. + * Stores contextual information for resource bundle compilation. * * @author Bernhard Schussek */ -class CompilationContext implements CompilationContextInterface +class CompilationContext { /** * @var string @@ -63,7 +63,10 @@ public function __construct($sourceDir, $binaryDir, Filesystem $filesystem, Bund } /** - * {@inheritdoc} + * Returns the directory where the source versions of the resource bundles + * are stored. + * + * @return string An absolute path to a directory. */ public function getSourceDir() { @@ -71,7 +74,9 @@ public function getSourceDir() } /** - * {@inheritdoc} + * Returns the directory where the binary resource bundles are stored. + * + * @return string An absolute path to a directory. */ public function getBinaryDir() { @@ -79,7 +84,9 @@ public function getBinaryDir() } /** - * {@inheritdoc} + * Returns a tool for manipulating the filesystem. + * + * @return \Symfony\Component\Filesystem\Filesystem The filesystem manipulator. */ public function getFilesystem() { @@ -87,7 +94,9 @@ public function getFilesystem() } /** - * {@inheritdoc} + * Returns a resource bundle compiler. + * + * @return \Symfony\Component\Intl\ResourceBundle\Compiler\BundleCompilerInterface The loaded resource bundle compiler. */ public function getCompiler() { @@ -95,7 +104,9 @@ public function getCompiler() } /** - * {@inheritdoc} + * Returns the ICU version of the bundles being converted. + * + * @return string The ICU version string. */ public function getIcuVersion() { @@ -103,7 +114,9 @@ public function getIcuVersion() } /** - * {@inheritdoc} + * Returns a locale scanner. + * + * @return \Symfony\Component\Intl\ResourceBundle\Scanner\LocaleScanner The locale scanner. */ public function getLocaleScanner() { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/CompilationContextInterface.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/CompilationContextInterface.php deleted file mode 100644 index 06e8d9c6a8ae2..0000000000000 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/CompilationContextInterface.php +++ /dev/null @@ -1,63 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Intl\ResourceBundle\Transformer; - -/** - * Stores contextual information for resource bundle compilation. - * - * @author Bernhard Schussek - */ -interface CompilationContextInterface -{ - /** - * Returns the directory where the source versions of the resource bundles - * are stored. - * - * @return string An absolute path to a directory. - */ - public function getSourceDir(); - - /** - * Returns the directory where the binary resource bundles are stored. - * - * @return string An absolute path to a directory. - */ - public function getBinaryDir(); - - /** - * Returns a tool for manipulating the filesystem. - * - * @return \Symfony\Component\Filesystem\Filesystem The filesystem manipulator. - */ - public function getFilesystem(); - - /** - * Returns a resource bundle compiler. - * - * @return \Symfony\Component\Intl\ResourceBundle\Compiler\BundleCompilerInterface The loaded resource bundle compiler. - */ - public function getCompiler(); - - /** - * Returns the ICU version of the bundles being converted. - * - * @return string The ICU version string. - */ - public function getIcuVersion(); - - /** - * Returns a locale scanner. - * - * @return \Symfony\Component\Intl\ResourceBundle\Scanner\LocaleScanner The locale scanner. - */ - public function getLocaleScanner(); -} diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php index f8efdaf9acd73..ad5e24823ae92 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php @@ -15,8 +15,8 @@ use Symfony\Component\Intl\ResourceBundle\CurrencyBundle; use Symfony\Component\Intl\ResourceBundle\CurrencyBundleInterface; use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader; -use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContextInterface; -use Symfony\Component\Intl\ResourceBundle\Transformer\StubbingContextInterface; +use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContext; +use Symfony\Component\Intl\ResourceBundle\Transformer\StubbingContext; use Symfony\Component\Intl\ResourceBundle\Writer\TextBundleWriter; use Symfony\Component\Intl\Util\IcuVersion; @@ -48,7 +48,7 @@ public function getBundleName() /** * {@inheritdoc} */ - public function beforeCompile(CompilationContextInterface $context) + public function beforeCompile(CompilationContext $context) { $tempDir = sys_get_temp_dir().'/icu-data-currencies'; @@ -89,7 +89,7 @@ public function beforeCompile(CompilationContextInterface $context) /** * {@inheritdoc} */ - public function afterCompile(CompilationContextInterface $context) + public function afterCompile(CompilationContext $context) { // Remove the temporary directory $context->getFilesystem()->remove(sys_get_temp_dir().'/icu-data-currencies-source'); @@ -98,7 +98,7 @@ public function afterCompile(CompilationContextInterface $context) /** * {@inheritdoc} */ - public function beforeCreateStub(StubbingContextInterface $context) + public function beforeCreateStub(StubbingContext $context) { $currencies = array(); @@ -119,7 +119,7 @@ public function beforeCreateStub(StubbingContextInterface $context) /** * {@inheritdoc} */ - public function afterCreateStub(StubbingContextInterface $context) + public function afterCreateStub(StubbingContext $context) { } } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php index f6d202b7c6e75..47e65a1cfd528 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php @@ -13,8 +13,8 @@ use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\ResourceBundle\LanguageBundleInterface; -use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContextInterface; -use Symfony\Component\Intl\ResourceBundle\Transformer\StubbingContextInterface; +use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContext; +use Symfony\Component\Intl\ResourceBundle\Transformer\StubbingContext; use Symfony\Component\Intl\ResourceBundle\Writer\TextBundleWriter; use Symfony\Component\Intl\Util\IcuVersion; @@ -46,7 +46,7 @@ public function getBundleName() /** * {@inheritdoc} */ - public function beforeCompile(CompilationContextInterface $context) + public function beforeCompile(CompilationContext $context) { $tempDir = sys_get_temp_dir().'/icu-data-languages'; @@ -72,14 +72,14 @@ public function beforeCompile(CompilationContextInterface $context) /** * {@inheritdoc} */ - public function afterCompile(CompilationContextInterface $context) + public function afterCompile(CompilationContext $context) { } /** * {@inheritdoc} */ - public function beforeCreateStub(StubbingContextInterface $context) + public function beforeCreateStub(StubbingContext $context) { return array( 'Languages' => $this->languageBundle->getLanguageNames('en'), @@ -90,7 +90,7 @@ public function beforeCreateStub(StubbingContextInterface $context) /** * {@inheritdoc} */ - public function afterCreateStub(StubbingContextInterface $context) + public function afterCreateStub(StubbingContext $context) { } } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php index c73b16e28fdd3..dc0b616b1f33c 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php @@ -18,8 +18,8 @@ use Symfony\Component\Intl\ResourceBundle\LanguageBundleInterface; use Symfony\Component\Intl\ResourceBundle\LocaleBundleInterface; use Symfony\Component\Intl\ResourceBundle\RegionBundleInterface; -use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContextInterface; -use Symfony\Component\Intl\ResourceBundle\Transformer\StubbingContextInterface; +use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContext; +use Symfony\Component\Intl\ResourceBundle\Transformer\StubbingContext; use Symfony\Component\Intl\ResourceBundle\Writer\TextBundleWriter; /** @@ -62,7 +62,7 @@ public function getBundleName() /** * {@inheritdoc} */ - public function beforeCompile(CompilationContextInterface $context) + public function beforeCompile(CompilationContext $context) { $tempDir = sys_get_temp_dir().'/icu-data-locales'; @@ -97,7 +97,7 @@ public function beforeCompile(CompilationContextInterface $context) /** * {@inheritdoc} */ - public function afterCompile(CompilationContextInterface $context) + public function afterCompile(CompilationContext $context) { $context->getFilesystem()->remove(sys_get_temp_dir().'/icu-data-locales'); } @@ -105,7 +105,7 @@ public function afterCompile(CompilationContextInterface $context) /** * {@inheritdoc} */ - public function beforeCreateStub(StubbingContextInterface $context) + public function beforeCreateStub(StubbingContext $context) { return array( 'Locales' => $this->localeBundle->getLocaleNames('en'), @@ -115,7 +115,7 @@ public function beforeCreateStub(StubbingContextInterface $context) /** * {@inheritdoc} */ - public function afterCreateStub(StubbingContextInterface $context) + public function afterCreateStub(StubbingContext $context) { } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/RegionBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/RegionBundleTransformationRule.php index b383ceff0520d..541202ef32aea 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/RegionBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/RegionBundleTransformationRule.php @@ -13,8 +13,8 @@ use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\ResourceBundle\RegionBundleInterface; -use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContextInterface; -use Symfony\Component\Intl\ResourceBundle\Transformer\StubbingContextInterface; +use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContext; +use Symfony\Component\Intl\ResourceBundle\Transformer\StubbingContext; use Symfony\Component\Intl\ResourceBundle\Writer\TextBundleWriter; use Symfony\Component\Intl\Util\IcuVersion; @@ -46,7 +46,7 @@ public function getBundleName() /** * {@inheritdoc} */ - public function beforeCompile(CompilationContextInterface $context) + public function beforeCompile(CompilationContext $context) { $tempDir = sys_get_temp_dir().'/icu-data-languages'; @@ -72,14 +72,14 @@ public function beforeCompile(CompilationContextInterface $context) /** * {@inheritdoc} */ - public function afterCompile(CompilationContextInterface $context) + public function afterCompile(CompilationContext $context) { } /** * {@inheritdoc} */ - public function beforeCreateStub(StubbingContextInterface $context) + public function beforeCreateStub(StubbingContext $context) { return array( 'Countries' => $this->regionBundle->getCountryNames('en'), @@ -89,7 +89,7 @@ public function beforeCreateStub(StubbingContextInterface $context) /** * {@inheritdoc} */ - public function afterCreateStub(StubbingContextInterface $context) + public function afterCreateStub(StubbingContext $context) { } } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/TransformationRuleInterface.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/TransformationRuleInterface.php index 3965e0d2b7e04..fe213e0f40624 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/TransformationRuleInterface.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/TransformationRuleInterface.php @@ -11,8 +11,8 @@ namespace Symfony\Component\Intl\ResourceBundle\Transformer\Rule; -use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContextInterface; -use Symfony\Component\Intl\ResourceBundle\Transformer\StubbingContextInterface; +use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContext; +use Symfony\Component\Intl\ResourceBundle\Transformer\StubbingContext; /** * Contains instruction for compiling a resource bundle. @@ -32,39 +32,39 @@ public function getBundleName(); * Runs instructions to be executed before compiling the sources of the * resource bundle. * - * @param CompilationContextInterface $context The contextual information of + * @param CompilationContext $context The contextual information of * the compilation. * * @return string[] The source directories/files of the bundle. */ - public function beforeCompile(CompilationContextInterface $context); + public function beforeCompile(CompilationContext $context); /** * Runs instructions to be executed after compiling the sources of the * resource bundle. * - * @param CompilationContextInterface $context The contextual information of + * @param CompilationContext $context The contextual information of * the compilation. */ - public function afterCompile(CompilationContextInterface $context); + public function afterCompile(CompilationContext $context); /** * Runs instructions to be executed before creating the stub version of the * resource bundle. * - * @param StubbingContextInterface $context The contextual information of + * @param StubbingContext $context The contextual information of * the compilation. * * @return mixed The data to include in the stub version. */ - public function beforeCreateStub(StubbingContextInterface $context); + public function beforeCreateStub(StubbingContext $context); /** * Runs instructions to be executed after creating the stub version of the * resource bundle. * - * @param StubbingContextInterface $context The contextual information of + * @param StubbingContext $context The contextual information of * the compilation. */ - public function afterCreateStub(StubbingContextInterface $context); + public function afterCreateStub(StubbingContext $context); } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/StubbingContext.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/StubbingContext.php index 25ab68dbfc9a2..ab9940097ee6a 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/StubbingContext.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/StubbingContext.php @@ -14,9 +14,11 @@ use Symfony\Component\Filesystem\Filesystem; /** + * Stores contextual information for resource bundle stub creation. + * * @author Bernhard Schussek */ -class StubbingContext implements StubbingContextInterface +class StubbingContext { /** * @var string @@ -47,7 +49,9 @@ public function __construct($binaryDir, $stubDir, Filesystem $filesystem, $icuVe } /** - * {@inheritdoc} + * Returns the directory where the binary resource bundles are stored. + * + * @return string An absolute path to a directory. */ public function getBinaryDir() { @@ -55,7 +59,9 @@ public function getBinaryDir() } /** - * {@inheritdoc} + * Returns the directory where the stub resource bundles are stored. + * + * @return string An absolute path to a directory. */ public function getStubDir() { @@ -63,7 +69,9 @@ public function getStubDir() } /** - * {@inheritdoc} + * Returns a tool for manipulating the filesystem. + * + * @return \Symfony\Component\Filesystem\Filesystem The filesystem manipulator. */ public function getFilesystem() { @@ -71,7 +79,9 @@ public function getFilesystem() } /** - * {@inheritdoc} + * Returns the ICU version of the bundles being converted. + * + * @return string The ICU version string. */ public function getIcuVersion() { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/StubbingContextInterface.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/StubbingContextInterface.php deleted file mode 100644 index dc49255620fff..0000000000000 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/StubbingContextInterface.php +++ /dev/null @@ -1,46 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Intl\ResourceBundle\Transformer; - -/** - * @author Bernhard Schussek - */ -interface StubbingContextInterface -{ - /** - * Returns the directory where the binary resource bundles are stored. - * - * @return string An absolute path to a directory. - */ - public function getBinaryDir(); - - /** - * Returns the directory where the stub resource bundles are stored. - * - * @return string An absolute path to a directory. - */ - public function getStubDir(); - - /** - * Returns a tool for manipulating the filesystem. - * - * @return \Symfony\Component\Filesystem\Filesystem The filesystem manipulator. - */ - public function getFilesystem(); - - /** - * Returns the ICU version of the bundles being converted. - * - * @return string The ICU version string. - */ - public function getIcuVersion(); -} From 8f983ed57132fa254877d380f3bfac4e2cf4e807 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Mon, 7 Oct 2013 10:59:08 +0200 Subject: [PATCH 30/48] [Intl] Moved consistency tests to Icu component --- phpunit.xml.dist | 1 - ...stencyTest.php => ConsistencyTestCase.php} | 2 +- .../CurrencyBundleConsistencyTestCase.php} | 24 ++++----- .../LanguageBundleConsistencyTestCase.php} | 50 +++++++------------ .../LocaleBundleConsistencyTestCase.php} | 28 +++++------ .../RegionBundleConsistencyTestCase.php} | 32 ++++++------ src/Symfony/Component/Intl/phpunit.xml.dist | 6 --- 7 files changed, 54 insertions(+), 89 deletions(-) rename src/Symfony/Component/Intl/Test/{ConsistencyTest.php => ConsistencyTestCase.php} (95%) rename src/Symfony/Component/Intl/{Tests/ResourceBundle/CurrencyBundleConsistencyTest.php => Test/CurrencyBundleConsistencyTestCase.php} (73%) rename src/Symfony/Component/Intl/{Tests/ResourceBundle/LanguageBundleConsistencyTest.php => Test/LanguageBundleConsistencyTestCase.php} (75%) rename src/Symfony/Component/Intl/{Tests/ResourceBundle/LocaleBundleConsistencyTest.php => Test/LocaleBundleConsistencyTestCase.php} (70%) rename src/Symfony/Component/Intl/{Tests/ResourceBundle/RegionBundleConsistencyTest.php => Test/RegionBundleConsistencyTestCase.php} (77%) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 5ce6d483e47ae..66323e43d16bc 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -29,7 +29,6 @@ benchmark - icu-consistency diff --git a/src/Symfony/Component/Intl/Test/ConsistencyTest.php b/src/Symfony/Component/Intl/Test/ConsistencyTestCase.php similarity index 95% rename from src/Symfony/Component/Intl/Test/ConsistencyTest.php rename to src/Symfony/Component/Intl/Test/ConsistencyTestCase.php index 92eaf1295102b..c6a1684146393 100644 --- a/src/Symfony/Component/Intl/Test/ConsistencyTest.php +++ b/src/Symfony/Component/Intl/Test/ConsistencyTestCase.php @@ -16,7 +16,7 @@ /** * @author Bernhard Schussek */ -class ConsistencyTest extends \PHPUnit_Framework_TestCase +abstract class ConsistencyTestCase extends \PHPUnit_Framework_TestCase { public function provideLocales() { diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/CurrencyBundleConsistencyTest.php b/src/Symfony/Component/Intl/Test/CurrencyBundleConsistencyTestCase.php similarity index 73% rename from src/Symfony/Component/Intl/Tests/ResourceBundle/CurrencyBundleConsistencyTest.php rename to src/Symfony/Component/Intl/Test/CurrencyBundleConsistencyTestCase.php index c8207f1f5f2a9..e4c72184eb97f 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/CurrencyBundleConsistencyTest.php +++ b/src/Symfony/Component/Intl/Test/CurrencyBundleConsistencyTestCase.php @@ -9,30 +9,25 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Intl\Tests\ResourceBundle; +namespace Symfony\Component\Intl\Test; use Symfony\Component\Intl\Intl; -use Symfony\Component\Intl\Test\ConsistencyTest; /** * @author Bernhard Schussek */ -class CurrencyBundleConsistencyTest extends ConsistencyTest +abstract class CurrencyBundleConsistencyTestCase extends ConsistencyTestCase { - // The below arrays document the current state of the ICU data. - // This state is verified in the tests below. - // You can add arbitrary rules here if you want to document the availability - // of other currencies. - private static $localesWithoutTranslationForUSD = array('as', 'bem', 'bo', 'dua', 'dyo', 'eo', 'fo', 'gv', 'ig', 'ii', 'kl', 'kok', 'kw', 'mgh', 'mgo', 'mt', 'nus', 'or', 'pa', 'ps', 'to', 'rw', 'uz', 'yav'); - private static $localesWithoutTranslationForEUR = array('as', 'bem', 'bo', 'dua', 'eo', 'fo', 'gv', 'haw', 'ig', 'ii', 'kok', 'kw', 'mgh', 'mgo', 'nus', 'or', 'pa', 'ps', 'to', 'rw', 'uz'); - private static $localesWithoutTranslationForGBP = array('as', 'bem', 'bo', 'dua', 'dyo', 'eo', 'fo', 'gv', 'haw', 'ig', 'ii', 'jgo', 'kl', 'kok', 'kw', 'mgh', 'mgo', 'mt', 'nus', 'or', 'pa', 'ps', 'so', 'to', 'rw', 'uz'); - private static $localesWithoutTranslationForJPY = array('as', 'bem', 'bo', 'dua', 'eo', 'fo', 'gv', 'haw', 'ig', 'ii', 'jgo', 'kl', 'kok', 'kw', 'mgh', 'mgo', 'mt', 'nus', 'or', 'pa', 'ps', 'so', 'to', 'rw', 'uz'); - private static $localesWithoutTranslationForCNY = array('as', 'bem', 'dua', 'eo', 'fo', 'gv', 'haw', 'ig', 'jgo', 'kl', 'kok', 'kw', 'mgh', 'mgo', 'mt', 'nus', 'or', 'pa', 'ps', 'so', 'to', 'rw', 'uz'); + protected static $localesWithoutTranslationForUSD = array(); + protected static $localesWithoutTranslationForEUR = array(); + protected static $localesWithoutTranslationForGBP = array(); + protected static $localesWithoutTranslationForJPY = array(); + protected static $localesWithoutTranslationForCNY = array(); /** * @var \Symfony\Component\Intl\ResourceBundle\CurrencyBundleInterface */ - private static $currencyBundle; + protected static $currencyBundle; public static function setUpBeforeClass() { @@ -107,6 +102,7 @@ public function testGetCurrencyNames($displayLocale) /** * @dataProvider provideLocaleAliases + * @group locale-alias-based */ public function testGetCurrencyNamesSupportsAliases($alias, $ofLocale) { @@ -134,7 +130,6 @@ public function testGetRoundingIncrement($currency) /** * @dataProvider provideLocales - * @group icu-consistency */ public function testGetCurrencyNamesAndGetCurrencyNameAreConsistent($displayLocale) { @@ -147,7 +142,6 @@ public function testGetCurrencyNamesAndGetCurrencyNameAreConsistent($displayLoca /** * @dataProvider provideLocales - * @group icu-consistency */ public function testGetCurrencyNamesAndGetCurrencySymbolAreConsistent($displayLocale) { diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/LanguageBundleConsistencyTest.php b/src/Symfony/Component/Intl/Test/LanguageBundleConsistencyTestCase.php similarity index 75% rename from src/Symfony/Component/Intl/Tests/ResourceBundle/LanguageBundleConsistencyTest.php rename to src/Symfony/Component/Intl/Test/LanguageBundleConsistencyTestCase.php index a1fc92e50c9c7..6effedf65fae6 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/LanguageBundleConsistencyTest.php +++ b/src/Symfony/Component/Intl/Test/LanguageBundleConsistencyTestCase.php @@ -9,48 +9,34 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Intl\Tests\ResourceBundle; +namespace Symfony\Component\Intl\Test; use Symfony\Component\Intl\Exception\NoSuchEntryException; use Symfony\Component\Intl\Intl; -use Symfony\Component\Intl\Test\ConsistencyTest; /** * @author Bernhard Schussek */ -class LanguageBundleConsistencyTest extends ConsistencyTest +abstract class LanguageBundleConsistencyTestCase extends ConsistencyTestCase { - // The below arrays document the current state of the ICU data. - // This state is verified in the tests below. - // You can add arbitrary rules here if you want to document the availability - // of other languages. - private static $localesWithoutTranslationForThemselves = array('nmg'); - private static $localesWithoutTranslationForEnglish = array('as', 'bo', 'dua', 'fo', 'gv', 'kl', 'kw', 'mgo', 'uz'); - private static $localesWithoutTranslationForFrench = array('as', 'bo', 'dua', 'fo', 'gv', 'kl', 'kw', 'mgo', 'uz'); - private static $localesWithoutTranslationForSpanish = array('as', 'bo', 'dua', 'fo', 'gv', 'jgo', 'kl', 'kw', 'lo', 'mgo', 'ps', 'uz'); - private static $localesWithoutTranslationForRussian = array('as', 'dua', 'fo', 'gv', 'jgo', 'kl', 'kw', 'mgo', 'pa', 'uz'); - private static $localesWithoutTranslationForChinese = array('as', 'dua', 'fo', 'gv', 'kl', 'kw', 'mgo', 'pa', 'rw', 'ti', 'uz'); - private static $localesWithoutTranslationForGerman = array('as', 'bo', 'dua', 'fo', 'gv', 'kl', 'kw', 'mgo', 'uz'); - - private static $localesWithoutTranslationForAnyScript = array( - 'agq', 'ak', 'asa', 'bas', 'bem', 'bez', 'bm', 'cgg', 'dav', 'dje', 'dua', - 'dyo', 'ebu', 'eo', 'ewo', 'ff', 'fo', 'ga', 'guz', 'gv', 'ha', 'haw', - 'ig', 'jmc', 'kab', 'kam', 'kde', 'khq', 'ki', 'kl', 'kln', 'kok', 'ksb', - 'ksf', 'kw', 'lag', 'lg', 'ln', 'lu', 'luo', 'luy', 'mas', 'mer', 'mfe', - 'mg', 'mgh', 'mua', 'naq', 'nd', 'nmg', 'nus', 'nyn', 'pa', 'rn', 'rof', - 'rw', 'rwk', 'saq', 'sbp', 'seh', 'ses', 'sg', 'shi', 'sn', 'swc', 'teo', - 'twq', 'tzm', 'uz', 'vai', 'vun', 'xog', 'yav', 'yo' - ); - - private static $localesWithoutTranslationForLatinScript = array('as', 'bo', 'lo', 'ps', 'so'); - private static $localesWithoutTranslationForSimplifiedScript = array('as', 'bo', 'jgo', 'mgo', 'om', 'ps', 'so', 'sq', 'ti'); - private static $localesWithoutTranslationForTraditionalScript = array('as', 'bo', 'jgo', 'mgo', 'om', 'ps', 'so', 'sq', 'ti'); - private static $localesWithoutTranslationForCyrillicScript = array('as', 'bo', 'jgo', 'lo', 'mgo', 'om', 'ps', 'so', 'mt', 'sq', 'ti'); + protected static $localesWithoutTranslationForThemselves = array(); + protected static $localesWithoutTranslationForEnglish = array(); + protected static $localesWithoutTranslationForFrench = array(); + protected static $localesWithoutTranslationForSpanish = array(); + protected static $localesWithoutTranslationForRussian = array(); + protected static $localesWithoutTranslationForChinese = array(); + protected static $localesWithoutTranslationForGerman = array(); + + protected static $localesWithoutTranslationForAnyScript = array(); + protected static $localesWithoutTranslationForLatinScript = array(); + protected static $localesWithoutTranslationForSimplifiedScript = array(); + protected static $localesWithoutTranslationForTraditionalScript = array(); + protected static $localesWithoutTranslationForCyrillicScript = array(); /** * @var \Symfony\Component\Intl\ResourceBundle\LanguageBundleInterface */ - private static $languageBundle; + protected static $languageBundle; public static function setUpBeforeClass() { @@ -211,6 +197,7 @@ public function testGetScriptNames($displayLocale) /** * @dataProvider provideLocaleAliases + * @group locale-alias-based */ public function testGetLanguageNamesSupportsAliases($alias, $ofLocale) { @@ -222,6 +209,7 @@ public function testGetLanguageNamesSupportsAliases($alias, $ofLocale) /** * @dataProvider provideLocaleAliasesWithScripts + * @group locale-alias-based */ public function testGetScriptNamesSupportsAliases($alias, $ofLocale) { @@ -233,7 +221,6 @@ public function testGetScriptNamesSupportsAliases($alias, $ofLocale) /** * @dataProvider provideLocales - * @group icu-consistency */ public function testGetLanguageNamesAndGetLanguageNameAreConsistent($displayLocale) { @@ -246,7 +233,6 @@ public function testGetLanguageNamesAndGetLanguageNameAreConsistent($displayLoca /** * @dataProvider provideLocalesWithScripts - * @group icu-consistency */ public function testGetScriptNamesAndGetScriptNameAreConsistent($displayLocale) { diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/LocaleBundleConsistencyTest.php b/src/Symfony/Component/Intl/Test/LocaleBundleConsistencyTestCase.php similarity index 70% rename from src/Symfony/Component/Intl/Tests/ResourceBundle/LocaleBundleConsistencyTest.php rename to src/Symfony/Component/Intl/Test/LocaleBundleConsistencyTestCase.php index 7f7c7a387248d..542bce4847c14 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/LocaleBundleConsistencyTest.php +++ b/src/Symfony/Component/Intl/Test/LocaleBundleConsistencyTestCase.php @@ -9,32 +9,28 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Intl\Tests\ResourceBundle; +namespace Symfony\Component\Intl\Test; use Symfony\Component\Intl\Intl; -use Symfony\Component\Intl\Test\ConsistencyTest; +use Symfony\Component\Intl\Test\ConsistencyTestCase; /** * @author Bernhard Schussek */ -class LocaleBundleConsistencyTest extends ConsistencyTest +abstract class LocaleBundleConsistencyTestCase extends ConsistencyTestCase { - // The below arrays document the current state of the ICU data. - // This state is verified in the tests below. - // You can add arbitrary rules here if you want to document the availability - // of other locale names. - private static $localesWithoutTranslationForThemselves = array('nmg'); - private static $localesWithoutTranslationForEnglish = array('as', 'bo', 'dua', 'fo', 'gv', 'kl', 'kw', 'mgo', 'uz'); - private static $localesWithoutTranslationForFrench = array('as', 'bo', 'dua', 'fo', 'gv', 'kl', 'kw', 'mgo', 'uz'); - private static $localesWithoutTranslationForSpanish = array('as', 'bo', 'dua', 'fo', 'gv', 'jgo', 'kl', 'kw', 'lo', 'mgo', 'ps', 'uz'); - private static $localesWithoutTranslationForRussian = array('as', 'dua', 'fo', 'gv', 'jgo', 'kl', 'kw', 'mgo', 'pa', 'uz'); - private static $localesWithoutTranslationForChinese = array('as', 'dua', 'fo', 'gv', 'kl', 'kw', 'mgo', 'pa', 'rw', 'ti', 'uz'); - private static $localesWithoutTranslationForGerman = array('as', 'bo', 'dua', 'fo', 'gv', 'kl', 'kw', 'mgo', 'uz'); + protected static $localesWithoutTranslationForThemselves = array('nmg'); + protected static $localesWithoutTranslationForEnglish = array('as', 'bo', 'dua', 'fo', 'gv', 'kl', 'kw', 'mgo', 'uz'); + protected static $localesWithoutTranslationForFrench = array('as', 'bo', 'dua', 'fo', 'gv', 'kl', 'kw', 'mgo', 'uz'); + protected static $localesWithoutTranslationForSpanish = array('as', 'bo', 'dua', 'fo', 'gv', 'jgo', 'kl', 'kw', 'lo', 'mgo', 'ps', 'uz'); + protected static $localesWithoutTranslationForRussian = array('as', 'dua', 'fo', 'gv', 'jgo', 'kl', 'kw', 'mgo', 'pa', 'uz'); + protected static $localesWithoutTranslationForChinese = array('as', 'dua', 'fo', 'gv', 'kl', 'kw', 'mgo', 'pa', 'rw', 'ti', 'uz'); + protected static $localesWithoutTranslationForGerman = array('as', 'bo', 'dua', 'fo', 'gv', 'kl', 'kw', 'mgo', 'uz'); /** * @var \Symfony\Component\Intl\ResourceBundle\LocaleBundleInterface */ - private static $localeBundle; + protected static $localeBundle; public static function setUpBeforeClass() { @@ -93,6 +89,7 @@ public function testGetLocaleNames($displayLocale) /** * @dataProvider provideLocaleAliases + * @group locale-alias-based */ public function testGetLocaleNamesSupportsAliases($alias, $ofLocale) { @@ -104,7 +101,6 @@ public function testGetLocaleNamesSupportsAliases($alias, $ofLocale) /** * @dataProvider provideLocales - * @group icu-consistency */ public function testGetLocaleNamesAndGetLocaleNameAreConsistent($displayLocale) { diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/RegionBundleConsistencyTest.php b/src/Symfony/Component/Intl/Test/RegionBundleConsistencyTestCase.php similarity index 77% rename from src/Symfony/Component/Intl/Tests/ResourceBundle/RegionBundleConsistencyTest.php rename to src/Symfony/Component/Intl/Test/RegionBundleConsistencyTestCase.php index a7c1e8fb21f56..ab325dc89c00b 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/RegionBundleConsistencyTest.php +++ b/src/Symfony/Component/Intl/Test/RegionBundleConsistencyTestCase.php @@ -9,35 +9,31 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\Intl\Tests\ResourceBundle; +namespace Symfony\Component\Intl\Test; use Symfony\Component\Intl\Exception\NoSuchEntryException; use Symfony\Component\Intl\Intl; -use Symfony\Component\Intl\Test\ConsistencyTest; +use Symfony\Component\Intl\Test\ConsistencyTestCase; /** * @author Bernhard Schussek */ -class RegionBundleConsistencyTest extends ConsistencyTest +abstract class RegionBundleConsistencyTestCase extends ConsistencyTestCase { - // The below arrays document the current state of the ICU data. - // This state is verified in the tests below. - // You can add arbitrary rules here if you want to document the availability - // of other countries. - private static $localesWithoutTranslationForAnyCountry = array('ti'); - private static $localesWithoutTranslationForUS = array('bem', 'dua', 'dyo', 'gv', 'ig', 'jgo', 'kl', 'kok', 'kw', 'mgo', 'nus', 'pa', 'ps', 'rw', 'uz'); - private static $localesWithoutTranslationForDE = array('bem', 'dua', 'gv', 'ig', 'kl', 'kok', 'kw', 'mgh', 'mgo', 'nus', 'pa', 'rw', 'uz'); - private static $localesWithoutTranslationForGB = array('bem', 'dua', 'dyo', 'fo', 'ig', 'jgo', 'kl', 'kok', 'mgh', 'mgo', 'nus', 'pa', 'rw', 'uz'); - private static $localesWithoutTranslationForFR = array('bem', 'bo', 'dua', 'gv', 'ig', 'kl', 'kok', 'kw', 'mgo', 'nus', 'pa', 'rw', 'uz'); - private static $localesWithoutTranslationForIT = array('bem', 'dua', 'gv', 'ig', 'kl', 'kok', 'kw', 'mgo', 'nus', 'pa', 'rw', 'uz'); - private static $localesWithoutTranslationForBR = array('bem', 'bo', 'dua', 'gv', 'haw', 'ig', 'kl', 'kok', 'kw', 'mgh', 'mgo', 'pa', 'ps', 'rw', 'uz'); - private static $localesWithoutTranslationForRU = array('bem', 'dua', 'dyo', 'gv', 'ig', 'kl', 'kok', 'kw', 'mgh', 'mgo', 'nus', 'pa', 'rw', 'uz'); - private static $localesWithoutTranslationForCN = array('bem', 'dua', 'gv', 'kl', 'kok', 'kw', 'mgo', 'pa', 'rw', 'uz'); + protected static $localesWithoutTranslationForAnyCountry = array('ti'); + protected static $localesWithoutTranslationForUS = array('bem', 'dua', 'dyo', 'gv', 'ig', 'jgo', 'kl', 'kok', 'kw', 'mgo', 'nus', 'pa', 'ps', 'rw', 'uz'); + protected static $localesWithoutTranslationForDE = array('bem', 'dua', 'gv', 'ig', 'kl', 'kok', 'kw', 'mgh', 'mgo', 'nus', 'pa', 'rw', 'uz'); + protected static $localesWithoutTranslationForGB = array('bem', 'dua', 'dyo', 'fo', 'ig', 'jgo', 'kl', 'kok', 'mgh', 'mgo', 'nus', 'pa', 'rw', 'uz'); + protected static $localesWithoutTranslationForFR = array('bem', 'bo', 'dua', 'gv', 'ig', 'kl', 'kok', 'kw', 'mgo', 'nus', 'pa', 'rw', 'uz'); + protected static $localesWithoutTranslationForIT = array('bem', 'dua', 'gv', 'ig', 'kl', 'kok', 'kw', 'mgo', 'nus', 'pa', 'rw', 'uz'); + protected static $localesWithoutTranslationForBR = array('bem', 'bo', 'dua', 'gv', 'haw', 'ig', 'kl', 'kok', 'kw', 'mgh', 'mgo', 'pa', 'ps', 'rw', 'uz'); + protected static $localesWithoutTranslationForRU = array('bem', 'dua', 'dyo', 'gv', 'ig', 'kl', 'kok', 'kw', 'mgh', 'mgo', 'nus', 'pa', 'rw', 'uz'); + protected static $localesWithoutTranslationForCN = array('bem', 'dua', 'gv', 'kl', 'kok', 'kw', 'mgo', 'pa', 'rw', 'uz'); /** * @var \Symfony\Component\Intl\ResourceBundle\RegionBundleInterface */ - private static $regionBundle; + protected static $regionBundle; public static function setUpBeforeClass() { @@ -172,6 +168,7 @@ public function testGetCountryNames($displayLocale) /** * @dataProvider provideLocaleAliasesWithCountries + * @group locale-alias-based */ public function testGetCountryNamesSupportsAliases($alias, $ofLocale) { @@ -183,7 +180,6 @@ public function testGetCountryNamesSupportsAliases($alias, $ofLocale) /** * @dataProvider provideLocalesWithCountries - * @group icu-consistency */ public function testGetCountryNamesAndGetCountryNameAreConsistent($displayLocale) { diff --git a/src/Symfony/Component/Intl/phpunit.xml.dist b/src/Symfony/Component/Intl/phpunit.xml.dist index 21c67fc622604..5e709f137f60a 100644 --- a/src/Symfony/Component/Intl/phpunit.xml.dist +++ b/src/Symfony/Component/Intl/phpunit.xml.dist @@ -17,12 +17,6 @@ - - - icu-consistency - - - ./ From 52820dad6f6f4f0faddb3a5b0ded9c5a43774f5f Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Mon, 7 Oct 2013 14:31:38 +0200 Subject: [PATCH 31/48] [Intl] Simplified consistency tests --- .../Intl/Test/ConsistencyTestCase.php | 59 ++--- .../CurrencyBundleConsistencyTestCase.php | 122 +++++----- .../LanguageBundleConsistencyTestCase.php | 218 ++++++++---------- .../Test/LocaleBundleConsistencyTestCase.php | 109 +++++---- .../Test/RegionBundleConsistencyTestCase.php | 153 +++++------- 5 files changed, 297 insertions(+), 364 deletions(-) diff --git a/src/Symfony/Component/Intl/Test/ConsistencyTestCase.php b/src/Symfony/Component/Intl/Test/ConsistencyTestCase.php index c6a1684146393..0f123d658083e 100644 --- a/src/Symfony/Component/Intl/Test/ConsistencyTestCase.php +++ b/src/Symfony/Component/Intl/Test/ConsistencyTestCase.php @@ -18,44 +18,51 @@ */ abstract class ConsistencyTestCase extends \PHPUnit_Framework_TestCase { + protected function setUp() + { + \Locale::setDefault('en'); + } + public function provideLocales() { - $parameters = array(); + return array_map( + function ($locale) { return array($locale); }, + $this->getLocales() + ); + } - foreach (Intl::getLocaleBundle()->getLocales() as $locale) { - $parameters[] = array($locale); - } + public function provideRootLocales() + { + return array_map( + function ($locale) { return array($locale); }, + $this->getRootLocales() + ); + } - return $parameters; + public function provideLocaleAliases() + { + return array_map( + function ($alias, $ofLocale) { return array($alias, $ofLocale); }, + array_keys($this->getLocaleAliases()), + $this->getLocaleAliases() + ); } - public function provideRootLocales() + protected static function getLocales() { - $parameters = array(); - $locales = Intl::getLocaleBundle()->getLocales(); - $aliases = Intl::getLocaleBundle()->getLocaleAliases(); + return array(); + } - $locales = array_filter($locales, function ($locale) use ($aliases) { - // no aliases + protected static function getRootLocales() + { + return array_filter(static::getLocales(), function ($locale) { // no locales for which fallback is possible (e.g "en_GB") - return !isset($aliases[$locale]) && false === strpos($locale, '_'); + return false === strpos($locale, '_'); }); - - foreach ($locales as $locale) { - $parameters[] = array($locale); - } - - return $parameters; } - public function provideLocaleAliases() + protected static function getLocaleAliases() { - $parameters = array(); - - foreach (Intl::getLocaleBundle()->getLocaleAliases() as $alias => $ofLocale) { - $parameters[] = array($alias, $ofLocale); - } - - return $parameters; + return array(); } } diff --git a/src/Symfony/Component/Intl/Test/CurrencyBundleConsistencyTestCase.php b/src/Symfony/Component/Intl/Test/CurrencyBundleConsistencyTestCase.php index e4c72184eb97f..3aed66240b188 100644 --- a/src/Symfony/Component/Intl/Test/CurrencyBundleConsistencyTestCase.php +++ b/src/Symfony/Component/Intl/Test/CurrencyBundleConsistencyTestCase.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Intl\Test; +use Symfony\Component\Intl\Exception\NoSuchEntryException; use Symfony\Component\Intl\Intl; /** @@ -18,86 +19,75 @@ */ abstract class CurrencyBundleConsistencyTestCase extends ConsistencyTestCase { - protected static $localesWithoutTranslationForUSD = array(); - protected static $localesWithoutTranslationForEUR = array(); - protected static $localesWithoutTranslationForGBP = array(); - protected static $localesWithoutTranslationForJPY = array(); - protected static $localesWithoutTranslationForCNY = array(); + protected static $localesWithoutTranslationForAnyCurrency = array(); + protected static $localesWithoutTranslationForCurrency = array(); - /** - * @var \Symfony\Component\Intl\ResourceBundle\CurrencyBundleInterface - */ - protected static $currencyBundle; - - public static function setUpBeforeClass() - { - static::$currencyBundle = Intl::getCurrencyBundle(); - } + protected static $currencies = array(); public function provideCurrencies() { - $locales = Intl::getLocaleBundle()->getLocales(); - $aliases = Intl::getLocaleBundle()->getLocaleAliases(); + $aliases = $this->getLocaleAliases(); - // Filter non-root locales - $locales = array_filter($locales, function ($locale) use ($aliases) { + // Filter non-root and alias locales + $locales = array_filter($this->getLocales(), function ($locale) use ($aliases) { return false === strpos($locale, '_') && !isset($aliases[$locale]); }); - $currencies = array(); - $currencyBundle = Intl::getCurrencyBundle(); + return array_map( + function ($currency) { return array($currency); }, + static::$currencies + ); + } - // Merge all currency codes that can be found for any locale - foreach ($locales as $locale) { - $currencies = array_replace($currencies, $currencyBundle->getCurrencyNames($locale)); + public function testGetCurrencyNames() + { + $translatedLocales = array(); + $rootLocales = $this->getRootLocales(); + + foreach ($rootLocales as $displayLocale) { + try { + Intl::getCurrencyBundle()->getCurrencyNames($displayLocale); + $translatedLocales[] = $displayLocale; + } catch (NoSuchEntryException $e) { + } } - $currencies = array_keys($currencies); - $parameters = array(); + $untranslatedLocales = array_diff($rootLocales, $translatedLocales); - foreach ($currencies as $currency) { - $parameters[] = array($currency); - } + sort($untranslatedLocales); - return $parameters; + $this->assertEquals(static::$localesWithoutTranslationForAnyCurrency, $untranslatedLocales); + } + + public function provideTestedCurrencies() + { + return array_map( + function ($currency) { return array($currency); }, + array_keys(static::$localesWithoutTranslationForCurrency) + ); } /** - * @dataProvider provideRootLocales + * @dataProvider provideTestedCurrencies */ - public function testGetCurrencyNames($displayLocale) + public function testGetCurrencyName($currency) { - $currencyNames = static::$currencyBundle->getCurrencyNames($displayLocale); - - if (in_array($displayLocale, static::$localesWithoutTranslationForUSD)) { - $this->assertArrayNotHasKey('USD', $currencyNames); - } else { - $this->assertArrayHasKey('USD', $currencyNames); + $translatedLocales = array(); + $rootLocales = $this->getRootLocales(); + + foreach ($rootLocales as $displayLocale) { + try { + Intl::getCurrencyBundle()->getCurrencyName($currency, $displayLocale); + $translatedLocales[] = $displayLocale; + } catch (NoSuchEntryException $e) { + } } - if (in_array($displayLocale, static::$localesWithoutTranslationForEUR)) { - $this->assertArrayNotHasKey('EUR', $currencyNames); - } else { - $this->assertArrayHasKey('EUR', $currencyNames); - } + $untranslatedLocales = array_diff($rootLocales, static::$localesWithoutTranslationForAnyCurrency, $translatedLocales); - if (in_array($displayLocale, static::$localesWithoutTranslationForGBP)) { - $this->assertArrayNotHasKey('GBP', $currencyNames); - } else { - $this->assertArrayHasKey('GBP', $currencyNames); - } - - if (in_array($displayLocale, static::$localesWithoutTranslationForJPY)) { - $this->assertArrayNotHasKey('JPY', $currencyNames); - } else { - $this->assertArrayHasKey('JPY', $currencyNames); - } + sort($untranslatedLocales); - if (in_array($displayLocale, static::$localesWithoutTranslationForCNY)) { - $this->assertArrayNotHasKey('CNY', $currencyNames); - } else { - $this->assertArrayHasKey('CNY', $currencyNames); - } + $this->assertEquals(static::$localesWithoutTranslationForCurrency[$currency], $untranslatedLocales); } /** @@ -107,8 +97,8 @@ public function testGetCurrencyNames($displayLocale) public function testGetCurrencyNamesSupportsAliases($alias, $ofLocale) { $this->assertEquals( - static::$currencyBundle->getCurrencyNames($ofLocale), - static::$currencyBundle->getCurrencyNames($alias) + Intl::getCurrencyBundle()->getCurrencyNames($ofLocale), + Intl::getCurrencyBundle()->getCurrencyNames($alias) ); } @@ -117,7 +107,7 @@ public function testGetCurrencyNamesSupportsAliases($alias, $ofLocale) */ public function testGetFractionDigits($currency) { - $this->assertTrue(is_numeric(static::$currencyBundle->getFractionDigits($currency))); + $this->assertTrue(is_numeric(Intl::getCurrencyBundle()->getFractionDigits($currency))); } /** @@ -125,7 +115,7 @@ public function testGetFractionDigits($currency) */ public function testGetRoundingIncrement($currency) { - $this->assertTrue(is_numeric(static::$currencyBundle->getRoundingIncrement($currency))); + $this->assertTrue(is_numeric(Intl::getCurrencyBundle()->getRoundingIncrement($currency))); } /** @@ -133,10 +123,10 @@ public function testGetRoundingIncrement($currency) */ public function testGetCurrencyNamesAndGetCurrencyNameAreConsistent($displayLocale) { - $names = static::$currencyBundle->getCurrencyNames($displayLocale); + $names = Intl::getCurrencyBundle()->getCurrencyNames($displayLocale); foreach ($names as $currency => $name) { - $this->assertSame($name, static::$currencyBundle->getCurrencyName($currency, $displayLocale)); + $this->assertSame($name, Intl::getCurrencyBundle()->getCurrencyName($currency, $displayLocale)); } } @@ -145,10 +135,10 @@ public function testGetCurrencyNamesAndGetCurrencyNameAreConsistent($displayLoca */ public function testGetCurrencyNamesAndGetCurrencySymbolAreConsistent($displayLocale) { - $names = static::$currencyBundle->getCurrencyNames($displayLocale); + $names = Intl::getCurrencyBundle()->getCurrencyNames($displayLocale); foreach ($names as $currency => $name) { - $this->assertGreaterThan(0, mb_strlen(static::$currencyBundle->getCurrencySymbol($currency, $displayLocale))); + $this->assertGreaterThan(0, mb_strlen(Intl::getCurrencyBundle()->getCurrencySymbol($currency, $displayLocale))); } } } diff --git a/src/Symfony/Component/Intl/Test/LanguageBundleConsistencyTestCase.php b/src/Symfony/Component/Intl/Test/LanguageBundleConsistencyTestCase.php index 6effedf65fae6..61710124bd4c4 100644 --- a/src/Symfony/Component/Intl/Test/LanguageBundleConsistencyTestCase.php +++ b/src/Symfony/Component/Intl/Test/LanguageBundleConsistencyTestCase.php @@ -19,43 +19,21 @@ */ abstract class LanguageBundleConsistencyTestCase extends ConsistencyTestCase { + protected static $localesWithoutTranslationForAnyLanguage = array(); protected static $localesWithoutTranslationForThemselves = array(); - protected static $localesWithoutTranslationForEnglish = array(); - protected static $localesWithoutTranslationForFrench = array(); - protected static $localesWithoutTranslationForSpanish = array(); - protected static $localesWithoutTranslationForRussian = array(); - protected static $localesWithoutTranslationForChinese = array(); - protected static $localesWithoutTranslationForGerman = array(); + protected static $localesWithoutTranslationForLanguage = array(); protected static $localesWithoutTranslationForAnyScript = array(); - protected static $localesWithoutTranslationForLatinScript = array(); - protected static $localesWithoutTranslationForSimplifiedScript = array(); - protected static $localesWithoutTranslationForTraditionalScript = array(); - protected static $localesWithoutTranslationForCyrillicScript = array(); - - /** - * @var \Symfony\Component\Intl\ResourceBundle\LanguageBundleInterface - */ - protected static $languageBundle; - - public static function setUpBeforeClass() - { - static::$languageBundle = Intl::getLanguageBundle(); - } + protected static $localesWithoutTranslationForScript = array(); public function provideLocalesWithScripts() { - $parameters = array(); - $localesWithoutScript = array_flip(static::$localesWithoutTranslationForAnyScript); - $aliasesWithoutScripts = Intl::getLocaleBundle()->getLocaleAliases(); - $aliasesWithoutScripts = array_intersect_assoc($aliasesWithoutScripts, static::$localesWithoutTranslationForAnyScript); - - $locales = Intl::getLocaleBundle()->getLocales(); + $aliasesWithoutScripts = array_intersect_assoc($this->getLocaleAliases(), static::$localesWithoutTranslationForAnyScript); // remove locales that have no "Scripts" block or are an alias to a locale // without "Scripts" block - $locales = array_filter($locales, function ($locale) use ($localesWithoutScript, $aliasesWithoutScripts) { + $locales = array_filter($this->getLocales(), function ($locale) use ($localesWithoutScript, $aliasesWithoutScripts) { while (null !== $locale) { if (isset($localesWithoutScript[$locale]) || isset($aliasesWithoutScripts[$locale])) { return false; @@ -67,22 +45,18 @@ public function provideLocalesWithScripts() return true; }); - foreach ($locales as $locale) { - $parameters[] = array($locale); - } - - return $parameters; + return array_map( + function ($locale) { return array($locale); }, + $locales + ); } public function provideLocaleAliasesWithScripts() { - $parameters = array(); - - $aliases = Intl::getLocaleBundle()->getLocaleAliases(); $localesWithoutScript = array_flip(static::$localesWithoutTranslationForAnyScript); // Remove aliases that point to a locale without "Scripts" block - $aliases = array_filter($aliases, function ($targetLocale) use ($localesWithoutScript) { + $aliases = array_filter($this->getLocaleAliases(), function ($targetLocale) use ($localesWithoutScript) { while (null !== $targetLocale) { if (isset($localesWithoutScript[$targetLocale])) { return false; @@ -94,105 +68,113 @@ public function provideLocaleAliasesWithScripts() return true; }); - foreach ($aliases as $alias => $ofLocale) { - $parameters[] = array($alias, $ofLocale); + return array_map( + function ($alias, $ofLocale) { return array($alias, $ofLocale); }, + array_keys($aliases), + $aliases + ); + } + + public function testGetLanguageNames() + { + $translatedLocales = array(); + $rootLocales = $this->getRootLocales(); + + foreach ($rootLocales as $displayLocale) { + try { + Intl::getLanguageBundle()->getLanguageNames($displayLocale); + $translatedLocales[] = $displayLocale; + } catch (NoSuchEntryException $e) { + } } - return $parameters; + $untranslatedLocales = array_diff($rootLocales, $translatedLocales); + + sort($untranslatedLocales); + + $this->assertEquals(static::$localesWithoutTranslationForAnyLanguage, $untranslatedLocales); + } + + public function provideTestedLanguages() + { + return array_map( + function ($language) { return array($language); }, + array_keys(static::$localesWithoutTranslationForLanguage) + ); } /** - * @dataProvider provideRootLocales + * @dataProvider provideTestedLanguages */ - public function testGetLanguageNames($displayLocale) + public function testGetLanguageName($language) { - $languageNames = static::$languageBundle->getLanguageNames($displayLocale); - - if (in_array($displayLocale, static::$localesWithoutTranslationForThemselves)) { - $this->assertArrayNotHasKey($displayLocale, $languageNames); - } else { - $this->assertArrayHasKey($displayLocale, $languageNames); + $translatedLocales = array(); + $rootLocales = $this->getRootLocales(); + + foreach ($rootLocales as $displayLocale) { + try { + Intl::getLanguageBundle()->getLanguageName($language ?: $displayLocale, null, $displayLocale); + $translatedLocales[] = $displayLocale; + } catch (NoSuchEntryException $e) { + } } - if (in_array($displayLocale, static::$localesWithoutTranslationForEnglish)) { - $this->assertArrayNotHasKey('en', $languageNames); - } else { - $this->assertArrayHasKey('en', $languageNames); - } + $untranslatedLocales = array_diff($rootLocales, static::$localesWithoutTranslationForAnyLanguage, $translatedLocales); - if (in_array($displayLocale, static::$localesWithoutTranslationForFrench)) { - $this->assertArrayNotHasKey('fr', $languageNames); - } else { - $this->assertArrayHasKey('fr', $languageNames); - } + sort($untranslatedLocales); - if (in_array($displayLocale, static::$localesWithoutTranslationForSpanish)) { - $this->assertArrayNotHasKey('es', $languageNames); - } else { - $this->assertArrayHasKey('es', $languageNames); - } + $this->assertEquals(static::$localesWithoutTranslationForLanguage[$language], $untranslatedLocales); + } - if (in_array($displayLocale, static::$localesWithoutTranslationForRussian)) { - $this->assertArrayNotHasKey('ru', $languageNames); - } else { - $this->assertArrayHasKey('ru', $languageNames); + public function testGetScriptNames() + { + $translatedLocales = array(); + $rootLocales = $this->getRootLocales(); + + foreach ($rootLocales as $displayLocale) { + try { + Intl::getLanguageBundle()->getScriptNames($displayLocale); + $translatedLocales[] = $displayLocale; + } catch (NoSuchEntryException $e) { + } } - if (in_array($displayLocale, static::$localesWithoutTranslationForChinese)) { - $this->assertArrayNotHasKey('zh', $languageNames); - } else { - $this->assertArrayHasKey('zh', $languageNames); - } + $untranslatedLocales = array_diff($rootLocales, $translatedLocales); - if (in_array($displayLocale, static::$localesWithoutTranslationForGerman)) { - $this->assertArrayNotHasKey('de', $languageNames); - } else { - $this->assertArrayHasKey('de', $languageNames); - } + sort($untranslatedLocales); + + $this->assertEquals(static::$localesWithoutTranslationForAnyScript, $untranslatedLocales); + } + + public function provideTestedScripts() + { + return array_map( + function ($script) { return array($script); }, + array_keys(static::$localesWithoutTranslationForScript) + ); } /** - * @dataProvider provideRootLocales + * @dataProvider provideTestedScripts */ - public function testGetScriptNames($displayLocale) + public function testGetScriptName($script) { - try { - $scriptNames = static::$languageBundle->getScriptNames($displayLocale); - - if (in_array($displayLocale, static::$localesWithoutTranslationForAnyScript)) { - $this->fail('Did not expect any script translations for locale '.$displayLocale); - } - } catch (NoSuchEntryException $e) { - if (in_array($displayLocale, static::$localesWithoutTranslationForAnyScript)) { - return; + $translatedLocales = array(); + $rootLocales = $this->getRootLocales(); + + foreach ($rootLocales as $displayLocale) { + try { + Intl::getLanguageBundle()->getScriptName($script, null, $displayLocale); + $translatedLocales[] = $displayLocale; + } catch (NoSuchEntryException $e) { } - - throw $e; } - if (in_array($displayLocale, static::$localesWithoutTranslationForLatinScript)) { - $this->assertArrayNotHasKey('Latn', $scriptNames); - } else { - $this->assertArrayHasKey('Latn', $scriptNames); - } + $untranslatedLocales = array_diff($rootLocales, static::$localesWithoutTranslationForAnyScript, $translatedLocales); - if (in_array($displayLocale, static::$localesWithoutTranslationForSimplifiedScript)) { - $this->assertArrayNotHasKey('Hans', $scriptNames); - } else { - $this->assertArrayHasKey('Hans', $scriptNames); - } + sort($untranslatedLocales); - if (in_array($displayLocale, static::$localesWithoutTranslationForTraditionalScript)) { - $this->assertArrayNotHasKey('Hant', $scriptNames); - } else { - $this->assertArrayHasKey('Hant', $scriptNames); - } - - if (in_array($displayLocale, static::$localesWithoutTranslationForCyrillicScript)) { - $this->assertArrayNotHasKey('Cyrl', $scriptNames); - } else { - $this->assertArrayHasKey('Cyrl', $scriptNames); - } + $this->assertEquals(static::$localesWithoutTranslationForScript[$script], $untranslatedLocales); } /** @@ -202,8 +184,8 @@ public function testGetScriptNames($displayLocale) public function testGetLanguageNamesSupportsAliases($alias, $ofLocale) { $this->assertEquals( - static::$languageBundle->getLanguageNames($ofLocale), - static::$languageBundle->getLanguageNames($alias) + Intl::getLanguageBundle()->getLanguageNames($ofLocale), + Intl::getLanguageBundle()->getLanguageNames($alias) ); } @@ -214,8 +196,8 @@ public function testGetLanguageNamesSupportsAliases($alias, $ofLocale) public function testGetScriptNamesSupportsAliases($alias, $ofLocale) { $this->assertEquals( - static::$languageBundle->getScriptNames($ofLocale), - static::$languageBundle->getScriptNames($alias) + Intl::getLanguageBundle()->getScriptNames($ofLocale), + Intl::getLanguageBundle()->getScriptNames($alias) ); } @@ -224,10 +206,10 @@ public function testGetScriptNamesSupportsAliases($alias, $ofLocale) */ public function testGetLanguageNamesAndGetLanguageNameAreConsistent($displayLocale) { - $names = static::$languageBundle->getLanguageNames($displayLocale); + $names = Intl::getLanguageBundle()->getLanguageNames($displayLocale); foreach ($names as $language => $name) { - $this->assertSame($name, static::$languageBundle->getLanguageName($language, null, $displayLocale)); + $this->assertSame($name, Intl::getLanguageBundle()->getLanguageName($language, null, $displayLocale)); } } @@ -236,10 +218,10 @@ public function testGetLanguageNamesAndGetLanguageNameAreConsistent($displayLoca */ public function testGetScriptNamesAndGetScriptNameAreConsistent($displayLocale) { - $names = static::$languageBundle->getScriptNames($displayLocale); + $names = Intl::getLanguageBundle()->getScriptNames($displayLocale); foreach ($names as $script => $name) { - $this->assertSame($name, static::$languageBundle->getScriptName($script, null, $displayLocale)); + $this->assertSame($name, Intl::getLanguageBundle()->getScriptName($script, null, $displayLocale)); } } } diff --git a/src/Symfony/Component/Intl/Test/LocaleBundleConsistencyTestCase.php b/src/Symfony/Component/Intl/Test/LocaleBundleConsistencyTestCase.php index 542bce4847c14..38588b72b1a81 100644 --- a/src/Symfony/Component/Intl/Test/LocaleBundleConsistencyTestCase.php +++ b/src/Symfony/Component/Intl/Test/LocaleBundleConsistencyTestCase.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Intl\Test; +use Symfony\Component\Intl\Exception\NoSuchEntryException; use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\Test\ConsistencyTestCase; @@ -19,72 +20,68 @@ */ abstract class LocaleBundleConsistencyTestCase extends ConsistencyTestCase { - protected static $localesWithoutTranslationForThemselves = array('nmg'); - protected static $localesWithoutTranslationForEnglish = array('as', 'bo', 'dua', 'fo', 'gv', 'kl', 'kw', 'mgo', 'uz'); - protected static $localesWithoutTranslationForFrench = array('as', 'bo', 'dua', 'fo', 'gv', 'kl', 'kw', 'mgo', 'uz'); - protected static $localesWithoutTranslationForSpanish = array('as', 'bo', 'dua', 'fo', 'gv', 'jgo', 'kl', 'kw', 'lo', 'mgo', 'ps', 'uz'); - protected static $localesWithoutTranslationForRussian = array('as', 'dua', 'fo', 'gv', 'jgo', 'kl', 'kw', 'mgo', 'pa', 'uz'); - protected static $localesWithoutTranslationForChinese = array('as', 'dua', 'fo', 'gv', 'kl', 'kw', 'mgo', 'pa', 'rw', 'ti', 'uz'); - protected static $localesWithoutTranslationForGerman = array('as', 'bo', 'dua', 'fo', 'gv', 'kl', 'kw', 'mgo', 'uz'); + protected static $localesWithoutTranslationForAnyLocale = array(); + protected static $localesWithoutTranslationForLocale = array(); - /** - * @var \Symfony\Component\Intl\ResourceBundle\LocaleBundleInterface - */ - protected static $localeBundle; - - public static function setUpBeforeClass() + public function testGetLocales() { - static::$localeBundle = Intl::getLocaleBundle(); + $this->assertEquals($this->getLocales(), Intl::getLocaleBundle()->getLocales()); } - /** - * @dataProvider provideRootLocales - */ - public function testGetLocaleNames($displayLocale) + public function testGetLocaleAliases() { - $locales = static::$localeBundle->getLocaleNames($displayLocale); + $this->assertEquals($this->getLocaleAliases(), Intl::getLocaleBundle()->getLocaleAliases()); + } - if (in_array($displayLocale, static::$localesWithoutTranslationForThemselves)) { - $this->assertArrayNotHasKey($displayLocale, $locales); - } else { - $this->assertArrayHasKey($displayLocale, $locales); + public function testGetLocaleNames() + { + $translatedLocales = array(); + $rootLocales = $this->getRootLocales(); + + foreach ($rootLocales as $displayLocale) { + try { + Intl::getLocaleBundle()->getLocaleNames($displayLocale); + $translatedLocales[] = $displayLocale; + } catch (NoSuchEntryException $e) { + } } - if (in_array($displayLocale, static::$localesWithoutTranslationForEnglish)) { - $this->assertArrayNotHasKey('en', $locales); - } else { - $this->assertArrayHasKey('en', $locales); - } + $untranslatedLocales = array_diff($rootLocales, $translatedLocales); - if (in_array($displayLocale, static::$localesWithoutTranslationForFrench)) { - $this->assertArrayNotHasKey('fr', $locales); - } else { - $this->assertArrayHasKey('fr', $locales); - } + sort($untranslatedLocales); - if (in_array($displayLocale, static::$localesWithoutTranslationForSpanish)) { - $this->assertArrayNotHasKey('es', $locales); - } else { - $this->assertArrayHasKey('es', $locales); - } + $this->assertEquals(static::$localesWithoutTranslationForAnyLocale, $untranslatedLocales); + } - if (in_array($displayLocale, static::$localesWithoutTranslationForRussian)) { - $this->assertArrayNotHasKey('ru', $locales); - } else { - $this->assertArrayHasKey('ru', $locales); - } + public function provideTestedLocales() + { + return array_map( + function ($locale) { return array($locale); }, + array_keys(static::$localesWithoutTranslationForLocale) + ); + } - if (in_array($displayLocale, static::$localesWithoutTranslationForChinese)) { - $this->assertArrayNotHasKey('zh', $locales); - } else { - $this->assertArrayHasKey('zh', $locales); + /** + * @dataProvider provideTestedLocales + */ + public function testGetLocaleName($locale) + { + $translatedLocales = array(); + $rootLocales = $this->getRootLocales(); + + foreach ($rootLocales as $displayLocale) { + try { + Intl::getLocaleBundle()->getLocaleName($locale ?: $displayLocale, $displayLocale); + $translatedLocales[] = $displayLocale; + } catch (NoSuchEntryException $e) { + } } - if (in_array($displayLocale, static::$localesWithoutTranslationForGerman)) { - $this->assertArrayNotHasKey('de', $locales); - } else { - $this->assertArrayHasKey('de', $locales); - } + $untranslatedLocales = array_diff($rootLocales, static::$localesWithoutTranslationForAnyLocale, $translatedLocales); + + sort($untranslatedLocales); + + $this->assertEquals(static::$localesWithoutTranslationForLocale[$locale], $untranslatedLocales); } /** @@ -94,8 +91,8 @@ public function testGetLocaleNames($displayLocale) public function testGetLocaleNamesSupportsAliases($alias, $ofLocale) { $this->assertEquals( - static::$localeBundle->getLocaleNames($ofLocale), - static::$localeBundle->getLocaleNames($alias) + Intl::getLocaleBundle()->getLocaleNames($ofLocale), + Intl::getLocaleBundle()->getLocaleNames($alias) ); } @@ -104,10 +101,10 @@ public function testGetLocaleNamesSupportsAliases($alias, $ofLocale) */ public function testGetLocaleNamesAndGetLocaleNameAreConsistent($displayLocale) { - $names = static::$localeBundle->getLocaleNames($displayLocale); + $names = Intl::getLocaleBundle()->getLocaleNames($displayLocale); foreach ($names as $locale => $name) { - $this->assertSame($name, static::$localeBundle->getLocaleName($locale, $displayLocale)); + $this->assertSame($name, Intl::getLocaleBundle()->getLocaleName($locale, $displayLocale)); } } } diff --git a/src/Symfony/Component/Intl/Test/RegionBundleConsistencyTestCase.php b/src/Symfony/Component/Intl/Test/RegionBundleConsistencyTestCase.php index ab325dc89c00b..54af15d502c81 100644 --- a/src/Symfony/Component/Intl/Test/RegionBundleConsistencyTestCase.php +++ b/src/Symfony/Component/Intl/Test/RegionBundleConsistencyTestCase.php @@ -20,39 +20,17 @@ */ abstract class RegionBundleConsistencyTestCase extends ConsistencyTestCase { - protected static $localesWithoutTranslationForAnyCountry = array('ti'); - protected static $localesWithoutTranslationForUS = array('bem', 'dua', 'dyo', 'gv', 'ig', 'jgo', 'kl', 'kok', 'kw', 'mgo', 'nus', 'pa', 'ps', 'rw', 'uz'); - protected static $localesWithoutTranslationForDE = array('bem', 'dua', 'gv', 'ig', 'kl', 'kok', 'kw', 'mgh', 'mgo', 'nus', 'pa', 'rw', 'uz'); - protected static $localesWithoutTranslationForGB = array('bem', 'dua', 'dyo', 'fo', 'ig', 'jgo', 'kl', 'kok', 'mgh', 'mgo', 'nus', 'pa', 'rw', 'uz'); - protected static $localesWithoutTranslationForFR = array('bem', 'bo', 'dua', 'gv', 'ig', 'kl', 'kok', 'kw', 'mgo', 'nus', 'pa', 'rw', 'uz'); - protected static $localesWithoutTranslationForIT = array('bem', 'dua', 'gv', 'ig', 'kl', 'kok', 'kw', 'mgo', 'nus', 'pa', 'rw', 'uz'); - protected static $localesWithoutTranslationForBR = array('bem', 'bo', 'dua', 'gv', 'haw', 'ig', 'kl', 'kok', 'kw', 'mgh', 'mgo', 'pa', 'ps', 'rw', 'uz'); - protected static $localesWithoutTranslationForRU = array('bem', 'dua', 'dyo', 'gv', 'ig', 'kl', 'kok', 'kw', 'mgh', 'mgo', 'nus', 'pa', 'rw', 'uz'); - protected static $localesWithoutTranslationForCN = array('bem', 'dua', 'gv', 'kl', 'kok', 'kw', 'mgo', 'pa', 'rw', 'uz'); - - /** - * @var \Symfony\Component\Intl\ResourceBundle\RegionBundleInterface - */ - protected static $regionBundle; - - public static function setUpBeforeClass() - { - static::$regionBundle = Intl::getRegionBundle(); - } + protected static $localesWithoutTranslationForAnyCountry = array(); + protected static $localesWithoutTranslationForCountry = array(); public function provideLocalesWithCountries() { - $parameters = array(); - $localesWithoutScript = array_flip(static::$localesWithoutTranslationForAnyCountry); - $aliasesWithoutCountries = Intl::getLocaleBundle()->getLocaleAliases(); - $aliasesWithoutCountries = array_intersect_assoc($aliasesWithoutCountries, static::$localesWithoutTranslationForAnyCountry); - - $locales = Intl::getLocaleBundle()->getLocales(); + $aliasesWithoutCountries = array_intersect_assoc($this->getLocaleAliases(), static::$localesWithoutTranslationForAnyCountry); // remove locales that have no "Countries" block or are an alias to a locale // without "Countries" block - $locales = array_filter($locales, function ($locale) use ($localesWithoutScript, $aliasesWithoutCountries) { + $locales = array_filter($this->getLocales(), function ($locale) use ($localesWithoutScript, $aliasesWithoutCountries) { while (null !== $locale) { if (isset($localesWithoutScript[$locale]) || isset($aliasesWithoutCountries[$locale])) { return false; @@ -64,22 +42,18 @@ public function provideLocalesWithCountries() return true; }); - foreach ($locales as $locale) { - $parameters[] = array($locale); - } - - return $parameters; + return array_map( + function ($locale) { return array($locale); }, + $locales + ); } public function provideLocaleAliasesWithCountries() { - $parameters = array(); - - $aliases = Intl::getLocaleBundle()->getLocaleAliases(); $localesWithoutScript = array_flip(static::$localesWithoutTranslationForAnyCountry); // Remove aliases that point to a locale without "Countries" block - $aliases = array_filter($aliases, function ($targetLocale) use ($localesWithoutScript) { + $aliases = array_filter($this->getLocaleAliases(), function ($targetLocale) use ($localesWithoutScript) { while (null !== $targetLocale) { if (isset($localesWithoutScript[$targetLocale])) { return false; @@ -91,79 +65,62 @@ public function provideLocaleAliasesWithCountries() return true; }); - foreach ($aliases as $alias => $ofLocale) { - $parameters[] = array($alias, $ofLocale); - } - - return $parameters; + return array_map( + function ($alias, $ofLocale) { return array($alias, $ofLocale); }, + array_keys($aliases), + $aliases + ); } - /** - * @dataProvider provideRootLocales - */ - public function testGetCountryNames($displayLocale) + public function testGetCountryNames() { - try { - $countryNames = static::$regionBundle->getCountryNames($displayLocale); - - if (in_array($displayLocale, static::$localesWithoutTranslationForAnyCountry)) { - $this->fail('Did not expect any country translations for locale '.$displayLocale); + $translatedLocales = array(); + $rootLocales = $this->getRootLocales(); + + foreach ($rootLocales as $displayLocale) { + try { + Intl::getRegionBundle()->getCountryNames($displayLocale); + $translatedLocales[] = $displayLocale; + } catch (NoSuchEntryException $e) { } - } catch (NoSuchEntryException $e) { - if (in_array($displayLocale, static::$localesWithoutTranslationForAnyCountry)) { - return; - } - - throw $e; } - if (in_array($displayLocale, static::$localesWithoutTranslationForUS)) { - $this->assertArrayNotHasKey('US', $countryNames); - } else { - $this->assertArrayHasKey('US', $countryNames); - } + $untranslatedLocales = array_diff($rootLocales, $translatedLocales); - if (in_array($displayLocale, static::$localesWithoutTranslationForDE)) { - $this->assertArrayNotHasKey('DE', $countryNames); - } else { - $this->assertArrayHasKey('DE', $countryNames); - } + sort($untranslatedLocales); - if (in_array($displayLocale, static::$localesWithoutTranslationForGB)) { - $this->assertArrayNotHasKey('GB', $countryNames); - } else { - $this->assertArrayHasKey('GB', $countryNames); - } + $this->assertEquals(static::$localesWithoutTranslationForAnyCountry, $untranslatedLocales); + } - if (in_array($displayLocale, static::$localesWithoutTranslationForFR)) { - $this->assertArrayNotHasKey('FR', $countryNames); - } else { - $this->assertArrayHasKey('FR', $countryNames); - } + public function provideTestedCountries() + { + return array_map( + function ($country) { return array($country); }, + array_keys(static::$localesWithoutTranslationForCountry) + ); + } - if (in_array($displayLocale, static::$localesWithoutTranslationForIT)) { - $this->assertArrayNotHasKey('IT', $countryNames); - } else { - $this->assertArrayHasKey('IT', $countryNames); + /** + * @dataProvider provideTestedCountries + */ + public function testGetCountryName($country) + { + $translatedLocales = array(); + $rootLocales = $this->getRootLocales(); + + foreach ($rootLocales as $displayLocale) { + try { + Intl::getRegionBundle()->getCountryName($country, $displayLocale); + $translatedLocales[] = $displayLocale; + } catch (NoSuchEntryException $e) { + } } - if (in_array($displayLocale, static::$localesWithoutTranslationForBR)) { - $this->assertArrayNotHasKey('BR', $countryNames); - } else { - $this->assertArrayHasKey('BR', $countryNames); - } + $untranslatedLocales = array_diff($rootLocales, static::$localesWithoutTranslationForAnyCountry, $translatedLocales); - if (in_array($displayLocale, static::$localesWithoutTranslationForRU)) { - $this->assertArrayNotHasKey('RU', $countryNames); - } else { - $this->assertArrayHasKey('RU', $countryNames); - } + sort($untranslatedLocales); - if (in_array($displayLocale, static::$localesWithoutTranslationForCN)) { - $this->assertArrayNotHasKey('CN', $countryNames); - } else { - $this->assertArrayHasKey('CN', $countryNames); - } + $this->assertEquals(static::$localesWithoutTranslationForCountry[$country], $untranslatedLocales); } /** @@ -173,8 +130,8 @@ public function testGetCountryNames($displayLocale) public function testGetCountryNamesSupportsAliases($alias, $ofLocale) { $this->assertEquals( - static::$regionBundle->getCountryNames($ofLocale), - static::$regionBundle->getCountryNames($alias) + Intl::getRegionBundle()->getCountryNames($ofLocale), + Intl::getRegionBundle()->getCountryNames($alias) ); } @@ -183,10 +140,10 @@ public function testGetCountryNamesSupportsAliases($alias, $ofLocale) */ public function testGetCountryNamesAndGetCountryNameAreConsistent($displayLocale) { - $names = static::$regionBundle->getCountryNames($displayLocale); + $names = Intl::getRegionBundle()->getCountryNames($displayLocale); foreach ($names as $country => $name) { - $this->assertSame($name, static::$regionBundle->getCountryName($country, $displayLocale)); + $this->assertSame($name, Intl::getRegionBundle()->getCountryName($country, $displayLocale)); } } } From 9935cb8c6d992994c4dac4eaa1cfd8d107d0fec3 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Wed, 9 Oct 2013 14:17:12 +0200 Subject: [PATCH 32/48] [Intl] Changed transformation rules to include more information in the generated resource bundles --- .../Intl/ResourceBundle/CurrencyBundle.php | 2 +- .../Intl/ResourceBundle/LanguageBundle.php | 2 +- .../Intl/ResourceBundle/LocaleBundle.php | 4 +- .../Intl/ResourceBundle/RegionBundle.php | 2 +- .../ResourceBundle/Scanner/LocaleScanner.php | 2 +- .../Rule/CurrencyBundleTransformationRule.php | 48 +++++++++++++---- .../Rule/LanguageBundleTransformationRule.php | 51 ++++++++++++++++--- .../Rule/LocaleBundleTransformationRule.php | 9 ++-- .../Rule/RegionBundleTransformationRule.php | 44 +++++++++++++--- .../Scanner/LocaleScannerTest.php | 2 +- 10 files changed, 132 insertions(+), 34 deletions(-) diff --git a/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundle.php b/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundle.php index 0534cee87f484..8f22df8ecfe15 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundle.php @@ -31,7 +31,7 @@ class CurrencyBundle extends AbstractBundle implements CurrencyBundleInterface */ public function getLocales() { - $locales = $this->readEntry('misc', array('Locales')); + $locales = $this->readEntry('meta', array('Locales')); if ($locales instanceof \Traversable) { $locales = iterator_to_array($locales); diff --git a/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php b/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php index 10c369e58a08f..86b8d8a96d248 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php @@ -25,7 +25,7 @@ class LanguageBundle extends AbstractBundle implements LanguageBundleInterface */ public function getLocales() { - $locales = $this->readEntry('misc', array('Locales')); + $locales = $this->readEntry('meta', array('Locales')); if ($locales instanceof \Traversable) { $locales = iterator_to_array($locales); diff --git a/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php b/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php index fac0c10d558fb..24584cc8c8391 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php @@ -23,7 +23,7 @@ class LocaleBundle extends AbstractBundle implements LocaleBundleInterface */ public function getLocales() { - $locales = $this->readEntry('misc', array('Locales')); + $locales = $this->readEntry('meta', array('Locales')); if ($locales instanceof \Traversable) { $locales = iterator_to_array($locales); @@ -37,7 +37,7 @@ public function getLocales() */ public function getLocaleAliases() { - $aliases = $this->readEntry('misc', array('Aliases')); + $aliases = $this->readEntry('meta', array('Aliases')); if ($aliases instanceof \Traversable) { $aliases = iterator_to_array($aliases); diff --git a/src/Symfony/Component/Intl/ResourceBundle/RegionBundle.php b/src/Symfony/Component/Intl/ResourceBundle/RegionBundle.php index e9156c58c9790..85f8889b8f52b 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/RegionBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/RegionBundle.php @@ -23,7 +23,7 @@ class RegionBundle extends AbstractBundle implements RegionBundleInterface */ public function getLocales() { - $locales = $this->readEntry('misc', array('Locales')); + $locales = $this->readEntry('meta', array('Locales')); if ($locales instanceof \Traversable) { $locales = iterator_to_array($locales); diff --git a/src/Symfony/Component/Intl/ResourceBundle/Scanner/LocaleScanner.php b/src/Symfony/Component/Intl/ResourceBundle/Scanner/LocaleScanner.php index 2751365c5a518..cd3c62e78753e 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Scanner/LocaleScanner.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Scanner/LocaleScanner.php @@ -33,7 +33,7 @@ class LocaleScanner */ private static $blackList = array( 'root', - 'misc', + 'meta', 'supplementalData', 'supplementaldata', ); diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php index ad5e24823ae92..bb3a5b83224c2 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php @@ -11,6 +11,7 @@ namespace Symfony\Component\Intl\ResourceBundle\Transformer\Rule; +use Symfony\Component\Intl\Exception\RuntimeException; use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\ResourceBundle\CurrencyBundle; use Symfony\Component\Intl\ResourceBundle\CurrencyBundleInterface; @@ -55,32 +56,57 @@ public function beforeCompile(CompilationContext $context) $context->getFilesystem()->remove($tempDir); $context->getFilesystem()->mkdir(array($tempDir, $tempDir.'/res')); - // The currency data is contained in the locales and misc bundles + // The currency data is contained in the locales and meta bundles // in ICU <= 4.2 if (IcuVersion::compare($context->getIcuVersion(), '4.2', '<=', 1)) { $context->getFilesystem()->mirror($context->getSourceDir().'/locales', $tempDir.'/txt'); - $context->getFilesystem()->copy($context->getSourceDir().'/misc/supplementalData.txt', $tempDir.'/txt/misc.txt'); + $context->getFilesystem()->copy($context->getSourceDir().'/misc/supplementalData.txt', $tempDir.'/txt/meta.txt'); } else { $context->getFilesystem()->mirror($context->getSourceDir().'/curr', $tempDir.'/txt'); - $context->getFilesystem()->rename($tempDir.'/txt/supplementalData.txt', $tempDir.'/txt/misc.txt'); + $context->getFilesystem()->rename($tempDir.'/txt/supplementalData.txt', $tempDir.'/txt/meta.txt'); } - // Replace "supplementalData" in the file by "misc" before compilation - file_put_contents($tempDir.'/txt/misc.txt', str_replace('supplementalData', 'misc', file_get_contents($tempDir.'/txt/misc.txt'))); + // Replace "supplementalData" in the file by "meta" before compilation + file_put_contents($tempDir.'/txt/meta.txt', str_replace('supplementalData', 'meta', file_get_contents($tempDir.'/txt/meta.txt'))); - $context->getCompiler()->compile($tempDir.'/txt/misc.txt', $tempDir.'/res'); + $context->getCompiler()->compile($tempDir.'/txt', $tempDir.'/res'); - // Read file, add locales and write again + // Read file, add locales and currencies and write again $reader = new BinaryBundleReader(); - $data = iterator_to_array($reader->read($tempDir.'/res', 'misc')); + $meta = iterator_to_array($reader->read($tempDir.'/res', 'meta')); // Key must not exist - assert(!isset($data['Locales'])); + if (isset($meta['AvailableLocales'])) { + throw new RuntimeException('The key "AvailableLocales" should not exist.'); + } + + if (isset($meta['Currencies'])) { + throw new RuntimeException('The key "Currencies" should not exist.'); + } + + // Collect supported locales of the bundle + $meta['AvailableLocales'] = $context->getLocaleScanner()->scanLocales($tempDir.'/txt'); + + // Collect complete list of currencies in all locales + $meta['Currencies'] = array(); + + foreach ($meta['AvailableLocales'] as $locale) { + $bundle = $reader->read($tempDir.'/res', $locale); + + // isset() on \ResourceBundle returns true even if the value is null + if (isset($bundle['Currencies']) && null !== $bundle['Currencies']) { + $meta['Currencies'] = array_merge( + $meta['Currencies'], + array_keys(iterator_to_array($bundle['Currencies'])) + ); + } + } - $data['Locales'] = $context->getLocaleScanner()->scanLocales($tempDir.'/txt'); + $meta['Currencies'] = array_unique($meta['Currencies']); + sort($meta['Currencies']); $writer = new TextBundleWriter(); - $writer->write($tempDir.'/txt', 'misc', $data, false); + $writer->write($tempDir.'/txt', 'meta', $meta, false); // The temporary directory now contains all sources to be compiled return $tempDir.'/txt'; diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php index 47e65a1cfd528..140c8b9a46ec5 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php @@ -11,8 +11,10 @@ namespace Symfony\Component\Intl\ResourceBundle\Transformer\Rule; +use Symfony\Component\DependencyInjection\Tests\DefinitionDecoratorTest; use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\ResourceBundle\LanguageBundleInterface; +use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader; use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContext; use Symfony\Component\Intl\ResourceBundle\Transformer\StubbingContext; use Symfony\Component\Intl\ResourceBundle\Writer\TextBundleWriter; @@ -58,15 +60,50 @@ public function beforeCompile(CompilationContext $context) } $context->getFilesystem()->remove($tempDir); - $context->getFilesystem()->mirror($sourceDir, $tempDir); + $context->getFilesystem()->mkdir(array($tempDir, $tempDir.'/res')); + $context->getFilesystem()->mirror($sourceDir, $tempDir.'/txt'); - // Create misc file with all available locales + $context->getCompiler()->compile($tempDir.'/txt', $tempDir.'/res'); + + $meta = array( + 'AvailableLocales' => $context->getLocaleScanner()->scanLocales($tempDir.'/txt'), + 'Languages' => array(), + 'Scripts' => array(), + ); + + $reader = new BinaryBundleReader(); + + // Collect complete list of languages and scripts in all locales + foreach ($meta['AvailableLocales'] as $locale) { + $bundle = $reader->read($tempDir.'/res', $locale); + + // isset() on \ResourceBundle returns true even if the value is null + if (isset($bundle['Languages']) && null !== $bundle['Languages']) { + $meta['Languages'] = array_merge( + $meta['Languages'], + array_keys(iterator_to_array($bundle['Languages'])) + ); + } + + if (isset($bundle['Scripts']) && null !== $bundle['Scripts']) { + $meta['Scripts'] = array_merge( + $meta['Scripts'], + array_keys(iterator_to_array($bundle['Scripts'])) + ); + } + } + + $meta['Languages'] = array_unique($meta['Languages']); + sort($meta['Languages']); + + $meta['Scripts'] = array_unique($meta['Scripts']); + sort($meta['Scripts']); + + // Create meta file with all available locales $writer = new TextBundleWriter(); - $writer->write($tempDir, 'misc', array( - 'Locales' => $context->getLocaleScanner()->scanLocales($tempDir), - ), false); + $writer->write($tempDir.'/txt', 'meta', $meta, false); - return $tempDir; + return $tempDir.'/txt'; } /** @@ -74,6 +111,8 @@ public function beforeCompile(CompilationContext $context) */ public function afterCompile(CompilationContext $context) { + // Remove the temporary directory + $context->getFilesystem()->remove(sys_get_temp_dir().'/icu-data-languages'); } /** diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php index dc0b616b1f33c..99797b1e2df8f 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php @@ -17,6 +17,7 @@ use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\ResourceBundle\LanguageBundleInterface; use Symfony\Component\Intl\ResourceBundle\LocaleBundleInterface; +use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader; use Symfony\Component\Intl\ResourceBundle\RegionBundleInterface; use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContext; use Symfony\Component\Intl\ResourceBundle\Transformer\StubbingContext; @@ -82,11 +83,13 @@ public function beforeCompile(CompilationContext $context) $writer->write($tempDir, $alias, array('%%ALIAS' => $aliasOf)); } - // Create misc file with all available locales - $writer->write($tempDir, 'misc', array( + $meta = array( 'Locales' => $locales, 'Aliases' => $aliases, - ), false); + ); + + // Create meta file with all available locales + $writer->write($tempDir, 'meta', $meta, false); // Create empty root file, other wise locale fallback is not working $writer->write($tempDir, 'root', array('___' => '')); diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/RegionBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/RegionBundleTransformationRule.php index 541202ef32aea..5b34763216088 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/RegionBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/RegionBundleTransformationRule.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Intl\ResourceBundle\Transformer\Rule; use Symfony\Component\Intl\Intl; +use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader; use Symfony\Component\Intl\ResourceBundle\RegionBundleInterface; use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContext; use Symfony\Component\Intl\ResourceBundle\Transformer\StubbingContext; @@ -48,7 +49,7 @@ public function getBundleName() */ public function beforeCompile(CompilationContext $context) { - $tempDir = sys_get_temp_dir().'/icu-data-languages'; + $tempDir = sys_get_temp_dir().'/icu-data-regions'; // The region data is contained in the locales bundle in ICU <= 4.2 if (IcuVersion::compare($context->getIcuVersion(), '4.2', '<=', 1)) { @@ -58,15 +59,42 @@ public function beforeCompile(CompilationContext $context) } $context->getFilesystem()->remove($tempDir); - $context->getFilesystem()->mirror($sourceDir, $tempDir); + $context->getFilesystem()->mkdir(array($tempDir, $tempDir.'/res')); + $context->getFilesystem()->mirror($sourceDir, $tempDir.'/txt'); - // Create misc file with all available locales + $context->getCompiler()->compile($tempDir.'/txt', $tempDir.'/res'); + + $meta = array( + 'AvailableLocales' => $context->getLocaleScanner()->scanLocales($tempDir.'/txt'), + 'Countries' => array(), + ); + + $reader = new BinaryBundleReader(); + + // Collect complete list of countries in all locales + foreach ($meta['AvailableLocales'] as $locale) { + $bundle = $reader->read($tempDir.'/res', $locale); + + // isset() on \ResourceBundle returns true even if the value is null + if (isset($bundle['Countries']) && null !== $bundle['Countries']) { + $meta['Countries'] = array_merge( + $meta['Countries'], + array_keys(iterator_to_array($bundle['Countries'])) + ); + } + } + + $meta['Countries'] = array_unique($meta['Countries']); + $meta['Countries'] = array_filter($meta['Countries'], function ($country) { + return !ctype_digit((string) $country); + }); + sort($meta['Countries']); + + // Create meta file with all available locales $writer = new TextBundleWriter(); - $writer->write($tempDir, 'misc', array( - 'Locales' => $context->getLocaleScanner()->scanLocales($tempDir), - ), false); + $writer->write($tempDir.'/txt', 'meta', $meta, false); - return $tempDir; + return $tempDir.'/txt'; } /** @@ -74,6 +102,8 @@ public function beforeCompile(CompilationContext $context) */ public function afterCompile(CompilationContext $context) { + // Remove the temporary directory + $context->getFilesystem()->remove(sys_get_temp_dir().'/icu-data-regions'); } /** diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Scanner/LocaleScannerTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Scanner/LocaleScannerTest.php index aef40d484e340..2896bbf43b34d 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Scanner/LocaleScannerTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Scanner/LocaleScannerTest.php @@ -48,7 +48,7 @@ protected function setUp() $this->filesystem->touch($this->directory.'/root.txt'); $this->filesystem->touch($this->directory.'/supplementalData.txt'); $this->filesystem->touch($this->directory.'/supplementaldata.txt'); - $this->filesystem->touch($this->directory.'/misc.txt'); + $this->filesystem->touch($this->directory.'/meta.txt'); file_put_contents($this->directory.'/en_alias.txt', 'en_alias{"%%ALIAS"{"en"}}'); file_put_contents($this->directory.'/de_alias.txt', 'de_alias{"%%ALIAS"{"de"}}'); From 3ca410812e4f8648dcfc7a760ea35f7b1a1e041d Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 10 Oct 2013 09:50:52 +0200 Subject: [PATCH 33/48] [Intl] Tested Intl::getFallbackLocale() and let it return "root" for top-level locales --- src/Symfony/Component/Intl/Intl.php | 6 +++- src/Symfony/Component/Intl/Tests/IntlTest.php | 35 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 src/Symfony/Component/Intl/Tests/IntlTest.php diff --git a/src/Symfony/Component/Intl/Intl.php b/src/Symfony/Component/Intl/Intl.php index 1fd006c342b52..eaf38c6b5ab05 100644 --- a/src/Symfony/Component/Intl/Intl.php +++ b/src/Symfony/Component/Intl/Intl.php @@ -197,7 +197,11 @@ public static function getIcuStubVersion() public static function getFallbackLocale($locale) { if (false === $pos = strrpos($locale, '_')) { - return null; + if ('root' === $locale) { + return null; + } + + return 'root'; } return substr($locale, 0, $pos); diff --git a/src/Symfony/Component/Intl/Tests/IntlTest.php b/src/Symfony/Component/Intl/Tests/IntlTest.php new file mode 100644 index 0000000000000..f54d89ff5d92b --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/IntlTest.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Tests; + +use Symfony\Component\Intl\Intl; + +/** + * @author Bernhard Schussek + */ +class IntlTest extends \PHPUnit_Framework_TestCase +{ + public function testGetFallbackLocale() + { + $this->assertSame('fr', Intl::getFallbackLocale('fr_FR')); + } + + public function testGetFallbackLocaleForTopLevelLocale() + { + $this->assertSame('root', Intl::getFallbackLocale('en')); + } + + public function testGetFallbackLocaleForRoot() + { + $this->assertNull(Intl::getFallbackLocale('root')); + } +} From f4c82dd0d9c4c8de9e68ff16c559006644a409d1 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 10 Oct 2013 14:34:20 +0200 Subject: [PATCH 34/48] [Intl] Changed BinaryBundleReader to never set the fallback flag in \ResourceBundle --- ...hp => ResourceBundleNotFoundException.php} | 4 +- .../Reader/BinaryBundleReader.php | 22 +--- .../Reader/BundleEntryReader.php | 4 +- .../ResourceBundle/Reader/PhpBundleReader.php | 8 +- .../Reader/BinaryBundleReaderTest.php | 16 +-- .../Reader/BundleEntryReaderTest.php | 112 ++++++++++-------- .../Reader/PhpBundleReaderTest.php | 4 +- 7 files changed, 85 insertions(+), 85 deletions(-) rename src/Symfony/Component/Intl/Exception/{NoSuchLocaleException.php => ResourceBundleNotFoundException.php} (76%) diff --git a/src/Symfony/Component/Intl/Exception/NoSuchLocaleException.php b/src/Symfony/Component/Intl/Exception/ResourceBundleNotFoundException.php similarity index 76% rename from src/Symfony/Component/Intl/Exception/NoSuchLocaleException.php rename to src/Symfony/Component/Intl/Exception/ResourceBundleNotFoundException.php index 4853481c3952e..59da5ec0d53d5 100644 --- a/src/Symfony/Component/Intl/Exception/NoSuchLocaleException.php +++ b/src/Symfony/Component/Intl/Exception/ResourceBundleNotFoundException.php @@ -12,10 +12,8 @@ namespace Symfony\Component\Intl\Exception; /** - * Thrown when an invalid locale was requested. - * * @author Bernhard Schussek */ -class NoSuchLocaleException extends RuntimeException +class ResourceBundleNotFoundException extends RuntimeException { } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/BinaryBundleReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/BinaryBundleReader.php index bfb5993739bfa..fca9214b78488 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/BinaryBundleReader.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/BinaryBundleReader.php @@ -11,8 +11,7 @@ namespace Symfony\Component\Intl\ResourceBundle\Reader; -use Symfony\Component\Intl\Exception\RuntimeException; -use Symfony\Component\Intl\Exception\NoSuchLocaleException; +use Symfony\Component\Intl\Exception\ResourceBundleNotFoundException; use Symfony\Component\Intl\ResourceBundle\Util\ArrayAccessibleResourceBundle; /** @@ -29,25 +28,14 @@ public function read($path, $locale) { // Point for future extension: Modify this class so that it works also // if the \ResourceBundle class is not available. - $bundle = new \ResourceBundle($locale, $path); + + // Never enable fallback. We want to know if a bundle cannot be found + $bundle = new \ResourceBundle($locale, $path, false); // 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 RuntimeException(sprintf( - 'The resource bundle "%s/%s.res" could not be found.', - $path, - $locale - )); - } - - // The error U_USING_DEFAULT_WARNING appears if the locale is not found, - // no fallback can be used and the current default locale is used - // instead. - // Note that fallback to default is only working when a bundle contains - // a root.res file. - if (in_array($bundle->getErrorCode(), array(U_USING_DEFAULT_WARNING, U_USING_FALLBACK_WARNING), true)) { - throw new NoSuchLocaleException(sprintf( + throw new ResourceBundleNotFoundException(sprintf( 'The resource bundle "%s/%s.res" could not be found.', $path, $locale diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReader.php index f2d666a6788f3..dd1234c0d6c1f 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReader.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReader.php @@ -13,7 +13,7 @@ use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\Exception\NoSuchEntryException; -use Symfony\Component\Intl\Exception\NoSuchLocaleException; +use Symfony\Component\Intl\Exception\ResourceBundleNotFoundException; use Symfony\Component\Intl\Exception\OutOfBoundsException; use Symfony\Component\Intl\ResourceBundle\Util\RecursiveArrayAccess; @@ -125,7 +125,7 @@ public function readEntry($path, $locale, array $indices, $fallback = true) // If this or the previous entry was multi-valued, we are dealing // with a merged, multi-valued entry now $isMultiValued = $isMultiValued || $isCurrentMultiValued; - } catch (NoSuchLocaleException $e) { + } catch (ResourceBundleNotFoundException $e) { // Continue if there is a fallback locale for the current // locale $exception = $e; diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/PhpBundleReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/PhpBundleReader.php index af0db80e60c0b..739e6de39024a 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/PhpBundleReader.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/PhpBundleReader.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Intl\ResourceBundle\Reader; -use Symfony\Component\Intl\Exception\NoSuchLocaleException; +use Symfony\Component\Intl\Exception\ResourceBundleNotFoundException; use Symfony\Component\Intl\Exception\RuntimeException; /** @@ -26,14 +26,10 @@ class PhpBundleReader implements BundleReaderInterface */ public function read($path, $locale) { - if ('en' !== $locale) { - throw new NoSuchLocaleException('Only the locale "en" is supported.'); - } - $fileName = $path . '/' . $locale . '.php'; if (!file_exists($fileName)) { - throw new RuntimeException(sprintf( + throw new ResourceBundleNotFoundException(sprintf( 'The resource bundle "%s/%s.php" does not exist.', $path, $locale diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BinaryBundleReaderTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BinaryBundleReaderTest.php index eeb24daee36c6..526424d1a3d19 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BinaryBundleReaderTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BinaryBundleReaderTest.php @@ -50,32 +50,32 @@ public function testReadFollowsAlias() $this->assertFalse(isset($data['ExistsNot'])); } - public function testReadFollowsFallback() + public function testReadDoesNotFollowFallback() { // "ro_MD" -> "ro" $data = $this->reader->read(__DIR__.'/Fixtures/res', 'ro_MD'); $this->assertInstanceOf('\ArrayAccess', $data); $this->assertSame('Bam', $data['Baz']); - $this->assertTrue(isset($data['Foo']), 'entries from the fallback locale are reported to be set...'); - $this->assertNull($data['Foo'], '...but are always NULL. WTF.'); + $this->assertFalse(isset($data['Foo'])); + $this->assertNull($data['Foo']); $this->assertFalse(isset($data['ExistsNot'])); } - public function testReadFollowsFallbackAlias() + public function testReadDoesNotFollowFallbackAlias() { // "mo" = "ro_MD" -> "ro" $data = $this->reader->read(__DIR__.'/Fixtures/res', 'mo'); $this->assertInstanceOf('\ArrayAccess', $data); $this->assertSame('Bam', $data['Baz'], 'data from the aliased locale can be accessed'); - $this->assertTrue(isset($data['Foo']), 'entries from the fallback locale are reported to be set...'); - $this->assertNull($data['Foo'], '...but are always NULL. WTF.'); + $this->assertFalse(isset($data['Foo'])); + $this->assertNull($data['Foo']); $this->assertFalse(isset($data['ExistsNot'])); } /** - * @expectedException \Symfony\Component\Intl\Exception\NoSuchLocaleException + * @expectedException \Symfony\Component\Intl\Exception\ResourceBundleNotFoundException */ public function testReadFailsIfNonExistingLocale() { @@ -83,7 +83,7 @@ public function testReadFailsIfNonExistingLocale() } /** - * @expectedException \Symfony\Component\Intl\Exception\NoSuchLocaleException + * @expectedException \Symfony\Component\Intl\Exception\ResourceBundleNotFoundException */ public function testReadFailsIfNonExistingFallbackLocale() { diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BundleEntryReaderTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BundleEntryReaderTest.php index b81065455fcd5..52ed96ae568b8 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BundleEntryReaderTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BundleEntryReaderTest.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Intl\Tests\ResourceBundle\Reader; -use Symfony\Component\Intl\Exception\NoSuchLocaleException; +use Symfony\Component\Intl\Exception\ResourceBundleNotFoundException; use Symfony\Component\Intl\ResourceBundle\Reader\StructuredBundleReader; /** @@ -31,6 +31,35 @@ class BundleEntryReaderTest extends \PHPUnit_Framework_TestCase */ private $readerImpl; + private static $data = array( + 'Entries' => array( + 'Foo' => 'Bar', + 'Bar' => 'Baz', + ), + 'Foo' => 'Bar', + 'Version' => '2.0', + ); + + private static $fallbackData = array( + 'Entries' => array( + 'Foo' => 'Foo', + 'Bam' => 'Lah', + ), + 'Baz' => 'Foo', + 'Version' => '1.0', + ); + + private static $mergedData = array( + // no recursive merging -> too complicated + 'Entries' => array( + 'Foo' => 'Bar', + 'Bar' => 'Baz', + ), + 'Baz' => 'Foo', + 'Version' => '2.0', + 'Foo' => 'Bar', + ); + protected function setUp() { $this->readerImpl = $this->getMock('Symfony\Component\Intl\ResourceBundle\Reader\StructuredBundleReaderInterface'); @@ -39,38 +68,37 @@ protected function setUp() public function testForwardCallToRead() { - $data = array('foo', 'bar'); - $this->readerImpl->expects($this->once()) ->method('read') - ->with(self::RES_DIR, 'en') - ->will($this->returnValue($data)); + ->with(self::RES_DIR, 'root') + ->will($this->returnValue(self::$data)); - $this->assertSame($data, $this->reader->read(self::RES_DIR, 'en')); + $this->assertSame(self::$data, $this->reader->read(self::RES_DIR, 'root')); } public function testReadEntireDataFileIfNoIndicesGiven() { - $data = array('foo', 'bar'); - - $this->readerImpl->expects($this->once()) + $this->readerImpl->expects($this->at(0)) ->method('read') ->with(self::RES_DIR, 'en') - ->will($this->returnValue($data)); + ->will($this->returnValue(self::$data)); + + $this->readerImpl->expects($this->at(1)) + ->method('read') + ->with(self::RES_DIR, 'root') + ->will($this->returnValue(self::$fallbackData)); - $this->assertSame($data, $this->reader->readEntry(self::RES_DIR, 'en', array())); + $this->assertSame(self::$mergedData, $this->reader->readEntry(self::RES_DIR, 'en', array())); } public function testReadExistingEntry() { - $data = array('Foo' => array('Bar' => 'Baz')); - $this->readerImpl->expects($this->once()) ->method('read') - ->with(self::RES_DIR, 'en') - ->will($this->returnValue($data)); + ->with(self::RES_DIR, 'root') + ->will($this->returnValue(self::$data)); - $this->assertSame('Baz', $this->reader->readEntry(self::RES_DIR, 'en', array('Foo', 'Bar'))); + $this->assertSame('Bar', $this->reader->readEntry(self::RES_DIR, 'root', array('Entries', 'Foo'))); } /** @@ -78,33 +106,27 @@ public function testReadExistingEntry() */ public function testReadNonExistingEntry() { - $data = array('Foo' => 'Baz'); - $this->readerImpl->expects($this->once()) ->method('read') - ->with(self::RES_DIR, 'en') - ->will($this->returnValue($data)); + ->with(self::RES_DIR, 'root') + ->will($this->returnValue(self::$data)); - $this->reader->readEntry(self::RES_DIR, 'en', array('Foo', 'Bar')); + $this->reader->readEntry(self::RES_DIR, 'root', array('Entries', 'NonExisting')); } public function testFallbackIfEntryDoesNotExist() { - $data = array('Foo' => 'Bar'); - $this->readerImpl->expects($this->at(0)) ->method('read') ->with(self::RES_DIR, 'en_GB') - ->will($this->returnValue($data)); - - $fallbackData = array('Foo' => array('Bar' => 'Baz')); + ->will($this->returnValue(self::$data)); $this->readerImpl->expects($this->at(1)) ->method('read') ->with(self::RES_DIR, 'en') - ->will($this->returnValue($fallbackData)); + ->will($this->returnValue(self::$fallbackData)); - $this->assertSame('Baz', $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'))); + $this->assertSame('Lah', $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Entries', 'Bam'))); } /** @@ -112,14 +134,12 @@ public function testFallbackIfEntryDoesNotExist() */ public function testDontFallbackIfEntryDoesNotExistAndFallbackDisabled() { - $data = array('Foo' => 'Bar'); - $this->readerImpl->expects($this->once()) ->method('read') ->with(self::RES_DIR, 'en_GB') - ->will($this->returnValue($data)); + ->will($this->returnValue(self::$data)); - $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'), false); + $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Entries', 'Bam'), false); } public function testFallbackIfLocaleDoesNotExist() @@ -127,16 +147,14 @@ public function testFallbackIfLocaleDoesNotExist() $this->readerImpl->expects($this->at(0)) ->method('read') ->with(self::RES_DIR, 'en_GB') - ->will($this->throwException(new NoSuchLocaleException())); - - $fallbackData = array('Foo' => array('Bar' => 'Baz')); + ->will($this->throwException(new ResourceBundleNotFoundException())); $this->readerImpl->expects($this->at(1)) ->method('read') ->with(self::RES_DIR, 'en') - ->will($this->returnValue($fallbackData)); + ->will($this->returnValue(self::$fallbackData)); - $this->assertSame('Baz', $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'))); + $this->assertSame('Lah', $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Entries', 'Bam'))); } /** @@ -147,9 +165,9 @@ public function testDontFallbackIfLocaleDoesNotExistAndFallbackDisabled() $this->readerImpl->expects($this->once()) ->method('read') ->with(self::RES_DIR, 'en_GB') - ->will($this->throwException(new NoSuchLocaleException())); + ->will($this->throwException(new ResourceBundleNotFoundException())); - $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'), false); + $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Entries', 'Bam'), false); } public function provideMergeableValues() @@ -173,21 +191,21 @@ public function testMergeDataWithFallbackData($childData, $parentData, $result) if (null === $childData || is_array($childData)) { $this->readerImpl->expects($this->at(0)) ->method('read') - ->with(self::RES_DIR, 'en_GB') + ->with(self::RES_DIR, 'en') ->will($this->returnValue($childData)); $this->readerImpl->expects($this->at(1)) ->method('read') - ->with(self::RES_DIR, 'en') + ->with(self::RES_DIR, 'root') ->will($this->returnValue($parentData)); } else { $this->readerImpl->expects($this->once()) ->method('read') - ->with(self::RES_DIR, 'en_GB') + ->with(self::RES_DIR, 'en') ->will($this->returnValue($childData)); } - $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'en_GB', array(), true)); + $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'en', array(), true)); } /** @@ -211,21 +229,21 @@ public function testMergeExistingEntryWithExistingFallbackEntry($childData, $par if (null === $childData || is_array($childData)) { $this->readerImpl->expects($this->at(0)) ->method('read') - ->with(self::RES_DIR, 'en_GB') + ->with(self::RES_DIR, 'en') ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); $this->readerImpl->expects($this->at(1)) ->method('read') - ->with(self::RES_DIR, 'en') + ->with(self::RES_DIR, 'root') ->will($this->returnValue(array('Foo' => array('Bar' => $parentData)))); } else { $this->readerImpl->expects($this->once()) ->method('read') - ->with(self::RES_DIR, 'en_GB') + ->with(self::RES_DIR, 'en') ->will($this->returnValue(array('Foo' => array('Bar' => $childData)))); } - $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'en_GB', array('Foo', 'Bar'), true)); + $this->assertSame($result, $this->reader->readEntry(self::RES_DIR, 'en', array('Foo', 'Bar'), true)); } /** diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/PhpBundleReaderTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/PhpBundleReaderTest.php index a2a21d656c074..c3411e9de015c 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/PhpBundleReaderTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/PhpBundleReaderTest.php @@ -38,9 +38,9 @@ public function testReadReturnsArray() } /** - * @expectedException \Symfony\Component\Intl\Exception\NoSuchLocaleException + * @expectedException \Symfony\Component\Intl\Exception\ResourceBundleNotFoundException */ - public function testReadFailsIfLocaleOtherThanEn() + public function testReadFailsIfNonExistingLocale() { $this->reader->read(__DIR__ . '/Fixtures/php', 'foo'); } From 241e704b44b9e13ef2b8bd3b80be97702faa3973 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Wed, 9 Oct 2013 14:58:38 +0200 Subject: [PATCH 35/48] [Intl] Implemented new Locale class --- src/Symfony/Component/Intl/Intl.php | 23 +-- src/Symfony/Component/Intl/Locale.php | 145 ++++++++++++++++++ .../Intl/ResourceBundle/LocaleBundle.php | 59 ++----- .../ResourceBundle/LocaleBundleInterface.php | 3 + .../Rule/LocaleBundleTransformationRule.php | 80 +++++----- .../Test/LocaleBundleConsistencyTestCase.php | 110 ------------- .../DataProvider/AbstractDataProviderTest.php | 50 ++++++ .../AbstractLocaleDataProviderTest.php | 76 +++++++++ .../Component/Intl/Tests/LocaleTest.php | 62 ++++++++ .../Tests/ResourceBundle/LocaleBundleTest.php | 64 -------- 10 files changed, 400 insertions(+), 272 deletions(-) create mode 100644 src/Symfony/Component/Intl/Locale.php delete mode 100644 src/Symfony/Component/Intl/Test/LocaleBundleConsistencyTestCase.php create mode 100644 src/Symfony/Component/Intl/Tests/DataProvider/AbstractDataProviderTest.php create mode 100644 src/Symfony/Component/Intl/Tests/DataProvider/AbstractLocaleDataProviderTest.php create mode 100644 src/Symfony/Component/Intl/Tests/LocaleTest.php delete mode 100644 src/Symfony/Component/Intl/Tests/ResourceBundle/LocaleBundleTest.php diff --git a/src/Symfony/Component/Intl/Intl.php b/src/Symfony/Component/Intl/Intl.php index eaf38c6b5ab05..66045723afeb8 100644 --- a/src/Symfony/Component/Intl/Intl.php +++ b/src/Symfony/Component/Intl/Intl.php @@ -65,7 +65,7 @@ class Intl /** * @var BundleEntryReader */ - private static $bundleReader; + private static $entryReader; /** * Returns whether the intl extension is installed. @@ -85,7 +85,7 @@ public static function isExtensionLoaded() public static function getCurrencyBundle() { if (null === self::$currencyBundle) { - self::$currencyBundle = new IcuCurrencyBundle(self::getBundleReader()); + self::$currencyBundle = new IcuCurrencyBundle(self::getEntryReader()); } return self::$currencyBundle; @@ -99,7 +99,7 @@ public static function getCurrencyBundle() public static function getLanguageBundle() { if (null === self::$languageBundle) { - self::$languageBundle = new IcuLanguageBundle(self::getBundleReader()); + self::$languageBundle = new IcuLanguageBundle(self::getEntryReader()); } return self::$languageBundle; @@ -109,11 +109,14 @@ public static function getLanguageBundle() * Returns the bundle containing locale information. * * @return ResourceBundle\LocaleBundleInterface The locale resource bundle. + * + * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0. + * Use the {@link Locale} class instead. */ public static function getLocaleBundle() { if (null === self::$localeBundle) { - self::$localeBundle = new IcuLocaleBundle(self::getBundleReader()); + self::$localeBundle = new IcuLocaleBundle(self::getEntryReader()); } return self::$localeBundle; @@ -127,7 +130,7 @@ public static function getLocaleBundle() public static function getRegionBundle() { if (null === self::$regionBundle) { - self::$regionBundle = new IcuRegionBundle(self::getBundleReader()); + self::$regionBundle = new IcuRegionBundle(self::getEntryReader()); } return self::$regionBundle; @@ -212,20 +215,20 @@ public static function getFallbackLocale($locale) * * @return ResourceBundle\Reader\BundleEntryReaderInterface The resource reader. */ - private static function getBundleReader() + public static function getEntryReader() { - if (null === self::$bundleReader) { - self::$bundleReader = new BundleEntryReader(new BufferedBundleReader( + if (null === self::$entryReader) { + self::$entryReader = new BundleEntryReader(new BufferedBundleReader( IcuData::getBundleReader(), self::BUFFER_SIZE )); // Make sure that self::$bundleReader is already set to prevent // a cycle - self::$bundleReader->setLocaleAliases(self::getLocaleBundle()->getLocaleAliases()); + self::$entryReader->setLocaleAliases(Locale::getAliases()); } - return self::$bundleReader; + return self::$entryReader; } /** diff --git a/src/Symfony/Component/Intl/Locale.php b/src/Symfony/Component/Intl/Locale.php new file mode 100644 index 0000000000000..7f875f94f8d45 --- /dev/null +++ b/src/Symfony/Component/Intl/Locale.php @@ -0,0 +1,145 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl; + +use Symfony\Component\Icu\LocaleDataProvider; +use Symfony\Component\Intl\Exception\InvalidArgumentException; +use Symfony\Component\Intl\Exception\NoSuchEntryException; + +/** + * Provides access to locale-related data. + * + * @since 2.5 + * @author Bernhard Schussek + * + * @api + */ +class Locale extends \Locale +{ + /** + * @var IcuCurrencyDataProvider + */ + private static $dataProvider; + + /** + * @var string[]|null + */ + private static $locales; + + /** + * Returns all available locales. + * + * @return string[] A list of ICU locale codes + * + * @api + */ + public static function getLocales() + { + if (null === self::$locales) { + self::$locales = self::getDataProvider()->getLocales(); + } + + return self::$locales; + } + + /** + * Returns the name of a locale in a specified display locale. + * + * If there is no suitable name found for a display locale, the ICU locale + * code is used instead. + * + * If null is passed as display locale, the result of + * {@link \Locale::getDefault()} is used instead. + * + * @param string $locale The ICU locale code to return the name of (e.g. "de_AT") + * @param string $displayLocale The ICU locale code to return the name in + * + * @return string The name of the locale + * + * @api + */ + public static function getDisplayName($locale, $displayLocale = null) + { + if (!in_array($locale, self::getLocales(), true)) { + throw new InvalidArgumentException('The locale "' . $locale . '" does not exist.'); + } + + if (null !== $displayLocale && !in_array($displayLocale, self::getLocales(), true)) { + throw new InvalidArgumentException('The locale "' . $displayLocale . '" does not exist.'); + } + + if (null === $displayLocale) { + $displayLocale = \Locale::getDefault(); + } + + return self::getDataProvider()->getDisplayName($locale, $displayLocale); + } + + /** + * Returns the names of all known locales in a specified display locale. + * + * If null is passed as display locale, the result of + * {@link \Locale::getDefault()} is used instead. + * + * @param string $displayLocale The ICU locale code to return the names in + * + * @return string[] A list of locale names indexed by the corresponding ICU + * locale codes + * + * @api + */ + public static function getDisplayNames($displayLocale = null) + { + if (null !== $displayLocale && !in_array($displayLocale, Locale::getLocales(), true)) { + throw new InvalidArgumentException('The locale "' . $displayLocale . '" does not exist.'); + } + + if (null === $displayLocale) { + $displayLocale = \Locale::getDefault(); + } + + return self::getDataProvider()->getDisplayNames($displayLocale); + } + + /** + * Returns a list of locale aliases. + * + * @return string[] An array with locale aliases as keys and ICU locale + * codes as values + * + * @api + */ + public static function getAliases() + { + return self::getDataProvider()->getAliases(); + } + + /** + * @return LocaleDataProvider + */ + private static function getDataProvider() + { + if (null === self::$dataProvider) { + self::$dataProvider = new LocaleDataProvider( + LocaleDataProvider::getResourceDirectory(), + Intl::getEntryReader() + ); + } + + return self::$dataProvider; + } + + /** + * This class must not be instantiated. + */ + private function __construct() {} +} diff --git a/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php b/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php index 24584cc8c8391..fdf7cc3994884 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php @@ -11,70 +11,39 @@ namespace Symfony\Component\Intl\ResourceBundle; +use Symfony\Component\Icu\LocaleDataProvider; + /** * Default implementation of {@link LocaleBundleInterface}. * * @author Bernhard Schussek + * + * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0. + * Use {@link LocaleDataProvider} instead. */ -class LocaleBundle extends AbstractBundle implements LocaleBundleInterface +class LocaleBundle extends LocaleDataProvider implements LocaleBundleInterface { /** - * {@inheritdoc} - */ - public function getLocales() - { - $locales = $this->readEntry('meta', array('Locales')); - - if ($locales instanceof \Traversable) { - $locales = iterator_to_array($locales); - } - - return $locales; - } - - /** - * {@inheritdoc} + * Alias of {@link getDisplayNames()}. */ - public function getLocaleAliases() + public function getLocaleNames($locale = null) { - $aliases = $this->readEntry('meta', array('Aliases')); - - if ($aliases instanceof \Traversable) { - $aliases = iterator_to_array($aliases); - } - - return $aliases; + return $this->getDisplayNames($locale); } /** - * {@inheritdoc} + * Alias of {@link getDisplayName()}. */ public function getLocaleName($ofLocale, $locale = null) { - if (null === $locale) { - $locale = \Locale::getDefault(); - } - - return $this->readEntry($locale, array('Locales', $ofLocale)); + return $this->getDisplayName($ofLocale, $locale); } /** - * {@inheritdoc} + * Alias of {@link getAliases()}. */ - public function getLocaleNames($locale = null) + public function getLocaleAliases() { - if (null === $locale) { - $locale = \Locale::getDefault(); - } - - if (null === ($locales = $this->readEntry($locale, array('Locales')))) { - return array(); - } - - if ($locales instanceof \Traversable) { - $locales = iterator_to_array($locales); - } - - return $locales; + return $this->getAliases(); } } diff --git a/src/Symfony/Component/Intl/ResourceBundle/LocaleBundleInterface.php b/src/Symfony/Component/Intl/ResourceBundle/LocaleBundleInterface.php index 4905c6a4f7b48..2475389927090 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/LocaleBundleInterface.php +++ b/src/Symfony/Component/Intl/ResourceBundle/LocaleBundleInterface.php @@ -15,6 +15,9 @@ * Gives access to locale-related ICU data. * * @author Bernhard Schussek + * + * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0. + * Use {@link \Symfony\Component\Icu\LocaleDataProvider} instead. */ interface LocaleBundleInterface extends ResourceBundleInterface { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php index 99797b1e2df8f..bbdc86fa803e9 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php @@ -12,12 +12,10 @@ namespace Symfony\Component\Intl\ResourceBundle\Transformer\Rule; use Symfony\Component\Intl\Exception\NoSuchEntryException; -use Symfony\Component\Intl\Exception\NoSuchLocaleException; -use Symfony\Component\Intl\Exception\RuntimeException; +use Symfony\Component\Intl\Exception\ResourceBundleNotFoundException; use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\ResourceBundle\LanguageBundleInterface; use Symfony\Component\Intl\ResourceBundle\LocaleBundleInterface; -use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader; use Symfony\Component\Intl\ResourceBundle\RegionBundleInterface; use Symfony\Component\Intl\ResourceBundle\Transformer\CompilationContext; use Symfony\Component\Intl\ResourceBundle\Transformer\StubbingContext; @@ -83,16 +81,11 @@ public function beforeCompile(CompilationContext $context) $writer->write($tempDir, $alias, array('%%ALIAS' => $aliasOf)); } - $meta = array( - 'Locales' => $locales, + // Create root file which maps locale codes to locale codes, for fallback + $writer->write($tempDir, 'root', array( + 'Locales' => array_combine($locales, $locales), 'Aliases' => $aliases, - ); - - // Create meta file with all available locales - $writer->write($tempDir, 'meta', $meta, false); - - // Create empty root file, other wise locale fallback is not working - $writer->write($tempDir, 'root', array('___' => '')); + )); return $tempDir; } @@ -124,46 +117,21 @@ public function afterCreateStub(StubbingContext $context) private function generateTextFiles(TextBundleWriter $writer, $targetDirectory, array $locales, array $aliases) { - // Collect locales for which translations exist - $displayLocales = array_unique(array_merge( - $this->languageBundle->getLocales(), - $this->regionBundle->getLocales() - )); - // Flip to facilitate lookup - $displayLocales = array_flip($displayLocales); $locales = array_flip($locales); // Don't generate names for aliases (names will be generated for the // locale they are duplicating) - $displayLocales = array_diff_key($displayLocales, $aliases); - - // Generate a list of (existing) locale fallbacks - $fallbacks = array(); - - foreach ($displayLocales as $displayLocale => $_) { - $fallbacks[$displayLocale] = null; - $fallback = $displayLocale; - - // Recursively search for a fallback locale until one is found - while (null !== ($fallback = Intl::getFallbackLocale($fallback))) { - // Currently, no locale has an alias as fallback locale. - // If this starts to be the case, we need to add code here. - assert(!isset($aliases[$fallback])); - - // Check whether the fallback exists - if (isset($displayLocales[$fallback])) { - $fallbacks[$displayLocale] = $fallback; - break; - } - } - } + $displayLocales = array_diff_key($locales, $aliases); // Since fallbacks are always shorter than their source, we can sort // the display locales so that fallbacks are always processed before // their variants ksort($displayLocales); + // Generate a list of (existing) locale fallbacks + $fallbackMapping = $this->generateFallbackMapping($displayLocales, $aliases); + $localeNames = array(); // Generate locale names for all locales that have translations in @@ -181,6 +149,7 @@ private function generateTextFiles(TextBundleWriter $writer, $targetDirectory, a $localeNames[$displayLocale][$locale] = $name; } } catch (NoSuchEntryException $e) { + } catch (ResourceBundleNotFoundException $e) { } } @@ -188,8 +157,8 @@ private function generateTextFiles(TextBundleWriter $writer, $targetDirectory, a // keep the differences $fallback = $displayLocale; - while (isset($fallbacks[$fallback])) { - $fallback = $fallbacks[$fallback]; + while (isset($fallbackMapping[$fallback])) { + $fallback = $fallbackMapping[$fallback]; $localeNames[$displayLocale] = array_diff( $localeNames[$displayLocale], $localeNames[$fallback] @@ -275,4 +244,29 @@ private function generateLocaleName($locale, $displayLocale) return $name; } + + private function generateFallbackMapping(array $displayLocales, array $aliases) + { + $mapping = array(); + + foreach ($displayLocales as $displayLocale => $_) { + $mapping[$displayLocale] = null; + $fallback = $displayLocale; + + // Recursively search for a fallback locale until one is found + while (null !== ($fallback = Intl::getFallbackLocale($fallback))) { + // Currently, no locale has an alias as fallback locale. + // If this starts to be the case, we need to add code here. + assert(!isset($aliases[$fallback])); + + // Check whether the fallback exists + if (isset($displayLocales[$fallback])) { + $mapping[$displayLocale] = $fallback; + break; + } + } + } + + return $mapping; + } } diff --git a/src/Symfony/Component/Intl/Test/LocaleBundleConsistencyTestCase.php b/src/Symfony/Component/Intl/Test/LocaleBundleConsistencyTestCase.php deleted file mode 100644 index 38588b72b1a81..0000000000000 --- a/src/Symfony/Component/Intl/Test/LocaleBundleConsistencyTestCase.php +++ /dev/null @@ -1,110 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Intl\Test; - -use Symfony\Component\Intl\Exception\NoSuchEntryException; -use Symfony\Component\Intl\Intl; -use Symfony\Component\Intl\Test\ConsistencyTestCase; - -/** - * @author Bernhard Schussek - */ -abstract class LocaleBundleConsistencyTestCase extends ConsistencyTestCase -{ - protected static $localesWithoutTranslationForAnyLocale = array(); - protected static $localesWithoutTranslationForLocale = array(); - - public function testGetLocales() - { - $this->assertEquals($this->getLocales(), Intl::getLocaleBundle()->getLocales()); - } - - public function testGetLocaleAliases() - { - $this->assertEquals($this->getLocaleAliases(), Intl::getLocaleBundle()->getLocaleAliases()); - } - - public function testGetLocaleNames() - { - $translatedLocales = array(); - $rootLocales = $this->getRootLocales(); - - foreach ($rootLocales as $displayLocale) { - try { - Intl::getLocaleBundle()->getLocaleNames($displayLocale); - $translatedLocales[] = $displayLocale; - } catch (NoSuchEntryException $e) { - } - } - - $untranslatedLocales = array_diff($rootLocales, $translatedLocales); - - sort($untranslatedLocales); - - $this->assertEquals(static::$localesWithoutTranslationForAnyLocale, $untranslatedLocales); - } - - public function provideTestedLocales() - { - return array_map( - function ($locale) { return array($locale); }, - array_keys(static::$localesWithoutTranslationForLocale) - ); - } - - /** - * @dataProvider provideTestedLocales - */ - public function testGetLocaleName($locale) - { - $translatedLocales = array(); - $rootLocales = $this->getRootLocales(); - - foreach ($rootLocales as $displayLocale) { - try { - Intl::getLocaleBundle()->getLocaleName($locale ?: $displayLocale, $displayLocale); - $translatedLocales[] = $displayLocale; - } catch (NoSuchEntryException $e) { - } - } - - $untranslatedLocales = array_diff($rootLocales, static::$localesWithoutTranslationForAnyLocale, $translatedLocales); - - sort($untranslatedLocales); - - $this->assertEquals(static::$localesWithoutTranslationForLocale[$locale], $untranslatedLocales); - } - - /** - * @dataProvider provideLocaleAliases - * @group locale-alias-based - */ - public function testGetLocaleNamesSupportsAliases($alias, $ofLocale) - { - $this->assertEquals( - Intl::getLocaleBundle()->getLocaleNames($ofLocale), - Intl::getLocaleBundle()->getLocaleNames($alias) - ); - } - - /** - * @dataProvider provideLocales - */ - public function testGetLocaleNamesAndGetLocaleNameAreConsistent($displayLocale) - { - $names = Intl::getLocaleBundle()->getLocaleNames($displayLocale); - - foreach ($names as $locale => $name) { - $this->assertSame($name, Intl::getLocaleBundle()->getLocaleName($locale, $displayLocale)); - } - } -} diff --git a/src/Symfony/Component/Intl/Tests/DataProvider/AbstractDataProviderTest.php b/src/Symfony/Component/Intl/Tests/DataProvider/AbstractDataProviderTest.php new file mode 100644 index 0000000000000..c9e50d989c6f1 --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/DataProvider/AbstractDataProviderTest.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Tests\DataProvider; + +/** + * @author Bernhard Schussek + */ +abstract class AbstractDataProviderTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + \Locale::setDefault('en'); + } + + public function provideLocales() + { + return array_map( + function ($locale) { return array($locale); }, + $this->getLocales() + ); + } + + public function provideLocaleAliases() + { + return array_map( + function ($alias, $ofLocale) { return array($alias, $ofLocale); }, + array_keys($this->getLocaleAliases()), + $this->getLocaleAliases() + ); + } + + protected static function getLocales() + { + return array(); + } + + protected static function getLocaleAliases() + { + return array(); + } +} diff --git a/src/Symfony/Component/Intl/Tests/DataProvider/AbstractLocaleDataProviderTest.php b/src/Symfony/Component/Intl/Tests/DataProvider/AbstractLocaleDataProviderTest.php new file mode 100644 index 0000000000000..a7e15a8b2e9d7 --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/DataProvider/AbstractLocaleDataProviderTest.php @@ -0,0 +1,76 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Tests\DataProvider; + +/** + * @author Bernhard Schussek + */ +abstract class AbstractLocaleDataProviderTest extends AbstractDataProviderTest +{ + /** + * @var \Symfony\Component\Icu\LocaleDataProvider + */ + protected $dataProvider; + + protected function setUp() + { + $this->dataProvider = $this->createDataProvider(); + } + + abstract protected function createDataProvider(); + + public function testGetLocales() + { + $this->assertEquals($this->getLocales(), $this->dataProvider->getLocales()); + } + + public function testGetLocaleAliases() + { + $this->assertEquals($this->getLocaleAliases(), $this->dataProvider->getAliases()); + } + + /** + * @dataProvider provideLocales + */ + public function testGetDisplayNames($displayLocale) + { + $locales = array_keys($this->dataProvider->getDisplayNames($displayLocale)); + + sort($locales); + + $this->assertEquals($this->getLocales(), $locales); + } + + /** + * @dataProvider provideLocaleAliases + * @group locale-alias-based + */ + public function testGetDisplayNamesSupportsAliases($alias, $ofLocale) + { + $this->assertEquals( + $this->dataProvider->getDisplayNames($ofLocale), + $this->dataProvider->getDisplayNames($alias) + ); + } + + /** + * @dataProvider provideLocales + */ + public function testGetDisplayName($displayLocale) + { + $names = $this->dataProvider->getDisplayNames($displayLocale); + + foreach ($names as $locale => $name) { + $this->assertSame($name, $this->dataProvider->getDisplayName($locale, $displayLocale)); + } + } +} diff --git a/src/Symfony/Component/Intl/Tests/LocaleTest.php b/src/Symfony/Component/Intl/Tests/LocaleTest.php new file mode 100644 index 0000000000000..006669c4f271d --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/LocaleTest.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Tests; + +use Symfony\Component\Intl\Locale; + +/** + * @author Bernhard Schussek + */ +class LocaleTest extends \PHPUnit_Framework_TestCase +{ + protected function setUp() + { + Locale::setDefault('en'); + } + + public function testGetDisplayName() + { + $this->assertSame('English', Locale::getDisplayName('en', 'en')); + } + + /** + * @expectedException \Symfony\Component\Intl\Exception\InvalidArgumentException + */ + public function testGetDisplayNameFailsOnInvalidLocale() + { + Locale::getDisplayName('foo'); + } + + /** + * @expectedException \Symfony\Component\Intl\Exception\InvalidArgumentException + */ + public function testGetDisplayNameFailsOnInvalidDisplayLocale() + { + Locale::getDisplayName('en', 'foo'); + } + + public function testGetDisplayNames() + { + $names = Locale::getDisplayNames('en'); + + $this->assertArrayHasKey('en', $names); + $this->assertSame('English', $names['en']); + } + + /** + * @expectedException \Symfony\Component\Intl\Exception\InvalidArgumentException + */ + public function testGetDisplayNamesFailsOnInvalidDisplayLocale() + { + Locale::getDisplayNames('foo'); + } +} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/LocaleBundleTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/LocaleBundleTest.php deleted file mode 100644 index ddfdc3d2485ff..0000000000000 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/LocaleBundleTest.php +++ /dev/null @@ -1,64 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Intl\Tests\ResourceBundle; - -use Symfony\Component\Intl\ResourceBundle\LocaleBundle; - -/** - * @author Bernhard Schussek - */ -class LocaleBundleTest extends \PHPUnit_Framework_TestCase -{ - const RES_DIR = '/base/locales'; - - /** - * @var LocaleBundle - */ - private $bundle; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $reader; - - protected function setUp() - { - $this->reader = $this->getMock('Symfony\Component\Intl\ResourceBundle\Reader\StructuredBundleReaderInterface'); - $this->bundle = new LocaleBundle(self::RES_DIR, $this->reader); - } - - public function testGetLocaleName() - { - $this->reader->expects($this->once()) - ->method('readEntry') - ->with(self::RES_DIR, 'en', array('Locales', 'de_AT')) - ->will($this->returnValue('German (Austria)')); - - $this->assertSame('German (Austria)', $this->bundle->getLocaleName('de_AT', 'en')); - } - - public function testGetLocaleNames() - { - $sortedLocales = array( - 'en_IE' => 'English (Ireland)', - 'en_GB' => 'English (United Kingdom)', - 'en_US' => 'English (United States)', - ); - - $this->reader->expects($this->once()) - ->method('readEntry') - ->with(self::RES_DIR, 'en', array('Locales')) - ->will($this->returnValue($sortedLocales)); - - $this->assertSame($sortedLocales, $this->bundle->getLocaleNames('en')); - } -} From 43076d6ad20c8fa6685b620160df3c04bda54a8d Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Thu, 10 Oct 2013 16:51:54 +0200 Subject: [PATCH 36/48] [Intl] Fixed TextWriter to output integer arrays indexed by strings as tables instead of intvectors --- .../Intl/ResourceBundle/Writer/TextBundleWriter.php | 9 +++++---- .../Intl/Tests/ResourceBundle/Writer/Fixtures/en.txt | 5 +++++ .../Tests/ResourceBundle/Writer/TextBundleWriterTest.php | 2 ++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php b/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php index 563b3ba141ab5..310126c510c24 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php @@ -82,15 +82,16 @@ private function writeResource($file, $value, $indentation, $requireBraces = tru } if (is_array($value)) { - if (count($value) === count(array_filter($value, 'is_int'))) { + $intValues = count($value) === count(array_filter($value, 'is_int')); + $intKeys = count($value) === count(array_filter(array_keys($value), 'is_int')); + + if ($intValues && $intKeys) { $this->writeIntVector($file, $value, $indentation); return; } - $keys = array_keys($value); - - if (count($keys) === count(array_filter($keys, 'is_int'))) { + if ($intKeys) { $this->writeArray($file, $value, $indentation); return; diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/en.txt b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/en.txt index 0ee0d7f2f5a35..04b4584e89602 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/en.txt +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/en.txt @@ -14,6 +14,11 @@ en{ 2, 3, } + IntVectorWithStringKeys{ + a:int{0} + b:int{1} + c:int{2} + } FalseBoolean{"false"} TrueBoolean{"true"} Null{""} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php index f55497a622966..0aa035794a353 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php @@ -54,6 +54,7 @@ public function testWrite() 'Array' => array('foo', 'bar', array('Key' => 'value')), 'Integer' => 5, 'IntVector' => array(0, 1, 2, 3), + 'IntVectorWithStringKeys' => array('a' => 0, 'b' => 1, 'c' => 2), 'FalseBoolean' => false, 'TrueBoolean' => true, 'Null' => null, @@ -72,6 +73,7 @@ public function testWriteTraversable() 'Array' => array('foo', 'bar', array('Key' => 'value')), 'Integer' => 5, 'IntVector' => array(0, 1, 2, 3), + 'IntVectorWithStringKeys' => array('a' => 0, 'b' => 1, 'c' => 2), 'FalseBoolean' => false, 'TrueBoolean' => true, 'Null' => null, From e271491e7053aa3ecdfc01e273fefbe441fdb3c1 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Mon, 14 Oct 2013 10:48:57 +0200 Subject: [PATCH 37/48] [Intl] Implemented Currency class --- src/Symfony/Component/Intl/Currency.php | 254 ++++++++++++++++++ src/Symfony/Component/Intl/Intl.php | 3 + .../Intl/NumberFormatter/NumberFormatter.php | 12 +- .../Intl/ResourceBundle/CurrencyBundle.php | 60 +---- .../CurrencyBundleInterface.php | 3 + .../Rule/CurrencyBundleTransformationRule.php | 96 ++++--- .../CurrencyBundleConsistencyTestCase.php | 144 ---------- .../Component/Intl/Tests/CurrencyTest.php | 117 ++++++++ .../AbstractCurrencyDataProviderTest.php | 155 +++++++++++ .../ResourceBundle/CurrencyBundleTest.php | 98 ------- 10 files changed, 613 insertions(+), 329 deletions(-) create mode 100644 src/Symfony/Component/Intl/Currency.php delete mode 100644 src/Symfony/Component/Intl/Test/CurrencyBundleConsistencyTestCase.php create mode 100644 src/Symfony/Component/Intl/Tests/CurrencyTest.php create mode 100644 src/Symfony/Component/Intl/Tests/DataProvider/AbstractCurrencyDataProviderTest.php delete mode 100644 src/Symfony/Component/Intl/Tests/ResourceBundle/CurrencyBundleTest.php diff --git a/src/Symfony/Component/Intl/Currency.php b/src/Symfony/Component/Intl/Currency.php new file mode 100644 index 0000000000000..4709af8d6d2ab --- /dev/null +++ b/src/Symfony/Component/Intl/Currency.php @@ -0,0 +1,254 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl; + +use Symfony\Component\Icu\CurrencyDataProvider; +use Symfony\Component\Intl\Exception\InvalidArgumentException; + +/** + * Provides access to currency-related data. + * + * @since 2.4 + * @author Bernhard Schussek + * + * @api + */ +class Currency +{ + /** + * @var IcuCurrencyDataProvider + */ + private static $dataProvider; + + /** + * @var string[]|null + */ + private static $currencies; + + /** + * Returns all available currencies. + * + * @return string[] An array of ISO 4217 currency codes + * + * @api + */ + public static function getCurrencies() + { + if (null === self::$currencies) { + self::$currencies = self::getDataProvider()->getCurrencies(); + } + + return self::$currencies; + } + + /** + * Returns the symbol of a currency in the given locale. + * + * For example, the symbol of the US Dollar ("USD") in the locale "en_US" is + * "$". If the resource data for the given locale contains no entry for the + * given currency, then the ISO 4217 currency code is returned. + * + * If null is passed as locale, the result of + * {@link \Locale::getDefault()} is used instead. + * + * @param string $currency An ISO 4217 currency code (e.g. "EUR") + * @param string $displayLocale The ICU locale code to return the symbol in + * + * @return string The currency symbol for the specified locale + * + * @throws Exception\InvalidArgumentException If the currency or the locale + * is invalid + * + * @api + */ + public static function getSymbol($currency, $displayLocale = null) + { + if (!in_array($currency, self::getCurrencies(), true)) { + throw new InvalidArgumentException('The currency "' . $currency . '" does not exist.'); + } + + if (null !== $displayLocale && !in_array($displayLocale, Locale::getLocales(), true)) { + throw new InvalidArgumentException('The locale "' . $displayLocale . '" does not exist.'); + } + + if (null === $displayLocale) { + $displayLocale = \Locale::getDefault(); + } + + try { + return self::getDataProvider()->getSymbol($currency, $displayLocale); + } catch (NoSuchEntryException $e) { + return $currency; + } + } + + /** + * Returns the name for this currency in the given locale. + * + * For example, the name of the Euro ("EUR") in the locale "ru_RU" is + * "Евро". If the resource data for the given locale contains no entry for + * the given currency, then the ISO 4217 currency code is returned. + * + * If null is passed as locale, the result of + * {@link \Locale::getDefault()} is used instead. + * + * @param string $currency An ISO 4217 currency code (e.g. "EUR") + * @param string $displayLocale The ICU locale code to return the name in + * + * @return string The name of the currency + * + * @throws Exception\InvalidArgumentException If the currency or the locale + * is invalid + * + * @api + */ + public static function getDisplayName($currency, $displayLocale = null) + { + if (!in_array($currency, self::getCurrencies(), true)) { + throw new InvalidArgumentException('The currency "' . $currency . '" does not exist.'); + } + + if (null !== $displayLocale && !in_array($displayLocale, Locale::getLocales(), true)) { + throw new InvalidArgumentException('The locale "' . $displayLocale . '" does not exist.'); + } + + if (null === $displayLocale) { + $displayLocale = \Locale::getDefault(); + } + + try { + return self::getDataProvider()->getDisplayName($currency, $displayLocale); + } catch (NoSuchEntryException $e) { + return $currency; + } + } + + /** + * Returns the names of all known currencies in the specified locale. + * + * If the resource data for the given locale contains no entry for a + * currency, then the ISO 4217 currency code is used instead. + * + * If null is passed as locale, the result of + * {@link \Locale::getDefault()} is used instead. + * + * @param string $displayLocale The ICU locale code to return the names in + * + * @return string[] An array of currency names indexed by currency codes + * + * @throws Exception\InvalidArgumentException If the locale is invalid + * + * @api + */ + public static function getDisplayNames($displayLocale = null) + { + if (null !== $displayLocale && !in_array($displayLocale, Locale::getLocales(), true)) { + throw new InvalidArgumentException('The locale "' . $displayLocale . '" does not exist.'); + } + + if (null === $displayLocale) { + $displayLocale = \Locale::getDefault(); + } + + return self::getDataProvider()->getDisplayNames($displayLocale); + } + + /** + * Returns the default number of fraction digits used with a currency. + * + * For example, the default number of fraction digits for the Euro is 2, + * while for the Japanese Yen it's 0. + * + * @param string $currency An ISO 4217 currency code (e.g. "EUR") + * + * @return integer The number of digits after the comma + * + * @throws Exception\InvalidArgumentException If the currency is invalid + * + * @api + */ + public static function getFractionDigits($currency) + { + if (!in_array($currency, self::getCurrencies(), true)) { + throw new InvalidArgumentException('The currency "' . $currency . '" does not exist.'); + } + + return self::getDataProvider()->getFractionDigits($currency); + } + + /** + * Returns the rounding increment of a currency. + * + * The rounding increment indicates to which number a currency is rounded. + * For example, 1230 rounded to the nearest 50 is 1250. 1.234 rounded to the + * nearest 0.65 is 1.3. + * + * @param string $currency An ISO 4217 currency code (e.g. "EUR") + * + * @return integer The rounding increment + * + * @throws Exception\InvalidArgumentException If the currency is invalid + * + * @api + */ + public static function getRoundingIncrement($currency) + { + if (!in_array($currency, self::getCurrencies(), true)) { + throw new InvalidArgumentException('The currency "' . $currency . '" does not exist.'); + } + + return self::getDataProvider()->getRoundingIncrement($currency); + } + + /** + * Returns the ISO 4217 numeric code of a currency. + * + * For example, the numeric code of the Canadian Dollar ("CAD") is 124. If + * no numeric code is available for a currency, 0 is returned. + * + * @param string $currency An ISO 4217 currency code (e.g. "EUR") + * + * @return integer The numeric code + * + * @throws Exception\InvalidArgumentException If the currency is invalid + * + * @api + */ + public static function getNumericCode($currency) + { + if (!in_array($currency, self::getCurrencies(), true)) { + throw new InvalidArgumentException('The currency "' . $currency . '" does not exist.'); + } + + return self::getDataProvider()->getNumericCode($currency); + } + + /** + * @return CurrencyDataProvider + */ + private static function getDataProvider() + { + if (null === self::$dataProvider) { + self::$dataProvider = new CurrencyDataProvider( + CurrencyDataProvider::getResourceDirectory(), + Intl::getEntryReader() + ); + } + + return self::$dataProvider; + } + + /** + * This class must not be instantiated. + */ + private function __construct() {} +} diff --git a/src/Symfony/Component/Intl/Intl.php b/src/Symfony/Component/Intl/Intl.php index 66045723afeb8..22fb1b450e0d8 100644 --- a/src/Symfony/Component/Intl/Intl.php +++ b/src/Symfony/Component/Intl/Intl.php @@ -81,6 +81,9 @@ public static function isExtensionLoaded() * Returns the bundle containing currency information. * * @return ResourceBundle\CurrencyBundleInterface The currency resource bundle. + * + * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0. + * Use the {@link Currency} class instead. */ public static function getCurrencyBundle() { diff --git a/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php b/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php index 8b3388e7ae373..a1929ae06f3b4 100644 --- a/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php +++ b/src/Symfony/Component/Intl/NumberFormatter/NumberFormatter.php @@ -16,8 +16,8 @@ use Symfony\Component\Intl\Exception\MethodArgumentNotImplementedException; use Symfony\Component\Intl\Exception\MethodArgumentValueNotImplementedException; use Symfony\Component\Intl\Globals\IntlGlobals; -use Symfony\Component\Intl\Intl; -use Symfony\Component\Intl\Locale\Locale; +use Symfony\Component\Intl\Currency; +use Symfony\Component\Intl\Locale; /** * Replacement for PHP's native {@link \NumberFormatter} class. @@ -322,8 +322,8 @@ public function formatCurrency($value, $currency) return $this->format($value); } - $symbol = Intl::getCurrencyBundle()->getCurrencySymbol($currency, 'en'); - $fractionDigits = Intl::getCurrencyBundle()->getFractionDigits($currency); + $symbol = Currency::getSymbol($currency, 'en'); + $fractionDigits = Currency::getFractionDigits($currency); $value = $this->roundCurrency($value, $currency); @@ -675,8 +675,8 @@ protected function resetError() */ private function roundCurrency($value, $currency) { - $fractionDigits = Intl::getCurrencyBundle()->getFractionDigits($currency); - $roundingIncrement = Intl::getCurrencyBundle()->getRoundingIncrement($currency); + $fractionDigits = Currency::getFractionDigits($currency); + $roundingIncrement = Currency::getRoundingIncrement($currency); // Round with the formatter rounding mode $value = $this->round($value, $fractionDigits); diff --git a/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundle.php b/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundle.php index 8f22df8ecfe15..954c089b4df66 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundle.php @@ -11,33 +11,25 @@ namespace Symfony\Component\Intl\ResourceBundle; +use Symfony\Component\Icu\CurrencyDataProvider; +use Symfony\Component\Locale\Locale; + /** * Default implementation of {@link CurrencyBundleInterface}. * * @author Bernhard Schussek + * + * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0. + * Use {@link CurrencyDataProvider} instead. */ -class CurrencyBundle extends AbstractBundle implements CurrencyBundleInterface +class CurrencyBundle extends CurrencyDataProvider implements CurrencyBundleInterface { - const INDEX_NAME = 0; - - const INDEX_SYMBOL = 1; - - const INDEX_FRACTION_DIGITS = 2; - - const INDEX_ROUNDING_INCREMENT = 3; - /** * {@inheritdoc} */ public function getLocales() { - $locales = $this->readEntry('meta', array('Locales')); - - if ($locales instanceof \Traversable) { - $locales = iterator_to_array($locales); - } - - return $locales; + return Locale::getLocales(); } /** @@ -49,7 +41,7 @@ public function getCurrencySymbol($currency, $locale = null) $locale = \Locale::getDefault(); } - return $this->readEntry($locale, array('Currencies', $currency, static::INDEX_SYMBOL)); + return $this->getSymbol($currency, $locale); } /** @@ -61,7 +53,7 @@ public function getCurrencyName($currency, $locale = null) $locale = \Locale::getDefault(); } - return $this->readEntry($locale, array('Currencies', $currency, static::INDEX_NAME)); + return $this->getDisplayName($currency, $locale); } /** @@ -73,36 +65,6 @@ public function getCurrencyNames($locale = null) $locale = \Locale::getDefault(); } - if (null === ($currencies = $this->readEntry($locale, array('Currencies')))) { - return array(); - } - - if ($currencies instanceof \Traversable) { - $currencies = iterator_to_array($currencies); - } - - $index = static::INDEX_NAME; - - array_walk($currencies, function (&$value) use ($index) { - $value = $value[$index]; - }); - - return $currencies; - } - - /** - * {@inheritdoc} - */ - public function getFractionDigits($currency) - { - return $this->readEntry('en', array('Currencies', $currency, static::INDEX_FRACTION_DIGITS)); - } - - /** - * {@inheritdoc} - */ - public function getRoundingIncrement($currency) - { - return $this->readEntry('en', array('Currencies', $currency, static::INDEX_ROUNDING_INCREMENT)); + return $this->getDisplayNames($locale); } } diff --git a/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundleInterface.php b/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundleInterface.php index 1a88e93722055..3bd32b3959a9a 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundleInterface.php +++ b/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundleInterface.php @@ -15,6 +15,9 @@ * Gives access to currency-related ICU data. * * @author Bernhard Schussek + * + * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0. + * Use {@link \Symfony\Component\Icu\CurrencyDataProvider} instead. */ interface CurrencyBundleInterface extends ResourceBundleInterface { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php index bb3a5b83224c2..dcd6fe1be3589 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php @@ -11,8 +11,6 @@ namespace Symfony\Component\Intl\ResourceBundle\Transformer\Rule; -use Symfony\Component\Intl\Exception\RuntimeException; -use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\ResourceBundle\CurrencyBundle; use Symfony\Component\Intl\ResourceBundle\CurrencyBundleInterface; use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader; @@ -60,53 +58,87 @@ public function beforeCompile(CompilationContext $context) // in ICU <= 4.2 if (IcuVersion::compare($context->getIcuVersion(), '4.2', '<=', 1)) { $context->getFilesystem()->mirror($context->getSourceDir().'/locales', $tempDir.'/txt'); - $context->getFilesystem()->copy($context->getSourceDir().'/misc/supplementalData.txt', $tempDir.'/txt/meta.txt'); + $context->getFilesystem()->copy($context->getSourceDir().'/misc/supplementalData.txt', $tempDir.'/txt/supplementalData.txt'); } else { $context->getFilesystem()->mirror($context->getSourceDir().'/curr', $tempDir.'/txt'); - $context->getFilesystem()->rename($tempDir.'/txt/supplementalData.txt', $tempDir.'/txt/meta.txt'); } - // Replace "supplementalData" in the file by "meta" before compilation - file_put_contents($tempDir.'/txt/meta.txt', str_replace('supplementalData', 'meta', file_get_contents($tempDir.'/txt/meta.txt'))); - $context->getCompiler()->compile($tempDir.'/txt', $tempDir.'/res'); + $context->getCompiler()->compile($context->getSourceDir().'/misc/currencyNumericCodes.txt', $tempDir.'/res'); - // Read file, add locales and currencies and write again $reader = new BinaryBundleReader(); - $meta = iterator_to_array($reader->read($tempDir.'/res', 'meta')); - - // Key must not exist - if (isset($meta['AvailableLocales'])) { - throw new RuntimeException('The key "AvailableLocales" should not exist.'); - } - - if (isset($meta['Currencies'])) { - throw new RuntimeException('The key "Currencies" should not exist.'); - } + $writer = new TextBundleWriter(); // Collect supported locales of the bundle - $meta['AvailableLocales'] = $context->getLocaleScanner()->scanLocales($tempDir.'/txt'); + $availableLocales = $context->getLocaleScanner()->scanLocales($tempDir.'/txt'); + + // Drop and regenerate txt files + $context->getFilesystem()->remove($tempDir.'/txt'); + $context->getFilesystem()->mkdir($tempDir.'/txt'); - // Collect complete list of currencies in all locales - $meta['Currencies'] = array(); + $currencies = array(); - foreach ($meta['AvailableLocales'] as $locale) { + // Generate a text file for each locale + foreach ($availableLocales as $locale) { $bundle = $reader->read($tempDir.'/res', $locale); - // isset() on \ResourceBundle returns true even if the value is null if (isset($bundle['Currencies']) && null !== $bundle['Currencies']) { - $meta['Currencies'] = array_merge( - $meta['Currencies'], - array_keys(iterator_to_array($bundle['Currencies'])) - ); + $symbolNamePairs = iterator_to_array($bundle['Currencies']); + + // Remove the unknown currency + unset($symbolNamePairs['XXX']); + + // No other keys but "Currencies" are needed for now + $writer->write($tempDir.'/txt', $locale, array( + 'Version' => $bundle['Version'], + 'Currencies' => $symbolNamePairs, + )); + + // Add currencies to the list of known currencies + $currencies = array_merge($currencies, array_keys($symbolNamePairs)); } } - $meta['Currencies'] = array_unique($meta['Currencies']); - sort($meta['Currencies']); + // Remove duplicate currencies and sort + $currencies = array_unique($currencies); + sort($currencies); + + // Open resource bundles that contain currency metadata + $root = $reader->read($tempDir.'/res', 'root'); + $supplementalData = $reader->read($tempDir.'/res', 'supplementalData'); + $numericCodes = $reader->read($tempDir.'/res', 'currencyNumericCodes'); + + // Generate default currency names and symbols + $defaultSymbolNamePairs = array_map( + function ($currency) use ($root) { + if (isset($root['Currencies'][$currency]) && null !== $root['Currencies'][$currency]) { + return $root['Currencies'][$currency]; + } + + // by default both the symbol and the name equal the ISO code + return array($currency, $currency); + }, + $currencies + ); - $writer = new TextBundleWriter(); - $writer->write($tempDir.'/txt', 'meta', $meta, false); + // Replace keys by currencies + $defaultSymbolNamePairs = array_combine($currencies, $defaultSymbolNamePairs); + + // Generate and sort the mapping from 3-letter codes to numeric codes + $alpha3ToNumericMapping = iterator_to_array($numericCodes['codeMap']); + + asort($alpha3ToNumericMapping); + + // Filter unknown currencies (e.g. "AYM") + $alpha3ToNumericMapping = array_intersect_key($alpha3ToNumericMapping, $defaultSymbolNamePairs); + + // Write the root resource bundle + $writer->write($tempDir.'/txt', 'root', array( + 'Version' => $root['Version'], + 'Currencies' => $defaultSymbolNamePairs, + 'CurrencyMeta' => $supplementalData['CurrencyMeta'], + 'Alpha3ToNumeric' => $alpha3ToNumericMapping, + )); // The temporary directory now contains all sources to be compiled return $tempDir.'/txt'; @@ -118,7 +150,7 @@ public function beforeCompile(CompilationContext $context) public function afterCompile(CompilationContext $context) { // Remove the temporary directory - $context->getFilesystem()->remove(sys_get_temp_dir().'/icu-data-currencies-source'); + //$context->getFilesystem()->remove(sys_get_temp_dir().'/icu-data-currencies-source'); } /** diff --git a/src/Symfony/Component/Intl/Test/CurrencyBundleConsistencyTestCase.php b/src/Symfony/Component/Intl/Test/CurrencyBundleConsistencyTestCase.php deleted file mode 100644 index 3aed66240b188..0000000000000 --- a/src/Symfony/Component/Intl/Test/CurrencyBundleConsistencyTestCase.php +++ /dev/null @@ -1,144 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Intl\Test; - -use Symfony\Component\Intl\Exception\NoSuchEntryException; -use Symfony\Component\Intl\Intl; - -/** - * @author Bernhard Schussek - */ -abstract class CurrencyBundleConsistencyTestCase extends ConsistencyTestCase -{ - protected static $localesWithoutTranslationForAnyCurrency = array(); - protected static $localesWithoutTranslationForCurrency = array(); - - protected static $currencies = array(); - - public function provideCurrencies() - { - $aliases = $this->getLocaleAliases(); - - // Filter non-root and alias locales - $locales = array_filter($this->getLocales(), function ($locale) use ($aliases) { - return false === strpos($locale, '_') && !isset($aliases[$locale]); - }); - - return array_map( - function ($currency) { return array($currency); }, - static::$currencies - ); - } - - public function testGetCurrencyNames() - { - $translatedLocales = array(); - $rootLocales = $this->getRootLocales(); - - foreach ($rootLocales as $displayLocale) { - try { - Intl::getCurrencyBundle()->getCurrencyNames($displayLocale); - $translatedLocales[] = $displayLocale; - } catch (NoSuchEntryException $e) { - } - } - - $untranslatedLocales = array_diff($rootLocales, $translatedLocales); - - sort($untranslatedLocales); - - $this->assertEquals(static::$localesWithoutTranslationForAnyCurrency, $untranslatedLocales); - } - - public function provideTestedCurrencies() - { - return array_map( - function ($currency) { return array($currency); }, - array_keys(static::$localesWithoutTranslationForCurrency) - ); - } - - /** - * @dataProvider provideTestedCurrencies - */ - public function testGetCurrencyName($currency) - { - $translatedLocales = array(); - $rootLocales = $this->getRootLocales(); - - foreach ($rootLocales as $displayLocale) { - try { - Intl::getCurrencyBundle()->getCurrencyName($currency, $displayLocale); - $translatedLocales[] = $displayLocale; - } catch (NoSuchEntryException $e) { - } - } - - $untranslatedLocales = array_diff($rootLocales, static::$localesWithoutTranslationForAnyCurrency, $translatedLocales); - - sort($untranslatedLocales); - - $this->assertEquals(static::$localesWithoutTranslationForCurrency[$currency], $untranslatedLocales); - } - - /** - * @dataProvider provideLocaleAliases - * @group locale-alias-based - */ - public function testGetCurrencyNamesSupportsAliases($alias, $ofLocale) - { - $this->assertEquals( - Intl::getCurrencyBundle()->getCurrencyNames($ofLocale), - Intl::getCurrencyBundle()->getCurrencyNames($alias) - ); - } - - /** - * @dataProvider provideCurrencies - */ - public function testGetFractionDigits($currency) - { - $this->assertTrue(is_numeric(Intl::getCurrencyBundle()->getFractionDigits($currency))); - } - - /** - * @dataProvider provideCurrencies - */ - public function testGetRoundingIncrement($currency) - { - $this->assertTrue(is_numeric(Intl::getCurrencyBundle()->getRoundingIncrement($currency))); - } - - /** - * @dataProvider provideLocales - */ - public function testGetCurrencyNamesAndGetCurrencyNameAreConsistent($displayLocale) - { - $names = Intl::getCurrencyBundle()->getCurrencyNames($displayLocale); - - foreach ($names as $currency => $name) { - $this->assertSame($name, Intl::getCurrencyBundle()->getCurrencyName($currency, $displayLocale)); - } - } - - /** - * @dataProvider provideLocales - */ - public function testGetCurrencyNamesAndGetCurrencySymbolAreConsistent($displayLocale) - { - $names = Intl::getCurrencyBundle()->getCurrencyNames($displayLocale); - - foreach ($names as $currency => $name) { - $this->assertGreaterThan(0, mb_strlen(Intl::getCurrencyBundle()->getCurrencySymbol($currency, $displayLocale))); - } - } -} diff --git a/src/Symfony/Component/Intl/Tests/CurrencyTest.php b/src/Symfony/Component/Intl/Tests/CurrencyTest.php new file mode 100644 index 0000000000000..94cd136089f81 --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/CurrencyTest.php @@ -0,0 +1,117 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Tests; + +use Symfony\Component\Intl\Currency; + +/** + * @author Bernhard Schussek + */ +class CurrencyTest extends \PHPUnit_Framework_TestCase +{ + public function testGetSymbol() + { + $this->assertSame('€', Currency::getSymbol('EUR', 'en')); + } + + /** + * @expectedException \Symfony\Component\Intl\Exception\InvalidArgumentException + */ + public function testGetSymbolFailsOnInvalidCurrency() + { + Currency::getSymbol('FOO'); + } + + /** + * @expectedException \Symfony\Component\Intl\Exception\InvalidArgumentException + */ + public function testGetSymbolFailsOnInvalidDisplayLocale() + { + Currency::getSymbol('EUR', 'foo'); + } + + public function testGetDisplayName() + { + $this->assertSame('Euro', Currency::getDisplayName('EUR', 'en')); + } + + /** + * @expectedException \Symfony\Component\Intl\Exception\InvalidArgumentException + */ + public function testGetDisplayNameFailsOnInvalidCurrency() + { + Currency::getDisplayName('FOO'); + } + + /** + * @expectedException \Symfony\Component\Intl\Exception\InvalidArgumentException + */ + public function testGetDisplayNameFailsOnInvalidDisplayLocale() + { + Currency::getDisplayName('EUR', 'foo'); + } + + public function testGetDisplayNames() + { + $names = Currency::getDisplayNames('en'); + + $this->assertArrayHasKey('EUR', $names); + $this->assertSame('Euro', $names['EUR']); + } + + /** + * @expectedException \Symfony\Component\Intl\Exception\InvalidArgumentException + */ + public function testGetDisplayNamesFailsOnInvalidDisplayLocale() + { + Currency::getDisplayNames('foo'); + } + + public function testGetFractionDigits() + { + $this->assertSame(2, Currency::getFractionDigits('EUR')); + } + + /** + * @expectedException \Symfony\Component\Intl\Exception\InvalidArgumentException + */ + public function testGetFractionDigitsFailsOnInvalidCurrency() + { + Currency::getFractionDigits('FOO'); + } + + public function testGetRoundingIncrement() + { + $this->assertSame(0, Currency::getRoundingIncrement('EUR')); + } + + /** + * @expectedException \Symfony\Component\Intl\Exception\InvalidArgumentException + */ + public function testGetRoundingIncrementFailsOnInvalidCurrency() + { + Currency::getRoundingIncrement('FOO'); + } + + public function testGetNumericCode() + { + $this->assertSame(978, Currency::getNumericCode('EUR')); + } + + /** + * @expectedException \Symfony\Component\Intl\Exception\InvalidArgumentException + */ + public function testGetNumericCodeFailsOnInvalidCurrency() + { + Currency::getNumericCode('FOO'); + } +} diff --git a/src/Symfony/Component/Intl/Tests/DataProvider/AbstractCurrencyDataProviderTest.php b/src/Symfony/Component/Intl/Tests/DataProvider/AbstractCurrencyDataProviderTest.php new file mode 100644 index 0000000000000..3904e4df9217d --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/DataProvider/AbstractCurrencyDataProviderTest.php @@ -0,0 +1,155 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Tests\DataProvider; + +/** + * @author Bernhard Schussek + */ +abstract class AbstractCurrencyDataProviderTest extends AbstractDataProviderTest +{ + protected static $localesWithoutTranslationForAnyCurrency = array(); + protected static $localesWithoutTranslationForCurrency = array(); + + protected static $currencies = array(); + + protected static $alpha3ToNumeric = array(); + + /** + * @var \Symfony\Component\Icu\CurrencyDataProvider + */ + protected $dataProvider; + + protected function setUp() + { + $this->dataProvider = $this->createDataProvider(); + } + + abstract protected function createDataProvider(); + + public function provideCurrencies() + { + return array_map( + function ($currency) { return array($currency); }, + static::$currencies + ); + } + + public function testGetAlpha3CurrencyCodes() + { + $this->assertEquals(static::$currencies, $this->dataProvider->getCurrencies()); + } + + /** + * @dataProvider provideLocales + */ + public function testGetDisplayNames($displayLocale) + { + $names = $this->dataProvider->getDisplayNames($displayLocale); + + $keys = array_keys($names); + + sort($keys); + + $this->assertEquals(static::$currencies, $keys); + + // Names should be sorted + $sortedNames = $names; + $collator = new \Collator($displayLocale); + $collator->asort($names); + + $this->assertSame($sortedNames, $names); + } + + /** + * @dataProvider provideLocaleAliases + * @group locale-alias-based + */ + public function testGetDisplayNamesSupportsAliases($alias, $ofLocale) + { + $this->assertEquals( + $this->dataProvider->getDisplayNames($ofLocale), + $this->dataProvider->getDisplayNames($alias) + ); + } + + /** + * @dataProvider provideLocales + */ + public function testGetDisplayName($displayLocale) + { + $names = $this->dataProvider->getDisplayNames($displayLocale); + + foreach ($names as $currency => $name) { + $this->assertSame($name, $this->dataProvider->getDisplayName($currency, $displayLocale)); + } + } + + /** + * @dataProvider provideLocales + */ + public function testGetSymbol($displayLocale) + { + $names = $this->dataProvider->getDisplayNames($displayLocale); + + foreach ($names as $currency => $name) { + $this->assertGreaterThan(0, mb_strlen($this->dataProvider->getSymbol($currency, $displayLocale))); + } + } + + /** + * @dataProvider provideCurrencies + */ + public function testGetFractionDigits($currency) + { + $this->assertTrue(is_numeric($this->dataProvider->getFractionDigits($currency))); + } + + /** + * @dataProvider provideCurrencies + */ + public function testGetRoundingIncrement($currency) + { + $this->assertTrue(is_numeric($this->dataProvider->getRoundingIncrement($currency))); + } + + public function provideCurrenciesWithNumericEquivalent() + { + return array_map( + function ($value) { return array($value); }, + array_keys(static::$alpha3ToNumeric) + ); + } + + /** + * @dataProvider provideCurrenciesWithNumericEquivalent + */ + public function testGetNumericCode($currency) + { + $this->assertSame(static::$alpha3ToNumeric[$currency], $this->dataProvider->getNumericCode($currency)); + } + + public function provideCurrenciesWithoutNumericEquivalent() + { + return array_map( + function ($value) { return array($value); }, + array_diff(static::$currencies, array_keys(static::$alpha3ToNumeric)) + ); + } + + /** + * @dataProvider provideCurrenciesWithoutNumericEquivalent + */ + public function testGetNumericCodeReturnsZeroIfNoNumericEquivalent($currency) + { + $this->assertSame(0, $this->dataProvider->getNumericCode($currency)); + } +} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/CurrencyBundleTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/CurrencyBundleTest.php deleted file mode 100644 index b66a6727bfab8..0000000000000 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/CurrencyBundleTest.php +++ /dev/null @@ -1,98 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Intl\Tests\ResourceBundle; - -use Symfony\Component\Intl\ResourceBundle\CurrencyBundle; - -/** - * @author Bernhard Schussek - */ -class CurrencyBundleTest extends \PHPUnit_Framework_TestCase -{ - const RES_DIR = '/base/curr'; - - /** - * @var CurrencyBundle - */ - private $bundle; - - /** - * @var \PHPUnit_Framework_MockObject_MockObject - */ - private $reader; - - protected function setUp() - { - $this->reader = $this->getMock('Symfony\Component\Intl\ResourceBundle\Reader\StructuredBundleReaderInterface'); - $this->bundle = new CurrencyBundle(self::RES_DIR, $this->reader); - } - - public function testGetCurrencySymbol() - { - $this->reader->expects($this->once()) - ->method('readEntry') - ->with(self::RES_DIR, 'en', array('Currencies', 'EUR', 1)) - ->will($this->returnValue('€')); - - $this->assertSame('€', $this->bundle->getCurrencySymbol('EUR', 'en')); - } - - public function testGetCurrencyName() - { - $this->reader->expects($this->once()) - ->method('readEntry') - ->with(self::RES_DIR, 'en', array('Currencies', 'EUR', 0)) - ->will($this->returnValue('Euro')); - - $this->assertSame('Euro', $this->bundle->getCurrencyName('EUR', 'en')); - } - - public function testGetCurrencyNames() - { - $sortedCurrencies = array( - 'USD' => array(0 => 'Dollar'), - 'EUR' => array(0 => 'Euro'), - ); - - $this->reader->expects($this->once()) - ->method('readEntry') - ->with(self::RES_DIR, 'en', array('Currencies')) - ->will($this->returnValue($sortedCurrencies)); - - $sortedNames = array( - 'USD' => 'Dollar', - 'EUR' => 'Euro', - ); - - $this->assertSame($sortedNames, $this->bundle->getCurrencyNames('en')); - } - - public function testGetFractionDigits() - { - $this->reader->expects($this->once()) - ->method('readEntry') - ->with(self::RES_DIR, 'en', array('Currencies', 'EUR', 2)) - ->will($this->returnValue(123)); - - $this->assertSame(123, $this->bundle->getFractionDigits('EUR')); - } - - public function testGetRoundingIncrement() - { - $this->reader->expects($this->once()) - ->method('readEntry') - ->with(self::RES_DIR, 'en', array('Currencies', 'EUR', 3)) - ->will($this->returnValue(123)); - - $this->assertSame(123, $this->bundle->getRoundingIncrement('EUR')); - } -} From 350f864529c212df14be6248173ac282cf1b3d7e Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Mon, 14 Oct 2013 10:55:56 +0200 Subject: [PATCH 38/48] [Intl] Renamed getDisplayName()/getDisplayNames() to getName()/getNames() --- src/Symfony/Component/Intl/Currency.php | 8 +-- src/Symfony/Component/Intl/Locale.php | 60 +++++++++++++++++-- src/Symfony/Component/Intl/Locale/Locale.php | 2 +- .../Intl/ResourceBundle/CurrencyBundle.php | 4 +- .../Intl/ResourceBundle/LocaleBundle.php | 8 +-- .../Component/Intl/Tests/CurrencyTest.php | 20 +++---- .../AbstractCurrencyDataProviderTest.php | 18 +++--- .../AbstractLocaleDataProviderTest.php | 16 ++--- .../Intl/Tests/Locale/LocaleTest.php | 4 +- .../Component/Intl/Tests/LocaleTest.php | 20 +++---- 10 files changed, 105 insertions(+), 55 deletions(-) diff --git a/src/Symfony/Component/Intl/Currency.php b/src/Symfony/Component/Intl/Currency.php index 4709af8d6d2ab..f68f300e61c5e 100644 --- a/src/Symfony/Component/Intl/Currency.php +++ b/src/Symfony/Component/Intl/Currency.php @@ -111,7 +111,7 @@ public static function getSymbol($currency, $displayLocale = null) * * @api */ - public static function getDisplayName($currency, $displayLocale = null) + public static function getName($currency, $displayLocale = null) { if (!in_array($currency, self::getCurrencies(), true)) { throw new InvalidArgumentException('The currency "' . $currency . '" does not exist.'); @@ -126,7 +126,7 @@ public static function getDisplayName($currency, $displayLocale = null) } try { - return self::getDataProvider()->getDisplayName($currency, $displayLocale); + return self::getDataProvider()->getName($currency, $displayLocale); } catch (NoSuchEntryException $e) { return $currency; } @@ -149,7 +149,7 @@ public static function getDisplayName($currency, $displayLocale = null) * * @api */ - public static function getDisplayNames($displayLocale = null) + public static function getNames($displayLocale = null) { if (null !== $displayLocale && !in_array($displayLocale, Locale::getLocales(), true)) { throw new InvalidArgumentException('The locale "' . $displayLocale . '" does not exist.'); @@ -159,7 +159,7 @@ public static function getDisplayNames($displayLocale = null) $displayLocale = \Locale::getDefault(); } - return self::getDataProvider()->getDisplayNames($displayLocale); + return self::getDataProvider()->getNames($displayLocale); } /** diff --git a/src/Symfony/Component/Intl/Locale.php b/src/Symfony/Component/Intl/Locale.php index 7f875f94f8d45..96dc738beea5a 100644 --- a/src/Symfony/Component/Intl/Locale.php +++ b/src/Symfony/Component/Intl/Locale.php @@ -60,14 +60,20 @@ public static function getLocales() * If null is passed as display locale, the result of * {@link \Locale::getDefault()} is used instead. * - * @param string $locale The ICU locale code to return the name of (e.g. "de_AT") + * @param string $locale The ICU locale code to return the name of + * (e.g. "de_AT") * @param string $displayLocale The ICU locale code to return the name in * * @return string The name of the locale * + * @throws InvalidArgumentException If the locale or the display locale is + * invalid + * + * @see getNames + * * @api */ - public static function getDisplayName($locale, $displayLocale = null) + public static function getName($locale, $displayLocale = null) { if (!in_array($locale, self::getLocales(), true)) { throw new InvalidArgumentException('The locale "' . $locale . '" does not exist.'); @@ -81,7 +87,7 @@ public static function getDisplayName($locale, $displayLocale = null) $displayLocale = \Locale::getDefault(); } - return self::getDataProvider()->getDisplayName($locale, $displayLocale); + return self::getDataProvider()->getName($locale, $displayLocale); } /** @@ -95,9 +101,13 @@ public static function getDisplayName($locale, $displayLocale = null) * @return string[] A list of locale names indexed by the corresponding ICU * locale codes * + * @throws InvalidArgumentException If the display locale is invalid + * + * @see getName + * * @api */ - public static function getDisplayNames($displayLocale = null) + public static function getNames($displayLocale = null) { if (null !== $displayLocale && !in_array($displayLocale, Locale::getLocales(), true)) { throw new InvalidArgumentException('The locale "' . $displayLocale . '" does not exist.'); @@ -107,7 +117,47 @@ public static function getDisplayNames($displayLocale = null) $displayLocale = \Locale::getDefault(); } - return self::getDataProvider()->getDisplayNames($displayLocale); + return self::getDataProvider()->getNames($displayLocale); + } + + /** + * Alias of {@link getName()}. + * + * This method exists for compatibility with the {@link \Locale} class. + * + * @param string $locale The ICU locale code to return the name of + * (e.g. "de_AT") + * @param string $displayLocale The ICU locale code to return the name in + * + * @return string The name of the locale + * + * @throws InvalidArgumentException If the locale or the display locale is + * invalid + * + * @see getName + */ + public static function getDisplayName($locale, $displayLocale = null) + { + return static::getName($locale, $displayLocale); + } + + /** + * Alias of {@link getNames()}. + * + * This method exists for compatibility with the {@link \Locale} class. + * + * @param string $displayLocale The ICU locale code to return the names in + * + * @return string[] A list of locale names indexed by the corresponding ICU + * locale codes + * + * @throws InvalidArgumentException If the display locale is invalid + * + * @see getNames + */ + public static function getDisplayNames($locale, $displayLocale = null) + { + return static::getNames($locale, $displayLocale); } /** diff --git a/src/Symfony/Component/Intl/Locale/Locale.php b/src/Symfony/Component/Intl/Locale/Locale.php index f16d937b02729..ce7ba96c9d36f 100644 --- a/src/Symfony/Component/Intl/Locale/Locale.php +++ b/src/Symfony/Component/Intl/Locale/Locale.php @@ -146,7 +146,7 @@ public static function getDisplayLanguage($locale, $inLocale = null) * * @throws MethodNotImplementedException */ - public static function getDisplayName($locale, $inLocale = null) + public static function getName($locale, $inLocale = null) { throw new MethodNotImplementedException(__METHOD__); } diff --git a/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundle.php b/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundle.php index 954c089b4df66..437a412736fad 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundle.php @@ -53,7 +53,7 @@ public function getCurrencyName($currency, $locale = null) $locale = \Locale::getDefault(); } - return $this->getDisplayName($currency, $locale); + return $this->getName($currency, $locale); } /** @@ -65,6 +65,6 @@ public function getCurrencyNames($locale = null) $locale = \Locale::getDefault(); } - return $this->getDisplayNames($locale); + return $this->getNames($locale); } } diff --git a/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php b/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php index fdf7cc3994884..3fccd8976213d 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/LocaleBundle.php @@ -24,19 +24,19 @@ class LocaleBundle extends LocaleDataProvider implements LocaleBundleInterface { /** - * Alias of {@link getDisplayNames()}. + * Alias of {@link getNames()}. */ public function getLocaleNames($locale = null) { - return $this->getDisplayNames($locale); + return $this->getNames($locale); } /** - * Alias of {@link getDisplayName()}. + * Alias of {@link getName()}. */ public function getLocaleName($ofLocale, $locale = null) { - return $this->getDisplayName($ofLocale, $locale); + return $this->getName($ofLocale, $locale); } /** diff --git a/src/Symfony/Component/Intl/Tests/CurrencyTest.php b/src/Symfony/Component/Intl/Tests/CurrencyTest.php index 94cd136089f81..87e5678db7782 100644 --- a/src/Symfony/Component/Intl/Tests/CurrencyTest.php +++ b/src/Symfony/Component/Intl/Tests/CurrencyTest.php @@ -39,30 +39,30 @@ public function testGetSymbolFailsOnInvalidDisplayLocale() Currency::getSymbol('EUR', 'foo'); } - public function testGetDisplayName() + public function testGetName() { - $this->assertSame('Euro', Currency::getDisplayName('EUR', 'en')); + $this->assertSame('Euro', Currency::getName('EUR', 'en')); } /** * @expectedException \Symfony\Component\Intl\Exception\InvalidArgumentException */ - public function testGetDisplayNameFailsOnInvalidCurrency() + public function testGetNameFailsOnInvalidCurrency() { - Currency::getDisplayName('FOO'); + Currency::getName('FOO'); } /** * @expectedException \Symfony\Component\Intl\Exception\InvalidArgumentException */ - public function testGetDisplayNameFailsOnInvalidDisplayLocale() + public function testGetNameFailsOnInvalidDisplayLocale() { - Currency::getDisplayName('EUR', 'foo'); + Currency::getName('EUR', 'foo'); } - public function testGetDisplayNames() + public function testGetNames() { - $names = Currency::getDisplayNames('en'); + $names = Currency::getNames('en'); $this->assertArrayHasKey('EUR', $names); $this->assertSame('Euro', $names['EUR']); @@ -71,9 +71,9 @@ public function testGetDisplayNames() /** * @expectedException \Symfony\Component\Intl\Exception\InvalidArgumentException */ - public function testGetDisplayNamesFailsOnInvalidDisplayLocale() + public function testGetNamesFailsOnInvalidDisplayLocale() { - Currency::getDisplayNames('foo'); + Currency::getNames('foo'); } public function testGetFractionDigits() diff --git a/src/Symfony/Component/Intl/Tests/DataProvider/AbstractCurrencyDataProviderTest.php b/src/Symfony/Component/Intl/Tests/DataProvider/AbstractCurrencyDataProviderTest.php index 3904e4df9217d..9d3dc3e939e31 100644 --- a/src/Symfony/Component/Intl/Tests/DataProvider/AbstractCurrencyDataProviderTest.php +++ b/src/Symfony/Component/Intl/Tests/DataProvider/AbstractCurrencyDataProviderTest.php @@ -51,9 +51,9 @@ public function testGetAlpha3CurrencyCodes() /** * @dataProvider provideLocales */ - public function testGetDisplayNames($displayLocale) + public function testGetNames($displayLocale) { - $names = $this->dataProvider->getDisplayNames($displayLocale); + $names = $this->dataProvider->getNames($displayLocale); $keys = array_keys($names); @@ -73,23 +73,23 @@ public function testGetDisplayNames($displayLocale) * @dataProvider provideLocaleAliases * @group locale-alias-based */ - public function testGetDisplayNamesSupportsAliases($alias, $ofLocale) + public function testGetNamesSupportsAliases($alias, $ofLocale) { $this->assertEquals( - $this->dataProvider->getDisplayNames($ofLocale), - $this->dataProvider->getDisplayNames($alias) + $this->dataProvider->getNames($ofLocale), + $this->dataProvider->getNames($alias) ); } /** * @dataProvider provideLocales */ - public function testGetDisplayName($displayLocale) + public function testGetName($displayLocale) { - $names = $this->dataProvider->getDisplayNames($displayLocale); + $names = $this->dataProvider->getNames($displayLocale); foreach ($names as $currency => $name) { - $this->assertSame($name, $this->dataProvider->getDisplayName($currency, $displayLocale)); + $this->assertSame($name, $this->dataProvider->getName($currency, $displayLocale)); } } @@ -98,7 +98,7 @@ public function testGetDisplayName($displayLocale) */ public function testGetSymbol($displayLocale) { - $names = $this->dataProvider->getDisplayNames($displayLocale); + $names = $this->dataProvider->getNames($displayLocale); foreach ($names as $currency => $name) { $this->assertGreaterThan(0, mb_strlen($this->dataProvider->getSymbol($currency, $displayLocale))); diff --git a/src/Symfony/Component/Intl/Tests/DataProvider/AbstractLocaleDataProviderTest.php b/src/Symfony/Component/Intl/Tests/DataProvider/AbstractLocaleDataProviderTest.php index a7e15a8b2e9d7..5dbeb46e1ccd7 100644 --- a/src/Symfony/Component/Intl/Tests/DataProvider/AbstractLocaleDataProviderTest.php +++ b/src/Symfony/Component/Intl/Tests/DataProvider/AbstractLocaleDataProviderTest.php @@ -41,9 +41,9 @@ public function testGetLocaleAliases() /** * @dataProvider provideLocales */ - public function testGetDisplayNames($displayLocale) + public function testGetNames($displayLocale) { - $locales = array_keys($this->dataProvider->getDisplayNames($displayLocale)); + $locales = array_keys($this->dataProvider->getNames($displayLocale)); sort($locales); @@ -54,23 +54,23 @@ public function testGetDisplayNames($displayLocale) * @dataProvider provideLocaleAliases * @group locale-alias-based */ - public function testGetDisplayNamesSupportsAliases($alias, $ofLocale) + public function testGetNamesSupportsAliases($alias, $ofLocale) { $this->assertEquals( - $this->dataProvider->getDisplayNames($ofLocale), - $this->dataProvider->getDisplayNames($alias) + $this->dataProvider->getNames($ofLocale), + $this->dataProvider->getNames($alias) ); } /** * @dataProvider provideLocales */ - public function testGetDisplayName($displayLocale) + public function testGetName($displayLocale) { - $names = $this->dataProvider->getDisplayNames($displayLocale); + $names = $this->dataProvider->getNames($displayLocale); foreach ($names as $locale => $name) { - $this->assertSame($name, $this->dataProvider->getDisplayName($locale, $displayLocale)); + $this->assertSame($name, $this->dataProvider->getName($locale, $displayLocale)); } } } diff --git a/src/Symfony/Component/Intl/Tests/Locale/LocaleTest.php b/src/Symfony/Component/Intl/Tests/Locale/LocaleTest.php index 2a5d5d2db710d..1eb12a96fadf1 100644 --- a/src/Symfony/Component/Intl/Tests/Locale/LocaleTest.php +++ b/src/Symfony/Component/Intl/Tests/Locale/LocaleTest.php @@ -61,9 +61,9 @@ public function testGetDisplayLanguage() /** * @expectedException \Symfony\Component\Intl\Exception\MethodNotImplementedException */ - public function testGetDisplayName() + public function testGetName() { - $this->call('getDisplayName', 'pt-Latn-BR', 'en'); + $this->call('getName', 'pt-Latn-BR', 'en'); } /** diff --git a/src/Symfony/Component/Intl/Tests/LocaleTest.php b/src/Symfony/Component/Intl/Tests/LocaleTest.php index 006669c4f271d..027643a65fa7b 100644 --- a/src/Symfony/Component/Intl/Tests/LocaleTest.php +++ b/src/Symfony/Component/Intl/Tests/LocaleTest.php @@ -23,30 +23,30 @@ protected function setUp() Locale::setDefault('en'); } - public function testGetDisplayName() + public function testGetName() { - $this->assertSame('English', Locale::getDisplayName('en', 'en')); + $this->assertSame('English', Locale::getName('en', 'en')); } /** * @expectedException \Symfony\Component\Intl\Exception\InvalidArgumentException */ - public function testGetDisplayNameFailsOnInvalidLocale() + public function testGetNameFailsOnInvalidLocale() { - Locale::getDisplayName('foo'); + Locale::getName('foo'); } /** * @expectedException \Symfony\Component\Intl\Exception\InvalidArgumentException */ - public function testGetDisplayNameFailsOnInvalidDisplayLocale() + public function testGetNameFailsOnInvalidDisplayLocale() { - Locale::getDisplayName('en', 'foo'); + Locale::getName('en', 'foo'); } - public function testGetDisplayNames() + public function testGetNames() { - $names = Locale::getDisplayNames('en'); + $names = Locale::getNames('en'); $this->assertArrayHasKey('en', $names); $this->assertSame('English', $names['en']); @@ -55,8 +55,8 @@ public function testGetDisplayNames() /** * @expectedException \Symfony\Component\Intl\Exception\InvalidArgumentException */ - public function testGetDisplayNamesFailsOnInvalidDisplayLocale() + public function testGetNamesFailsOnInvalidDisplayLocale() { - Locale::getDisplayNames('foo'); + Locale::getNames('foo'); } } From be2ce30bd6fc994d7d414e071bcd250ccd51a0b5 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Mon, 14 Oct 2013 11:07:27 +0200 Subject: [PATCH 39/48] [Intl] Changed "@since" version numbers of new code to 2.5 --- src/Symfony/Component/Intl/Currency.php | 2 +- .../Component/Intl/Exception/UnexpectedTypeException.php | 2 +- .../Intl/ResourceBundle/Compiler/GenrbBundleCompiler.php | 2 +- .../Component/Intl/ResourceBundle/Reader/BundleEntryReader.php | 2 +- .../Intl/ResourceBundle/Reader/BundleEntryReaderInterface.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Symfony/Component/Intl/Currency.php b/src/Symfony/Component/Intl/Currency.php index f68f300e61c5e..5ff6cfcd701f8 100644 --- a/src/Symfony/Component/Intl/Currency.php +++ b/src/Symfony/Component/Intl/Currency.php @@ -17,7 +17,7 @@ /** * Provides access to currency-related data. * - * @since 2.4 + * @since 2.5 * @author Bernhard Schussek * * @api diff --git a/src/Symfony/Component/Intl/Exception/UnexpectedTypeException.php b/src/Symfony/Component/Intl/Exception/UnexpectedTypeException.php index 20a4517443d1b..1076ca904893b 100644 --- a/src/Symfony/Component/Intl/Exception/UnexpectedTypeException.php +++ b/src/Symfony/Component/Intl/Exception/UnexpectedTypeException.php @@ -14,7 +14,7 @@ /** * Thrown when a method argument had an unexpected type. * - * @since 2.4 + * @since 2.5 * @author Bernhard Schussek */ class UnexpectedTypeException extends InvalidArgumentException diff --git a/src/Symfony/Component/Intl/ResourceBundle/Compiler/GenrbBundleCompiler.php b/src/Symfony/Component/Intl/ResourceBundle/Compiler/GenrbBundleCompiler.php index d5d4f05b5cf28..c8845cb0f7e3c 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Compiler/GenrbBundleCompiler.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Compiler/GenrbBundleCompiler.php @@ -16,7 +16,7 @@ /** * Compiles .txt resource bundles to binary .res files. * - * @since 2.4 + * @since 2.5 * @author Bernhard Schussek */ class GenrbBundleCompiler implements BundleCompilerInterface diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReader.php index dd1234c0d6c1f..9d9b36455d675 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReader.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReader.php @@ -20,7 +20,7 @@ /** * A structured reader wrapping an existing resource bundle reader. * - * @since 2.4 + * @since 2.5 * @author Bernhard Schussek * @see BundleEntryReaderInterface */ diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReaderInterface.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReaderInterface.php index f52ced36ec862..00c15e9fff9df 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReaderInterface.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReaderInterface.php @@ -14,7 +14,7 @@ /** * Reads individual entries of a resource file. * - * @since 2.4 + * @since 2.5 * @author Bernhard Schussek */ interface BundleEntryReaderInterface extends BundleReaderInterface From c27325be37d07d5c897abcca6c7fe4ef688b4b26 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Mon, 14 Oct 2013 11:10:41 +0200 Subject: [PATCH 40/48] [Intl] Fixed "@var" declarations in data provider properties --- src/Symfony/Component/Intl/Currency.php | 2 +- src/Symfony/Component/Intl/Locale.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Intl/Currency.php b/src/Symfony/Component/Intl/Currency.php index 5ff6cfcd701f8..e3c7b1075494f 100644 --- a/src/Symfony/Component/Intl/Currency.php +++ b/src/Symfony/Component/Intl/Currency.php @@ -25,7 +25,7 @@ class Currency { /** - * @var IcuCurrencyDataProvider + * @var CurrencyDataProvider */ private static $dataProvider; diff --git a/src/Symfony/Component/Intl/Locale.php b/src/Symfony/Component/Intl/Locale.php index 96dc738beea5a..5062b9f275c95 100644 --- a/src/Symfony/Component/Intl/Locale.php +++ b/src/Symfony/Component/Intl/Locale.php @@ -26,7 +26,7 @@ class Locale extends \Locale { /** - * @var IcuCurrencyDataProvider + * @var LocaleDataProvider */ private static $dataProvider; From ad3d1f460243e27fc876d75fb8b3257eeba7f57e Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Mon, 14 Oct 2013 11:21:49 +0200 Subject: [PATCH 41/48] [Intl] Updated doc comments --- src/Symfony/Component/Intl/Currency.php | 2 +- src/Symfony/Component/Intl/Locale.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Symfony/Component/Intl/Currency.php b/src/Symfony/Component/Intl/Currency.php index e3c7b1075494f..e364313efd144 100644 --- a/src/Symfony/Component/Intl/Currency.php +++ b/src/Symfony/Component/Intl/Currency.php @@ -92,7 +92,7 @@ public static function getSymbol($currency, $displayLocale = null) } /** - * Returns the name for this currency in the given locale. + * Returns the name of a currency in the given locale. * * For example, the name of the Euro ("EUR") in the locale "ru_RU" is * "Евро". If the resource data for the given locale contains no entry for diff --git a/src/Symfony/Component/Intl/Locale.php b/src/Symfony/Component/Intl/Locale.php index 5062b9f275c95..28ff2bb410127 100644 --- a/src/Symfony/Component/Intl/Locale.php +++ b/src/Symfony/Component/Intl/Locale.php @@ -52,7 +52,7 @@ public static function getLocales() } /** - * Returns the name of a locale in a specified display locale. + * Returns the name of a locale in the given display locale. * * If there is no suitable name found for a display locale, the ICU locale * code is used instead. @@ -91,7 +91,7 @@ public static function getName($locale, $displayLocale = null) } /** - * Returns the names of all known locales in a specified display locale. + * Returns the names of all known locales in the given display locale. * * If null is passed as display locale, the result of * {@link \Locale::getDefault()} is used instead. From 76e493044c9aec0be60c150a43d9ae8d9eeeeb90 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Mon, 14 Oct 2013 17:44:18 +0200 Subject: [PATCH 42/48] [Intl] Implemented Language class --- src/Symfony/Component/Intl/Language.php | 273 ++++++++++++++++++ .../Intl/ResourceBundle/CurrencyBundle.php | 2 +- .../Intl/ResourceBundle/LanguageBundle.php | 47 +-- .../LanguageBundleInterface.php | 3 + .../Rule/LanguageBundleTransformationRule.php | 141 +++++++-- .../AbstractCurrencyDataProviderTest.php | 5 +- .../AbstractLanguageDataProviderTest.php | 107 +++++++ .../Component/Intl/Tests/LanguageTest.php | 113 ++++++++ 8 files changed, 643 insertions(+), 48 deletions(-) create mode 100644 src/Symfony/Component/Intl/Language.php create mode 100644 src/Symfony/Component/Intl/Tests/DataProvider/AbstractLanguageDataProviderTest.php create mode 100644 src/Symfony/Component/Intl/Tests/LanguageTest.php diff --git a/src/Symfony/Component/Intl/Language.php b/src/Symfony/Component/Intl/Language.php new file mode 100644 index 0000000000000..ffb24461cf149 --- /dev/null +++ b/src/Symfony/Component/Intl/Language.php @@ -0,0 +1,273 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl; + +use Symfony\Component\Icu\LanguageDataProvider; +use Symfony\Component\Intl\Exception\InvalidArgumentException; +use Symfony\Component\Intl\Exception\NoSuchEntryException; + +/** + * Provides access to language-related data. + * + * @since 2.5 + * @author Bernhard Schussek + * + * @api + */ +class Language +{ + /** + * @var LanguageDataProvider + */ + private static $dataProvider; + + /** + * @var string[]|null + */ + private static $languages; + + /** + * @var integer[]|null + */ + private static $lookupTable; + + /** + * @var string[]|null + */ + private static $aliases; + + /** + * Returns all available languages. + * + * Languages are returned as lowercase ISO 639-1 two-letter language codes. + * For languages that don't have a two-letter code, the ISO 639-2 + * three-letter code is used instead. + * + * A full table of ISO 639 language codes can be found here: + * http://www-01.sil.org/iso639-3/codes.asp + * + * @return string[] An array of canonicalized ISO 639 language codes + * + * @api + */ + public static function getLanguages() + { + if (null === self::$languages) { + self::$languages = self::getDataProvider()->getLanguages(); + } + + return self::$languages; + } + + /** + * Returns whether the given ISO 639 language code exists. + * + * This method does not canonicalize the given language code. Specifically, + * it will return false if the language code is not correctly cased or uses + * hyphens ("-") as separators between the subtags instead of underscores + * ("_"). For example, this method returns false for "en-GB", but true + * for "en_GB". + * + * This method also returns false if an ISO 639-2 three-letter code is + * provided where an equivalent ISO 639-1 two-letter code exists. + * + * If you want to support the above cases, you should manually canonicalize + * the language code in prior to calling this method. + * + * @param string $language A canonicalized ISO 639 language code (e.g. "en") + * + * @return Boolean Whether the language code exists + * + * @see canonicalize + * + * @api + */ + public static function exists($language) + { + if (null === self::$lookupTable) { + self::$lookupTable = array_flip(static::getLanguages()); + } + + return isset(self::$lookupTable[$language]); + } + + /** + * Canonicalizes the given ISO 639 language code. + * + * Canonicalization performs the following steps: + * + * 1. Hyphens ("-") are replaced by underscores ("_") + * 2. The first subtag is interpreted as language code. The language + * code is lowercased. If a corresponding ISO 639-1 two-letter code + * exists for a given ISO 639-2 three-letter code, the two-letter code + * is used instead. For example, "DEU" is converted to "de". + * 3. The second subtag is interpreted as region code. The region code + * is uppercased. If the region code is an alias, it is replaced by + * the aliased region code. For example "aut" is converted to "AT". + * + * Canonicalization does not check whether a given language or region + * actually exists. In case of doubt, you should pass the canonicalized + * language to {@link exists()}. + * + * @param string $language A canonicalized ISO 639 language code (e.g. "en") + * + * @return string The canonicalized language name + * + * @see exists + * + * @api + */ + public static function canonicalize($language) + { + if (static::exists($language)) { + return $language; + } + + if (null === self::$aliases) { + self::$aliases = self::getDataProvider()->getAliases(); + } + + $parts = preg_split('/[-_]/', $language); + + // The language code is always in lower case + $parts[0] = strtolower($parts[0]); + + if (isset(self::$aliases[$parts[0]])) { + $parts[0] = self::$aliases[$parts[0]]; + } + + if (isset($parts[1])) { + // TODO: Uncomment once Region::canonicalize() is implemented + //$parts[1] = Region::canonicalize($parts[1]); + } + + // TODO: change index to 2 once Region::canonicalize() is implemented + for ($i = 1; $i < count($parts); ++$i) { + $parts[$i] = strtoupper($parts[$i]); + } + + return implode('_', $parts); + } + + /** + * Returns the name for a language in the given locale. + * + * For example, the name of British English ("en_GB") in the locale "ru_RU" + * is "британский английский". If the resource data for the given locale + * contains no entry for the given language, then the ISO 639 language code + * is returned. + * + * If null is passed as locale, the result of + * {@link \Locale::getDefault()} is used instead. + * + * @param string $language A canonicalized ISO 639 language code + * (e.g. "en") + * @param string $displayLocale The ICU locale code to return the name in + * + * @return string The name of the language + * + * @throws InvalidArgumentException If the language or the locale is invalid + * + * @api + */ + public static function getName($language, $displayLocale = null) + { + if (!static::exists($language)) { + throw new InvalidArgumentException('The language "' . $language . '" does not exist.'); + } + + if (null !== $displayLocale && !in_array($displayLocale, Locale::getLocales(), true)) { + throw new InvalidArgumentException('The locale "' . $displayLocale . '" does not exist.'); + } + + if (null === $displayLocale) { + $displayLocale = \Locale::getDefault(); + } + + try { + return self::getDataProvider()->getName($language, $displayLocale); + } catch (NoSuchEntryException $e) { + return $language; + } + } + + /** + * Returns the names of all known languages in the specified locale. + * + * If the resource data for the given locale contains no entry for a + * language, then the canonicalized ISO 639 language code is used instead. + * + * If null is passed as locale, the result of + * {@link \Locale::getDefault()} is used instead. + * + * @param string $displayLocale The ICU locale code to return the names in + * + * @return string[] An array of language names indexed by their + * canonicalized ISO 639 language codes + * + * @throws InvalidArgumentException If the locale is invalid + * + * @api + */ + public static function getNames($displayLocale = null) + { + if (null !== $displayLocale && !in_array($displayLocale, Locale::getLocales(), true)) { + throw new InvalidArgumentException('The locale "' . $displayLocale . '" does not exist.'); + } + + if (null === $displayLocale) { + $displayLocale = \Locale::getDefault(); + } + + return self::getDataProvider()->getNames($displayLocale); + } + + /** + * Returns the ISO 639-2 three-letter code of a language. + * + * @param string $language A canonicalized ISO 639 language code (e.g. "en") + * + * @return string The ISO 639-2 three-letter code of the language + * + * @throws InvalidArgumentException If the language is invalid + * + * @api + */ + public static function getAlpha3Code($language) + { + if (!static::exists($language)) { + throw new InvalidArgumentException('The language "' . $language . '" does not exist.'); + } + + return self::getDataProvider->getAlpha3Code($language); + } + + /** + * @return LanguageDataProvider + */ + private static function getDataProvider() + { + if (null === self::$dataProvider) { + self::$dataProvider = new LanguageDataProvider( + LanguageDataProvider::getResourceDirectory(), + Intl::getEntryReader() + ); + } + + return self::$dataProvider; + } + + /** + * This class must not be instantiated. + */ + private function __construct() {} +} diff --git a/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundle.php b/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundle.php index 437a412736fad..429c24195fd85 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/CurrencyBundle.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Intl\ResourceBundle; use Symfony\Component\Icu\CurrencyDataProvider; -use Symfony\Component\Locale\Locale; +use Symfony\Component\Intl\Locale; /** * Default implementation of {@link CurrencyBundleInterface}. diff --git a/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php b/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php index 86b8d8a96d248..885576d8b10fb 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php @@ -11,27 +11,46 @@ namespace Symfony\Component\Intl\ResourceBundle; +use Symfony\Component\Icu\LanguageDataProvider; use Symfony\Component\Intl\Exception\NoSuchEntryException; +use Symfony\Component\Intl\Locale; +use Symfony\Component\Intl\ResourceBundle\Reader\BundleEntryReaderInterface; /** * Default implementation of {@link LanguageBundleInterface}. * * @author Bernhard Schussek + * + * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0. + * Use {@link LanguageDataProvider} instead. */ class LanguageBundle extends AbstractBundle implements LanguageBundleInterface { /** - * {@inheritdoc} + * @var LanguageDataProvider */ - public function getLocales() + private $languageDataProvider; + + /** + * Creates a bundle at the given path using the given reader for reading + * bundle entries. + * + * @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) { - $locales = $this->readEntry('meta', array('Locales')); + $this->languageDataProvider = new LanguageDataProvider($path, $reader); - if ($locales instanceof \Traversable) { - $locales = iterator_to_array($locales); - } + parent::__construct($path, $reader); + } - return $locales; + /** + * {@inheritdoc} + */ + public function getLocales() + { + return Locale::getLocales(); } /** @@ -47,12 +66,12 @@ public function getLanguageName($lang, $region = null, $locale = null) // i.e. "en_GB" is translated as "British English" if (null !== $region) { try { - return $this->readEntry($locale, array('Languages', $lang.'_'.$region)); + return $this->languageDataProvider->getName($lang.'_'.$region, $locale); } catch (NoSuchEntryException $e) { } } - return $this->readEntry($locale, array('Languages', $lang)); + return $this->languageDataProvider->getName($lang, $locale); } /** @@ -64,15 +83,7 @@ public function getLanguageNames($locale = null) $locale = \Locale::getDefault(); } - if (null === ($languages = $this->readEntry($locale, array('Languages')))) { - return array(); - } - - if ($languages instanceof \Traversable) { - $languages = iterator_to_array($languages); - } - - return $languages; + return $this->languageDataProvider->getNames($locale); } /** diff --git a/src/Symfony/Component/Intl/ResourceBundle/LanguageBundleInterface.php b/src/Symfony/Component/Intl/ResourceBundle/LanguageBundleInterface.php index de50bda05737b..063d1b3fff7f6 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/LanguageBundleInterface.php +++ b/src/Symfony/Component/Intl/ResourceBundle/LanguageBundleInterface.php @@ -15,6 +15,9 @@ * Gives access to language-related ICU data. * * @author Bernhard Schussek + * + * @deprecated Deprecated since version 2.5, to be removed in Symfony 3.0. + * Use {@link LanguageDataProvider} instead. */ interface LanguageBundleInterface extends ResourceBundleInterface { diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php index 140c8b9a46ec5..7fe329ca106e5 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LanguageBundleTransformationRule.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Intl\ResourceBundle\Transformer\Rule; use Symfony\Component\DependencyInjection\Tests\DefinitionDecoratorTest; +use Symfony\Component\Intl\Exception\RuntimeException; use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\ResourceBundle\LanguageBundleInterface; use Symfony\Component\Intl\ResourceBundle\Reader\BinaryBundleReader; @@ -27,6 +28,48 @@ */ class LanguageBundleTransformationRule implements TransformationRuleInterface { + /** + * Source: http://www-01.sil.org/iso639-3/codes.asp + * + * @var array + */ + private static $preferredAlpha2ToAlpha3Mapping = array( + 'ak' => 'aka', + 'ar' => 'ara', + 'ay' => 'aym', + 'az' => 'aze', + 'cr' => 'cre', + 'et' => 'est', + 'fa' => 'fas', + 'ff' => 'ful', + 'gn' => 'grn', + 'ik' => 'ipk', + 'iu' => 'iku', + 'kr' => 'kau', + 'kg' => 'kon', + 'kv' => 'kom', + 'ku' => 'kur', + 'lv' => 'lav', + 'mg' => 'mlg', + 'mn' => 'mon', + 'ms' => 'msa', + 'nb' => 'nob', + 'ne' => 'nep', + 'oj' => 'oji', + 'om' => 'orm', + 'or' => 'ori', + 'ps' => 'pus', + 'qu' => 'que', + 'ro' => 'ron', + 'sc' => 'srd', + 'sq' => 'sqi', + 'sw' => 'swa', + 'uz' => 'uzb', + 'yi' => 'yid', + 'za' => 'zha', + 'zh' => 'zho', + ); + /** * @var LanguageBundleInterface */ @@ -64,44 +107,93 @@ public function beforeCompile(CompilationContext $context) $context->getFilesystem()->mirror($sourceDir, $tempDir.'/txt'); $context->getCompiler()->compile($tempDir.'/txt', $tempDir.'/res'); - - $meta = array( - 'AvailableLocales' => $context->getLocaleScanner()->scanLocales($tempDir.'/txt'), - 'Languages' => array(), - 'Scripts' => array(), - ); + $context->getCompiler()->compile($context->getSourceDir().'/misc/metadata.txt', $tempDir.'/res'); $reader = new BinaryBundleReader(); + $writer = new TextBundleWriter(); + + // Collect supported locales of the bundle + $availableLocales = $context->getLocaleScanner()->scanLocales($tempDir.'/txt'); + + // Drop and regenerate txt files + $context->getFilesystem()->remove($tempDir.'/txt'); + $context->getFilesystem()->mkdir($tempDir.'/txt'); + + $languages = array(); // Collect complete list of languages and scripts in all locales - foreach ($meta['AvailableLocales'] as $locale) { + foreach ($availableLocales as $locale) { $bundle = $reader->read($tempDir.'/res', $locale); // isset() on \ResourceBundle returns true even if the value is null if (isset($bundle['Languages']) && null !== $bundle['Languages']) { - $meta['Languages'] = array_merge( - $meta['Languages'], - array_keys(iterator_to_array($bundle['Languages'])) - ); - } + $languageNames = iterator_to_array($bundle['Languages']); + + $writer->write($tempDir.'/txt', $locale, array( + 'Version' => $bundle['Version'], + 'Languages' => $languageNames, + )); - if (isset($bundle['Scripts']) && null !== $bundle['Scripts']) { - $meta['Scripts'] = array_merge( - $meta['Scripts'], - array_keys(iterator_to_array($bundle['Scripts'])) - ); + $languages = array_merge($languages, array_keys($languageNames)); } } - $meta['Languages'] = array_unique($meta['Languages']); - sort($meta['Languages']); - - $meta['Scripts'] = array_unique($meta['Scripts']); - sort($meta['Scripts']); + $languages = array_unique($languages); + sort($languages); + + $root = $reader->read($tempDir.'/res', 'root'); + + // Read the metadata bundle with the language aliases + $metadata = $reader->read($tempDir.'/res', 'metadata'); + + // Create the mapping from two-letter to three-letter codes + $aliases = $metadata['languageAlias']; + $alpha2ToAlpha3 = array(); + + foreach ($aliases as $alias => $language) { + if (2 === strlen($language) && 3 === strlen($alias)) { + 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]; + + if ($language !== $aliases[$alpha3]) { + throw new RuntimeException( + 'The statically set three-letter mapping '.$alpha3.' '. + 'for the language code '.$language.' seems to be '. + 'an alias for '.$aliases[$alpha3].'. 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; + } + } + } - // Create meta file with all available locales + // Create root file with all available locales $writer = new TextBundleWriter(); - $writer->write($tempDir.'/txt', 'meta', $meta, false); + $writer->write($tempDir.'/txt', 'root', array( + 'Version' => $root['Version'], + 'Languages' => array_combine($languages, $languages), + 'Aliases' => $metadata['languageAlias'], + 'Alpha2ToAlpha3' => $alpha2ToAlpha3, + )); return $tempDir.'/txt'; } @@ -122,7 +214,6 @@ public function beforeCreateStub(StubbingContext $context) { return array( 'Languages' => $this->languageBundle->getLanguageNames('en'), - 'Scripts' => $this->languageBundle->getScriptNames('en'), ); } diff --git a/src/Symfony/Component/Intl/Tests/DataProvider/AbstractCurrencyDataProviderTest.php b/src/Symfony/Component/Intl/Tests/DataProvider/AbstractCurrencyDataProviderTest.php index 9d3dc3e939e31..3ece5856df077 100644 --- a/src/Symfony/Component/Intl/Tests/DataProvider/AbstractCurrencyDataProviderTest.php +++ b/src/Symfony/Component/Intl/Tests/DataProvider/AbstractCurrencyDataProviderTest.php @@ -16,9 +16,6 @@ */ abstract class AbstractCurrencyDataProviderTest extends AbstractDataProviderTest { - protected static $localesWithoutTranslationForAnyCurrency = array(); - protected static $localesWithoutTranslationForCurrency = array(); - protected static $currencies = array(); protected static $alpha3ToNumeric = array(); @@ -43,7 +40,7 @@ function ($currency) { return array($currency); }, ); } - public function testGetAlpha3CurrencyCodes() + public function testGetCurrencies() { $this->assertEquals(static::$currencies, $this->dataProvider->getCurrencies()); } diff --git a/src/Symfony/Component/Intl/Tests/DataProvider/AbstractLanguageDataProviderTest.php b/src/Symfony/Component/Intl/Tests/DataProvider/AbstractLanguageDataProviderTest.php new file mode 100644 index 0000000000000..8148bb122c560 --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/DataProvider/AbstractLanguageDataProviderTest.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Tests\DataProvider; + +/** + * @author Bernhard Schussek + */ +abstract class AbstractLanguageDataProviderTest extends AbstractDataProviderTest +{ + protected static $languages = array(); + protected static $alpha2ToAlpha3 = array(); + + /** + * @var \Symfony\Component\Icu\LanguageDataProvider + */ + protected $dataProvider; + + protected function setUp() + { + $this->dataProvider = $this->createDataProvider(); + } + + abstract protected function createDataProvider(); + + public function testGetLanguages() + { + $this->assertEquals(static::$languages, $this->dataProvider->getLanguages()); + } + + /** + * @dataProvider provideLocales + */ + public function testGetNames($displayLocale) + { + $languages = array_keys($this->dataProvider->getNames($displayLocale)); + + sort($languages); + + $this->assertEquals(static::$languages, $languages); + } + + /** + * @dataProvider provideLocaleAliases + * @group locale-alias-based + */ + public function testGetNamesSupportsAliases($alias, $ofLocale) + { + $this->assertEquals( + $this->dataProvider->getNames($ofLocale), + $this->dataProvider->getNames($alias) + ); + } + + /** + * @dataProvider provideLocales + */ + public function testGetName($displayLocale) + { + $names = $this->dataProvider->getNames($displayLocale); + + foreach ($names as $language => $name) { + $this->assertSame($name, $this->dataProvider->getName($language, $displayLocale)); + } + } + + public function provideLanguagesWithAlpha3Equivalent() + { + return array_map( + function ($value) { return array($value); }, + array_keys(static::$alpha2ToAlpha3) + ); + } + + /** + * @dataProvider provideLanguagesWithAlpha3Equivalent + */ + public function testGetAlpha3Code($language) + { + $this->assertSame(static::$alpha2ToAlpha3[$language], $this->dataProvider->getAlpha3Code($language)); + } + + public function provideLanguagesWithoutAlpha3Equivalent() + { + return array_map( + function ($value) { return array($value); }, + array_diff(static::$languages, array_keys(static::$alpha2ToAlpha3)) + ); + } + + /** + * @dataProvider provideLanguagesWithoutAlpha3Equivalent + * @expectedException \Symfony\Component\Intl\Exception\NoSuchEntryException + */ + public function testGetAlpha3CodeFailsIfNoAlpha3Equivalent($currency) + { + $this->dataProvider->getAlpha3Code($currency); + } +} diff --git a/src/Symfony/Component/Intl/Tests/LanguageTest.php b/src/Symfony/Component/Intl/Tests/LanguageTest.php new file mode 100644 index 0000000000000..c4924c6f25a16 --- /dev/null +++ b/src/Symfony/Component/Intl/Tests/LanguageTest.php @@ -0,0 +1,113 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Intl\Tests; + +use Symfony\Component\Intl\Language; + +/** + * @author Bernhard Schussek + */ +class LanguageTest extends \PHPUnit_Framework_TestCase +{ + public function existsProvider() + { + return array( + array(true, 'de'), + array(true, 'de_AT'), + // scripts are not supported + array(false, 'de_Latn_AT'), + // different casing is not supported + array(false, 'De_AT'), + // hyphens are not supported + array(false, 'de-AT'), + // aliases with individual translations are supported + array(true, 'mo'), + // ISO 936-2 is not supported if an equivalent exists in ISO 936-1 + array(false, 'deu'), + array(false, 'deu_AT'), + // country aliases are not supported + array(false, 'de_AUT'), + ); + } + + /** + * @dataProvider existsProvider + */ + public function testExists($exists, $language) + { + $this->assertSame($exists, Language::exists($language)); + } + + public function canonicalizationProvider() + { + return array( + array('EN-GB', 'en_GB'), + array('De_at', 'de_AT'), + // Scripts in languages are not supported and are interpreted + // as custom additional subtags + array('IT_Latn_IT', 'it_LATN_IT'), + // Aliases are converted + array('DEU', 'de'), + array('deu-CH', 'de_CH'), + // Aliases with individual translations are not converted + array('mo', 'mo'), + // Country aliases are converted + // TODO uncomment once the Region class is implemented + //array('de_AUT', 'de_AT'), + ); + } + + /** + * @dataProvider canonicalizationProvider + */ + public function testCanonicalize($language, $canonicalized) + { + $this->assertSame($canonicalized, Language::canonicalize($language)); + } + + public function testGetName() + { + $this->assertSame('German', Language::getName('de', 'en')); + } + + /** + * @expectedException \Symfony\Component\Intl\Exception\InvalidArgumentException + */ + public function testGetNameFailsOnInvalidLanguage() + { + Language::getName('FOO'); + } + + /** + * @expectedException \Symfony\Component\Intl\Exception\InvalidArgumentException + */ + public function testGetNameFailsOnInvalidDisplayLocale() + { + Language::getName('de', 'foo'); + } + + public function testGetNames() + { + $names = Language::getNames('en'); + + $this->assertArrayHasKey('de', $names); + $this->assertSame('German', $names['de']); + } + + /** + * @expectedException \Symfony\Component\Intl\Exception\InvalidArgumentException + */ + public function testGetNamesFailsOnInvalidDisplayLocale() + { + Language::getNames('foo'); + } +} From 42f123821c742949223a7a02c3f3d1f0dd8731b1 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Mon, 14 Oct 2013 17:58:14 +0200 Subject: [PATCH 43/48] [Intl] Renamed NoSuchEntryException to MissingResourceException --- UPGRADE-2.4.md | 6 ++--- src/Symfony/Component/Intl/CHANGELOG.md | 2 +- src/Symfony/Component/Intl/Currency.php | 26 ++++++------------- ...ption.php => MissingResourceException.php} | 2 +- src/Symfony/Component/Intl/Language.php | 13 ++++------ src/Symfony/Component/Intl/Locale.php | 1 - .../Intl/ResourceBundle/LanguageBundle.php | 4 +-- .../Reader/BundleEntryReader.php | 4 +-- .../Reader/BundleEntryReaderInterface.php | 5 ++-- .../Rule/LocaleBundleTransformationRule.php | 4 +-- .../LanguageBundleConsistencyTestCase.php | 10 +++---- .../Test/RegionBundleConsistencyTestCase.php | 6 ++--- .../AbstractLanguageDataProviderTest.php | 2 +- .../ResourceBundle/LanguageBundleTest.php | 4 +-- .../Reader/BundleEntryReaderTest.php | 8 +++--- 15 files changed, 42 insertions(+), 55 deletions(-) rename src/Symfony/Component/Intl/Exception/{NoSuchEntryException.php => MissingResourceException.php} (88%) diff --git a/UPGRADE-2.4.md b/UPGRADE-2.4.md index aa7679e5e5cd3..18395c3fd56a7 100644 --- a/UPGRADE-2.4.md +++ b/UPGRADE-2.4.md @@ -17,7 +17,7 @@ Intl * The methods in the various resource bundles of the `Intl` class used to return `null` when invalid arguments were given. These methods throw a - `NoSuchEntryException` now. + `MissingResourceException` now. Before: @@ -39,7 +39,7 @@ Intl ``` use Symfony\Component\Intl\Intl; - use Symfony\Component\Intl\Exception\NoSuchEntryException; + use Symfony\Component\Intl\Exception\MissingResourceException; use Symfony\Component\Intl\Exception\NoSuchLocaleException; try { @@ -48,7 +48,7 @@ Intl // invalid locale $language = Intl::getLanguageBundle()->getLanguageName('de', null, 'foo'); - } catch (NoSuchEntryException $e) { + } catch (MissingResourceException $e) { if ($e->getPrevious() instanceof NoSuchLocaleException) { // locale was invalid... } else { diff --git a/src/Symfony/Component/Intl/CHANGELOG.md b/src/Symfony/Component/Intl/CHANGELOG.md index 00021602a06be..d91887ebf2365 100644 --- a/src/Symfony/Component/Intl/CHANGELOG.md +++ b/src/Symfony/Component/Intl/CHANGELOG.md @@ -4,7 +4,7 @@ CHANGELOG 2.4.0 ----- - * [BC BREAK] the various Intl methods now throw a `NoSuchEntryException` + * [BC BREAK] the various Intl methods now throw a `MissingResourceException` whenever a non-existing locale, language, currency, etc. is accessed * the available locales of each resource bundle are now stored in a generic "misc.res" file in order to improve reading performance diff --git a/src/Symfony/Component/Intl/Currency.php b/src/Symfony/Component/Intl/Currency.php index e364313efd144..0297f51bd7ba3 100644 --- a/src/Symfony/Component/Intl/Currency.php +++ b/src/Symfony/Component/Intl/Currency.php @@ -65,8 +65,7 @@ public static function getCurrencies() * * @return string The currency symbol for the specified locale * - * @throws Exception\InvalidArgumentException If the currency or the locale - * is invalid + * @throws InvalidArgumentException If the currency or the locale is invalid * * @api */ @@ -84,11 +83,7 @@ public static function getSymbol($currency, $displayLocale = null) $displayLocale = \Locale::getDefault(); } - try { - return self::getDataProvider()->getSymbol($currency, $displayLocale); - } catch (NoSuchEntryException $e) { - return $currency; - } + return self::getDataProvider()->getSymbol($currency, $displayLocale); } /** @@ -106,8 +101,7 @@ public static function getSymbol($currency, $displayLocale = null) * * @return string The name of the currency * - * @throws Exception\InvalidArgumentException If the currency or the locale - * is invalid + * @throws InvalidArgumentException If the currency or the locale is invalid * * @api */ @@ -125,11 +119,7 @@ public static function getName($currency, $displayLocale = null) $displayLocale = \Locale::getDefault(); } - try { - return self::getDataProvider()->getName($currency, $displayLocale); - } catch (NoSuchEntryException $e) { - return $currency; - } + return self::getDataProvider()->getName($currency, $displayLocale); } /** @@ -145,7 +135,7 @@ public static function getName($currency, $displayLocale = null) * * @return string[] An array of currency names indexed by currency codes * - * @throws Exception\InvalidArgumentException If the locale is invalid + * @throws InvalidArgumentException If the locale is invalid * * @api */ @@ -172,7 +162,7 @@ public static function getNames($displayLocale = null) * * @return integer The number of digits after the comma * - * @throws Exception\InvalidArgumentException If the currency is invalid + * @throws InvalidArgumentException If the currency is invalid * * @api */ @@ -196,7 +186,7 @@ public static function getFractionDigits($currency) * * @return integer The rounding increment * - * @throws Exception\InvalidArgumentException If the currency is invalid + * @throws InvalidArgumentException If the currency is invalid * * @api */ @@ -219,7 +209,7 @@ public static function getRoundingIncrement($currency) * * @return integer The numeric code * - * @throws Exception\InvalidArgumentException If the currency is invalid + * @throws InvalidArgumentException If the currency is invalid * * @api */ diff --git a/src/Symfony/Component/Intl/Exception/NoSuchEntryException.php b/src/Symfony/Component/Intl/Exception/MissingResourceException.php similarity index 88% rename from src/Symfony/Component/Intl/Exception/NoSuchEntryException.php rename to src/Symfony/Component/Intl/Exception/MissingResourceException.php index 494d816887993..e2eb3f210e751 100644 --- a/src/Symfony/Component/Intl/Exception/NoSuchEntryException.php +++ b/src/Symfony/Component/Intl/Exception/MissingResourceException.php @@ -16,6 +16,6 @@ * * @author Bernhard Schussek */ -class NoSuchEntryException extends RuntimeException +class MissingResourceException extends RuntimeException { } diff --git a/src/Symfony/Component/Intl/Language.php b/src/Symfony/Component/Intl/Language.php index ffb24461cf149..ad59b84375a58 100644 --- a/src/Symfony/Component/Intl/Language.php +++ b/src/Symfony/Component/Intl/Language.php @@ -13,7 +13,6 @@ use Symfony\Component\Icu\LanguageDataProvider; use Symfony\Component\Intl\Exception\InvalidArgumentException; -use Symfony\Component\Intl\Exception\NoSuchEntryException; /** * Provides access to language-related data. @@ -193,11 +192,7 @@ public static function getName($language, $displayLocale = null) $displayLocale = \Locale::getDefault(); } - try { - return self::getDataProvider()->getName($language, $displayLocale); - } catch (NoSuchEntryException $e) { - return $language; - } + return self::getDataProvider()->getName($language, $displayLocale); } /** @@ -238,7 +233,9 @@ public static function getNames($displayLocale = null) * * @return string The ISO 639-2 three-letter code of the language * - * @throws InvalidArgumentException If the language is invalid + * @throws Exception\InvalidArgumentException If the language is invalid + * @throws Exception\MissingResourceException If the language has no + * corresponding three-letter code * * @api */ @@ -248,7 +245,7 @@ public static function getAlpha3Code($language) throw new InvalidArgumentException('The language "' . $language . '" does not exist.'); } - return self::getDataProvider->getAlpha3Code($language); + return self::getDataProvider()->getAlpha3Code($language); } /** diff --git a/src/Symfony/Component/Intl/Locale.php b/src/Symfony/Component/Intl/Locale.php index 28ff2bb410127..90a214e44a8ef 100644 --- a/src/Symfony/Component/Intl/Locale.php +++ b/src/Symfony/Component/Intl/Locale.php @@ -13,7 +13,6 @@ use Symfony\Component\Icu\LocaleDataProvider; use Symfony\Component\Intl\Exception\InvalidArgumentException; -use Symfony\Component\Intl\Exception\NoSuchEntryException; /** * Provides access to locale-related data. diff --git a/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php b/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php index 885576d8b10fb..e47b6d90d25c8 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/LanguageBundle.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Intl\ResourceBundle; use Symfony\Component\Icu\LanguageDataProvider; -use Symfony\Component\Intl\Exception\NoSuchEntryException; +use Symfony\Component\Intl\Exception\MissingResourceException; use Symfony\Component\Intl\Locale; use Symfony\Component\Intl\ResourceBundle\Reader\BundleEntryReaderInterface; @@ -67,7 +67,7 @@ public function getLanguageName($lang, $region = null, $locale = null) if (null !== $region) { try { return $this->languageDataProvider->getName($lang.'_'.$region, $locale); - } catch (NoSuchEntryException $e) { + } catch (MissingResourceException $e) { } } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReader.php index 9d9b36455d675..28ecf41b8075a 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReader.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReader.php @@ -12,7 +12,7 @@ namespace Symfony\Component\Intl\ResourceBundle\Reader; use Symfony\Component\Intl\Intl; -use Symfony\Component\Intl\Exception\NoSuchEntryException; +use Symfony\Component\Intl\Exception\MissingResourceException; use Symfony\Component\Intl\Exception\ResourceBundleNotFoundException; use Symfony\Component\Intl\Exception\OutOfBoundsException; use Symfony\Component\Intl\ResourceBundle\Util\RecursiveArrayAccess; @@ -177,6 +177,6 @@ public function readEntry($path, $locale, array $indices, $fallback = true) ); } - throw new NoSuchEntryException($errorMessage, 0, $exception); + throw new MissingResourceException($errorMessage, 0, $exception); } } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReaderInterface.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReaderInterface.php index 00c15e9fff9df..a7001de831735 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReaderInterface.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReaderInterface.php @@ -44,8 +44,9 @@ interface BundleEntryReaderInterface extends BundleReaderInterface * in the requested locale. * * @return mixed Returns an array or {@link \ArrayAccess} instance for - * complex data, a scalar value for simple data and NULL - * if the given path could not be accessed. + * complex data and a scalar value for simple data. + * + * @throws \Symfony\Component\Intl\Exception\MissingResourceException If the indices cannot be accessed */ public function readEntry($path, $locale, array $indices, $fallback = true); } diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php index bbdc86fa803e9..202dfcfd81d02 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Intl\ResourceBundle\Transformer\Rule; -use Symfony\Component\Intl\Exception\NoSuchEntryException; +use Symfony\Component\Intl\Exception\MissingResourceException; use Symfony\Component\Intl\Exception\ResourceBundleNotFoundException; use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\ResourceBundle\LanguageBundleInterface; @@ -148,7 +148,7 @@ private function generateTextFiles(TextBundleWriter $writer, $targetDirectory, a if (null !== ($name = $this->generateLocaleName($locale, $displayLocale))) { $localeNames[$displayLocale][$locale] = $name; } - } catch (NoSuchEntryException $e) { + } catch (MissingResourceException $e) { } catch (ResourceBundleNotFoundException $e) { } } diff --git a/src/Symfony/Component/Intl/Test/LanguageBundleConsistencyTestCase.php b/src/Symfony/Component/Intl/Test/LanguageBundleConsistencyTestCase.php index 61710124bd4c4..7410f85d1608e 100644 --- a/src/Symfony/Component/Intl/Test/LanguageBundleConsistencyTestCase.php +++ b/src/Symfony/Component/Intl/Test/LanguageBundleConsistencyTestCase.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Intl\Test; -use Symfony\Component\Intl\Exception\NoSuchEntryException; +use Symfony\Component\Intl\Exception\MissingResourceException; use Symfony\Component\Intl\Intl; /** @@ -84,7 +84,7 @@ public function testGetLanguageNames() try { Intl::getLanguageBundle()->getLanguageNames($displayLocale); $translatedLocales[] = $displayLocale; - } catch (NoSuchEntryException $e) { + } catch (MissingResourceException $e) { } } @@ -115,7 +115,7 @@ public function testGetLanguageName($language) try { Intl::getLanguageBundle()->getLanguageName($language ?: $displayLocale, null, $displayLocale); $translatedLocales[] = $displayLocale; - } catch (NoSuchEntryException $e) { + } catch (MissingResourceException $e) { } } @@ -135,7 +135,7 @@ public function testGetScriptNames() try { Intl::getLanguageBundle()->getScriptNames($displayLocale); $translatedLocales[] = $displayLocale; - } catch (NoSuchEntryException $e) { + } catch (MissingResourceException $e) { } } @@ -166,7 +166,7 @@ public function testGetScriptName($script) try { Intl::getLanguageBundle()->getScriptName($script, null, $displayLocale); $translatedLocales[] = $displayLocale; - } catch (NoSuchEntryException $e) { + } catch (MissingResourceException $e) { } } diff --git a/src/Symfony/Component/Intl/Test/RegionBundleConsistencyTestCase.php b/src/Symfony/Component/Intl/Test/RegionBundleConsistencyTestCase.php index 54af15d502c81..d133b7ebb16a2 100644 --- a/src/Symfony/Component/Intl/Test/RegionBundleConsistencyTestCase.php +++ b/src/Symfony/Component/Intl/Test/RegionBundleConsistencyTestCase.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Intl\Test; -use Symfony\Component\Intl\Exception\NoSuchEntryException; +use Symfony\Component\Intl\Exception\MissingResourceException; use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\Test\ConsistencyTestCase; @@ -81,7 +81,7 @@ public function testGetCountryNames() try { Intl::getRegionBundle()->getCountryNames($displayLocale); $translatedLocales[] = $displayLocale; - } catch (NoSuchEntryException $e) { + } catch (MissingResourceException $e) { } } @@ -112,7 +112,7 @@ public function testGetCountryName($country) try { Intl::getRegionBundle()->getCountryName($country, $displayLocale); $translatedLocales[] = $displayLocale; - } catch (NoSuchEntryException $e) { + } catch (MissingResourceException $e) { } } diff --git a/src/Symfony/Component/Intl/Tests/DataProvider/AbstractLanguageDataProviderTest.php b/src/Symfony/Component/Intl/Tests/DataProvider/AbstractLanguageDataProviderTest.php index 8148bb122c560..f7253785fc45d 100644 --- a/src/Symfony/Component/Intl/Tests/DataProvider/AbstractLanguageDataProviderTest.php +++ b/src/Symfony/Component/Intl/Tests/DataProvider/AbstractLanguageDataProviderTest.php @@ -98,7 +98,7 @@ function ($value) { return array($value); }, /** * @dataProvider provideLanguagesWithoutAlpha3Equivalent - * @expectedException \Symfony\Component\Intl\Exception\NoSuchEntryException + * @expectedException \Symfony\Component\Intl\Exception\MissingResourceException */ public function testGetAlpha3CodeFailsIfNoAlpha3Equivalent($currency) { diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/LanguageBundleTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/LanguageBundleTest.php index 25326b7ff6cd5..dc0ee2b7d7466 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/LanguageBundleTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/LanguageBundleTest.php @@ -11,7 +11,7 @@ namespace Symfony\Component\Intl\Tests\ResourceBundle; -use Symfony\Component\Intl\Exception\NoSuchEntryException; +use Symfony\Component\Intl\Exception\MissingResourceException; use Symfony\Component\Intl\ResourceBundle\LanguageBundle; /** @@ -62,7 +62,7 @@ public function testGetLanguageNameWithUntranslatedRegion() $this->reader->expects($this->at(0)) ->method('readEntry') ->with(self::RES_DIR, 'en', array('Languages', 'en_US')) - ->will($this->throwException(new NoSuchEntryException())); + ->will($this->throwException(new MissingResourceException())); $this->reader->expects($this->at(1)) ->method('readEntry') diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BundleEntryReaderTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BundleEntryReaderTest.php index 52ed96ae568b8..b7f806cbbb78b 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BundleEntryReaderTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Reader/BundleEntryReaderTest.php @@ -102,7 +102,7 @@ public function testReadExistingEntry() } /** - * @expectedException \Symfony\Component\Intl\Exception\NoSuchEntryException + * @expectedException \Symfony\Component\Intl\Exception\MissingResourceException */ public function testReadNonExistingEntry() { @@ -130,7 +130,7 @@ public function testFallbackIfEntryDoesNotExist() } /** - * @expectedException \Symfony\Component\Intl\Exception\NoSuchEntryException + * @expectedException \Symfony\Component\Intl\Exception\MissingResourceException */ public function testDontFallbackIfEntryDoesNotExistAndFallbackDisabled() { @@ -158,7 +158,7 @@ public function testFallbackIfLocaleDoesNotExist() } /** - * @expectedException \Symfony\Component\Intl\Exception\NoSuchEntryException + * @expectedException \Symfony\Component\Intl\Exception\MissingResourceException */ public function testDontFallbackIfLocaleDoesNotExistAndFallbackDisabled() { @@ -290,7 +290,7 @@ public function testMergeExistingEntryWithNonExistingFallbackEntry($childData, $ } /** - * @expectedException \Symfony\Component\Intl\Exception\NoSuchEntryException + * @expectedException \Symfony\Component\Intl\Exception\MissingResourceException */ public function testFailIfEntryFoundNeitherInParentNorChild() { From 1b6aab02e9f8ec3f0c12e9997826066abaa3a960 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Mon, 14 Oct 2013 18:04:27 +0200 Subject: [PATCH 44/48] [Intl] Moved Intl::getFallbackLocale() to Locale::getFallback() --- src/Symfony/Component/Intl/Intl.php | 20 ----------- src/Symfony/Component/Intl/Locale.php | 23 ++++++++++++ .../Reader/BundleEntryReader.php | 4 +-- .../Rule/LocaleBundleTransformationRule.php | 4 +-- .../LanguageBundleConsistencyTestCase.php | 5 +-- .../Test/RegionBundleConsistencyTestCase.php | 5 +-- src/Symfony/Component/Intl/Tests/IntlTest.php | 35 ------------------- .../Component/Intl/Tests/LocaleTest.php | 15 ++++++++ 8 files changed, 48 insertions(+), 63 deletions(-) delete mode 100644 src/Symfony/Component/Intl/Tests/IntlTest.php diff --git a/src/Symfony/Component/Intl/Intl.php b/src/Symfony/Component/Intl/Intl.php index 22fb1b450e0d8..259836f8f69db 100644 --- a/src/Symfony/Component/Intl/Intl.php +++ b/src/Symfony/Component/Intl/Intl.php @@ -193,26 +193,6 @@ public static function getIcuStubVersion() return '51.2'; } - /** - * Returns the fallback locale for a given locale, if any - * - * @param string $locale The locale to find the fallback for. - * - * @return string|null The fallback locale, or null if no parent exists - */ - public static function getFallbackLocale($locale) - { - if (false === $pos = strrpos($locale, '_')) { - if ('root' === $locale) { - return null; - } - - return 'root'; - } - - return substr($locale, 0, $pos); - } - /** * Returns a resource bundle reader for .php resource bundle files. * diff --git a/src/Symfony/Component/Intl/Locale.php b/src/Symfony/Component/Intl/Locale.php index 90a214e44a8ef..1ce5b27029673 100644 --- a/src/Symfony/Component/Intl/Locale.php +++ b/src/Symfony/Component/Intl/Locale.php @@ -172,6 +172,29 @@ public static function getAliases() return self::getDataProvider()->getAliases(); } + /** + * Returns the fallback locale for a given locale, if any + * + * @param string $locale The ICU locale code to find the fallback for. + * + * @return string|null The ICU locale code of the fallback locale, or null + * if no fallback exists + * + * @api + */ + public static function getFallback($locale) + { + if (false === $pos = strrpos($locale, '_')) { + if ('root' === $locale) { + return null; + } + + return 'root'; + } + + return substr($locale, 0, $pos); + } + /** * @return LocaleDataProvider */ diff --git a/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReader.php b/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReader.php index 28ecf41b8075a..bf8965646259b 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReader.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Reader/BundleEntryReader.php @@ -11,10 +11,10 @@ namespace Symfony\Component\Intl\ResourceBundle\Reader; -use Symfony\Component\Intl\Intl; use Symfony\Component\Intl\Exception\MissingResourceException; use Symfony\Component\Intl\Exception\ResourceBundleNotFoundException; use Symfony\Component\Intl\Exception\OutOfBoundsException; +use Symfony\Component\Intl\Locale; use Symfony\Component\Intl\ResourceBundle\Util\RecursiveArrayAccess; /** @@ -144,7 +144,7 @@ public function readEntry($path, $locale, array $indices, $fallback = true) } // Then determine fallback locale - $currentLocale = Intl::getFallbackLocale($currentLocale); + $currentLocale = Locale::getFallback($currentLocale); } // Multi-valued entry was merged diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php index 202dfcfd81d02..4890524c2e77f 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/LocaleBundleTransformationRule.php @@ -13,7 +13,7 @@ use Symfony\Component\Intl\Exception\MissingResourceException; use Symfony\Component\Intl\Exception\ResourceBundleNotFoundException; -use Symfony\Component\Intl\Intl; +use Symfony\Component\Intl\Locale; use Symfony\Component\Intl\ResourceBundle\LanguageBundleInterface; use Symfony\Component\Intl\ResourceBundle\LocaleBundleInterface; use Symfony\Component\Intl\ResourceBundle\RegionBundleInterface; @@ -254,7 +254,7 @@ private function generateFallbackMapping(array $displayLocales, array $aliases) $fallback = $displayLocale; // Recursively search for a fallback locale until one is found - while (null !== ($fallback = Intl::getFallbackLocale($fallback))) { + while (null !== ($fallback = Locale::getFallback($fallback))) { // Currently, no locale has an alias as fallback locale. // If this starts to be the case, we need to add code here. assert(!isset($aliases[$fallback])); diff --git a/src/Symfony/Component/Intl/Test/LanguageBundleConsistencyTestCase.php b/src/Symfony/Component/Intl/Test/LanguageBundleConsistencyTestCase.php index 7410f85d1608e..46ff99fc24637 100644 --- a/src/Symfony/Component/Intl/Test/LanguageBundleConsistencyTestCase.php +++ b/src/Symfony/Component/Intl/Test/LanguageBundleConsistencyTestCase.php @@ -13,6 +13,7 @@ use Symfony\Component\Intl\Exception\MissingResourceException; use Symfony\Component\Intl\Intl; +use Symfony\Component\Intl\Locale; /** * @author Bernhard Schussek @@ -39,7 +40,7 @@ public function provideLocalesWithScripts() return false; } - $locale = Intl::getFallbackLocale($locale); + $locale = Locale::getFallback($locale); } return true; @@ -62,7 +63,7 @@ public function provideLocaleAliasesWithScripts() return false; } - $targetLocale = Intl::getFallbackLocale($targetLocale); + $targetLocale = Locale::getFallback($targetLocale); } return true; diff --git a/src/Symfony/Component/Intl/Test/RegionBundleConsistencyTestCase.php b/src/Symfony/Component/Intl/Test/RegionBundleConsistencyTestCase.php index d133b7ebb16a2..17e0db28513a0 100644 --- a/src/Symfony/Component/Intl/Test/RegionBundleConsistencyTestCase.php +++ b/src/Symfony/Component/Intl/Test/RegionBundleConsistencyTestCase.php @@ -13,6 +13,7 @@ use Symfony\Component\Intl\Exception\MissingResourceException; use Symfony\Component\Intl\Intl; +use Symfony\Component\Intl\Locale; use Symfony\Component\Intl\Test\ConsistencyTestCase; /** @@ -36,7 +37,7 @@ public function provideLocalesWithCountries() return false; } - $locale = Intl::getFallbackLocale($locale); + $locale = Locale::getFallback($locale); } return true; @@ -59,7 +60,7 @@ public function provideLocaleAliasesWithCountries() return false; } - $targetLocale = Intl::getFallbackLocale($targetLocale); + $targetLocale = Locale::getFallback($targetLocale); } return true; diff --git a/src/Symfony/Component/Intl/Tests/IntlTest.php b/src/Symfony/Component/Intl/Tests/IntlTest.php deleted file mode 100644 index f54d89ff5d92b..0000000000000 --- a/src/Symfony/Component/Intl/Tests/IntlTest.php +++ /dev/null @@ -1,35 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\Intl\Tests; - -use Symfony\Component\Intl\Intl; - -/** - * @author Bernhard Schussek - */ -class IntlTest extends \PHPUnit_Framework_TestCase -{ - public function testGetFallbackLocale() - { - $this->assertSame('fr', Intl::getFallbackLocale('fr_FR')); - } - - public function testGetFallbackLocaleForTopLevelLocale() - { - $this->assertSame('root', Intl::getFallbackLocale('en')); - } - - public function testGetFallbackLocaleForRoot() - { - $this->assertNull(Intl::getFallbackLocale('root')); - } -} diff --git a/src/Symfony/Component/Intl/Tests/LocaleTest.php b/src/Symfony/Component/Intl/Tests/LocaleTest.php index 027643a65fa7b..c552763d7a626 100644 --- a/src/Symfony/Component/Intl/Tests/LocaleTest.php +++ b/src/Symfony/Component/Intl/Tests/LocaleTest.php @@ -59,4 +59,19 @@ public function testGetNamesFailsOnInvalidDisplayLocale() { Locale::getNames('foo'); } + + public function testGetFallback() + { + $this->assertSame('fr', Locale::getFallback('fr_FR')); + } + + public function testGetFallbackForTopLevelLocale() + { + $this->assertSame('root', Locale::getFallback('en')); + } + + public function testGetFallbackForRoot() + { + $this->assertNull(Locale::getFallback('root')); + } } From a7e8e683759ef5cdba2a72d3563dcfc84b589864 Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Mon, 14 Oct 2013 18:17:13 +0200 Subject: [PATCH 45/48] [Intl] Implemented Locale::exists() --- src/Symfony/Component/Intl/Currency.php | 6 +-- src/Symfony/Component/Intl/Language.php | 6 +-- src/Symfony/Component/Intl/Locale.php | 38 ++++++++++++++++++- .../Component/Intl/Tests/LanguageTest.php | 6 +++ .../Component/Intl/Tests/LocaleTest.php | 31 +++++++++++++++ 5 files changed, 79 insertions(+), 8 deletions(-) diff --git a/src/Symfony/Component/Intl/Currency.php b/src/Symfony/Component/Intl/Currency.php index 0297f51bd7ba3..0909b7829c521 100644 --- a/src/Symfony/Component/Intl/Currency.php +++ b/src/Symfony/Component/Intl/Currency.php @@ -75,7 +75,7 @@ public static function getSymbol($currency, $displayLocale = null) throw new InvalidArgumentException('The currency "' . $currency . '" does not exist.'); } - if (null !== $displayLocale && !in_array($displayLocale, Locale::getLocales(), true)) { + if (null !== $displayLocale && !Locale::exists($displayLocale)) { throw new InvalidArgumentException('The locale "' . $displayLocale . '" does not exist.'); } @@ -111,7 +111,7 @@ public static function getName($currency, $displayLocale = null) throw new InvalidArgumentException('The currency "' . $currency . '" does not exist.'); } - if (null !== $displayLocale && !in_array($displayLocale, Locale::getLocales(), true)) { + if (null !== $displayLocale && !Locale::exists($displayLocale)) { throw new InvalidArgumentException('The locale "' . $displayLocale . '" does not exist.'); } @@ -141,7 +141,7 @@ public static function getName($currency, $displayLocale = null) */ public static function getNames($displayLocale = null) { - if (null !== $displayLocale && !in_array($displayLocale, Locale::getLocales(), true)) { + if (null !== $displayLocale && !Locale::exists($displayLocale)) { throw new InvalidArgumentException('The locale "' . $displayLocale . '" does not exist.'); } diff --git a/src/Symfony/Component/Intl/Language.php b/src/Symfony/Component/Intl/Language.php index ad59b84375a58..6722b5450940e 100644 --- a/src/Symfony/Component/Intl/Language.php +++ b/src/Symfony/Component/Intl/Language.php @@ -80,7 +80,7 @@ public static function getLanguages() * provided where an equivalent ISO 639-1 two-letter code exists. * * If you want to support the above cases, you should manually canonicalize - * the language code in prior to calling this method. + * the language code prior to calling this method. * * @param string $language A canonicalized ISO 639 language code (e.g. "en") * @@ -184,7 +184,7 @@ public static function getName($language, $displayLocale = null) throw new InvalidArgumentException('The language "' . $language . '" does not exist.'); } - if (null !== $displayLocale && !in_array($displayLocale, Locale::getLocales(), true)) { + if (null !== $displayLocale && !Locale::exists($displayLocale)) { throw new InvalidArgumentException('The locale "' . $displayLocale . '" does not exist.'); } @@ -215,7 +215,7 @@ public static function getName($language, $displayLocale = null) */ public static function getNames($displayLocale = null) { - if (null !== $displayLocale && !in_array($displayLocale, Locale::getLocales(), true)) { + if (null !== $displayLocale && !Locale::exists($displayLocale)) { throw new InvalidArgumentException('The locale "' . $displayLocale . '" does not exist.'); } diff --git a/src/Symfony/Component/Intl/Locale.php b/src/Symfony/Component/Intl/Locale.php index 1ce5b27029673..19301740966bb 100644 --- a/src/Symfony/Component/Intl/Locale.php +++ b/src/Symfony/Component/Intl/Locale.php @@ -34,6 +34,11 @@ class Locale extends \Locale */ private static $locales; + /** + * @var integer[]|null + */ + private static $lookupTable; + /** * Returns all available locales. * @@ -50,6 +55,35 @@ public static function getLocales() return self::$locales; } + /** + * Returns whether the given ICU locale exists. + * + * This method does not canonicalize the given locale. Specifically, it will + * return false if the locale is not correctly cased or uses hyphens ("-") + * as separators between the subtags instead of underscores ("_"). For + * example, this method returns false for "en-Latn-GB", but true for + * "en_Latn_GB". + * + * If you want to support the above cases, you should manually canonicalize + * the locale prior to calling this method. + * + * @param string $locale A canonicalized ICU locale (e.g. "en_Latn_GB") + * + * @return Boolean Whether the locale exists + * + * @see canonicalize + * + * @api + */ + public static function exists($locale) + { + if (null === self::$lookupTable) { + self::$lookupTable = array_flip(static::getLocales()); + } + + return isset(self::$lookupTable[$locale]); + } + /** * Returns the name of a locale in the given display locale. * @@ -74,7 +108,7 @@ public static function getLocales() */ public static function getName($locale, $displayLocale = null) { - if (!in_array($locale, self::getLocales(), true)) { + if (!static::exists($locale)) { throw new InvalidArgumentException('The locale "' . $locale . '" does not exist.'); } @@ -108,7 +142,7 @@ public static function getName($locale, $displayLocale = null) */ public static function getNames($displayLocale = null) { - if (null !== $displayLocale && !in_array($displayLocale, Locale::getLocales(), true)) { + if (null !== $displayLocale && !Locale::exists($displayLocale)) { throw new InvalidArgumentException('The locale "' . $displayLocale . '" does not exist.'); } diff --git a/src/Symfony/Component/Intl/Tests/LanguageTest.php b/src/Symfony/Component/Intl/Tests/LanguageTest.php index c4924c6f25a16..02eb9ea7c5c0b 100644 --- a/src/Symfony/Component/Intl/Tests/LanguageTest.php +++ b/src/Symfony/Component/Intl/Tests/LanguageTest.php @@ -12,12 +12,18 @@ namespace Symfony\Component\Intl\Tests; use Symfony\Component\Intl\Language; +use Symfony\Component\Intl\Locale; /** * @author Bernhard Schussek */ class LanguageTest extends \PHPUnit_Framework_TestCase { + protected function setUp() + { + Locale::setDefault('en'); + } + public function existsProvider() { return array( diff --git a/src/Symfony/Component/Intl/Tests/LocaleTest.php b/src/Symfony/Component/Intl/Tests/LocaleTest.php index c552763d7a626..bd4858e253fc3 100644 --- a/src/Symfony/Component/Intl/Tests/LocaleTest.php +++ b/src/Symfony/Component/Intl/Tests/LocaleTest.php @@ -23,6 +23,37 @@ protected function setUp() Locale::setDefault('en'); } + public function existsProvider() + { + return array( + array(true, 'de'), + array(true, 'de_AT'), + // scripts are supported in some cases + array(true, 'zh_Hant_TW'), + // but not in others + array(false, 'de_Latn_AT'), + // different casing is not supported + array(false, 'De_AT'), + // hyphens are not supported + array(false, 'de-AT'), + // aliases with individual translations are supported + array(true, 'mo'), + // ISO 936-2 is not supported if an equivalent exists in ISO 936-1 + array(false, 'deu'), + array(false, 'deu_AT'), + // country aliases are not supported + array(false, 'de_AUT'), + ); + } + + /** + * @dataProvider existsProvider + */ + public function testExists($exists, $language) + { + $this->assertSame($exists, Locale::exists($language)); + } + public function testGetName() { $this->assertSame('English', Locale::getName('en', 'en')); From 50b516a2ad54d1d6985619284a2c154b2bed5e4e Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Mon, 14 Oct 2013 19:03:35 +0200 Subject: [PATCH 46/48] [Intl] Fixed TextBundleWriter to write tables with numeric keys --- .../Intl/ResourceBundle/Writer/TextBundleWriter.php | 6 +++++- .../Intl/Tests/ResourceBundle/Writer/Fixtures/en.txt | 5 +++++ .../Tests/ResourceBundle/Writer/TextBundleWriterTest.php | 2 ++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php b/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php index 310126c510c24..eb825af4d7426 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Writer/TextBundleWriter.php @@ -83,7 +83,11 @@ private function writeResource($file, $value, $indentation, $requireBraces = tru if (is_array($value)) { $intValues = count($value) === count(array_filter($value, 'is_int')); - $intKeys = count($value) === count(array_filter(array_keys($value), 'is_int')); + + $keys = array_keys($value); + $intKeys = count($value) === count(array_filter($keys, 'is_int')) && + // check that the keys are 0-indexed and ascending + array_sum($keys) === array_sum(range(0, count($keys)-1)); if ($intValues && $intKeys) { $this->writeIntVector($file, $value, $indentation); diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/en.txt b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/en.txt index 04b4584e89602..deb8a9464d37d 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/en.txt +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/Fixtures/en.txt @@ -19,6 +19,11 @@ en{ b:int{1} c:int{2} } + TableWithIntKeys{ + 0:int{0} + 1:int{1} + 3:int{3} + } FalseBoolean{"false"} TrueBoolean{"true"} Null{""} diff --git a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php index 0aa035794a353..05dfab7d63160 100644 --- a/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php +++ b/src/Symfony/Component/Intl/Tests/ResourceBundle/Writer/TextBundleWriterTest.php @@ -55,6 +55,7 @@ public function testWrite() 'Integer' => 5, 'IntVector' => array(0, 1, 2, 3), 'IntVectorWithStringKeys' => array('a' => 0, 'b' => 1, 'c' => 2), + 'TableWithIntKeys' => array(0 => 0, 1 => 1, 3 => 3), 'FalseBoolean' => false, 'TrueBoolean' => true, 'Null' => null, @@ -74,6 +75,7 @@ public function testWriteTraversable() 'Integer' => 5, 'IntVector' => array(0, 1, 2, 3), 'IntVectorWithStringKeys' => array('a' => 0, 'b' => 1, 'c' => 2), + 'TableWithIntKeys' => array(0 => 0, 1 => 1, 3 => 3), 'FalseBoolean' => false, 'TrueBoolean' => true, 'Null' => null, From 05e4761b3ef4e17b326fe517ffcf8503a4215d4f Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Tue, 15 Oct 2013 14:52:38 +0200 Subject: [PATCH 47/48] [Intl] Fixed RecursiveArrayAccess/ArrayAccessibleResourceBundle to pass through numeric strings without converting them to integers --- .../ResourceBundle/Util/ArrayAccessibleResourceBundle.php | 6 +++--- .../Intl/ResourceBundle/Util/RecursiveArrayAccess.php | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/Intl/ResourceBundle/Util/ArrayAccessibleResourceBundle.php b/src/Symfony/Component/Intl/ResourceBundle/Util/ArrayAccessibleResourceBundle.php index 9a4cccb461145..6bec8781e8e88 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Util/ArrayAccessibleResourceBundle.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Util/ArrayAccessibleResourceBundle.php @@ -30,16 +30,16 @@ public function __construct(\ResourceBundle $bundleImpl) $this->bundleImpl = $bundleImpl; } - public function get($offset, $fallback = null) + public function get($offset) { - $value = $this->bundleImpl->get($offset, $fallback); + $value = $this->bundleImpl->get($offset); return $value instanceof \ResourceBundle ? new static($value) : $value; } public function offsetExists($offset) { - return null !== $this->bundleImpl[$offset]; + return null !== $this->bundleImpl->get($offset); } public function offsetGet($offset) diff --git a/src/Symfony/Component/Intl/ResourceBundle/Util/RecursiveArrayAccess.php b/src/Symfony/Component/Intl/ResourceBundle/Util/RecursiveArrayAccess.php index 28ba138383ba1..ece5ec29a96c4 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Util/RecursiveArrayAccess.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Util/RecursiveArrayAccess.php @@ -26,7 +26,11 @@ public static function get($array, array $indices) throw new OutOfBoundsException('The index '.$index.' does not exist.'); } - $array = $array[$index]; + if ($array instanceof \ArrayAccess) { + $array = $array->offsetGet($index); + } else { + $array = $array[$index]; + } } return $array; From 1cc7c7a199c78f3aca1f25f0e0ecfd13445435eb Mon Sep 17 00:00:00 2001 From: Bernhard Schussek Date: Tue, 15 Oct 2013 15:07:24 +0200 Subject: [PATCH 48/48] [Intl] Added Currency::exists(), Currency::canonicalize() and Currency::forNumericCode() --- src/Symfony/Component/Intl/Currency.php | 122 +++++++++++++++--- src/Symfony/Component/Intl/Language.php | 16 +-- .../Rule/CurrencyBundleTransformationRule.php | 15 +++ .../Component/Intl/Tests/CurrencyTest.php | 13 ++ .../AbstractCurrencyDataProviderTest.php | 65 +++++++++- 5 files changed, 205 insertions(+), 26 deletions(-) diff --git a/src/Symfony/Component/Intl/Currency.php b/src/Symfony/Component/Intl/Currency.php index 0909b7829c521..9230389f147e1 100644 --- a/src/Symfony/Component/Intl/Currency.php +++ b/src/Symfony/Component/Intl/Currency.php @@ -13,6 +13,7 @@ use Symfony\Component\Icu\CurrencyDataProvider; use Symfony\Component\Intl\Exception\InvalidArgumentException; +use Symfony\Component\Intl\Exception\MissingResourceException; /** * Provides access to currency-related data. @@ -37,7 +38,7 @@ class Currency /** * Returns all available currencies. * - * @return string[] An array of ISO 4217 currency codes + * @return string[] An array of ISO 4217 three-letter currency codes * * @api */ @@ -50,17 +51,71 @@ public static function getCurrencies() return self::$currencies; } + /** + * Returns whether the given ISO 4217 currency code exists. + * + * This method does not canonicalize the given currency code. Specifically, + * it will return false if the currency is not correctly cased or is + * provided as numeric code instead of as three-letter code. For + * example, this method returns false for "cad" and 124 (the numeric ISO + * 4217 code of the Canadian Dollar), but true for "CAD". + * + * If you want to support the lowercase currencies, you should manually + * canonicalize the currency code prior to calling this method. If you + * want to support numeric codes, you should convert them into three-letter + * codes by calling {@link forNumericCode()}. + * + * @param string $currency A canonical ISO 4217 three-letter currency code + * (e.g. "EUR") + * + * @return Boolean Whether the currency code exists + * + * @see canonicalize + * @see forNumericCode + * + * @api + */ + public static function exists($currency) + { + if (null === self::$lookupTable) { + self::$lookupTable = array_flip(static::getCurrencies()); + } + + return isset(self::$lookupTable[$currency]); + } + + /** + * Canonicalizes the given ISO 4217 currency code. + * + * The currency code is converted to uppercase during canonicalization. This + * method does not check whether the given currency actually exists. In case + * of doubt, you should pass the canonicalized currency to {@link exists()}. + * + * @param string $currency An ISO 4217 three-letter currency code (e.g. "EUR") + * + * @return string The canonicalized currency code + * + * @see exists + * + * @api + */ + public static function canonicalize($currency) + { + return strtoupper($currency); + } + /** * Returns the symbol of a currency in the given locale. * * For example, the symbol of the US Dollar ("USD") in the locale "en_US" is * "$". If the resource data for the given locale contains no entry for the - * given currency, then the ISO 4217 currency code is returned. + * given currency, then the ISO 4217 three-letter currency code is returned. * * If null is passed as locale, the result of * {@link \Locale::getDefault()} is used instead. * - * @param string $currency An ISO 4217 currency code (e.g. "EUR") + * @param string $currency A canonical ISO 4217 three-letter currency + * code (e.g. "EUR") * @param string $displayLocale The ICU locale code to return the symbol in * * @return string The currency symbol for the specified locale @@ -71,7 +126,7 @@ public static function getCurrencies() */ public static function getSymbol($currency, $displayLocale = null) { - if (!in_array($currency, self::getCurrencies(), true)) { + if (!static::exists($currency)) { throw new InvalidArgumentException('The currency "' . $currency . '" does not exist.'); } @@ -91,12 +146,14 @@ public static function getSymbol($currency, $displayLocale = null) * * For example, the name of the Euro ("EUR") in the locale "ru_RU" is * "Евро". If the resource data for the given locale contains no entry for - * the given currency, then the ISO 4217 currency code is returned. + * the given currency, then the ISO 4217 three-letter currency code is + * returned. * * If null is passed as locale, the result of * {@link \Locale::getDefault()} is used instead. * - * @param string $currency An ISO 4217 currency code (e.g. "EUR") + * @param string $currency A canonical ISO 4217 three-letter currency + * code (e.g. "EUR") * @param string $displayLocale The ICU locale code to return the name in * * @return string The name of the currency @@ -107,7 +164,7 @@ public static function getSymbol($currency, $displayLocale = null) */ public static function getName($currency, $displayLocale = null) { - if (!in_array($currency, self::getCurrencies(), true)) { + if (!static::exists($currency)) { throw new InvalidArgumentException('The currency "' . $currency . '" does not exist.'); } @@ -126,14 +183,15 @@ public static function getName($currency, $displayLocale = null) * Returns the names of all known currencies in the specified locale. * * If the resource data for the given locale contains no entry for a - * currency, then the ISO 4217 currency code is used instead. + * currency, then the ISO 4217 three-letter currency code is used instead. * * If null is passed as locale, the result of * {@link \Locale::getDefault()} is used instead. * * @param string $displayLocale The ICU locale code to return the names in * - * @return string[] An array of currency names indexed by currency codes + * @return string[] An array of currency names indexed by ISO 4217 + * three-letter currency codes * * @throws InvalidArgumentException If the locale is invalid * @@ -158,7 +216,8 @@ public static function getNames($displayLocale = null) * For example, the default number of fraction digits for the Euro is 2, * while for the Japanese Yen it's 0. * - * @param string $currency An ISO 4217 currency code (e.g. "EUR") + * @param string $currency A canonical ISO 4217 three-letter currency code + * (e.g. "EUR") * * @return integer The number of digits after the comma * @@ -168,7 +227,7 @@ public static function getNames($displayLocale = null) */ public static function getFractionDigits($currency) { - if (!in_array($currency, self::getCurrencies(), true)) { + if (!static::exists($currency)) { throw new InvalidArgumentException('The currency "' . $currency . '" does not exist.'); } @@ -182,7 +241,8 @@ public static function getFractionDigits($currency) * For example, 1230 rounded to the nearest 50 is 1250. 1.234 rounded to the * nearest 0.65 is 1.3. * - * @param string $currency An ISO 4217 currency code (e.g. "EUR") + * @param string $currency A canonical ISO 4217 three-letter currency code + * (e.g. "EUR") * * @return integer The rounding increment * @@ -192,7 +252,7 @@ public static function getFractionDigits($currency) */ public static function getRoundingIncrement($currency) { - if (!in_array($currency, self::getCurrencies(), true)) { + if (!static::exists($currency)) { throw new InvalidArgumentException('The currency "' . $currency . '" does not exist.'); } @@ -205,7 +265,8 @@ public static function getRoundingIncrement($currency) * For example, the numeric code of the Canadian Dollar ("CAD") is 124. If * no numeric code is available for a currency, 0 is returned. * - * @param string $currency An ISO 4217 currency code (e.g. "EUR") + * @param string $currency A canonical ISO 4217 three-letter currency code + * (e.g. "EUR") * * @return integer The numeric code * @@ -215,13 +276,42 @@ public static function getRoundingIncrement($currency) */ public static function getNumericCode($currency) { - if (!in_array($currency, self::getCurrencies(), true)) { - throw new InvalidArgumentException('The currency "' . $currency . '" does not exist.'); + if (!static::exists($currency)) { + throw new InvalidArgumentException('The currency "'.$currency.'" does not exist.'); } return self::getDataProvider()->getNumericCode($currency); } + /** + * Returns the matching ISO 4217 three-letter codes for a numeric code. + * + * For example, the numeric code 124 belongs to the Canadian Dollar ("CAD"). + * Some numeric codes belong to multiple currencies. For example, the + * number 428 is assigned to both the Latvian Ruble ("LVR") and the Latvian + * Lats ("LVL"). For this reason, this method always returns an array. + * + * @param integer $numericCode An ISO 4217 numeric currency code (e.g. 124) + * + * @return string[] The matching ISO 4217 three-letter currency codes + * + * @throws InvalidArgumentException If the numeric code does not exist + * + * @api + */ + public static function forNumericCode($numericCode) + { + try { + return self::getDataProvider()->forNumericCode($numericCode); + } catch (MissingResourceException $e) { + throw new InvalidArgumentException( + 'The numeric currency code "'.$numericCode.'" does not exist.', + 0, + $e + ); + } + } + /** * @return CurrencyDataProvider */ diff --git a/src/Symfony/Component/Intl/Language.php b/src/Symfony/Component/Intl/Language.php index 6722b5450940e..f0caba4354d7c 100644 --- a/src/Symfony/Component/Intl/Language.php +++ b/src/Symfony/Component/Intl/Language.php @@ -54,7 +54,7 @@ class Language * A full table of ISO 639 language codes can be found here: * http://www-01.sil.org/iso639-3/codes.asp * - * @return string[] An array of canonicalized ISO 639 language codes + * @return string[] An array of canonical ISO 639 language codes * * @api */ @@ -82,7 +82,7 @@ public static function getLanguages() * If you want to support the above cases, you should manually canonicalize * the language code prior to calling this method. * - * @param string $language A canonicalized ISO 639 language code (e.g. "en") + * @param string $language A canonical ISO 639 language code (e.g. "en") * * @return Boolean Whether the language code exists * @@ -117,9 +117,9 @@ public static function exists($language) * actually exists. In case of doubt, you should pass the canonicalized * language to {@link exists()}. * - * @param string $language A canonicalized ISO 639 language code (e.g. "en") + * @param string $language A language code (e.g. "en") * - * @return string The canonicalized language name + * @return string The canonicalized ISO 639 language codde * * @see exists * @@ -168,7 +168,7 @@ public static function canonicalize($language) * If null is passed as locale, the result of * {@link \Locale::getDefault()} is used instead. * - * @param string $language A canonicalized ISO 639 language code + * @param string $language A canonical ISO 639 language code * (e.g. "en") * @param string $displayLocale The ICU locale code to return the name in * @@ -199,7 +199,7 @@ public static function getName($language, $displayLocale = null) * Returns the names of all known languages in the specified locale. * * If the resource data for the given locale contains no entry for a - * language, then the canonicalized ISO 639 language code is used instead. + * language, then the canonical ISO 639 language code is used instead. * * If null is passed as locale, the result of * {@link \Locale::getDefault()} is used instead. @@ -207,7 +207,7 @@ public static function getName($language, $displayLocale = null) * @param string $displayLocale The ICU locale code to return the names in * * @return string[] An array of language names indexed by their - * canonicalized ISO 639 language codes + * canonical ISO 639 language codes * * @throws InvalidArgumentException If the locale is invalid * @@ -229,7 +229,7 @@ public static function getNames($displayLocale = null) /** * Returns the ISO 639-2 three-letter code of a language. * - * @param string $language A canonicalized ISO 639 language code (e.g. "en") + * @param string $language A canonical ISO 639 language code (e.g. "en") * * @return string The ISO 639-2 three-letter code of the language * diff --git a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php index dcd6fe1be3589..17062502089bb 100644 --- a/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php +++ b/src/Symfony/Component/Intl/ResourceBundle/Transformer/Rule/CurrencyBundleTransformationRule.php @@ -132,12 +132,27 @@ function ($currency) use ($root) { // Filter unknown currencies (e.g. "AYM") $alpha3ToNumericMapping = array_intersect_key($alpha3ToNumericMapping, $defaultSymbolNamePairs); + // Generate numeric code to 3-letter code mapping + $numericToAlpha3Mapping = array(); + + 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] = array(); + } + + $numericToAlpha3Mapping[$numeric][] = $alpha3; + } + // Write the root resource bundle $writer->write($tempDir.'/txt', 'root', array( 'Version' => $root['Version'], 'Currencies' => $defaultSymbolNamePairs, 'CurrencyMeta' => $supplementalData['CurrencyMeta'], 'Alpha3ToNumeric' => $alpha3ToNumericMapping, + 'NumericToAlpha3' => $numericToAlpha3Mapping, )); // The temporary directory now contains all sources to be compiled diff --git a/src/Symfony/Component/Intl/Tests/CurrencyTest.php b/src/Symfony/Component/Intl/Tests/CurrencyTest.php index 87e5678db7782..c72e8578b0c25 100644 --- a/src/Symfony/Component/Intl/Tests/CurrencyTest.php +++ b/src/Symfony/Component/Intl/Tests/CurrencyTest.php @@ -114,4 +114,17 @@ public function testGetNumericCodeFailsOnInvalidCurrency() { Currency::getNumericCode('FOO'); } + + public function testForNumericCode() + { + $this->assertSame(array('EUR'), Currency::forNumericCode(978)); + } + + /** + * @expectedException \Symfony\Component\Intl\Exception\InvalidArgumentException + */ + public function testForNumericCodeFailsOnInvalidNumber() + { + Currency::forNumericCode(12345); + } } diff --git a/src/Symfony/Component/Intl/Tests/DataProvider/AbstractCurrencyDataProviderTest.php b/src/Symfony/Component/Intl/Tests/DataProvider/AbstractCurrencyDataProviderTest.php index 3ece5856df077..24f4970766bb4 100644 --- a/src/Symfony/Component/Intl/Tests/DataProvider/AbstractCurrencyDataProviderTest.php +++ b/src/Symfony/Component/Intl/Tests/DataProvider/AbstractCurrencyDataProviderTest.php @@ -144,9 +144,70 @@ function ($value) { return array($value); }, /** * @dataProvider provideCurrenciesWithoutNumericEquivalent + * @expectedException \Symfony\Component\Intl\Exception\MissingResourceException */ - public function testGetNumericCodeReturnsZeroIfNoNumericEquivalent($currency) + public function testGetNumericCodeFailsIfNoNumericEquivalent($currency) { - $this->assertSame(0, $this->dataProvider->getNumericCode($currency)); + $this->dataProvider->getNumericCode($currency); + } + + public function provideValidNumericCodes() + { + $numericToAlpha3 = $this->getNumericToAlpha3Mapping(); + + return array_map( + function ($numeric, $alpha3) { return array($numeric, $alpha3); }, + array_keys($numericToAlpha3), + $numericToAlpha3 + ); + } + + /** + * @dataProvider provideValidNumericCodes + */ + public function testForNumericCode($numeric, $expected) + { + $actual = $this->dataProvider->forNumericCode($numeric); + + // Make sure that a different array order doesn't break the test + sort($actual); + sort($expected); + + $this->assertEquals($expected, $actual); + } + + public function provideInvalidNumericCodes() + { + $validNumericCodes = array_keys($this->getNumericToAlpha3Mapping()); + $invalidNumericCodes = array_diff(range(0, 1000), $validNumericCodes); + + return array_map( + function ($value) { return array($value); }, + $invalidNumericCodes + ); + } + + /** + * @dataProvider provideInvalidNumericCodes + * @expectedException \Symfony\Component\Intl\Exception\MissingResourceException + */ + public function testForNumericCodeFailsIfInvalidNumericCode($currency) + { + $this->dataProvider->forNumericCode($currency); + } + + private function getNumericToAlpha3Mapping() + { + $numericToAlpha3 = array(); + + foreach (static::$alpha3ToNumeric as $alpha3 => $numeric) { + if (!isset($numericToAlpha3[$numeric])) { + $numericToAlpha3[$numeric] = array(); + } + + $numericToAlpha3[$numeric][] = $alpha3; + } + + return $numericToAlpha3; } }