From 0274a4d0a1a27d354fb2bac954a878023542d891 Mon Sep 17 00:00:00 2001 From: Viktor Miller Date: Fri, 6 Feb 2026 23:31:07 +0900 Subject: [PATCH] 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 --- .planning/ROADMAP.md | 11 +- .planning/phases/09-i18n/09-01-PLAN.md | 232 +++++++++++++ .planning/phases/09-i18n/09-02-PLAN.md | 429 +++++++++++++++++++++++++ 3 files changed, 667 insertions(+), 5 deletions(-) create mode 100644 .planning/phases/09-i18n/09-01-PLAN.md create mode 100644 .planning/phases/09-i18n/09-02-PLAN.md diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 19be31a..8df1658 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -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 | - | diff --git a/.planning/phases/09-i18n/09-01-PLAN.md b/.planning/phases/09-i18n/09-01-PLAN.md new file mode 100644 index 0000000..eea9707 --- /dev/null +++ b/.planning/phases/09-i18n/09-01-PLAN.md @@ -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" +--- + + +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__()`). + + + +@~/.claude/get-shit-done/workflows/execute-plan.md +@~/.claude/get-shit-done/templates/summary.md + + + +@.planning/PROJECT.md +@.planning/ROADMAP.md +@.planning/STATE.md +@.planning/phases/09-i18n/09-CONTEXT.md +@.planning/phases/09-i18n/09-RESEARCH.md + + + + + + Task 1: Text domain fix, loading infrastructure, and change_locale hook + umzugsliste.php + +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. + + +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` + + Plugin header declares text domain as siegel-umzugsliste, text domain loads on init, change_locale hook reloads text domain for switch_to_locale() compatibility. + + + + Task 2: Wrap admin and infrastructure strings in gettext + + includes/class-cpt.php + includes/class-admin-menu.php + includes/class-settings.php + includes/class-captcha.php + includes/class-date-helpers.php + + +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 `

` 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 = '