Skip to main content

Overview

ServITech provides comprehensive multi-language support for all API responses, error messages, and user-facing content. The system automatically adapts to the client’s preferred language using Laravel’s localization features enhanced by the LaravelLang package.

Supported Languages

The API currently supports:
  • English (en) - Default fallback language
  • Spanish (es) - Primary language (default locale)
The default locale is set to Spanish (es) as specified in the .env file: APP_LOCALE=es. English serves as the fallback when translations are missing.

Language Detection

The API detects the client’s preferred language through multiple methods, checked in this order:

1. URL Parameter (Highest Priority)

GET /api/articles?locale=en
Cookie: Accept-Language=es

3. Session

Session value Accept-Language

4. HTTP Header (Most Common)

GET /api/articles
Accept-Language: es
or
Accept-Language: en

5. Default Locale

If none of the above are provided, the API falls back to the configured default (es).

Configuration

Localization settings are defined in config/localization.php:8 and config/app.php:81.

Application Locale Settings

// config/app.php
'locale' => env('APP_LOCALE', 'en'),
'fallback_locale' => env('APP_FALLBACK_LOCALE', 'en'),
'faker_locale' => env('APP_FAKER_LOCALE', 'en_US'),

Route Configuration

// config/localization.php
'routes' => [
    'names' => [
        'parameter' => 'locale',           // URL parameter name
        'header'    => 'Accept-Language',  // HTTP header name
        'cookie'    => 'Accept-Language',  // Cookie name
        'session'   => 'Accept-Language',  // Session key
    ],
    
    'name_prefix' => 'localized.',
    'redirect_default' => false,
    'hide_default' => false,
],

Middleware Chain

All API routes use the localizedGroup() helper which applies these middleware in order:
// config/localization.php:147
'middlewares' => [
    'default' => [
        LaravelLang\Routes\Middlewares\LocalizationByCookie::class,
        LaravelLang\Routes\Middlewares\LocalizationByHeader::class,
        LaravelLang\Routes\Middlewares\LocalizationBySession::class,
        LaravelLang\Routes\Middlewares\LocalizationByModel::class,
    ],
],

Localized Routes

All API routes are wrapped in the localizedGroup() helper:
// routes/api.php:27
Route::localizedGroup(function () {
    // Authentication Routes
    Route::prefix('auth')->group(function () {
        Route::post('login', [AuthController::class, 'login']);
        Route::post('register', [AuthController::class, 'register']);
        // ...
    });
    
    // All other routes...
});
The localizedGroup() helper automatically applies localization middleware to all routes within the group, eliminating the need for manual middleware assignment on each route.

Translation Files

Translation files are organized by language in the lang/ directory:
lang/
├── en/
│   ├── actions.php
│   ├── auth.php
│   ├── http-statuses.php
│   ├── messages.php
│   ├── pagination.php
│   ├── passwords.php
│   └── validation.php
├── es/
│   ├── actions.php
│   ├── auth.php
│   ├── http-statuses.php
│   ├── messages.php
│   ├── pagination.php
│   ├── passwords.php
│   └── validation.php
├── en.json
└── es.json

Translation Structure

Translations use a nested array structure for organization:
// lang/en/messages.php
return [
    'common' => [
        'not_found' => ':item not found.',
        'created' => ':item created successfully.',
        'updated' => ':item updated successfully.',
        'deleted' => ':item deleted successfully.',
    ],
    
    'entities' => [
        'article' => [
            'plural' => 'Articles',
            'singular' => 'Article',
        ],
        'user' => [
            'plural' => 'Users',
            'singular' => 'User',
        ],
    ],
    
    'user' => [
        'registered' => 'User registered successfully.',
        'logged_in' => 'User logged in successfully.',
        'logged_out' => 'User logged out successfully.',
    ],
];
// lang/es/messages.php
return [
    'common' => [
        'not_found' => ':item no encontrado.',
        'created' => ':item creado exitosamente.',
        'updated' => ':item actualizado exitosamente.',
        'deleted' => ':item eliminado exitosamente.',
    ],
    
    'entities' => [
        'article' => [
            'plural' => 'Artículos',
            'singular' => 'Artículo',
        ],
        'user' => [
            'plural' => 'Usuarios',
            'singular' => 'Usuario',
        ],
    ],
    
    'user' => [
        'registered' => 'Usuario registrado con éxito.',
        'logged_in' => 'Usuario conectado con éxito.',
        'logged_out' => 'Usuario desconectado con éxito.',
    ],
];

Using Translations

In Controllers

The __() helper function retrieves translated strings:
// app/Http/Controllers/Auth/AuthController.php:110
return ApiResponse::success(
    message: __('messages.user.logged_in'),
    data: [
        'user' => UserResource::make($user),
        'token' => $token,
        'expires_in' => $expiresIn
    ]
);

