Curso de Laravel

Traducir los mensajes de Laravel al español

Por Víctor Peña · Publicado el

Hola, ¿cómo están? Ya tenemos un CRUD completo y funcionando, pero cuando la validación falla el usuario lee «The cedula field is required».

Hoy lo ponemos todo en español.

Parece un detalle menor, pero es de las primeras cosas que nota quien usa el sistema, y arreglarlo cuesta diez minutos.

¡Empecemos!

Publicar los archivos de idioma

En las versiones actuales de Laravel la carpeta lang/ no viene incluida. Se genera con:

php artisan lang:publish

Eso crea lang/en/ con cuatro archivos:

lang/
└── en/
    ├── auth.php          mensajes de autenticación
    ├── pagination.php    "Previous" / "Next"
    ├── passwords.php     recuperación de contraseña
    └── validation.php    todos los mensajes de validación

Crear la carpeta en español

cp -r lang/en lang/es

En Windows, copia y pega la carpeta y renómbrala a es.

Cambiar el idioma de la aplicación

En el archivo .env:

APP_LOCALE=es
APP_FALLBACK_LOCALE=es
APP_FAKER_LOCALE=es_ES

Ese APP_FAKER_LOCALE hace que los factories generen nombres y direcciones en español, que para datos de prueba se agradece bastante.

Y después:

php artisan config:clear

Ese paso se olvida constantemente. Si cambiaste el .env y sigues viendo inglés, casi siempre es la configuración en caché.

Traducir validation.php

Es el archivo importante. Abre lang/es/validation.php y traduce las reglas que uses:

return [
    'required' => 'El campo :attribute es obligatorio.',
    'email' => 'El campo :attribute debe ser un correo electrónico válido.',
    'unique' => 'El :attribute ya está registrado.',
    'confirmed' => 'La confirmación de :attribute no coincide.',
    'numeric' => 'El campo :attribute debe ser un número.',
    'integer' => 'El campo :attribute debe ser un número entero.',
    'date' => 'El campo :attribute no es una fecha válida.',
    'before' => 'El campo :attribute debe ser una fecha anterior a :date.',
    'after' => 'El campo :attribute debe ser una fecha posterior a :date.',
    'exists' => 'El :attribute seleccionado no es válido.',
    'image' => 'El campo :attribute debe ser una imagen.',
    'in' => 'El :attribute seleccionado no es válido.',

    'max' => [
        'numeric' => 'El campo :attribute no debe ser mayor que :max.',
        'file' => 'El archivo :attribute no debe pesar más de :max kilobytes.',
        'string' => 'El campo :attribute no debe tener más de :max caracteres.',
        'array' => 'El campo :attribute no debe tener más de :max elementos.',
    ],

    'min' => [
        'numeric' => 'El campo :attribute debe ser al menos :min.',
        'file' => 'El archivo :attribute debe pesar al menos :min kilobytes.',
        'string' => 'El campo :attribute debe tener al menos :min caracteres.',
        'array' => 'El campo :attribute debe tener al menos :min elementos.',
    ],
];

Fíjate en que max y min tienen cuatro variantes. Laravel elige la correcta según el tipo del campo, y por eso el mensaje de un archivo habla de kilobytes y el de un texto de caracteres.

Los :attribute, :max, :date son marcadores que se reemplazan al mostrar el mensaje.

Existen traducciones completas ya hechas en internet, pero conviene revisarlas: muchas usan un español neutro que suena raro en un sistema de gestión.

Nombres de campos: el arreglo attributes

Sin esto, los mensajes quedan a medias: «El campo fecha_nacimiento es obligatorio».

Al final de validation.php hay un arreglo attributes vacío. Ahí se define cómo se llama cada campo para el usuario:

'attributes' => [
    'cedula' => 'cédula',
    'nombre' => 'nombre',
    'apellido' => 'apellido',
    'fecha_nacimiento' => 'fecha de nacimiento',
    'correo' => 'correo electrónico',
    'telefono' => 'teléfono',
    'doctor_id' => 'doctor',
    'password' => 'contraseña',
],

Y el mensaje pasa a ser: «El campo fecha de nacimiento es obligatorio».

Este arreglo es el que más diferencia hace y el que más se ignora. Con las reglas traducidas pero sin los nombres, el sistema sigue viéndose descuidado.

Ese doctor_id es un buen ejemplo: el usuario no tiene por qué saber que hay una columna llamada así.

Mensajes por campo y regla

Para casos concretos, dentro del arreglo custom:

'custom' => [
    'cedula' => [
        'unique' => 'Ya existe un paciente registrado con esa cédula.',
        'required' => 'Necesitamos la cédula para registrar al paciente.',
    ],
    'fecha_nacimiento' => [
        'before' => 'La fecha de nacimiento no puede ser futura.',
    ],
],

Esto tiene prioridad sobre el mensaje general. Sirve para las reglas donde el texto genérico no explica bien qué pasó.

También se puede hacer desde el Form Request con el método messages(). La diferencia práctica: en validation.php los mensajes están centralizados; en el Form Request quedan junto a las reglas. Para reglas de negocio concretas, prefiero el Form Request; para textos que se repiten en todo el sistema, el archivo de idioma.

