Files
MobilityOps/docs/fleet-ops-final-localization/audit.md
T
NuklearRabbit 1fbb20b1ab 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.
2026-08-04 03:02:50 +02:00

8.6 KiB
Raw Blame History

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 <thead> 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 HTTPExceptions, 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.