I'm developing a multi language website with Laravel 5.4 and I'm having trouble setting the middleware and the service provider logic. I already realized that servide provider is executed before the middleware. The problem is that I set the application locale on the middleware. On other hand, I have a service provider to feed localized data into the application's nav bar and footer. My problem is that by the time the service provider is executed, it doesn't know what the real locale is.
This is my handle function on Locale middleware
public function handle($request, Closure $next, $guard = null)
{
$locales_arr = Language::localesArr();
// if there isn't a slug on URL
if(!session()->has('app.locale') && $request->segment(1) === null)
{
// browser preference
$client_locale = substr(\Request::server('HTTP_ACCEPT_LANGUAGE'), 0, 2);
// if it is a valid locale
if(in_array($client_locale, $locales_arr))
{
$locale = $client_locale;
}
else {
$locale = config('app.fallback_locale');
}
}
else
{
$locale = in_array($request->segment(1), $locales_arr) ? $request->segment(1) : config('app.fallback_locale');
}
// set locale prefix
$locale_prefix = ($locale != config('app.fallback_locale')) ? $locale : '';
// set locale
app()->setLocale($locale);
config(['app.locale_prefix' => $locale_prefix]);
session(['app.locale' => $locale]);
session()->save();
setlocale(LC_TIME, $locale . "_" . strtoupper($locale));
return $next($request);
}
And this is my boot function on ViewComposerServiceProvider
public function boot()
{
/* this doesn't work properly!!! */
dump(app()->getLocale());
}
Can someone please help? I'm really stuck on this issue...
Related
I am trying to set up localization in a laravel app but my middleware seems to be causing an ERR_TOO_MANY_REDIRECTS error. The site is going to differ per region slightly and I am using the laravel lang/ files to swap out phone numbers etc. This works okay. When I change my locale I get the correct numbers from the lang files. And when I just have the middleware to check if the cookie has been set to set the locale that works too.
I also have middleware to forget the prefix on localised routes so I don't have to update my methods with a locale parameter.
My issue is setting a url prefix if a cookie has been set with an allowed locale in my middleware. I also want to ignore the prefix if the locale is 'en' as it will be default.
Below is the code for everything
routes/web.php
Route::get('/', 'HomeController#index')->name('home');
Route::get('/reader/{identifier}', 'ReaderController#show')->name('reader');
Route::get('/locale/{locale}', SetLocaleController::class)->name('set.locale');
Route::group(['prefix' => '{locale?}', 'where' => ['locale' => implode('|', array_keys(config('app.allowed_locales')))], 'middleware' => 'redirect.localization'], function () {
Route::get('/', 'HomeController#index')->name('home');
Route::middleware('forget.prefix')->get('/reader/{identifier}', 'ReaderController#show')->name('reader');
});
Middleware/Localization.php - This is added to the web middleware group in Kernel.php. It checks for a cookie and sets the locale and should redirect to the proper locale prefix. If no cookie is set then we get the users geo and set it.
public function handle($request, Closure $next)
{
// Get requested url
$segments = collect($request->segments());
if ($segments->count() && Arr::exists(config('app.allowed_locales'), $segments[0])) {
$segments->shift();
}
$locale = Cookie::get('locale');
if ($locale ?? false) {
// Set the app locale.
App::setLocale($locale);
if ($locale != 'en') {
$segments->prepend($locale);
return redirect()->to($segments->implode('/'));
}
} else {
// Check for geo here and set it.
}
return $next($request);
}
Middleware/RedirectLocalization.php - This middleware is only set on the route group for the prefix. It checks if the locale passed is allowed and the sets the locale and cookie. This is set in the $routeMiddleware array in Kernel.php
public function handle($request, Closure $next)
{
$locale = $request->segment(1);
if (Arr::exists(config('app.allowed_locales'), $locale)) {
// Set the app locale.
App::setLocale($locale);
// Save app locale in a Cookie.
Cookie::queue(Cookie::make('locale', $locale, 525600));
}
return $next($request);
}
Controllers/SetLocaleController.php - This is where the locale can be manually set from a menu on the site.
public function __invoke($locale, Request $request)
{
$redirectUrl = parse_url(url()->previous());
$segments = Str::of($redirectUrl['path'])->trim('/')->explode('/');
if (Arr::exists(config('app.allowed_locales'), $segments[0])) {
$segments->shift();
}
if (Arr::exists(config('app.allowed_locales'), $locale)) {
// Set the app locale.
App::setLocale($locale);
// Save app locale in a Cookie.
Cookie::queue(Cookie::make('locale', $locale, 525600));
// Add locale to segments for redirect.
if ($locale != 'en') {
$segments->prepend($locale);
}
} else {
// Set locale to current locale.
App::setLocale(config('app.fallback_locale'));
}
// Redirect back
return redirect()->to($segments->implode('/'));
}
Controllers/ReaderController.php - Nothing out of the ordinary here but I wanted to add it to explain the forget.prefix middleware. If I don't add the forget.prefix middleware then the $reader param becomes the locale.
public function show($reader, Request $request)
{
$reader = Reader::find($reader);
return view('readers.show', [
'reader' => $reader
]);
}
Middleware/ForgetPrefix.php - This middleware removes the prefix so we can access parameter in controller methods without having to add a $locale param to the method in the controller.
public function handle($request, Closure $next)
{
$request->route()->forgetParameter('locale');
return $next($request);
}
So my question is how can I set the URL prefix if the locale has been set in a cookie without getting the too many redirects error?
So I found that my issue was coming from having the Middleware/Localization.php added to the web middleware group. I added this middleware to $routeMiddleware in Kernel.php. Instead I only added the Middleware/Localization.php to the default locale routes.
My updated web/routes.php
Route::group(['middleware' => 'localization'], function () {
Route::get('/', 'HomeController#index')->name('home');
Route::get('/reader/{identifier}', 'ReaderController#show')->name('reader');
});
Route::group(['prefix' => '{locale?}', 'where' => ['locale' => implode('|', array_keys(config('app.allowed_locales')))], 'middleware' => 'redirect.localization'], function () {
Route::get('/', 'HomeController#index')->name('home');
Route::group(['middleware' => 'forget.prefix'], function () {
Route::get('/reader/{identifier}', 'ReaderController#show')->name('reader');
});
});
// Switch Locale route
Route::get('/locale/{locale}', SetLocaleController::class)->name('set.locale');
How do I change the language of each user? For example, some people don't change the language. Some people change the language.
Middleware :
use Closure;
use Auth;
class Localization
{
public function handle($request, Closure $next)
{
if(\Session::has('locale')) {
\App::setLocale(\Session::get('locale'));
}
return $next($request);
}
}
Save the locale for each user in database. This way you can override app's default locale to user's preferred locale in the middleware.
public function handle($request, Closure $next)
{
if($user = Auth::user()) {
App::setLocale($user->locale);
}
return $next($request);
}
If the your application doesn't require user to be authenticated, you can save locale in session for each user when the user change language.
Your method is right, no need to change the middleware. You can put the session on user with controller, like this below way.
Route :
Route::get('/lang',[
'uses' => 'HomeController#lang',
'as' => 'lang.index'
]);
Controller :
public function lang(Request $request)
{
$user = Auth::user()->id;
$locale = $request->language;
App::setLocale($locale);
session()->put('locale', $locale);
// if you want to save the locale on your user table, you can do it here
return redirect()->back();
}
Note : I added the GET method on route, so your URI will be like http://127.0.0.1:8000/lang?language=en, and o controller $request->language will catch the language parameter from your Request. You can modify and use POST method instead
I want to make condition on login. The system will have different types of users.
Example: admin, manager, user.
I have tried all solutions given but it redirects the user to the same page.
Database: Table - users : column: role.
The condition will be based on the role when they register.
Here is my code:
Logincontroller.php
protected $redirectTo = '/dashboard';
public function __construct() {
$this->middleware('guest')->except('logout');
$this->redirectTo = '/dashboard';
$user = \Auth::user();
if ( ($role->admin() ) {
// an admin
$this->redirectTo = '/admin';
} elseif ( ($role->manager() ) {
// it's a manager
$this->redirectTo = '/manager/home';
}
else {
// it's a user
$this->redirectTo = '/dashboard';
}
}
Thanks in advance!
You cannot add the redirect logic in the constructor because it will be called before the user authentication is even attempted, therefore it will always be "/dashboard".
You could add the authenticated() method to your LoginController, this method will be run after the user was successfully authenticated.
protected function authenticated(Request $request, User $user)
{
if ($user->admin()) {
// an admin
$redirect = '/admin';
} elseif ($user->manager()) {
// it's a manager
$redirect = '/manager/home';
} else {
// it's a user
$redirect = '/dashboard';
}
return redirect($redirect);
}
Why not go for custom middleware for authentication and filtering user
Middleware
register in kernel and then apply your logic in middleware handle function
And as per your logic ,kindly confirm if you are checking your role properly because this may cause it end up in else condition.
I suggest making an isAdmin() and isManager()in the user model.
Do something like this:
In App\User.php
const ADMIN_TYPE = 'admin';
const MANAGER_TYPE = 'manager';
public function isAdmin() {
return $this->role === self::ADMIN_TYPE;
}
public function isManager() {
return $this->role === self::MANAGER_TYPE;
}
In the LoginController
use AuthenticatesUsers;
protected function authenticated() {
if (auth()->user()->isAdmin()) {
// an admin
$this->redirectTo = '/admin';
}
}
Thank you everyone for the solutions. After trying your code i modify a bit and get the solutions (but might occur problem later).
use Illuminate\Http\Request;
protected function authenticated($request, $user)
{
if($user->position = 'manager') {
// change the redirectTo variable as needed
$this->redirectTo = ('/manager/home');
}
else //($user->position = 'admin')
{
// change the redirectTo variable as needed
$this->redirectTo = ('/admin/dashboard');
}
return redirect()->intended($this->redirectPath());
}
I am trying to override Laravel's default function for resetting passwords. Before I go ahead with resetting the password, I want to dynamically set $redirectTo based on certain user information, so that the user is properly redirected to a different page based on said info. However, when I go try to go through with it I end up at a completely white page with the path ".../password/reset". The user's password is being properly reset, but the redirect isn't working as intended.
My code is referenced below. What am I doing wrong?
ResetPasswordController.php
class ResetPasswordController extends Controller
{
use ResetsPasswords;
protected $redirectTo;
public function __construct()
{
$this->middleware('guest');
}
public function resetPasswordByUser(Request $request, Users $users)
{
$user = $users->findUserByEmail($request->input('email'));
if ($user->case == 1) {
$this->redirectTo = '/case1';
} elseif ($user->case == 2) {
$this->redirectTo = '/case2';
} elseif ($user->case == 3) {
$this->redirectTo = '/case3';
}
$this->reset($request);
}
}
web.php
Auth::routes();
.......
Route::post('/password/reset', 'Auth\ResetPasswordController#resetPasswordByUser');
I want to detect my client language by getting the browser recommended language.
For Example, if you open the browser in Japan it will give me country code or country name current user opened like "en-jp" or "japan".
I try this code but it seems to display the language that I previously selected and by default it's English.
I set a Middleware and I need to exclude the api part because I have some routers pinging this address and router browser does not have language information which brick the system.
class BeforeMiddleware
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
protected $except_urls = [
'api/*'
];
public function handle($request, Closure $next)
{
$langArr = array("en", "fr");
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$languages = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
} else {
$languages[0] = "en";
}
if (Session::has('locale')) {
App::setLocale(Session::get('locale'));
} else {
if (in_array($languages[0], $langArr))
App::setLocale($languages[0]);
}
return $next($request);
}
} /* end class */
Thank you for you help.
Or you can use Illuminate\Http\Request::getPreferredLanguage
Like this, in middleware:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Contracts\Session\Session;
use Illuminate\Http\Request;
class Locale {
const SESSION_KEY = 'locale';
const LOCALES = ['en', 'cs'];
public function handle(Request $request, Closure $next) {
/** #var Session $session */
$session = $request->getSession();
if (!$session->has(self::SESSION_KEY)) {
$session->put(self::SESSION_KEY, $request->getPreferredLanguage(self::LOCALES));
}
if ($request->has('lang')) {
$lang = $request->get('lang');
if (in_array($lang, self::LOCALES)) {
$session->put(self::SESSION_KEY, $lang);
}
}
app()->setLocale($session->get(self::SESSION_KEY));
return $next($request);
}
}
To simply get the locale from the header, you can grab the http-accept-language value from the request. This is accessible via a facade or you can use the request variable in your middleware:
Request::server('HTTP_ACCEPT_LANGUAGE')
// OR
$request->server('HTTP_ACCEPT_LANGUAGE');
This returns a string which looks like this: en-GB,en;q=0.8. You can then parse it (perhaps using explode()?) and grab the language from there.
However, this sort of thing can sometimes get complicated. If you need to do something more advanced, there's a package which can do all of this for you:
https://github.com/mcamara/laravel-localization
I just made a Middleware for this, it may be useful.
First you set $availableLangs the array of the available languages in your app, you may use config\app.php instead of initializing the array in the middleware as I did.
If the first language is available in the request language data, it sets the locale, if not, it will search the next one, and so on.
class GetRequestLanguage
{
public function handle($request, Closure $next)
{
if (Session::has('locale')) {
App::setLocale(Session::get('locale'));
} else {
$availableLangs = ['pt', 'en'];
$userLangs = preg_split('/,|;/', $request->server('HTTP_ACCEPT_LANGUAGE'));
foreach ($availableLangs as $lang) {
if(in_array($lang, $userLangs)) {
App::setLocale($lang);
Session::push('locale', $lang);
break;
}
}
}
return $next($request);
}
}
In a middleware do:
$acceptableLocales = config('app.available_locales');
$userLocales = $request->getLanguages();
if(!empty($userLocales)) {
foreach ($userLocales as $lang) {
$langToSearch = str_replace('_','-',$lang);
if(in_array($langToSearch, $acceptableLocales)) {
app('translator')->setLocale($langToSearch);
break;
}
}
}
return $next($request);
If you want to return the language in the response to the client, create another middleware and do:
/** #var Response $response */
$response = $next($request);
$response->header('Content-Language', app('translator')->getLocale());
return $response;
This is old question but i'll apply my anwser.
If you want to automatic localization use the bellow code in your Middleware
public function handle(Request $request, Closure $next)
{
$asTranslation = ['en','pt'];
$userLocales = $request->getLanguages();
$locale = Str::before($request->getPreferredLanguage($userLocales),'_');
//check if the locale is in the array of available locales
if (!in_array($locale, $asTranslation)) {
$locale = 'en';
}
//remove the brackets from the locale
app()->setLocale( Str::before($locale, '_'));
return $next($request);
}
I use a middleware too, but a lit bit smaller.
<?php
namespace Transfer\Http\Middleware;
use Closure;
class SetLanguage
{
public function handle($request, Closure $next)
{
$locale = $request->getLocale();
if ($request->session()->has('locale')) {
$locale = $request->session()->get('locale');
}
config(['app.locale' => $locale]);
return $next($request);
}
}
Detect language in Route and redirect to /en, /es, or ...
File web.php
use Illuminate\Support\Facades\Request;
Route::get('/', function () {
$lang = config('app.locale');
try {
$lang = Request::getPreferredLanguage(config('app.locales'));
} catch (\Exception $e) {
}
return redirect()->to($lang);
});
File config/app.php
'locale' => 'es',
'locales' => ['es','en'],
For Laravel 8 you can use $request->segment(1)
Example of Request:
Request::server('HTTP_ACCEPT_LANGUAGE') = en-DE,en-GB;q=0.9,en-US;q=0.8,en;q=0.7,de;q=0.6
Code:
$request->segment(1)
Result:
'en'
So easily set the locale: \App::setlocale($request->segment(1));