diff --git a/composer.json b/composer.json index ff8018b..4de5f24 100644 --- a/composer.json +++ b/composer.json @@ -34,7 +34,10 @@ "classmap": [], "psr-0": { "Mcamara\\LaravelLocalization": "src/" - } + }, + "files": [ + "src/Mcamara/LaravelLocalization/helper.php" + ] }, "autoload-dev": { "psr-4": { diff --git a/src/Mcamara/LaravelLocalization/Facades/LaravelLocalization.php b/src/Mcamara/LaravelLocalization/Facades/LaravelLocalization.php index 153bdff..1002e10 100644 --- a/src/Mcamara/LaravelLocalization/Facades/LaravelLocalization.php +++ b/src/Mcamara/LaravelLocalization/Facades/LaravelLocalization.php @@ -8,7 +8,7 @@ * @method static bool isHiddenDefault(string $locale) * @method static void setSupportedLocales(array $locales) * @method static string localizeURL(string|null $url = null, string|bool|null $locale = null) - * @method static string|false getLocalizedURL(string|false|null $locale = null, string|bool|null $url = null, array $attributes = [], bool $forceDefaultLocation = false) + * @method static string getLocalizedURL(string|null $locale = null, string|null $url = null, array $attributes = [], bool $forceDefaultLocation = false) * @method static string|false getURLFromRouteNameTranslated(string|bool $locale, string $transKeyName, array $attributes = [], bool $forceDefaultLocation = false) * @method static string getNonLocalizedURL(string|false|null $url = null) * @method static string getDefaultLocale() @@ -28,7 +28,8 @@ * @method static array getSupportedLanguagesKeys() * @method static bool checkLocaleInSupportedLocales(string|bool $locale) * @method static void setRouteName(string $routeName) - * @method static string transRoute(string $routeName) + * @method static string transRoute(string $routeName, array $parameters = [], string|null $locale = null) + * @method static string route(string $key, array $parameters = [], string|null $locale = null) * @method static string|false getRouteNameFromAPath(string $path) * @method static \Illuminate\Contracts\Config\Repository getConfigRepository() * @method static bool useAcceptLanguageHeader() diff --git a/src/Mcamara/LaravelLocalization/LaravelLocalization.php b/src/Mcamara/LaravelLocalization/LaravelLocalization.php index c175b43..6a41080 100644 --- a/src/Mcamara/LaravelLocalization/LaravelLocalization.php +++ b/src/Mcamara/LaravelLocalization/LaravelLocalization.php @@ -4,70 +4,41 @@ use Illuminate\Contracts\Config\Repository as ConfigRepository; use Illuminate\Contracts\Foundation\Application; -use Illuminate\Contracts\Routing\UrlGenerator; -use Illuminate\Contracts\Routing\UrlRoutable; -use Illuminate\Contracts\Translation\Translator; -use Illuminate\Http\Request; -use Illuminate\Routing\Router; -use Illuminate\Support\Str; -use Illuminate\Support\Env; +use Illuminate\Support\Facades\Route; use Mcamara\LaravelLocalization\Exceptions\SupportedLocalesNotDefined; use Mcamara\LaravelLocalization\Exceptions\UnsupportedLocaleException; +use Mcamara\LaravelLocalization\Services\LocalizedUrlGenerator; class LaravelLocalization { - protected string $baseUrl; protected string $defaultLocale; protected array $supportedLocales; protected array $localesMapping; - protected string | false $currentLocale = false; + protected string $currentLocale; /** - * An array that contains all routes that should be translated. - */ - protected array $translatedRoutes = []; - - /** - * Name of the translation key of the current route, it is used for url translations. - * - */ - protected string $routeName; - - /** - * An array that contains all translated routes by url - */ - protected array $cachedTranslatedRoutesByUrl = []; - - /** - * Creates new instance. - * * @throws UnsupportedLocaleException */ public function __construct( protected readonly Application $app, protected readonly ConfigRepository $configRepository, - protected readonly Translator $translator, - protected readonly Router $router, - protected readonly Request $request, - protected readonly UrlGenerator $url + protected readonly LocalizedUrlGenerator $localizationUrlGenerator, ) { - // set default locale - $this->defaultLocale = $this->configRepository->get('app.locale'); + $locale = $this->configRepository->get('app.locale'); $supportedLocales = $this->getSupportedLocales(); - if (empty($supportedLocales[$this->defaultLocale])) { + if (empty($supportedLocales[$locale])) { throw new UnsupportedLocaleException('Laravel default locale is not in the supportedLocales array.'); } - } + $this->defaultLocale = $locale; + $this->currentLocale = $locale; + } - /** - * Check if $locale is default locale and supposed to be hidden in url - */ - public function isHiddenDefault(string $locale): bool - { - return ($this->getDefaultLocale() === $locale && $this->hideDefaultLocaleInURL()); - } + public function isHiddenDefault(string $locale): bool + { + return ($this->getDefaultLocale() === $locale && $this->hideDefaultLocaleInURL()); + } /** * Set and return supported locales. @@ -77,186 +48,75 @@ public function setSupportedLocales(array $locales): void $this->supportedLocales = $locales; } - /** - * Returns an URL adapted to $locale or current locale. - * - * @param string|null $url URL to adapt. If not passed, the current url would be taken. - * @param string|bool|null $locale Locale to adapt, false to remove locale - * - * @throws UnsupportedLocaleException - */ - public function localizeURL(string | null $url = null, string | bool | null $locale = null): string + public function route(string $key, array $parameters = [], string|null $locale = null): string { - return $this->getLocalizedURL($locale, $url); - } - - /** - * Returns an URL adapted to $locale. - * - * @param string|false|null $locale Locale to adapt, false to remove locale - * @param string|false|null $url URL to adapt in the current language. If not passed, the current url would be taken. - * @param array $attributes Attributes to add to the route, if empty, the system would try to extract them from the url. - * @param bool $forceDefaultLocation Force to show default location even hideDefaultLocaleInURL set as TRUE - * - * @throws SupportedLocalesNotDefined - * @throws UnsupportedLocaleException - * - * @return string|false URL translated, False if url does not exist - */ - public function getLocalizedURL( - string | false | null $locale = null, - string | false | null $url = null, - array $attributes = [], - bool $forceDefaultLocation = false - ): string | false { - if ($locale === null) { - $locale = $this->getCurrentLocale(); - } - - if (!$this->checkLocaleInSupportedLocales($locale)) { - throw new UnsupportedLocaleException('Locale \''.$locale.'\' is not in the list of supported locales.'); - } + $computedLocale = $locale ?? $this->getCurrentLocale(); - if (empty($attributes)) { - $attributes = $this->extractAttributes($url, $locale); + if($this->isHiddenDefault($computedLocale)){ + return route('without_locale.' . $key, $parameters); } - if (empty($url)) { - $url = $this->request->fullUrl(); - $urlQuery = parse_url($url, PHP_URL_QUERY); - $urlQuery = $urlQuery ? '?'.$urlQuery : ''; - - if (!empty($this->routeName)) { - return $this->getURLFromRouteNameTranslated($locale, $this->routeName, $attributes, $forceDefaultLocation) . $urlQuery; - } - } else { - $urlQuery = parse_url($url, PHP_URL_QUERY); - $urlQuery = $urlQuery ? '?'.$urlQuery : ''; - - $url = $this->url->to($url); - } - - $url = preg_replace('/'. preg_quote($urlQuery, '/') . '$/', '', $url); - - if ($locale && $translatedRoute = $this->findTranslatedRouteByUrl($url, $attributes, $this->currentLocale)) { - return $this->getURLFromRouteNameTranslated($locale, $translatedRoute, $attributes, $forceDefaultLocation).$urlQuery; - } - - $base_path = $this->request->getBaseUrl(); - $parsed_url = parse_url($url); - $url_locale = $this->getDefaultLocale(); - - if (!$parsed_url || empty($parsed_url['path'])) { - $path = $parsed_url['path'] = ''; - } else { - $parsed_url['path'] = str_replace($base_path, '', '/'.ltrim($parsed_url['path'], '/')); - $path = $parsed_url['path']; - foreach ($this->getSupportedLocales() as $localeCode => $lang) { - $localeCode = $this->getLocaleFromMapping($localeCode); - - $parsed_url['path'] = preg_replace('%^/?'.$localeCode.'/%', '$1', $parsed_url['path']); - if ($parsed_url['path'] !== $path) { - $url_locale = $localeCode; - break; - } - - $parsed_url['path'] = preg_replace('%^/?'.$localeCode.'$%', '$1', $parsed_url['path']); - if ($parsed_url['path'] !== $path) { - $url_locale = $localeCode; - break; - } - } - } - - $parsed_url['path'] = ltrim($parsed_url['path'], '/'); - - if ($translatedRoute = $this->findTranslatedRouteByPath($parsed_url['path'], $url_locale)) { - return $this->getURLFromRouteNameTranslated($locale, $translatedRoute, $attributes, $forceDefaultLocation).$urlQuery; - } + return route($key, $parameters); + } - $locale = $this->getLocaleFromMapping($locale); + public function transRoute(string $key, array $parameters = [], string|null $locale = null): string + { + $computedLocale = $locale ?? $this->getCurrentLocale(); - if (!empty($locale)) { - if ($forceDefaultLocation || $locale != $this->getDefaultLocale() || !$this->hideDefaultLocaleInURL()) { - $parsed_url['path'] = $locale.'/'.ltrim($parsed_url['path'], '/'); - } - } - $parsed_url['path'] = ltrim(ltrim($base_path, '/').'/'.$parsed_url['path'], '/'); + $routeName = "trans_route_for_locale_{$computedLocale}_{$key}"; - //Make sure that the pass path is returned with a leading slash only if it come in with one. - if (Str::startsWith($path, '/') === true) { - $parsed_url['path'] = '/'.$parsed_url['path']; + if (!Route::has($routeName)) { + return $key; } - $parsed_url['path'] = rtrim($parsed_url['path'], '/'); - $url = $this->unparseUrl($parsed_url); - - if ($this->checkUrl($url)) { - return $url.$urlQuery; + if(!isset($parameters['locale'])) { + $parameters['locale'] = $computedLocale; } - return $this->createUrlFromUri($url).$urlQuery; + return route($routeName, $parameters); } /** - * Returns an URL adapted to the route name and the locale given. + * Returns an URL adapted to $locale. * * - * @param string|bool $locale Locale to adapt - * @param string $transKeyName Translation key name of the url to adapt - * @param array $attributes Attributes for the route (only needed if transKeyName needs them) - * @param bool $forceDefaultLocation Force to show default location even hideDefaultLocaleInURL set as TRUE + * @param string|null $locale Locale to adapt + * @param string|null $url URL to adapt in the current language. If not passed, the current url would be taken. + * @param array $attributes Attributes to add to the route, if empty, the system would try to extract them from the url. + * @param bool $forceDefaultLocation Force to show default location even hideDefaultLocaleInURL set as TRUE * * @throws SupportedLocalesNotDefined * @throws UnsupportedLocaleException * - * @return string|false URL translated + * @return string URL translated, returns same url if no route is found */ - public function getURLFromRouteNameTranslated( - string | bool $locale, - string $transKeyName, - array $attributes = [], - bool $forceDefaultLocation = false - ): string | false { - if (!$this->checkLocaleInSupportedLocales($locale)) { - throw new UnsupportedLocaleException('Locale \''.$locale.'\' is not in the list of supported locales.'); - } - - if (!\is_string($locale)) { - $locale = $this->getDefaultLocale(); - } - - $route = ''; + public function getLocalizedURL(string|null $locale = null, string|null $url = null, array $attributes = [], bool $forceDefaultLocation = false): string + { + $locale = $locale ?: $this->getCurrentLocale(); + $locale = $this->getLocaleFromMapping($locale); - if ($forceDefaultLocation || !($locale === $this->defaultLocale && $this->hideDefaultLocaleInURL())) { - $route = '/'.$locale; + if (!$this->checkLocaleInSupportedLocales($locale)) { + throw new UnsupportedLocaleException("Locale '{$locale}' is not supported."); } - if ($this->translator->has($transKeyName, $locale)) { - $translation = $this->translator->get($transKeyName, [], $locale); - $route .= '/'.$translation; - $route = $this->substituteAttributesInRoute($attributes, $route, $locale); - } + if($url === null){ + // fullUrl() is including protocol, domain and query , e.g. `https://example.com/posts?page=2&sort=asc` - if (empty($route)) { - // This locale does not have any key for this route name - return false; + // Use the request() helper instead of $this->request, + // because the injected request may be stale if this class + // was constructed before the current request was bound. + $url = request()->fullUrl(); } - return rtrim($this->createUrlFromUri($route), '/'); - } - - /** - * It returns an URL without locale (if it has it) - * Convenience function wrapping getLocalizedURL(false). - * - * @param string|false|null $url URL to clean, if false, current url would be taken - * - * @return string URL with no locale in path - */ - public function getNonLocalizedURL(string | false | null $url = null): string - { - return $this->getLocalizedURL(false, $url); + return $this->localizationUrlGenerator->getLocalizedURL( + locale: $locale, + url: $url, + attributes: $attributes, + supportedLocales: $this->getSupportedLocales(), + forceDefaultLocation: $forceDefaultLocation, + defaultLocale: $this->defaultLocale, + hiddenDefault: $this->hideDefaultLocaleInURL() + ); } /** @@ -395,22 +255,11 @@ public function setCurrentLocale(string $locale): void { } /** - * Returns current language. + * Returns current language of url request */ public function getCurrentLocale(): string { - if ($this->currentLocale) { - return $this->currentLocale; - } - - if ($this->useAcceptLanguageHeader() && !$this->app->runningInConsole()) { - $negotiator = new LanguageNegotiator($this->defaultLocale, $this->getSupportedLocales(), $this->request); - - return $negotiator->negotiateLanguage(); - } - - // or get application default language - return $this->configRepository->get('app.locale'); + return $this->currentLocale; } /** @@ -455,142 +304,6 @@ public function checkLocaleInSupportedLocales($locale): bool return true; } - /** - * Change route attributes for the ones in the $attributes array. - */ - protected function substituteAttributesInRoute(array $attributes, string $route, string $locale): string - { - foreach ($attributes as $key => $value) { - if ($value instanceOf Interfaces\LocalizedUrlRoutable) { - $value = $value->getLocalizedRouteKey($locale); - } - elseif ($value instanceOf UrlRoutable) { - $value = $value->getRouteKey(); - } - $route = str_replace(array('{'.$key.'}', '{'.$key.'?}'), $value, $route); - } - - // delete empty optional arguments that are not in the $attributes array - $route = preg_replace('/\/{[^)]+\?}/', '', $route); - - return $route; - } - - /** - * Returns translated routes. - */ - protected function getTranslatedRoutes(): array - { - return $this->translatedRoutes; - } - - /** - * Set current route name. - */ - public function setRouteName(string $routeName): void - { - $this->routeName = $routeName; - } - - /** - * Translate routes and save them to the translated routes array (used in the localize route filter). - */ - public function transRoute(string $routeName): string - { - if (!\in_array($routeName, $this->translatedRoutes)) { - $this->translatedRoutes[] = $routeName; - } - - return $this->translator->get($routeName); - } - - /** - * Returns the translation key for a given path. - * - * @param string $path Path to get the key translated - * - * @return string|false Key for translation, false if not exist - */ - public function getRouteNameFromAPath(string $path): string | false - { - $attributes = $this->extractAttributes($path); - - $path = parse_url($path)['path']; - $path = trim(str_replace('/'.$this->currentLocale.'/', '', $path), "/"); - - foreach ($this->translatedRoutes as $route) { - if (trim($this->substituteAttributesInRoute($attributes, $this->translator->get($route), $this->currentLocale), '/') === $path) { - return $route; - } - } - - return false; - } - - /** - * Returns the translated route for the path and the url given. - */ - protected function findTranslatedRouteByPath(string $path, string $url_locale): string | false - { - // check if this url is a translated url - foreach ($this->translatedRoutes as $translatedRoute) { - if ($this->translator->get($translatedRoute, [], $url_locale) == rawurldecode($path)) { - return $translatedRoute; - } - } - - return false; - } - - /** - * Returns the translated route for an url and the attributes given and a locale. - * - * - * @param string|false|null $url Url to check if it is a translated route - * @param array $attributes Attributes to check if the url exists in the translated routes array - * @param string|false $locale Language to check if the url exists - * - * @throws SupportedLocalesNotDefined - * @throws UnsupportedLocaleException - * - * @return string|false Key for translation, false if not exist - */ - protected function findTranslatedRouteByUrl( - string | false | null $url, - array $attributes, - string | false $locale - ): string | false { - if (empty($url)) { - return false; - } - - if (isset($this->cachedTranslatedRoutesByUrl[$locale][$url])) { - return $this->cachedTranslatedRoutesByUrl[$locale][$url]; - } - - // check if this url is a translated url - foreach ($this->translatedRoutes as $translatedRoute) { - $routeName = $this->getURLFromRouteNameTranslated($locale, $translatedRoute, $attributes); - - // We can ignore extra url parts and compare only their url_path (ignore arguments that are not attributes) - if (parse_url($this->getNonLocalizedURL($routeName), PHP_URL_PATH) == parse_url($this->getNonLocalizedURL(urldecode($url)), PHP_URL_PATH)) { - $this->cachedTranslatedRoutesByUrl[$locale][$url] = $translatedRoute; - - return $translatedRoute; - } - } - - return false; - } - - /** - * Returns true if the string given is a valid url. - */ - protected function checkUrl(string $url): bool - { - return (bool) filter_var($url, FILTER_VALIDATE_URL); - } - /** * Returns the config repository for this instance. */ @@ -619,200 +332,4 @@ public function hideDefaultLocaleInURL(): bool { return $this->configRepository->get('laravellocalization.hideDefaultLocaleInURL'); } - - /** - * Create an url from the uri. - */ - public function createUrlFromUri(string $uri): string - { - $uri = ltrim($uri, '/'); - - if (empty($this->baseUrl)) { - return app('url')->to($uri); - } - - return $this->baseUrl.$uri; - } - - /** - * Sets the base url for the site. - * - * @param string $url Base url for the site - */ - public function setBaseUrl(string $url): void - { - if (substr($url, -1) != '/') { - $url .= '/'; - } - - $this->baseUrl = $url; - } - - /** - * Returns serialized translated routes for caching purposes. - */ - public function getSerializedTranslatedRoutes(): string - { - return base64_encode(serialize($this->translatedRoutes)); - } - - /** - * Sets the translated routes list. - * Only useful from a cached routes context. - */ - public function setSerializedTranslatedRoutes(string $serializedRoutes): void - { - if ( ! $serializedRoutes) { - return; - } - - $this->translatedRoutes = unserialize(base64_decode($serializedRoutes)); - } - - /** - * Extract attributes for current url. - * - * @param string|bool|null $url to extract attributes, if not present, the system will look for attributes in the current call - */ - protected function extractAttributes( - string | bool | null $url = false, - string $locale = '' - ): array { - if (!empty($url)) { - $attributes = []; - $parse = parse_url($url); - if (isset($parse['path'])) { - $parse['path'] = trim(str_replace('/'.$this->currentLocale.'/', '', $parse['path']), "/"); - $url = explode('/', trim($parse['path'], '/')); - } else { - $url = []; - } - - foreach ($this->router->getRoutes() as $route) { - $attributes = []; - $path = method_exists($route, 'uri') ? $route->uri() : $route->getUri(); - - if (!preg_match("/{[\w]+\??}/", $path)) { - continue; - } - - $path = explode('/', $path); - $i = 0; - - // The system's route can't be smaller - // only the $url can be missing segments (optional parameters) - // We can assume it's the wrong route - if (count($path) < count($url)) { - continue; - } - - $match = true; - foreach ($path as $j => $segment) { - if (isset($url[$i])) { - if ($segment === $url[$i]) { - $i++; - continue; - } elseif (preg_match("/{[\w]+}/", $segment)) { - // must-have parameters - $attribute_name = preg_replace(['/}/', '/{/', "/\?/"], '', $segment); - $attributes[$attribute_name] = $url[$i]; - $i++; - continue; - } elseif (preg_match("/{[\w]+\?}/", $segment)) { - // optional parameters - if (!isset($path[$j + 1]) || $path[$j + 1] !== $url[$i]) { - // optional parameter taken - $attribute_name = preg_replace(['/}/', '/{/', "/\?/"], '', $segment); - $attributes[$attribute_name] = $url[$i]; - $i++; - continue; - } else { - $match = false; - break; - } - } else { - // As soon as one segment doesn't match, then we have the wrong route - $match = false; - break; - } - } elseif (preg_match("/{[\w]+\?}/", $segment)) { - $attribute_name = preg_replace(['/}/', '/{/', "/\?/"], '', $segment); - $attributes[$attribute_name] = null; - $i++; - } else { - // no optional parameters but no more $url given - // this route does not match the url - $match = false; - break; - } - } - - if (isset($url[$i + 1])) { - $match = false; - } - - if ($match) { - return $attributes; - } - } - } else { - if (!$this->router->current()) { - return []; - } - - $attributes = $this->normalizeAttributes($this->router->current()->parameters()); - $response = event('routes.translation', [$locale, $attributes]); - - if (!empty($response)) { - $response = array_shift($response); - } - - if (\is_array($response)) { - $attributes = array_merge($attributes, $response); - } - } - - return $attributes; - } - - /** - * Build URL using array data from parse_url. - */ - protected function unparseUrl(array | false $parsed_url): string - { - if (empty($parsed_url)) { - return ''; - } - - $url = ''; - $url .= isset($parsed_url['scheme']) ? $parsed_url['scheme'].'://' : ''; - $url .= $parsed_url['host'] ?? ''; - $url .= isset($parsed_url['port']) ? ':'.$parsed_url['port'] : ''; - $user = $parsed_url['user'] ?? ''; - $pass = isset($parsed_url['pass']) ? ':'.$parsed_url['pass'] : ''; - $url .= $user.(($user || $pass) ? "$pass@" : ''); - - if (!empty($url)) { - $url .= isset($parsed_url['path']) ? '/'.ltrim($parsed_url['path'], '/') : ''; - } else { - $url .= $parsed_url['path'] ?? ''; - } - - $url .= isset($parsed_url['query']) ? '?'.$parsed_url['query'] : ''; - $url .= isset($parsed_url['fragment']) ? '#'.$parsed_url['fragment'] : ''; - - return $url; - } - - /** - * Normalize attributes gotten from request parameters. - */ - protected function normalizeAttributes(array $attributes): array - { - if (array_key_exists('data', $attributes) && \is_array($attributes['data']) && ! \count($attributes['data'])) { - $attributes['data'] = null; - return $attributes; - } - return $attributes; - } } diff --git a/src/Mcamara/LaravelLocalization/LaravelLocalizationServiceProvider.php b/src/Mcamara/LaravelLocalization/LaravelLocalizationServiceProvider.php index f12eb64..1e3f27a 100644 --- a/src/Mcamara/LaravelLocalization/LaravelLocalizationServiceProvider.php +++ b/src/Mcamara/LaravelLocalization/LaravelLocalizationServiceProvider.php @@ -2,7 +2,10 @@ namespace Mcamara\LaravelLocalization; +use Illuminate\Support\Facades\App; +use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Route; +use Illuminate\Support\Facades\URL; use Illuminate\Support\ServiceProvider; class LaravelLocalizationServiceProvider extends ServiceProvider @@ -13,6 +16,8 @@ public function boot(): void __DIR__.'/../../config/config.php' => config_path('laravellocalization.php'), ], 'config'); + URL::defaults(['locale' => App::getLocale()]); + $this->registerMacros(); } @@ -49,15 +54,29 @@ protected function registerMacros(): void Route::macro($localizationMacroName, function (callable $routes, array $middleware = []) { Route::middleware($middleware)->group(function () use ($routes) { - Route::name('default_lang.')->group($routes); - $supportedLocales = array_keys(config('laravellocalization.supportedLocales', [])); $localesMapping = array_keys(config('laravellocalization.localesMapping', [])); + $hideDefaultLocaleInURL = config('laravellocalization.hideDefaultLocaleInURL', false); + $useAcceptLanguageHeader = config('laravellocalization.useAcceptLanguageHeader', false); + $allowedLocales = implode('|', array_unique(array_merge($supportedLocales, $localesMapping))); Route::prefix('/{locale}') ->where(['locale' => $allowedLocales]) ->group($routes); + + if($hideDefaultLocaleInURL || $useAcceptLanguageHeader){ + Route::name('without_locale.')->group($routes); + } }); }); + + + $transRouter = app(\Mcamara\LaravelLocalization\Services\TransRouter::class); + + foreach (['get', 'post', 'put', 'delete'] as $method) { + Route::macro("trans" . ucfirst($method), function (string $routeKey, array $controller) use ($transRouter, $method) { + $transRouter->registerTransRoute($routeKey, $controller, $method); + }); + } } } diff --git a/src/Mcamara/LaravelLocalization/Middleware/LaravelLocalizationRoutes.php b/src/Mcamara/LaravelLocalization/Middleware/LaravelLocalizationRoutes.php deleted file mode 100644 index 5e62667..0000000 --- a/src/Mcamara/LaravelLocalization/Middleware/LaravelLocalizationRoutes.php +++ /dev/null @@ -1,25 +0,0 @@ -shouldIgnore($request)) { - return $next($request); - } - - $app = app(); - - $routeName = $app['laravellocalization']->getRouteNameFromAPath($request->getUri()); - - $app['laravellocalization']->setRouteName($routeName); - - return $next($request); - } -} diff --git a/src/Mcamara/LaravelLocalization/Middleware/LocaleCookieRedirect.php b/src/Mcamara/LaravelLocalization/Middleware/LocaleCookieRedirect.php index fe11fb5..10712c9 100644 --- a/src/Mcamara/LaravelLocalization/Middleware/LocaleCookieRedirect.php +++ b/src/Mcamara/LaravelLocalization/Middleware/LocaleCookieRedirect.php @@ -4,7 +4,7 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Cookie; -use Mcamara\LaravelLocalization\LanguageNegotiator; +use Mcamara\LaravelLocalization\Services\LanguageNegotiator; class LocaleCookieRedirect extends LaravelLocalizationMiddlewareBase { diff --git a/src/Mcamara/LaravelLocalization/Middleware/LocaleMappingMiddleware.php b/src/Mcamara/LaravelLocalization/Middleware/LocaleMappingMiddleware.php index 669968a..49a3324 100644 --- a/src/Mcamara/LaravelLocalization/Middleware/LocaleMappingMiddleware.php +++ b/src/Mcamara/LaravelLocalization/Middleware/LocaleMappingMiddleware.php @@ -4,13 +4,13 @@ use Closure; use Illuminate\Contracts\Config\Repository as ConfigRepository; -use Illuminate\Contracts\Foundation\Application; -use Illuminate\Contracts\Translation\Translator; +use Mcamara\LaravelLocalization\LaravelLocalization; class LocaleMappingMiddleware extends LaravelLocalizationMiddlewareBase { public function __construct( private readonly ConfigRepository $configRepository, + private readonly LaravelLocalization $laravelLocalization, ){ } @@ -20,22 +20,20 @@ public function handle($request, Closure $next) return $next($request); } - // Get the 'locale' parameter from the route $locale = $request->route('locale'); - - // Check if this locale has a mapping $localesMapping = $this->configRepository->get('laravellocalization.localesMapping'); if (array_key_exists($locale, $localesMapping)) { - // @toDO if locale maps to default locale, and hidedefault locale is on, simply redirect to locale = null - // @toDo needs to be tested, not sure if this works $mappedLocale = $localesMapping[$locale]; - $url = $request->fullUrl(); // Get the full URL + $url = $request->fullUrl(); - // Replace only the first occurrence of the locale in the URL - $newUrl = preg_replace("#/$locale#", "/$mappedLocale", $url, 1); + if($this->laravelLocalization->isHiddenDefault($mappedLocale)){ + $newUrl = preg_replace("#/$locale#", "/", $url, 1); + }else{ + $newUrl = preg_replace("#/$locale#", "/$mappedLocale", $url, 1); + } - return redirect($newUrl, 301); // Permanent redirect + return redirect($newUrl, 301); } return $next($request); diff --git a/src/Mcamara/LaravelLocalization/Middleware/LocaleSessionRedirect.php b/src/Mcamara/LaravelLocalization/Middleware/LocaleSessionRedirect.php index ac1d4a1..7618b3b 100644 --- a/src/Mcamara/LaravelLocalization/Middleware/LocaleSessionRedirect.php +++ b/src/Mcamara/LaravelLocalization/Middleware/LocaleSessionRedirect.php @@ -5,7 +5,7 @@ use Closure; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; -use Mcamara\LaravelLocalization\LanguageNegotiator; +use Mcamara\LaravelLocalization\Services\LanguageNegotiator; class LocaleSessionRedirect extends LaravelLocalizationMiddlewareBase { diff --git a/src/Mcamara/LaravelLocalization/Middleware/SetLocale.php b/src/Mcamara/LaravelLocalization/Middleware/SetLocale.php index 3f43e5d..b6a63d8 100644 --- a/src/Mcamara/LaravelLocalization/Middleware/SetLocale.php +++ b/src/Mcamara/LaravelLocalization/Middleware/SetLocale.php @@ -6,13 +6,10 @@ use Illuminate\Contracts\Config\Repository as ConfigRepository; use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\Translation\Translator; -use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\URL; -use Mcamara\LaravelLocalization\Exceptions\SupportedLocalesNotDefined; -use Mcamara\LaravelLocalization\Exceptions\UnsupportedLocaleException; -use Mcamara\LaravelLocalization\LanguageNegotiator; use Mcamara\LaravelLocalization\LaravelLocalization; +use Mcamara\LaravelLocalization\Services\LanguageNegotiator; class SetLocale extends LaravelLocalizationMiddlewareBase { @@ -33,8 +30,12 @@ public function handle(Request $request, Closure $next): mixed $locale = $request->route('locale'); + // @toDo translated routes need to be here, or I need to use {locale} for them aswell.. + + // The locale here cannot be an "inverse" mapping, as such cases are handled + // earlier by the locale mapping middleware. if($locale == null || empty($this->laravelLocalization->getSupportedLocales()[$locale])) { - $locale = $this->fallbackLocale($request); + $locale = $this->computeLocale($request); } $this->app->setLocale($locale); @@ -42,7 +43,7 @@ public function handle(Request $request, Closure $next): mixed $this->laravelLocalization->setCurrentLocale($locale); URL::defaults(['locale' => $locale]); - // Regional locale such as de_DE, so formatLocalized works in Carbon + // Configure regional locale settings (e.g., de_DE) for proper formatting in Carbon. $regional = $this->laravelLocalization->getCurrentLocaleRegional(); $suffix = $this->configRepository->get('laravellocalization.utf8suffix'); if ($regional) { @@ -53,18 +54,18 @@ public function handle(Request $request, Closure $next): mixed return $next($request); } - // if the first segment/locale passed is not valid the system would either take default locale, - // (if hideDefaultLocaleInURL is set, or retrieve it from the browser - protected function fallbackLocale(Request $request): string + protected function computeLocale(Request $request): string { $defaultLocale = $this->configRepository->get('app.locale'); - // if we reached this point and hideDefaultLocaleInURL is true, take default + // If we reached this point and `hideDefaultLocaleInURL` is enabled, enforce the default locale. + // In this case, `useAcceptLanguageHeader` is only considered by the `LaravelSessionRedirect` middleware + // when no locale has been set in the session. if ($this->laravelLocalization->hideDefaultLocaleInURL()) { return $defaultLocale; } - // but if hideDefaultLocaleInURL is false, we may have to retrieve it from the browser... + // If browser language negotiation is enabled, attempt to detect the best match. if ($this->laravelLocalization->useAcceptLanguageHeader()) { $negotiator = new LanguageNegotiator($defaultLocale, $this->laravelLocalization->getSupportedLocales(), $request); diff --git a/src/Mcamara/LaravelLocalization/LanguageNegotiator.php b/src/Mcamara/LaravelLocalization/Services/LanguageNegotiator.php similarity index 99% rename from src/Mcamara/LaravelLocalization/LanguageNegotiator.php rename to src/Mcamara/LaravelLocalization/Services/LanguageNegotiator.php index 7ee9794..01105ff 100644 --- a/src/Mcamara/LaravelLocalization/LanguageNegotiator.php +++ b/src/Mcamara/LaravelLocalization/Services/LanguageNegotiator.php @@ -1,6 +1,6 @@ matchRouteForAnyRoute($url); + + if ($route === null){ + // If hideDefaultLocale is disabled and negotiator is disabled, then only routes with locale can be matched + $route = $this->attemptRouteMatchingWithDefaultLocale($url, $defaultLocale, $supportedLocales); + } + + if ($route === null){ + // no route found, gracefully return $url as fallback + return $url; + } + + if(empty($attributes)){ + $attributes = $route->parameters(); + } + + $uri = $route->uri(); + $urlQuery = parse_url($url, PHP_URL_QUERY); + // e.g. `?page=2&sort=asc` + $urlQuery = $urlQuery ? '?'.$urlQuery : ''; + $hideLocaleInUrl = ($locale === $defaultLocale && !$forceDefaultLocation && $hiddenDefault); + + + // Handle transRoutes + if ($route->getName()) { + $routeName = $route->getName(); + + if (preg_match('/^trans_route_(for|no)_locale_(.*?)_(.*)$/', $routeName, $matches)) { + $type = ($hideLocaleInUrl) ? 'no' : 'for'; + $newRouteName = "trans_route_{$type}_locale_{$locale}_{$matches[3]}"; + if(!isset($attributes['locale'])){ + $attributes['locale'] = $locale; + } + + return route($newRouteName, $attributes) . $urlQuery; + } + } + + // Since we deal now with normal routes, we only need to modify, add or remove the locale from uri + + if (!isset($attributes['locale'])){ + if($hideLocaleInUrl){ + // locale already hidden in url + return $url; + } + + return $this->urlGenerator->to($locale . '/' . $uri, $attributes) . $urlQuery; + } + + $localeOfUrl = $attributes['locale']; + if($locale === $localeOfUrl){ + // no need to change locale of url + return $url; + } + + if ($hideLocaleInUrl) { + unset($attributes['locale']); + $cleanedUri = preg_replace('%^/?{locale}(/|$)%', '', $uri); + return $this->urlGenerator->to($cleanedUri, $attributes) . $urlQuery; + } + + $attributes['locale'] = $locale; + return $this->urlGenerator->to($uri, $attributes) . $urlQuery; + } + + protected function matchRouteForAnyRoute(string $url): Route|null + { + $methods = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']; + + foreach ($methods as $method) { + try { + $request = Request::create($url, $method); + $route = $this->router->getRoutes()->match($request); + + return $route; + } catch (\Exception $e) { + continue; + } + } + + return null; + } + + + protected function attemptRouteMatchingWithDefaultLocale(string $url, string $defaultLocale, array $supportedLocales): ?Route + { + $uri = parse_url($url, PHP_URL_PATH); + + // Extract the first segment of the URI + $segments = explode('/', trim($uri, '/')); + $firstSegment = $segments[0] ?? null; + + if(!empty($supportedLocales[$firstSegment])){ + array_unshift($segments, $defaultLocale); + $newUri = '/' . implode('/', $segments); + $url = preg_replace('/' . preg_quote($uri, '/') . '/', $newUri, $url, 1); + return $this->matchRouteForAnyRoute($url); + } + + return null; + } + + + +} diff --git a/src/Mcamara/LaravelLocalization/Services/TransRouter.php b/src/Mcamara/LaravelLocalization/Services/TransRouter.php new file mode 100644 index 0000000..c650870 --- /dev/null +++ b/src/Mcamara/LaravelLocalization/Services/TransRouter.php @@ -0,0 +1,60 @@ +allowedLocales = array_unique(array_merge($supportedLocales, $localesMapping)); + $this->hideDefaultLocaleInURL = config('laravellocalization.hideDefaultLocaleInURL', false); + $this->useAcceptLanguageHeader = config('laravellocalization.useAcceptLanguageHeader', false); + } + + public function registerTransRoute(string $routeKey, array|callable $controller, string $methodType): void + { + foreach ($this->allowedLocales as $locale) { + $key = "routes.$routeKey"; + + $route = trans($key, [], $locale); + if ($route === $key) { + continue; + } + + $route = ltrim($route, '/'); + $name = "trans_route_for_locale_{$locale}_{$routeKey}"; + + $middleware = [SetLocale::class]; + + if ($this->hideDefaultLocaleInURL && $locale === App::getLocale()) { + Route::$methodType($route, $controller) + ->middleware($middleware) + ->name($name); + } else { + Route::prefix('/{locale}')->$methodType($route, $controller) + ->middleware($middleware) + ->name($name); + + if ($this->useAcceptLanguageHeader) { + Route::$methodType($route, $controller) + ->middleware($middleware) + ->name("trans_route_no_locale_{$locale}_{$routeKey}"); + } + } + } + + + } +} diff --git a/src/Mcamara/LaravelLocalization/helper.php b/src/Mcamara/LaravelLocalization/helper.php new file mode 100644 index 0000000..4533d8b --- /dev/null +++ b/src/Mcamara/LaravelLocalization/helper.php @@ -0,0 +1,17 @@ +app, - $this->app['config'], - $translator, - $this->app['router'], - $this->app['request'], - $this->app['url'] - ); - - $localization->setLocale('es'); - - $this->assertEquals('es', $translator->getLocale()); - } -} diff --git a/tests/LaravelLocalizationTest.php b/tests/LaravelLocalizationTest.php index b9b814a..83716a2 100644 --- a/tests/LaravelLocalizationTest.php +++ b/tests/LaravelLocalizationTest.php @@ -2,13 +2,13 @@ namespace Mcamara\LaravelLocalization\Tests; -use PHPUnit\Framework\Attributes\DataProvider; use Illuminate\Support\Facades\Request; use Illuminate\Support\Facades\Route; use Mcamara\LaravelLocalization\LaravelLocalization; use Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRedirectFilter; use Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRoutes; use Mcamara\LaravelLocalization\Middleware\SetLocale; +use PHPUnit\Framework\Attributes\DataProvider; final class LaravelLocalizationTest extends TestCase { @@ -24,40 +24,45 @@ protected function setUp(): void parent::setUp(); $this->setUpRoutes(); + + // Manually refresh named route lookups because routes are defined in setUp() + // and no request has been made yet to trigger Laravel's automatic boot logic + app('router')->getRoutes()->refreshNameLookups(); } protected function setUpRoutes(): void { Route::localized(function () { Route::get('/', ['as' => 'index', function () { - return app('translator')->get('LaravelLocalization::routes.hello'); + return __('routes.hello'); }]); Route::get('test', ['as' => 'test', function () { - return app('translator')->get('LaravelLocalization::routes.test_text'); - }]); - - Route::get(app('laravellocalization')->transRoute('LaravelLocalization::routes.about'), ['as' => 'about', function () { - return app('laravellocalization')->getLocalizedURL('es') ?: 'Not url available'; - }]); - - Route::get(app('laravellocalization')->transRoute('LaravelLocalization::routes.view'), ['as' => 'view', function () { - return app('laravellocalization')->getLocalizedURL('es') ?: 'Not url available'; - }]); - - Route::get(app('laravellocalization')->transRoute('LaravelLocalization::routes.view_project'), ['as' => 'view_project', function () { - return app('laravellocalization')->getLocalizedURL('es') ?: 'Not url available'; + return __('routes.test_text'); }]); - Route::get(app('laravellocalization')->transRoute('LaravelLocalization::routes.manage'), ['as' => 'manage', function () { - return app('laravellocalization')->getLocalizedURL('es') ?: 'Not url available'; - }]); }, [ SetLocale::class, - LaravelLocalizationRoutes::class, LaravelLocalizationRedirectFilter::class, ]); + Route::transGet('about', [function () { + return app('laravellocalization')->getLocalizedURL('es') ?: 'Not url available'; + }]); + + Route::transGet('view', [function () { + return app('laravellocalization')->getLocalizedURL('es') ?: 'Not url available'; + }]); + + Route::transGet('view', [function () { + return app('laravellocalization')->getLocalizedURL('es') ?: 'Not url available'; + }]); + + Route::transGet('manage', [function () { + return app('laravellocalization')->getLocalizedURL('es') ?: 'Not url available'; + }]); + + Route::get('/skipped', ['as' => 'skipped', function () { return Request::url(); }]); @@ -118,30 +123,25 @@ protected function getEnvironmentSetUp($app) $this->supportedLocales = app('config')->get('laravellocalization.supportedLocales'); - app('translator')->getLoader()->addNamespace('LaravelLocalization', realpath(dirname(__FILE__)).'/lang'); - - app('translator')->load('LaravelLocalization', 'routes', 'es'); - app('translator')->load('LaravelLocalization', 'routes', 'en'); - - app('laravellocalization')->setBaseUrl(self::$testUrl); + app()->useLangPath(realpath(dirname(__FILE__)).'/lang'); } public function testTranslatedRoutes(): void { - $this->assertEquals(route('about'), 'http://localhost/about'); + $this->assertEquals('http://localhost/en/about', localized_trans_route('about')); - $this->get(route('about', ['locale' => 'es'])) + $this->get(localized_trans_route('about', ['locale' => 'es'])) ->assertStatus(200); $this->assertEquals('es', app('laravellocalization')->getCurrentLocale()); - $this->assertEquals(route('about'), 'http://localhost/acerca'); + $this->assertEquals(localized_trans_route('about'), 'http://localhost/es/acerca'); - $this->get(route('about', ['locale' => 'en'])) + $this->get(localized_trans_route('about', ['locale' => 'en'])) ->assertStatus(200); - $this->assertEquals(route('about'), 'http://localhost/about'); + $this->assertEquals( 'http://localhost/en/about', localized_trans_route('about')); - $this->get(route('about', ['locale' => 'de'])) + $this->get(localized_trans_route('about', ['locale' => 'de'])) ->assertStatus(200); $this->assertEquals('en', app('laravellocalization')->getCurrentLocale()); @@ -203,44 +203,71 @@ public function testLocalizeURL(): void ); } - public function testGetLocalizedURL(): void + private function getUrl(string $uri = '') { + return self::$testUrl . $uri; + } + + public function testGetLocalizedURLLong(): void + { + /** @var LaravelLocalization $localization */ + $localization = app('laravellocalization'); + $this->assertEquals( - self::$testUrl.app('laravellocalization')->getCurrentLocale(), - app('laravellocalization')->getLocalizedURL() + $this->getUrl('es/acerca'), + $localization->getLocalizedURL('es', $this->getUrl('about')) + ); + + $this->assertEquals( + $this->getUrl('en/about'), + $localization->getLocalizedURL('en', $this->getUrl('about')) + ); + + $this->assertEquals( + $this->getUrl('es/acerca'), + $localization->getLocalizedURL('es', $this->getUrl('acerca')) + ); + + $this->assertEquals( + $this->getUrl('en/about'), + $localization->getLocalizedURL('en', $this->getUrl('acerca')) + ); + + $this->assertEquals( + $this->getUrl('en'), + $localization->getLocalizedURL() ); app('config')->set('laravellocalization.hideDefaultLocaleInURL', true); - // testing default language hidden $this->assertNotEquals( - self::$testUrl.app('laravellocalization')->getDefaultLocale(), - app('laravellocalization')->getLocalizedURL() + $this->getUrl('en'), + $localization->getLocalizedURL() ); - app()->setLocale('es'); + $localization->setCurrentLocale('es'); $this->assertNotEquals( - self::$testUrl, - app('laravellocalization')->getLocalizedURL() + $this->getUrl(), + $localization->getLocalizedURL() ); $this->assertNotEquals( - self::$testUrl.app('laravellocalization')->getDefaultLocale(), - app('laravellocalization')->getLocalizedURL() + $this->getUrl('en'), + $localization->getLocalizedURL() ); $this->assertEquals( - self::$testUrl.app('laravellocalization')->getCurrentLocale(), - app('laravellocalization')->getLocalizedURL() + $this->getUrl('es'), + $localization->getLocalizedURL() ); $this->assertEquals( - self::$testUrl.'es/acerca', - app('laravellocalization')->getLocalizedURL('es', self::$testUrl.'about') + $this->getUrl('es/acerca'), + $localization->getLocalizedURL('es', $this->getUrl('acerca')) ); - app()->setLocale('en'); + app('laravellocalization')->setCurrentLocale('es'); $response = $this->get(self::$testUrl.'about', ['Accept-Language' => 'en,es']); @@ -253,17 +280,17 @@ public function testGetLocalizedURL(): void app('config')->set('laravellocalization.hideDefaultLocaleInURL', true); $this->assertEquals( - self::$testUrl.'test', - app('laravellocalization')->getLocalizedURL('en', self::$testUrl.'test') + $this->getUrl('test'), + $localization->getLocalizedURL('en', $this->getUrl('test')) ); $this->assertEquals( - self::$testUrl.'test?a=1', - app('laravellocalization')->getLocalizedURL('en', self::$testUrl.'test?a=1') + $this->getUrl('test?a=1'), + $localization->getLocalizedURL('en', $this->getUrl('test?a=1')) ); $response = $this->get( - app('laravellocalization')->getLocalizedURL('en', self::$testUrl.'test'), + $this->getUrl('test'), ['Accept-Language' => 'en,es'] ); @@ -274,13 +301,13 @@ public function testGetLocalizedURL(): void ); $this->assertEquals( - self::$testUrl.'es/test', - app('laravellocalization')->getLocalizedURL('es', self::$testUrl.'test') + $this->getUrl('es/test'), + $localization->getLocalizedURL('es', self::$testUrl.'test') ); $this->assertEquals( - self::$testUrl.'es/test?a=1', - app('laravellocalization')->getLocalizedURL('es', self::$testUrl.'test?a=1') + $this->getUrl('es/test?a=1'), + $localization->getLocalizedURL('es', $this->getUrl('test?a=1')) ); } @@ -737,7 +764,7 @@ public function testLanguageNegotiation($accept_string, $must_resolve_to, $asd = $request = $this->createMock(\Illuminate\Http\Request::class); $request->expects($this->any())->method('header')->with('Accept-Language')->willReturn($accept_string); - $negotiator = app(\Mcamara\LaravelLocalization\LanguageNegotiator::class, + $negotiator = app(\Mcamara\LaravelLocalization\Services\LanguageNegotiator::class, [ 'defaultLocale' => 'wrong', 'supportedLanguages' => $full_config['supportedLocales'], @@ -792,7 +819,7 @@ public function testLanguageNegotiationWithMapping(): void { $request = $this->createMock(\Illuminate\Http\Request::class); $request->expects($this->any())->method('header')->with('Accept-Language')->willReturn($accept_string); - $negotiator = app(\Mcamara\LaravelLocalization\LanguageNegotiator::class, + $negotiator = app(\Mcamara\LaravelLocalization\Services\LanguageNegotiator::class, [ 'defaultLocale' => 'wrong', 'supportedLanguages' => $full_config['supportedLocales'], diff --git a/v3_changes.md b/v3_changes.md new file mode 100644 index 0000000..079c213 --- /dev/null +++ b/v3_changes.md @@ -0,0 +1,75 @@ +# Changes + +The major architectural change, discussed in [#921](https://github.com/mcamara/laravel-localization/issues/921), shifts locale handling from the route file to middleware. + +This allows us to remove the custom caching solution and rely on Laravel's default caching. However, it introduces a new workaround for translated routes—although they never worked completely bug-free in the first place. + +Below is a list of functions and features removed in v3. Some code was removed due to a lack of documentation, tests, or a clear explanation in the pull request. Additionally, a lot of code appeared to be unused, and even after careful reverse engineering, its purpose remained unclear—so it was removed. + +If you notice something critical missing, please **open an issue**. + +The main improvement of the code is: + +- We can use native caching +- Much faster +- Most of the 35 current open issues are fixed, should now be compatible with other packages +- Code base much simpler + +However, this comes with a cost: + +- Using `route(..)` helper does not work for translated routes (those defined in `/lang/routes.php` ). + You can use `localized_trans_route` helper instead. +- If you enable `hiddenDefaultLocales` and want to avoid an additional redirect for every route going to the default locale, + you should use `localized_route` helper instead. + + +## Removals & Changes + +- **Removed custom caching command** – Now fully compatible with Laravel’s built-in caching. +- **Locale is now a route parameter** instead of being set directly in route definitions. +- **Removed `baseUrl` property and related methods**. +- **`getBaseUrl()` is no longer used** – If you have a valid use case, please open an issue. +- **Removed `route.translation` event** – Documentation was unclear, there were open issues, and it was inconsistently triggered (only when the URL was empty during localization). +- **The `data` attribute is no longer removed from routes attributes** – This should not be the responsibility of the package. +- **`getLocalizedURL(locale: false)` no longer removes the locale from the URL**. +- - **`getLocalizedURL()` no longer returns false if url is not found, instead, the same url is returned**. +- **Dropped alias `localizeURL`** – If needed, you can define a custom helper. +- **`translatedRoutes` is no longer stored inside `LaravelLocalization`**. +- **Removed `getNonLocalizedURL()`**. +- **All `translatableRoutes` related methods have been removed** from `LaravelLocalization`. +- **Removed `LaravelLocalizationRoutes` middleware** and its associated `$routeName` attribute. +- Removed `createUrlFromUri` method +- Removed huge `extractAttributes` method, no longer needed +- Translated routes (defined in `/lang/routes.php`) can no longer have a manual route name. Instead, each route gets a localized name per allowed locale. + Routes outside of `/lang/routes.php` can still use manual names. To generate URLs for translated routes, you may use `LaralvelLocalisation::transRoute($key)` or tha alias helper `localized_trans_route($key)` with the corresponding key `$key` from `/lang/routes.php.` +- In case you use `hiddenDefaultLocale` there might be an addition redirect when using `redirect(route(...))`. This is caused, because the name of your route is always mapped to the route with the `{localize}` parameter. + To avoid +- Removed side effect of `transRoute`, this no longer changes the current locale. +- The `currentLocale` variable of LaravelLocallization is now always identical to `App::getLocale()`. It may be removed. + +If something crucial was removed by mistake or if you encounter missing functionality, feel free to **create an issue**. + +## Test changes + +### Changes in `testTranslatedRoutes` +General: Instead of `route` helper we need to use `localized_trans_route` helper. + +Old: + +```php +$this->assertEquals(route('about'), 'http://localhost/about'); +$this->assertEquals(route('about'), 'http://localhost/about'); +``` + +New: + +```php +$this->assertEquals('http://localhost/en/about', localized_trans_route('about')); +$this->assertEquals( 'http://localhost/en/about', localized_trans_route('about')); +``` + +Explanation: + +1. Translated routes can no longer have manual names. Use the `localized_trans_route` helper instead. +2. The expected value should come first in assertEquals. +3. `hiddenDefaultLocale` is disabled, and the locale is set to `en`, so the URL must include the locale.