Przejdź do głównej zawartości

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

ZmiennaOpisDomyślna
NEXT_PUBLIC_DEFAULT_LOCALEDomyślny locale (ukryty w URL przy as-needed prefix)'pl'
uwaga

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.

CookieCzas życiaOpis
preferred-language1 rokUjednolicony 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:

  1. initialLanguage prop (z URL locale, server-side)
  2. preferred-language cookie (walidowany vs supported)
  3. 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 enLanguageSyncProvider 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'] } });