managarten/docs/optimizable/i18n-migration-inventory.md
Till JS eec369bd04 chore(i18n): add coverage audit + migration inventory
Translation infrastructure (@mana/shared-i18n + svelte-i18n + 35
per-module locale files with ~3500 lines across de/en/it/fr/es) is fully
wired, but 65/78 modules still hardcode German in .svelte templates
rather than calling {$_('module.key')}.

Adds:
  - scripts/audit-i18n-coverage.mjs — scans lib/modules/**/*.svelte for
    hardcoded German keywords (Abbrechen, Speichern, Löschen, etc.) in
    files that don't import $_(). Reports per-module hit counts,
    bucket (FULL/PARTIAL/NONE), and whether the locale file exists.
    Supports --summary and --top N flags.
  - pnpm run audit:i18n-coverage  wires it into the audit:* family
    (reporting only, not a CI gate — existing debt would fail
    validate:all otherwise).
  - docs/optimizable/i18n-migration-inventory.md — priority list,
    per-module workflow, and prevention plan.

Top offenders: broadcast (26 hits), articles (24), events (23),
invoices (22), quiz (20), stretch (20), library (19), profile (17),
skilltree (15, PARTIAL), calendar (14, PARTIAL). Modules without a
locale file (broadcast/articles/events/invoices/…) need the locale
stubs scaffolded first.

Real string migration is per-site careful work (key naming, 5-language
parity, UI visual QA) and is left for per-module follow-up sessions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:16:55 +02:00

3.8 KiB
Raw Blame History

i18n Migration Inventory

Status as of 2026-04-22. Run pnpm run audit:i18n-coverage for current numbers.

The gap

The unified Mana web app has @mana/shared-i18n + svelte-i18n fully wired — per-module translation files live under apps/mana/apps/web/src/lib/i18n/locales/{module}/{de,en,it,fr,es}.json for 35 modules, with ~3500 lines of German and matching English / Italian / French / Spanish. The infrastructure is done.

What's missing is the usage: most module .svelte templates hardcode German strings like "Abbrechen", "+ Neues Mood", "Keine Daten" instead of calling {$_('module.key')}.

Scanning lib/modules/**/*.svelte:

  • FULL (4 modules) — all files import $_() from svelte-i18n
  • PARTIAL (9 modules) — mixed usage
  • NONE (65 modules) — German hardcoded throughout

Why not auto-migrate?

String migration is per-site careful work:

  • pick a key name (or reuse existing one from the locale file)
  • pick the German source text (may differ slightly from existing keys)
  • add key to all 5 language files (de/en/it/fr/es)
  • replace in template, test UI visually
  • tests covering copy may break

It's not a mechanical codemod. Each module is a session of its own.

Priorities

Rank by keyword-hits × user-impact. Run the audit for current numbers:

pnpm run audit:i18n-coverage --summary --top 20

Top offenders (2026-04-22 snapshot):

Rank Module Hits Files Locale? Notes
1 broadcast 26 10 Content broadcast compose + recipient UI
2 articles 24 16 Reader view, highlights, filter labels
3 events 23 12 RSVP, guest list, discovery
4 invoices 22 9 Business-critical: line items, payment states
5 quiz 20 3 Edit + play flows, question types
6 stretch 20 6 Session flows, reminders, routines
7 library 19 11 Book/media tracker: status, filtering
8 profile 17 4 Interview flow, context overview
9 skilltree 15 11 PARTIAL — modals done, ListView hardcoded
10 calendar 14 15 PARTIAL — EventForm done, ListView labels

Already FULL: body, todo, times, and the modules whose keyword count is zero (either trivially small templates or consistent use of $_()).

  1. If no locale file exists — run the skilltree/moodlit layout as a template. Start with: nav.*, common.{save,cancel,delete,confirm}, list.{empty,count,newItem}, create.{title,save,namePlaceholder}, plus module-specific vocabulary.
  2. Extract German strings into de.json — keep them verbatim so existing copy doesn't regress.
  3. Translate to en.json (required for sync across language switch). Keep it/fr/es.json with English fallbacks if translator not available.
  4. Replace in templatesimport { _ } from 'svelte-i18n' at the top, then {$_('module.key')} inline. For attributes: placeholder={$_('module.namePlaceholder')}.
  5. Update i18n/index.ts — add the module to registerLocale() if it isn't already listed (broadcast/articles/events/invoices/quiz/ stretch/library/wishes/guides/habits/dreams/firsts/companion/ ai-missions all need entries).
  6. Re-run the auditpnpm run audit:i18n-coverage --summary — expect the module to move from NONE → FULL.
  7. Manual browser test — the audit is pattern-based; visual QA catches anything it missed.

Prevention (future work)

The audit currently reports only — it doesn't fail CI. A future step could graduate it to a gate that:

  • blocks PRs that add hardcoded German to ListViews already migrated
  • continues to tolerate existing debt until it's cleared

Until then: audit:i18n-coverage is the contract.