Files
carei.pagedev.pl/.paul/phases/17-bilingual-packages-and-softra-errors/17-01-PLAN.md
2026-04-22 22:00:50 +02:00

19 KiB
Raw Blame History

phase, plan, type, wave, depends_on, files_modified, autonomous, delegation
phase plan type wave depends_on files_modified autonomous delegation
17-bilingual-packages-and-softra-errors 01 execute 1
16-01
wp-content/plugins/carei-reservation/includes/class-admin-panel.php
wp-content/plugins/carei-reservation/includes/class-rest-proxy.php
wp-content/plugins/carei-reservation/includes/class-softra-api.php
wp-content/plugins/carei-reservation/assets/js/carei-reservation.js (drobne — użycie istniejących reject* kluczy)
false off
## Goal (1) Panel administratora pakietów ochronnych SOFT/PREMIUM dostaje dodatkowe pola `name_en` i `description_en`. REST endpoint `/protection-packages` zwraca wariant zgodny z aktualnym locale (`get_locale()` lub parametr `?lang=`): PL → pola bazowe, EN → pola `_en` z fallbackiem do bazowych gdy puste. (2) Słownik mapowania ~1215 typowych komunikatów Softra API (PL stringi zwracane przez zewnętrzny system) na lokalizowane klucze — warstwa w `Carei_Softra_API` / `Carei_REST_Proxy` podmienia `message` w `WP_Error`/response na tekst wg current locale.

Purpose

Bez Phase 17 użytkownik na wersji EN widziałby (1) polskie nazwy pakietów ochronnych wprowadzone przez admina w panelu WP, (2) polskie komunikaty z Softra API przy konflikcie rezerwacji / braku pojazdu / błędzie walidacji. Phase 17 domyka jedyne dwa źródła „obcych" polskich tekstów pozostałych po Phase 16 — po niej całość UI jest dwujęzyczna bez wyjątków.

Output

  • Panel Rezerwacje → Pakiety ochronne: każdy pakiet (SOFT/PREMIUM) ma 4 pola tekstowe zamiast 2 — name + name_en, description + description_en (cena pricePerDay pozostaje jedna, wspólna)
  • Option carei_protection_packages w DB ma nową strukturę: soft: {name, name_en, description, description_en, pricePerDay, enabled}, analogicznie premium
  • REST /protection-packages zwraca wariant zlokalizowany wg determine_locale() — klucze name/description w odpowiedzi są już właściwe dla języka (EN lub PL), frontend nie musi wiedzieć nic o wariantach
  • Słownik mapowania w Carei_Softra_API (nowa metoda map_error_message( $pl_message )) — zwraca sparowany klucz tłumaczenia z textdomain carei-reservation dla znanych komunikatów, albo oryginał jeśli brak mapowania
  • Komunikaty w REST response / WP_Error przechodzą przez filtr mapowania przed zwróceniem do frontu
  • Zero regresji w wersji PL — wszystkie pakiety i błędy wyświetlają się identycznie jak po Phase 16
@.paul/PROJECT.md @.paul/ROADMAP.md @.paul/STATE.md @.paul/phases/13-protection-packages/13-02-SUMMARY.md @.paul/phases/16-i18n-plugin-refactor/16-01-SUMMARY.md @wp-content/plugins/carei-reservation/includes/class-admin-panel.php @wp-content/plugins/carei-reservation/includes/class-rest-proxy.php @wp-content/plugins/carei-reservation/includes/class-softra-api.php @wp-content/plugins/carei-reservation/assets/js/carei-reservation.js

<acceptance_criteria>

AC-1: Panel pakietów obsługuje pola _en

Given administrator wchodzi w `wp-admin → Rezerwacje → Pakiety ochronne`
When wypełnia pola `Nazwa` / `Nazwa (EN)` / `Opis` / `Opis (EN)` dla SOFT i PREMIUM i klika "Zapisz"
Then wszystkie 4 pola tekstowe per pakiet są zapisane w option `carei_protection_packages`
  And po odświeżeniu strony formularz pokazuje zapisane wartości (w tym EN)
  And walidacja/sanityzacja jest konsystentna między polami PL i EN
  And brak EN (puste pole `name_en`/`description_en`) jest poprawny — to oznacza „fallback do PL"

AC-2: REST endpoint zwraca wariant per locale

Given w DB są zapisane pakiety z polami PL i EN
When frontend PL woła `/wp-json/carei/v1/protection-packages` (locale = pl_PL)
Then odpowiedź zawiera `soft.name` i `soft.description` z wariantu PL
  And analogicznie dla premium

