docs(09): create phase plan for internationalization
Phase 09: Internationalization - 2 plans in 2 waves - 1 parallel (wave 1), 1 sequential (wave 2) - Ready for execution Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -94,7 +94,7 @@ Plans:
|
||||
- [x] 07-01: Captcha verification and inline validation
|
||||
|
||||
### Phase 8: Bug Fixes & Legacy Parity
|
||||
**Goal**: Fix session ID bug in error handling, integrate additional work sections (Montage, Schrank, Elektriker, Dübelarbeiten, Packarbeiten, Anfahrt) into form and email, add Sonstiges free text field
|
||||
**Goal**: Fix session ID bug in error handling, integrate additional work sections (Montage, Schrank, Elektriker, Dubelarbeiten, Packarbeiten, Anfahrt) into form and email, add Sonstiges free text field
|
||||
**Depends on**: Phase 7
|
||||
**Research**: Unlikely (internal fixes and integration of existing data)
|
||||
**Gap Closure**: Closes session bug, additional work sections, Sonstiges gaps from v1.0 audit
|
||||
@@ -110,11 +110,12 @@ Plans:
|
||||
**Depends on**: Phase 8
|
||||
**Research**: Unlikely (WordPress i18n is well-documented)
|
||||
**Gap Closure**: Closes REQ-7 (i18n support) from v1.0 audit
|
||||
**Plans**: 0/1
|
||||
**Status**: Not started
|
||||
**Plans**: 2 plans
|
||||
**Status**: Planning complete
|
||||
|
||||
Plans:
|
||||
- [ ] 09-01: Wrap strings in gettext, generate translation files, load text domain
|
||||
- [ ] 09-01-PLAN.md -- i18n infrastructure, text domain loading, and admin/infrastructure string wrapping
|
||||
- [ ] 09-02-PLAN.md -- Form-facing string wrapping, JS localization, email locale forcing, translation files
|
||||
|
||||
## Progress
|
||||
|
||||
@@ -128,4 +129,4 @@ Plans:
|
||||
| 6. Email System | 1/1 | Complete | 2026-01-16 |
|
||||
| 7. Captcha & Validation | 1/1 | Complete | 2026-01-16 |
|
||||
| 8. Bug Fixes & Legacy Parity | 2/2 | Complete | 2026-02-06 |
|
||||
| 9. Internationalization | 0/1 | Not started | - |
|
||||
| 9. Internationalization | 0/2 | Planning complete | - |
|
||||
|
||||
232
.planning/phases/09-i18n/09-01-PLAN.md
Normal file
232
.planning/phases/09-i18n/09-01-PLAN.md
Normal file
@@ -0,0 +1,232 @@
|
||||
---
|
||||
phase: 09-i18n
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- umzugsliste.php
|
||||
- includes/class-cpt.php
|
||||
- includes/class-admin-menu.php
|
||||
- includes/class-settings.php
|
||||
- includes/class-captcha.php
|
||||
- includes/class-date-helpers.php
|
||||
autonomous: true
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "Plugin text domain is 'siegel-umzugsliste' matching folder name convention"
|
||||
- "Text domain loads on init hook via load_plugin_textdomain()"
|
||||
- "change_locale hook reloads plugin text domain (workaround for WP core bug #39210)"
|
||||
- "All admin-facing strings are wrapped in gettext functions"
|
||||
- "CPT labels are translatable"
|
||||
- "Settings page labels and descriptions are translatable"
|
||||
artifacts:
|
||||
- path: "umzugsliste.php"
|
||||
provides: "Text domain loading, change_locale hook, updated plugin header"
|
||||
contains: "load_plugin_textdomain"
|
||||
- path: "includes/class-cpt.php"
|
||||
provides: "Translatable CPT labels"
|
||||
contains: "__( '"
|
||||
- path: "includes/class-admin-menu.php"
|
||||
provides: "Translatable admin menu strings"
|
||||
contains: "__( '"
|
||||
- path: "includes/class-settings.php"
|
||||
provides: "Translatable settings page strings"
|
||||
contains: "esc_html__( '"
|
||||
- path: "includes/class-date-helpers.php"
|
||||
provides: "Translatable date label strings"
|
||||
contains: "__( '"
|
||||
key_links:
|
||||
- from: "umzugsliste.php"
|
||||
to: "languages/"
|
||||
via: "load_plugin_textdomain path"
|
||||
pattern: "load_plugin_textdomain.*siegel-umzugsliste.*languages"
|
||||
- from: "umzugsliste.php"
|
||||
to: "change_locale action"
|
||||
via: "add_action hook"
|
||||
pattern: "add_action.*change_locale"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Set up i18n infrastructure and wrap all admin/infrastructure PHP strings in gettext functions.
|
||||
|
||||
Purpose: Establishes the text domain loading foundation that all translations depend on, and wraps admin-side strings (CPT labels, settings page, admin menu, date selectors) in gettext functions so they become translatable. This is the prerequisite for form-facing string wrapping and translation file generation in Plan 02.
|
||||
|
||||
Output: Updated plugin header with correct text domain, text domain loading on init, change_locale hook workaround, and all admin/infrastructure strings wrapped in appropriate gettext functions (`__()`, `_e()`, `esc_html__()`, `esc_attr__()`).
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@~/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/09-i18n/09-CONTEXT.md
|
||||
@.planning/phases/09-i18n/09-RESEARCH.md
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Text domain fix, loading infrastructure, and change_locale hook</name>
|
||||
<files>umzugsliste.php</files>
|
||||
<action>
|
||||
In `umzugsliste.php`:
|
||||
|
||||
1. **Fix plugin header** - Change `Text Domain: umzugsliste` to `Text Domain: siegel-umzugsliste`. Keep `Domain Path: /languages`.
|
||||
|
||||
2. **Add text domain loading function** - Create a function `siegel_umzugsliste_load_textdomain()` that calls `load_plugin_textdomain( 'siegel-umzugsliste', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' )`. Hook it on `init` action. Place this BEFORE the `Umzugsliste` class definition so it runs early. Use priority 1 to ensure it loads before plugin init.
|
||||
|
||||
3. **Add change_locale hook workaround** - Add an action on `change_locale` that unloads and reloads the text domain. This is the workaround for WordPress core bug #39210 where `switch_to_locale()` doesn't reload plugin translations:
|
||||
|
||||
```php
|
||||
add_action( 'change_locale', function() {
|
||||
unload_textdomain( 'siegel-umzugsliste' );
|
||||
load_plugin_textdomain(
|
||||
'siegel-umzugsliste',
|
||||
false,
|
||||
dirname( plugin_basename( __FILE__ ) ) . '/languages'
|
||||
);
|
||||
} );
|
||||
```
|
||||
|
||||
Place this right after the `siegel_umzugsliste_load_textdomain` function and its `add_action` call.
|
||||
|
||||
**Important:** The text domain string must always be the literal `'siegel-umzugsliste'` -- never a variable or constant. This is a WordPress requirement for POT extraction tools.
|
||||
</action>
|
||||
<verify>
|
||||
Grep the file for `Text Domain: siegel-umzugsliste` (header present).
|
||||
Grep for `load_plugin_textdomain.*siegel-umzugsliste` (loading function present).
|
||||
Grep for `change_locale` (hook workaround present).
|
||||
Grep for `unload_textdomain.*siegel-umzugsliste` (unload before reload).
|
||||
Confirm no syntax errors: `php -l umzugsliste.php`
|
||||
</verify>
|
||||
<done>Plugin header declares text domain as siegel-umzugsliste, text domain loads on init, change_locale hook reloads text domain for switch_to_locale() compatibility.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Wrap admin and infrastructure strings in gettext</name>
|
||||
<files>
|
||||
includes/class-cpt.php
|
||||
includes/class-admin-menu.php
|
||||
includes/class-settings.php
|
||||
includes/class-captcha.php
|
||||
includes/class-date-helpers.php
|
||||
</files>
|
||||
<action>
|
||||
Wrap all user-facing strings in these files using gettext functions with text domain `'siegel-umzugsliste'`. Use the appropriate function for each context:
|
||||
|
||||
- `__()` for strings returned as values (e.g., array values, function returns)
|
||||
- `_e()` for strings echoed directly in templates (NOT used in these files much)
|
||||
- `esc_html__()` for strings output in HTML content context
|
||||
- `esc_attr__()` for strings output in HTML attribute context
|
||||
- `esc_html_e()` for echoed strings in HTML content
|
||||
|
||||
**class-cpt.php** - Wrap all CPT label strings:
|
||||
```php
|
||||
$labels = array(
|
||||
'name' => __( 'Entries', 'siegel-umzugsliste' ),
|
||||
'singular_name' => __( 'Entry', 'siegel-umzugsliste' ),
|
||||
'menu_name' => __( 'Entries', 'siegel-umzugsliste' ),
|
||||
'name_admin_bar' => __( 'Entry', 'siegel-umzugsliste' ),
|
||||
'add_new' => __( 'Add New', 'siegel-umzugsliste' ),
|
||||
'add_new_item' => __( 'Add New Entry', 'siegel-umzugsliste' ),
|
||||
'new_item' => __( 'New Entry', 'siegel-umzugsliste' ),
|
||||
'edit_item' => __( 'Edit Entry', 'siegel-umzugsliste' ),
|
||||
'view_item' => __( 'View Entry', 'siegel-umzugsliste' ),
|
||||
'all_items' => __( 'All Entries', 'siegel-umzugsliste' ),
|
||||
'search_items' => __( 'Search Entries', 'siegel-umzugsliste' ),
|
||||
'not_found' => __( 'No entries found', 'siegel-umzugsliste' ),
|
||||
'not_found_in_trash' => __( 'No entries found in Trash', 'siegel-umzugsliste' ),
|
||||
);
|
||||
```
|
||||
Note: CPT labels use ENGLISH as source strings (WordPress convention per CONTEXT.md). The German translations will go in the .po file.
|
||||
|
||||
**class-admin-menu.php** - Wrap menu page titles and menu titles:
|
||||
- `'Umzugsliste'` page titles -> `__( 'Moving List', 'siegel-umzugsliste' )`
|
||||
- `'Eintraege'` -> `__( 'Entries', 'siegel-umzugsliste' )`
|
||||
- `'Einstellungen'` -> `__( 'Settings', 'siegel-umzugsliste' )`
|
||||
|
||||
**class-settings.php** - Wrap all settings labels, descriptions, and section titles. There are many strings here:
|
||||
- Section titles: `'Email-Einstellungen'` -> `__( 'Email Settings', 'siegel-umzugsliste' )`
|
||||
- Section descriptions: `'Konfigurieren Sie die E-Mail-Adresse...'` -> `esc_html__( 'Configure the email address for form inquiries.', 'siegel-umzugsliste' )`
|
||||
- Field labels: `'Empfaenger-E-Mail'` -> `__( 'Receiver Email', 'siegel-umzugsliste' )`
|
||||
- Field descriptions in `<p class="description">` tags: use `esc_html__()`
|
||||
- Select option labels: `'Kein Captcha'` -> `__( 'No Captcha', 'siegel-umzugsliste' )`
|
||||
- Settings page title: `'Umzugsliste Einstellungen'` -> `esc_html__( 'Moving List Settings', 'siegel-umzugsliste' )`
|
||||
|
||||
Use English as source strings. Full list of settings strings to wrap:
|
||||
- `'Email-Einstellungen'` -> `'Email Settings'`
|
||||
- `'Konfigurieren Sie die E-Mail-Adresse fuer Formularanfragen.'` -> `'Configure the email address for form inquiries.'`
|
||||
- `'Empfaenger-E-Mail'` -> `'Receiver Email'`
|
||||
- `'Die E-Mail-Adresse, an die Formularanfragen gesendet werden.'` -> `'The email address where form inquiries will be sent.'`
|
||||
- `'Captcha-Einstellungen'` -> `'Captcha Settings'`
|
||||
- `'Waehlen Sie einen Captcha-Anbieter zum Schutz vor Spam.'` -> `'Choose a captcha provider to protect against spam.'`
|
||||
- `'Captcha-Anbieter'` -> `'Captcha Provider'`
|
||||
- `'Kein Captcha'` -> `'No Captcha'`
|
||||
- `'Waehlen Sie einen Captcha-Dienst oder deaktivieren Sie Captcha.'` -> `'Choose a captcha service or disable captcha.'`
|
||||
- `'Der Site Key von Ihrem Captcha-Anbieter.'` -> `'The site key from your captcha provider.'`
|
||||
- `'Der Secret Key von Ihrem Captcha-Anbieter.'` -> `'The secret key from your captcha provider.'`
|
||||
- `'Formular-Einstellungen'` -> `'Form Settings'`
|
||||
- `'Konfigurieren Sie das Verhalten des Formulars.'` -> `'Configure the form behavior.'`
|
||||
- `'Danke-Seite URL'` -> `'Thank You Page URL'`
|
||||
- `'Die URL, zu der nach erfolgreicher Formularuebermittlung weitergeleitet wird.'` -> `'The URL to redirect to after successful form submission.'`
|
||||
- `'Umzugsliste Einstellungen'` -> `'Moving List Settings'`
|
||||
|
||||
**class-captcha.php** - No user-facing strings to wrap (captcha error message is in class-form-handler.php).
|
||||
|
||||
**class-date-helpers.php** - Wrap the label strings:
|
||||
- `'Tag'` -> `__( 'Day', 'siegel-umzugsliste' )`
|
||||
- `'Monat'` -> `__( 'Month', 'siegel-umzugsliste' )`
|
||||
- `'Jahr'` -> `__( 'Year', 'siegel-umzugsliste' )`
|
||||
|
||||
For date helpers, the label strings are embedded in HTML string concatenation. Wrap them with `esc_html__()` for proper escaping:
|
||||
```php
|
||||
$html = '<div class="small-4 columns"><label>' . esc_html__( 'Day', 'siegel-umzugsliste' ) . '</label><select name="day" class="Stil2">';
|
||||
```
|
||||
|
||||
**CRITICAL:** All strings must use English as the source language per the CONTEXT.md decision. The German translations will be provided in the .po file created in Plan 02.
|
||||
</action>
|
||||
<verify>
|
||||
For each file, run `php -l` to confirm no syntax errors.
|
||||
Grep each file for `'siegel-umzugsliste'` to confirm text domain is present.
|
||||
Grep class-cpt.php for `__( '` to confirm labels are wrapped.
|
||||
Grep class-settings.php for `esc_html__( '` to confirm descriptions are wrapped.
|
||||
Grep class-date-helpers.php for `esc_html__( '` to confirm labels are wrapped.
|
||||
Confirm no bare German strings remain as labels in these files (spot check: no unquoted `Einstellungen`, `Empfaenger`, `Konfigurieren` outside of gettext calls).
|
||||
</verify>
|
||||
<done>All admin-facing strings in CPT labels, admin menu, settings page, and date helpers are wrapped in appropriate gettext functions with 'siegel-umzugsliste' text domain, using English source strings.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
1. `php -l umzugsliste.php` -- no syntax errors
|
||||
2. `php -l includes/class-cpt.php` -- no syntax errors
|
||||
3. `php -l includes/class-admin-menu.php` -- no syntax errors
|
||||
4. `php -l includes/class-settings.php` -- no syntax errors
|
||||
5. `php -l includes/class-date-helpers.php` -- no syntax errors
|
||||
6. Grep for old text domain: `grep -r "umzugsliste'" umzugsliste.php includes/` should NOT find the bare old text domain `'umzugsliste'` in any gettext function calls (only in option names, post type slugs, etc.)
|
||||
7. Grep for new text domain: `grep -r "siegel-umzugsliste" umzugsliste.php includes/` finds multiple matches across all modified files
|
||||
8. Plugin header contains `Text Domain: siegel-umzugsliste`
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Plugin header text domain is `siegel-umzugsliste`
|
||||
- `load_plugin_textdomain()` is called on `init` with correct parameters
|
||||
- `change_locale` hook workaround is registered for `switch_to_locale()` compatibility
|
||||
- All CPT labels in class-cpt.php use `__()` with English source strings
|
||||
- All admin menu strings in class-admin-menu.php use `__()` with English source strings
|
||||
- All settings page strings in class-settings.php use appropriate gettext functions with English source strings
|
||||
- Date helper labels in class-date-helpers.php use `esc_html__()` with English source strings
|
||||
- All PHP files pass `php -l` syntax check
|
||||
- Text domain is always the literal string `'siegel-umzugsliste'` (never variable/constant)
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/09-i18n/09-01-SUMMARY.md`
|
||||
</output>
|
||||
429
.planning/phases/09-i18n/09-02-PLAN.md
Normal file
429
.planning/phases/09-i18n/09-02-PLAN.md
Normal file
@@ -0,0 +1,429 @@
|
||||
---
|
||||
phase: 09-i18n
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 2
|
||||
depends_on: ["09-01"]
|
||||
files_modified:
|
||||
- includes/class-furniture-data.php
|
||||
- includes/class-form-renderer.php
|
||||
- includes/class-form-handler.php
|
||||
- includes/class-email-generator.php
|
||||
- includes/class-shortcode.php
|
||||
- assets/js/form.js
|
||||
- languages/siegel-umzugsliste.pot
|
||||
- languages/siegel-umzugsliste-de_DE.po
|
||||
- languages/siegel-umzugsliste-de_DE.mo
|
||||
autonomous: true
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "All form-facing strings are wrapped in gettext functions"
|
||||
- "JavaScript validation messages come from PHP via wp_localize_script"
|
||||
- "Email content is always generated in German regardless of site locale"
|
||||
- "Furniture names and room labels are translatable in the form"
|
||||
- "German .po/.mo files ship with the plugin for out-of-box German support"
|
||||
- "POT file exists for Loco Translate compatibility"
|
||||
- "Validation error messages are translatable"
|
||||
artifacts:
|
||||
- path: "includes/class-furniture-data.php"
|
||||
provides: "Translatable furniture names and room labels"
|
||||
contains: "__( '"
|
||||
- path: "includes/class-form-renderer.php"
|
||||
provides: "Translatable form UI strings"
|
||||
contains: "esc_html__( '"
|
||||
- path: "includes/class-form-handler.php"
|
||||
provides: "Translatable validation errors, email locale forcing"
|
||||
contains: "switch_to_locale"
|
||||
- path: "includes/class-shortcode.php"
|
||||
provides: "wp_localize_script for JS translation strings"
|
||||
contains: "wp_localize_script"
|
||||
- path: "assets/js/form.js"
|
||||
provides: "Uses localized strings from PHP instead of hardcoded German"
|
||||
contains: "umzugslisteL10n"
|
||||
- path: "languages/siegel-umzugsliste.pot"
|
||||
provides: "POT template for Loco Translate"
|
||||
- path: "languages/siegel-umzugsliste-de_DE.po"
|
||||
provides: "German translations source file"
|
||||
- path: "languages/siegel-umzugsliste-de_DE.mo"
|
||||
provides: "Compiled German translations"
|
||||
key_links:
|
||||
- from: "includes/class-form-handler.php"
|
||||
to: "switch_to_locale('de_DE')"
|
||||
via: "locale switch before email generation"
|
||||
pattern: "switch_to_locale.*de_DE"
|
||||
- from: "includes/class-form-handler.php"
|
||||
to: "restore_previous_locale()"
|
||||
via: "locale restore after email generation"
|
||||
pattern: "restore_previous_locale"
|
||||
- from: "includes/class-shortcode.php"
|
||||
to: "assets/js/form.js"
|
||||
via: "wp_localize_script passing translated strings"
|
||||
pattern: "wp_localize_script.*umzugslisteL10n"
|
||||
- from: "assets/js/form.js"
|
||||
to: "umzugslisteL10n"
|
||||
via: "references global object for translated strings"
|
||||
pattern: "umzugslisteL10n\\."
|
||||
---
|
||||
|
||||
<objective>
|
||||
Wrap all form-facing PHP strings in gettext, localize JavaScript validation messages, force German locale for email generation, and generate translation files (POT/PO/MO).
|
||||
|
||||
Purpose: Completes the i18n implementation by making the public-facing form translatable, ensuring emails always stay in German (sacred requirement for office staff), providing JS validation messages via `wp_localize_script()`, and shipping ready-to-use German translation files. This closes REQ-7 (i18n support) from the v1.0 audit.
|
||||
|
||||
Output: Fully internationalized plugin with German and English support, POT template for Loco Translate, and compiled German .mo file for out-of-box German display.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@~/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@~/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/09-i18n/09-CONTEXT.md
|
||||
@.planning/phases/09-i18n/09-RESEARCH.md
|
||||
@.planning/phases/09-i18n/09-01-SUMMARY.md
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Wrap form-facing strings and JS localization</name>
|
||||
<files>
|
||||
includes/class-furniture-data.php
|
||||
includes/class-form-renderer.php
|
||||
includes/class-form-handler.php
|
||||
includes/class-email-generator.php
|
||||
includes/class-shortcode.php
|
||||
assets/js/form.js
|
||||
</files>
|
||||
<action>
|
||||
**class-furniture-data.php** - Wrap room names and furniture item names in `__()`:
|
||||
|
||||
Room names in `get_rooms()`:
|
||||
```php
|
||||
return array(
|
||||
'wohnzimmer' => __( 'Living Room', 'siegel-umzugsliste' ),
|
||||
'schlafzimmer' => __( 'Bedroom', 'siegel-umzugsliste' ),
|
||||
'arbeitszimmer' => __( 'Study', 'siegel-umzugsliste' ),
|
||||
'bad' => __( 'Bathroom', 'siegel-umzugsliste' ),
|
||||
'kueche_esszimmer' => __( 'Kitchen/Dining Room', 'siegel-umzugsliste' ),
|
||||
'kinderzimmer' => __( 'Children\'s Room', 'siegel-umzugsliste' ),
|
||||
'keller' => __( 'Basement/Storage/Garage', 'siegel-umzugsliste' ),
|
||||
);
|
||||
```
|
||||
|
||||
Furniture item names in `get_all_furniture_data()`: Wrap every `'name'` value in `__()`. For example:
|
||||
```php
|
||||
array( 'name' => __( 'Sofa, Couch, per seat', 'siegel-umzugsliste' ), 'cbm' => 0.4, 'montage' => true ),
|
||||
```
|
||||
|
||||
Do this for ALL furniture items across ALL rooms. Use English source strings. There are ~90 items total. Be thorough -- every single `'name' => '...'` must become `'name' => __( '...', 'siegel-umzugsliste' )`.
|
||||
|
||||
Additional work section labels and field names in `get_additional_work()`: Wrap all `'label'` and `'name'` values in `__()`. For example:
|
||||
```php
|
||||
'montage' => array(
|
||||
'label' => __( 'Assembly Work', 'siegel-umzugsliste' ),
|
||||
'fields' => array(
|
||||
array( 'name' => __( 'No assembly work required', 'siegel-umzugsliste' ), 'type' => 'checkbox' ),
|
||||
...
|
||||
),
|
||||
),
|
||||
```
|
||||
|
||||
Do NOT wrap the `'type'` or `'key'` values -- only `'label'` and `'name'`.
|
||||
|
||||
**class-form-renderer.php** - Wrap all hardcoded UI strings using English source strings:
|
||||
|
||||
- `'Umzugsliste'` (h1) -> `esc_html__( 'Moving List', 'siegel-umzugsliste' )`
|
||||
- `'Voraussichtlicher Umzugstermin'` (legend) -> `esc_html__( 'Expected Moving Date', 'siegel-umzugsliste' )`
|
||||
- The privacy policy paragraph: Leave the company address and contact info as-is (not translatable -- company-specific). Wrap only the translatable text: `'In unserer'` -> not needed, this is a static paragraph with a link. Actually, wrap the full sentence text around the link. Use `sprintf` with `__()`:
|
||||
```php
|
||||
printf(
|
||||
/* translators: %s: link to privacy policy */
|
||||
esc_html__( 'In our %s you can learn how Siegel Umzuege GmbH & Co. KG collects and uses your data.', 'siegel-umzugsliste' ),
|
||||
'<a href="http://siegel-umzug.de/datenschutz.html">' . esc_html__( 'Privacy Policy', 'siegel-umzugsliste' ) . '</a>'
|
||||
);
|
||||
```
|
||||
Wait -- actually, since the privacy paragraph is interleaved with HTML and company info, it's simpler to wrap just the key phrases. But the CONTEXT.md says English is source. Do the printf approach above.
|
||||
- `'Beladeadresse'` (h3) -> `esc_html__( 'Loading Address', 'siegel-umzugsliste' )`
|
||||
- `'Entladeadresse'` (h3) -> `esc_html__( 'Unloading Address', 'siegel-umzugsliste' )`
|
||||
- Address field labels: `'Name*'`, `'Strasse*'`, `'PLZ/Ort*'`, `'Geschoss'`, `'Telefon*'`, `'Telefax'`, `'Mobil'`, `'E-Mail*'`, `'Telefon'` -- wrap each in the `render_address_field()` call. Since labels are passed as parameters, wrap at the CALL SITE:
|
||||
```php
|
||||
self::render_address_field( __( 'Name*', 'siegel-umzugsliste' ), 'bName', true );
|
||||
```
|
||||
Actually, since these labels go into `esc_html()` inside `render_address_field()`, wrapping at the call site with `__()` is correct.
|
||||
- `'Lift'` label -> `__( 'Elevator', 'siegel-umzugsliste' )`
|
||||
- `'Nein'`/`'Ja'` radio labels in lift field -> `esc_html__( 'No', 'siegel-umzugsliste' )` / `esc_html__( 'Yes', 'siegel-umzugsliste' )`
|
||||
- `'*Pflichtfelder'` -> `esc_html__( '*Required fields', 'siegel-umzugsliste' )`
|
||||
- Table headers: `'Anzahl'`, `'Bezeichnung'`, `'qbm'`, `'Montage?'` -> wrap each in `esc_html__()`
|
||||
- `'Summe '` prefix in room totals footer -> `esc_html__( 'Total ', 'siegel-umzugsliste' )` (the room label is already translated from get_rooms)
|
||||
- `'Gesamtsumme'` heading -> `esc_html__( 'Grand Total', 'siegel-umzugsliste' )`
|
||||
- `'Gesamtsumme aller Zimmer'` -> `esc_html__( 'Grand total all rooms', 'siegel-umzugsliste' )`
|
||||
- `'Anfrage absenden'` button -> `esc_html__( 'Submit Request', 'siegel-umzugsliste' )`
|
||||
- `'Bitte korrigieren Sie folgende Fehler:'` -> `esc_html__( 'Please correct the following errors:', 'siegel-umzugsliste' )`
|
||||
- `'Sonstiges'` heading -> `esc_html__( 'Other', 'siegel-umzugsliste' )`
|
||||
- `'Weitere Hinweise oder Wuensche:'` label -> `esc_html__( 'Additional notes or requests:', 'siegel-umzugsliste' )`
|
||||
- `'Weitere Hinweise oder Wuensche...'` placeholder -> `esc_attr__( 'Additional notes or requests...', 'siegel-umzugsliste' )`
|
||||
- Radio labels in additional work: `'Abbau'`, `'Aufbau'`, `'Beides'` -> wrap in `esc_html__()`
|
||||
- `'Abbau'` -> `esc_html__( 'Disassembly', 'siegel-umzugsliste' )`
|
||||
- `'Aufbau'` -> `esc_html__( 'Assembly', 'siegel-umzugsliste' )`
|
||||
- `'Beides'` -> `esc_html__( 'Both', 'siegel-umzugsliste' )`
|
||||
- `'Anz.'` placeholder -> `esc_attr__( 'Qty.', 'siegel-umzugsliste' )`
|
||||
|
||||
Note: The `render_address_field()` method receives `$label` and uses `esc_html( $label )`. Since labels are now passed through `__()`, they'll be translated. No change needed to the method internals.
|
||||
|
||||
Similarly, `render_lift_field()` has inline labels -- update those inline.
|
||||
|
||||
**class-form-handler.php** - Wrap validation messages and error strings:
|
||||
|
||||
- `'Security verification failed. Please try again.'` -- this is already English, just wrap: `__( 'Security verification failed. Please try again.', 'siegel-umzugsliste' )`
|
||||
- Captcha error: `'Captcha-Verifizierung fehlgeschlagen...'` -> `__( 'Captcha verification failed. Please try again.', 'siegel-umzugsliste' )`
|
||||
- Required field labels in `$required_fields` array -- wrap each label:
|
||||
```php
|
||||
$required_fields = array(
|
||||
'bName' => __( 'Name (Loading Address)', 'siegel-umzugsliste' ),
|
||||
'bStrasse' => __( 'Street (Loading Address)', 'siegel-umzugsliste' ),
|
||||
'bort' => __( 'ZIP/City (Loading Address)', 'siegel-umzugsliste' ),
|
||||
'bTelefon' => __( 'Phone (Loading Address)', 'siegel-umzugsliste' ),
|
||||
'eName' => __( 'Name (Unloading Address)', 'siegel-umzugsliste' ),
|
||||
'eStrasse' => __( 'Street (Unloading Address)', 'siegel-umzugsliste' ),
|
||||
'eort' => __( 'ZIP/City (Unloading Address)', 'siegel-umzugsliste' ),
|
||||
);
|
||||
```
|
||||
- Validation error messages:
|
||||
- `'Pflichtfeld fehlt: '` -> Use `sprintf( __( 'Required field missing: %s', 'siegel-umzugsliste' ), $label )`
|
||||
- `'Ungueltige E-Mail-Adresse'` -> `__( 'Invalid email address', 'siegel-umzugsliste' )`
|
||||
- `'Umzugstermin fehlt'` -> `__( 'Moving date is missing', 'siegel-umzugsliste' )`
|
||||
- `'Bitte geben Sie mindestens eine Moebelmenge ein'` -> `__( 'Please enter at least one furniture quantity', 'siegel-umzugsliste' )`
|
||||
- Email failure wp_die message: Wrap the HTML strings. Use `sprintf` and `__()` for the error page:
|
||||
```php
|
||||
wp_die(
|
||||
'<h1>' . esc_html__( 'Email could not be sent', 'siegel-umzugsliste' ) . '</h1>'
|
||||
. '<p>' . esc_html__( 'Your request has been saved, but the email could not be sent.', 'siegel-umzugsliste' ) . '</p>'
|
||||
. '<p><strong>' . esc_html__( 'Please contact us by phone:', 'siegel-umzugsliste' ) . '</strong></p>'
|
||||
. '<p>Wiesbaden: <a href="tel:+4961122020">(06 11) 2 20 20</a><br>'
|
||||
. 'Mainz: <a href="tel:+49613122141">(0 61 31) 22 21 41</a></p>'
|
||||
. '<p><a href="' . esc_url( home_url() ) . '">' . esc_html__( 'Back to homepage', 'siegel-umzugsliste' ) . '</a></p>',
|
||||
esc_html__( 'Email Error', 'siegel-umzugsliste' )
|
||||
);
|
||||
```
|
||||
- CPT post title: `'Anfrage vom '` -> Use `sprintf( __( 'Request from %s - %s', 'siegel-umzugsliste' ), $date_string, $customer_name )`. Actually, since CPT titles are internal/admin, keep them simple. But they should still be translatable. Actually, the CPT title is just stored data -- it doesn't need translation. But for admin display it's nice. Use sprintf with `__()`.
|
||||
|
||||
**Email locale forcing in class-form-handler.php** - In the `send_email()` method, wrap the email generation call in locale switching:
|
||||
```php
|
||||
private function send_email( $entry_id, $data ) {
|
||||
// Force German locale for email generation
|
||||
switch_to_locale( 'de_DE' );
|
||||
|
||||
// Generate email HTML (all __() calls now return German)
|
||||
$email_html = Umzugsliste_Email_Generator::generate( $data );
|
||||
|
||||
// Email subject also in German
|
||||
$subject = 'Internetanfrage - Anfrage vom ' . date( 'd.m.Y H:i' );
|
||||
|
||||
// Restore original locale
|
||||
restore_previous_locale();
|
||||
|
||||
// ... rest of send_email (get receiver, headers, wp_mail) stays the same
|
||||
}
|
||||
```
|
||||
|
||||
The email subject line stays hardcoded in German -- do NOT wrap it in `__()`. The CONTEXT.md says email content always stays in German.
|
||||
|
||||
**class-email-generator.php** - Do NOT wrap strings in this file with gettext. The email generator's strings (table headers like 'Anzahl', 'Bezeichnung', 'qbm', 'Gesamt', 'Montage?', 'Beladeadresse', 'Entladeadresse', 'Gesamtsummen', 'Summe', 'Sonstiges', 'Voraussichtlicher Umzugstermin', the HTML title) are email content that must ALWAYS be in German. Since `send_email()` switches to German locale before calling `generate()`, any `__()` calls in the data arrays (furniture names, room labels from `get_rooms()`) will automatically return German translations. The static strings in the email generator are already in German and should stay that way -- do not touch them.
|
||||
|
||||
**class-shortcode.php** - Add `wp_localize_script()` to pass translated JS validation messages:
|
||||
|
||||
In the `enqueue_assets()` method, AFTER the `wp_enqueue_script()` call for `'umzugsliste-form'`, add:
|
||||
|
||||
```php
|
||||
wp_localize_script( 'umzugsliste-form', 'umzugslisteL10n', array(
|
||||
'fieldRequired' => __( 'This field is required', 'siegel-umzugsliste' ),
|
||||
'invalidEmail' => __( 'Please enter a valid email address', 'siegel-umzugsliste' ),
|
||||
'selectMovingDate' => __( 'Please select a complete moving date', 'siegel-umzugsliste' ),
|
||||
'enterFurnitureItem' => __( 'Please enter at least one furniture item', 'siegel-umzugsliste' ),
|
||||
) );
|
||||
```
|
||||
|
||||
**assets/js/form.js** - Replace hardcoded German validation messages with the localized strings:
|
||||
|
||||
- `'Dieses Feld ist erforderlich'` -> `umzugslisteL10n.fieldRequired`
|
||||
- `'Bitte geben Sie eine gueltige E-Mail-Adresse ein'` -> `umzugslisteL10n.invalidEmail`
|
||||
- `'Bitte waehlen Sie ein vollstaendiges Umzugsdatum'` -> `umzugslisteL10n.selectMovingDate`
|
||||
- `'Bitte geben Sie mindestens ein Moebelstueck ein'` -> `umzugslisteL10n.enterFurnitureItem`
|
||||
|
||||
Check that the `umzugslisteL10n` variable is accessed safely -- it should always be defined since `wp_localize_script` runs before the script loads, but a defensive fallback is good practice:
|
||||
```javascript
|
||||
var l10n = typeof umzugslisteL10n !== 'undefined' ? umzugslisteL10n : {
|
||||
fieldRequired: 'This field is required',
|
||||
invalidEmail: 'Please enter a valid email address',
|
||||
selectMovingDate: 'Please select a complete moving date',
|
||||
enterFurnitureItem: 'Please enter at least one furniture item'
|
||||
};
|
||||
```
|
||||
Place this at the top of the IIFE, then use `l10n.fieldRequired` etc.
|
||||
</action>
|
||||
<verify>
|
||||
Run `php -l` on all modified PHP files.
|
||||
Grep class-furniture-data.php for `__( '` -- should have matches for room names AND furniture item names.
|
||||
Grep class-form-renderer.php for `esc_html__( '` -- should have many matches.
|
||||
Grep class-form-handler.php for `switch_to_locale` -- should find the locale switching call.
|
||||
Grep class-form-handler.php for `restore_previous_locale` -- should find the restore call.
|
||||
Grep class-shortcode.php for `wp_localize_script` -- should find the localization call.
|
||||
Grep assets/js/form.js for `umzugslisteL10n` -- should find references to localized strings.
|
||||
Grep class-email-generator.php for `__( '` -- should find NO matches (email strings stay hardcoded German).
|
||||
Verify the email subject line is NOT wrapped in gettext.
|
||||
</verify>
|
||||
<done>All form-facing strings wrapped in gettext with English source strings. JS validation messages localized via wp_localize_script. Email generation forced to German locale via switch_to_locale/restore_previous_locale. Email generator strings remain hardcoded German.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Generate POT, PO, and MO translation files</name>
|
||||
<files>
|
||||
languages/siegel-umzugsliste.pot
|
||||
languages/siegel-umzugsliste-de_DE.po
|
||||
languages/siegel-umzugsliste-de_DE.mo
|
||||
</files>
|
||||
<action>
|
||||
1. **Create `languages/` directory** if it doesn't exist: `mkdir -p languages`
|
||||
|
||||
2. **Generate POT file** using WP-CLI:
|
||||
```bash
|
||||
cd /Users/vmiller/Local\ Sites/siegel/app/public/wp-content/plugins/Siegel-Umzugsliste
|
||||
wp i18n make-pot . languages/siegel-umzugsliste.pot --domain=siegel-umzugsliste
|
||||
```
|
||||
|
||||
If WP-CLI is not available or `wp i18n make-pot` fails, create the POT file manually by running the plugin through the WP environment:
|
||||
```bash
|
||||
# Try via the Local by WP site's WP-CLI
|
||||
cd "/Users/vmiller/Local Sites/siegel/app/public"
|
||||
wp i18n make-pot wp-content/plugins/Siegel-Umzugsliste wp-content/plugins/Siegel-Umzugsliste/languages/siegel-umzugsliste.pot --domain=siegel-umzugsliste
|
||||
```
|
||||
|
||||
If WP-CLI is truly unavailable, create the POT file manually with proper gettext format. The POT file must contain all translatable strings extracted from the PHP and JS files.
|
||||
|
||||
3. **Create German PO file** - Copy POT to PO and fill in German translations.
|
||||
|
||||
Use `msginit` if available:
|
||||
```bash
|
||||
msginit --input=languages/siegel-umzugsliste.pot --locale=de_DE --output-file=languages/siegel-umzugsliste-de_DE.po --no-translator
|
||||
```
|
||||
|
||||
Then edit the PO file to add German translations for every `msgid`. Every English source string must have its German `msgstr` filled in. Here are the key translations (be thorough -- translate EVERY string):
|
||||
|
||||
**Admin strings:**
|
||||
- "Entries" -> "Eintraege"
|
||||
- "Entry" -> "Eintrag"
|
||||
- "Add New" -> "Neu hinzufuegen"
|
||||
- "Add New Entry" -> "Neuen Eintrag hinzufuegen"
|
||||
- "New Entry" -> "Neuer Eintrag"
|
||||
- "Edit Entry" -> "Eintrag bearbeiten"
|
||||
- "View Entry" -> "Eintrag ansehen"
|
||||
- "All Entries" -> "Alle Eintraege"
|
||||
- "Search Entries" -> "Eintraege durchsuchen"
|
||||
- "No entries found" -> "Keine Eintraege gefunden"
|
||||
- "No entries found in Trash" -> "Keine Eintraege im Papierkorb gefunden"
|
||||
- "Moving List" -> "Umzugsliste"
|
||||
- "Settings" -> "Einstellungen"
|
||||
- "Email Settings" -> "Email-Einstellungen"
|
||||
- "Configure the email address for form inquiries." -> "Konfigurieren Sie die E-Mail-Adresse fuer Formularanfragen."
|
||||
- "Receiver Email" -> "Empfaenger-E-Mail"
|
||||
- (and ALL other settings strings from Plan 01...)
|
||||
|
||||
**Form strings:**
|
||||
- "Expected Moving Date" -> "Voraussichtlicher Umzugstermin"
|
||||
- "Loading Address" -> "Beladeadresse"
|
||||
- "Unloading Address" -> "Entladeadresse"
|
||||
- "Elevator" -> "Lift"
|
||||
- "Yes" -> "Ja"
|
||||
- "No" -> "Nein"
|
||||
- "Grand Total" -> "Gesamtsumme"
|
||||
- "Submit Request" -> "Anfrage absenden"
|
||||
- (and ALL other form renderer strings...)
|
||||
|
||||
**Room names:**
|
||||
- "Living Room" -> "Wohnzimmer"
|
||||
- "Bedroom" -> "Schlafzimmer"
|
||||
- "Study" -> "Arbeitszimmer"
|
||||
- "Bathroom" -> "Bad"
|
||||
- "Kitchen/Dining Room" -> "Kueche/Esszimmer"
|
||||
- "Children's Room" -> "Kinderzimmer"
|
||||
- "Basement/Storage/Garage" -> "Keller/Speicher/Garage"
|
||||
|
||||
**All ~90 furniture item names** - translate each English name back to the original German. The German translations are the original strings that were in the code before Plan 01/02 converted them to English. For example:
|
||||
- "Sofa, Couch, per seat" -> "Sofa, Couch, je Sitz"
|
||||
- "Seat elements, per seat" -> "Sitzelemente, je Sitz"
|
||||
- (etc. for ALL items)
|
||||
|
||||
**All additional work strings** - translate back to original German.
|
||||
|
||||
**Validation messages:**
|
||||
- "This field is required" -> "Dieses Feld ist erforderlich"
|
||||
- "Please enter a valid email address" -> "Bitte geben Sie eine gueltige E-Mail-Adresse ein"
|
||||
- "Please select a complete moving date" -> "Bitte waehlen Sie ein vollstaendiges Umzugsdatum"
|
||||
- "Please enter at least one furniture item" -> "Bitte geben Sie mindestens ein Moebelstueck ein"
|
||||
- (etc.)
|
||||
|
||||
**IMPORTANT:** Use proper German characters with UTF-8 encoding in the PO file. Use umlauts: ae->ä, oe->ö, ue->ü, ss->ß where appropriate. The PO file header must declare `Content-Type: text/plain; charset=UTF-8`.
|
||||
|
||||
4. **Compile MO file** from PO:
|
||||
```bash
|
||||
msgfmt languages/siegel-umzugsliste-de_DE.po -o languages/siegel-umzugsliste-de_DE.mo
|
||||
```
|
||||
|
||||
If `msgfmt` is not available (check with `which msgfmt`), try:
|
||||
- `brew install gettext` then use the brew-installed `msgfmt`
|
||||
- Or `/usr/local/opt/gettext/bin/msgfmt` (common Homebrew path)
|
||||
- Or `/opt/homebrew/opt/gettext/bin/msgfmt` (Apple Silicon Homebrew path)
|
||||
|
||||
If neither WP-CLI nor msgfmt are available, note this in the summary and the MO can be generated later.
|
||||
</action>
|
||||
<verify>
|
||||
Verify `languages/siegel-umzugsliste.pot` exists and contains translatable strings (grep for `msgid`).
|
||||
Verify `languages/siegel-umzugsliste-de_DE.po` exists and has German translations (grep for `msgstr` with non-empty values).
|
||||
Verify `languages/siegel-umzugsliste-de_DE.mo` exists (binary file, just check existence and non-zero size).
|
||||
Spot check: PO file contains translation for "Living Room" -> "Wohnzimmer".
|
||||
Spot check: PO file contains translation for "Submit Request" -> "Anfrage absenden".
|
||||
Spot check: PO file header has `charset=UTF-8`.
|
||||
Confirm POT file naming matches Loco Translate convention: `siegel-umzugsliste.pot`.
|
||||
</verify>
|
||||
<done>POT template file exists for Loco Translate auto-discovery. German PO file contains complete translations for all English source strings. Compiled MO file ships with plugin for out-of-box German support.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
1. All modified PHP files pass `php -l` syntax checks
|
||||
2. No hardcoded German strings remain in form-renderer, form-handler, furniture-data (except email-generator which is intentionally German)
|
||||
3. `class-email-generator.php` has NO gettext calls (email stays German)
|
||||
4. `class-form-handler.php` has `switch_to_locale('de_DE')` before email generation and `restore_previous_locale()` after
|
||||
5. `class-shortcode.php` calls `wp_localize_script()` with validation message translations
|
||||
6. `form.js` uses `umzugslisteL10n` variable for validation messages
|
||||
7. `languages/siegel-umzugsliste.pot` exists with extracted strings
|
||||
8. `languages/siegel-umzugsliste-de_DE.po` exists with German translations
|
||||
9. `languages/siegel-umzugsliste-de_DE.mo` exists (compiled)
|
||||
10. PO file has proper UTF-8 encoding with German umlauts
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- All user-facing form strings use gettext functions with English source strings and text domain 'siegel-umzugsliste'
|
||||
- Furniture names, room labels, additional work labels all wrapped in __()
|
||||
- JavaScript validation messages delivered via wp_localize_script, not hardcoded
|
||||
- Email generation wrapped in switch_to_locale('de_DE') / restore_previous_locale()
|
||||
- Email generator file (class-email-generator.php) contains zero gettext calls
|
||||
- Email subject line stays hardcoded German (not wrapped in gettext)
|
||||
- POT file generated and named siegel-umzugsliste.pot in languages/ directory
|
||||
- German PO file has complete translations for every msgid
|
||||
- MO file compiled from PO (or documented as pending if tools unavailable)
|
||||
- All PHP files pass syntax check
|
||||
- Plugin displays in German when site locale is de_DE
|
||||
- Plugin displays in English when site locale is en_US
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/09-i18n/09-02-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user