Desarrollo Web 5-8 minutos

Localización en Laravel 13: traducciones, rutas bilingües y multi-idioma paso a paso

Diego Cortés
Diego Cortés
Full Stack Developer & SEO Specialist
Compartir:
Localización en Laravel 13: traducciones, rutas bilingües y multi-idioma paso a paso

Un blog bilingüe multiplica su audiencia, y Laravel 13 trae la localización de serie con dos formas de guardar traducciones. Este mismo sitio ES/EN es la prueba de que funciona: te enseño el flujo completo de i18n, del helper __() a las rutas /es y /en.

Qué es la localización en Laravel 13 y por dónde empezar

La localización en Laravel 13 es el sistema del framework para mostrar strings en varios idiomas sin tocar la lógica de la aplicación. En lugar de escribir el texto fijo en las vistas, usas claves que se resuelven al idioma activo en cada petición. Es la base de cualquier proyecto multi-idioma, y Laravel 13 lo mantiene estable en el núcleo, presentado dentro de un stack pensado "para artesanos y agentes".

Las dos formas de guardar traducciones: archivos lang y JSON

Laravel ofrece dos mecanismos complementarios. El primero son archivos de idioma con claves: un directorio por idioma y un archivo por dominio (por ejemplo, lang/es/messages.php), donde cada clave devuelve su texto. El segundo son los JSON translations: un único lang/es.json que mapea el string original a su traducción, ideal para strings sueltas que no tienen clave propia.

Publicar el directorio lang con artisan

El skeleton de Laravel 13 no incluye el directorio lang por defecto, así que el primer paso es generarlo. Ejecuta php artisan lang:publish y el framework creará la estructura base con lang/en.json y un archivo de ejemplo por idioma. A partir de ahí, añades tus idiomas: para español, crea lang/es.json y el directorio lang/es/.

Primeras traducciones con el helper __()

El helper __() es la puerta de entrada a la localización: recupera una traducción por su clave teniendo en cuenta el locale activo.

Claves, archivos por idioma y fallback

En un archivo con claves defines pares clave-valor: 'welcome' => 'Bienvenido'. En la vista usas __('messages.welcome'), donde messages es el archivo y welcome la clave. El framework resuelve la clave en el idioma de la petición y, si no existe, usa el locale de fallback definido en config/app.php. En los JSON, la clave es el propio texto original: __('Hello') busca "Hello" en lang/es.json.

Parámetros y placeholders (:name)

Las traducciones rara vez son estáticas, y por eso el helper soporta placeholders. Define 'welcome_user' => 'Bienvenido, :name' y llámalo con __('messages.welcome_user', ['name' => $user->name]). El placeholder se sustituye en tiempo de ejecución, así que la misma clave sirve para cualquier usuario.

Pluralización con {0}, {1} y [2,*]

El plural en español e inglés no sigue la misma regla, y Laravel lo resuelve con rangos. Define 'apples' => '{0} No hay manzanas|{1} Hay una manzana|[2,*] Hay :count manzanas' y usa trans_choice('messages.apples', $count, ['count' => $count]). Cada idioma declara sus propios rangos, así que el plural es correcto en todos ellos.

JSON translations: strings sueltas y paquetes

Los JSON translations entran en juego cuando no controlas la clave, sobre todo con strings de paquetes o librerías de terceros. Cualquier cadena que el framework no encuentre como clave la busca en lang/{locale}.json. Esto permite traducir textos como "Login" o "Reset Password" de vendor sin tocar su código: añades la entrada en el JSON y el paquete ya muestra tu traducción. Es también la vía rápida para traducir el propio core de Laravel si usas un paquete de idiomas.

Cambiar el idioma: middleware y app()->setLocale()

Con las traducciones listas, falta decidir cómo se elige el idioma en cada petición. La pieza clave es app()->setLocale(), que fija el locale activo, y el lugar correcto para llamarlo es un middleware.

Por qué el middleware y no el service provider