When frontend EN (Polylang język EN) woła ten sam endpoint
Then odpowiedź zawiera `soft.name` = wartość `name_en` (lub PL jeśli pole EN puste)
  And `soft.description` = `description_en` z fallbackiem
  And struktura odpowiedzi nie zmienia kluczy (`name`, `description`, `pricePerDay`, `enabled`) — tylko treści
  And frontend JS nie wymaga żadnej zmiany logicznej (oprócz ewentualnego `?lang=` jeśli Polylang nie ustawia locale serwerowo)

AC-3: Błędy Softra mapowane na lokalizowane stringi

Given użytkownik próbuje zarezerwować pojazd niedostępny w danym terminie
When Softra API zwraca message "Brak dostępnego pojazdu w wybranym terminie" (lub podobny)
Then `Carei_Softra_API::map_error_message()` rozpoznaje string
  And zwraca lokalizowany wariant zgodny z current locale (`__('rejectCarNotFound', ...)` lub bezpośredni tekst EN)
  And REST response `message` zawiera tekst w języku zgodnym z UI
  And dla nieznanych komunikatów — przepuszcza oryginał (graceful fallback)
  And słownik pokrywa co najmniej 12 typowych komunikatów (lista w Task 2)

</acceptance_criteria>

Task 1: Panel admina — pola name_en / description_en + zapis/odczyt wp-content/plugins/carei-reservation/includes/class-admin-panel.php 1. Zaktualizuj `get_protection_packages_defaults()` — dodaj domyślne klucze `name_en` i `description_en` per pakiet (wartości puste albo angielskie defaulty typu `'Protection SOFT'` / `'Protection PREMIUM'`). 2. Zaktualizuj `get_protection_packages()` — metodę czyszczenia/merge z defaultami, żeby obsłużyła nowe pola (użytkownicy z starą strukturą dostają puste EN, bez crasha). 3. W `render_protection_packages_page()`: - Dla każdego pakietu (SOFT, PREMIUM) dodać pod polami PL dwa kolejne wiersze: - `` z labelką `esc_html__( 'Nazwa (EN)', 'carei-reservation' )` - `` z labelką `esc_html__( 'Opis (EN)', 'carei-reservation' )` - Wizualnie oddzielone (np. `<small>` note: „puste = fallback do wersji polskiej") - Placeholdery w `esc_attr__()` 4. W `handle_protection_packages_save()`: - Sanitize `name_en` przez `sanitize_text_field()` - Sanitize `description_en` przez `sanitize_textarea_field()` - Zapisz oba pola w tej samej strukturze co PL - Nie ruszaj walidacji `pricePerDay` ani `enabled` — bez zmian 5. Nie zmieniaj slugów meta, endpointu REST (Task 2), żadnej logiki biznesowej poza polem form. <pre><code>Unikaj: - Dodawania nowego mechanizmu po stronie DB (np. osobnej opcji dla EN) — wszystko w istniejącym `carei_protection_packages` - Tłumaczenia opisu na bieżąco w panelu admin — admin wpisuje ręcznie EN - Refaktoru istniejącej struktury defaulti — tylko DODAJ klucze </code></pre> </action> <verify> 1. Otwórz `wp-admin → Rezerwacje → Pakiety ochronne` — widoczne 4 pola tekstowe per pakiet (PL name, EN name, PL description, EN description) + cena + enabled 2. Wpisz testowe wartości w EN, zapisz → po odświeżeniu zachowane 3. `get_option( 'carei_protection_packages' )` w wp-admin → Tools → Site Health → Info (lub `wp db query` na locale) zawiera nowe klucze `name_en`, `description_en` 4. `php -l class-admin-panel.php` → No syntax errors 5. Stary kod readujący `$packages['soft']['name']` dalej działa (brak BC break) </verify> <done>AC-1 satysfakcjonowane.</done> </task> <task type="auto"> <name>Task 2: REST endpoint per-locale + mapowanie błędów Softra</name> <files> wp-content/plugins/carei-reservation/includes/class-rest-proxy.php, wp-content/plugins/carei-reservation/includes/class-softra-api.php </files> <action> <pre><code>**Część A — REST `/protection-packages` zwraca wariant zlokalizowany:** 1. W `class-rest-proxy.php` odnajdź handler endpointu `/protection-packages` (prawdopodobnie metoda typu `get_protection_packages()`). 2. Dodaj helper `resolve_locale()`: ```php private function resolve_locale( $request ) { $lang = $request->get_param( 'lang' ); if ( $lang && in_array( strtolower( $lang ), array( 'pl', 'en' ), true ) ) { return strtolower( $lang ); } $locale = function_exists( 'determine_locale' ) ? determine_locale() : get_locale(); return ( 0 === strpos( $locale, 'en' ) ) ? 'en' : 'pl'; } ``` - Polylang przy żądaniach REST powinien ustawić locale automatycznie (filter `locale` lub `determine_locale`). Gdyby nie — frontend może dopisać `?lang=en` jawnie (fallback). JS zostaje bez zmian jeśli Polylang dobrze współpracuje z WP REST. 3. W handlerze endpointu: po pobraniu `$packages = Carei_Admin_Panel::get_protection_packages()`: - Dla lokalu `'en'`: podmień `$pkg['name']` na `$pkg['name_en']` gdy niepuste, inaczej pozostaw `$pkg['name']`. Analogicznie `description`. - Dla lokalu `'pl'`: bez zmian. - Usuń z odpowiedzi klucze `name_en`/`description_en` (frontend nie musi ich widzieć — unika leakowania i mylenia schematu). 4. Odpowiedź REST zachowuje schemat: `{ soft: { name, description, pricePerDay, enabled }, premium: {...} }`. **Część B — Mapowanie błędów Softra:** 5. W `class-softra-api.php` dodaj nową public static method: ```php public static function map_error_message( $original_message ) { if ( ! is_string( $original_message ) || '' === trim( $original_message ) ) { return $original_message; } $dict = array( 'Brak dostępnego pojazdu w wybranym terminie' => __( 'Brak dostępnego pojazdu w wybranym terminie. Zmień daty lub segment.', 'carei-reservation' ), 'Nieprawidłowy zakres dat' => __( 'Nieprawidłowy zakres dat', 'carei-reservation' ), 'Nie znaleziono oddziału' => __( 'Nie znaleziono oddziału', 'carei-reservation' ), 'Klient o tych danych już istnieje' => __( 'Klient o tych danych już istnieje w systemie', 'carei-reservation' ), 'Nieprawidłowy numer PESEL' => __( 'Nieprawidłowy numer PESEL', 'carei-reservation' ), 'Cennik wygasł' => __( 'Cennik wygasł. Odśwież formularz i spróbuj ponownie.', 'carei-reservation' ), 'Token wygasł' => __( 'Sesja wygasła. Odśwież stronę.', 'carei-reservation' ), 'Nieprawidłowe dane logowania' => __( 'Błąd autoryzacji API. Skontaktuj się z administratorem.', 'carei-reservation' ), 'Brak uprawnień' => __( 'Brak uprawnień do wykonania operacji.', 'carei-reservation' ), 'Błąd serwera' => __( 'Błąd serwera. Spróbuj ponownie za chwilę.', 'carei-reservation' ), 'Przekroczono limit rezerwacji' => __( 'Przekroczono limit rezerwacji dla tego klienta.', 'carei-reservation' ), 'Nieprawidłowy numer telefonu' => __( 'Podaj poprawny numer telefonu (min. 9 cyfr).', 'carei-reservation' ), 'Wymagane pole' => __( 'To pole jest wymagane.', 'carei-reservation' ), ); // Exact match if ( isset( $dict[ $original_message ] ) ) { return $dict[ $original_message ]; } // Fuzzy: prefix match (Softra bywa mało przewidywalny z końcówkami) foreach ( $dict as $pl_key => $translated ) { if ( 0 === stripos( $original_message, $pl_key ) ) { return $translated; } } return $original_message; // graceful fallback } ``` - Zasada: `__()` przechodzi przez textdomain `carei-reservation` → dla locale EN pobiera tłumaczenie z `.mo` (Phase 18). W PL zwraca ten sam tekst co msgid (co jest OK — zachowuje polski oryginał). 6. Zintegruj mapowanie: - W miejscach gdzie `Carei_Softra_API` zwraca błąd (np. metoda `make_booking`, `get_car_classes`, itp.) — po odebraniu `$response['error']['message']` lub `$response['message']` (sprawdź strukturę w istniejącym kodzie) zawiń przez `self::map_error_message( $msg )` przed utworzeniem `WP_Error` / przed zwrotem. - Jeśli API używa `WP_Error` już z warstwy `rest-proxy` — zaktualizuj tam (przechwyć message, mapuj, przekaż dalej). **Część C — Frontend (drobna zmiana w JS):** 7. W `carei-reservation.js` w miejscach gdzie łapiesz `err.message` z odpowiedzi API przy niepowodzeniu rezerwacji — jeśli obecnie wyświetlasz surowy message, zostaw bez zmian (backend już mapuje). Jeśli masz heurystykę kategoryzacji po kluczach (np. "no car available" → pokaż specjalny alert), rozważ aktualizację. W MINIMUM: upewnij się że fallbacki `rejectCarNotFound` itd. w `careiI18n` są używane gdzie trzeba. Prawdopodobnie zmiana zero/minimal. 8. Jeśli frontend musi jawnie dopisać `?lang=` przy requeście `/protection-packages` (bo Polylang nie ustawia locale REST automatycznie) — dodaj to w `loadProtectionPackages()`: ```js var lang = (document.documentElement.lang || '').toLowerCase().indexOf('en') === 0 ? 'en' : 'pl'; fetch(REST_URL + 'protection-packages?lang=' + lang, {...}) ``` Unikaj: - Tłumaczenia komunikatów po stronie JS — wszystko leci z PHP (`__()` rozwiązuje per locale) - Wprowadzania nowego schemat odpowiedzi (`name_pl` / `name_en` w payloadzie) — odpowiedź zawsze ma `name` w właściwym języku - Nadpisywania obecnej logiki reject — tylko dodaj warstwę mapowania </code></pre> </action> <verify> 1. `php -l` dla obu zmienionych plików → No syntax errors 2. W PL (Polylang = PL): `curl /wp-json/carei/v1/protection-packages` → `soft.name` = polska nazwa, `soft.description` = polski opis 3. W EN (Polylang = EN) albo `?lang=en`: `soft.name` = angielska nazwa (lub polska fallback gdy puste) 4. Odpowiedź REST NIE zawiera kluczy `name_en`/`description_en` (czyste API) 5. Wywołanie błędnej rezerwacji (niedostępny pojazd) → komunikat zwrócony do frontendu = tłumaczenie z `__()` zamiast surowego Softra-PL (w EN) 6. Nieznany komunikat Softra (spoza słownika) → passthrough bez zmian (graceful) </verify> <done>AC-2, AC-3 satysfakcjonowane.</done> </task> <task type="checkpoint:human-verify" gate="blocking"> <what-built> - Panel `wp-admin → Rezerwacje → Pakiety ochronne` ma teraz 4 pola tekstowe per pakiet (`name`, `name_en`, `description`, `description_en`) + cena + enabled - Option `carei_protection_packages` przechowuje wersje PL i EN każdego pakietu - REST `/protection-packages` zwraca wariant per locale (Polylang EN → pola `_en` z fallbackiem do PL) - Słownik `Carei_Softra_API::map_error_message()` z 12+ wpisami + exact/fuzzy match, wbudowany w ścieżkę zwrotu błędów API - Wersja PL — zero regresji, wersja EN — pakiety i błędy w języku EN (po załadowaniu .mo w Phase 18) </what-built> <how-to-verify> 1. Wypchnij zmienione 34 pliki przez SFTP 2. **Admin panel test:** - `wp-admin → Rezerwacje → Pakiety ochronne` - Sprawdź że są widoczne nowe pola: „Nazwa (EN)", „Opis (EN)" dla SOFT i PREMIUM - Wypełnij testowo: SOFT EN name = „SOFT Protection", EN description = „Basic protection package..." - PREMIUM EN name = „PREMIUM Protection", EN description = „Enhanced protection..." - Kliknij „Zapisz" → odśwież stronę → wartości EN są zachowane 3. **Frontend PL (Polylang = PL):** - Otwórz modal rezerwacji → sekcja „Pakiety ochronne" - Kafelki SOFT/PREMIUM pokazują **polskie** nazwy i opisy (jak zapisane w polach PL) - Cena `X.XX zł/doba` — bez zmian 4. **Frontend EN (Polylang = EN):** - Przełącz język na EN w switcherze Polylang - Otwórz modal rezerwacji → sekcja „Pakiety ochronne" - Kafelki pokazują **angielskie** nazwy i opisy (wartości z pól `_en`) - Jeśli któreś pole `_en` zostawiłeś puste → frontend powinien pokazać PL wariant (fallback) 5. **Test fallbacku:** - Wróć do admin → pole EN jednego pakietu wyczyść - Frontend EN → ten pakiet pokazuje polski oryginał (graceful) 6. **Error mapping test:** - Wymuś błąd Softra (np. data w przeszłości albo niedostępny pojazd — jeśli API tak odpowiada) - PL: komunikat w języku polskim (bez zmian) - EN: komunikat po angielsku (po załadowaniu .mo w Phase 18) albo oryginalne PL jeśli Phase 18 jeszcze niedostępne - Uwaga: **dopóki .po/.mo nie istnieją (Phase 18), `__()` zwraca oryginalny polski string w obu językach.** To jest OK — Phase 17 dostarcza infrastrukturę, Phase 18 dostarcza treść. 7. **DevTools Network:** - `GET /wp-json/carei/v1/protection-packages` w PL → odpowiedź z PL - `GET /wp-json/carei/v1/protection-packages?lang=en` → odpowiedź z EN - Oba responsy nie zawierają pól `name_en`/`description_en` w payloadzie 8. **Brak regresji:** - Cały flow rezerwacji PL działa jak po Phase 16 — nic się nie popsuło <pre><code>**Kryterium przejścia:** Admin zapisuje i odczytuje EN pola, REST zwraca wariant per locale, error mapping działa (graceful fallback dla nieznanych), PL bez zmian. </code></pre> </how-to-verify> <resume-signal>Napisz "approved" lub opisz co nie działa.</resume-signal> </task> </tasks> <boundaries> <h2 id="user-content-do-not-change" dir="auto">DO NOT CHANGE</h2> <ul dir="auto"> <li>Schema <code>carei_reservation</code> CPT, meta keys (<code>_carei_*</code>), statusy w DB</li> <li>Logika JWT auth w <code>Carei_Softra_API</code> (cache, retry)</li> <li>Phase 13 struktura REST endpointu <code>/protection-packages</code> — klucze odpowiedzi (<code>name</code>, <code>description</code>, <code>pricePerDay</code>, <code>enabled</code>) pozostają, zmienia się treść per locale</li> <li>Phase 15 filtr Softra-insurance (drop) — nietknięty</li> <li>Phase 16 textdomain i wp_localize_script — tylko ROZSZERZAMY (dokładamy nowe klucze jeśli potrzeba), nie refaktorujemy</li> <li>Frontend JS — minimalne zmiany (ewentualnie <code>?lang=</code> parameter), bez refactoru flow</li> </ul> <h2 id="user-content-scope-limits" dir="auto">SCOPE LIMITS</h2> <ul dir="auto"> <li>Nie generujemy tłumaczeń <code>.po</code>/<code>.mo</code> — to Phase 18</li> <li>Nie dodajemy mechanizmu Polylang "String Translation" per-post-meta (nadkomplikacja) — option w DB z polami <code>_en</code> wystarczy dla 2 pakietów</li> <li>Nie rozszerzamy słownika Softra na >20 pozycji — 1215 pokrywa realne przypadki, resztę mapujemy iteracyjnie gdy się pojawią</li> <li>Nie tłumaczymy statusów rezerwacji w DB (<code>nowe</code>/<code>przeczytane</code>/<code>zrealizowane</code>) — tylko UI labels (już zrobione w Phase 16)</li> <li>Nie dotykamy Elementora ani treści stron </boundaries></li> </ul> <verification> Przed zamknięciem planu: - [ ] Panel admin pokazuje 4 pola per pakiet (PL + EN name, PL + EN description) - [ ] Zapisanie EN w panelu → dane utrzymują się po reload - [ ] REST `/protection-packages` w PL zwraca polskie, w EN zwraca angielskie (lub PL fallback) - [ ] Odpowiedź REST nie zawiera `name_en`/`description_en` (czyste API) - [ ] `Carei_Softra_API::map_error_message()` istnieje, słownik ma >= 12 wpisów - [ ] Exact match + fuzzy prefix match działają - [ ] Zero regresji w PL (human-verify) - [ ] AC-1, AC-2, AC-3 przeszły weryfikację </verification> <p dir="auto"><success_criteria></p> <ul dir="auto"> <li>Wszystkie 2 auto tasks zakończone</li> <li>Checkpoint human-verify zatwierdzony ("approved")</li> <li>PL bez regresji</li> <li>EN: pakiety pokazują wartości z pól <code>_en</code> (lub PL fallback)</li> <li>Infrastruktura mapowania błędów Softra gotowa (tłumaczenia pojawią się po Phase 18) </success_criteria></li> </ul> <output> Po zakończeniu: `.paul/phases/17-bilingual-packages-and-softra-errors/17-01-SUMMARY.md` </output> </body></html>