Laravel 8 route() does not localize strings - laravel

EDIT: found the answer to all my questions: https://github.com/codezero-be/laravel-localized-routes (and, of course, 42).
I'm working on a multi-language website where the URL's contain localized strings. For instance:
https://site/en/members/john-doe
https://site/nl/leden/john-doe
(and so for every language)
In web.php, I prefixed my routes. Via middleware, I set the current language (retrieved from the first URL-segment). This works fine for everything (both in controllers and in blade)... except for the URLs generated by route(). They are always in the default language. Even though everything else is correctly localized. If I dump app()->getLocale() in a controller of blade it shows the correct language. Only the URLs generated by the route()-function are always in the default fallback language.
I've tried moving my middleware-class higher up in the middleware-groups list. That didn't make a difference.
web.php
Route::prefix('{lang}')->group(function() {
Route::get(trans('routes.members') . '/{username}/', 'MembersController#profile')
->name('members.show');
}
SetLanguage middleware
...
public function handle(Request $request, Closure $next)
{
$lang = $request->segment(1) ?? app()->getLocale();
//set app language
app()->setLocale($lang);
//set default url parameter
URL::defaults(['lang' => $lang]);
return $next($request);
}
...
App\Http\Kernel.php
...
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Laravel\Jetstream\Http\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\App\Http\Middleware\SetLanguage::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\UserLastSeenAt::class,
],
...

Got it (with a little help from a friend)
In the AppServiceProvider's boot function I've added
public function boot(Request $request)
{
//set app language
app()->setLocale($request->segment(1) ?? app()->getLocale());
}
& the SetLanguage-middleware now only contains
public function handle(Request $request, Closure $next)
{
//set default url parameter
URL::defaults(['lang' => app()->getLocale()]);
return $next($request);
}
That did the trick!

Great! I've done the same as you, but i have a problem when i chose to change the lang from switcher.
The switcher in blade:
<a class="ml-1 underline ml-2 mr-2" href="{{ route('change-lang')}}">
<span>{{ $locale_name }}</span>
</a>
The target route:
Route::get('/lang', [LangController::class, 'changeLang'])->name('change-lang');
The function in LangController:
public function changeLang($locale){
App::setLocale($locale);
session()->put('locale', $locale);
return redirect()->back();
}
Let me Exaplain:
If i navigate, for example, in about section and my url is "webname/it/azienda" and i switch language in EN my url becomes "webname/it/en";
I don't have issues if i change language if I have "webname/azienda" or "/" for home page.
I cannot change only the parameters LOCALE without adding it.

Related

Localization in laravel

I designed a site with Laravel. now I want add new language to it.I read laravel document . It was good but I have a problem.suppose I have a page that show detail of products so I have a route like mysite.com/product/id that get product's id and show it.also I have a method in controller like
public function showProduct($id){
...
}
If I add new Language , the route will change to this: mysite/en/product/id
now I must change my method because now two parameter send my method.something like this :
public function showProduct($lang,$id){
...
}
So two problems arise:
I must change all method in my site which is time consuming
I do not need language parameter in methods because I set $locan via middleware
pay attention that I do not want remove for example en from my URL (because of SEO)
Open your RouteServiceProvider and say that language parameter actually is not a parameter, it's a global prefix.
protected function mapWebRoutes()
{
Route::group([
'middleware' => 'web',
'namespace' => $this->namespace,
'prefix' => Request::segment(1) // but also you need a middleware about that for making controls..
], function ($router) {
require base_path('routes/web.php');
});
}
here is sample language middleware, but it's need to be improve
public function handle($request, Closure $next)
{
$langSegment = $request->segment(1);
// no need for admin side right ?
if ($langSegment === "admin")
return $next($request);
// if it's home page, get language but if it's not supported, then fallback locale gonna run
if (is_null($langSegment)) {
app()->setLocale($request->getPreferredLanguage((config("app.locales"))));
return $next($request);
}
// if first segment is language parameter then go on
if (strlen($langSegment) == 2)
return $next($request);
else
// if it's not, then you may want to add locale language parameter or you may want to abort 404
return redirect(url(config("app.locale") . "/" . implode($request->segments())));
}
So in your controller, or in your routes. you don't have deal with language parameter
Something like
Route::group(['prefix' => 'en'], function () {
App::setLocale('en');
//Same routes pointing to the same methods...
});
Or
Route::group(['prefix' => 'en', 'middleware' => 'yourMiddleware'], function () {
//Same routes pointing to the same methods...
});

Laravel policies strangely not working