El AppServiceProvider se ejecuta antes de que la sesión esté disponible y antes de que exista el usuario autenticado, así que no puedes leer ahí el idioma guardado. Un middleware, en cambio, corre después de que la sesión se haya iniciado y puede leer el locale de la sesión, de la URL o del usuario. Es el patrón estándar en aplicaciones Laravel multi-idioma: el middleware decide y el resto de la app solo consume el locale ya fijado.

Detección del idioma del navegador (Accept-Language)

Si quieres adivinar el idioma del visitante, lee la cabecera Accept-Language de la petición dentro del middleware y escoge el primer idioma que soporte tu app. Es un buen punto de partida para la primera visita, combinado con un selector de idioma visible que guarde la elección en sesión para las siguientes.

Rutas localizadas: /es y /en

Para un blog o una web de contenido, la URL debe reflejar el idioma. Las rutas localizadas con prefijo son la opción recomendada, también desde el punto de vista SEO.

Grupo de rutas con prefijo {locale}

Agrupa tus rutas bajo un prefijo dinámico y valídalo: Route::prefix('{locale}')->where('locale', 'en|es')->group(function () { ... }). Dentro del grupo, un middleware lee el parámetro {locale} y llama a app()->setLocale($locale). La validación con where evita que rutas inexistentes caigan en el grupo y permite tener rutas sin prefijo (por ejemplo, una redirección a la home localizada) fuera.

Alternativa: mcamara/laravel-localization

Si no quieres montar el mecanismo a mano, el paquete mcamara/laravel-localization añade detección del idioma del navegador, smart routing (defines las rutas una sola vez y las sirve en todos los idiomas) y middleware de prefijo de locale. Es la opción más usada en producción y acelera la configuración inicial.

Generar URLs con el prefijo activo

Al usar prefijos, los enlaces deben generarse con el idioma correcto. Con el grupo {locale}, pasa el locale como parámetro: route('home', ['locale' => app()->getLocale()]). Con mcamara, el helper localized_route() hace el trabajo. Un selector de idioma en Blade es entonces un par de enlaces a la misma ruta con distinto prefijo.

Traducciones dinámicas en base de datos con spatie/laravel-translation-loader

Los archivos lang exigen un despliegue para cada cambio de texto, y en un blog no siempre es aceptable. El paquete spatie/laravel-translation-loader guarda las traducciones en base de datos con caché, de modo que un editor puede cambiar un texto sin tocar código. Registras el loader como driver de traducción y el helper __() sigue funcionando igual: solo cambia el origen de los datos. Es la vía natural cuando el contenido lo gestiona un equipo no técnico.

Acelerar con laravel-lang/lang: traducciones listas

No tiene sentido traducir a mano las claves del core. El paquete laravel-lang/lang ofrece traducciones listas para decenas de idiomas, incluyendo auth, pagination, validation y el resto de dominios de Laravel. Lo instalas, publicas los idiomas que necesites y tu app ya muestra mensajes de validación y paginación correctamente traducidos, dejando tu equipo libre para centrarse en el contenido propio.

SEO multi-idioma: hreflang, canonical y sitemap

Publicar en dos idiomas sin preparar el SEO provoca contenido duplicado a ojos de Google. La solución tiene tres patas. Primero, cada página declara sus alternativas con etiquetas link rel="alternate" hreflang="es" y hreflang="en", además de hreflang="x-default". Segundo, la canonical de cada versión apunta a su propia URL localizada, no a la versión sin prefijo. Tercero, el sitemap incluye cada URL con su idioma como entradas independientes. Con esas tres piezas, Google entiende que son versiones del mismo contenido y no duplicados, y muestra a cada usuario su idioma.

Conclusión

La localización en Laravel 13 es madura y resolvible solo con el núcleo: archivos lang o JSON, helper __(), un middleware que fije el locale y rutas con prefijo /es y /en. Los paquetes de Spatie, mcamara y laravel-lang cubren los casos avanzados, y el bloque hreflang-canonical-sitemap cierra el círculo para el SEO. Este blog ES/EN es el ejemplo de que el flujo funciona en producción. Sigue leyendo para más guías de desarrollo web con Laravel.

Categorías