# Multilingual Site Entry and Layout — request

Audience: AI coding agents first.

**Goal:** Ship automatic language-specific homepage/posts-page behavior, clear Page-role/layout UX, and structurally safe Site Editor translation through templates, template parts, navigation, and synced patterns.

**Canonical design:** `docs/specs/2026-08-09-multilingual-site-entry-and-layout-design.md`. Every invariant, failure rule, release gate, and non-goal in that spec is mandatory. This request pins implementation seams and acceptance; never weaken the design to reuse generic post translation.

## Context

Current gaps:

- `international-press-zone.php:126` starts full plugin on `plugins_loaded` priority 10; locale bootstrap required before that point does not exist.
- `includes/Core/Plugin.php::filter_language_root_request()` sends a language root to canonical `page_on_front`, not its target-language translation.
- `includes/Frontend/ContentFilter.php::filterPosts()` filters language after routing and cannot substitute special-page identity.
- `includes/API/PostsController.php::getPosts()` lacks homepage/posts-page/effective-layout metadata and canonical-group pagination.
- `includes/Translation/PostTypeRegistry.php::PROTECTED_TYPES` correctly excludes Site Editor entities. Preserve this exclusion.
- `includes/Translation/TranslationBridge.php::create_translation()` omits Page-template and hierarchy fidelity.
- `admin/src/pages/translations.js` defaults to Theme strings and has no Site Content route.
- backend structured translation handles posts, not stable segmented Site Content resources.

Target deployed WordPress is dev1. Local WordPress content is assistant fixture data and MUST NOT support claims about dev1 configuration.

## Files

### Plugin bootstrap, routing, URLs, SEO

- Modify `international-press-zone.php` — register minimal locale bootstrap before full priority-10 initialization.
- Create `includes/Frontend/LocaleBootstrap.php` — immediate hook registration only; lazy fail-closed context bootstrap.
- Create `includes/Frontend/LocaleContextCoordinator.php` — requested/effective language, locale switching, restoration, request cleanup.
- Create `includes/Frontend/LanguageRequestContext.php` — immutable request context value.
- Create `includes/Frontend/SpecialPageResolver.php` — canonical Reading-role detection and translated-role resolution.
- Create `includes/Frontend/EffectiveTemplateResolver.php` — native `front-page`/`home`/Page hierarchy reporting.
- Create `includes/Frontend/LocalizedURLResolver.php` — one canonical URL source for links, redirects, switcher, SEO, and sitemap.
- Modify `includes/Core/Plugin.php` — consume bootstrap context; replace source-ID root normalization; register exact lifecycle/SEO seams.
- Modify `includes/Frontend/ContentFilter.php` — use effective language and preserve exact-root front-page fallback.
- Modify `includes/Frontend/URLManager.php` — delegate special-role URL generation.
- Modify `includes/Frontend/LanguageSwitcher.php` — root-to-root links and unavailable-target behavior.
- Modify `includes/Compatibility/YoastSEOIntegration.php` — documented ownership filters only; no rendered-HTML scraping.

### Page API and translation fidelity

- Modify `includes/API/PostsController.php` — role/effective-layout/source-group fields; query-level canonical grouping before filters/pagination.
- Modify `includes/Translation/TranslationBridge.php` — copy safe Page layout semantics and map translated parent when available.
- Modify `includes/Translation/PostTypeRegistry.php` only if needed to document/route protection; NEVER remove Site Editor protected types.
- Modify `includes/Core/Plugin.php` — register new REST controller and invalidation hooks.

### Site Content plugin domain

- Create `includes/SiteContent/SiteEntityIdentity.php` — typed internal identity; raw key never exposed as route segment.
- Create `includes/SiteContent/SiteContentCatalog.php` — official WordPress template/entity enumeration and opaque catalog IDs.
- Create `includes/SiteContent/BlockSegmentCodec.php` — block extraction, stable identity/reconciliation, field-specific validation, current-source apply.
- Create `includes/SiteContent/SiteContentRepository.php` — translation rows, monotonic revisions, compare-and-swap, theme/source lifecycle.
- Create `includes/SiteContent/BlockTemplateTranslationAdapter.php` — `wp_template`/`wp_template_part` runtime hooks.
- Create `includes/SiteContent/NavigationTranslationAdapter.php` — `wp_navigation` labels/link mapping through navigation seams.
- Create `includes/SiteContent/SyncedPatternTranslationAdapter.php` — persisted synced `wp_block` overlays through pattern seams.
- Create `includes/API/SiteContentController.php` — catalog/detail/queue/review/delete routes with capability/object/size/idempotency/revision enforcement.
- Create `includes/Translation/TranslationResourceHandler.php` — exactly the spec contract.
- Create `includes/Translation/PostTranslationResourceHandler.php` — existing post finalization behind handler without behavior regression.
- Create `includes/Translation/SiteContentTranslationResourceHandler.php` — segmented payload/finalization for Site Content records.
- Modify `includes/Translation/JobSender.php`, `includes/Translation/JobReceiver.php`, `includes/Translation/TranslationFinalizer.php`, and `includes/Translation/TranslationService.php` — dispatch through exactly two resource handlers; preserve existing post contracts.
- Modify `includes/Core/Database.php` — additive Site Content table plus idempotency columns/indexes and versioned multisite-safe schema creation.
- Modify existing migration/maintenance files under `includes/Migrations/` and `includes/Database/` that own schema version, new-site creation, bounded cleanup, and cache invalidation; do not create a second migration framework.

