TenantAtlas/specs/252-platform-localization-v1/plan.md
Ahmed Darrazi d51df2800b
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 56s
feat: implement platform localization v1
2026-04-28 21:27:22 +02:00

28 KiB

Implementation Plan: Platform Localization v1 (DE/EN)

Branch: 252-platform-localization-v1 | Date: 2026-04-28 | Spec: /Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/252-platform-localization-v1/spec.md Input: Feature specification from /Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/252-platform-localization-v1/spec.md

Note: This template is filled in by the /speckit.plan command. See .specify/scripts/ for helper scripts.

Summary

  • Add one bounded locale foundation for the platform runtime only: admin and tenant panels use the full locale precedence chain (explicit override -> user preference -> workspace default -> system default), while the system panel uses the v1 subset (explicit override -> system default) because it authenticates a separate platform actor.
  • Keep persistence narrow and repo-native: store the workspace default locale through the existing workspace settings infrastructure, persist the personal locale preference directly on the workspace-bound user surface, and avoid a generic preferences framework, a second settings stack, or a second preference store for PlatformUser.
  • Translate the panel shell and the highest-signal governance surfaces first, including the shared context bar, auth copy, Findings, Baseline Compare, representative workspace and tenant membership tables, monitoring and operations feedback, and customer-safe review or report viewer chrome, while keeping exports, audit logs, JSON payloads, and other machine-readable artifacts invariant.

Technical Context

Language/Version: PHP 8.4 (Laravel 12)
Primary Dependencies: Filament v5 + Livewire v4, Laravel translator, existing workspace settings stack (SettingsRegistry, SettingsResolver, SettingsWriter), current panel providers, existing Filament notifications and view layer
Storage: PostgreSQL via one workspace-bound user-owned locale preference field plus one workspace-owned locale default setting; translation catalogs in apps/platform/lang/en and apps/platform/lang/de
Testing: Pest unit and feature tests via Laravel Sail
Validation Lanes: fast-feedback, confidence
Target Platform: Monorepo Laravel web application in apps/platform with admin, tenant, and system Filament panels
Project Type: web
Performance Goals: No extra remote calls during locale resolution, constant-time locale lookup from request/session + current user + workspace settings, and no measurable overhead on ordinary panel navigation or Livewire round-trips
Constraints: Exactly two locales (en, de), no apps/website scope, no new global-search semantics, no RBAC behavior change, invariant CSV/JSON/audit/raw payloads, and no generic preference framework
Scale/Scope: One locale resolver, one request-time locale application seam, one workspace default setting, one workspace-bound personal preference path, system-panel explicit-override support only, and first-wave translation coverage for shell/auth plus core governance surface families

Filament v5 / Panel Notes

  • Livewire v4.0+ compliance: The slice stays inside existing Filament v5 pages, widgets, resources, render hooks, and Livewire-backed request flows. No Livewire v3 assumptions or compatibility work are introduced.
  • Provider registration location: No panel/provider registration changes are planned. Existing Laravel 12 + Filament provider registration remains in bootstrap/providers.php.
  • Global search: No new global-search resource is introduced and no global-search routing or authorization semantics are changed. Localization only affects visible copy where current search access already exists.
  • Destructive and high-impact actions: No destructive action is added by this slice. Locale preference and workspace default changes are low-risk settings mutations; they still use existing authorization and settings audit paths where applicable.
  • Asset strategy: No new panel or shared assets are planned. Deployment remains unchanged, including cd apps/platform && php artisan filament:assets when registered Filament assets are deployed elsewhere in the product.

