Modificar las rutas de login con otro idioma en Laravel

Spread the love

Hace unas semanas en un proyecto me pidieron tener las rutas para autentificar usuarios en ingles y español, y han de pensar que Laravel ya lo tiene resuelto, pero no.

Laravel es un framework increíblemente conveniente, en una linea de código agregas todo lo necesario para autenticar, recuperar contraseñas, verificar E-mails y hasta tener registro de usuarios.

El gran problema llega cuando quieres personalizar esto, requieres copiar y pegar de forma manual todas las rutas desde la clase Router.

Y para lo que requería (Traducir las rutas) termine con algo como esto:

Route::get(trans('routes.login'), 'Auth\LoginController@showLoginForm')->name('login');
Route::post(trans('routes.login'), 'Auth\LoginController@login');
Route::post(trans('routes.logout'), 'Auth\LoginController@logout')->name('logout');

Route::get(trans('routes.register'), 'Auth\RegisterController@showRegistrationForm')->name('register');
Route::post(trans('routes.register'), 'Auth\RegisterController@register');

Route::get(trans('routes.password.reset'), 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request');
Route::post(trans('routes.password.email'), 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');
Route::get(trans('routes.password.reset-token'), 'Auth\ResetPasswordController@showResetForm')->name('password.reset');
Route::post(trans('routes.password.reset'), 'Auth\ResetPasswordController@reset')->name('password.update');

Route::get(trans('routes.email.verify'), 'Auth\VerificationController@show')->name('verification.notice');
Route::get(trans('routes.email.verify-id'), 'Auth\VerificationController@verify')->name('verification.verify');
Route::get(trans('routes.email.resend'), 'Auth\VerificationController@resend')->name('verification.resend');

Hasta aquí; queda resuelto el problema. Pero ahora, para quitar por ejemplo el registro de usuarios tengo que borrar todas las rutas relacionadas.

Si quiero tener una funcionalidad similar a la de Auth::routes() tengo que agregar condiciones directamente sobre el archivo routes/web.php. y honestamente ver el archivos de rutas así, me recuerda al código espagueti.

Afortunadamente siempre existe otra manera y al final del día termine con algo como esto.

Route::localizeAuth();

¿Interesante? veamos como hacerlo.

Creando archivos de lenguaje para las rutas.

Esta es la parte mas sencillas aprovechando que Laravel ya tiene la funcionalidad de localización, lo único que hice fue crear un archivo routes.php. para cada idioma.

Es decir un archivo para resources/lang/en/router.php y otro para resources/lang/es/router.php. El contenido quedo de la siguiente forma.

//resources/lang/en/routers.php
return [

    'login' => 'login',
    'logout' => 'logout',
    'register' => 'register',

    'password' => [
        'reset' => 'password/reset',
        'email' => 'password/email',
        'reset-token' => 'password/reset/{token}',
    ],

    'email' => [
        'verify' => 'email/verify',
        'verify-id' => 'email/verify/{id}',
        'resend' => 'email/resend',
    ],
];

De esta forma podemos usar la función trans() para traducir las rutas.

trans('login') //'login'
trans('email.verify-id') //'email/verify/{id}'

Si el idioma no esta definido en el archivo config/app.php se utiliza el valor por defecto (Ingles).

Extendiendo el Facade Route

Para esta parte tenemos dos opciones, una es usar la herencia y agregar la funcionalidad requerida a la clase Illuminate\Routing\Router.

La otra opción es usar macros, una funcionalidad increíble que implementa laravel y con la cual podemos extender varias clases del Framework sin tener que usar la opción uno.

Decidí usar la segunda opción ya que no tengo que usar herencia para extender la clase Router.

Registra la macro

Las macros nos permiten agregar métodos a una clase de forma dinámica mediante el uso de closures y sin tener que extender la clase directamente mediante herencia.

Para que funcionen tenemos que agregar la macro en el RouteServiceProvider en el boot . Esto es para que la macro se registre y pueda usar el helper trans()

/**
     * Define your route model bindings, pattern filters, etc.
     *
     * @return void
     */
    public function boot()
    {
        Route::macro('localizeAuth', function ($options = []){
            // Authentication Routes...
            Route::get(trans('routes.login'), 'Auth\LoginController@showLoginForm')->name('login');
            Route::post(trans('routes.login'), 'Auth\LoginController@login');
            Route::post(trans('routes.logout'), 'Auth\LoginController@logout')->name('logout');

            // Registration Routes...
            if ($options['register'] ?? true) {
                Route::get(trans('routes.register'), 'Auth\RegisterController@showRegistrationForm')->name('register');
                Route::post(trans('routes.register'), 'Auth\RegisterController@register');
            }

            // Password Reset Routes...
            if ($options['reset'] ?? true) {
                Route::get(trans('routes.password.reset'), 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request');
                Route::post(trans('routes.password.email'), 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');
                Route::get(trans('routes.password.reset-token'), 'Auth\ResetPasswordController@showResetForm')->name('password.reset');
                Route::post(trans('routes.password.reset'), 'Auth\ResetPasswordController@reset')->name('password.update');
            }

            // Email Verification Routes...
            if ($options['verify'] ?? false) {
                Route::get(trans('routes.email.verify'), 'Auth\VerificationController@show')->name('verification.notice');
                Route::get(trans('routes.email.verify-id'), 'Auth\VerificationController@verify')->name('verification.verify');
                Route::get(trans('routes.email.resend'), 'Auth\VerificationController@resend')->name('verification.resend');
            }
        });
        parent::boot();
    }

Finalmente solo tienes que llamar a la macro en el archivo routes/web.php

//llamada simple
Route::localizeAuth();

//llama con prefijo
Route::prefix('administracion')->group(function(){
    Route::localizeAuth();
});

//Si no queremos registro de usuarios
Route::localizeAuth(['register' => false]);

Mejorando la solución

Hasta aquí puedes dejar la macro y todo debe de funcionar sin problemas, pero si eres de los que busca un paso mas para acomodar mejor el código sigue leyendo.

Creación de servicio para registrar la macro

Generalmente no me gusta dejar las macros como esta en el código anterior, debido a que suelen ser poco legibles y generan mucho espacio cuando tienes muchas lineas de código.

Así que lo que hago es crear una clase que sirve como servicio para contener toda la lógica de la macro.

namespace App\Macros;

use Illuminate\Routing\Router;
use Illuminate\Support\Facades\Route;

/**
 * Class AuthRouteService
 *
 * @package App\Macros
 */
class AuthRouteService
{
    /**
     * Register authentication routes
     */
    public function registerMacro() : void
    {
        $authRoute = $this;
        Router::macro('localizeAuth', function ($options = []) use($authRoute){

            // Authentication Routes...
            $authRoute->login();

            // Registration Routes...
            if ($options['register'] ?? true) {
                $authRoute->registration();
            }

            // Password Reset Routes...
            if ($options['reset'] ?? true) {
                $authRoute->resetPassword();
            }

            // Email Verification Routes...
            if ($options['verify'] ?? false) {
                $authRoute->emailVerification();
            }
        });
    }

    /**
     * Add login routes
     */
    public function login() : void
    {
        Route::get(trans('routes.login'), 'Auth\LoginController@showLoginForm')->name('login');
        Route::post(trans('routes.login'), 'Auth\LoginController@login');
        Route::post(trans('routes.logout'), 'Auth\LoginController@logout')->name('logout');
    }

    /**
     * Add registration routes
     */
    public function registration() : void
    {
        Route::get(trans('routes.register'), 'Auth\RegisterController@showRegistrationForm')->name('register');
        Route::post(trans('routes.register'), 'Auth\RegisterController@register');
    }

    /**
     * Add password reset routes
     */
    public function resetPassword() : void
    {
        Route::get(trans('routes.password.reset'), 'Auth\ForgotPasswordController@showLinkRequestForm')->name('password.request');
        Route::post(trans('routes.password.email'), 'Auth\ForgotPasswordController@sendResetLinkEmail')->name('password.email');
        Route::get(trans('routes.password.reset-token'), 'Auth\ResetPasswordController@showResetForm')->name('password.reset');
        Route::post(trans('routes.password.reset'), 'Auth\ResetPasswordController@reset')->name('password.update');
    }

    /**
     * Add email verification routes
     */
    public function emailVerification() : void
    {
        Route::get(trans('routes.email.verify'), 'Auth\VerificationController@show')->name('verification.notice');
        Route::get(trans('routes.email.verify-id'), 'Auth\VerificationController@verify')->name('verification.verify');
        Route::get(trans('routes.email.resend'), 'Auth\VerificationController@resend')->name('verification.resend');
    }
}

Lo único que hace el servicio es separar la rutas usando método que son llamados al momento de crear la macro.

Esta clase la registro en el AppServiceContainer

public function register()
    {
        $this->app->singleton(AuthRouteService::class, function ($app) {
            return new AuthRouteService();
        });
    }

Posterior a eso, solo tienes que llamar la clase en el RouteServiceProvider para registrar la macro.

    public function boot()
    {
        resolve(AuthRouteService::class)->registerMacro();
    }

Lo demás permanece sin cambios.

Conclusión

Hemos visto que Laravel a pesar de ser muy conveniente no resuelve todos nuestros problemas, pero su diseño es una maravilla, porque te permite extender de forma muy sencilla para adaptarlo a tus necesidades en la mayoría de los casos.

Esta idea se puede mejorar para que soporte mas de un lenguaje, no me he puesto en ello todavía pero debe de ser posible.

Espero que les sea de utilidad este tip y por favor déjame saber que opinas dejando un mensaje y comparte este articulo!.

Recuerda el código que no tiene pruebas no existe!.