Browse Source

Avoiding multiple Cache::remember() calls when loading translators.

The problem was there was no fallback translator for newly created instances,
so had to move the code around.
Jose Lorenzo Rodriguez 11 years ago
parent
commit
e78fa2a33d
3 changed files with 162 additions and 52 deletions
  1. 13 44
      src/I18n/I18n.php
  2. 93 8
      src/I18n/TranslatorRegistry.php
  3. 56 0
      tests/TestCase/I18n/I18nTest.php

+ 13 - 44
src/I18n/I18n.php

@@ -37,14 +37,6 @@ class I18n {
 	protected static $_collection;
 
 /**
- * The name of the default formatter to use for newly created
- * translators
- *
- * @var string
- */
-	protected static $_defaultFormatter = 'default';
-
-/**
  * Returns the translators collection instance. It can be used
  * for getting specific translators based of their name and locale
  * or to configure some aspect of future translations that are not yet constructed.
@@ -115,6 +107,17 @@ class I18n {
 		if ($loader !== null) {
 			$packages = static::translators()->getPackages();
 			$locale = $locale ?: static::defaultLocale();
+
+			if ($name !== 'default') {
+				$loader = function() use ($loader) {
+					$package = $loader();
+					if (!$package->getFallback()) {
+						$package->setFallback('default');
+					}
+					return $package;
+				};
+			}
+
 			$packages->set($name, $locale, $loader);
 			return;
 		}
@@ -126,11 +129,7 @@ class I18n {
 			static::translators()->setLocale($locale);
 		}
 
-		try {
-			$translator = $translators->get($name);
-		} catch (LoadException $e) {
-			$translator = static::_fallbackTranslator($name, $locale);
-		}
+		$translator = $translators->get($name);
 
 		if (isset($currentLocale)) {
 			$translators->setLocale($currentLocale);
@@ -223,11 +222,7 @@ class I18n {
  * @return string The name of the formatter.
  */
 	public static function defaultFormatter($name = null) {
-		if ($name === null) {
-			return static::$_defaultFormatter;
-		}
-
-		static::$_defaultFormatter = $name;
+		return static::translators()->defaultFormatter($name);
 	}
 
 /**
@@ -240,30 +235,4 @@ class I18n {
 		static::$_collection = null;
 	}
 
-/**
- * Returns a new translator instance for the given name and locale
- * based of conventions.
- *
- * @param string $name The translation package name.
- * @param string $locale The locale to create the translator for.
- * @return \Aura\Intl\Translator
- */
-	protected static function _fallbackTranslator($name, $locale) {
-		$chain = new ChainMessagesLoader([
-			new MessagesFileLoader($name, $locale, 'mo'),
-			new MessagesFileLoader($name, $locale, 'po')
-		]);
-
-		// \Aura\Intl\Package by default uses formatter configured with key "basic".
-		// and we want to make sure the cake domain always uses the default formatter
-		$formatter = $name === 'cake' ? 'default' : static::$_defaultFormatter;
-		$chain = function() use ($formatter, $chain) {
-			$package = $chain();
-			$package->setFormatter($formatter);
-			return $package;
-		};
-		static::translator($name, $locale, $chain);
-		return static::translators()->get($name);
-	}
-
 }

+ 93 - 8
src/I18n/TranslatorRegistry.php

@@ -31,7 +31,16 @@ class TranslatorRegistry extends TranslatorLocator {
  *
  * @var array
  */
-	protected $_loader;
+	protected $_loaders;
+
+
+/**
+ * The name of the default formatter to use for newly created
+ * translators from the fallback loader
+ *
+ * @var string
+ */
+	protected $_defaultFormatter = 'default';
 
 /**
  * Gets a translator from the registry by package for a locale.
@@ -44,6 +53,10 @@ class TranslatorRegistry extends TranslatorLocator {
  * for the given locale.
  */
 	public function get($name, $locale = null) {
+		if (!$name) {
+			return null;
+		}
+
 		if ($locale === null) {
 			$locale = $this->getLocale();
 		}
@@ -54,11 +67,13 @@ class TranslatorRegistry extends TranslatorLocator {
 				try {
 					return parent::get($name, $locale);
 				} catch (\Aura\Intl\Exception $e) {
-					if (!isset($this->_loaders[$name])) {
-						throw $e;
-					}
-					return $this->_getFromLoader($name, $locale);
 				}
+
+				if (!isset($this->_loaders[$name])) {
+					$this->registerLoader($name, $this->_partialLoader());
+				}
+
+				return $this->_getFromLoader($name, $locale);
 			}, '_cake_core_');
 		}
 
