Skip to content

feat(mail): client-language emails + multilingual email templates - #26

Merged
Panelica merged 5 commits into
Panelica:mainfrom
hedon77:feat/email-template-i18n
Sep 4, 2026
Merged

feat(mail): client-language emails + multilingual email templates#26
Panelica merged 5 commits into
Panelica:mainfrom
hedon77:feat/email-template-i18n

Conversation

@hedon77

@hedon77 hedon77 commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Podsumowanie

Dodaje pełną lokalizację e-maili klienta wg języka profilu oraz wielojęzyczne szablony e-mail z automatyczną propagacją.

1. E-maile renderują się w języku klienta

Problem: Mailable renderuje widok Blade w withLocale($mailable->locale) zanim odpali się event MessageSending, więc wcześniejsze ustawienie locale z listenera działało za późno — body renderowało się w domyślnym (angielskim) locale, a __('email.*') ignorował język klienta.

Rozwiązanie: trait App\Mail\Concerns\LocalizesToRecipient ustawia $mailable->locale z Client->language w konstruktorze każdego maila (przed wysyłką). Stosowany w 23 mailable'ach. Obsługuje: model z relacją client(), sam Client, oraz email (lookup Client::where('email') dla resetu hasła/zmiany loginu).

Temat nadal lokalizuje się z szablonu (istniejący ApplyEmailTemplate przez forMailable(..., $locale) z fallbackiem do EN).

2. Pole „Język" dla klienta

  • Admin: clients/create + clients/edit — select language z aktywnych języków (Language::getActiveLanguages()).
  • Klient: account/profile — select language.
  • ClientController/AccountController walidują i zapisują language.

3. Wielojęzyczne szablony e-mail

  • Migracja 2026_09_03_000001_email_templates_multilingual:
    • email_templates.name → unikalność (name, language),
    • kopie EN do wszystkich języków (custom=false),
    • polskie tłumaczenia 26 szablonów.
  • EmailTemplateObserver — nowy szablon EN propaguje się do pozostałych języków.
  • LanguageObserver — nowy język dostaje kopie wszystkich szablonów EN.
  • Widok email-templates — selektor języka u góry.

Pliki

37 plików, +612/−31. Kluczowe: app/Mail/Concerns/LocalizesToRecipient.php, 23 maile, app/Observers/*, EmailTemplateService, migracja, widoki client/admin.

@Panelica

Panelica commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Thank you — this is a valuable feature and the root-cause write-up (the withLocale timing) is exactly right. One blocker before we can merge, caught by the suite:

AccountController::updateProfile sets "language" => $request->language, but clients.language is NOT NULL. When a profile update does not carry a language field the value is null, so the UPDATE fails with SQLSTATE[23000]: Column "language" cannot be null and the request 500s. Three existing tests now fail on it (AccountTest "user can update profile", both LoginEmailChangeTest cases, and the client-update "correcting a name…" case).

A small fix keeps it safe, e.g. "language" => $request->input("language") ?: $client->language (or only include the key when $request->filled("language")). Everything else — the LocalizesToRecipient trait and the multilingual-templates migration — looks great; just this NULL path to close.

@hedon77

hedon77 commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

Fixed — thanks for catching the NULL path.

AccountController::updateProfile now writes 'language' => $request->input('language') ?: $client->language, so a profile update that doesn't carry the field keeps the client's current language instead of nulling the NOT NULL column.

The admin path was already safe: ClientController::update/store go through $validated with language as nullable, so an absent field is simply not in the payload (and clients.language has a DB default of en for new rows).

Rebased on main and pushed — the three failing tests (AccountTest profile update, both LoginEmailChangeTest cases, the client-update name fix) should be green now.

@Panelica
Panelica merged commit d5a1ebd into Panelica:main Sep 4, 2026
1 check passed
@Panelica

Panelica commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Merged — thank you, this is a clean, well-thought-out change.

I reviewed it end to end and ran the full suite against a fresh test database with the migration applied:

  • The email_templates_multilingual migration runs cleanly (drops the name unique, adds the (name, language) composite, seeds non-English languages with exists() guards, and only touches custom=false copies for the Polish translation — operator customizations stay intact, down() reverses correctly).
  • All 183 language/mail/i18n/parity/Chinese tests pass; 2333 pass overall. The only two failures are the pre-existing environmental db-backup tests (unrelated to this PR).
  • Your last commit (af54a4a) correctly fixes the earlier clients.language NULL issue — language => $request->input('language') ?: $client->language keeps the current value when the form omits it. Verified.

The LocalizesToRecipient trait is the right approach — setting the locale in the constructor so it's in place before Mailable::send() wraps the render in withLocale(). Nicely done.

One deployment note on our side (not a code issue): for existing installs the migration assumes the unique index is named email_templates_name_unique. That's the Laravel default from ->string('name')->unique(), so it matches our stock schema — we'll just double-check any customized deployment before rolling it out there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants