Files
carei.pagedev.pl/.paul/phases/16-i18n-plugin-refactor/16-01-PLAN.md
2026-04-22 22:00:50 +02:00

317 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
phase: 16-i18n-plugin-refactor
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- wp-content/plugins/carei-reservation/carei-reservation.php
- wp-content/plugins/carei-reservation/includes/class-elementor-widget.php
- 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-search-widget.php
- wp-content/plugins/carei-reservation/includes/class-cities-widget.php
- wp-content/plugins/carei-reservation/includes/class-map-widget.php
- wp-content/plugins/carei-reservation/includes/class-branches-widget.php
- wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
- wp-content/plugins/carei-reservation/languages/carei-reservation.pot (nowy)
autonomous: false
delegation: off
---
<objective>
## Goal
Przygotować plugin `carei-reservation` do dwujęzyczności: wszystkie user-facing stringi w PHP owinięte w `__()`/`esc_html__()`/`esc_attr__()` z textdomain `carei-reservation`; stringi w JS zmigrowane do `wp_localize_script` (obiekt `careiI18n`) tłumaczony po stronie PHP; textdomain ładowany w bootstrapie; wygenerowany plik `.pot` gotowy do tłumaczenia w Phase 18.
## Purpose
Plugin pokrywa ~100% interakcji użytkownika w języku rezerwacji (modal, hero search, admin panel, widgety mapa/miasta/oddziały). Bez i18n żaden zewnętrzny translator (Polylang, Automatic Translate Addon, Loco) nie jest w stanie tłumaczyć tej zawartości — Polylang widzi tylko treść WordPressa/Elementora, a stringi w JS nie istnieją w DOM-ie serwerowo. i18n-refactor jest twardym blokerem dla Phase 17 i 18 milestone'u v0.7.
## Output
- 8 plików PHP z stringami owiniętymi w funkcje i18n
- Bootstrap `carei-reservation.php` ładuje `load_plugin_textdomain` na `plugins_loaded`
- `carei-reservation.js` nie ma hardkodowanych stringów PL — używa obiektu `careiI18n`
- `class-elementor-widget.php` enqueue `wp_localize_script('carei-reservation', 'careiI18n', [...])` z kluczami zmapowanymi 1:1 na użycia w JS
- `languages/carei-reservation.pot` zawiera ~80150 wpisów gotowych do tłumaczenia
- Strona po polsku działa **identycznie jak przed zmianami** (żaden tekst się nie zmienia — tylko jest owinięty)
</objective>
<context>
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
@.paul/phases/13-protection-packages/13-02-SUMMARY.md
@.paul/phases/15-remove-softra-insurance/15-01-SUMMARY.md
@wp-content/plugins/carei-reservation/carei-reservation.php
@wp-content/plugins/carei-reservation/includes/class-elementor-widget.php
@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-search-widget.php
@wp-content/plugins/carei-reservation/includes/class-cities-widget.php
@wp-content/plugins/carei-reservation/includes/class-map-widget.php
@wp-content/plugins/carei-reservation/includes/class-branches-widget.php
@wp-content/plugins/carei-reservation/assets/js/carei-reservation.js
</context>
<acceptance_criteria>
## AC-1: PHP i18n kompletny, textdomain załadowany
```gherkin
Given plugin carei-reservation jest aktywny
When wyszukamy `grep -rn "__\|_e\|esc_html__\|esc_attr__" wp-content/plugins/carei-reservation/`
Then każdy user-facing string w plikach PHP jest owinięty w odpowiednią funkcję z textdomain 'carei-reservation'
And bootstrap `carei-reservation.php` wywołuje `load_plugin_textdomain('carei-reservation', false, dirname(plugin_basename(__FILE__)) . '/languages/')` na haku `plugins_loaded`
And żaden user-facing literał PL nie pozostaje bez opakowania (z wyjątkiem: komentarzy, kluczy meta zaczynających się od `_`, nazw taxonomii jako slugi)
```
## AC-2: JS migrated do careiI18n, brak regresji w PL
```gherkin
Given modal rezerwacji jest otwierany w języku polskim
When użytkownik przechodzi przez pełny flow (wybór dat/oddziału/klasy podsumowanie booking)
Then wszystkie etykiety, komunikaty błędów, placeholdery i nagłówki są identyczne tekstowo jak przed refactorem (AC: parity wizualna 1:1)
And plik `carei-reservation.js` NIE zawiera żadnego literału z polskimi znakami diakrytycznymi (ąćęłńóśźż) ani polskich słów kluczowych
And w DevTools Console: `typeof window.careiI18n === 'object'` zwraca `true` po załadowaniu strony
And obiekt `careiI18n` zawiera klucze odpowiadające wszystkim zmigrowanym stringom
```
## AC-3: .pot wygenerowany, gotowy do tłumaczenia
```gherkin
Given Task 1 i 2 zakończone
When otworzymy `wp-content/plugins/carei-reservation/languages/carei-reservation.pot`
Then plik zawiera nagłówek z metadanymi pluginu (Project-Id-Version, Language: en)
And zawiera wpisy `msgid` dla wszystkich stringów z plików PHP (włącznie z kluczami z careiI18n w wp_localize_script)
And wszystkie `msgstr` są puste (ready for translation)
And ilość wpisów >= 80 (szacunek na podstawie skanu)
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: PHP i18n — wrap stringów + textdomain bootstrap</name>
<files>
wp-content/plugins/carei-reservation/carei-reservation.php,
wp-content/plugins/carei-reservation/includes/class-elementor-widget.php,
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-search-widget.php,
wp-content/plugins/carei-reservation/includes/class-cities-widget.php,
wp-content/plugins/carei-reservation/includes/class-map-widget.php,
wp-content/plugins/carei-reservation/includes/class-branches-widget.php
</files>
<action>
1. W `carei-reservation.php` w bootstrap pluginu dodać (na haku `plugins_loaded` lub przy init):
```php
add_action( 'plugins_loaded', function () {
load_plugin_textdomain( 'carei-reservation', false, dirname( plugin_basename( __FILE__ ) ) . '/languages/' );
} );
```
2. Utworzyć katalog `wp-content/plugins/carei-reservation/languages/` (pusty placeholder lub `.gitkeep`).
3. Dla każdego z 8 plików PHP — owinąć wszystkie user-facing stringi:
- Tekst w HTML-u → `esc_html__( 'tekst', 'carei-reservation' )` lub `esc_html_e( 'tekst', 'carei-reservation' )` (wewnątrz `echo`)
- Atrybuty (placeholder, aria-label, title, alt) → `esc_attr__( 'tekst', 'carei-reservation' )`
- Etykiety kontrolek Elementor (`add_control`, `add_responsive_control`) — `'label' => esc_html__( 'tekst', 'carei-reservation' )`
- Labels CPT i meta box (`class-admin-panel.php`) → `__( 'tekst', 'carei-reservation' )` w tablicach labels
- Kolumny admin listy, filtry, akcje masowe → `__()` / `esc_html__()`
- Komunikaty błędów z REST proxy (`class-rest-proxy.php`) w `WP_Error` → `__( 'komunikat', 'carei-reservation' )`
- Tytuły widgetów Elementor i ich opisy (get_title, get_keywords) → `esc_html__()`
4. NIE OWIJAJ:
- Slugów CPT/post_type/taxonomy (`carei_reservation`)
- Kluczy meta (`_carei_protection_package`, `_carei_status`)
- Nazw pól w payloadach do Softra API (klucze JSON)
- Kluczy tablic konfiguracyjnych, nazw hooków, nazw klas/funkcji
- Komentarzy PHP i docblocków
- Logów error_log (to stringi techniczne, nie user-facing)
- Wartości statusów w DB (`nowe`, `przeczytane`, `zrealizowane`) — zostają jako slug; tłumaczymy tylko UI labels (osobny klucz → label mapping)
5. Komunikaty z Softra API (Phase 17 zajmie się mapowaniem — w tym planie NIE ruszamy error message coming FROM Softra). Owijamy tylko nasze własne błędy.
6. Użyj konsystentnie `'carei-reservation'` jako textdomain — bez wyjątków.
Unikaj: globalnego find/replace bez kontekstu — każdy string wymaga decyzji czy jest user-facing czy nie. Nie zmieniaj logiki biznesowej, tylko opakowanie tekstu.
</action>
<verify>
1. `grep -rn "esc_html__\|__(\|esc_attr__" wp-content/plugins/carei-reservation/includes/ | wc -l` → >= 60 wystąpień
2. `grep -rn "load_plugin_textdomain" wp-content/plugins/carei-reservation/` → 1 wystąpienie w bootstrap
3. Ręcznie otwórz stronę z modalem po polsku → każdy tekst identyczny jak przed zmianą (nic się nie przetłumaczyło bo .po jeszcze nie ma)
4. Otwórz wp-admin → Rezerwacje → lista + szczegóły → wszystkie etykiety po PL identyczne
5. `php -l` na każdym z 8 plików → No syntax errors
</verify>
<done>AC-1 satysfakcjonowane.</done>
</task>
<task type="auto">
<name>Task 2: JS i18n — migracja stringów do careiI18n przez wp_localize_script</name>
<files>
wp-content/plugins/carei-reservation/assets/js/carei-reservation.js,
wp-content/plugins/carei-reservation/includes/class-elementor-widget.php
</files>
<action>
1. W `class-elementor-widget.php` — w metodzie enqueue skryptów (tam gdzie `wp_enqueue_script('carei-reservation', ...)` ) dodać **po** enqueue:
```php
wp_localize_script( 'carei-reservation', 'careiI18n', array(
'selectSegment' => esc_html__( 'Wybierz segment', 'carei-reservation' ),
'selectBranch' => esc_html__( 'Wybierz oddział', 'carei-reservation' ),
'selectDates' => esc_html__( 'Wybierz daty', 'carei-reservation' ),
'loading' => esc_html__( 'Ładowanie...', 'carei-reservation' ),
'errorNetwork' => esc_html__( 'Błąd połączenia. Spróbuj ponownie.', 'carei-reservation' ),
'errorRequired' => esc_html__( 'To pole jest wymagane', 'carei-reservation' ),
// ...wszystkie pozostałe stringi użyte w JS
) );
```
**Uwaga:** jeśli widget renderuje się w więcej niż jednym miejscu (search widget też ładuje ten sam JS?), upewnij się że `wp_localize_script` leci tylko raz per żądanie (handle `carei-reservation` jest globalny).
2. W `carei-reservation.js` — odnaleźć WSZYSTKIE polskie stringi literalne i zamienić na odwołania do `careiI18n.kluczX`:
- Polskie znaki diakrytyczne (ąćęłńóśźż) są dobrym pierwotnym filtrem do wyszukania
- Etykiety w `buildExtraCard`, nagłówki sekcji, komunikaty walidacji, błędy API, teksty przycisków, placeholdery setowane z JS, toast/error summary
- Stringi budowane dynamicznie (`'Pakiet ochronny: ' + name + ' — ' + price`) → użyj wrappera z template string: `careiI18n.protectionLine.replace('%name%', name).replace('%price%', price)` i podobnie w PHP `sprintf()` nie jest potrzebny po stronie JS — użyj prostego `.replace()` z placeholderami `%name%`/`%days%`/`%total%`. Alternatywnie: przetłumaczony szablon z `{name}` / `{days}`.
- Lista krajów, dni tygodnia, nazwy miesięcy (jeśli są hardkodowane PL) → do `careiI18n` albo do natywnego `Intl.DateTimeFormat(locale)` tam gdzie to możliwe
- Konsol.log/error → **NIE ruszaj** (to techniczne)
3. Dla stringów z formatowaniem liczbowo-pluralnym (np. "1 doba" / "2 doby" / "5 dób") zdefiniuj w careiI18n 3 warianty (`dayOne`, `dayFew`, `dayMany`) i w JS napisz pomocnika `pluralPl(n, one, few, many)` który używa reguł polskich. Dla EN wystarczy `dayOne` / `dayOther`.
4. Końcowa weryfikacja: `grep -nE "[ąćęłńóśźż]" wp-content/plugins/carei-reservation/assets/js/carei-reservation.js` → zero wyników (poza komentarzami jeśli są).
Unikaj:
- Zmiany logiki biznesowej (walidacja, flow booking, API calls) — tylko tekst
- Tworzenia dziesięciu funkcji pomocniczych — jedna `t(key)` z fallbackiem do `key` wystarczy
- Zmiany `console.*` komunikatów (techniczne, nie user-facing)
- Nie twórz duplikatów stringów — jeśli „Wybierz oddział" występuje w 3 miejscach, ma 1 klucz
</action>
<verify>
1. `grep -cP "[ąćęłńóśźż]" wp-content/plugins/carei-reservation/assets/js/carei-reservation.js` → 0 (lub tylko w komentarzach)
2. Otwórz modal w przeglądarce → `window.careiI18n` w console → obiekt z kluczami
3. Kliknij przez cały flow rezerwacji w języku polskim → tekst identyczny jak przed Task 2
4. DevTools Network → brak 404 dla `carei-reservation.js`
5. `node --check wp-content/plugins/carei-reservation/assets/js/carei-reservation.js` → OK (walidacja składni)
</verify>
<done>AC-2 satysfakcjonowane.</done>
</task>
<task type="auto">
<name>Task 3: Generate .pot file w languages/</name>
<files>wp-content/plugins/carei-reservation/languages/carei-reservation.pot</files>
<action>
1. Opcja A (preferowana jeśli dostępne WP-CLI na serwerze):
```bash
wp i18n make-pot wp-content/plugins/carei-reservation wp-content/plugins/carei-reservation/languages/carei-reservation.pot --domain=carei-reservation
```
2. Opcja B (ręcznie, jeśli brak WP-CLI):
- Skan wszystkich plików PHP (8 sztuk) i wyciągnięcie wszystkich `__()`, `esc_html__()`, `esc_attr__()` z textdomain `carei-reservation`
- Dodatkowo wyciągnięcie kluczy z `careiI18n` w `class-elementor-widget.php` (bo są też owinięte w `esc_html__`)
- Stworzenie pliku `.pot` zgodnie z formatem gettext:
```
# Copyright (C) 2026 Carei
# This file is distributed under the same license as the Carei Reservation plugin.
msgid ""
msgstr ""
"Project-Id-Version: Carei Reservation 1.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-04-22\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: manual\n"
"Language-Team: \n"
#: includes/class-elementor-widget.php:XXX
msgid "Złóż zapytanie o rezerwację"
msgstr ""
[...kolejne wpisy...]
```
- Każdy msgid pojawia się raz (deduplikacja), wszystkie msgstr puste
3. Jeśli opcja A zadziała → zweryfikuj liczbę wpisów (`grep -c "^msgid " plik.pot` >= 80)
4. Jeśli używamy B → spróbuj użyć **Loco Translate** (plugin w WP) do skanu i wygenerowania .pot — to najszybsza ścieżka w praktyce (Loco Translate → Plugins → Carei Reservation → Create template). Wtedy Task 3 staje się głównie zadaniem w wp-admin (nie w kodzie), i plik pojawia się w `languages/carei-reservation.pot` generowany przez Loco.
Unikaj: ręcznego edytowania .pot jeśli WP-CLI lub Loco dostępne. Automatyczne narzędzie nie przegapi stringa.
</action>
<verify>
1. Plik `languages/carei-reservation.pot` istnieje
2. `grep -c "^msgid " wp-content/plugins/carei-reservation/languages/carei-reservation.pot` → >= 80
3. Plik kończy się pustą linią i ma poprawny nagłówek (UTF-8)
4. Otwórz w POEdit / text editor → czy widoczne są stringi w formacie `msgid "Wybierz oddział"` z pustym `msgstr ""`
</verify>
<done>AC-3 satysfakcjonowane: .pot gotowy dla Phase 18 (tłumaczenie).</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<what-built>
- 8 plików PHP z hardkodowanymi PL stringami przeniesionymi do `__()`/`esc_html__()`/`esc_attr__()` z textdomain `carei-reservation`
- Bootstrap `carei-reservation.php` ładuje textdomain przez `load_plugin_textdomain` na `plugins_loaded`
- `carei-reservation.js` bez hardkodowanych PL — wszystkie stringi przez `careiI18n` globalny obiekt
- `class-elementor-widget.php` robi `wp_localize_script('carei-reservation', 'careiI18n', [...])` z PHP-owymi tłumaczeniami
- `languages/carei-reservation.pot` zawiera >= 80 wpisów gotowych do tłumaczenia
- **Strona po polsku działa identycznie jak przed Phase 16** — to jest kluczowe: żaden tekst się nie zmienia, zmienia się TYLKO sposób jego dostarczenia do frontendu
</what-built>
<how-to-verify>
1. Wypchnij cały plugin przez SFTP (całe `wp-content/plugins/carei-reservation/`)
2. W wp-admin: Plugins → deaktywuj i aktywuj ponownie „Carei Reservation" (żeby upewnić się że bootstrap nie crashuje)
3. Otwórz stronę z modalem rezerwacji PO POLSKU (Polylang = PL)
4. Pełen flow:
a. Wypełnij krok 1: segment, daty, oddział, klasa
b. Sprawdź sekcję „Pakiety ochronne" (SOFT/PREMIUM) — tekst identyczny
c. Sprawdź sekcję „Opcje dodatkowe"
d. Sekcja „Wyjazd zagraniczny" — wyszukiwarka krajów
e. Krok 2: podsumowanie → złóż rezerwację
f. Success view
5. Sprawdź hero search form (mini formularz w hero) — etykiety, placeholder, CTA
6. Sprawdź widgety: mapa Polski (tooltipy), grid miast, grid oddziałów — wszystkie teksty po PL
7. Wejdź w wp-admin → Rezerwacje:
- Lista z kolumnami, filtrem statusu — etykiety po PL
- Kliknij rezerwację → meta box szczegółów po PL
- Status dropdown (nowe/przeczytane/zrealizowane) po PL
8. Wejdź w wp-admin → Rezerwacje → Pakiety ochronne — formularz edycji SOFT/PREMIUM — etykiety po PL
9. DevTools Console (na stronie frontowej):
- `window.careiI18n` → obiekt z kluczami
- `typeof window.careiI18n.selectBranch` → `'string'` (lub jakikolwiek klucz który był migrowany)
10. Sprawdź plik `wp-content/plugins/carei-reservation/languages/carei-reservation.pot` — otwórz, zobacz stringi
11. (Opcjonalnie) Zainstaluj Loco Translate → Plugins → Carei Reservation → zobacz że plugin jest rozpoznany jako "translatable"
**Kryterium przejścia:** PL działa IDENTYCZNIE jak przed zmianą, zero regresji tekstowych.
</how-to-verify>
<resume-signal>Napisz "approved" aby zamknąć plan, albo opisz które miejsca pokazują nietłumaczone / regresyjne stringi.</resume-signal>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Logika biznesowa pluginu: `class-softra-api.php` (API calls, JWT auth, caching) — nie owijamy stringów technicznych ani nie zmieniamy flow
- Slugi CPT (`carei_reservation`), kluczy meta (`_carei_*`), nazw taxonomii, statusów w bazie (`nowe`, `przeczytane`, `zrealizowane`)
- Struktura payloadów wysyłanych do Softra API (klucze JSON, wartości boolean, nazwy pól)
- `class-softra-api.php` — ten plik nie ma user-facing stringów; zostaje nietknięty (mapowanie komunikatów z Softra = Phase 17)
- Hooki, nazwy akcji, nazwy filtrów WordPress
- Logika Phase 13 (pakiety ochronne): UI labels owijamy, ale struktura danych (REST `/protection-packages`, meta `_carei_protection_package`) bez zmian
- Logika Phase 15 (drop Softra-insurance): filtr pozostaje, komunikaty JS też owijamy w careiI18n
## SCOPE LIMITS
- Nie generujemy tłumaczeń EN w tym planie — tylko `.pot`. Phase 18 zajmie się `.po`/`.mo`
- Nie dodajemy dwujęzycznych pól w panelu pakietów ochronnych — Phase 17
- Nie mapujemy komunikatów z Softra API — Phase 17
- Nie tłumaczymy treści dynamicznych z bazy (np. nazwy pakietów w DB zostają po PL na tym etapie — zmieni to Phase 17)
- Nie dotykamy CSS
- Nie dotykamy treści w Elementorze (strony, widgety natywne) — te już są tłumaczone przez Polylang addon
</boundaries>
<verification>
Przed zamknięciem planu:
- [ ] `grep -rn "load_plugin_textdomain" wp-content/plugins/carei-reservation/carei-reservation.php` → 1 wynik
- [ ] `grep -rnE "__\(|esc_html__|esc_attr__" wp-content/plugins/carei-reservation/includes/ | wc -l` → >= 60
- [ ] `grep -cP "[ąćęłńóśźż]" wp-content/plugins/carei-reservation/assets/js/carei-reservation.js` → 0 (lub tylko komentarze)
- [ ] `wp-content/plugins/carei-reservation/languages/carei-reservation.pot` istnieje, >= 80 wpisów msgid
- [ ] `php -l` dla wszystkich 8 plików → No syntax errors
- [ ] Strona PL działa bez regresji (checkpoint human-verify)
- [ ] AC-1, AC-2, AC-3 przeszły weryfikację
</verification>
<success_criteria>
- Wszystkie 3 auto tasks zakończone
- Checkpoint human-verify zatwierdzony ("approved")
- Brak regresji w języku polskim
- `.pot` gotowy do Phase 18
</success_criteria>
<output>
Po zakończeniu: `.paul/phases/16-i18n-plugin-refactor/16-01-SUMMARY.md`
</output>