-
webdeveloper Junior Member
 
-
Posts: 15
Threads: 6
Joined: Jan 2016
Reputation:
0
06-08-2022, 06:23 AM
(This post was last modified: 06-11-2022, 04:35 AM by webdeveloper.)
Hi, I just want to share with you one of my changes I made to base router. Hopefully, it will save some time, to somebody who will search for the same solution like I did.
I was wondering why my project takes URL like
Code: www.domain.com/blah/users
when I've never defined it? Why this works?
What I found out:
PHP Code: $routes->group('{locale}', function ($routes) { $routes->group('users', function ($routes) { $routes->get('/', 'Users::index'); }); });
{locale} will take ANYTHING
Why does all this URLs works?
Code: www.domain.com/this/users
www.domain.com/makes/users
www.domain.com/no/users
www.domain.com/sense/users
www.domain.com/to/users
www.domain.com/me/users
{locale} should represent locale placeholder. According to me, it does not. It represent anything. Yes, it takes default locale value, if placeholder doesn't match any supported language.
PHP Code: public $supportedLocales = ['en'];
Only this should works.
Code: www.domain.com/en/users
Changes I have made:
Create new file app/Services/Router.php
PHP Code: <?php
namespace App\Services;
use CodeIgniter\HTTP\Request; use CodeIgniter\Router\Exceptions\RedirectException; use CodeIgniter\Router\RouteCollectionInterface; use CodeIgniter\Router\Router as CoreRouter;
class Router extends CoreRouter { public function __construct(RouteCollectionInterface $routes, ?Request $request = null) { parent::__construct($routes, $request); }
/** * @param string $uri * @return bool * @throws RedirectException */ protected function checkRoutes(string $uri): bool { $routes = $this->collection->getRoutes($this->collection->getHTTPVerb());
// Don't waste any time if (empty($routes)) { return false; }
$uri = $uri === '/' ? $uri : trim($uri, '/ ');
// Loop through the route array looking for wildcards foreach ($routes as $key => $val) { // Reset localeSegment $localeSegment = null;
$key = $key === '/' ? $key : ltrim($key, '/ ');
$matchedKey = $key;
// Are we dealing with a locale? if (strpos($key, '{locale}') !== false) { $localeSegment = array_search('{locale}', preg_split('/[\/]*((^[a-zA-Z0-9])|\(([^()]*)\))*[\/]+/m', $key), true);
// Replace it with a regex so it // will actually match. $key = str_replace('/', '\/', $key); $key = str_replace('{locale}', '[^\/]+', $key); }
// Does the RegEx match? if (preg_match('#^' . $key . '$#u', $uri, $matches)) { // Is this route supposed to redirect to another? if ($this->collection->isRedirect($key)) { throw new RedirectException(is_array($val) ? key($val) : $val, $this->collection->getRedirectCode($key)); } // Store our locale so CodeIgniter object can // assign it to the Request. if (isset($localeSegment)) { // The following may be inefficient, but doesn't upset NetBeans :-/ $temp = (explode('/', $uri));
/** * CORE EDIT START */ $supportedLocales = config('App')->supportedLocales;
if (!in_array($temp[$localeSegment], $supportedLocales)) { return false; } /** * CORE EDIT END */
$this->detectedLocale = $temp[$localeSegment]; }
// Are we using Closures? If so, then we need // to collect the params into an array // so it can be passed to the controller method later. if (!is_string($val) && is_callable($val)) { $this->controller = $val;
// Remove the original string from the matches array array_shift($matches);
$this->params = $matches;
$this->matchedRoute = [ $matchedKey, $val, ];
$this->matchedRouteOptions = $this->collection->getRoutesOptions($matchedKey);
return true; } // Are we using the default method for back-references?
// Support resource route when function with subdirectory // ex: $routes->resource('Admin/Admins'); if (strpos($val, '$') !== false && strpos($key, '(') !== false && strpos($key, '/') !== false) { $replacekey = str_replace('/(.*)', '', $key); $val = preg_replace('#^' . $key . '$#u', $val, $uri); $val = str_replace($replacekey, str_replace('/', '\\', $replacekey), $val); } else if (strpos($val, '$') !== false && strpos($key, '(') !== false) { $val = preg_replace('#^' . $key . '$#u', $val, $uri); } else if (strpos($val, '/') !== false) { [ $controller, $method, ] = explode('::', $val);
// Only replace slashes in the controller, not in the method. $controller = str_replace('/', '\\', $controller);
$val = $controller . '::' . $method; }
$this->setRequest(explode('/', $val));
$this->matchedRoute = [ $matchedKey, $val, ];
$this->matchedRouteOptions = $this->collection->getRoutesOptions($matchedKey);
return true; } }
return false; } }
Check added part of code:
PHP Code: /** * CORE EDIT START */ $supportedLocales = config('App')->supportedLocales;
if (!in_array($temp[$localeSegment], $supportedLocales)) { return false; } /** * CORE EDIT END */
Add this use to app/Config/Services.php
Add custom router:
PHP Code: /** * @param RouteCollectionInterface|null $routes * @param \CodeIgniter\HTTP\Request|null $request * @param bool $getShared * @return Router */ public static function router(RouteCollectionInterface $routes = null, \CodeIgniter\HTTP\Request $request = null, $getShared = true): Router { if ($getShared) { return static::getSharedInstance('router', $routes, $request); }
$routes ??= AppServices::routes(); $request ??= AppServices::request();
return new Router($routes, $request); }
-
includebeer CodeIgniter Team
-
Posts: 1,018
Threads: 18
Joined: Oct 2014
Reputation:
40
You are right, it doesn't make a lot of sense how unsupported locales are handled by the framework. Instead of a custom router, I made a filter :
PHP Code: <?php namespace App\Filters;
use CodeIgniter\HTTP\RequestInterface; use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\Filters\FilterInterface;
class Localize implements FilterInterface { public function before(RequestInterface $request, $arguments = null) { log_message('debug', "FilterLocalize --- start ---"); $uri = &$request->uri; $segments = array_filter($uri->getSegments()); $nbSegments = count($segments); log_message('debug', "FilterLocalize - {$nbSegments} segments = " . print_r($segments, true));
// Keep only the first 2 letters (en-UK => en) $userLocale = strtolower(substr($request->getLocale(), 0, 2)); log_message('debug', "FilterLocalize - Visitor's locale $userLocale");
// If the user's language is not a supported language, take the default language $locale = in_array($userLocale, $request->config->supportedLocales) ? $userLocale : $request->config->defaultLocale; log_message('debug', "FilterLocalize - Selected locale $locale");
// If we request /, redirect to /{locale} if ($nbSegments == 0) { log_message('debug', "FilterLocalize - Redirect / to /{$locale}"); log_message('debug', "FilterLocalize --- end ---"); return redirect()->to("/{$locale}"); }
log_message('debug', "FilterLocalize - segments[0] = " . $segments[0]); $locale = $segments[0];
// If the first segment of the URI is not a valid locale, trigger a 404 error if ( ! in_array($locale, $request->config->supportedLocales)) { log_message('debug', "FilterLocalize - ERROR Invalid locale '{$locale}'"); log_message('debug', "FilterLocalize --- end ---"); throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound(); }
log_message('debug', "FilterLocalize - Valid locale '$locale'"); log_message('debug', "FilterLocalize --- end ---"); }
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null) { // Do something here }
}
-
webdeveloper Junior Member
 