UI / Surface Guardrail Plan

  • Guardrail scope: changed surfaces
  • Native vs custom classification summary: mixed
  • Shared-family relevance: shell navigation, auth copy, workspace settings, notifications, status messaging, dashboard and compare surfaces, customer-safe report viewers
  • State layers in scope: shell, page, detail, URL-query or session
  • Audience modes in scope: operator-MSP, support-platform, customer-read-only
  • Decision/diagnostic/raw hierarchy plan: decision-first on shell and core governance surfaces; diagnostics-second on monitoring and operational feedback surfaces; support/raw payloads stay third and unchanged
  • Raw/support gating plan: unchanged capability-gated or collapsed raw detail; localization applies only to surrounding UI copy, not to raw payloads or audit artifacts
  • One-primary-action / duplicate-truth control: the shell remains the one place where language is chosen intentionally; all other surfaces consume the resolved locale and do not become independent configuration surfaces
  • Handling modes by drift class or surface: review-mandatory because mixed-language drift across shell, notifications, and core governance surfaces would undercut the shared locale contract immediately
  • Repository-signal treatment: review-mandatory
  • Special surface test profiles: global-context-shell, standard-native-filament, shared-detail-family
  • Required tests or manual smoke: functional-core, state-contract
  • Exception path and spread control: no page-local locale state, no custom translation framework, no second shell, and no localized machine artifacts
  • Active feature PR close-out entry: Guardrail

Review Outcome

  • Outcome class: acceptable-special-case
  • Workflow outcome: keep
  • Why this remains acceptable: the package touches multiple surface families, but every change is still anchored to one shared locale contract and a tightly bounded first-wave translation inventory.

Shared Pattern & System Fit

  • Cross-cutting feature marker: yes
  • Systems touched: bootstrap/app.php, panel providers (AdminPanelProvider, TenantPanelProvider, SystemPanelProvider), shared topbar render hook and resources/views/filament/partials/context-bar.blade.php, existing auth/login pages, workspace settings infrastructure, User model persistence, PlatformUser-backed system auth behavior, translation catalogs under lang/, Filament notifications, and representative governance/detail pages and report viewers
  • Shared abstractions reused: existing translation helpers (__() and Laravel translator), existing settings registry/resolver/writer, current workspace context resolution, current panel render hooks, and existing Filament notification and page/resource surfaces
  • New abstraction introduced? why?: one bounded LocaleResolver plus one request-time application seam are justified because the repo currently lacks any single locale precedence decision that can serve shell, auth, Livewire, notifications, and report viewers consistently
  • Why the existing abstraction was sufficient or insufficient: Laravel translation helpers are already sufficient for rendering translated strings, and the workspace settings infrastructure is already sufficient for a workspace default on admin and tenant planes. They are insufficient because there is no central locale resolution contract and no workspace-bound user locale preference path today.
  • Bounded deviation / spread control: no generic preferences registry, no page-local language switches, and no second translation catalog scheme beyond standard Laravel lang/{locale} files

OperationRun UX Impact

  • Touches OperationRun start/completion/link UX?: no
  • Central contract reused: N/A
  • Delegated UX behaviors: N/A
  • Surface-owned behavior kept local: localized copy only on existing run and monitoring surfaces
  • Queued DB-notification policy: N/A
  • Terminal notification path: N/A
  • Exception path: none

Provider Boundary & Portability Fit

  • Shared provider/platform boundary touched?: no
  • Provider-owned seams: N/A
  • Platform-core seams: locale resolution, glossary translation, UI copy, and viewer chrome language behavior
  • Neutral platform terms / contracts preserved: Finding, Baseline, Drift, Risk Accepted, Evidence Gap, Run, Alert, Workspace, Tenant
  • Retained provider-specific semantics and why: none; provider payloads remain untranslated and raw where they already exist
  • Bounded extraction or follow-up path: none

Constitution Check

GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.

  • Inventory-first: PASS - the slice changes operator-facing runtime copy and locale choice only; it does not introduce new inventory or backup truth.
  • Read/write separation: PASS - the only new writes are low-risk preference mutations using existing user/workspace ownership and current settings patterns.
  • Graph contract path: PASS - no new Microsoft Graph path is introduced.
  • Deterministic capabilities: PASS - authorization semantics for shell, workspace settings, and read-only viewers remain unchanged.
  • RBAC-UX: PASS - /admin, /admin/t, and /system remain separated; language choice does not alter 404 versus 403 semantics.
  • Workspace isolation: PASS - workspace selection and tenant selection stay authoritative, and locale does not create a second context layer.
  • RBAC-UX destructive confirmation: N/A - no destructive action is introduced.
  • RBAC-UX global search: PASS - search scope and visibility remain unchanged.
  • Tenant isolation: PASS - translated labels and fallback text must not leak inaccessible tenant or workspace information.
  • Run observability: PASS - no new run family or start flow is introduced.
  • OperationRun start UX: N/A - no start semantics change.
  • Ops-UX 3-surface feedback: PASS - only existing copy becomes locale-aware; lifecycle and notification mechanics stay unchanged.
  • Ops-UX lifecycle: N/A - no lifecycle contract change.
  • Ops-UX summary counts: N/A - no summary shape change.
  • Ops-UX guards: N/A - no new run guard family is planned.
  • Ops-UX system runs: N/A - unchanged.
  • Automation: N/A - no new queued or scheduled workflow family is introduced.
  • Data minimization: PASS - no new sensitive payload storage is introduced.
  • Test governance (TEST-GOV-001): PASS - the plan stays in focused unit + feature lanes with explicit proof commands and limited fixture growth.
  • Proportionality (PROP-001): PASS - persistence stays to one workspace-bound user-owned preference and one workspace setting; one resolver is the narrowest viable shared seam.
  • No premature abstraction (ABSTR-001): PASS - no registry, strategy system, or framework is planned beyond one locale resolver and supported-locale allowlist.
  • Persisted truth (PERSIST-001): PASS - the new persisted values represent real workspace-bound user and workspace-owned preference truth, while the system plane remains explicit-override or system-default only.
  • Behavioral state (STATE-001): PASS - the locale set changes real request behavior, formatting, and translated surface output.
  • UI semantics (UI-SEM-001): PASS - the plan favors direct domain-to-translation mapping instead of a new interpretation framework.
  • Shared pattern first (XCUT-001): PASS - existing translator, panel hooks, settings stack, and existing page/resource surfaces are reused first.
  • Provider boundary (PROV-001): PASS - localization is platform-core and provider-neutral.
  • V1 explicitness / few layers (V1-EXP-001, LAYER-001): PASS - one resolver, one middleware path, two locales, and a bounded first-wave surface inventory.
  • Spec discipline / bloat check (SPEC-DISC-001, BLOAT-001): PASS - the whole locale foundation remains in one coherent spec and explicitly avoids website/email/framework drift.
  • Badge semantics (BADGE-001): PASS - translated badges continue to use existing central semantics rather than new color or state mappings.
  • Filament-native UI (UI-FIL-001): PASS - the slice extends existing Filament pages/resources/widgets, login pages, and render-hook partials.
  • Filament-native UI local Blade/Tailwind: PASS - existing custom Blade surfaces like the shared context bar and selected viewer shells remain in Filament visual language.
  • UI/UX surface taxonomy (UI-CONST-001 / UI-SURF-001): PASS - no new surface type is introduced.
  • Decision-first operating model (DECIDE-001): PASS - shell choice happens once, and primary governance surfaces stay decision-first.
  • Audience-aware disclosure (DECIDE-AUD-001 / OPSURF-001): PASS - localization improves readability without exposing hidden diagnostics or translating raw payloads.
  • UI/UX inspect model (UI-HARD-001): PASS - no inspect/open model changes are planned.
  • UI/UX action hierarchy (UI-HARD-001 / UI-EX-001): PASS - existing action hierarchy remains intact.
  • UI/UX scope, truth, and naming (UI-HARD-001 / UI-NAMING-001 / OPSURF-001): PASS - canonical nouns remain stable across translated shells and pages.
  • UI/UX placeholder ban (UI-HARD-001): PASS - no placeholder controls are planned.
  • UI naming (UI-NAMING-001): PASS - translated labels preserve Verb + Object semantics and canonical domain vocabulary.
  • Operator surfaces (OPSURF-001): PASS - shell, governance, monitoring, and customer-safe viewers stay explicit and bounded.
  • Operator surface page contract: PASS - the spec already defines the affected surface contracts.
  • Filament UI Action Surface Contract: PASS - no new action family is introduced beyond one shell-level locale control and a workspace settings field.
  • Filament UI UX-001 (Layout & IA): PASS - the slice extends existing shells, settings, and detail surfaces only.
  • Action-surface discipline (ACTSURF-001 / HDR-001): PASS - language choice stays on the shell or settings surfaces and is not duplicated on every page.
  • UI review workflow: PASS - guardrail classification, shell ownership, fallback behavior, and invariant machine-format rules remain explicit in this plan.