@@ -81,6 +96,59 @@ class TranslatorRegistry extends TranslatorLocator {
 	}
 
 /**
+ * Sets the name of the default messages formatter to use for future
+ * translator instances.
+ *
+ * If called with no arguments, it will return the currently configured value.
+ *
+ * @param string $name The name of the formatter to use.
+ * @return string The name of the formatter.
+ */
+	public function defaultFormatter($name = null) {
+		if ($name === null) {
+			return $this->_defaultFormatter;
+		}
+		return $this->_defaultFormatter = $name;
+	}
+
+/**
+ * Returns a new translator instance for the given name and locale
+ * based of conventions.
+ *
+ * @param string $name The translation package name.
+ * @param string $locale The locale to create the translator for.
+ * @return \Aura\Intl\Translator
+ */
+	protected function _fallbackLoader($name, $locale) {
+		$chain = new ChainMessagesLoader([
+			new MessagesFileLoader($name, $locale, 'mo'),
+			new MessagesFileLoader($name, $locale, 'po')
+		]);
+
+		// \Aura\Intl\Package by default uses formatter configured with key "basic".
+		// and we want to make sure the cake domain always uses the default formatter
+		$formatter = $name === 'cake' ? 'default' : $this->_defaultFormatter;
+		$chain = function() use ($formatter, $chain) {
+			$package = $chain();
+			$package->setFormatter($formatter);
+			return $package;
+		};
+
+		return $chain;
+	}
+
+/**
+ * Returns a function that can be used as a loader for the registerLoaderMethod
+ *
+ * @return callable
+ */
+	protected function _partialLoader() {
+		return function($name, $locale) {
+			return $this->_fallbackLoader($name, $locale);
+		};
+	}
+
+/**
  * Registers a new package by passing the register loaded function for the
  * package name.
  *
@@ -89,9 +157,26 @@ class TranslatorRegistry extends TranslatorLocator {
  * @return \Aura\Intl\TranslatorInterface A translator object.
  */
 	protected function _getFromLoader($name, $locale) {
-		$this->packages->set($name, $locale, function() use ($name, $locale) {
-			return $this->_loaders[$name]($name, $locale);
-		});
+		$loader = $this->_loaders[$name]($name, $locale);
+		$package = $loader;
+
+		if (!is_callable($loader)) {
+			$loader = function() use ($package) {
+				return $package;
+			};
+		}
+
+		if ($name !== 'default') {
+			$loader = function() use ($loader) {
+				$package = $loader();
+				if (!$package->getFallback()) {
+					$package->setFallback('default');
+				}
+				return $package;
+			};
+		}
+
+		$this->packages->set($name, $locale, $loader);
 		return parent::get($name, $locale);
 	}
 

+ 56 - 0
tests/TestCase/I18n/I18nTest.php

@@ -299,6 +299,7 @@ class I18nTest extends TestCase {
 
 		$this->assertSame($english, I18n::translator());
 		$this->assertSame($spanish, I18n::translator('default', 'es_ES'));
+		$this->assertSame($english, I18n::translator());
 	}
 
 /**
@@ -347,4 +348,59 @@ class I18nTest extends TestCase {
 		$this->assertEquals('%d is 1 (po translated)', $translator->translate('%d = 1'));
 	}
 
+/**
+ * Tests that missing translations will get fallbacked to the default translator
+ *
+ * @return void
+ */
+	public function testFallbackTranslator() {
+		I18n::translator('default', 'fr_FR', function() {
+			$package = new Package('default');
+			$package->setMessages([
+				'Dog' => 'Le bark'
+			]);
+			return $package;
+		});
+
+		I18n::translator('custom', 'fr_FR', function() {
+			$package = new Package('default');
+			$package->setMessages([
+				'Cow' => 'Le moo'
+			]);
+			return $package;
+		});
+
+		$translator = I18n::translator('custom', 'fr_FR');
+		$this->assertEquals('Le moo', $translator->translate('Cow'));
+		$this->assertEquals('Le bark', $translator->translate('Dog'));
+	}
+
+/**
+ * Tests that it is possible to register a generic translators factory for a domain
+ * instead of having to create them manually
+ *
+ * @return void
+ */
+	public function testFallbackTranslatorWithFactory() {
+		I18n::translator('default', 'fr_FR', function() {
+			$package = new Package('default');
+			$package->setMessages([
+				'Dog' => 'Le bark'
+			]);
+			return $package;
+		});
+		I18n::config('custom', function($name, $locale) {
+			$this->assertEquals('custom', $name);
+			$package = new Package('default');
+			$package->setMessages([
+				'Cow' => 'Le moo',
+			]);
+			return $package;
+		});
+
+		$translator = I18n::translator('custom', 'fr_FR');
+		$this->assertEquals('Le moo', $translator->translate('Cow'));
+		$this->assertEquals('Le bark', $translator->translate('Dog'));
+	}
+
 }