### Admin UI

- Modify `admin/src/pages/translations.js` — order `Pages`, `Posts`, `Custom Types`, `Site Content`, `Theme Text`, `Plugin Text`; default Pages; preserve legacy hashes.
- Modify `admin/src/pages/content-translate.js` — site-entry cards, role/effective-layout badges, canonical source rows, role/layout/source-language filters, editor guidance.
- Create `admin/src/pages/site-content.js` — Templates, Headers & Footers, Navigation, Patterns subtabs and translation/review states.
- Modify `admin/src/main.js` — Site Content route and capability-aware loading.
- Modify `admin/src/styles/pages/_translations.scss` — responsive, RTL, dark-mode, focus, status, and card styles; no inline CSS.
- Modify existing admin API/i18n utilities only where required by the new routes; preserve centralized escaping and request handling.
- Commit rebuilt `admin/dist/` artifacts and manifest produced by the existing build.

### Backend segmented contract

- Modify `press-zone-backend/api/src/types/index.ts` — backward-compatible segmented request/response types.
- Modify `press-zone-backend/api/src/utils/structuredFields.ts` — validate exact IDs, bounds, and protected placeholders without permitting provider-owned structure.
- Modify `press-zone-backend/api/src/services/geminiClient.ts` — translate segment text/context only; exact-ID response.
- Modify `press-zone-backend/api/src/services/translationService.ts` — detect/validate segmented resources while preserving title/excerpt/content behavior.
- Modify `press-zone-backend/api/src/worker.ts` — queued segmented handling, idempotent completion, exact response contract.
- Modify backend capability/health metadata route used by plugin negotiation — advertise segmented Site Content only after deployment supports it.

### Tests

- Create `tests/Unit/Frontend/SpecialPageResolverTest.php`.
- Create `tests/Unit/Frontend/EffectiveTemplateResolverTest.php`.
- Create `tests/Unit/Frontend/LocaleContextCoordinatorTest.php`.
- Create `tests/Unit/Frontend/LocalizedURLResolverTest.php`.
- Extend `tests/Unit/Core/PluginRewriteRulesTest.php`, `tests/Unit/Frontend/ContentFilterTest.php`, and `tests/Unit/Frontend/URLManagerTest.php`.
- Create `tests/Unit/SiteContent/BlockSegmentCodecTest.php`.
- Create `tests/Unit/SiteContent/SiteContentRepositoryTest.php`.
- Create `tests/Unit/SiteContent/RuntimeAdaptersTest.php`.
- Create `tests/Unit/API/SiteContentControllerTest.php`.
- Add focused Page grouping/fidelity tests under `tests/Unit/API/` and `tests/Unit/Translation/`.
- Create `tests/e2e/multilingual-site-entry.spec.js`.
- Create `tests/e2e/site-content-translation.spec.js`.
- Add backend unit/integration tests beside existing `structuredFields`, `geminiClient`, `translationService`, and worker tests under `press-zone-backend/api/src/__tests__/`.

## Contract

### Locale bootstrap

```text
LocaleBootstrap::register(): void
LocaleContextCoordinator::bootstrap(): LanguageRequestContext
LocaleContextCoordinator::finalizeForParsedRequest(array $queryVars): LanguageRequestContext
LocaleContextCoordinator::restore(): void
```

- `LocaleBootstrap::register()` is called directly from main plugin file after constants/autoload and before `add_action(...mpz_init, 10)`.
- It immediately registers `determine_locale` priority 1, `plugins_loaded` priority 0, and `shutdown` `PHP_INT_MAX`.
- `determine_locale` lazily initializes context if called before `plugins_loaded` 0.
- Missing schema, requirements, language data, option data, or valid URL marker returns canonical locale without initializing/upgrading full plugin.
- Full `Core\Plugin` init at priority 10 consumes the context.
- Explicit public fallback switches through WordPress locale APIs; shutdown/test/worker finalization restores prior locale and clears request state.

### Special pages and effective templates

