From 1fbb20b1abe67d3859342c78500aae8e92fc1518 Mon Sep 17 00:00:00 2001 From: NuklearRabbit <145918611+NuklearRabbit@users.noreply.github.com> Date: Tue, 4 Aug 2026 03:02:50 +0200 Subject: [PATCH] docs: audit remaining Fleet Ops localization gaps Documents every remaining untranslated/incorrect NL/FR string, raw-backend- error call site, over-permissive i18n allowlist entry, the static-greeting bug, and doc staleness found by a dedicated read-only sweep before any file was touched, per the Fleet Ops final localization brief. --- docs/fleet-ops-final-localization/audit.md | 130 +++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 docs/fleet-ops-final-localization/audit.md diff --git a/docs/fleet-ops-final-localization/audit.md b/docs/fleet-ops-final-localization/audit.md new file mode 100644 index 0000000..6a083c1 --- /dev/null +++ b/docs/fleet-ops-final-localization/audit.md @@ -0,0 +1,130 @@ +# Fleet Ops final localization — gap audit + +Branch: `fix/fleet-ops-final-i18n-ux` (created from `master` @ `f7805579f7c73bd3085d73a725fa985b4a4892ed`, +working tree clean at audit time). Deployed revision on Unraid at audit time: `de0bdea84fea01b4501deb7099107bc753c2e6d7` +(the merge commit; `f780557` is an evidence-only commit not separately deployed). Both containers healthy. + +## 1. Remaining untranslated/incorrect text + +### nl-BE +| File | Key | Current | Fix | +|---|---|---|---| +| `audit.json` | `title` | "Audit trail" | "Auditgeschiedenis" | +| `audit.json` | `columns.actor` | "Actor" | "Uitvoerder" | +| `navigation.json` | `items.audit` | "Audit trail" | "Auditgeschiedenis" | +| `auth.json` | `exploreAsOperationsManager` | "Verken als Operations Manager" | "Verken als Operationsmanager" | +| `auth.json` | `exploreAsRentalEmployee` | "Verken als Rental Employee" | "Verken als Verhuurmedewerker" | +| `auth.json` | `roleOperationsManager` | "Operations manager" | "Operationsmanager" | +| `auth.json` | `roleRentalEmployee` | "Rental employee" | "Verhuurmedewerker" | +| `demo.json` | `scenarios.roles.operations_manager` | "Operations Manager" | "Operationsmanager" | +| `demo.json` | `scenarios.roles.rental_employee` | "Rental Employee" | "Verhuurmedewerker" | +| `demo.json` | `scenarios.startScenario` | "Start scenario" | "Scenario starten" | +| `integrations.json` | `ledger.filterRecent` | "Recent" | "Recentste" | +| `quality.json` | `list.statusOpen` | "Open" | "Openstaand" | +| `audit.json`, `quality.json`, `integrations.json`, `demo.json` | 8 `managerOnly`/`whyItMatters`/`scopeBody`/etc. keys (16 nl+fr occurrences) | embedded "Operations Manager(s)" mid-sentence | "Operationsmanager(s)" | + +`columns.details` ("Details") judged fine as-is: short data-table column header, genuine NL/EN cognate, +siblings are single-word labels too. + +### fr-BE +Same key set as nl-BE (role labels + embedded mentions), fr-BE `columns.actor` is already correctly +"Acteur" (no fix needed). French role translations: "Responsable des opérations" / +"Collaborateur de location", per the correction brief. + +### Hardcoded JSX (bypasses i18n entirely) +`frontend/src/pages/DataQualityIssueDetail.tsx` line ~213: `data-label="Field"` — literal English, +never localized. Fix: reuse the already-existing, already-translated +`detail.duplicateCustomer.fieldColumn` key (same table's `` seven lines above already uses it +correctly) — zero locale-file changes needed, pure JSX fix. + +No other hardcoded `data-label`/`aria-label`/`title`/`placeholder` found across `frontend/src/**/*.tsx`. + +## 2. Raw backend errors shown directly + +13 call sites across 7 files (`Automation.tsx`, `ReturnForm.tsx` ×2, `DataQuality.tsx`, `DemoGuide.tsx`, +`Layout.tsx`, `DataQualityIssueDetail.tsx` ×7, `Knowledge.tsx`) all follow: +`err instanceof ApiError ? err.message : t("some:fallback")` — i.e. the **common** case (a real, +structured `ApiError` from the backend) shows raw, un-localized English `error.message` verbatim; the +translated fallback only fires for network-level failures where no `ApiError` could even be +constructed. One site (`DataQualityIssueDetail.tsx` apply-status handler) already special-cases +`err.code === "RECOMMENDATION_STALE"` inline — this needs migrating into the new central system rather +than staying a one-off. + +Backend `AppError`/`HTTPException` codes found (32 semantic `AppError` codes + generic HTTP-status +fallback codes "401"/"403"/"404"/"422" for plain `HTTPException`s, verified via +`app/main.py`'s `error_body()` envelope — both AppError and HTTPException responses share the same +`{"error": {"code", "message", "correlation_id"}}` shape): + +`BOOKING_NOT_ACTIVE, BOOKING_NOT_FOUND, CONFLICT_STILL_PRESENT, CORRECTED_VALUE_REQUIRED, +CORRECTION_BELOW_CANONICAL, CUSTOMER_NOT_FOUND, EMPTY_VALUE, ENTITY_NOT_FOUND, EVENT_NOT_FOUND, +IDEMPOTENCY_KEY_REUSED, INVALID_BOOKING_REFERENCE, INVALID_BOOKING_STATE, INVALID_EVENT_ID, +INVALID_FIELD, INVALID_FIELD_OVERRIDE, INVALID_IDEMPOTENCY_KEY, INVALID_SURVIVOR, ISSUE_NOT_FOUND, +ISSUE_NOT_OPEN, MANUAL_REVIEW_REQUIRED, NOT_AN_ODOMETER_ISSUE, NOT_AN_OVERLAP_ISSUE, +NOT_A_DUPLICATE_ISSUE, NOT_A_MISSING_FIELD_ISSUE, NOT_A_STATUS_CONFLICT_ISSUE, NOT_RETRYABLE, +NO_CONFLICT_DETECTED, NO_FIELDS_PROVIDED, OVERLAP_STILL_PRESENT, RECOMMENDATION_STALE, +UNAUTHORIZED_SERVICE, UNSUPPORTED_ENTITY, VEHICLE_NOT_FOUND`. + +Only one code (`INVALID_SURVIVOR`) carries structured `details` params; the rest embed specifics only +in the raw English `message` string — so localized messages will be generic per-code (title + +explanation + optional next step), not parameterized with extracted specifics, with the raw string +preserved verbatim under "Technical details". + +`errors.json` namespace already exists (all 3 locales) with 6 generic keys (`generic`, +`workspaceLoadFailed`, `unauthorized`, `forbidden`, `notFound`, `networkUnavailable`) but is not wired +to `ApiError.code` at all — only used as the non-`ApiError` fallback string. + +## 3. i18n allowlist over-permissiveness + +`frontend/e2e/i18n-coverage.spec.ts`'s `IDENTICAL_VALUE_ALLOWLIST` currently contains 4 entries that +must be removed once role labels are translated: `auth.roleOperationsManager`, +`auth.roleRentalEmployee`, `demo.scenarios.roles.operations_manager`, +`demo.scenarios.roles.rental_employee` (comment: "deliberately-untranslated role title" — no longer +true once fixed). `audit.title` and `navigation.items.audit` ("Audit trail" kept as compliance term) +also need removing once translated to "Auditgeschiedenis". + +Remaining ~19 allowlist entries are genuine cognates/proper nouns/templates (verified by the audit +agent against a broad Dutch-word grep of fr-BE — zero Dutch leakage found) and should stay. + +Also noted: the coverage test's identical-value check only catches **whole-string** identity to en-GB, +not **mid-sentence embedded English** (the 16 "Operations Manager(s)" occurrences above) — this is a +real blind spot the new tests (section 8 of the correction brief) need to close with a targeted, +explicit check for known English substrings appearing in nl-BE/fr-BE prose. + +## 4. Dashboard greeting + +No time-of-day logic exists anywhere in the codebase — `dashboard.json`'s `title` key is a **static** +string ("Good morning. Here's the fleet." / "Goedemorgen. Hier is je wagenpark." / "Bonjour. Voici +votre flotte.") shown unconditionally at all times of day, despite implying dynamism. Needs: a central, +testable, clock-injectable greeting function keyed on `Europe/Brussels` wall-clock hour, 4 periods per +the brief, updating on language change and on period rollover while the app stays open. + +## 5. Documentation staleness + +- `PROJECT_STATE.md` "Locked decisions" block: `"Product name: MobilityOps."` and `"PoC only..."` — + predates the Fleet Ops rebrand, contradicts the later (correct) sections of the same file. +- `PROJECT_STATE.md`'s final section header still reads `"...IN PROGRESS on + fix/fleet-ops-i18n-status-flow"` and states `"Not yet merged to master"` / `"Do not claim PASS..."` — + **false**: the merge (`de0bdea`) and final evidence commit (`f780557`) both already exist in git + history, neither is mentioned in the file, and the branch name has moved on to + `fix/fleet-ops-final-i18n-ux`. +- A separate, older `"## Demo productization (in progress, same branch + feat/mobilityops-functional-completion)"` section header was also never marked complete. +- README.md is accurate and current — no fix needed there beyond a version-count refresh after this + round's test additions. +- No false "RAGcore live" / "MCP Hub connected" claims found anywhere — this part is already honest. + +## Plan + +1. Fix the ~10 nl-BE + ~10 fr-BE locale-file translations above (role labels, "Audit trail", "Actor", + "Start scenario", "Recent", "Open", embedded mid-sentence mentions). +2. Fix the one hardcoded `data-label="Field"` JSX bug. +3. Build a central `describeApiError(t, err)` helper + shared rendering component, wire all 13 call + sites through it, with a code→message map covering all 32 backend codes + generic HTTP fallbacks, + raw text demoted to "Technical details". +4. Tighten the allowlist (remove the 6 now-stale entries) and add a targeted embedded-English-substring + test, a `describeApiError` coverage test, and greeting boundary tests. +5. Build the time-of-day greeting function + wire into `Dashboard.tsx`, with matching locale copy for + 4 periods × 3 languages + a localized description line replacing the current static one. +6. Fix `PROJECT_STATE.md` staleness (append a new dated entry, do not rewrite prior entries). +7. Full local validation → clean-checkout drill → deploy fix branch → live validation in 3 languages → + merge to master → redeploy → final evidence.