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.
8.6 KiB
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 tofix/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
- 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).
- Fix the one hardcoded
data-label="Field"JSX bug. - 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". - Tighten the allowlist (remove the 6 now-stale entries) and add a targeted embedded-English-substring
test, a
describeApiErrorcoverage test, and greeting boundary tests. - 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. - Fix
PROJECT_STATE.mdstaleness (append a new dated entry, do not rewrite prior entries). - Full local validation → clean-checkout drill → deploy fix branch → live validation in 3 languages → merge to master → redeploy → final evidence.