-
Notifications
You must be signed in to change notification settings - Fork 0
Closed
Description
📌 Parent Epic
🎯 Goal
Implement locale detection middleware that reads Accept-Language header and sets Laravel's application locale accordingly. Extract existing translatable strings, translate them via Translation.io, and ensure all user-facing text is properly internationalized.
📋 Implementation Tasks
- Create
SetLocaleFromHeadermiddleware - Register middleware in
apimiddleware group - Test middleware with different
Accept-Languageheaders - Extract translatable strings from existing code:
- Validation error messages
- Authentication error messages
- Email templates (password reset)
- API response messages
- Run
php artisan translation:syncto push strings to Translation.io - Translate strings to German in Translation.io dashboard
- Pull translations with
php artisan translation:sync - Add tests verifying translations work correctly
- Update CHANGELOG.md with entry in [Unreleased] → Added section
- All tests passing (≥80% coverage maintained)
- PHPStan and Pint checks passing
✅ Acceptance Criteria
- Middleware correctly parses
Accept-Languageheader - Locale is set to
dewhen header containsdewith highest priority - Locale defaults to
enwhen header is missing or invalid - All existing user-facing strings are translatable
- German translations are complete and functional
- Tests verify locale switching works correctly
- Tests verify translations are used (not hardcoded English)
- No breaking changes to existing API endpoints
- All tests passing (≥80% coverage)
- PHPStan level max passing
- Laravel Pint passing
- CHANGELOG.md updated
- LOC count ≤ 600 (excluding tests, or marked with
large-pr-approvedlabel if needed)
🔗 Dependencies
- Depends on: PR-1: API Translation.io Integration & Configuration #84 (PR-1: Configuration & Setup must be merged first)
- Related: User Language Preference in Profile (Override Accept-Language Header) #86 (User language preference - future enhancement)
- Part of: [EPIC] Multi-Language Support with Translation.io #83 (Epic: Multi-Language Support)
📝 Technical Notes
Middleware Implementation
// app/Http/Middleware/SetLocaleFromHeader.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
class SetLocaleFromHeader
{
public function handle(Request $request, Closure $next)
{
$supportedLocales = ['en', 'de'];
// Get preferred language from Accept-Language header
$locale = $request->getPreferredLanguage($supportedLocales);
// Fall back to default if no match
App::setLocale($locale ?: config('app.locale'));
return $next($request);
}
}Registration
// bootstrap/app.php or app/Http/Kernel.php
->withMiddleware(function (Middleware $middleware) {
$middleware->api(append: [
\App\Http\Middleware\SetLocaleFromHeader::class,
]);
})Translation Workflow
-
Mark strings as translatable using Laravel's
__()helper:// Before 'message' => 'Email is required' // After 'message' => __('validation.required', ['attribute' => __('attributes.email')])
-
Run sync to extract strings:
ddev exec php artisan translation:sync -
Translate in Translation.io dashboard: https://translation.io/secpal/api
-
Pull translations back:
ddev exec php artisan translation:sync
Areas to Translate
app/Http/Controllers/AuthController.php- Error messagesresources/lang/en/validation.php- Laravel validation messages (if custom)resources/views/emails/- Email templates- Any API response messages with user-facing text
🧪 Testing Strategy
test('locale is set from Accept-Language header', function () {
$response = $this->withHeader('Accept-Language', 'de-DE,de;q=0.9')
->getJson('/api/v1/health');
expect(App::getLocale())->toBe('de');
});
test('validation errors are translated to German', function () {
$response = $this->withHeader('Accept-Language', 'de')
->postJson('/api/v1/auth/token', []);
$response->assertStatus(422);
// Verify German error message
expect($response->json('errors.email.0'))
->toContain('E-Mail'); // German: "Das E-Mail-Feld ist erforderlich"
});
test('default locale is English when header is missing', function () {
$response = $this->postJson('/api/v1/auth/token', []);
expect(App::getLocale())->toBe('en');
});
test('password reset email is translated', function () {
Queue::fake();
$user = User::factory()->create(['email' => 'test@example.com']);
$this->withHeader('Accept-Language', 'de')
->postJson('/api/v1/auth/password/reset-request', [
'email' => 'test@example.com',
]);
// Assert email queued with German locale
// Verify email content contains German text
});📝 PR Linking Instructions
When creating the PR for this sub-issue, use this in your PR description:
Fixes #<this-sub-issue-number>
Part of: #83- Do NOT use
Fixes #83- this is not the last sub-issue - The epic closes when frontend implementation (PR-2: Frontend Translation.io Integration & i18n Setup #85) is complete
- This PR depends on PR-1: API Translation.io Integration & Configuration #84 being merged first
Metadata
Metadata
Assignees
Type
Projects
Status
✅ Done