```text
SpecialPageResolver::getOverview(): SpecialPageOverview
SpecialPageResolver::getRole(int $postId): null|'front_page'|'posts_page'
SpecialPageResolver::resolveForLanguage(string $role, string $languageCode): SpecialPageResolution
EffectiveTemplateResolver::forRole(string $role, ?int $resolvedPostId): EffectiveTemplateResolution
```

- Canonical source IDs come only from captured `show_on_front`, `page_on_front`, and active `page_for_posts`.
- Static front-page target resolves by translation relationship and published visibility.
- Missing/unpublished front target uses whole-document source fallback only at exact language root: source locale/text direction/localization/query filtering, source canonical, `noindex,follow`, no false alternate/sitemap.
- Published posts-page target uses translated slug and native `is_home()` semantics.
- Missing/unpublished posts-page target has no URL, fallback, switcher target, alternate, or sitemap entry; guessed source slug follows normal routing.
- `front-page` controls site front; `home` controls posts index. Cards report effective hierarchy, not `_wp_page_template` alone.
- Stored Reading options never change.

Hook priorities and context exclusions MUST match canonical design exactly.

### URLs and SEO

```text
LocalizedURLResolver::forRole(string $role, string $languageCode): URLResolution
LocalizedURLResolver::forContent(int $postId, string $languageCode): URLResolution
```

One service feeds page/permalink filters, role redirects, language switcher, canonicals, `hreflang`, `x-default`, robots, and WordPress sitemap. Normalize before redirect; preserve safe pagination/query state; never loop. Never redirect preview/feed/embed/REST/admin/cron/CLI. Yoast integration replaces only outputs owned through documented filters.

### Page API response

Existing Page list rows gain:

```json
{
  "canonical_source_id": 123,
  "source_language": "en",
  "site_roles": ["front_page"],
  "assigned_layout": {"id": "template-id", "label": "Default layout"},
  "effective_layout": {"id": "front-page", "label": "Front Page", "origin": "theme"}
}
```

Group translation relationships before search/role/layout/source filters and pagination. Totals describe canonical source rows. Unlinked Pages remain rows.

Translated Page creation preserves `_wp_page_template`, menu order, safe publication attributes, featured-image reference, existing ACF policy, and translated parent when available. It never copies identity/lock/revision/cache/language-link metadata blindly.

### Site catalog and routes

```text
SiteContentCatalog::list(SiteContentQuery $query): SiteContentPage
SiteContentCatalog::get(SiteEntityIdentity $identity): ?SiteEntity
```

Catalog uses official WordPress APIs. Public `catalog_id` is a stable URL-safe digest resolved against current catalog; zero/multiple matches fail. Raw `theme//slug` never enters route path or filesystem lookup.

Routes:

- `GET /site/overview`
- `GET /site-content`
- `GET /site-content/{type}/{catalog_id}`
- `POST /site-content/{type}/{catalog_id}/translations`
- `PUT /site-content/{type}/{catalog_id}/translations/{language}`
- `DELETE /site-content/{type}/{catalog_id}/translations/{language}`

WordPress handles cookie `X-WP-Nonce`; route callbacks enforce capability and object authorization. Validate configured active language, source/target inequality, request bytes, segment count, segment length, total characters, and opaque ID.

POST requires idempotency key. Persist actor/scope/fingerprint/job/response/expiry for 24 hours through terminal status. Same key+fingerprint returns original response without new charge; same key+different fingerprint returns 409. PUT/DELETE require source and translation revisions; stale compare-and-swap returns 409.

### Segmented resource

```json
{
  "resource_type": "site_content",
  "source_language": "en",
  "target_language": "he",
  "segments": [{"id": "opaque-id", "text": "Source text", "context": "core/heading"}]
}
```

Response contains each input ID exactly once and no unknown IDs. Existing post structured-field payloads remain unchanged.

`BlockSegmentCodec` separates `segment_id` from source hash; reconciles revisions by tree/sequence matching; duplicate/ambiguous matches require review. Providers cannot alter URLs, IDs, block names, attributes, placeholders, markup, order, or nesting unless field schema explicitly permits value markup. Runtime starts from current source structure; translated segments are authority; compiled serialization is disposable cache.

### Storage

Site translation row contains numeric ID, entity type/key, source/target languages, source SHA-256, monotonic `translation_revision`, translated segments, disposable compiled cache, status/error/audit timestamps, and unique entity+key+target key.

Every mutation atomically compares source and translation revisions and increments translation revision. Theme/source changes reconcile compatible segments; changed/ambiguous segments show current source and `needs_update`. Theme switch makes old rows inactive; multisite schema/data remain per blog.

### Runtime adapters

- Block template adapter handles only `wp_template` and `wp_template_part` official retrieval hooks.
- Navigation adapter handles `wp_navigation` retrieval/render hooks and internal translated-link mapping.
- Synced pattern adapter handles persisted synced `wp_block`; unsynced/non-persisted patterns belong to containing content.
- Public frontend or explicit plugin preview only. Source Site Editor, generic REST, admin, cron, CLI, management AJAX, and global canonical caches never receive translated variants.

