From e632608f1ce594102c84cf5640a8a317bf461a31 Mon Sep 17 00:00:00 2001 From: Anthony G <1@anthony.sh> Date: Mon, 10 Aug 2026 15:22:01 +0200 Subject: [PATCH] docs: add i18n design spec (next-intl, FR/EN/ES/IT, URL routing) --- .../specs/2026-08-10-i18n-design.md | 137 ++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-10-i18n-design.md diff --git a/docs/superpowers/specs/2026-08-10-i18n-design.md b/docs/superpowers/specs/2026-08-10-i18n-design.md new file mode 100644 index 0000000..8e58092 --- /dev/null +++ b/docs/superpowers/specs/2026-08-10-i18n-design.md @@ -0,0 +1,137 @@ +# Internationalisation (i18n) — Design Spec + +**Date:** 2026-08-10 +**Locales:** FR, EN, ES, IT +**Library:** next-intl +**Default locale (fallback):** en + +--- + +## Scope + +Add multilingual support to the Ombrora-YTDLP Next.js frontend. The backend (API routes, worker, Prisma) is out of scope — no changes there. + +--- + +## Routing + +All UI pages move under a `[locale]` dynamic segment. Every URL gains a locale prefix. + +| Before | After | +|--------|-------| +| `/` | `/en`, `/fr`, `/es`, `/it` | +| `/status/[uuid]` | `/en/status/[uuid]`, `/fr/status/[uuid]`, etc. | + +A visitor hitting `/` is redirected by the middleware to the detected locale (or `en` if undetectable/unsupported). + +API routes (`/api/*`) are unaffected. + +--- + +## File Structure + +``` +src/ + app/ + [locale]/ + layout.tsx # replaces src/app/layout.tsx + page.tsx # replaces src/app/page.tsx + status/ + [uuid]/ + page.tsx # replaces src/app/status/[uuid]/page.tsx + api/ # unchanged + components/ + SubmitForm.tsx # updated to use useTranslations + StatusView.tsx # updated to use useTranslations + LanguageSwitcher.tsx # new + middleware.ts # new — locale detection + redirect + i18n/ + routing.ts # locales + defaultLocale definition + request.ts # next-intl server config +messages/ + en.json + fr.json + es.json + it.json +``` + +--- + +## Locale Detection (middleware) + +`next-intl/middleware` reads the `Accept-Language` request header and redirects to the best matching locale. If no match among `[fr, en, es, it]`, falls back to `en`. + +The detected locale is stored in the URL path (not a cookie, not a header). Switching language updates the URL. + +--- + +## Translation Files + +One JSON file per locale. All UI strings extracted from `SubmitForm.tsx`, `StatusView.tsx`, and the two page files. + +Keys are namespaced by page/component: + +```json +{ + "home": { + "title": "...", + "urlLabel": "...", + "format": "...", + "quality": "...", + "subtitles": "...", + "advanced": "...", + "submit": "...", + "submitting": "...", + "errorRateLimit": "...", + "errorGeneric": "..." + }, + "status": { + "title": "...", + "notFound": "...", + "loading": "...", + "pending": "...", + "processing": "...", + "polling": "...", + "ready": "...", + "download": "...", + "linkExpires": "...", + "failed": "...", + "deleted": "...", + "unknown": "..." + } +} +``` + +`yt-dlp` error output (`errorMsg`) is not translated — it stays as-is from the process stderr. + +--- + +## LanguageSwitcher Component + +Client component placed in the `[locale]/layout.tsx`, visible on all pages. + +Displays four emoji-flag buttons: 🇫🇷 🇺🇸 🇪🇸 🇮🇹 + +On click, uses `next-intl`'s `useRouter` + `usePathname` to call `router.replace(pathname, { locale: selectedLocale })`, which swaps only the locale prefix while keeping the rest of the path (e.g. `/fr/status/uuid` → `/en/status/uuid`). + +Active locale button is visually distinguished (e.g. opacity or underline). + +--- + +## Metadata & HTML lang + +`[locale]/layout.tsx` sets `` dynamically. The page `` is fetched via `getTranslations` (server-side) and included in the `metadata` export. + +--- + +## Internal Navigation + +`SubmitForm` redirects to `/[locale]/status/[uuid]` on success, preserving the current locale. Uses `next-intl`'s `useRouter` (which is locale-aware) instead of the raw Next.js router. + +--- + +## Constraints + +- `next-intl` is pure JS — no native binaries. Compatible with o2switch shared hosting. +- Must be added to root `package.json` (the single `node_modules` for the whole app per CLAUDE.md constraint). +- No changes to API routes, worker, Prisma schema, or tests.