Files
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

131 lines
8.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 `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.