### Admin literals

Top tabs, in order: `Pages`, `Posts`, `Custom Types`, `Site Content`, `Theme Text`, `Plugin Text`.

Site Content subtabs: `Templates`, `Headers & Footers`, `Navigation`, `Patterns`.

Required status/guidance strings:

- `Latest posts (automatic)`
- `Homepage`
- `Posts page`
- `Blog index: editor content is not displayed`
- `Effective layout`
- `Missing homepage translation`
- `Homepage translation is not published`
- `Missing`, `Translating`, `Ready`, `Needs update`, `Failed`

Classic themes retain visible Site Content tab with explanatory disabled state. No fake template rows.

## Behavior

- Release 1 ships homepage/posts-page routing, SEO, and Pages UX with no segmented-backend dependency.
- Release 2 ships Templates and Headers & Footers only after backend metadata advertises segmented capability.
- Release 3 ships Navigation and Patterns only after their runtime adapters pass release gates.
- Keep releases capability-gated and independently deployable; never expose a later release before its named gate.
- Support WordPress 6.0 baseline. Use block attribute `role=content` on WordPress 6.7+ and tested core-schema extraction on WordPress 6.0–6.6.
- Default-language changes atomically reclassify canonical source ownership, invalidate URLs/SEO/catalog caches, and preserve recoverable translation history.
- Uninstall follows existing retention policy: retain translation data unless explicit destructive uninstall is enabled; remove new tables/idempotency records only through that existing destructive path.
- Every failure keeps current source structure live and never exposes stale compiled markup.
- Every cache has invalidation for Reading settings, links, post status/deletion, URL mode, default language, entity/source revision, theme switch, plugin update, and translation mutation.
- Admin interactions satisfy keyboard, live-region, focus restoration, RTL, dark mode, narrow width, reduced motion, and text-plus-icon status requirements.
- Do not add inline CSS.

## Out of scope

- Per-language theme selection.
- Per-language layout/design forks.
- PHP template source translation.
- Dynamic query-result translation as template text.
- Replacement of WordPress Reading settings.
- Unrelated licensing/workflow/admin redesign.
- Removing Site Editor types from `PostTypeRegistry::PROTECTED_TYPES`.
- Using local WordPress data as evidence for dev1.

## Acceptance

### Plugin PHP

Run from `plugins/international-press-zone`:

```bash
composer test
composer phpcs
composer phpstan
```

Expected: all commands exit 0 with no warnings. Tests prove every PHP/unit/integration case listed in canonical design, including bootstrap order, pre-`plugins_loaded` lazy locale, front-page/home hierarchy, source fallback document locale, missing posts-page no-fallback, query-level grouping pagination, redirect loops, REST limits/auth/idempotency/concurrency, block ambiguity, runtime context isolation, theme switch, and multisite new-site schema.

### Admin

Run from `plugins/international-press-zone/admin`:

```bash
npm run lint:js
npm run build
```

Expected: exit 0 with no warnings; committed `admin/dist/` matches source and manifest.

### Backend

Run from `press-zone-backend/api`:

```bash
npm test -- --runInBand
npm run build
```

Expected: exit 0 with no warnings; segmented tests prove exact IDs, limits, placeholder preservation, malformed provider rejection, terminal idempotency replay, retries, and old post payload compatibility.

### Browser

Run Playwright only through `~/.claude/bin/e2e-remote`; never launch workstation browsers. Execute focused `tests/e2e/multilingual-site-entry.spec.js` and `tests/e2e/site-content-translation.spec.js` against authorized dev1 after deployment.

Expected:

- Pages identifies real dev1 homepage without title guessing.
- latest-posts mode states no homepage Page exists.
- cards show correct effective `front-page`/`home` layout.
- target language root renders published translated homepage.
- translated homepage slug redirects once to language root.
- missing homepage uses whole-document source fallback contract.
- missing posts target exposes no false URL/fallback.
- Site Content translation changes text without block-structure changes.
- source template changes produce `Needs update` with current structure.
- screenshots pass desktop/narrow, LTR/RTL, light/dark checks.

### Distribution and dev1

From `plugins/international-press-zone`:

```bash
node tools/build-distribution.mjs
node tools/deploy-dev1.mjs <generated-international-press-zone-version.zip>
```

Expected: archive verification passes; deploy script reports installed active expected version and byte-matching deployed admin assets. Use generated archive path emitted by build; never invent filename.

Deploy backend segmented capability before enabling Site Content queue UI. Verify backend health/capability metadata after deployment. On dev1, read actual Reading configuration, translate one safe homepage and one safe Site Content fixture, verify URLs/SEO/cache/RTL, and remove only fixtures created by this run.