Test Governance Check

  • Test purpose / classification by changed surface: Unit for locale precedence and validation; Feature for request-time application, workspace and personal preference flows, translated core surfaces, localized feedback, and invariant machine-format behavior
  • Affected validation lanes: fast-feedback, confidence
  • Why this lane mix is the narrowest sufficient proof: the core risk is deterministic resolution and rendered surface behavior across existing request paths, not browser-only interaction nuance or heavy governance semantics
  • Narrowest proving command(s):
    • export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Unit/Localization/LocaleResolverTest.php
    • export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Localization/AuthAndSystemSurfaceLocalizationTest.php tests/Feature/Localization/LocalePreferenceFlowTest.php tests/Feature/Localization/WorkspaceDefaultLocaleTest.php tests/Feature/Filament/Localization/CoreGovernanceSurfaceLocalizationTest.php
    • export PATH="/bin:/usr/bin:/usr/local/bin:$PATH" && cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Localization/LocalizedNotificationFormattingTest.php tests/Feature/Localization/TranslationFallbackGuardTest.php tests/Feature/Localization/MachineFormatInvarianceTest.php
  • Fixture / helper / factory / seed / context cost risks: limited to users, workspaces, memberships, workspace settings, session override state, and representative governance surface fixtures; add one focused wrong-plane or non-member and missing-capability proof path without widening the test family
  • Expensive defaults or shared helper growth introduced?: no - implementation should reuse existing factories and add only thin locale helpers where repeated assertions demand it
  • Heavy-family additions, promotions, or visibility changes: none
  • Surface-class relief / special coverage rule: global-context-shell proof for request-wide locale behavior, standard-native relief for ordinary Filament surfaces, and shared-detail-family proof for localized report viewer chrome with invariant artifacts
  • Closing validation and reviewer handoff: rerun the exact targeted commands above, verify admin login, tenant panel, and system panel locale continuity, verify unsupported locale fallback behavior, verify dashboard plus core governance surfaces do not render raw keys, verify wrong-plane or non-member 404 and member-but-no-capability 403 behavior stays unchanged under locale changes, and verify exported or audited machine formats remain stable with no new remote locale lookups introduced
  • Budget / baseline / trend follow-up: none expected beyond ordinary feature-local growth
  • Review-stop questions: does one resolver truly own locale precedence, does Livewire preserve the selected locale, does the first-wave translation scope stay bounded, and do exports or audit payloads remain invariant
  • Escalation path: document-in-feature if one surface family needs temporary English-only fallback; follow-up-spec only if a later broader email or website localization program becomes necessary
  • Active feature PR close-out entry: Guardrail
  • Why no dedicated follow-up spec is needed: the planned work stays bounded to the platform runtime and current high-signal governance surfaces; broader public-site or multi-locale expansion remains explicitly out of scope

Project Structure

Documentation (this feature)

specs/252-platform-localization-v1/
├── plan.md
├── research.md
├── data-model.md
├── quickstart.md
├── contracts/
│   └── locale-resolution-and-translation-governance.logical.openapi.yaml
├── checklists/
│   └── requirements.md
└── tasks.md

Source Code (repository root)

apps/platform/
├── app/
│   ├── Http/
│   │   └── Middleware/
│   │       └── ApplyResolvedLocale.php
│   ├── Models/
│   │   └── User.php
│   ├── Providers/
│   │   ├── AppServiceProvider.php
│   │   └── Filament/
│   │       ├── AdminPanelProvider.php
│   │       ├── TenantPanelProvider.php
│   │       └── SystemPanelProvider.php
│   ├── Services/
│   │   ├── Localization/
│   │   │   └── LocaleResolver.php
│   │   └── Settings/
│   │       ├── SettingsResolver.php
│   │       └── SettingsWriter.php
│   └── Support/
│       └── Settings/
│           └── SettingsRegistry.php
├── bootstrap/
│   └── app.php
├── database/
│   └── migrations/
│       └── *_add_preferred_locale_to_users_table.php
├── lang/
│   ├── de/
│   └── en/
├── resources/views/
│   └── filament/
│       ├── partials/context-bar.blade.php
│       ├── pages/
│       ├── widgets/
│       └── system/
└── tests/
    ├── Feature/
    │   ├── Filament/Localization/
    │   └── Localization/
    └── Unit/
        └── Localization/