-
Posts: 15
Threads: 6
Joined: Jan 2016
Reputation:
0
06-09-2022, 01:56 AM
(This post was last modified: 06-09-2022, 01:57 AM by webdeveloper.)
Thank you for comment! Always happy to see any other solutions.
Yep, it can be done like this too. I have never think about to create filter for this. Router should handle this "right".
One important difference is that your locale have to be placed always on the first place of URL.
This wont work for you:
PHP Code: $routes->group('translations', function ($routes) { $routes->group('{locale}', function ($routes) { $routes->get('/', 'Translations::index'); }); });
Second one difference is that router is called before filter. A lot of (possibly unimportant) code can be done until page not found exception is thrown.
Why did you remove second part of locale? This is used in most cases for variants of language e.g. "en-US". I consider this language fallback behaviour as useful
Quote:So, if you are using the locale fr-CA, then a localized message will first be sought in Language/fr/CA, then in Language/fr, and finally in Language/en.
-
includebeer CodeIgniter Team
-
Posts: 1,018
Threads: 18
Joined: Oct 2014
Reputation:
40
(06-09-2022, 01:56 AM)webdeveloper Wrote: One important difference is that your locale have to be placed always on the first place of URL.
This wont work for you:
PHP Code: $routes->group('translations', function ($routes) { $routes->group('{locale}', function ($routes) { $routes->get('/', 'Translations::index'); }); });
You're right, I didn't think it could be a problem as all my routes are configured with the locale as the first segment of the URL.
(06-09-2022, 01:56 AM)webdeveloper Wrote: Why did you remove second part of locale? This is used in most cases for variants of language e.g. "en-US". I consider this language fallback behaviour as useful
Quote:So, if you are using the locale fr-CA, then a localized message will first be sought in Language/fr/CA, then in Language/fr, and finally in Language/en.
I remove the second part of the locale because it doesn't really make a difference. At least not in my case. My blog is in French and in English. So if the locale detected is en-US, en-UK or any other en-xx it redirects to the English version of the blog. Same thing for fr-FR, fr-CA, etc, it goes to the French version. Anything else goes to the default locale, with is English.
-
InsiteFX Super Moderator
     
