Увійти Реєстрація
Блог Серії
Кар'єра
Вакансії Компанії
Навчання
Документація Співбесіди Тестування Відео
Екосистема
Пакети Ресурси Проєкти Інструменти Події
Інше
Про нас Реклама

Senior: питання на співбесіді з теми «Локалізація»

Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.

2 питання

Мову зазвичай визначає middleware - з URL, налаштувань користувача чи заголовка браузера:

class SetLocale
{
    public function handle(Request $request, Closure $next): Response
    {
        $locale = $request->route('locale')
            ?? $request->user()?->locale
            ?? $request->getPreferredLanguage(['uk', 'en']);

        App::setLocale($locale);

        return $next($request);
    }
}

Де це стикається з кешем - три різні місця:

1. Кеш застосунку. Значення, покладене під ключем nav.items, буде віддане й іншій мові. Ключ має включати локаль:

Cache::remember('nav.items.'.app()->getLocale(), 3600, fn () => ...);

2. Кеш HTTP і CDN. Якщо мова визначається заголовком Accept-Language, а не URL, то одна адреса віддає різний вміст - і проксі роздасть усім ту версію, яка потрапила в кеш першою. Рятує або Vary: Accept-Language, або мова в URL.

3. Кеш маршрутів. route:cache фіксує маршрути один раз, тож локаль не може бути частиною визначення маршруту - лише параметром.

Чому мову краще тримати в URL. Окремі адреси на кожну мову - це єдиний варіант, який нормально індексується: у пошуку зʼявляються обидві версії, hreflang їх звʼязує, а посилання веде туди, куди вело в того, хто ним поділився. Мова в сесії всього цього не дає.

Дрібниця, яку часто пропускають: App::setLocale() не змінює локаль Carbon - для дат потрібен окремий Carbon::setLocale().

Докладніше в документації: Локалізація

Проблема. Локаль - це стан поточного процесу: middleware встановлює App::setLocale('uk') для запиту. Але:

  • адміністратор з англійським інтерфейсом змінює статус замовлення - і лист клієнту генерується англійською;
  • воркер черги та запланована команда взагалі не мають запиту - вони працюють з локаллю за замовчуванням з config/app.php;
  • воркер обробляє завдання різних користувачів поспіль - локаль, встановлена одним завданням через setLocale, «протікає» в наступні.

Рішення 1 - явна локаль при надсиланні:

Mail::to($customer)->locale('uk')->queue(new OrderShipped($order));
$user->notify((new InvoicePaid($invoice))->locale('pl'));

Laravel запам'ятовує локаль у самому завданні, і воркер перемикається на неї лише на час рендерингу листа, а потім повертає попередню.

Рішення 2 - бажана локаль на моделі (краще):

use Illuminate\Contracts\Translation\HasLocalePreference;

class User extends Authenticatable implements HasLocalePreference
{
    public function preferredLocale(): string
    {
        return $this->locale ?? config('app.locale');
    }
}

Тепер усі листи й сповіщення для цього користувача автоматично йдуть його мовою - незалежно від того, хто і звідки їх надіслав. Викликати locale() не потрібно.

Що ще залежить від локалі - і про що забувають:

  • дати й числа: Carbon має власну локаль; $date->translatedFormat('j F') у листі має відповідати мові отримувача;
  • URL у листах: якщо мова закодована в адресі (/uk/orders/42), посилання мають генеруватися для локалі отримувача, а не поточного запиту;
  • тема листа, що формується в envelope() через __(), теж рендериться в локалі завдання - тож перекладайте її там, а не в конструкторі (конструктор виконується в локалі відправника);
  • довільні завдання, що формують тексти (PDF, експорт), - для них перемикання не автоматичне:
use Illuminate\Support\Traits\Localizable;

class GenerateInvoicePdf implements ShouldQueue
{
    use Localizable;

    public function handle(): void
    {
        $this->withLocale($this->user->preferredLocale(), fn () => $this->render());
    }
}

Трейт Localizable (його ж використовують Mailable і відправник сповіщень) повертає попередню локаль у блоці finally - навіть після винятку, на відміну від ручного setLocale.

Тести: відправити лист користувачу з локаллю uk, коли застосунок працює з en, і перевірити assertSeeInHtml('Замовлення відправлено') - цей тест ловить більшість проблем.

Докладніше в документації: Пошта: бажані локалі користувачів