Structure Decision: Single Laravel/Filament application inside apps/platform, with one new bounded localization resolver, one request-time locale application path, one workspace-bound user preference mutation path, one workspace-owned default setting, system-panel explicit-override support only, and focused translation catalog growth for selected existing surfaces.

Likely Implementation Surfaces

  • bootstrap/app.php plus a new app/Http/Middleware/ApplyResolvedLocale.php for request-time locale application in ordinary web requests and panel traffic
  • app/Providers/Filament/AdminPanelProvider.php, TenantPanelProvider.php, and SystemPanelProvider.php for consistent panel-level middleware and shell affordances
  • resources/views/filament/partials/context-bar.blade.php as the shared topbar language-control anchor for the admin and tenant panels
  • current auth pages such as app/Filament/Pages/Auth/Login.php and app/Filament/System/Pages/Auth/Login.php for translated login and auth-adjacent copy
  • app/Models/User.php plus a user migration for the personal locale preference field, while PlatformUser remains on explicit override plus system default only
  • app/Support/Settings/SettingsRegistry.php, app/Services/Settings/SettingsResolver.php, app/Services/Settings/SettingsWriter.php, and app/Filament/Pages/Settings/WorkspaceSettings.php for the workspace-owned default locale path and audit-backed save semantics
  • a new app/Services/Localization/LocaleResolver.php for precedence, supported-locale validation, and fallback behavior
  • lang/en/* and new lang/de/* catalogs for shell, Findings, Baseline Compare, monitoring, operations, workspace or tenant management tables, and customer-safe review or report viewer shells
  • representative existing surfaces such as app/Filament/Pages/TenantDashboard.php, app/Filament/System/Pages/Dashboard.php, app/Filament/Resources/FindingResource.php, app/Filament/Pages/BaselineCompareLanding.php, resources/views/filament/pages/baseline-compare-landing.blade.php, resources/views/filament/widgets/tenant/tenant-review-pack-card.blade.php, app/Filament/Resources/TenantResource/RelationManagers/TenantMembershipsRelationManager.php, and app/Filament/Resources/Workspaces/RelationManagers/WorkspaceMembershipsRelationManager.php
  • localized feedback surfaces such as current Filament notifications, validation messages, and relative-time labels already present across monitoring, onboarding, and review surfaces

Complexity Tracking

Violation Why Needed Simpler Alternative Rejected Because
One bounded LocaleResolver Shell, auth, Livewire, notifications, and report viewers need one deterministic locale source Page-local or panel-local locale reads would drift immediately and make fallback behavior inconsistent
One new workspace locale setting plus one personal preference field The roadmap precedence chain requires real persisted workspace and user truth Session-only locale switching would not satisfy inherited defaults or stable user choice

Proportionality Review

  • Current operator problem: The product is partially translation-aware but not intentionally localized. Operators cannot choose a language reliably, and current core surfaces mix raw English with extracted translations.
  • Existing structure is insufficient because: Laravel translation helpers alone do not answer which locale to use, when to inherit workspace defaults, how to persist a user choice, or how to keep Livewire and report-viewer surfaces aligned.
  • Narrowest correct implementation: exactly two locales, one locale resolver where admin/tenant panels use the full precedence chain and the system panel uses explicit override plus system default, one workspace-bound user preference field, one workspace setting, one request-time application path, and a bounded first-wave translation inventory on existing high-signal surfaces.
  • Ownership cost created: ongoing EN/DE catalog maintenance, one resolver, one migration, one workspace setting key, and regression tests for fallback plus invariant machine formats.
  • Alternative intentionally rejected: a generic preferences framework, broad website/email program, or translating every page first was rejected because the current release needs a runtime foundation, not a full localization platform.
  • Release truth: current-release truth. Core governance, monitoring, and customer-safe review surfaces already need language continuity in the live platform.

Phase 0 — Research (output: research.md)

See: /Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/252-platform-localization-v1/research.md

Goals:

  • Confirm the narrowest persistence shape for user preference plus workspace default without creating a generic preferences subsystem.
  • Confirm the cleanest request-time locale application seam across normal web and Livewire requests for all three current panels, while keeping the system panel on explicit override plus system default only.
  • Confirm which first-wave governance and viewer surfaces are already translation-aware enough to translate now and which ones still rely on raw English strings.
  • Confirm invariant machine-format boundaries for exports, audit entries, report payloads, and raw evidence.

Phase 1 — Design & Contracts (outputs: data-model.md, contracts/, quickstart.md)

See:

  • /Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/252-platform-localization-v1/data-model.md
  • /Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/252-platform-localization-v1/contracts/locale-resolution-and-translation-governance.logical.openapi.yaml
  • /Users/ahmeddarrazi/Documents/projects/wt-plattform/specs/252-platform-localization-v1/quickstart.md

Design focus:

  • Persist one personal locale preference directly on the workspace-bound user-owned surface and one workspace default locale through the existing settings infrastructure.
  • Add one bounded locale resolver plus one request-time middleware or application path shared by admin, tenant, and system panels, with explicit override plus system default only on the system plane.
  • Place the user-facing locale switch on the existing shared shell or context surface instead of inventing a new page shell.
  • Translate first-wave shell, governance, monitoring, and customer-safe viewer surfaces using standard Laravel catalogs and controlled English fallback.
  • Keep exports, audit logs, raw JSON, and machine-readable artifacts invariant even when the surrounding UI becomes locale-aware.

Implementation Close-Out

  • Workflow outcome: keep.
  • Validation lanes completed: fast-feedback and confidence.
  • Targeted proof results:
    • ./vendor/bin/sail artisan test --compact tests/Unit/Localization/LocaleResolverTest.php ... tests/Feature/Localization/MachineFormatInvarianceTest.php passed with 15 tests and 103 assertions.
    • ./vendor/bin/sail artisan test --compact tests/Feature/SettingsFoundation/WorkspaceSettingsAuditTest.php tests/Feature/SettingsFoundation/WorkspaceSettingsManageTest.php tests/Feature/SettingsFoundation/WorkspaceSettingsViewOnlyTest.php passed with 18 tests and 248 assertions.
    • ./vendor/bin/sail artisan test --compact tests/Feature/Reviews/CustomerReviewWorkspaceLaunchLinksTest.php tests/Feature/Reviews/CustomerReviewWorkspacePageTest.php tests/Feature/Reviews/CustomerReviewWorkspaceAuthorizationTest.php tests/Feature/Reviews/CustomerReviewWorkspacePackAccessTest.php tests/Feature/TenantReview/TenantReviewUiContractTest.php passed with 24 tests and 135 assertions.
    • ./vendor/bin/sail bin pint --dirty --format agent passed.
    • git diff --check passed.
  • Browser smoke result: passed on http://localhost/admin/settings/workspace, http://localhost/admin/t/18000000-0000-4000-8000-000000000180, and http://localhost/admin/reviews/workspace. The smoke verified the shared language switch from English to German, German locale menu state, tenant dashboard German navigation/title, customer review workspace German viewer chrome, no raw localization.* keys, and no current browser console errors from the tested tab.
  • Guardrail close-out: acceptable-special-case / keep remains valid because the implementation still uses one resolver, one middleware seam, one user preference field, one workspace setting key, standard Laravel catalogs, and no localized machine artifact path.
  • document-in-feature note: broader pre-existing Workspace Settings sections and deeper diagnostic/payload text outside the locale setting and review/report chrome may still render English in German mode. This is recorded as existing unrelated localization debt rather than widened into this first platform-runtime slice; the active implementation localizes the new locale controls, workspace default locale field, core shell/dashboard labels, Findings/Baseline catalog coverage, notifications, and customer-safe review/report chrome.