This commit is contained in:
2026-04-22 22:00:50 +02:00
parent 16be247ce1
commit e979fbe755
46 changed files with 5302 additions and 274 deletions

View File

@@ -0,0 +1,305 @@
---
phase: 17-bilingual-packages-and-softra-errors
plan: 01
type: execute
wave: 1
depends_on: ["16-01"]
files_modified:
- 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)
autonomous: false
delegation: off
---
<objective>
## 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
</objective>
<context>
@.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
</context>
<acceptance_criteria>
## AC-1: Panel pakietów obsługuje pola _en
```gherkin
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
```gherkin
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
```gherkin
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>
<tasks>
<task type="auto">
<name>Task 1: Panel admina — pola name_en / description_en + zapis/odczyt</name>
<files>wp-content/plugins/carei-reservation/includes/class-admin-panel.php</files>
<action>
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:
- `<input type="text" name="packages[soft][name_en]">` z labelką `esc_html__( 'Nazwa (EN)', 'carei-reservation' )`
- `<textarea name="packages[soft][description_en]">` 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.
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
</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>
**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
</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
**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.
</how-to-verify>
<resume-signal>Napisz "approved" lub opisz co nie działa.</resume-signal>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Schema `carei_reservation` CPT, meta keys (`_carei_*`), statusy w DB
- Logika JWT auth w `Carei_Softra_API` (cache, retry)
- Phase 13 struktura REST endpointu `/protection-packages` — klucze odpowiedzi (`name`, `description`, `pricePerDay`, `enabled`) pozostają, zmienia się treść per locale
- Phase 15 filtr Softra-insurance (drop) — nietknięty
- Phase 16 textdomain i wp_localize_script — tylko ROZSZERZAMY (dokładamy nowe klucze jeśli potrzeba), nie refaktorujemy
- Frontend JS — minimalne zmiany (ewentualnie `?lang=` parameter), bez refactoru flow
## SCOPE LIMITS
- Nie generujemy tłumaczeń `.po`/`.mo` — to Phase 18
- Nie dodajemy mechanizmu Polylang "String Translation" per-post-meta (nadkomplikacja) — option w DB z polami `_en` wystarczy dla 2 pakietów
- Nie rozszerzamy słownika Softra na >20 pozycji — 1215 pokrywa realne przypadki, resztę mapujemy iteracyjnie gdy się pojawią
- Nie tłumaczymy statusów rezerwacji w DB (`nowe`/`przeczytane`/`zrealizowane`) — tylko UI labels (już zrobione w Phase 16)
- Nie dotykamy Elementora ani treści stron
</boundaries>
<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>
<success_criteria>
- Wszystkie 2 auto tasks zakończone
- Checkpoint human-verify zatwierdzony ("approved")
- PL bez regresji
- EN: pakiety pokazują wartości z pól `_en` (lub PL fallback)
- Infrastruktura mapowania błędów Softra gotowa (tłumaczenia pojawią się po Phase 18)
</success_criteria>
<output>
Po zakończeniu: `.paul/phases/17-bilingual-packages-and-softra-errors/17-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,138 @@
---
phase: 17-bilingual-packages-and-softra-errors
plan: 01
subsystem: i18n
tags: [polylang, softra-errors, rest-api, protection-packages, bilingual]
requires:
- phase: 13-protection-packages
provides: panel WP pakietów SOFT/PREMIUM + REST /protection-packages
- phase: 16-i18n-plugin-refactor
provides: textdomain carei-reservation + __() we wszystkich plikach
provides:
- Pola name_en / description_en w panelu pakietów + zapis/odczyt
- REST /protection-packages zwraca wariant per locale (z fallbackiem PL)
- Carei_Softra_API::extract_softra_message() + map_error_message() ze słownikiem 13 komunikatów
- JS loadProtectionPackages() dodaje ?lang= na podstawie document.documentElement.lang
affects: [18-en-translation]
tech-stack:
added: []
patterns:
- "Locale resolution: ?lang= param → determine_locale() → get_locale() fallback"
- "Softra error mapping: exact match → fuzzy prefix match → graceful passthrough"
- "Bilingual fields pattern: base field + _en variant w tej samej WP option, fallback gdy _en puste"
key-files:
modified:
- 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
key-decisions:
- "Klucze _en nie wyciekają do REST response — frontend dostaje już rozwiązane name/description"
- "map_error_message przez __() zamiast surowych PL→EN — czeka na .mo (Phase 18) bez osobnego słownika angielskiego"
- "document.documentElement.lang zamiast explicit Polylang API — działa cross-plugin (TranslatePress, WPML) jeśli ktoś zmieni w przyszłości"
- "Default name_en w defaults (SOFT Protection / PREMIUM Protection) — admin dostaje sensowną propozycję od razu"
patterns-established:
- "Bilingual option: {base, base_en, base_description, description_en} — jedna opcja w WP DB, fallback gdy puste EN"
- "REST locale resolution z priorytetem ?lang=<pl|en> — explicit > implicit (determine_locale)"
duration: ~25min
started: 2026-04-22
completed: 2026-04-22
---
# Phase 17 Plan 01: Dwujęzyczne pakiety + mapowanie błędów Softra — Summary
**Panel admina pakietów ochronnych zyskał pola name_en/description_en, REST /protection-packages zwraca wariant per locale z fallbackiem, a błędy z Softra API przechodzą przez słownik 13 typowych komunikatów owinięty w __() — infrastruktura EN gotowa dla Phase 18.**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~25min |
| Tasks | 2 auto + 1 human-verify completed |
| Files modified | 4 |
| Delegation | 0 (inline — precyzyjne zmiany) |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Panel pakietów obsługuje pola _en | Pass | 4 pola tekstowe per pakiet, sanitize + zapis w tej samej WP option, fallback gdy EN puste |
| AC-2: REST endpoint zwraca wariant per locale | Pass | ?lang= param + determine_locale fallback; _en klucze nie wyciekają |
| AC-3: Błędy Softra mapowane | Pass | 13 wpisów w słowniku + exact match + fuzzy prefix + graceful passthrough |
## Accomplishments
- **Admin panel:** 4 pola tekstowe per pakiet (PL + EN dla name i description) + placeholder + opis "Puste = fallback do wersji polskiej"
- **`Carei_REST_Proxy::resolve_locale()`** — helper z priorytetem `?lang=pl|en``determine_locale()``get_locale()`
- **`/protection-packages`** zwraca `name`/`description` rozwiązane per locale, `_en` klucze ukryte w payloadzie
- **`Carei_Softra_API::extract_softra_message()`** — parser JSON odpowiedzi Softra (pola `message`/`error`/`details`/`description`)
- **`Carei_Softra_API::map_error_message()`** — 13 typowych komunikatów PL → `__()` z textdomain, exact + fuzzy prefix match
- **Integracja w `request()`** — błędy HTTP 4xx/5xx przechodzą przez extract→map przed `WP_Error`
- **Frontend `loadProtectionPackages()`** dodaje `?lang=` na podstawie `document.documentElement.lang`
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `includes/class-admin-panel.php` | Modified | Pola _en + sanitize/save |
| `includes/class-rest-proxy.php` | Modified | resolve_locale() + per-locale response |
| `includes/class-softra-api.php` | Modified | extract_softra_message() + map_error_message() + integracja w request() |
| `assets/js/carei-reservation.js` | Modified | ?lang= param w loadProtectionPackages() |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| `_en` keys nie w REST response | Czyste API — frontend nie wie o wariantach, dostaje już rozwiązany string | Brak zmian w JS poza lang= param |
| `map_error_message` przez `__()` a nie statyczne EN | Użyj istniejącej infrastruktury textdomain — Phase 18 automatycznie dostarcza tłumaczenia | Jeden słownik tłumaczeń, jedno miejsce aktualizacji |
| `document.documentElement.lang` zamiast Polylang-specific | Niezależne od konkretnego pluginu i18n; Polylang, TranslatePress, WPML wszystkie ustawiają html@lang | Future-proof przy zmianie wtyczki tłumaczeniowej |
## Deviations from Plan
### Summary
| Type | Count | Impact |
|------|-------|--------|
| Auto-fixed | 0 | — |
| Scope additions | 1 | Dodano `extract_softra_message()` — plan zakładał prostsze podejście, ale Softra zwraca różne struktury JSON |
| Deferred | 0 | — |
### Scope additions
**1. [Robustness] Helper `extract_softra_message()`**
- Found during: Task 2b (integracja mapowania w request())
- Issue: Softra API zwraca różne struktury błędu (message w root, zagnieżdżone w error, albo raw string) — prosty `$body['message']` by nie pokrył wszystkich przypadków
- Fix: Dedykowany parser z iteracją przez typowe klucze (`message`, `error`, `details`, `description`) + rekurencja dla stringa JSON
- Files: class-softra-api.php
- Verification: Edge cases sprawdzone w głowie — array passthrough, string JSON, nested, empty fallback
- Impact: +30 linii kodu, ale gwarantuje że mapowanie działa niezależnie od variantu odpowiedzi Softra
## Issues Encountered
None.
## Next Phase Readiness
**Ready:**
- Kompletna infrastruktura dla EN — Phase 18 tylko generuje `.po``.mo` i wrzuca w `languages/`
- Pakiety ochronne (SOFT/PREMIUM) — admin wypełni pola EN, frontend natychmiast pokaże (nie czeka na .mo)
- Błędy Softra — słownik 13 msgid gotowy do tłumaczenia w .po
**Concerns:**
- Jeśli w przyszłości Softra doda nowy komunikat → słownik trzeba ręcznie rozszerzać (mamy fuzzy prefix match, ale nie pokrywa wszystkiego)
- Performance `loadProtectionPackages()` — doszedł `?lang=` param; nie wpływa na caching (WordPress nie cachuje tego endpointu, Phase 13 save też inwaliduje)
- Admin EN pole może zostać puste → fallback do PL jest poprawny, ale trzeba to wyraźnie komunikować w UI (już jest: "Puste = fallback do wersji polskiej")
**Blockers:** None. Phase 18 może ruszyć.
---
*Phase: 17-bilingual-packages-and-softra-errors, Plan: 01*
*Completed: 2026-04-22*