Add i18n: per-instance participant language + admin UI language toggle #3

Open
opened 2026-05-06 10:20:53 +00:00 by mAi · 0 comments

What

The app currently has hardcoded mixed locales:

  • Admin pages (/, /login, /admin/feedback*): English
  • Participant page (/f/<slug>): German (chrome strings: "Senden", "Absenden", "Fragebogen", "Live-Feedback", "Dein Name (optional)", etc.)

m: "i18n is what we need." We want proper internationalization — the language of any given screen should be controlled, not hardcoded in either tongue.

Scope

A. Participant page — language is per-instance

The author who creates an instance picks its participant-facing language at create-time. The participant page renders chrome strings in that language. Question labels stay author-supplied (already free-form in form_definition).

  • Add feedback_instances.locale TEXT NOT NULL DEFAULT 'de' (or 'en' — m to confirm default; current production has German participant chrome, so 'de' keeps existing behaviour).
  • Locale options for v1: de, en. Easy to extend to fr, nl, es later.
  • Author UI on /admin/feedback/new and the Edit tab on detail page: a small select dropdown labelled "Participant language" / "Teilnehmer-Sprache".
  • /f/<slug> reads instance.locale and renders all chrome strings in that locale.

Admin can switch their UI language (English ↔ German) and the choice persists across sessions.

  • Cookie fdbck-admin-locale=en|de (httpOnly NOT needed — client-readable is fine).
  • Hook in +layout.server.ts reads the cookie and exposes locale in $page.data / locals.
  • A small language switcher in the admin chrome (e.g. top-right of /admin/feedback): a <select> or two icon buttons (🇬🇧 🇩🇪).
  • Default if no cookie: en.

C. String table

A simple JSON-based string table — no i18n library dependency for v1. Two files:

  • src/lib/i18n/admin.ts exporting { en: {...}, de: {...} } keyed by string ID
  • src/lib/i18n/participant.ts likewise

A tiny helper: t(locale, key) that falls back to the source locale (en for admin, de for participant) if a key is missing.

If the table grows past ~200 keys we can swap in svelte-i18n or paraglide later — stay lib-free for v1.

D. Code-side audit

Scan every .svelte file in src/routes/ for hardcoded chrome strings and replace with t('key') calls. Big buckets:

  • src/routes/+page.svelte — landing
  • src/routes/login/+page.svelte
  • src/routes/admin/feedback/+page.svelte — list + new-CTA
  • src/routes/admin/feedback/new/+page.svelte — create form
  • src/routes/admin/feedback/[id]/+page.svelte — detail (tabs, share, edit)
  • src/lib/components/FormBuilder.svelte — admin-side
  • src/lib/components/Results.svelte — admin-side aggregate display
  • src/routes/f/[slug]/+page.svelte — participant chrome (the German strings)

Out of scope

  • Translating user-supplied content (instance title, description, question labels, post bodies, banner copy) — that's authored, not chrome.
  • RTL languages (no Arabic/Hebrew yet).
  • Per-question localisation (a question with label_de + label_en).
  • Translating server error messages (HTTP 4xx/5xx bodies).

Acceptance

  • An author on /admin/feedback/new can pick German or English for the participant page.
  • An admin can switch UI language via a switcher; preference persists.
  • All chrome strings on every screen route through the string table — no hardcoded English or German left in .svelte files.
  • Default participant locale: matches existing prod behaviour (German for older instances; configurable for new ones).
  • Default admin locale: English for new sessions.

How to work

Single branch, multi-commit (string table → admin switcher → participant switcher → audit-and-replace). After merge, smoke all 6 routes in both locales. Probably a half-day's work.

## What The app currently has hardcoded mixed locales: - **Admin pages** (`/`, `/login`, `/admin/feedback*`): English - **Participant page** (`/f/<slug>`): German (chrome strings: "Senden", "Absenden", "Fragebogen", "Live-Feedback", "Dein Name (optional)", etc.) m: "i18n is what we need." We want proper internationalization — the language of any given screen should be controlled, not hardcoded in either tongue. ## Scope ### A. Participant page — language is per-instance The author who creates an instance picks its participant-facing language at create-time. The participant page renders chrome strings in that language. Question labels stay author-supplied (already free-form in `form_definition`). - Add `feedback_instances.locale TEXT NOT NULL DEFAULT 'de'` (or `'en'` — m to confirm default; current production has German participant chrome, so `'de'` keeps existing behaviour). - Locale options for v1: `de`, `en`. Easy to extend to `fr`, `nl`, `es` later. - Author UI on `/admin/feedback/new` and the Edit tab on detail page: a small select dropdown labelled "Participant language" / "Teilnehmer-Sprache". - `/f/<slug>` reads `instance.locale` and renders all chrome strings in that locale. ### B. Admin UI — language is per-admin (cookie-stored preference) Admin can switch their UI language (English ↔ German) and the choice persists across sessions. - Cookie `fdbck-admin-locale=en|de` (httpOnly NOT needed — client-readable is fine). - Hook in `+layout.server.ts` reads the cookie and exposes `locale` in `$page.data` / `locals`. - A small language switcher in the admin chrome (e.g. top-right of `/admin/feedback`): a `<select>` or two icon buttons (🇬🇧 🇩🇪). - Default if no cookie: `en`. ### C. String table A simple JSON-based string table — no i18n library dependency for v1. Two files: - `src/lib/i18n/admin.ts` exporting `{ en: {...}, de: {...} }` keyed by string ID - `src/lib/i18n/participant.ts` likewise A tiny helper: `t(locale, key)` that falls back to the source locale (`en` for admin, `de` for participant) if a key is missing. If the table grows past ~200 keys we can swap in `svelte-i18n` or `paraglide` later — stay lib-free for v1. ### D. Code-side audit Scan every `.svelte` file in `src/routes/` for hardcoded chrome strings and replace with `t('key')` calls. Big buckets: - `src/routes/+page.svelte` — landing - `src/routes/login/+page.svelte` - `src/routes/admin/feedback/+page.svelte` — list + new-CTA - `src/routes/admin/feedback/new/+page.svelte` — create form - `src/routes/admin/feedback/[id]/+page.svelte` — detail (tabs, share, edit) - `src/lib/components/FormBuilder.svelte` — admin-side - `src/lib/components/Results.svelte` — admin-side aggregate display - `src/routes/f/[slug]/+page.svelte` — participant chrome (the German strings) ## Out of scope - Translating user-supplied content (instance title, description, question labels, post bodies, banner copy) — that's authored, not chrome. - RTL languages (no Arabic/Hebrew yet). - Per-question localisation (a question with `label_de` + `label_en`). - Translating server error messages (HTTP 4xx/5xx bodies). ## Acceptance - An author on `/admin/feedback/new` can pick German or English for the participant page. - An admin can switch UI language via a switcher; preference persists. - All chrome strings on every screen route through the string table — no hardcoded English or German left in `.svelte` files. - Default participant locale: matches existing prod behaviour (German for older instances; configurable for new ones). - Default admin locale: English for new sessions. ## How to work Single branch, multi-commit (string table → admin switcher → participant switcher → audit-and-replace). After merge, smoke all 6 routes in both locales. Probably a half-day's work.
m referenced this issue from a commit 2026-05-06 10:31:57 +00:00
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: m/fdbck#3
No description provided.