Files
ipal-kit/docs/i18n.md
T

142 lines
5.1 KiB
Markdown

# i18n
Lokalizacja: konfiguracja locali dla Payload, negocjacja języka
(cookie / Accept-Language / default), budowanie ścieżek locale-aware,
przełączanie języka bez 404.
## Config (payload.config.ts)
```ts
ipalKit({
i18n: {
defaultLocale: 'pl',
locales: [
{ code: 'pl', label: 'Polski' },
{ code: 'en', label: 'English' },
// { code: 'ar', label: 'العربية', rtl: true },
],
// fallback: true, // domyślnie true
},
})
```
Plugin ustawia `config.localization` z tego. Walidacja jest eager (fail-fast
przy starcie): pusta lista, duplikaty kodów, `defaultLocale` spoza listy,
zły format kodu → błąd `[ipal] i18n: ...`.
## Front — helpery
Wszystkie helpery są czyste (przyjmują config jako argument). Trzymaj swój
`i18nConfig` w jednym miejscu i importuj gdzie trzeba.
```ts
import {
getLocaleCodes, getDefaultLocale, isValidLocale, getLocaleDefinition,
negotiateLocale, buildLocalizedPath, switchLocalePath,
getLocalizedSlugs, LOCALE_COOKIE_NAME,
} from '@intecion/ipal-kit'
const config = { defaultLocale: 'pl', locales: [{code:'pl',label:'Polski'},{code:'en',label:'English'}] }
getLocaleCodes(config) // ['pl', 'en']
isValidLocale('de', config) // false
```
### Budowanie ścieżek
```ts
// dokument pobrany z locale:'all' → slug to mapa { pl, en }
const doc = await payload.findByID({ collection: 'pages', id, locale: 'all' })
const slugs = getLocalizedSlugs({ slugField: doc.slug, config })
// { pl: 'o-nas', en: 'about' }
buildLocalizedPath({ slugs, locale: 'en', config }) // '/en/about'
// home slug ('home') zwija się do roota:
buildLocalizedPath({ slugs: { en: 'home' }, locale: 'en', config }) // '/en'
```
### Przełącznik języka (bez 404)
```ts
// /pl/strona-glowna → klik EN → /en/home (albo /en jeśli brak tłumaczenia)
const href = switchLocalePath({ slugs, targetLocale: 'en', config })
router.push(href)
```
`switchLocalePath` nigdy nie zwraca undefined — brak slug w danym locale →
fallback na `/{locale}` (root), zamiast dead-endu na 404.
## Middleware — patrz osobno
Negocjacja locale + redirect na wejściu (`domena.com` → `/pl`) jest w
`@intecion/ipal-kit/next/middleware`. Zobacz [proxy w tej sekcji](#proxy-dawniej-middleware)
niżej.
## Proxy (dawniej middleware)
> **Next 16:** plik nazywa się teraz `proxy.ts`, funkcja `proxy`. Import z
> pluginu (`@intecion/ipal-kit/next/middleware`) bez zmian — to nazwa subpath
> eksportu, niezależna od nazwy pliku projektu. Migracja:
> `npx @next/codemod@canary middleware-to-proxy .`
```ts
// proxy.ts (projekt klienta) — jedyna logika to podłączenie
import { NextResponse } from 'next/server'
import { createLocaleMiddleware, DEFAULT_MIDDLEWARE_MATCHER } from '@intecion/ipal-kit/next/middleware'
const i18nConfig = {
defaultLocale: 'pl',
locales: [{ code: 'pl', label: 'Polski' }, { code: 'en', label: 'English' }],
}
const localeMiddleware = createLocaleMiddleware({ config: i18nConfig })
export function proxy(req) {
const r = localeMiddleware(req)
if (r.type === 'next') return NextResponse.next()
const res = NextResponse.redirect(r.location)
if (r.cookie) res.cookies.set(r.cookie.name, r.cookie.value)
return res
}
export const config = { matcher: DEFAULT_MIDDLEWARE_MATCHER }
```
Zachowanie:
- ścieżka z locale (`/pl/...`) → przepuść
- root albo ścieżka bez locale (`/`, `/o-nas`) → redirect na `/{locale}...`,
locale z: cookie → Accept-Language → default
- wybrany locale zapisany w cookie (`LOCALE_COOKIE_NAME`)
`DEFAULT_MIDDLEWARE_MATCHER` wyklucza `api`, `admin`, `_next`, pliki statyczne.
## Cookie locale a zgoda (RODO)
Wybór języka zapisywany jest w cookie **`NEXT_LOCALE`** (konwencja Next.js —
kompatybilna z innymi bibliotekami i18n, które czytają aktywny locale). Ale
zapis podlega zgodzie: to cookie kategorii **functional**, więc:
- **Zapis TYLKO za zgodą.** Middleware zapisuje `NEXT_LOCALE` jedynie, gdy
użytkownik zgodził się na kategorię functional (`mayPersistLocale` sprawdza
zgodę). Bez zgody język działa per żądanie (negocjacja z Accept-Language),
ale nie jest utrwalany.
- **Sprzątanie po cofnięciu zgody.** Gdy użytkownik cofnie zgodę na functional,
cookie `NEXT_LOCALE` jest usuwane automatycznie (consent zna tę cookie przez
`DEFAULT_COOKIE_MAP` — patrz [consent.md](./consent.md)).
Nazwa cookie to jedna stała `LOCALE_COOKIE_NAME` (`modules/i18n/negotiateLocale`),
propagująca do middleware i sprzątania consent. Można nadpisać w
`createLocaleMiddleware({ cookieName })`, ale domyślnie `NEXT_LOCALE` jest
zalecane (interop).
### Kolejność negocjacji locale
1. Cookie `NEXT_LOCALE` (jeśli jest — czyli był wybór za zgodą)
2. Nagłówek `Accept-Language` (preferencje przeglądarki)
3. `defaultLocale` z konfiguracji
Wejście na `/` → negocjacja → redirect na `/pl` (albo wynik negocjacji).
Zmiana języka (URL `/en` różny od cookie) → zapis nowego wyboru (za zgodą).
> **Migracja ze starej nazwy:** wcześniej cookie nazywało się `ipal-locale`.
> Po zmianie na `NEXT_LOCALE` użytkownicy ze starą cookie przejdą raz ponowną
> negocjację (stara cookie ignorowana). Jednorazowe, bez wpływu na nowe projekty.