With Placeholders

Use :placeholder syntax for dynamic values:
// Translation file
'not_found' => ':item not found.'

// Usage
__('messages.common.not_found', ['item' => 'Article'])
// Output (en): "Article not found."
// Output (es): "Artículo no encontrado."

Nested Keys

Access nested translations with dot notation:
__('messages.common.created')        // "created successfully"
__('messages.entities.user.plural')  // "Users" or "Usuarios"
__('auth.password')                  // "These credentials do not match our records."

Response Examples

Success Response (English)

GET /api/user/profile
Accept-Language: en
Authorization: Bearer {token}
{
  "status": 200,
  "message": "User information retrieved successfully.",
  "data": {
    "user": {
      "id": 1,
      "name": "John Doe",
      "email": "[email protected]"
    }
  }
}

Success Response (Spanish)

GET /api/user/profile
Accept-Language: es
Authorization: Bearer {token}
{
  "status": 200,
  "message": "Información del usuario obtenida con éxito.",
  "data": {
    "user": {
      "id": 1,
      "name": "John Doe",
      "email": "[email protected]"
    }
  }
}

Error Response (English)

POST /api/auth/login
Accept-Language: en
Content-Type: application/json

{
  "email": "[email protected]",
  "password": "wrongpassword"
}
{
  "status": 400,
  "message": "We can't find a user with that email address.",
  "errors": {
    "email": "We can't find a user with that email address."
  }
}

Error Response (Spanish)

POST /api/auth/login
Accept-Language: es
Content-Type: application/json

{
  "email": "[email protected]",
  "password": "wrongpassword"
}
{
  "status": 400,
  "message": "No podemos encontrar un usuario con esa dirección de correo electrónico.",
  "errors": {
    "email": "No podemos encontrar un usuario con esa dirección de correo electrónico."
  }
}

Validation Errors

Validation errors are also fully localized:
POST /api/auth/register
Accept-Language: es
Content-Type: application/json

{
  "email": "invalid-email",
  "password": "123"
}
{
  "status": 422,
  "message": "The email field must be a valid email address.",
  "errors": {
    "email": [
      "El campo email debe ser una dirección de correo válida."
    ],
    "password": [
      "El campo password debe contener al menos 8 caracteres."
    ]
  }
}

Enum Localization

Enums like UserRoles have translated labels:
// app/Enums/UserRoles.php:18
public function label(): string
{
    return match ($this) {
        self::ADMIN => __('enums.user_roles.admin'),
        self::EMPLOYEE => __('enums.user_roles.employee'),
        self::USER => __('enums.user_roles.user'),
    };
}
Translation files:
// lang/en/enums.php
'user_roles' => [
    'admin' => 'Administrator',
    'employee' => 'Employee',
    'user' => 'User',
]

// lang/es/enums.php
'user_roles' => [
    'admin' => 'Administrador',
    'employee' => 'Empleado',
    'user' => 'Usuario',
]

Adding New Languages

1

Install language pack

composer require laravel-lang/lang:~14.0
php artisan lang:add {locale}
2

Create translation files

Create a new directory in lang/ with the locale code:
mkdir lang/fr
cp lang/en/*.php lang/fr/
3

Translate strings

Update all translation files in the new language directory.
4

Test localization

curl -H "Accept-Language: fr" http://localhost:8000/api/articles

Best Practices

// Good
return ApiResponse::success(message: __('messages.user.logged_in'));

// Bad
return ApiResponse::success(message: 'User logged in successfully');
// Translation: ':item created successfully.'
__('messages.common.created', ['item' => $entityName])
Group related translations under common keys:
  • messages.common.* - Reusable messages
  • messages.entities.* - Entity names
  • messages.{entity}.* - Entity-specific messages
Always define translations in both the default locale and fallback locale to prevent missing translation errors.
Include localization tests in your test suite:
$this->withHeaders(['Accept-Language' => 'es'])
     ->get('/api/articles')
     ->assertJson(['message' => 'Lista de Artículos obtenida exitosamente.']);

Environment Configuration

Configure localization in your .env file:
# Default language
APP_LOCALE=es

# Fallback when translation missing
APP_FALLBACK_LOCALE=en

# For test data generation
APP_FAKER_LOCALE=es_ES
Changing APP_LOCALE affects all default responses. Ensure all translation files are complete before switching the default locale in production.

Performance Considerations

  • Translation caching: Laravel caches translation files in production (config cache)
  • Minimal overhead: Locale detection adds negligible latency (~1ms)
  • CDN compatibility: Locale headers are respected by most CDN providers

Localization Flow

Next Steps

Error Handling

See how errors are localized and formatted

API Reference

Explore localized API endpoints

Build docs developers (and LLMs) love