Testing API Validation Errors With Different Locales

Published on by

Testing API Validation Errors With Different Locales image

Have you ever wondered how to provide API validation errors and translations for different locales? This beginner post will show you how to get started with setting a locale in a stateless Laravel API and testing various locale validation messages.

On a related note, if you want a quick way to pull translations into your project for validation, we’ve covered a package called Laravel-lang – Translations for Laravel. Larvel-lang has translations for over 68 languages and including translations for things like authentication and validation (among other things).

For now we’ll skip installing this package and get started with the built-in features that Laravel provides for Locale support.

Getting Started

First, we’re going to create a new Laravel project to get started. This tutorial isn’t going to leverage any Locale packages.

laravel new locale-api
cd locale-api

Next, let’s create the files we’ll use to demonstrate working with Locale in stateless APIs:

php artisan make:middleware DetectLocale
php artisan make:controller ProfileController
php artisan make:test LocaleDetectionTest
 
mkdir -p resources/lang/es/
touch resources/lang/es/validation.php

We will set the API Locale through a middleware, which will try to set a preferred locale. We create a controller and test so that we can demonstrate some validation tests we can perform with our API.

Last, we are creating a new validation file for Spanish to demonstrate the Localization of our API validation errors.

We’re not going to install the full Laravel-lang package, but for this tutorial, grab the contents of es/validation.php and add it to the resources/lang/es/validation.php file we created above.

The Middleware

Though I typically use TDD, we’re going to create the middleware and controller first, and then create a test as a means of using our middleware. The DetectLocale middleware is simple for our purposes since APIs are stateless; we will rely on the user sending an Accept-Locale header along with the request. You could also check for a query string param to set the locale, but we’ll keep it simple:

<?php
 
namespace App\Http\Middleware;
 
use Closure;
 
class DetectLocale
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @param mixed ...$locales
* @return mixed
*/
public function handle($request, Closure $next, ...$locales)
{
$locales = $locales ?: ['en'];
 
if ($language = $request->getPreferredLanguage($locales)) {
app()->setLocale($language);
}
 
return $next($request);
}
}

Our middleware takes a $locales configuration, which is an array of supported languages. We then call the super-convenient Request::getPreferredLanguage() method provided by the Symfony HTTP Foundation component, which takes an ordered array of available locales and returns the preferred locale.

If the method returns a preferred locale, we set it via Laravel’s App::setLocale() method.

Next, we configure the middleware in the app/Http/Kernel.php file:

protected $routeMiddleware = [
// ...
'locale' => \App\Http\Middleware\DetectLocale::class,
];

Last, in the same file we add the locale middleware to the api group with the preferred locales:

protected $middlewareGroups = [
// ...
'api' => [
'throttle:60,1',
'bindings',
'locale:en,es',
],
];

The Controller

We’re not going to write out a full controller; we are only going to create a simple validation check to trigger some validation rules:

<?php
 
namespace App\Http\Controllers;
 
use Illuminate\Http\Request;
 
class ProfileController extends Controller
{
public function store(Request $request)
{
$request->validate([
'name' => 'required',
'email' => 'required|email'
]);
 
return response()->json(['status' => 'OK']);
}
}

This controller is going to validate name and email inputs, which will throw an exception if validation fails, returning a 422 Unprocessable Entity statue code and JSON validation errors.

Next, we need to define the route in routes/api.php:

<?php
 
Route::post('/profile', 'ProfileController@store');

Next, we’ll use the example test case in the feature test we created in LocaleDetectionTest:

<?php
 
namespace Tests\Feature;
 
use Illuminate\Http\Response;
use Tests\TestCase;
 
class LocaleDetectionTest extends TestCase
{
/**
* A basic feature test example.
*
* @return void
*/
public function testExample()
{
$response = $this
->withHeaders([
'Accept-Language' => 'es',
])
->postJson('/api/profile', [
'email' => 'invalid.email',
]);
 
$response
->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY)
->assertJsonPath('errors.email', ['correo electrónico no es un correo válido'])
->assertJsonPath('errors.name', ['El campo nombre es obligatorio.']);
}
}

Our test makes a JSON post request to our profile endpoint and sends the Accept-Language header with es for Spanish. As you can see by running the tests, they pass when we assert the JSON returned using the newly contributed assertJsonPath() added in Laravel 6.0.4.

If you dump out the JSON as an array, you can see that the message is in English:

dd($response->decodeResponseJson());
..array:2 [
"message" => "The given data was invalid."
"errors" => array:2 [
"name" => array:1 [
0 => "El campo nombre es obligatorio."
]
"email" => array:1 [
0 => "correo electrónico no es un correo válido"
]
]
]

We won’t dive into solving this in this tutorial, but perhaps we will write a follow-up going more into detail in translating other parts of an API. A simple solution might be to catch ValidationException messages and use a translation for the message.

Another thing to consider is experimenting with what happens when you send a language that you don’t support. I’ll give you a hint: Symonfy’s getPreferredLanguage() will return the first preferred language that you pass it (in our case, en, which is first in the array). Play around with changing the order of preferred locales and testing with the use of an unsupported locale.

Well, that’s it for our brief walk-through of a simple way to set locale in a Laravel API. I’d like to credit Yaz Jallad (@ninjaparade), who was discussing this topic with me yesterday. From our discussion I was curious about how to add validation translations to an API—which I’ve never had to do before—and figured I’d write a quick post after digging in for a bit.

To learn more about Localization in Laravel, check out the Localization documentation.

Paul Redmond photo

Staff writer at Laravel News. Full stack web developer and author.

Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

Laravel Forge logo

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Forge
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
No Compromises logo

No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project. ⬧ Flat rate of $7500/mo. ⬧ No lengthy sales process. ⬧ No contracts. ⬧ 100% money back guarantee.

No Compromises
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
Bacancy logo

Bacancy

Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $2500/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Bacancy
Lucky Media logo

Lucky Media

Get Lucky Now - the ideal choice for Laravel Development, with over a decade of experience!

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

E-Commerce for Laravel. An open-source package that brings the power of modern headless e-commerce functionality to Laravel.

Lunar: Laravel E-Commerce
LaraJobs logo

LaraJobs

The official Laravel job board

LaraJobs
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit
Rector logo

Rector

Your partner for seamless Laravel upgrades, cutting costs, and accelerating innovation for successful companies

Rector
MongoDB logo

MongoDB

Enhance your PHP applications with the powerful integration of MongoDB and Laravel, empowering developers to build applications with ease and efficiency. Support transactional, search, analytics and mobile use cases while using the familiar Eloquent APIs. Discover how MongoDB's flexible, modern database can transform your Laravel applications.

MongoDB

The latest

View all →
A Resize Plugin for Alpine.js image

A Resize Plugin for Alpine.js

Read article
How to Migrate MySQL from DBngin to Laravel Herd image

How to Migrate MySQL from DBngin to Laravel Herd

Read article
Learn to master Query Scopes in Laravel image

Learn to master Query Scopes in Laravel

Read article
How to Redirect Uppercase URLs to Lowercase with Laravel Middleware image

How to Redirect Uppercase URLs to Lowercase with Laravel Middleware

Read article
PHP 8.4 Alpha 1 is now out! image

PHP 8.4 Alpha 1 is now out!

Read article
Generics Added to Eloquent Builder in Laravel 11.15 image

Generics Added to Eloquent Builder in Laravel 11.15

Read article