Files
HealthLog/docs/audit/v1415-i18n-coverage.md
T
Marc-André BombeckandClaude Opus 4.7 a1cf9bcb37 docs: scrub PII + internal jargon + German leaks from user-facing artifacts
Retroactive cleanup of CHANGELOG.md and docs/audit/v1.4.X-summary.md
files. Removes maintainer's real name, specific health figures tied to
the live tenant, internal release-process jargon, and stray German
strings in English-only release notes. Pre-release PII-grep gate added
to the v1.4.20 release pipeline. No git history rewrite.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:14:28 +02:00

129 lines
6.4 KiB
Markdown
Raw 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.
# v1.4.15 — i18n coverage audit
**Date**: 2026-05-09
**Stage**: C4
**Scope**: every `t("…")` / `useTranslations()` call in `src/`
vs. `messages/en.json` and `messages/de.json`. Goal: ensure every
v1.4.15-introduced key (cleanup A1–A4, B-mobile, B1–B6, C2, C3) has a
non-empty, non-placeholder translation in BOTH locales and follows the
namespace conventions baked in by the Stage 4b admin reorg.
## Method
1. `grep -rh -oE 't\("[a-zA-Z][a-zA-Z0-9_.-]*"' src/` — extract every
call-site key (1318 unique).
2. Walk both JSON files manually (no `JSON.parse`, since duplicate keys
silently shadow earlier values) — same parser used by the existing
`i18n-locale-integrity.test.ts`.
3. Compare:
- **drift**: keys in EN but not DE, or vice versa
- **empty**: `value === ""`
- **placeholders**: `value === keyLastSegment`, or `/\b(TODO|FIXME|XXX|TBD)\b/i`
4. Cross-locale identical strings checked per-namespace — many are
legitimate (brand names, ICU placeholder strings, anglicisms used
in DE) so the heuristic only flagged for review, not auto-fix.
## Findings
| Check | Count | Notes |
| ---------------------------------- | ----: | -------------------------------- |
| Total keys (EN) | 1817 | |
| Total keys (DE) | 1817 | |
| Keys only in EN | 0 | full parity |
| Keys only in DE | 0 | full parity |
| Empty values (EN) | 0 | |
| Empty values (DE) | 0 | |
| TODO/FIXME placeholders (EN) | 0 | |
| TODO/FIXME placeholders (DE) | 0 | |
| Used-in-`src/` but missing in EN | 0 | after excluding allow-list below |
| EN==key (last segment, both EN+DE) | 5 | all legitimate (see §Legitimate) |
### Allow-list — used-key matches that are NOT i18n keys
These appeared in the grep but are deliberately excluded:
- `job.*` — Wide Event names passed to `withBackgroundEvent("job.x")`,
not i18n.
- `scheduler.tick` — Wide Event name.
- `empty.measurements.*` — JSDoc `@example` block in
`src/components/ui/empty-state.tsx`, never executed.
- `settings.testConnection.errors.` — dynamic concat root in
`src/components/settings/test-connection-button.tsx`. Concrete
error-codes (`generic`, `credentials_rejected`, `timeout`, …) are
all present.
- `nope.also.missing`, `telegram.__definitely_missing__` — vitest
fixtures asserting fallback behaviour
(`src/lib/__tests__/server-translator.test.ts`).
### Legitimate EN==DE==key cases
5 entries where both locales return the key's last segment unchanged:
| Path | Value | Reason |
| --------------------------------- | --------- | --------------------------------------------------- |
| `settings.ntfy` | `ntfy` | brand name, not translated |
| `classifications.bp.Optimal` | `Optimal` | identical word in EN and DE |
| `classifications.bp.Normal` | `Normal` | identical |
| `classifications.pulse.Normal` | `Normal` | identical |
| `classifications.bodyFat.Fitness` | `Fitness` | identical (anglicism in DE medical/fitness context) |
A further 25 entries are EN==key but DE has an actual translation
(`classifications.bmi.Underweight` → `Untergewicht`, etc.) — these are
fine.
### v1.4.15 new namespaces — coverage check
| Namespace | Origin | EN keys | DE keys | Drift | Empty |
| ------------------------------- | ---------- | ---------: | ------: | ----: | ----: |
| `admin.section.backups.*` | B1 backups | 35 | 35 | 0 | 0 |
| `achievements.*` | B4 | 106 | 106 | 0 | 0 |
| `onboarding.tour.*` | B5 | 18 | 18 | 0 | 0 |
| `admin.overview.*` | A2 | 16 | 16 | 0 | 0 |
| `settings.notificationStatus.*` | B3 | 13 | 13 | 0 | 0 |
| `settings.integrationStatus.*` | B2 | 6 | 6 | 0 | 0 |
| `doctorReport.*` | B6 | 90 | 90 | 0 | 0 |
| `quickAdd.*` (`common.add` etc) | A3 | (`common`) | — | 0 | 0 |
### Naming consistency
Repo doc note: Stage 4b moved admin keys under
`admin.section.<slug>.*`. Verified all v1.4.15-introduced admin
sections follow that pattern:
```
admin.section.{system-status,general,services,integrations,
feedback,reminders,users,api-tokens,login-overview,
backups,danger-zone}
```
Pre-Phase-4b legacy keys remain at the flat `admin.*` namespace (e.g.
`admin.bugReportRepo`, `admin.userManagement`) — those are out of
scope for this audit (renaming would touch many components for zero
behavioural gain). New stage-B code did NOT introduce inconsistent
flat keys; e.g. B5 used `onboarding.tour.*` (not `tour.*`); B4 used
`achievements.*` (not flat `badges.*`); B1 used `admin.section.backups.*`
(not flat `backups.*`); B2/B3 added `settings.{integrationStatus,notificationStatus}.*`
under the existing `settings` umbrella.
## Outcome
**Zero gaps closed**. The five parallel stage-B work-streams and the
C2/C3 stages each added their own EN+DE translations as part of their
own commits, so by the time C4 ran sequentially after Batch 2 there was
nothing left to back-fill. The audit instead converted to a
quality-assurance sweep: tightening `i18n-locale-integrity.test.ts` to
lock in the non-empty + non-placeholder invariants going forward.
## Test guard
`src/lib/__tests__/i18n-locale-integrity.test.ts` is extended with:
- assertion that no value in either locale file is the empty string
- assertion that no value equals its key's last segment (with an
explicit allow-list for the 5 legitimate cases above)
- non-fatal warning (logged via `console.warn` in CI) for values
containing `\bTODO\b` / `\bFIXME\b` / `\bXXX\b`
These guards prevent regression: any future change that ships a
`"key": ""` or `"bugReport": "bugReport"` value will see CI go red.