-
Posts: 6,729
Threads: 344
Joined: Oct 2014
Reputation:
246
06-09-2022, 11:57 PM
(This post was last modified: 06-10-2022, 12:03 AM by InsiteFX.)
It's probaliy best to use the language+region code for translations en-US etc.
@ includebeer, I have been working on your multi-language tutorial and have a working language drop down
with country flags working, I just have to clean-up the flags because they are in language not country.
Example: Japan is named jp in the flags css flie not by language code jp
What did you Try? What did you Get? What did you Expect?
Joined CodeIgniter Community 2009. ( Skype: insitfx )
-
includebeer CodeIgniter Team
-
Posts: 1,018
Threads: 18
Joined: Oct 2014
Reputation:
40
(06-09-2022, 11:57 PM)InsiteFX Wrote: It's probaliy best to use the language+region code for translations en-US etc.
I guess it depends on the use case. For me it was the easiest way to redirect to the english or french articles. I should probably modify my filter to keep the real locale (en-US, en-UK...) but the only difference I can think of is how the dates are displayed. Otherwise, everything should be the same in en-US vs. en-UK, or fr-FR vs. fr-CA.
(06-09-2022, 11:57 PM)InsiteFX Wrote: @includebeer, I have been working on your multi-language tutorial and have a working language drop down
with country flags working, I just have to clean-up the flags because they are in language not country.
Example: Japan is named jp in the flags css flie not by language code jp
Cool!
-
kenjis Administrator
      
-
Posts: 3,671
Threads: 96
Joined: Oct 2014
Reputation:
230
-
includebeer CodeIgniter Team
-
Posts: 1,018
Threads: 18
Joined: Oct 2014
Reputation:
40
-
webdeveloper Junior Member
 
-
Posts: 15
Threads: 6
Joined: Jan 2016
Reputation:
0
06-11-2022, 04:29 AM
(This post was last modified: 06-11-2022, 05:26 AM by webdeveloper.)
(06-10-2022, 06:34 PM)includebeer Wrote: (06-10-2022, 06:05 PM)kenjis Wrote: See this PR: https://github.com/codeigniter4/CodeIgniter4/pull/6073
I like the idea of a $useSupportedLocalesOnly option. No need to hack the default behaviour with a filter!
The main question is why you run into this problem, that you need to redirect with right locale? How does the wrong locale appear in your URL? Is it even possible to happen just so?
@ kenjis This makes the solution which I posted even better! Like that.
DONE. Fallback behaviour implemented.
Add to: app/Config/Routes.php
PHP Code: $routes->useDefinedLocalesOnly(true);
Create file: app/Services/Routes.php
PHP Code: <?php
namespace App\Services;
use CodeIgniter\Autoloader\FileLocator; use CodeIgniter\Router\RouteCollection; use Config\Modules;
class Routes extends RouteCollection { /** * @var bool */ public bool $useDefinedLocalesOnly = false;
public function __construct(FileLocator $locator, Modules $moduleConfig) { parent::__construct($locator, $moduleConfig); }
/** * @param bool $value * @return $this */ public function useDefinedLocalesOnly(bool $value): self { $this->useDefinedLocalesOnly = $value;
return $this; } }
Add to: app/Services/Router.php (check the code in first post to find the place where to put this)
PHP Code: /** * CORE EDIT START */ if ($this->collection->useDefinedLocalesOnly) { $supportedLocales = config('App')->supportedLocales;
if (!in_array($temp[$localeSegment], $supportedLocales)) { return false; } } /** * CORE EDIT END */
Add use to: app/Config/Services.php
and function
PHP Code: /** * @param bool $getShared * @return Routes */ public static function routes($getShared = true): Routes { if ($getShared) { return static::getSharedInstance('routes'); }
return new Routes(AppServices::locator(), config('Modules')); }
|