Internationalizacja (i18n)
Storefront obsługuje wielojęzyczność poprzez integrację SDK language store z next-intl. Języki są konfigurowane w panelu admina — lista obsługiwanych lokali jest dynamiczna i pochodzi z backendu.
Architektura
URL (/en/products)
↓
next-intl middleware (proxy.ts) — reads preferred-language cookie
↓
LanguageSyncProvider — syncs URL locale → SDK language store
↓
SDK languageMiddleware — adds X-Language header to GraphQL requests
↓
Backend — returns data in requested language
Kluczowa zasada: URL jest single source of truth dla aktywnego języka.
Konfiguracja
Zmienne środowiskowe
| Zmienna | Opis | Domyślna |
|---|---|---|
NEXT_PUBLIC_DEFAULT_LOCALE | Domyślny locale (ukryty w URL przy as-needed prefix) | 'pl' |
defaultLocale jest statyczny (env var). Zmiana wymaga aktualizacji .env.local i restart dev servera. Natomiast lista supportedLanguages (dodawanie/usuwanie lokali) jest dynamiczna — działa natychmiast bez restartu.
Cookie
| Cookie | Czas życia | Opis |
|---|---|---|
preferred-language | 1 rok | Ujednolicony cookie — obsługuje zarówno next-intl (locale detection/routing) jak i SDK (X-Language header) |
Stała cookie jest eksportowana z SDK:
import { LANGUAGE_COOKIE_NAME } from '@doswiftly/storefront-sdk';
// → 'preferred-language'
SDK Language Store
API
import { useLanguageStore, useLanguageStoreApi } from '@doswiftly/storefront-sdk/react';
// Odczyt stanu (z re-renderem)
const language = useLanguageStore((s) => s.language); // 'pl' | 'en' | null
const defaultLang = useLanguageStore((s) => s.defaultLanguage); // 'pl' | null
const supported = useLanguageStore((s) => s.supportedLanguages); // ['pl', 'en', 'de']
const isLoaded = useLanguageStore((s) => s.isLoaded); // boolean
// Zmiana języka (w callbackach)
const api = useLanguageStoreApi();
api.getState().setLanguage('en');
Selektory
import {
selectLanguage,
selectDefaultLanguage,
selectSupportedLanguages,
selectLanguageIsLoaded,
} from '@doswiftly/storefront-sdk/react';
const language = useLanguageStore(selectLanguage);
Inicjalizacja
Store jest automatycznie inicjalizowany przez LanguageProvider (wewnątrz StorefrontProvider). Dane pochodzą z Shop query (defaultLanguage, supportedLanguages).
Priorytet wyboru języka przy inicjalizacji:
initialLanguageprop (z URL locale, server-side)preferred-languagecookie (walidowany vs supported)shop.defaultLanguage(fallback)
StorefrontProvider — prop initialLanguage
// app/[locale]/layout.tsx
import { StorefrontProvider } from '@doswiftly/storefront-sdk/react';
export default async function Layout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
const shopData = await fetchShop();
return (
<StorefrontProvider
config={{ apiUrl: '...', shopSlug: '...' }}
shopData={shopData}
initialLanguage={locale} // ← eliminuje flash złego języka
>
{children}
</StorefrontProvider>
);
}
LanguageSyncProvider
Template dostarcza LanguageSyncProvider — synchronizuje URL locale (z next-intl) z SDK language store:
// components/providers/language-sync-provider.tsx
"use client";
import { useEffect } from "react";
import { useLanguageStore } from "@doswiftly/storefront-sdk/react";
export function LanguageSyncProvider({ locale, children }) {
const setLanguage = useLanguageStore((s) => s.setLanguage);
useEffect(() => {
setLanguage(locale);
}, [locale, setLanguage]);
return <>{children}</>;
}
Flow: user nawiguje na /en/products → next-intl parsuje locale en → LanguageSyncProvider wywołuje setLanguage('en') → SDK middleware dodaje X-Language: en → backend zwraca dane po angielsku.
LanguageSwitcher
Gotowy komponent w template:
import { useLanguageStore } from "@doswiftly/storefront-sdk/react";
import { useRouter, usePathname } from "@/i18n/navigation";
import { useLocale } from "next-intl";
export function LanguageSwitcher() {
const locale = useLocale();
const router = useRouter();
const pathname = usePathname();
const supportedLanguages = useLanguageStore((s) => s.supportedLanguages);
if (supportedLanguages.length < 2) return null;
const handleLocaleChange = (newLocale: string) => {
router.replace(pathname, { locale: newLocale });
};
// ... render Select z supportedLanguages
}
Komponent automatycznie ukrywa się gdy sklep ma tylko 1 język.
Middleware SDK
languageMiddleware automatycznie dodaje header X-Language do każdego zapytania GraphQL:
import { languageMiddleware } from '@doswiftly/storefront-sdk';
// Middleware pipeline (automatycznie konfigurowany przez StorefrontProvider):
// auth → currency → language → bot-protection → [custom] → retry → timeout → errors
Header NIE jest wysyłany gdy language === null (store jeszcze nie zainicjalizowany) — zapobiega cache'owaniu odpowiedzi w domyślnym języku.
GraphQL — queries językowe
Schema GraphQL zawiera queries languages i translations(input:), ale nie mają one wygenerowanych hooków (brak operacji w storefront-operations/queries.graphql). i18n opiera się na next-intl + message files.
Jeśli potrzebujesz tych danych, użyj bezpośrednio klienta:
const client = useStorefrontClient();
// Lista języków
const { languages } = await client.query(`
query { languages { code nativeName englishName direction isDefault locale } }
`);
// Tłumaczenia
const { translations } = await client.query(`
query($input: TranslationsInput!) {
translations(input: $input) { language namespaces { namespace entries { key value } } }
}
`, { input: { language: 'en', namespaces: ['common'] } });