From ebf5cb29798fec3e12f299d7451b5ce6148a6a5f Mon Sep 17 00:00:00 2001 From: ARCANEDEV Date: Wed, 16 Sep 2015 10:29:47 +0100 Subject: [PATCH 01/11] Adding RouteTranslator --- src/Contracts/RouteTranslatorInterface.php | 81 ++++++++++ src/Utilities/RouteTranslator.php | 169 +++++++++++++++++++++ 2 files changed, 250 insertions(+) create mode 100644 src/Contracts/RouteTranslatorInterface.php create mode 100644 src/Utilities/RouteTranslator.php diff --git a/src/Contracts/RouteTranslatorInterface.php b/src/Contracts/RouteTranslatorInterface.php new file mode 100644 index 0000000..4589bd2 --- /dev/null +++ b/src/Contracts/RouteTranslatorInterface.php @@ -0,0 +1,81 @@ + + */ +interface RouteTranslatorInterface +{ + /* ------------------------------------------------------------------------------------------------ + | Getters & Setters + | ------------------------------------------------------------------------------------------------ + */ + /** + * Get current route. + * + * @return string + */ + public function getCurrentRoute(); + + /** + * Set the current route. + * + * @param string $currentRoute + * + * @return self + */ + public function setCurrentRoute($currentRoute); + + /** + * Get translated routes. + * + * @return array + */ + public function getTranslatedRoutes(); + + /* ------------------------------------------------------------------------------------------------ + | Main Functions + | ------------------------------------------------------------------------------------------------ + */ + /** + * Translate routes and save them to the translated routes array (used in the localize route filter). + * + * @param string $route + * + * @return string + */ + public function trans($route); + + /** + * Returns the translation key for a given path. + * + * @param string $uri + * @param string $locale + * + * @return false|string + */ + public function getRouteNameFromPath($uri, $locale); + + /** + * Returns the translated route for the path and the url given. + * + * @param string $path - Path to check if it is a translated route + * @param string $urlLocale - Language to check if the path exists + * + * @return string|false + */ + public function findTranslatedRouteByPath($path, $urlLocale); + + /* ------------------------------------------------------------------------------------------------ + | Check Functions + | ------------------------------------------------------------------------------------------------ + */ + /** + * Check if has current route. + * + * @return bool + */ + public function hasCurrentRoute(); +} diff --git a/src/Utilities/RouteTranslator.php b/src/Utilities/RouteTranslator.php new file mode 100644 index 0000000..fd64aff --- /dev/null +++ b/src/Utilities/RouteTranslator.php @@ -0,0 +1,169 @@ + + */ +class RouteTranslator implements RouteTranslatorInterface +{ + /* ------------------------------------------------------------------------------------------------ + | Properties + | ------------------------------------------------------------------------------------------------ + */ + /** + * The translator instance. + * + * @var \Illuminate\Translation\Translator + */ + private $translator; + + /** + * Current route. + * + * @var string + */ + protected $currentRoute = ''; + + /** + * Translated routes collection. + * + * @var array + */ + protected $translatedRoutes = []; + + /* ------------------------------------------------------------------------------------------------ + | Constructor + | ------------------------------------------------------------------------------------------------ + */ + /** + * Create RouteTranslator instance. + * + * @param Translator $translator + */ + public function __construct(Translator $translator) + { + $this->translator = $translator; + } + + /* ------------------------------------------------------------------------------------------------ + | Getters & Setters + | ------------------------------------------------------------------------------------------------ + */ + /** + * Get current route. + * + * @return string + */ + public function getCurrentRoute() + { + return $this->currentRoute; + } + + /** + * Set the current route. + * + * @param string $currentRoute + * + * @return self + */ + public function setCurrentRoute($currentRoute) + { + if (is_string($currentRoute)) { + $this->currentRoute = $currentRoute; + } + + return $this; + } + + /** + * Get translated routes. + * + * @return array + */ + public function getTranslatedRoutes() + { + return $this->translatedRoutes; + } + + /* ------------------------------------------------------------------------------------------------ + | Main Functions + | ------------------------------------------------------------------------------------------------ + */ + /** + * Translate routes and save them to the translated routes array (used in the localize route filter). + * + * @param string $route + * + * @return string + */ + public function trans($route) + { + if ( ! in_array($route, $this->translatedRoutes)) { + $this->translatedRoutes[] = $route; + } + + return $this->translator->trans($route); + } + + /** + * Returns the translation key for a given path. + * + * @param string $uri + * @param string $locale + * + * @return false|string + */ + public function getRouteNameFromPath($uri, $locale) + { + $attributes = Url::extractAttributes($uri); + $uri = str_replace([url(), "/$locale/"], ['', ''], $uri); + $uri = trim($uri, '/'); + + foreach ($this->translatedRoutes as $route) { + $url = Url::substituteAttributes($attributes, $this->translator->trans($route)); + + if ($url === $uri) return $route; + } + + return false; + } + + /** + * Returns the translated route for the path and the url given. + * + * @param string $path - Path to check if it is a translated route + * @param string $urlLocale - Language to check if the path exists + * + * @return string|false + */ + public function findTranslatedRouteByPath($path, $urlLocale) + { + // check if this url is a translated url + foreach ($this->translatedRoutes as $translatedRoute) { + if ($this->translator->trans($translatedRoute, [], '', $urlLocale) == rawurldecode($path)) { + return $translatedRoute; + } + } + + return false; + } + + /* ------------------------------------------------------------------------------------------------ + | Check Functions + | ------------------------------------------------------------------------------------------------ + */ + /** + * Check if has current route. + * + * @return bool + */ + public function hasCurrentRoute() + { + return ! empty($this->currentRoute); + } +} From af6d10425493980f1ff6568d7734c13eb2ea440c Mon Sep 17 00:00:00 2001 From: ARCANEDEV Date: Wed, 16 Sep 2015 10:30:13 +0100 Subject: [PATCH 02/11] Adding UtilitiesServiceProvider --- src/Providers/UtilitiesServiceProvider.php | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/Providers/UtilitiesServiceProvider.php diff --git a/src/Providers/UtilitiesServiceProvider.php b/src/Providers/UtilitiesServiceProvider.php new file mode 100644 index 0000000..00d6211 --- /dev/null +++ b/src/Providers/UtilitiesServiceProvider.php @@ -0,0 +1,29 @@ + + */ +class UtilitiesServiceProvider extends ServiceProvider +{ + /* ------------------------------------------------------------------------------------------------ + | Main Functions + | ------------------------------------------------------------------------------------------------ + */ + /** + * Register the service provider. + * + * @return void + */ + public function register() + { + $this->app->singleton('arcanedev.localization.translator', function ($app) { + return new RouteTranslator($app['translator']); + }); + } +} From 534998bfe56fdf5d222875ccc3d0a15fa0d5299d Mon Sep 17 00:00:00 2001 From: ARCANEDEV Date: Wed, 16 Sep 2015 10:31:07 +0100 Subject: [PATCH 03/11] Refactoring the negotiator --- src/Contracts/NegotiatorInterface.php | 25 ++++++++++ .../{LocaleNegotiator.php => Negotiator.php} | 50 ++++++++++--------- ...eNegotiatorTest.php => NegotiatorTest.php} | 4 +- 3 files changed, 54 insertions(+), 25 deletions(-) create mode 100644 src/Contracts/NegotiatorInterface.php rename src/Utilities/{LocaleNegotiator.php => Negotiator.php} (78%) rename tests/Utilities/{LocaleNegotiatorTest.php => NegotiatorTest.php} (92%) diff --git a/src/Contracts/NegotiatorInterface.php b/src/Contracts/NegotiatorInterface.php new file mode 100644 index 0000000..72f7f97 --- /dev/null +++ b/src/Contracts/NegotiatorInterface.php @@ -0,0 +1,25 @@ + + */ +interface NegotiatorInterface +{ + /* ------------------------------------------------------------------------------------------------ + | Main Functions + | ------------------------------------------------------------------------------------------------ + */ + /** + * Negotiate the request. + * + * @param Request $request + * + * @return string + */ + public function negotiate(Request $request); +} diff --git a/src/Utilities/LocaleNegotiator.php b/src/Utilities/Negotiator.php similarity index 78% rename from src/Utilities/LocaleNegotiator.php rename to src/Utilities/Negotiator.php index da87d1a..d88ccdf 100644 --- a/src/Utilities/LocaleNegotiator.php +++ b/src/Utilities/Negotiator.php @@ -1,16 +1,28 @@ + * + * Negotiates language with the user's browser through the Accept-Language HTTP header or the user's host address. + * Language codes are generally in the form "ll" for a language spoken in only one country, or "ll-CC" for a + * language spoken in a particular country. For example, U.S. English is "en-US", while British English is "en-UK". + * Portuguese as spoken in Portugal is "pt-PT", while Brazilian Portuguese is "pt-BR". + * + * This function is based on negotiateLanguage from Pear HTTP2 + * http://pear.php.net/package/HTTP2/ + * + * Quality factors in the Accept-Language: header are supported, e.g.: + * Accept-Language: en-UK;q=0.7, en-US;q=0.6, no, dk;q=0.8 */ -class LocaleNegotiator +class Negotiator implements NegotiatorInterface { /* ------------------------------------------------------------------------------------------------ | Properties @@ -28,7 +40,9 @@ class LocaleNegotiator */ private $supportedLocales; - /** @var Request */ + /** + * @var Request + */ private $request; /* ------------------------------------------------------------------------------------------------ @@ -36,17 +50,15 @@ class LocaleNegotiator | ------------------------------------------------------------------------------------------------ */ /** - * Make LocaleNegotiator instance. + * Make Negotiator instance. * * @param string $defaultLocale * @param LocaleCollection $supportedLanguages - * @param Request $request */ - public function __construct($defaultLocale, $supportedLanguages, Request $request) + public function __construct($defaultLocale, $supportedLanguages) { $this->defaultLocale = $defaultLocale; $this->supportedLocales = $supportedLanguages; - $this->request = $request; } /* ------------------------------------------------------------------------------------------------ @@ -54,24 +66,16 @@ public function __construct($defaultLocale, $supportedLanguages, Request $reques | ------------------------------------------------------------------------------------------------ */ /** - * Negotiates language with the user's browser through the Accept-Language - * HTTP header or the user's host address. Language codes are generally in - * the form "ll" for a language spoken in only one country, or "ll-CC" for a - * language spoken in a particular country. For example, U.S. English is - * "en-US", while British English is "en-UK". Portuguese as spoken in - * Portugal is "pt-PT", while Brazilian Portuguese is "pt-BR". - * - * This function is based on negotiateLanguage from Pear HTTP2 - * http://pear.php.net/package/HTTP2/ + * Negotiate the request. * - * Quality factors in the Accept-Language: header are supported, e.g.: - * Accept-Language: en-UK;q=0.7, en-US;q=0.6, no, dk;q=0.8 + * @param Request $request * - * @return string The negotiated language result or app.locale. + * @return string */ - public function negotiate() + public function negotiate(Request $request) { - $matches = $this->getMatchesFromAcceptedLanguages(); + $this->request = $request; + $matches = $this->getMatchesFromAcceptedLanguages(); foreach ($matches as $key => $q) { if ($this->supportedLocales->has($key)) { @@ -87,8 +91,8 @@ public function negotiate() return $locale->key(); } - if (class_exists('Locale') && ! empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { - $httpAcceptLanguage = Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']); + if (class_exists('Locale') && ! empty($this->request->server('HTTP_ACCEPT_LANGUAGE'))) { + $httpAcceptLanguage = Locale::acceptFromHttp($this->request->server('HTTP_ACCEPT_LANGUAGE')); if ($this->supportedLocales->has($httpAcceptLanguage)) { return $httpAcceptLanguage; diff --git a/tests/Utilities/LocaleNegotiatorTest.php b/tests/Utilities/NegotiatorTest.php similarity index 92% rename from tests/Utilities/LocaleNegotiatorTest.php rename to tests/Utilities/NegotiatorTest.php index 26eda31..2366ef5 100644 --- a/tests/Utilities/LocaleNegotiatorTest.php +++ b/tests/Utilities/NegotiatorTest.php @@ -3,12 +3,12 @@ use Arcanedev\Localization\Tests\TestCase; /** - * Class LocaleNegotiatorTest + * Class NegotiatorTest * * @package Arcanedev\Localization\Tests\Utilities * @author ARCANEDEV */ -class LocaleNegotiatorTest extends TestCase +class NegotiatorTest extends TestCase { /* ------------------------------------------------------------------------------------------------ | Main Functions From 797d75403a510cfe0e9604490ec7bd636ae2f2c2 Mon Sep 17 00:00:00 2001 From: ARCANEDEV Date: Wed, 16 Sep 2015 10:31:51 +0100 Subject: [PATCH 04/11] Refactoring the Localization Class --- src/Contracts/LocalizationInterface.php | 2 +- src/Localization.php | 90 +++++++------------------ src/LocalizationServiceProvider.php | 6 +- src/Middleware/LocalizationRoutes.php | 2 +- tests/LocalizationTest.php | 11 ++- 5 files changed, 40 insertions(+), 71 deletions(-) diff --git a/src/Contracts/LocalizationInterface.php b/src/Contracts/LocalizationInterface.php index 45b27bd..6ed74a2 100644 --- a/src/Contracts/LocalizationInterface.php +++ b/src/Contracts/LocalizationInterface.php @@ -187,7 +187,7 @@ public function getURLFromRouteNameTranslated($locale, $transKey, $attributes = * * @return string|false */ - public function getRouteNameFromAPath($path); + public function getRouteNameFromPath($path); /* ------------------------------------------------------------------------------------------------ | Check Functions diff --git a/src/Localization.php b/src/Localization.php index 62da02b..83b5fe2 100644 --- a/src/Localization.php +++ b/src/Localization.php @@ -4,7 +4,8 @@ use Arcanedev\Localization\Entities\LocaleCollection; use Arcanedev\Localization\Exceptions\UndefinedSupportedLocalesException; use Arcanedev\Localization\Exceptions\UnsupportedLocaleException; -use Arcanedev\Localization\Utilities\LocaleNegotiator; +use Arcanedev\Localization\Utilities\Negotiator; +use Arcanedev\Localization\Utilities\RouteTranslator; use Arcanedev\Localization\Utilities\Url; use Illuminate\Foundation\Application; @@ -63,18 +64,11 @@ class Localization implements LocalizationInterface protected $baseUrl; /** - * Translated routes collection. + * The RouteTranslator instance. * - * @var array + * @var RouteTranslator */ - protected $translatedRoutes = []; - - /** - * Current route name. - * - * @var string - */ - protected $routeName; + protected $routeTranslator; /* ------------------------------------------------------------------------------------------------ | Constructor @@ -88,11 +82,12 @@ class Localization implements LocalizationInterface * @throws UndefinedSupportedLocalesException * @throws UnsupportedLocaleException */ - public function __construct(Application $app) + public function __construct(Application $app, $routeTranslator) { $this->app = $app; $this->supportedLocales = new LocaleCollection; $this->locales = new LocaleCollection; + $this->routeTranslator = $routeTranslator; $this->defaultLocale = $this->config()->get('app.locale'); $this->getSupportedLocales(); @@ -292,13 +287,12 @@ public function getCurrentLocale() return $this->config()->get('app.locale'); } - $negotiator = new LocaleNegotiator( + $negotiator = new Negotiator( $this->defaultLocale, - $this->getSupportedLocales(), - $this->request() + $this->getSupportedLocales() ); - return $negotiator->negotiate(); + return $negotiator->negotiate($this->request()); } /** @@ -322,9 +316,7 @@ public function setBaseUrl($url) */ public function setRouteName($routeName) { - if (is_string($routeName)) { - $this->routeName = $routeName; - } + $this->routeTranslator->setCurrentRoute($routeName); } /* ------------------------------------------------------------------------------------------------ @@ -340,11 +332,7 @@ public function setRouteName($routeName) */ public function transRoute($routeName) { - if ( ! in_array($routeName, $this->translatedRoutes)) { - $this->translatedRoutes[] = $routeName; - } - - return $this->translator()->trans($routeName); + return $this->routeTranslator->trans($routeName); } /** @@ -403,8 +391,12 @@ public function getLocalizedURL($locale = null, $url = null, $attributes = []) } if (empty($url)) { - if ( ! empty($this->routeName)) { - return $this->getURLFromRouteNameTranslated($locale, $this->routeName, $attributes); + if ($this->routeTranslator->hasCurrentRoute()) { + return $this->getURLFromRouteNameTranslated( + $locale, + $this->routeTranslator->getCurrentRoute(), + $attributes + ); } $url = $this->request()->fullUrl(); @@ -446,7 +438,9 @@ public function getLocalizedURL($locale = null, $url = null, $attributes = []) $parsedUrl['path'] = ltrim($parsedUrl['path'], '/'); - if ($translatedRoute = $this->findTranslatedRouteByPath($parsedUrl['path'], $defaultLocale)) { + $translatedRoute = $this->routeTranslator->findTranslatedRouteByPath($parsedUrl['path'], $defaultLocale); + + if ($translatedRoute) { return $this->getURLFromRouteNameTranslated($locale, $translatedRoute, $attributes); } @@ -509,7 +503,7 @@ protected function findTranslatedRouteByUrl($url, $attributes, $locale) } // check if this url is a translated url - foreach ($this->translatedRoutes as $translatedRoute) { + foreach ($this->routeTranslator->getTranslatedRoutes() as $translatedRoute) { $routeName = $this->getURLFromRouteNameTranslated($locale, $translatedRoute, $attributes); if ($this->getNonLocalizedURL($routeName) == $this->getNonLocalizedURL($url)) { @@ -520,26 +514,6 @@ protected function findTranslatedRouteByUrl($url, $attributes, $locale) return false; } - /** - * Returns the translated route for the path and the url given. - * - * @param string $path - Path to check if it is a translated route - * @param string $urlLocale - Language to check if the path exists - * - * @return string|false - */ - protected function findTranslatedRouteByPath($path, $urlLocale) - { - // check if this url is a translated url - foreach ($this->translatedRoutes as $translatedRoute) { - if ($this->translator()->trans($translatedRoute, [], '', $urlLocale) == rawurldecode($path)) { - return $translatedRoute; - } - } - - return false; - } - /** * Returns an URL adapted to the route name and the locale given. * @@ -591,25 +565,9 @@ public function getURLFromRouteNameTranslated($locale, $transKey, $attributes = * * @return string|false */ - public function getRouteNameFromAPath($path) + public function getRouteNameFromPath($path) { - $attributes = Url::extractAttributes($path); - $path = str_replace(url(), '', $path); - - if ($path[0] !== '/') { - $path = '/' . $path; - } - - $path = str_replace('/' . $this->currentLocale . '/', '', $path); - $path = trim($path, '/'); - - foreach ($this->translatedRoutes as $route) { - if (Url::substituteAttributes($attributes, $this->translator()->trans($route)) === $path) { - return $route; - } - } - - return false; + return $this->routeTranslator->getRouteNameFromPath($path, $this->currentLocale); } /* ------------------------------------------------------------------------------------------------ diff --git a/src/LocalizationServiceProvider.php b/src/LocalizationServiceProvider.php index e711154..bd080e1 100644 --- a/src/LocalizationServiceProvider.php +++ b/src/LocalizationServiceProvider.php @@ -64,6 +64,8 @@ public function boot() public function register() { $this->registerConfig(); + + $this->app->register(Providers\UtilitiesServiceProvider::class); $this->registerLocalization(); $this->app->register(Providers\RouterServiceProvider::class); } @@ -88,7 +90,9 @@ public function provides() private function registerLocalization() { $this->app->singleton('arcanedev.localization', function($app) { - return new Localization($app); + $routeTranslator = $app['arcanedev.localization.translator']; + + return new Localization($app, $routeTranslator); }); $this->addFacade( diff --git a/src/Middleware/LocalizationRoutes.php b/src/Middleware/LocalizationRoutes.php index 518c9ff..0d44469 100644 --- a/src/Middleware/LocalizationRoutes.php +++ b/src/Middleware/LocalizationRoutes.php @@ -26,7 +26,7 @@ class LocalizationRoutes extends Middleware */ public function handle(Request $request, Closure $next) { - $routeName = localization()->getRouteNameFromAPath($request->getUri()); + $routeName = localization()->getRouteNameFromPath($request->getUri()); localization()->setRouteName($routeName); diff --git a/tests/LocalizationTest.php b/tests/LocalizationTest.php index 250f03a..6d2f444 100644 --- a/tests/LocalizationTest.php +++ b/tests/LocalizationTest.php @@ -30,7 +30,11 @@ public function it_can_be_instantiated() public function it_must_throw_unsupported_locale_exception_on_default_locale() { app('config')->set('app.locale', 'jp'); - new Localization($this->app); + + new Localization( + $this->app, + $this->app['arcanedev.localization.translator'] + ); } /** @@ -41,7 +45,10 @@ public function it_must_throw_unsupported_locale_exception_on_default_locale() public function it_must_throw_undefined_supported_locales_exception() { app('config')->set('localization.supported-locales', []); - new Localization($this->app); + new Localization( + $this->app, + $this->app['arcanedev.localization.translator'] + ); } /** @test */ From f8741569013069d80336e9088856ccc0d756a6b4 Mon Sep 17 00:00:00 2001 From: ARCANEDEV Date: Wed, 16 Sep 2015 10:37:29 +0100 Subject: [PATCH 05/11] Updating .gitattributes --- .gitattributes | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitattributes b/.gitattributes index feb4c02..c19cce8 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,4 +3,5 @@ /.gitignore export-ignore /.scrutinizer.yml export-ignore /.travis.yml export-ignore +/CONTRIBUTING.md export-ignore /phpunit.xml export-ignore From 4a4a3994aa2ba3eee17b60ba54c1d5d4b8fc608d Mon Sep 17 00:00:00 2001 From: ARCANEDEV Date: Wed, 16 Sep 2015 10:53:40 +0100 Subject: [PATCH 06/11] More refactoring for RouteTranslator --- src/Contracts/RouteTranslatorInterface.php | 4 +- .../InvalidTranslationException.php | 9 ++++ src/Utilities/RouteTranslator.php | 48 ++++++++++++++++--- 3 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 src/Exceptions/InvalidTranslationException.php diff --git a/src/Contracts/RouteTranslatorInterface.php b/src/Contracts/RouteTranslatorInterface.php index 4589bd2..0db8e5e 100644 --- a/src/Contracts/RouteTranslatorInterface.php +++ b/src/Contracts/RouteTranslatorInterface.php @@ -62,11 +62,11 @@ public function getRouteNameFromPath($uri, $locale); * Returns the translated route for the path and the url given. * * @param string $path - Path to check if it is a translated route - * @param string $urlLocale - Language to check if the path exists + * @param string $locale - Language to check if the path exists * * @return string|false */ - public function findTranslatedRouteByPath($path, $urlLocale); + public function findTranslatedRouteByPath($path, $locale); /* ------------------------------------------------------------------------------------------------ | Check Functions diff --git a/src/Exceptions/InvalidTranslationException.php b/src/Exceptions/InvalidTranslationException.php new file mode 100644 index 0000000..1987a43 --- /dev/null +++ b/src/Exceptions/InvalidTranslationException.php @@ -0,0 +1,9 @@ + + */ +class InvalidTranslationException extends LocalizationException {} diff --git a/src/Utilities/RouteTranslator.php b/src/Utilities/RouteTranslator.php index fd64aff..e464620 100644 --- a/src/Utilities/RouteTranslator.php +++ b/src/Utilities/RouteTranslator.php @@ -1,6 +1,7 @@ translatedRoutes[] = $route; } - return $this->translator->trans($route); + return $this->translate($route); } /** @@ -125,7 +126,7 @@ public function getRouteNameFromPath($uri, $locale) $uri = trim($uri, '/'); foreach ($this->translatedRoutes as $route) { - $url = Url::substituteAttributes($attributes, $this->translator->trans($route)); + $url = Url::substituteAttributes($attributes, $this->translate($route)); if ($url === $uri) return $route; } @@ -137,22 +138,55 @@ public function getRouteNameFromPath($uri, $locale) * Returns the translated route for the path and the url given. * * @param string $path - Path to check if it is a translated route - * @param string $urlLocale - Language to check if the path exists + * @param string $locale - Language to check if the path exists * * @return string|false */ - public function findTranslatedRouteByPath($path, $urlLocale) + public function findTranslatedRouteByPath($path, $locale) { // check if this url is a translated url - foreach ($this->translatedRoutes as $translatedRoute) { - if ($this->translator->trans($translatedRoute, [], '', $urlLocale) == rawurldecode($path)) { - return $translatedRoute; + foreach ($this->translatedRoutes as $route) { + if ($this->translate($route, $locale) == rawurldecode($path)) { + return $route; } } return false; } + /* ------------------------------------------------------------------------------------------------ + | Other Functions + | ------------------------------------------------------------------------------------------------ + */ + /** + * Get the translation for a given key. + * + * @param string $key + * @param string $locale + * + * @return string + * + * @throws InvalidTranslationException + */ + public function translate($key, $locale = null) + { + if (is_null($locale)) { + $locale = $this->translator->getLocale(); + } + + $translated = $this->translator->trans($key, [], '', $locale); + + // @codeCoverageIgnoreStart + if ( ! is_string($translated)) { + throw new InvalidTranslationException( + "The translation key [$key] for locale [$locale] has returned an array instead of string." + ); + } + // @codeCoverageIgnoreEnd + + return $translated; + } + /* ------------------------------------------------------------------------------------------------ | Check Functions | ------------------------------------------------------------------------------------------------ From 8c3520d7703f8f54c064591efc8b930eab0e0315 Mon Sep 17 00:00:00 2001 From: ARCANEDEV Date: Wed, 16 Sep 2015 11:11:46 +0100 Subject: [PATCH 07/11] Adding intl suggest --- composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/composer.json b/composer.json index c690e9f..3e279ca 100644 --- a/composer.json +++ b/composer.json @@ -31,6 +31,9 @@ "Arcanedev\\Localization\\Tests\\": "tests/" } }, + "suggest": { + "ext-intl": "Use Intl extension for 'Locale' class (an identifier used to get language)." + }, "extra": { "branch-alias": { "dev-master": "1.0.x-dev" From 8eafc75964195d53286aa662500c87bb148b286c Mon Sep 17 00:00:00 2001 From: ARCANEDEV Date: Wed, 16 Sep 2015 11:12:19 +0100 Subject: [PATCH 08/11] Refactoring and fixing stuff --- src/Utilities/Negotiator.php | 55 +++++++++++++++++-------------- src/Utilities/RouteTranslator.php | 2 -- 2 files changed, 31 insertions(+), 26 deletions(-) diff --git a/src/Utilities/Negotiator.php b/src/Utilities/Negotiator.php index d88ccdf..394863e 100644 --- a/src/Utilities/Negotiator.php +++ b/src/Utilities/Negotiator.php @@ -40,11 +40,6 @@ class Negotiator implements NegotiatorInterface */ private $supportedLocales; - /** - * @var Request - */ - private $request; - /* ------------------------------------------------------------------------------------------------ | Main Functions | ------------------------------------------------------------------------------------------------ @@ -74,13 +69,10 @@ public function __construct($defaultLocale, $supportedLanguages) */ public function negotiate(Request $request) { - $this->request = $request; - $matches = $this->getMatchesFromAcceptedLanguages(); + $matches = $this->getMatchesFromAcceptedLanguages($request); foreach ($matches as $key => $q) { - if ($this->supportedLocales->has($key)) { - return $key; - } + if ($this->isSupported($key)) return $key; } // If any (i.e. "*") is acceptable, return the first supported format @@ -91,39 +83,54 @@ public function negotiate(Request $request) return $locale->key(); } - if (class_exists('Locale') && ! empty($this->request->server('HTTP_ACCEPT_LANGUAGE'))) { - $httpAcceptLanguage = Locale::acceptFromHttp($this->request->server('HTTP_ACCEPT_LANGUAGE')); + if (class_exists('Locale') && ! empty($request->server('HTTP_ACCEPT_LANGUAGE'))) { + $httpAcceptLanguage = Locale::acceptFromHttp($request->server('HTTP_ACCEPT_LANGUAGE')); - if ($this->supportedLocales->has($httpAcceptLanguage)) { - return $httpAcceptLanguage; - } + if ($this->isSupported($httpAcceptLanguage)) return $httpAcceptLanguage; } - if ($this->request->server('REMOTE_HOST')) { - $remote_host = explode('.', $this->request->server('REMOTE_HOST')); - $lang = strtolower(end($remote_host)); + if ($request->server('REMOTE_HOST')) { + $remote_host = explode('.', $request->server('REMOTE_HOST')); + $locale = strtolower(end($remote_host)); - if ($this->supportedLocales->has($lang)) { - return $lang; - } + if ($this->isSupported($locale)) return $locale; } return $this->defaultLocale; } + /* ------------------------------------------------------------------------------------------------ + | Check Functions + | ------------------------------------------------------------------------------------------------ + */ + /** + * Check if the locale is supported. + * + * @param string $locale + * + * @return bool + */ + private function isSupported($locale) + { + return $this->supportedLocales->has($locale); + } + /* ------------------------------------------------------------------------------------------------ | Other Functions | ------------------------------------------------------------------------------------------------ */ /** * Return all the accepted languages from the browser - * @return array Matches from the header field Accept-Languages + * + * @param Request $request + * + * @return array - Matches from the header field Accept-Languages */ - private function getMatchesFromAcceptedLanguages() + private function getMatchesFromAcceptedLanguages(Request $request) { $matches = []; - if ($acceptLanguages = $this->request->header('Accept-Language')) { + if ($acceptLanguages = $request->header('Accept-Language')) { $acceptLanguages = explode(',', $acceptLanguages); $generic_matches = []; diff --git a/src/Utilities/RouteTranslator.php b/src/Utilities/RouteTranslator.php index e464620..ec8fea8 100644 --- a/src/Utilities/RouteTranslator.php +++ b/src/Utilities/RouteTranslator.php @@ -176,13 +176,11 @@ public function translate($key, $locale = null) $translated = $this->translator->trans($key, [], '', $locale); - // @codeCoverageIgnoreStart if ( ! is_string($translated)) { throw new InvalidTranslationException( "The translation key [$key] for locale [$locale] has returned an array instead of string." ); } - // @codeCoverageIgnoreEnd return $translated; } From e9cf1c324fee9c09347c7890ce89daac2047e98d Mon Sep 17 00:00:00 2001 From: ARCANEDEV Date: Wed, 16 Sep 2015 12:01:51 +0100 Subject: [PATCH 09/11] Refactoring --- src/Contracts/LocalizationInterface.php | 2 +- src/Localization.php | 32 ++++++++++--------------- src/Utilities/RouteTranslator.php | 22 ++++++++++++++--- tests/LocalizationTest.php | 31 ++++++++++++++++-------- 4 files changed, 53 insertions(+), 34 deletions(-) diff --git a/src/Contracts/LocalizationInterface.php b/src/Contracts/LocalizationInterface.php index 6ed74a2..1f31c2c 100644 --- a/src/Contracts/LocalizationInterface.php +++ b/src/Contracts/LocalizationInterface.php @@ -178,7 +178,7 @@ public function createUrlFromUri($uri); * * @return string|false */ - public function getURLFromRouteNameTranslated($locale, $transKey, $attributes = []); + public function getUrlFromRouteName($locale, $transKey, $attributes = []); /** * Returns the translation key for a given path. diff --git a/src/Localization.php b/src/Localization.php index 83b5fe2..b7ce518 100644 --- a/src/Localization.php +++ b/src/Localization.php @@ -118,16 +118,6 @@ private function request() return $this->app['request']; } - /** - * Get the translator instance. - * - * @return \Illuminate\Translation\Translator - */ - private function translator() - { - return $this->app['translator']; - } - /** * Returns default locale. * @@ -392,7 +382,7 @@ public function getLocalizedURL($locale = null, $url = null, $attributes = []) if (empty($url)) { if ($this->routeTranslator->hasCurrentRoute()) { - return $this->getURLFromRouteNameTranslated( + return $this->getUrlFromRouteName( $locale, $this->routeTranslator->getCurrentRoute(), $attributes @@ -406,7 +396,7 @@ public function getLocalizedURL($locale = null, $url = null, $attributes = []) $locale && $translatedRoute = $this->findTranslatedRouteByUrl($url, $attributes, $this->currentLocale) ) { - return $this->getURLFromRouteNameTranslated($locale, $translatedRoute, $attributes); + return $this->getUrlFromRouteName($locale, $translatedRoute, $attributes); } $baseUrl = $this->request()->getBaseUrl(); @@ -441,7 +431,7 @@ public function getLocalizedURL($locale = null, $url = null, $attributes = []) $translatedRoute = $this->routeTranslator->findTranslatedRouteByPath($parsedUrl['path'], $defaultLocale); if ($translatedRoute) { - return $this->getURLFromRouteNameTranslated($locale, $translatedRoute, $attributes); + return $this->getUrlFromRouteName($locale, $translatedRoute, $attributes); } if ( @@ -504,7 +494,7 @@ protected function findTranslatedRouteByUrl($url, $attributes, $locale) // check if this url is a translated url foreach ($this->routeTranslator->getTranslatedRoutes() as $translatedRoute) { - $routeName = $this->getURLFromRouteNameTranslated($locale, $translatedRoute, $attributes); + $routeName = $this->getUrlFromRouteName($locale, $translatedRoute, $attributes); if ($this->getNonLocalizedURL($routeName) == $this->getNonLocalizedURL($url)) { return $translatedRoute; @@ -521,12 +511,12 @@ protected function findTranslatedRouteByUrl($url, $attributes, $locale) * @param string $transKey * @param array $attributes * + * @return string|false + * * @throws UndefinedSupportedLocalesException * @throws UnsupportedLocaleException - * - * @return string|false */ - public function getURLFromRouteNameTranslated($locale, $transKey, $attributes = []) + public function getUrlFromRouteName($locale, $transKey, $attributes = []) { if ( ! $this->isLocaleSupported($locale)) { throw new UnsupportedLocaleException( @@ -540,15 +530,17 @@ public function getURLFromRouteNameTranslated($locale, $transKey, $attributes = $route = ''; - if ( ! ($locale === $this->defaultLocale && $this->hideDefaultLocaleInURL())) { + if ( + ! ($locale === $this->defaultLocale && $this->hideDefaultLocaleInURL()) + ) { $route = '/' . $locale; } if ( is_string($locale) && - $this->translator()->has($transKey, $locale) + $this->routeTranslator->hasTranslation($transKey, $locale) ) { - $translation = $this->translator()->trans($transKey, [ ], '', $locale); + $translation = $this->routeTranslator->trans($transKey, $locale); $route = Url::substituteAttributes($attributes, $route . '/' . $translation); } diff --git a/src/Utilities/RouteTranslator.php b/src/Utilities/RouteTranslator.php index ec8fea8..da555b1 100644 --- a/src/Utilities/RouteTranslator.php +++ b/src/Utilities/RouteTranslator.php @@ -98,17 +98,20 @@ public function getTranslatedRoutes() /** * Translate routes and save them to the translated routes array (used in the localize route filter). * - * @param string $route + * @param string $route + * @param string|null $locale * * @return string + * + * @throws InvalidTranslationException */ - public function trans($route) + public function trans($route, $locale = null) { if ( ! in_array($route, $this->translatedRoutes)) { $this->translatedRoutes[] = $route; } - return $this->translate($route); + return $this->translate($route, $locale); } /** @@ -198,4 +201,17 @@ public function hasCurrentRoute() { return ! empty($this->currentRoute); } + + /** + * Determine if a translation exists. + * + * @param string $key + * @param string $locale + * + * @return bool + */ + public function hasTranslation($key, $locale = null) + { + return $this->translator->has($key, $locale); + } } diff --git a/tests/LocalizationTest.php b/tests/LocalizationTest.php index 6d2f444..062ee0e 100644 --- a/tests/LocalizationTest.php +++ b/tests/LocalizationTest.php @@ -287,52 +287,63 @@ public function it_can_get_url_from_route_name_translated() { $this->assertEquals( $this->testUrlOne . 'es/acerca', - localization()->getURLFromRouteNameTranslated('es', 'localization::routes.about') + localization()->getUrlFromRouteName('es', 'localization::routes.about') ); $this->assertEquals( $this->testUrlOne . 'en/about', - localization()->getURLFromRouteNameTranslated('en', 'localization::routes.about') + localization()->getUrlFromRouteName('en', 'localization::routes.about') ); $this->assertEquals( $this->testUrlOne . 'en/view/1', - localization()->getURLFromRouteNameTranslated('en', 'localization::routes.view', [ 'id' => 1 ]) + localization()->getUrlFromRouteName('en', 'localization::routes.view', [ 'id' => 1 ]) ); app('config')->set('localization.hide-default-in-url', true); $this->assertEquals( $this->testUrlOne . 'about', - localization()->getURLFromRouteNameTranslated('en', 'localization::routes.about') + localization()->getUrlFromRouteName('en', 'localization::routes.about') ); $this->assertEquals( $this->testUrlOne . 'es/acerca', - localization()->getURLFromRouteNameTranslated('es', 'localization::routes.about') + localization()->getUrlFromRouteName('es', 'localization::routes.about') ); $this->assertEquals( $this->testUrlOne . 'es/ver/1', - localization()->getURLFromRouteNameTranslated('es', 'localization::routes.view', ['id' => 1]) + localization()->getUrlFromRouteName('es', 'localization::routes.view', ['id' => 1]) ); $this->assertEquals( $this->testUrlOne . 'view/1', - localization()->getURLFromRouteNameTranslated('en', 'localization::routes.view', ['id' => 1]) + localization()->getUrlFromRouteName('en', 'localization::routes.view', ['id' => 1]) ); $this->assertNotEquals( $this->testUrlOne . 'en/view/1', - localization()->getURLFromRouteNameTranslated('en', 'localization::routes.view', ['id' => 1]) + localization()->getUrlFromRouteName('en', 'localization::routes.view', ['id' => 1]) ); app('config')->set('localization.hide-default-in-url', false); $this->assertNotEquals( $this->testUrlOne . 'view/1', - localization()->getURLFromRouteNameTranslated('en', 'localization::routes.view', ['id' => 1]) + localization()->getUrlFromRouteName('en', 'localization::routes.view', ['id' => 1]) ); $this->assertEquals( $this->testUrlOne . 'en/view/1', - localization()->getURLFromRouteNameTranslated('en', 'localization::routes.view', ['id' => 1]) + localization()->getUrlFromRouteName('en', 'localization::routes.view', ['id' => 1]) ); } + /** + * @test + * + * @expectedException \Arcanedev\Localization\Exceptions\UnsupportedLocaleException + * @expectedExceptionMessage Locale 'jp' is not in the list of supported locales. + */ + public function it_must_throw_an_exception() + { + localization()->getUrlFromRouteName('jp', 'localization::routes.about'); + } + /** @test */ public function it_can_get_non_localized_url() { From 6dcff81508518b44458649ad6220446bc38ce551 Mon Sep 17 00:00:00 2001 From: ARCANEDEV Date: Wed, 16 Sep 2015 12:47:15 +0100 Subject: [PATCH 10/11] Fixing doc comments --- src/Localization.php | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Localization.php b/src/Localization.php index b7ce518..f971f28 100644 --- a/src/Localization.php +++ b/src/Localization.php @@ -131,9 +131,9 @@ public function getDefaultLocale() /** * Return an array of all supported Locales. * - * @throws UndefinedSupportedLocalesException - * * @return LocaleCollection + * + * @throws UndefinedSupportedLocalesException */ public function getSupportedLocales() { @@ -355,14 +355,16 @@ public function getNonLocalizedURL($url = null) /** * Returns an URL adapted to $locale. * + * @todo: Refactor this beast + * * @param string|bool $locale * @param string|false $url * @param array $attributes * + * @return string|false + * * @throws UndefinedSupportedLocalesException * @throws UnsupportedLocaleException - * - * @return string|false */ public function getLocalizedURL($locale = null, $url = null, $attributes = []) { @@ -463,6 +465,7 @@ public function getLocalizedURL($locale = null, $url = null, $attributes = []) public function createUrlFromUri($uri) { $uri = ltrim($uri, '/'); + if (empty($this->baseUrl)) { return app('url')->to($uri); } @@ -481,10 +484,10 @@ public function createUrlFromUri($uri) * @param array $attributes * @param string $locale * + * @return string|false + * * @throws UndefinedSupportedLocalesException * @throws UnsupportedLocaleException - * - * @return string|false */ protected function findTranslatedRouteByUrl($url, $attributes, $locale) { From 6bb26fbe757a4931083a10c1af544892bb9b9e2f Mon Sep 17 00:00:00 2001 From: ARCANEDEV Date: Wed, 16 Sep 2015 12:47:26 +0100 Subject: [PATCH 11/11] Refactoring --- src/Utilities/Negotiator.php | 111 ++++++++++++++++++++++------------- 1 file changed, 71 insertions(+), 40 deletions(-) diff --git a/src/Utilities/Negotiator.php b/src/Utilities/Negotiator.php index 394863e..1efab73 100644 --- a/src/Utilities/Negotiator.php +++ b/src/Utilities/Negotiator.php @@ -71,8 +71,9 @@ public function negotiate(Request $request) { $matches = $this->getMatchesFromAcceptedLanguages($request); - foreach ($matches as $key => $q) { - if ($this->isSupported($key)) return $key; + foreach (array_keys($matches) as $locale) { + if ($this->isSupported($locale)) + return $locale; } // If any (i.e. "*") is acceptable, return the first supported format @@ -86,14 +87,16 @@ public function negotiate(Request $request) if (class_exists('Locale') && ! empty($request->server('HTTP_ACCEPT_LANGUAGE'))) { $httpAcceptLanguage = Locale::acceptFromHttp($request->server('HTTP_ACCEPT_LANGUAGE')); - if ($this->isSupported($httpAcceptLanguage)) return $httpAcceptLanguage; + if ($this->isSupported($httpAcceptLanguage)) + return $httpAcceptLanguage; } if ($request->server('REMOTE_HOST')) { $remote_host = explode('.', $request->server('REMOTE_HOST')); $locale = strtolower(end($remote_host)); - if ($this->isSupported($locale)) return $locale; + if ($this->isSupported($locale)) + return $locale; } return $this->defaultLocale; @@ -132,53 +135,81 @@ private function getMatchesFromAcceptedLanguages(Request $request) if ($acceptLanguages = $request->header('Accept-Language')) { $acceptLanguages = explode(',', $acceptLanguages); - $generic_matches = []; - foreach ($acceptLanguages as $option) { - $option = array_map('trim', explode(';', $option)); - $l = $option[0]; + $genericMatches = $this->retrieveGenericMatches($acceptLanguages, $matches); - if (isset($option[1])) { - $q = (float) str_replace('q=', '', $option[1]); - } - else { - $q = null; - - // Assign default low weight for generic values - if ($l == '*/*') { - $q = 0.01; - } - elseif (substr($l, -1) == '*') { - $q = 0.02; - } - } + $matches = array_merge($genericMatches, $matches); + arsort($matches, SORT_NUMERIC); + } - // Unweighted values, get high weight by their position in the list - $matches[$l] = $q = isset($q) ? $q : 1000 - count($matches); + return $matches; + } - //If for some reason the Accept-Language header only sends language with country - //we should make the language without country an accepted option, with a value - //less than it's parent. - $l_ops = explode('-', $l); - array_pop($l_ops); + /** + * Get the generic matches. + * + * @param array $acceptLanguages + * @param array $matches + * + * @return array + */ + private function retrieveGenericMatches($acceptLanguages, &$matches) + { + $genericMatches = []; + + foreach ($acceptLanguages as $option) { + $option = array_map('trim', explode(';', $option)); + $locale = $option[0]; + $quality = $this->getQualityFactor($locale, $option); - while ( ! empty($l_ops)) { - //The new generic option needs to be slightly less important than it's base - $q -= 0.001; - $op = implode('-', $l_ops); + // Unweighted values, get high weight by their position in the list + $quality = isset($quality) ? $quality : 1000 - count($matches); + $matches[$locale] = $quality; - if (empty($generic_matches[ $op ]) || $generic_matches[$op] > $q) { - $generic_matches[ $op ] = $q; - } + // If for some reason the Accept-Language header only sends language with country we should make + // the language without country an accepted option, with a value less than it's parent. + $localeOptions = explode('-', $locale); + array_pop($localeOptions); - array_pop($l_ops); + while ( ! empty($localeOptions)) { + //The new generic option needs to be slightly less important than it's base + $quality -= 0.001; + $opt = implode('-', $localeOptions); + + if (empty($genericMatches[$opt]) || $genericMatches[$opt] > $quality) { + $genericMatches[$opt] = $quality; } + + array_pop($localeOptions); } + } - $matches = array_merge($generic_matches, $matches); - arsort($matches, SORT_NUMERIC); + return $genericMatches; + } + + /** + * Get the quality factor. + * + * @param string $locale + * @param array $option + * + * @return float|null + */ + private function getQualityFactor($locale, $option) + { + if (isset($option[1])) { + return (float) str_replace('q=', '', $option[1]); } - return $matches; + // Assign default low weight for generic values + if ($locale === '*/*') { + return 0.01; + } + + if (substr($locale, -1) === '*') { + return 0.02; + } + + return null; } }