The point is simple: I have a UserPolicy method that checks if a user wants to edit his/her own profile. So I did like this:
public function update(User $user, User $model)
{
return $user->id === $model->id;
}
And this is called in a UserController as it follows:
public function edit(User $user)
{
$this->authorize('update', $user);
return view('users.edit')->with('user', $user);
}
Everything is the same in a PostController and a PostPolicy, meant to check if a user can edit his/her own post and it works. The only difference is in their signature, since one has two users (the first one is the currently authenticated user injected by Laravel and the other is the instance I want to check it with) and the other has the above seen automatically injected authenticated user and a post instance. Anyway, it throws:
Symfony \ Component \ HttpKernel \ Exception \ AccessDeniedHttpException
This action is unauthorized.
I tried to dd($model) but I got the same exception.
Why? Thanks in advance!
EDIT
In my AuthServiceProvider is all set up, too:
protected $policies = [
// 'App\Model' => 'App\Policies\ModelPolicy',
Post::class => PostPolicy::class,
User::class => UserPolicy::class,
];
And so is my routes.php:
// Authentication Routes...
$this->post('login', 'Auth\LoginController#login')->name('login');
$this->post('logout', 'Auth\LoginController#logout')->name('logout');
// Registration Routes...
$this->post('register', 'Auth\RegisterController#register')->name('register');
// Password Reset Routes...
$this->get('password/reset', 'Auth\ForgotPasswordController#showLinkRequestForm')->name('password.request');
$this->post('password/email', 'Auth\ForgotPasswordController#sendResetLinkEmail')->name('password.email');
$this->get('password/reset/{token}', 'Auth\ResetPasswordController#showResetForm')->name('password.reset');
$this->post('password/reset', 'Auth\ResetPasswordController#reset');
Route::get('/', 'HomeController#index')->name('home');
Route::resource('posts', 'PostController');
Route::resource('users', 'UserController')->except('index', 'create', 'store');
Everything above is called right here:
#if ($user->id == Auth::id())
<a class="btn btn-link float-right p-0"
href="{{ route('users.edit', Auth::id()) }}">
<i class="fas fa-cog"></i>
Edit profile
</a>
<br><br><br>
#endif
I'm giving an answer myself: I tried to write the model and policy's full paths instead of registering the policies by the classes' names and it works (I don't know why, of course).
I did like this:
protected $policies = [
// 'App\Model' => 'App\Policies\ModelPolicy',
'App\User' => 'App\Policies\UserPolicy',
'App\Post' => 'App\Policies\PostPolicy',
];
Anyway, thanks everyone for trying to help me. Hope it will help someone else one day!
I just solved the same issue after fighting a whole day. Using full paths for register did not work for me. I fixed it by modifying my routes. I post my solution here hoping it may help someone someday.
If your routes are not protected by the authentication middleware, an AccessDeniedException will be thrown before applying your policies. The reason is that if your request comes in directly, you will never be treated as a logged-in user, so that you will be kicked off when trying to call $this->authorize('update') within the controller.
Route::middleware("auth:sanctum")->group(function () {
Route::post('/member/{id}', [MembersController::class, 'update']);
// ... and other path.
});

Laravel default locale not in url

Suppose I have 3 (or more languages) on site. English, Italian, French. English being the default.
I want the homepage url to be:
mysite.com/ - for english
mysite.com/it - for italian
mysite.com/fr - for french
My route is currently
Route::get('/{locale}', 'HomeController#index')->name('home');
This works for French, Italian but obviously not for English being just mysite.com/
I dont want another route like
Route::get('/', 'HomeController#index')
Because then I wouldn be able to simply call home in any language as
{{ route('home', $locale) }}
Whats the best solution?
One of my old solutions but still should work:
In the beginning of routes.php
$locale = Request::segment(1);
if(in_array($locale, ['en','fr','it'])){
app()->setLocale($locale);
}else{
app()->setLocale('en');
$locale = '';
}
Then
Route::group([
'prefix' => $locale
], function(){
Route::get('/demo', 'TestController#demo')->name('demo');
...
})
Important: You should always use named routes in this scenario. The generated URLs will then be for the current locale:
route('demo') will return /demo when app locale is english, and /it/demo when app locale is italian!
Laravel allows optional parameters in route definition, so you can set local route parameter as optional for your usage :
Route::get('/{locale?}', 'HomeController#index')->name('home');
And in your HomeController, check the parameter to know if locale is present
public function index(Request $request, $locale = null) {
if (empty($locale)) {
$locale = 'en'; // english by default
}
...
}
Hope it's helps you :)

