docs: add i18n design spec (next-intl, FR/EN/ES/IT, URL routing)
This commit is contained in:
@@ -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 `<html lang={locale}>` dynamically. The page `<title>` 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.
|
||||
Reference in New Issue
Block a user