Traducir la paginación

lang/es/pagination.php:

return [
    'previous' => '« Anterior',
    'next' => 'Siguiente »',
];

Aparece automáticamente en el {{ $pacientes->links() }} que vimos en la lección de listado.

Traducir la autenticación

lang/es/auth.php:

return [
    'failed' => 'Las credenciales no coinciden con nuestros registros.',
    'password' => 'La contraseña es incorrecta.',
    'throttle' => 'Demasiados intentos de acceso. Inténtalo de nuevo en :seconds segundos.',
];

Y lang/es/passwords.php:

return [
    'reset' => 'Tu contraseña ha sido restablecida.',
    'sent' => 'Te hemos enviado un correo con el enlace para restablecer tu contraseña.',
    'throttled' => 'Espera un momento antes de volver a intentarlo.',
    'token' => 'Este enlace de recuperación no es válido.',
    'user' => 'No encontramos ningún usuario con ese correo electrónico.',
];

Fechas en español

Los mensajes ya están traducidos, pero las fechas siguen mostrando «Monday» y «January».

Laravel usa Carbon para las fechas, y hay que configurarlo aparte. En app/Providers/AppServiceProvider.php:

use Carbon\Carbon;

public function boot(): void
{
    Carbon::setLocale('es');
}

Y ahora:

{{ $cita->fecha->translatedFormat('l d \d\e F \d\e Y') }}
{{-- lunes 24 de mayo de 2026 --}}

{{ $paciente->created_at->diffForHumans() }}
{{-- hace 3 días --}}

translatedFormat() y no format(). El format() normal siempre devuelve los nombres en inglés; es el segundo tropiezo clásico después del config:clear.

Si quieres un formato fijo en todo el sistema, un accesor en el modelo evita repetirlo:

protected function fechaFormateada(): Attribute
{
    return Attribute::get(
        fn () => $this->fecha->translatedFormat('d \d\e F \d\e Y')
    );
}
{{ $cita->fecha_formateada }}

Traducir tus propios textos

El mismo sistema sirve para los textos de tu aplicación. Crea lang/es/mensajes.php:

return [
    'guardado' => 'Los datos se guardaron correctamente.',
    'eliminado' => 'El registro fue eliminado.',
    'sin_permiso' => 'No tienes permiso para realizar esta acción.',
    'bienvenida' => 'Bienvenido, :nombre.',
];

Y se usan con la función __():

return redirect()->route('pacientes.index')
    ->with('exito', __('mensajes.guardado'));
<p>{{ __('mensajes.bienvenida', ['nombre' => auth()->user()->name]) }}</p>

¿Vale la pena si el sistema solo va a estar en español? En muchos proyectos, no: escribir el texto directamente es más simple y más legible.

Donde sí conviene es cuando el mismo mensaje aparece en varios sitios, o cuando existe la posibilidad real de añadir otro idioma. Si el sistema es de un solo idioma y no va a cambiar, no te compliques.

Plurales

Cuando el texto depende de una cantidad:

'citas' => '{0} Sin citas|{1} Una cita|[2,*] :count citas',
{{ trans_choice('mensajes.citas', $paciente->citas_count) }}

Con 0 muestra «Sin citas», con 1 «Una cita», con 5 «5 citas».

Varios idiomas

Si el sistema debe funcionar en dos idiomas, el cambio se hace con:

app()->setLocale('en');

Lo habitual es un middleware que lea la preferencia del usuario:

public function handle(Request $request, Closure $next)
{
    if ($idioma = $request->user()?->idioma) {
        app()->setLocale($idioma);
        Carbon::setLocale($idioma);
    }

    return $next($request);
}

Con eso, cada usuario ve el sistema en su idioma sin tocar nada más.

Errores comunes

  • No ejecutar config:clear después de cambiar el .env.
  • Traducir las reglas pero no el arreglo attributes, dejando los mensajes con nombres de columna.
  • format() en lugar de translatedFormat() para nombres de meses y días.
  • Olvidar Carbon::setLocale('es').
  • Traducir las cien reglas de validation.php cuando usas ocho.
  • Cambiar el nombre de la carpeta en en lugar de copiarla: conviene conservar el respaldo en inglés.

Para cerrar

Poner Laravel en español son tres pasos: lang:publish, copiar la carpeta a es y cambiar APP_LOCALE. Y luego el detalle que marca la diferencia: llenar el arreglo attributes.

Es de los cambios con mejor relación entre esfuerzo y percepción de calidad que hay en todo el framework.

Con esta lección cerramos el módulo de la aplicación web. En la siguiente empezamos con la autenticación: registro, inicio de sesión y rutas protegidas.

Saludos y éxitos.

Norvic Software

Desarrollamos el software que tu empresa necesita

Somos una fábrica de software en Bolivia. Construimos sistemas a medida y aplicaciones móviles, y llevamos Inteligencia Artificial a las empresas que ya tienen un sistema funcionando.

Solicitar cotizaciónVer todos los servicios

Cotización sin costo · Respuesta directa por WhatsApp