Laravel Multi-language routes without prefix - php

Currently, my system is using 2 languages which is English and German. my goal is to browse following routes by switching between the mentioned language -
base-url/contact - for english
base-url/kontakte - for german
Currently, I have required routes file in the resource folder, where I have put the necessary translated words.
resources/lang/en/routes.php
resources/lang/de/routes.php
In web.php I have currently -
Route::get(r('contact'), 'TestController#index')->name('contact');
by r() helper function I am getting the active translated word.
On my user table, I have locale column where I am storing the active language when I am updating the language from user profile -
\Session::put('locale', $request->input('locale'));
I have created a middleware Localization where I have currently -
public function handle($request, Closure $next)
{
if ( \Session::has('locale')) {
\App::setLocale(\Session::get('locale'));
Carbon::setLocale(\Session::get('locale'));
}
return $next($request);
}
Currently, the code is working fine for blade translated word. but the translated routes are not working. whenever I switch and visit any route, it gives me 404 error. but if I restart the server by PHP artisan serve, it works with changed language.
So how fix the issue?

:) Try to put \App::setLocale(\Session::get('locale') in the beginning of the routes file (routes.php, or web.php/api.php)

With PHP 8.1.0, Laravel 9.x
resources/lang/en/routes.php:
return [
'post' => '/post/',
'product' => '/product/',
'contact' => '/contact/',
'aboutUs' => '/about-us/'
];
Create LanguageType enum:
enum LanguageType: string
{
case EN = 'en';
case DE = 'de';
public function label(): string
{
return trans(match ($this) {
self::EN => 'English',
self::DE => 'German',
});
}
}
In web.php:
Route::get('{contactTranslation}', [ContactController::class, 'index']);
Route::get('{aboutUsTranslation}', [AboutUsController::class, 'index']);
Route::get('{productTranslation}', [ProductController::class, 'index']);
Route::get('{postTranslation}', [PostController::class, 'index']);
//...
In RouteServiceProvider.php/boot(), add:
foreach (trans('routes') as $key => $value) {
Route::pattern($key . 'Translation', $this->getTranslation($key));
}
And function:
private function getTranslation($slug): string
{
$slugList = collect();
foreach (LanguageType::cases() as $language) {
$slugList = $slugList->merge(trim(trans('routes.' . $slug, [], $language->value), '/'));
}
return $slugList->implode('|');
}

Related

Laravel 7 Binding Routes

I have this route declared on laravel:
Route::get('pages/{page}/{slug}', 'Common\Pages\CustomPageController#show')
->middleware(['web', 'prerenderIfCrawler']);
This route works fine and works if you make requests to:
https://example.com/pages/1/test-page
https://example.com/pages/2/other-page
https://example.com/pages/3/url-test
The problem is that I need a more friendly url as well as.
https://example.com/test-page
https://example.com/other-page
https://example.com/url-test
I want remove the suffix called pages, The numbers for the pages will never change and will be static for each one.
I've tried to make static routes for each one but can't get it to work.
Route::get('other-page', array('as' => 'other-page', function() {
return App::make('Common\Pages\CustomPageController')->show(2);
}))->middleware(['web', 'prerenderIfCrawler']);
I would appreciate a little help.
You could always get the URL segment in the Controller and use that to know what page you are on. If you don't want to do that you could pass extra information in the 'action' to specify the page:
Route::middleware(['web', 'prerenderIfCrawler'])->group(function () {
Route::get('test-page', [
'uses' => 'Common\Pages\CustomPageController#show',
'page' => 'test-page',
]);
...
});
Then you can get this extra information in the Controller:
public function show(Request $request)
{
$page = $request->route()->getAction('page');
...
}
If you knew all the pages you can use a route parameter with a regex constraint to restrict it to only those page names:
Route::get('{page:slug}', ...)->where('page', 'test-page|other-page|...');
public function show(Page $page)
{
...
}
You could just make use of a wildcard to catch your routes like this:
Route::get('/{slug}', 'Common\Pages\CustomPageController#show')
->middleware(['web', 'prerenderIfCrawler']);
Then in your controller:
public function show($slug)
{
$page = Page::where('slug', $slug)->first();
// ...
}
Just be careful with where you place the route. It should be at the end of your routes otherwise it will catch all the request of your app.
// ...
// my other routes
// ...
Route::get('/{slug}', ...);
By the way, if you want to bind your page models using the slug attribute do this:
Route::get('/{page:slug}', 'Common\Pages\CustomPageController#show')->//...
^^^^^^^^^
Then in your controller:
public function show(Page $page)
{ ^^^^^^^^^^
// ...
}
Check this section of the docs.

CakePHP 3: switching between Router scopes

I have a simple routing setup to address different languages. The goal is to have the language as a first parameter in the url like /en/docs or /de/docs. The language definitions are set as scopes as show below:
$builder = function ($routes)
{
$routes->connect('/:controller', ['action' => 'index']);
$routes->connect('/:controller/:action/*', ['action' => 'index']);
};
$languages = ['en', 'de'];
foreach ($languages as $lang) {
Router::scope('/'.$lang, ['language' => $lang], $builder);
}
Router::addUrlFilter(function ($params, $request) {
if ($request->param('language')) {
$params['language'] = $request->param('language');
}
else {
$params['language'] = 'en';
}
return $params;
});
Each of the scopes is working as expected. Even with some more complex connects and prefixes (I removed them from the code above to make the code more readable).
The problem is now: how to create a link to switch between the languages (scopes)?
I tried different urls, but depending on the current url (e.g. /de/docs), the language parameter does not have any effect on the created urls:
Router::url(['language' => 'en', 'controller' => 'docs']);
// -> /de/docs (expected: /en/docs)
Router::url(['language' => 'de', 'controller' => 'docs']);
// -> /de/docs
How to fix the routes to get the expected urls?
I found the reason for this behaviour. The function addUrlFilter replaced the language parameter when generating the urls and the result was always the current language. An updated version of this function does the job:
Router::addUrlFilter(function ($params, $request) {
if (!isset($params['language'])) {
$params['language'] = $request->param('language');
}
return $params;
});
Puuh, one hour of searching and trying...

Laravel 5.2 subdomain dynamical routes to controllers

Lets assume I have a site with cars: cars.com on Laravel 5.
I want to set up my routes.php so a user could type in a browser ford.cars.com/somethingOrnothing and get to the controller responsible for Ford™ cars (FordController).
Of course I could use something like this code:
Route::group(['middleware' => 'web'], function () {
Route::group(['domain' => 'ford.cars.com'], function(\Illuminate\Routing\Router $router) {
return $router->resource('/', 'FordController');
});
});
But I am not happy about writing and maintaining routes for hundreds of car brands.
I would like to write something like this:
Route::group(['domain' => '{brand}.cars.com'], function(\Illuminate\Routing\Router $router) {
return $router->get('/', function($brand) {
return Route::resource('/', $brand.'Controller');
});
});
So the question is: Is it possible to dynamically set routes for sub-domains and how to achieve this?
upd:
the desirable outcome is to have subdomains that completely repeat controllers structure. Like Route::controller() did (but it is now deprecated)
To emulate Route::controller() behaviour you could do this:
Route::group(['domain' => '{carbrand}.your.domain'], function () {
foreach (['get', 'post'] as $request_method) {
Route::$request_method(
'{action}/{one?}/{two?}/{three?}/{four?}',
function ($carbrand, $action, $one = null, $two = null, $three = null, $four = null) use ($request_method) {
$controller_classname = '\\App\\Http\\Controllers\\' . Str::title($carbrand).'Controller';
$action_name = $request_method . Str::title($action);
if ( ! class_exists($controller_classname) || ! method_exists($controller_classname, $action_name)) {
abort(404);
}
return App::make($controller_classname)->{$action_name}($one, $two, $three, $four);
}
);
}
});
This route group should go after all other routes, as it raises 404 Not found exception.
Probably this is what you need:
Sub-Domain Routing
Route groups may also be used to route wildcard sub-domains.
Sub-domains may be assigned route parameters just like route URIs,
allowing you to capture a portion of the sub-domain for usage in your
route or controller. The sub-domain may be specified using the domain
key on the group attribute array:
Route::group(['domain' => '{account}.myapp.com'], function () {
Route::get('user/{id}', function ($account, $id) {
//
});
});
From:
Laravel 5.2 Documentation
upd.
If you want to call your controller method you can do it like this:
Route::group(['domain' => '{account}.myapp.com'], function () {
Route::get('user/{id}', function ($account, $id) {
$controllerName = $account . 'Controller' //...or any other Controller Name resolving logic goes here
app('App\Http\Controllers\\' . $controllerName)->controllerMethod‌​($id);
});
});

Handle url's character casing in Laravel?

How to I redirect following urls to lower case url?
http://domain.com/City/really-long-slug-from-db/photos
http://domain.com/city/Really-Long-Slug-From-Db/photos
http://domain.com/City/really-long-slug-from-db/Photos
to
http://domain.com/city/really-long-slug-from-db/photos
This is my route:
Route::any('/{city}/{slug}/{page?}',
array(
'as' => 'slug-page',
function($city, $slug, $page="info"){
return View::make('default.template.'.$page)
->with('city', $city)
->with('page',$page)
->with('slug', $slug);
}
))
->where(
array(
'city' => '[a-z ]+',
'page' => '[a-z-]+',
'slug' => '(about|photos|videos)'
));
Currently I used regex [a-z-]+ to match only smaller case strings and that throws NotFoundHttpException for obvious reasons.
How do I accept all these parameters in case insensitive strings and 301 redirect(to avoid duplicate urls) to smaller case urls in Laravel 5.1?
You could easily do that with a route middleware. The middleware should check if there are any uppercase characters in the path and redirect to lowercased version.
First, define the middleware class:
class RedirectToLowercase
{
public function handle($request, Closure $next) {
$path = $request->path();
$pathLowercase = strtolower($path); // convert to lowercase
if ($path !== $pathLowercase) {
// redirect if lowercased path differs from original path
return redirect($pathLowercase);
}
return $next($request);
}
}
Then register the new middleware in your Kernel.php:
protected $routeMiddleware = array(
// ... some other middleware classes ...
'lowercase' => 'App\Http\Middleware\RedirectToLowercase'
);
Finally, apply the middleware to your route:
Route::any('/{city}/{slug}/{page?}', array(
'as' => 'slug-page',
'middleware' => 'lowercase',
function() {
// your code
})
);

Laravel 5 Route binding and Hashid

I am using Hashid to hide the id of a resource in Laravel 5.
Here is the route bind in the routes file:
Route::bind('schedule', function($value, $route)
{
$hashids = new Hashids\Hashids(env('APP_KEY'),8);
if( isset($hashids->decode($value)[0]) )
{
$id = $hashids->decode($value)[0];
return App\Schedule::findOrFail($id);
}
App::abort(404);
});
And in the model:
public function getRouteKey()
{
$hashids = new \Hashids\Hashids(env('APP_KEY'),8);
return $hashids->encode($this->getKey());
}
Now this works fine the resource displays perfectly and the ID is hashed.
BUT when I go to my create route, it 404's - if I remove App::abort(404) the create route goes to the resource 'show' view without any data...
Here is the Create route:
Route::get('schedules/create', [
'uses' => 'SchedulesController#create',
'as' => 'schedules.create'
]);
The Show route:
Route::get('schedules/{schedule}', [
'uses' => 'Schedules Controller#show',
'as' => 'schedules.show'
]);
I am also binding the model to the route:
Route::model('schedule', 'App\Schedule');
Any ideas why my create view is not showing correctly? The index view displays fine.
Turns out to solve this, I had to rearrange my crud routes.
Create needed to come before the Show route...
There's a package that does exactly what you want to do: https://github.com/balping/laravel-hashslug
Also note, that it's not a good idea to use APP_KEY as salt because it can be exposed.
Using the above package all you need to do is add a trait and typehint in controller:
class Post extends Model {
use HasHashSlug;
}
// routes/web.php
Route::resource('/posts', 'PostController');
// app/Http/Controllers/PostController.php
public function show(Post $post){
return view('post.show', compact('post'));
}

Categories