diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml
index f534b358be..95284306dd 100644
--- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml
+++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/translation.xml
@@ -13,6 +13,7 @@
Symfony\Component\Translation\Loader\XliffFileLoader
Symfony\Component\Translation\Loader\QtTranslationsLoader
Symfony\Component\Translation\Loader\CsvFileLoader
+ Symfony\Component\Translation\Loader\ResourceBundleLoader
Symfony\Component\Translation\Dumper\PhpFileDumper
Symfony\Component\Translation\Dumper\XliffFileDumper
Symfony\Component\Translation\Dumper\YamlFileDumper
diff --git a/src/Symfony/Component/Translation/Loader/ResourceBundleLoader.php b/src/Symfony/Component/Translation/Loader/ResourceBundleLoader.php
new file mode 100644
index 0000000000..b68aeb52cb
--- /dev/null
+++ b/src/Symfony/Component/Translation/Loader/ResourceBundleLoader.php
@@ -0,0 +1,80 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+use Symfony\Component\Translation\MessageCatalogue;
+use Symfony\Component\Config\Resource\DirectoryResource;
+use Symfony\Component\Config\Resource\FileResource;
+
+/**
+ * ResourceBundleLoader loads translations from a resource bundle.
+ *
+ * @author stealth35
+ */
+class ResourceBundleLoader implements LoaderInterface
+{
+ /**
+ * {@inheritdoc}
+ */
+ public function load($resource, $locale, $domain = 'messages')
+ {
+ $rb = new \ResourceBundle($locale, $resource);
+
+ if (!$rb) {
+ throw new \RuntimeException("cannot load this resource : $rb");
+ } elseif (intl_is_failure($rb->getErrorCode())) {
+ throw new \RuntimeException($rb->getErrorMessage(), $rb->getErrorCode());
+ }
+
+ $messages = $this->flatten($rb);
+ $catalogue = new MessageCatalogue($locale);
+ $catalogue->add($messages, $domain);
+
+ if (is_dir($resource)) {
+ $catalogue->addResource(new DirectoryResource($resource));
+ } elseif (is_file($resource.'.dat')) {
+ $catalogue->addResource(new FileResource($resource.'.dat'));
+ }
+
+ return $catalogue;
+ }
+
+ /**
+ * Flattens an ResourceBundle
+ *
+ * The scheme used is:
+ * key { key2 { key3 { "value" } } }
+ * Becomes:
+ * 'key.key2.key3' => 'value'
+ *
+ * This function takes an array by reference and will modify it
+ *
+ * @param array \ResourceBundle $rb the ResourceBundle that will be flattened
+ * @param array $messages used internally for recursive calls
+ * @param string $path current path being parsed, used internally for recursive calls
+ *
+ * @return array the flattened ResourceBundle
+ */
+ private function flatten(\ResourceBundle $rb, array &$messages = array(), $path = null)
+ {
+ foreach ($rb as $key => $value) {
+ $nodePath = $path ? $path.'.'.$key : $key;
+ if ($value instanceof \ResourceBundle) {
+ $this->flatten($value, $messages, $nodePath);
+ } else {
+ $messages[$nodePath] = $value;
+ }
+ }
+
+ return $messages;
+ }
+}
diff --git a/tests/Symfony/Tests/Component/Translation/Loader/LocalizedTestCase.php b/tests/Symfony/Tests/Component/Translation/Loader/LocalizedTestCase.php
new file mode 100644
index 0000000000..3f08272d17
--- /dev/null
+++ b/tests/Symfony/Tests/Component/Translation/Loader/LocalizedTestCase.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Tests\Component\Translation\Loader;
+
+abstract class LocalizedTestCase extends \PHPUnit_Framework_TestCase
+{
+ protected function setUp()
+ {
+ if (!extension_loaded('intl')) {
+ $this->markTestSkipped('The "intl" extension is not available');
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/Symfony/Tests/Component/Translation/Loader/ResourceBundleLoaderTest.php b/tests/Symfony/Tests/Component/Translation/Loader/ResourceBundleLoaderTest.php
new file mode 100644
index 0000000000..0d62b8a9e4
--- /dev/null
+++ b/tests/Symfony/Tests/Component/Translation/Loader/ResourceBundleLoaderTest.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\Tests\Component\Translation\Loader;
+
+use Symfony\Component\Translation\Loader\ResourceBundleLoader;
+use Symfony\Component\Config\Resource\DirectoryResource;
+use Symfony\Component\Config\Resource\FileResource;
+
+class ResourceBundleFileLoaderTest extends LocalizedTestCase
+{
+ public function testLoad()
+ {
+ $loader = new ResourceBundleLoader();
+ $resource = __DIR__.'/../fixtures/resourcebundle/res';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertEquals(array('foo' => 'bar'), $catalogue->all('domain1'));
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals(array(new DirectoryResource($resource)), $catalogue->getResources());
+ }
+
+ public function testDatEnglishLoad()
+ {
+ $loader = new ResourceBundleLoader();
+ $resource = __DIR__.'/../fixtures/resourcebundle/dat/resources';
+ $catalogue = $loader->load($resource, 'en', 'domain1');
+
+ $this->assertEquals(array('symfony' => 'Symfony 2 is great'), $catalogue->all('domain1'));
+ $this->assertEquals('en', $catalogue->getLocale());
+ $this->assertEquals(array(new FileResource($resource.'.dat')), $catalogue->getResources());
+ }
+
+ public function testDatFrenchLoad()
+ {
+ $loader = new ResourceBundleLoader();
+ $resource = __DIR__.'/../fixtures/resourcebundle/dat/resources';
+ $catalogue = $loader->load($resource, 'fr', 'domain1');
+
+ $this->assertEquals(array('symfony' => 'Symfony 2 est génial'), $catalogue->all('domain1'));
+ $this->assertEquals('fr', $catalogue->getLocale());
+ $this->assertEquals(array(new FileResource($resource.'.dat')), $catalogue->getResources());
+ }
+
+ /**
+ * @expectedException Exception
+ */
+ public function testLoadInvalidResource()
+ {
+ $loader = new ResourceBundleLoader();
+ $catalogue = $loader->load(__DIR__.'/../fixtures/resourcebundle/res/en.txt', 'en', 'domain1');
+ }
+}
diff --git a/tests/Symfony/Tests/Component/Translation/fixtures/resourcebundle/dat/en.txt b/tests/Symfony/Tests/Component/Translation/fixtures/resourcebundle/dat/en.txt
new file mode 100644
index 0000000000..c04a4e8522
--- /dev/null
+++ b/tests/Symfony/Tests/Component/Translation/fixtures/resourcebundle/dat/en.txt
@@ -0,0 +1,3 @@
+en{
+ symfony{"Symfony 2 is great"}
+}
\ No newline at end of file
diff --git a/tests/Symfony/Tests/Component/Translation/fixtures/resourcebundle/dat/fr.txt b/tests/Symfony/Tests/Component/Translation/fixtures/resourcebundle/dat/fr.txt
new file mode 100644
index 0000000000..7e84f67ae0
--- /dev/null
+++ b/tests/Symfony/Tests/Component/Translation/fixtures/resourcebundle/dat/fr.txt
@@ -0,0 +1,3 @@
+fr{
+ symfony{"Symfony 2 est génial"}
+}
\ No newline at end of file
diff --git a/tests/Symfony/Tests/Component/Translation/fixtures/resourcebundle/dat/resources.dat b/tests/Symfony/Tests/Component/Translation/fixtures/resourcebundle/dat/resources.dat
new file mode 100644
index 0000000000..b8b5b2bc5e
Binary files /dev/null and b/tests/Symfony/Tests/Component/Translation/fixtures/resourcebundle/dat/resources.dat differ
diff --git a/tests/Symfony/Tests/Component/Translation/fixtures/resourcebundle/res/en.res b/tests/Symfony/Tests/Component/Translation/fixtures/resourcebundle/res/en.res
new file mode 100644
index 0000000000..ed962ca285
Binary files /dev/null and b/tests/Symfony/Tests/Component/Translation/fixtures/resourcebundle/res/en.res differ
diff --git a/tests/Symfony/Tests/Component/Translation/fixtures/resourcebundle/res/en.txt b/tests/Symfony/Tests/Component/Translation/fixtures/resourcebundle/res/en.txt
new file mode 100644
index 0000000000..a8a87e5f44
--- /dev/null
+++ b/tests/Symfony/Tests/Component/Translation/fixtures/resourcebundle/res/en.txt
@@ -0,0 +1,3 @@
+en {
+ foo { "bar" }
+}
\ No newline at end of file