Migration to Laravel, Vanity URLs, middleware for all requests, Auth::user and $errors issue

I created a middleware class named PathParser which runs on every request. The purpose is to handle requests for "vanity URL paths" that we allowed users to create in our pre-Laravel vanilla PHP app. For example: A user created a URL path such as: http://example.com/i-love-this-place
What PathParser does is to check for 404 responses, and then look to see if the URL path matched one of our old vanity paths. Like this:
class PathParser
{
public function handle($request, Closure $next, $guard = null)
{
$next_response = $next($request);
$status_code = $next_response->getStatusCode();
if ($status_code === 404) {
$script_url = $request->server("SCRIPT_URL");
$vanity_controller = new VanityController();
$found_vanity_id = Search::findVanityPath($script_url);
if (!empty($found_vanity_id)) {
$next_response = response()->make($vanity_controller->one($found_vanity_id));
}
}
return $next_response;
}
}
Assume the following:
A user has never created a URL path that would conflict with any route
I must support a number of existing (pre-Laravel) vanity URL paths that are out in the wild -- posted to social media and so on.
In Kernel.php, I have the following:
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\App\Http\Middleware\PathParser::class,
//\Illuminate\Session\Middleware\StartSession::class,
//\Illuminate\View\Middleware\ShareErrorsFromSession::class,
];
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
In the $middleware array I tried added StartSession and ShareErrorsFromSession (uncommenting the 2 lines above), and this works partially. But with two major problems:
Auth::user is null, even for requests made to vanity paths by logged in users
$errors no longer gets populated on form submission (such as on the registration and login pages) when a user submits incorrect/invalid information
Is there a way both to check the route on all requests and get ahold of the authenticated user, also preserving $errors?
I have a feeling I don't understand the request lifecycle well enough to succeed. But maybe there is a way?
If it's not possible to do what I require, then use of 302 redirection to a standardized prefixed path (such as http://example.com/vanity/i-love-this-place) is a fine solution. But I'm hoping there is another means.
A couple suggestions:
If you don't need auth/sessions/etc, then you can just handle the Symfony\Component\HttpKernel\Exception\NotFoundHttpException exception inside your application's exception handler.
In app/Exceptions/Handler.php, modify the render() method to look something like:
public function render($request, Exception $e)
{
if ($e instanceof \Symfony\Component\HttpKernel\Exception\NotFoundHttpException) {
// your code that returns a Response
}
return parent::render($request, Exception $e);
}
If you do need auth/sessions/etc, I would suggest creating a "catchall" route at the end of your routes file. For example, as the very last line in your routes/web.php file, put:
Route::any('{catchall}', 'VanityController#handle')->where('catchall', '(.*)');
Then, inside your VanityController, have a handle method that looks like:
public function handle(Request $request, $url)
{
// logic to search and render your vanity page for $url
// if no vanity page was found:
throw new \Symfony\Component\HttpKernel\Exception\NotFoundHttpException();
}

Laravel 5 multi language not working

I tried to localization my app, but seems like I'm missing something. I don't have any previous knowledge about this and therefore it's pretty hard to get started. Here is my routes.php
Route::get('/lang/{lang}', 'LangController#index');
And here is my LangController.php
public function index($lang)
{
$langs =['en', 'de'];
if(in_array($lang, $langs)){
Session:set('lang', $lang);
return Redirect::back();
}
}
I set in middleware:(Lang.php)
public function handle($request, Closure $next)
{
if($lang = Session::get('lang')){
\Lang::setLocale($lang);
}
return $next($request);
}
Enable it in Http\Kernel.php:
protected $middleware = [
\App\Http\Middleware\Lang::class,
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
];
In my blade.php
{{ Lang::get('home.hello')}}
CURRENT: {{ Lang::getLocale() }} <br /> <br />
Germany | English
Please help.I do not see what I missing...
In field CURRENT when press Germany it shoul be 'de' and when press English it shoul be 'en' but when press Germany it still stay 'en'... (default is 'en' config/app.php -> 'locale' => 'en',)
Because you are using the session in your middleware, the values you need will not be available until the StartSession middleware sets up the session.
So you should add your middleware somewhere after that, like so:
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class, // Init session
\App\Http\Middleware\Lang::class, // Set locale
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
];
Aside from that you also have a small syntax error in your controller code. You forgot to add the second colon (:) for the scope resolution operator, when assigning the value to the session in your index controller method. So this:
Session:set('lang', $lang);
Should be this:
Session::set('lang', $lang);

Resources